solverplan.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  * *
3  * Copyright (C) 2007-2012 by Johan De Taeye, frePPLe bvba *
4  * *
5  * This library is free software; you can redistribute it and/or modify it *
6  * under the terms of the GNU Affero General Public License as published *
7  * by the Free Software Foundation; either version 3 of the License, or *
8  * (at your option) any later version. *
9  * *
10  * This library is distributed in the hope that it will be useful, *
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13  * GNU Affero General Public License for more details. *
14  * *
15  * You should have received a copy of the GNU Affero General Public *
16  * License along with this program. *
17  * If not, see <http://www.gnu.org/licenses/>. *
18  * *
19  ***************************************************************************/
20 
21 #define FREPPLE_CORE
22 #include "frepple/solver.h"
23 namespace frepple
24 {
25 
27 
28 
30 {
31  // Initialize only once
32  static bool init = false;
33  if (init)
34  {
35  logger << "Warning: Calling frepple::LibrarySolver::initialize() more "
36  << "than once." << endl;
37  return;
38  }
39  init = true;
40 
41  // Register all classes.
43  throw RuntimeException("Error registering solver_mrp Python type");
44 }
45 
46 
48 {
49  // Initialize the metadata
50  metadata = new MetaClass
51  ("solver","solver_mrp",Object::createString<SolverMRP>,true);
52 
53  // Initialize the Python class
54  FreppleClass<SolverMRP,Solver>::getType().addMethod("solve", solve, METH_VARARGS, "run the solver");
55  FreppleClass<SolverMRP,Solver>::getType().addMethod("commit", commit, METH_NOARGS, "commit the plan changes");
56  FreppleClass<SolverMRP,Solver>::getType().addMethod("rollback", rollback, METH_NOARGS, "rollback the plan changes");
58 }
59 
60 
62 {
63  if (l1->getPriority() != l2->getPriority())
64  return l1->getPriority() < l2->getPriority();
65  else if (l1->getDue() != l2->getDue())
66  return l1->getDue() < l2->getDue();
67  else
68  return l1->getQuantity() < l2->getQuantity();
69 }
70 
71 
73 {
74  // Check
75  if (!demands || !getSolver())
76  throw LogicException("Missing demands or solver.");
77 
78  // Message
80  if (Solver->getLogLevel()>0)
81  logger << "Start solving cluster " << cluster << " at " << Date::now() << endl;
82 
83  // Solve the planning problem
84  try
85  {
86  // TODO Propagate & solve initial shortages in buffers
87 
88  // Sort the demands of this problem.
89  // We use a stable sort to get reproducible results between platforms
90  // and STL implementations.
91  stable_sort(demands->begin(), demands->end(), demand_comparison);
92 
93  // Loop through the list of all demands in this planning problem
94  constrainedPlanning = (Solver->getPlanType() == 1);
95  for (deque<Demand*>::const_iterator i = demands->begin();
96  i != demands->end(); ++i)
97  {
98  CommandManager::Bookmark* topcommand = setBookmark();
99  try
100  {
101  // Delete previous constraints
102  (*i)->getConstraints().clear();
103 
104  // Create a state stack
105  State* mystate = state;
106  push();
107 
108  // Plan the demand
109  logConstraints = (Solver->getPlanType() == 1);
110  try {(*i)->solve(*Solver,this);}
111  catch (...)
112  {
113  while (state > mystate) pop();
114  throw;
115  }
116  while (state > mystate) pop();
117  }
118  catch (...)
119  {
120  // Error message
121  logger << "Error: Caught an exception while solving demand '"
122  << (*i)->getName() << "':" << endl;
123  try {throw;}
124  catch (const bad_exception&) {logger << " bad exception" << endl;}
125  catch (const exception& e) {logger << " " << e.what() << endl;}
126  catch (...) {logger << " Unknown type" << endl;}
127 
128  // Cleaning up
129  rollback(topcommand);
130  }
131  }
132 
133  // Clean the list of demands of this cluster
134  demands->clear();
135 
136  // TODO Solve for safety stock in buffers that haven't been planned by any demand yet.
137 
138  }
139  catch (...)
140  {
141  // We come in this exception handling code only if there is a problem with
142  // with this cluster that goes beyond problems with single orders.
143  // If the problem is with single orders, the exception handling code above
144  // will do a proper rollback.
145 
146  // Error message
147  logger << "Error: Caught an exception while solving cluster "
148  << cluster << ":" << endl;
149  try {throw;}
150  catch (const bad_exception&) {logger << " bad exception" << endl;}
151  catch (const exception& e) {logger << " " << e.what() << endl;}
152  catch (...) {logger << " Unknown type" << endl;}
153 
154  // Clean up the operationplans of this cluster
155  for (Operation::iterator f=Operation::begin(); f!=Operation::end(); ++f)
156  if (f->getCluster() == cluster)
157  f->deleteOperationPlans();
158 
159  // Clean the list of demands of this cluster
160  demands->clear();
161  }
162 
163  // Message
164  if (Solver->getLogLevel()>0)
165  logger << "End solving cluster " << cluster << " at " << Date::now() << endl;
166 }
167 
168 
170 {
171  // Categorize all demands in their cluster
172  for (Demand::iterator i = Demand::begin(); i != Demand::end(); ++i)
173  demands_per_cluster[i->getCluster()].push_back(&*i);
174 
175  // Delete of operationplans of the affected clusters
176  // This deletion is not multi-threaded... But on the other hand we need to
177  // loop through the operations only once (rather than as many times as there
178  // are clusters)
179  // A multi-threaded alternative would be to hash the operations here, and
180  // then delete in each thread.
181  if (getLogLevel()>0) logger << "Deleting previous plan" << endl;
182  for (Operation::iterator e=Operation::begin(); e!=Operation::end(); ++e)
183  // The next if-condition is actually redundant if we plan everything
184  if (demands_per_cluster.find(e->getCluster())!=demands_per_cluster.end())
185  e->deleteOperationPlans();
186 
187  // Count how many clusters we have to plan
188  int cl = demands_per_cluster.size();
189  if (cl<1) return;
190 
191  // Solve in parallel threads.
192  // When not solving in silent and autocommit mode, we only use a single
193  // solver thread.
194  // Otherwise we use as many worker threads as processor cores.
195  ThreadGroup threads;
196  if (getLogLevel()>0 || !getAutocommit())
197  threads.setMaxParallel(1);
198 
199  // Register all clusters to be solved
200  for (classified_demand::iterator j = demands_per_cluster.begin();
201  j != demands_per_cluster.end(); ++j)
202  threads.add(SolverMRPdata::runme, new SolverMRPdata(this, j->first, &(j->second)));
203 
204  // Run the planning command threads and wait for them to exit
205  threads.execute();
206 
207  // @todo Check the resource setups that were broken - needs to be removed
208  for (Resource::iterator gres = Resource::begin(); gres != Resource::end(); ++gres)
209  if (gres->getSetupMatrix()) gres->updateSetups();
210 }
211 
212 
214 {
215  // Writing a reference
216  if (m == REFERENCE)
217  {
218  o->writeElement
220  return;
221  }
222 
223  // Write the complete object
224  if (m != NOHEADER) o->BeginObject
226 
227  // Write the fields
229  if (plantype != 1) o->writeElement(Tags::tag_plantype, plantype);
230  if (iteration_threshold != 1.0)
231  o->writeElement(Tags::tag_iterationthreshold, iteration_threshold);
232  if (iteration_accuracy != 0.01)
233  o->writeElement(Tags::tag_iterationaccuracy, iteration_accuracy);
234  if (!autocommit) o->writeElement(Tags::tag_autocommit, autocommit);
235  if (userexit_flow)
236  o->writeElement(Tags::tag_userexit_flow, static_cast<string>(userexit_flow));
237  if (userexit_demand)
238  o->writeElement(Tags::tag_userexit_demand, static_cast<string>(userexit_demand));
239  if (userexit_buffer)
240  o->writeElement(Tags::tag_userexit_buffer, static_cast<string>(userexit_buffer));
241  if (userexit_resource)
242  o->writeElement(Tags::tag_userexit_resource, static_cast<string>(userexit_resource));
243  if (userexit_operation)
244  o->writeElement(Tags::tag_userexit_operation, static_cast<string>(userexit_operation));
245 
246  // Write the parent class
248 }
249 
250 
251 DECLARE_EXPORT void SolverMRP::endElement(XMLInput& pIn, const Attribute& pAttr, const DataElement& pElement)
252 {
253  if (pAttr.isA(Tags::tag_constraints))
254  setConstraints(pElement.getInt());
255  else if (pAttr.isA(Tags::tag_iterationthreshold))
256  setIterationThreshold(pElement.getDouble());
257  else if (pAttr.isA(Tags::tag_iterationaccuracy))
258  setIterationAccuracy(pElement.getDouble());
259  else if (pAttr.isA(Tags::tag_autocommit))
260  setAutocommit(pElement.getBool());
261  else if (pAttr.isA(Tags::tag_userexit_flow))
262  setUserExitFlow(pElement.getString());
263  else if (pAttr.isA(Tags::tag_userexit_demand))
264  setUserExitDemand(pElement.getString());
265  else if (pAttr.isA(Tags::tag_userexit_buffer))
266  setUserExitBuffer(pElement.getString());
267  else if (pAttr.isA(Tags::tag_userexit_resource))
268  setUserExitResource(pElement.getString());
269  else if (pAttr.isA(Tags::tag_userexit_operation))
270  setUserExitOperation(pElement.getString());
271  else if (pAttr.isA(Tags::tag_plantype))
272  setPlanType(pElement.getInt());
273  else
274  Solver::endElement(pIn, pAttr, pElement);
275 }
276 
277 
279 {
280  if (attr.isA(Tags::tag_constraints))
281  return PythonObject(getConstraints());
286  if (attr.isA(Tags::tag_autocommit))
287  return PythonObject(getAutocommit());
288  if (attr.isA(Tags::tag_userexit_flow))
289  return getUserExitFlow();
290  if (attr.isA(Tags::tag_userexit_demand))
291  return getUserExitDemand();
292  if (attr.isA(Tags::tag_userexit_buffer))
293  return getUserExitBuffer();
295  return getUserExitResource();
297  return getUserExitOperation();
298  if (attr.isA(Tags::tag_plantype))
299  return PythonObject(getPlanType());
300  return Solver::getattro(attr);
301 }
302 
303 
305 {
306  if (attr.isA(Tags::tag_constraints))
307  setConstraints(field.getInt());
308  else if (attr.isA(Tags::tag_iterationthreshold))
310  else if (attr.isA(Tags::tag_iterationaccuracy))
312  else if (attr.isA(Tags::tag_autocommit))
313  setAutocommit(field.getBool());
314  else if (attr.isA(Tags::tag_userexit_flow))
315  setUserExitFlow(field);
316  else if (attr.isA(Tags::tag_userexit_demand))
317  setUserExitDemand(field);
318  else if (attr.isA(Tags::tag_userexit_buffer))
319  setUserExitBuffer(field);
320  else if (attr.isA(Tags::tag_userexit_resource))
321  setUserExitResource(field);
322  else if (attr.isA(Tags::tag_userexit_operation))
323  setUserExitOperation(field);
324  else if (attr.isA(Tags::tag_plantype))
325  setPlanType(field.getInt());
326  else
327  return Solver::setattro(attr, field);
328  return 0;
329 }
330 
331 
332 DECLARE_EXPORT PyObject* SolverMRP::solve(PyObject *self, PyObject *args)
333 {
334  // Parse the argument
335  PyObject *dem = NULL;
336  if (args && !PyArg_ParseTuple(args, "|O:solve", &dem)) return NULL;
337  if (dem && !PyObject_TypeCheck(dem, Demand::metadata->pythonClass))
338  throw DataException("solver argument must be a demand");
339 
340  Py_BEGIN_ALLOW_THREADS // Free Python interpreter for other threads
341  try
342  {
343  SolverMRP* sol = static_cast<SolverMRP*>(self);
344  if (!dem)
345  {
346  // Complete replan
347  sol->setAutocommit(true);
348  sol->solve();
349  }
350  else
351  {
352  // Incrementally plan a single demand
353  sol->setAutocommit(false);
354  sol->commands.sol = sol;
355  static_cast<Demand*>(dem)->solve(*sol, &(sol->commands));
356  }
357  }
358  catch(...)
359  {
360  Py_BLOCK_THREADS;
361  PythonType::evalException();
362  return NULL;
363  }
364  Py_END_ALLOW_THREADS // Reclaim Python interpreter
365  return Py_BuildValue("");
366 }
367 
368 
369 DECLARE_EXPORT PyObject* SolverMRP::commit(PyObject *self, PyObject *args)
370 {
371  Py_BEGIN_ALLOW_THREADS // Free Python interpreter for other threads
372  try
373  {
374  SolverMRP * me = static_cast<SolverMRP*>(self);
375  me->scanExcess(&(me->commands));
376  me->commands.CommandManager::commit();
377  }
378  catch(...)
379  {
380  Py_BLOCK_THREADS;
381  PythonType::evalException();
382  return NULL;
383  }
384  Py_END_ALLOW_THREADS // Reclaim Python interpreter
385  return Py_BuildValue("");
386 }
387 
388 
389 DECLARE_EXPORT PyObject* SolverMRP::rollback(PyObject *self, PyObject *args)
390 {
391  Py_BEGIN_ALLOW_THREADS // Free Python interpreter for other threads
392  try
393  {
394  static_cast<SolverMRP*>(self)->commands.rollback();
395  }
396  catch(...)
397  {
398  Py_BLOCK_THREADS;
399  PythonType::evalException();
400  return NULL;
401  }
402  Py_END_ALLOW_THREADS // Reclaim Python interpreter
403  return Py_BuildValue("");
404 }
405 
406 } // end namespace