demand.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/model.h"
23 
24 namespace frepple
25 {
26 
27 template<class Demand> DECLARE_EXPORT Tree utils::HasName<Demand>::st;
30 
31 
33 {
34  // Initialize the metadata
35  metadata = new MetaCategory("demand", "demands", reader, writer);
36 
37  // Initialize the Python class
39 }
40 
41 
43 {
44  // Initialize the metadata
46  "demand",
47  "demand_default",
48  Object::createString<DemandDefault>, true);
49 
50  // Initialize the Python class
52 }
53 
54 
56 {
57  // Reject negative quantities, and no-change updates
58  double delta(f - qty);
59  if (f < 0.0 || fabs(delta)<ROUNDING_ERROR) return;
60 
61  // Update the quantity
62  qty = f;
63  setChanged();
64 }
65 
66 
68 {
69  // Reset the motive on all operationplans marked with this demand.
70  // This loop is linear with the model size. It doesn't scale well, but
71  // deleting a demand is not too common.
72  for (OperationPlan::iterator i; i != OperationPlan::end(); i++)
73  if (i->getMotive() == this) i->setMotive(NULL);
74 
75  // Remove the delivery operationplans
77 }
79 (bool deleteLocked, CommandManager* cmds)
80 {
81  // Delete all delivery operationplans.
82  // Note that an extra loop is used to assure that our iterator doesn't get
83  // invalidated during the deletion.
84  while (true)
85  {
86  // Find a candidate to delete
87  OperationPlan *candidate = NULL;
88  for (OperationPlan_list::iterator i = deli.begin(); i!=deli.end(); ++i)
89  if (deleteLocked || !(*i)->getLocked())
90  {
91  candidate = *i;
92  break;
93  }
94  if (!candidate) break;
95  if (cmds)
96  // Use delete command
97  cmds->add(new CommandDeleteOperationPlan(candidate));
98  else
99  // Delete immediately
100  delete candidate;
101  }
102 
103 
104  // Mark the demand as being changed, so the problems can be redetected
105  setChanged();
106 }
107 
108 
110 {
111  // Valid opplan check
112  if (!o) return;
113 
114  // See if the demand field on the operationplan points to this demand
115  if (o->dmd != this)
116  throw LogicException("Delivery operationplan incorrectly registered");
117 
118  // Remove the reference on the operationplan
119  o->dmd = NULL; // Required to avoid endless loop
120  o->setDemand(NULL);
121 
122  // Find in the list of deliveries
123  OperationPlan_list::iterator j = deli.begin();
124  while (j!=deli.end() && *j!=o) ++j;
125 
126  // Check that the operation is found
127  // It is possible it is not found! This happens if e.g. an operationplan
128  // is created but destroyed again before it is initialized.
129  if (j!=deli.end())
130  {
131  // Remove from the list
132  deli.erase(j);
133  // Mark the demand as being changed, so the problems can be redetected
134  setChanged();
135  }
136 }
137 
138 
140 {
141  // We need to check the sorting order of the list first! It could be disturbed
142  // when operationplans are being moved around.
143  // The sorting routine isn't very efficient, but should suffice since the
144  // list of delivery operationplans is short and isn't expected to be
145  // disturbed very often.
146  for (bool swapped(!deli.empty()); swapped; swapped=false)
147  {
148  OperationPlan_list::iterator j = const_cast<Demand*>(this)->deli.begin();
149  ++j;
150  for (OperationPlan_list::iterator i =
151  const_cast<Demand*>(this)->deli.begin();
152  j!=const_cast<Demand*>(this)->deli.end(); ++j)
153  {
154  if ((*i)->getDates().getEnd() < (*j)->getDates().getEnd())
155  {
156  // Oh yes, the ordering was disrupted indeed...
157  iter_swap(i,j);
158  // The Borland compiler doesn't understand that this variable is used.
159  // It gives a incorrect warning message...
160  swapped = true;
161  break;
162  }
163  ++i;
164  }
165  }
166 
167  return deli;
168 }
169 
170 
172 {
174  return l.empty() ? NULL : *(l.begin());
175 }
176 
177 
179 {
181  OperationPlan *last = NULL;
182  for (Demand::OperationPlan_list::const_iterator i = l.begin(); i!=l.end(); ++i)
183  last = *i;
184  return last;
185 }
186 
187 
189 {
190  // Dummy call to this function
191  if (!o) return;
192 
193  // Check if it is already in the list.
194  // If it is, simply exit the function. No need to give a warning message
195  // since it's harmless.
196  for (OperationPlan_list::iterator i = deli.begin(); i!=deli.end(); ++i)
197  if (*i == o) return;
198 
199  // Add to the list of delivery operationplans. The insertion is such
200  // that the delivery list is sorted in terms of descending end time.
201  // i.e. the opplan with the latest end date is on the front of the list.
202  // Note: We're forcing resorting the deliveries with the getDelivery()
203  // method. Operation plans dates could have changed, thus disturbing the
204  // original order.
205  getDelivery();
206  OperationPlan_list::iterator j = deli.begin();
207  while (j!=deli.end() && (*j)->getDates().getEnd()>o->getDates().getEnd()) ++j;
208  deli.insert(j, o);
209 
210  // Mark the demand as being changed, so the problems can be redetected
211  setChanged();
212 
213  // Create link between operationplan and demand
214  o->setDemand(this);
215 
216  // Check validity of operation being used
217  Operation* tmpOper = getDeliveryOperation();
218  if (tmpOper && tmpOper != o->getOperation())
219  logger << "Warning: Delivery Operation '" << o->getOperation()
220  << "' different than expected '" << tmpOper
221  << "' for demand '" << this << "'" << endl;
222 }
223 
224 
226 {
227  // Operation can be specified on the demand itself,
228  if (oper) return oper;
229  // ... or on the item,
230  if (it) return it->getOperation();
231  // ... or it doesn't exist at all
232  return NULL;
233 }
234 
235 
237 {
238  double delivered(0.0);
239  for (OperationPlan_list::const_iterator i=deli.begin(); i!=deli.end(); ++i)
240  delivered += (*i)->getQuantity();
241  return delivered;
242 }
243 
244 
246 {
247  // Writing a reference
248  if (m == REFERENCE)
249  {
250  o->writeElement(tag, Tags::tag_name, getName());
251  return;
252  }
253 
254  // Write the head
255  if (m != NOHEAD && m != NOHEADTAIL)
257 
258  // Write the fields
259  HasDescription::writeElement(o, tag);
263  Plannable::writeElement(o, tag);
264 
267  o->writeElement(Tags::tag_due, dueDate);
269  if (getMaxLateness() != TimePeriod::MAX)
271  if (getMinShipment() != 1.0)
273 
274  // Write extra plan information
275  if (o->getContentType() == XMLOutput::PLAN
276  || o->getContentType() == XMLOutput::PLANDETAIL)
277  {
278  if (!deli.empty())
279  {
281  for (OperationPlan_list::const_iterator i=deli.begin(); i!=deli.end(); ++i)
284  }
285  bool first = true;
286  for (Problem::const_iterator j = Problem::begin(const_cast<Demand*>(this), true); j!=Problem::end(); ++j)
287  {
288  if (first)
289  {
290  first = false;
292  }
294  }
295  if (!first) o->EndObject(Tags::tag_problems);
296  if (!constraints.empty())
297  {
299  for (Problem::const_iterator i = constraints.begin(); i != constraints.end(); ++i)
302  }
303  }
304 
305  // Write the tail
306  if (m != NOHEADTAIL && m != NOTAIL) o->EndObject(tag);
307 }
308 
309 
311 {
312  if (pAttr.isA (Tags::tag_item))
314  else if (pAttr.isA (Tags::tag_operation))
316  else if (pAttr.isA (Tags::tag_customer))
318  else if (pAttr.isA(Tags::tag_operationplan))
320  else
322 }
323 
324 
325 DECLARE_EXPORT void Demand::endElement(XMLInput& pIn, const Attribute& pAttr, const DataElement& pElement)
326 {
327  if (pAttr.isA (Tags::tag_quantity))
328  setQuantity (pElement.getDouble());
329  else if (pAttr.isA (Tags::tag_priority))
330  setPriority (pElement.getInt());
331  else if (pAttr.isA (Tags::tag_due))
332  setDue(pElement.getDate());
333  else if (pAttr.isA (Tags::tag_operation))
334  {
335  Operation *o = dynamic_cast<Operation*>(pIn.getPreviousObject());
336  if (o) setOperation(o);
337  else throw LogicException("Incorrect object type during read operation");
338  }
339  else if (pAttr.isA (Tags::tag_customer))
340  {
341  Customer *c = dynamic_cast<Customer*>(pIn.getPreviousObject());
342  if (c) setCustomer(c);
343  else throw LogicException("Incorrect object type during read operation");
344  }
345  else if (pAttr.isA (Tags::tag_item))
346  {
347  Item *i = dynamic_cast<Item*>(pIn.getPreviousObject());
348  if (i) setItem(i);
349  else throw LogicException("Incorrect object type during read operation");
350  }
351  else if (pAttr.isA (Tags::tag_maxlateness))
352  setMaxLateness(pElement.getTimeperiod());
353  else if (pAttr.isA (Tags::tag_minshipment))
354  setMinShipment(pElement.getDouble());
355  else if (pAttr.isA(Tags::tag_operationplan))
356  {
357  OperationPlan* opplan
358  = dynamic_cast<OperationPlan*>(pIn.getPreviousObject());
359  if (opplan) addDelivery(opplan);
360  else throw LogicException("Incorrect object type during read operation");
361  }
362  else
363  {
364  Plannable::endElement(pIn, pAttr, pElement);
365  HasDescription::endElement(pIn, pAttr, pElement);
366  HasHierarchy<Demand>::endElement (pIn, pAttr, pElement);
367  }
368 }
369 
370 
372 {
373  if (attr.isA(Tags::tag_name))
374  return PythonObject(getName());
375  if (attr.isA(Tags::tag_quantity))
376  return PythonObject(getQuantity());
377  if (attr.isA(Tags::tag_due))
378  return PythonObject(getDue());
379  if (attr.isA(Tags::tag_priority))
380  return PythonObject(getPriority());
381  if (attr.isA(Tags::tag_owner))
382  return PythonObject(getOwner());
383  if (attr.isA(Tags::tag_item))
384  return PythonObject(getItem());
385  if (attr.isA(Tags::tag_customer))
386  return PythonObject(getCustomer());
387  if (attr.isA(Tags::tag_operation))
388  return PythonObject(getOperation());
389  if (attr.isA(Tags::tag_description))
390  return PythonObject(getDescription());
391  if (attr.isA(Tags::tag_category))
392  return PythonObject(getCategory());
393  if (attr.isA(Tags::tag_subcategory))
394  return PythonObject(getSubCategory());
395  if (attr.isA(Tags::tag_minshipment))
396  return PythonObject(getMinShipment());
397  if (attr.isA(Tags::tag_maxlateness))
398  return PythonObject(getMaxLateness());
399  if (attr.isA(Tags::tag_hidden))
400  return PythonObject(getHidden());
401  if (attr.isA(Tags::tag_operationplans))
402  return new DemandPlanIterator(this);
403  if (attr.isA(Tags::tag_pegging))
404  return new PeggingIterator(this);
405  if (attr.isA(Tags::tag_constraints))
406  return new ProblemIterator(*(constraints.begin()));
407  if (attr.isA(Tags::tag_members))
408  return new DemandIterator(this);
409  return NULL;
410 }
411 
412 
414 {
415  if (attr.isA(Tags::tag_name))
416  setName(field.getString());
417  else if (attr.isA(Tags::tag_priority))
418  setPriority(field.getInt());
419  else if (attr.isA(Tags::tag_quantity))
420  setQuantity(field.getDouble());
421  else if (attr.isA(Tags::tag_due))
422  setDue(field.getDate());
423  else if (attr.isA(Tags::tag_item))
424  {
425  if (!field.check(Item::metadata))
426  {
427  PyErr_SetString(PythonDataException, "demand item must be of type item");
428  return -1;
429  }
430  Item* y = static_cast<Item*>(static_cast<PyObject*>(field));
431  setItem(y);
432  }
433  else if (attr.isA(Tags::tag_customer))
434  {
435  if (!field.check(Customer::metadata))
436  {
437  PyErr_SetString(PythonDataException, "demand customer must be of type customer");
438  return -1;
439  }
440  Customer* y = static_cast<Customer*>(static_cast<PyObject*>(field));
441  setCustomer(y);
442  }
443  else if (attr.isA(Tags::tag_description))
444  setDescription(field.getString());
445  else if (attr.isA(Tags::tag_category))
446  setCategory(field.getString());
447  else if (attr.isA(Tags::tag_subcategory))
448  setSubCategory(field.getString());
449  else if (attr.isA(Tags::tag_minshipment))
450  setMinShipment(field.getDouble());
451  else if (attr.isA(Tags::tag_maxlateness))
452  setMaxLateness(field.getTimeperiod());
453  else if (attr.isA(Tags::tag_owner))
454  {
455  if (!field.check(Demand::metadata))
456  {
457  PyErr_SetString(PythonDataException, "demand owner must be of type demand");
458  return -1;
459  }
460  Demand* y = static_cast<Demand*>(static_cast<PyObject*>(field));
461  setOwner(y);
462  }
463  else if (attr.isA(Tags::tag_operation))
464  {
465  if (!field.check(Operation::metadata))
466  {
467  PyErr_SetString(PythonDataException, "demand operation must be of type operation");
468  return -1;
469  }
470  Operation* y = static_cast<Operation*>(static_cast<PyObject*>(field));
471  setOperation(y);
472  }
473  else if (attr.isA(Tags::tag_hidden))
474  setHidden(field.getBool());
475  else
476  return -1; // Error
477  return 0; // OK
478 }
479 
480 
482 {
483  // Initialize the type
485  x.setName("demandplanIterator");
486  x.setDoc("frePPLe iterator for demand delivery operationplans");
487  x.supportiter();
488  return x.typeReady();
489 }
490 
491 
492 PyObject* DemandPlanIterator::iternext()
493 {
494  if (i == dem->getDelivery().end()) return NULL;
495  PyObject* result = const_cast<OperationPlan*>(&**(i++));
496  Py_INCREF(result);
497  return result;
498 }
499 
500 } // end namespace