solverplan.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  * *
3  * Copyright (C) 2007-2013 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 const Keyword tag_iterationthreshold("iterationthreshold");
28 const Keyword tag_iterationaccuracy("iterationaccuracy");
29 const Keyword tag_lazydelay("lazydelay");
30 
31 
33 {
34  // Initialize only once
35  static bool init = false;
36  if (init)
37  {
38  logger << "Warning: Calling frepple::LibrarySolver::initialize() more "
39  << "than once." << endl;
40  return;
41  }
42  init = true;
43 
44  // Register all classes.
46  throw RuntimeException("Error registering solver_mrp Python type");
47 }
48 
49 
51 {
52  // Initialize the metadata
53  metadata = new MetaClass
54  ("solver","solver_mrp",Object::createString<SolverMRP>,true);
55 
56  // Initialize the Python class
57  FreppleClass<SolverMRP,Solver>::getType().addMethod("solve", solve, METH_VARARGS, "run the solver");
58  FreppleClass<SolverMRP,Solver>::getType().addMethod("commit", commit, METH_NOARGS, "commit the plan changes");
59  FreppleClass<SolverMRP,Solver>::getType().addMethod("rollback", rollback, METH_NOARGS, "rollback the plan changes");
61 }
62 
63 
65 {
66  if (l1->getPriority() != l2->getPriority())
67  return l1->getPriority() < l2->getPriority();
68  else if (l1->getDue() != l2->getDue())
69  return l1->getDue() < l2->getDue();
70  else
71  return l1->getQuantity() < l2->getQuantity();
72 }
73 
74 
76 {
77  // Check
78  if (!demands || !getSolver())
79  throw LogicException("Missing demands or solver.");
80 
81  // Message
83  if (Solver->getLogLevel()>0)
84  logger << "Start solving cluster " << cluster << " at " << Date::now() << endl;
85 
86  // Solve the planning problem
87  try
88  {
89  // TODO Propagate & solve initial shortages in buffers
90 
91  // Sort the demands of this problem.
92  // We use a stable sort to get reproducible results between platforms
93  // and STL implementations.
94  stable_sort(demands->begin(), demands->end(), demand_comparison);
95 
96  // Loop through the list of all demands in this planning problem
97  constrainedPlanning = (Solver->getPlanType() == 1);
98  for (deque<Demand*>::const_iterator i = demands->begin();
99  i != demands->end(); ++i)
100  {
101  try
102  {
103  // Plan the demand
104  (*i)->solve(*Solver,this);
105  }
106  catch (...)
107  {
108  // Error message
109  logger << "Error: Caught an exception while solving demand '"
110  << (*i)->getName() << "':" << endl;
111  try {throw;}
112  catch (const bad_exception&) {logger << " bad exception" << endl;}
113  catch (const exception& e) {logger << " " << e.what() << endl;}
114  catch (...) {logger << " Unknown type" << endl;}
115  }
116  }
117 
118  // Clean the list of demands of this cluster
119  demands->clear();
120 
121  // TODO Solve for safety stock in buffers that haven't been planned by any demand yet.
122 
123  }
124  catch (...)
125  {
126  // We come in this exception handling code only if there is a problem with
127  // with this cluster that goes beyond problems with single orders.
128  // If the problem is with single orders, the exception handling code above
129  // will do a proper rollback.
130 
131  // Error message
132  logger << "Error: Caught an exception while solving cluster "
133  << cluster << ":" << endl;
134  try {throw;}
135  catch (const bad_exception&) {logger << " bad exception" << endl;}
136  catch (const exception& e) {logger << " " << e.what() << endl;}
137  catch (...) {logger << " Unknown type" << endl;}
138 
139  // Clean up the operationplans of this cluster
140  for (Operation::iterator f=Operation::begin(); f!=Operation::end(); ++f)
141  if (f->getCluster() == cluster)
142  f->deleteOperationPlans();
143 
144  // Clean the list of demands of this cluster
145  demands->clear();
146  }
147 
148  // Message
149  if (Solver->getLogLevel()>0)
150  logger << "End solving cluster " << cluster << " at " << Date::now() << endl;
151 }
152 
153 
155 {
156  // Categorize all demands in their cluster
157  for (Demand::iterator i = Demand::begin(); i != Demand::end(); ++i)
158  demands_per_cluster[i->getCluster()].push_back(&*i);
159 
160  // Delete of operationplans of the affected clusters
161  // This deletion is not multi-threaded... But on the other hand we need to
162  // loop through the operations only once (rather than as many times as there
163  // are clusters)
164  // A multi-threaded alternative would be to hash the operations here, and
165  // then delete in each thread.
166  if (getLogLevel()>0) logger << "Deleting previous plan" << endl;
167  for (Operation::iterator e=Operation::begin(); e!=Operation::end(); ++e)
168  // The next if-condition is actually redundant if we plan everything
169  if (demands_per_cluster.find(e->getCluster())!=demands_per_cluster.end())
170  e->deleteOperationPlans();
171 
172  // Count how many clusters we have to plan
173  int cl = demands_per_cluster.size();
174  if (cl<1) return;
175 
176  // Solve in parallel threads.
177  // When not solving in silent and autocommit mode, we only use a single
178  // solver thread.
179  // Otherwise we use as many worker threads as processor cores.
180  ThreadGroup threads;
181  if (getLogLevel()>0 || !getAutocommit())
182  threads.setMaxParallel(1);
183 
184  // Register all clusters to be solved
185  for (classified_demand::iterator j = demands_per_cluster.begin();
186  j != demands_per_cluster.end(); ++j)
187  threads.add(SolverMRPdata::runme, new SolverMRPdata(this, j->first, &(j->second)));
188 
189  // Run the planning command threads and wait for them to exit
190  threads.execute();
191 
192  // @todo Check the resource setups that were broken - needs to be removed
193  for (Resource::iterator gres = Resource::begin(); gres != Resource::end(); ++gres)
194  if (gres->getSetupMatrix()) gres->updateSetups();
195 }
196 
197 
199 {
200  // Writing a reference
201  if (m == REFERENCE)
202  {
203  o->writeElement
205  return;
206  }
207 
208  // Write the complete object
209  if (m != NOHEAD && m != NOHEADTAIL) o->BeginObject
211 
212  // Write the fields
214  if (plantype != 1) o->writeElement(Tags::tag_plantype, plantype);
215 
216  // Parameters
217  o->writeElement(tag_iterationthreshold, iteration_threshold);
218  o->writeElement(tag_iterationaccuracy, iteration_accuracy);
219  o->writeElement(tag_lazydelay, lazydelay);
220  o->writeElement(Tags::tag_autocommit, autocommit);
221 
222  // User exit
223  if (userexit_flow)
224  o->writeElement(Tags::tag_userexit_flow, static_cast<string>(userexit_flow));
225  if (userexit_demand)
226  o->writeElement(Tags::tag_userexit_demand, static_cast<string>(userexit_demand));
227  if (userexit_buffer)
228  o->writeElement(Tags::tag_userexit_buffer, static_cast<string>(userexit_buffer));
229  if (userexit_resource)
230  o->writeElement(Tags::tag_userexit_resource, static_cast<string>(userexit_resource));
231  if (userexit_operation)
232  o->writeElement(Tags::tag_userexit_operation, static_cast<string>(userexit_operation));
233 
234  // Write the parent class
235  Solver::writeElement(o, tag, NOHEAD);
236 }
237 
238 
239 DECLARE_EXPORT void SolverMRP::endElement(XMLInput& pIn, const Attribute& pAttr, const DataElement& pElement)
240 {
241  if (pAttr.isA(Tags::tag_constraints))
242  setConstraints(pElement.getInt());
243  else if (pAttr.isA(Tags::tag_autocommit))
244  setAutocommit(pElement.getBool());
245  else if (pAttr.isA(Tags::tag_userexit_flow))
246  setUserExitFlow(pElement.getString());
247  else if (pAttr.isA(Tags::tag_userexit_demand))
248  setUserExitDemand(pElement.getString());
249  else if (pAttr.isA(Tags::tag_userexit_buffer))
250  setUserExitBuffer(pElement.getString());
251  else if (pAttr.isA(Tags::tag_userexit_resource))
252  setUserExitResource(pElement.getString());
253  else if (pAttr.isA(Tags::tag_userexit_operation))
254  setUserExitOperation(pElement.getString());
255  else if (pAttr.isA(Tags::tag_plantype))
256  setPlanType(pElement.getInt());
257  // Less common parameters
258  else if (pAttr.isA(tag_iterationthreshold))
259  setIterationThreshold(pElement.getDouble());
260  else if (pAttr.isA(tag_iterationaccuracy))
261  setIterationAccuracy(pElement.getDouble());
262  else if (pAttr.isA(tag_lazydelay))
263  setLazyDelay(pElement.getTimeperiod());
264  // Default parameters
265  else
266  Solver::endElement(pIn, pAttr, pElement);
267 }
268 
269 
271 {
272  if (attr.isA(Tags::tag_constraints))
273  return PythonObject(getConstraints());
274  if (attr.isA(Tags::tag_autocommit))
275  return PythonObject(getAutocommit());
276  if (attr.isA(Tags::tag_userexit_flow))
277  return getUserExitFlow();
278  if (attr.isA(Tags::tag_userexit_demand))
279  return getUserExitDemand();
280  if (attr.isA(Tags::tag_userexit_buffer))
281  return getUserExitBuffer();
283  return getUserExitResource();
285  return getUserExitOperation();
286  if (attr.isA(Tags::tag_plantype))
287  return PythonObject(getPlanType());
288  // Less common parameters
289  if (attr.isA(tag_iterationthreshold))
291  if (attr.isA(tag_iterationaccuracy))
293  if (attr.isA(tag_lazydelay))
294  return PythonObject(getLazyDelay());
295  // Default parameters
296  return Solver::getattro(attr);
297 }
298 
299 
301 {
302  if (attr.isA(Tags::tag_constraints))
303  setConstraints(field.getInt());
304  else if (attr.isA(Tags::tag_autocommit))
305  setAutocommit(field.getBool());
306  else if (attr.isA(Tags::tag_userexit_flow))
307  setUserExitFlow(field);
308  else if (attr.isA(Tags::tag_userexit_demand))
309  setUserExitDemand(field);
310  else if (attr.isA(Tags::tag_userexit_buffer))
311  setUserExitBuffer(field);
312  else if (attr.isA(Tags::tag_userexit_resource))
313  setUserExitResource(field);
314  else if (attr.isA(Tags::tag_userexit_operation))
315  setUserExitOperation(field);
316  else if (attr.isA(Tags::tag_plantype))
317  setPlanType(field.getInt());
318  // Less common parameters
319  else if (attr.isA(tag_iterationthreshold))
321  else if (attr.isA(tag_iterationaccuracy))
323  else if (attr.isA(tag_lazydelay))
324  setLazyDelay(field.getTimeperiod());
325  // Default parameters
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  {
339  PyErr_SetString(PythonDataException, "solve(d) argument must be a demand");
340  return NULL;
341  }
342 
343  Py_BEGIN_ALLOW_THREADS // Free Python interpreter for other threads
344  try
345  {
346  SolverMRP* sol = static_cast<SolverMRP*>(self);
347  if (!dem)
348  {
349  // Complete replan
350  sol->setAutocommit(true);
351  sol->solve();
352  }
353  else
354  {
355  // Incrementally plan a single demand
356  sol->setAutocommit(false);
357  sol->commands.sol = sol;
358  static_cast<Demand*>(dem)->solve(*sol, &(sol->commands));
359  }
360  }
361  catch(...)
362  {
363  Py_BLOCK_THREADS;
364  PythonType::evalException();
365  return NULL;
366  }
367  Py_END_ALLOW_THREADS // Reclaim Python interpreter
368  return Py_BuildValue("");
369 }
370 
371 
372 DECLARE_EXPORT PyObject* SolverMRP::commit(PyObject *self, PyObject *args)
373 {
374  Py_BEGIN_ALLOW_THREADS // Free Python interpreter for other threads
375  try
376  {
377  SolverMRP * me = static_cast<SolverMRP*>(self);
378  me->scanExcess(&(me->commands));
379  me->commands.CommandManager::commit();
380  }
381  catch(...)
382  {
383  Py_BLOCK_THREADS;
384  PythonType::evalException();
385  return NULL;
386  }
387  Py_END_ALLOW_THREADS // Reclaim Python interpreter
388  return Py_BuildValue("");
389 }
390 
391 
392 DECLARE_EXPORT PyObject* SolverMRP::rollback(PyObject *self, PyObject *args)
393 {
394  Py_BEGIN_ALLOW_THREADS // Free Python interpreter for other threads
395  try
396  {
397  static_cast<SolverMRP*>(self)->commands.rollback();
398  }
399  catch(...)
400  {
401  Py_BLOCK_THREADS;
402  PythonType::evalException();
403  return NULL;
404  }
405  Py_END_ALLOW_THREADS // Reclaim Python interpreter
406  return Py_BuildValue("");
407 }
408 
409 } // end namespace