operationplan.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 
29 DECLARE_EXPORT unsigned long OperationPlan::counterMin = 1;
30 // The value of the max counter is hard-coded to 2^31 - 1. This value is the
31 // highest positive integer number that can safely be used on 32-bit platforms.
32 // An alternative approach is to use the value ULONG_MAX, but this has the
33 // disadvantage of not being portable across platforms and tools.
34 DECLARE_EXPORT unsigned long OperationPlan::counterMax = 2147483647;
35 
36 
38 {
39  // Initialize the metadata
40  OperationPlan::metacategory = new MetaCategory("operationplan", "operationplans",
42  OperationPlan::metadata = new MetaClass("operationplan", "operationplan");
43 
44  // Initialize the Python type
46  x.setName("operationplan");
47  x.setDoc("frePPLe operationplan");
48  x.supportgetattro();
49  x.supportsetattro();
50  x.supportstr();
52  x.addMethod("toXML", toXML, METH_VARARGS, "return a XML representation");
53  const_cast<MetaClass*>(metadata)->pythonClass = x.type_object();
54  return x.typeReady();
55 }
56 
57 
59 {
60  if (owner)
61  owner->setChanged(b);
62  else
63  {
64  oper->setChanged(b);
65  if (dmd) dmd->setChanged();
66  }
67 }
68 
69 
71 (const MetaClass* cat, const AttributeList& in)
72 {
73  // Pick up the action attribute
74  Action action = MetaClass::decodeAction(in);
75 
76  // Decode the attributes
77  const DataElement* opnameElement = in.get(Tags::tag_operation);
78  if (!*opnameElement && action==ADD)
79  // Operation name required
80  throw DataException("Missing operation attribute");
81  string opname = *opnameElement ? opnameElement->getString() : "";
82 
83  // Decode the operationplan identifier
84  unsigned long id = 0;
85  const DataElement* idfier = in.get(Tags::tag_id);
86  if (*idfier) id = idfier->getUnsignedLong();
87  if (!id && (action==CHANGE || action==REMOVE))
88  // Identifier is required
89  throw DataException("Missing operationplan identifier");
90 
91  // If an identifier is specified, we look up this operation plan
92  OperationPlan* opplan = NULL;
93  if (id)
94  {
95  opplan = OperationPlan::findId(id);
96  if (opplan && !opname.empty()
97  && opplan->getOperation()->getName()==opname)
98  {
99  // Previous and current operations don't match.
100  ostringstream ch;
101  ch << "Operationplan identifier " << id
102  << " defined multiple times with different operations: '"
103  << opplan->getOperation() << "' & '" << opname << "'";
104  throw DataException(ch.str());
105  }
106  }
107 
108  // Execute the proper action
109  switch (action)
110  {
111  case REMOVE:
112  if (opplan)
113  {
114  // Send out the notification to subscribers
115  if (opplan->getType().raiseEvent(opplan, SIG_REMOVE))
116  // Delete it
117  delete opplan;
118  else
119  {
120  // The callbacks disallowed the deletion!
121  ostringstream ch;
122  ch << "Can't delete operationplan with identifier " << id;
123  throw DataException(ch.str());
124  }
125  }
126  else
127  {
128  ostringstream ch;
129  ch << "Operationplan with identifier " << id << " doesn't exist";
130  throw DataException(ch.str());
131  }
132  return NULL;
133  case ADD:
134  if (opplan)
135  {
136  ostringstream ch;
137  ch << "Operationplan with identifier " << id
138  << " already exists and can't be added again";
139  throw DataException(ch.str());
140  }
141  if (opname.empty())
142  throw DataException
143  ("Operation name missing for creating an operationplan");
144  break;
145  case CHANGE:
146  if (!opplan)
147  {
148  ostringstream ch;
149  ch << "Operationplan with identifier " << id << " doesn't exist";
150  throw DataException(ch.str());
151  }
152  break;
153  case ADD_CHANGE: ;
154  }
155 
156  // Return the existing operationplan
157  if (opplan) return opplan;
158 
159  // Create a new operation plan
160  Operation* oper = Operation::find(opname);
161  if (!oper)
162  {
163  // Can't create operationplan because the operation doesn't exist
164  throw DataException("Operation '" + opname + "' doesn't exist");
165  }
166  else
167  {
168  // Create an operationplan
169  opplan = oper->createOperationPlan(0.0,Date::infinitePast,Date::infinitePast,NULL,NULL,id,false);
170  if (!opplan->getType().raiseEvent(opplan, SIG_ADD))
171  {
172  delete opplan;
173  throw DataException("Can't create operationplan");
174  }
175  return opplan;
176  }
177 }
178 
179 
181 {
182  // We are garantueed that there are no operationplans that have an id equal
183  // or higher than the current counter. This is garantueed by the
184  // instantiate() method.
185  if (l >= counterMin && l <= counterMax) return NULL;
186 
187  // Loop through all operationplans.
188  for (OperationPlan::iterator i = begin(); i != end(); ++i)
189  if (i->id == l) return &*i;
190 
191  // This ID was not found
192  return NULL;
193 }
194 
195 
196 DECLARE_EXPORT bool OperationPlan::activate(bool useMinCounter)
197 {
198  // At least a valid operation pointer must exist
199  if (!oper) throw LogicException("Initializing an invalid operationplan");
200 
201  // Avoid zero quantity on top-operationplans
202  if (getQuantity() <= 0.0 && !owner)
203  {
204  delete this;
205  return false;
206  }
207 
208  // Call any operation specific initialisation logic
209  if (!oper->extraInstantiate(this))
210  {
211  delete this;
212  return false;
213  }
214 
215  // Instantiate all suboperationplans as well
216  for (OperationPlan::iterator x(this); x != end(); ++x)
217  x->activate();
218 
219  // Create unique identifier
220  // Having an identifier assigned is an important flag.
221  // Only operation plans with an id :
222  // - can be linked in the global operation plan list.
223  // - can have problems (this results from the previous point).
224  // - can be linked with a demand.
225  // These properties allow us to delete operation plans without an id faster.
226  static Mutex onlyOne;
227  {
228  ScopeMutexLock l(onlyOne); // Need to assure that ids are unique!
229  if (id)
230  {
231  // An identifier was read in from input
232  if (id < counterMin || id > counterMax)
233  {
234  // The assigned id potentially clashes with an existing operationplan.
235  // Check whether it clashes with existing operationplans
236  OperationPlan* opplan = findId(id);
237  if (opplan && opplan->getOperation()!=oper)
238  {
239  ostringstream ch;
240  ch << "Operationplan id " << id
241  << " defined multiple times with different operations: '"
242  << opplan->getOperation() << "' & '" << oper << "'";
243  delete this;
244  throw DataException(ch.str());
245  }
246  }
247  // The new operationplan definately doesn't clash with existing id's.
248  // The counter need updating to garantuee that counter is always
249  // a safe starting point for tagging new operationplans.
250  else if (useMinCounter)
251  counterMin = id+1;
252  else
253  counterMax = id-1;
254  }
255  // Fresh operationplan with blank id
256  else if (useMinCounter)
257  id = counterMin++;
258  else
259  id = counterMax--;
260  // Check whether the counters are still okay
261  if (counterMin >= counterMax)
262  throw RuntimeException("Exhausted the range of available operationplan identifiers");
263  }
264 
265  // Insert into the doubly linked list of operationplans.
267 
268  // If we used the lazy creator, the flow- and loadplans have not been
269  // created yet. We do it now...
270  createFlowLoads();
271 
272  // Mark the operation to detect its problems
273  // Note that a single operationplan thus retriggers the problem computation
274  // for all operationplans of this operation. For models with 1) a large
275  // number of operationplans per operation and 2) very frequent problem
276  // detection, this could constitute a scalability problem. This combination
277  // is expected to be unusual and rare, justifying this design choice.
278  oper->setChanged();
279 
280  // The operationplan is valid
281  return true;
282 }
283 
284 
286 {
287  // Wasn't activated anyway
288  if (!id) return;
289 
290  id = 0;
291 
292  // Delete from the list of deliveries
293  if (dmd) dmd->removeDelivery(this);
294 
295  // Delete from the operationplan list
297 
298  // Mark the operation to detect its problems
299  oper->setChanged();
300 }
301 
302 
304 {
305 
306  // Check if already linked
307  if (prev || oper->first_opplan == this) return;
308 
309  if (!oper->first_opplan)
310  {
311  // First operationplan in the list
312  oper->first_opplan = this;
313  oper->last_opplan = this;
314  }
315  else if (*this < *(oper->first_opplan))
316  {
317  // First in the list
318  next = oper->first_opplan;
319  next->prev = this;
320  oper->first_opplan = this;
321  }
322  else if (*(oper->last_opplan) < *this)
323  {
324  // Last in the list
325  prev = oper->last_opplan;
326  prev->next = this;
327  oper->last_opplan = this;
328  }
329  else
330  {
331  // Insert in the middle of the list
332  OperationPlan *x = oper->last_opplan;
333  OperationPlan *y = NULL;
334  while (!(*x < *this))
335  {
336  y = x;
337  x = x->prev;
338  }
339  next = y;
340  prev = x;
341  if (x) x->next = this;
342  if (y) y->prev = this;
343  }
344 }
345 
346 
348 {
349  if (prev)
350  // In the middle
351  prev->next = next;
352  else if (oper->first_opplan == this)
353  // First opplan in the list of this operation
354  oper->first_opplan = next;
355  if (next)
356  // In the middle
357  next->prev = prev;
358  else if (oper->last_opplan == this)
359  // Last opplan in the list of this operation
360  oper->last_opplan = prev;
361 }
362 
363 
365 {
366  // Check
367  if (!o) throw LogicException("Adding null suboperationplan");
368 
369  // Adding a suboperationplan that was already added
370  if (o->owner == this) return;
371 
372  // Clear the previous owner, if there is one
373  if (o->owner) o->owner->eraseSubOperationPlan(o);
374 
375  // Link in the list, keeping the right ordering
376  if (!firstsubopplan)
377  {
378  // First element
379  firstsubopplan = o;
380  lastsubopplan = o;
381  }
382  else if (firstsubopplan->getOperation() != OperationSetup::setupoperation)
383  {
384  // New head
385  o->nextsubopplan = firstsubopplan;
386  firstsubopplan->prevsubopplan = o;
387  firstsubopplan = o;
388  }
389  else
390  {
391  // Insert right after the setup operationplan
392  OperationPlan *s = firstsubopplan->nextsubopplan;
393  o->nextsubopplan = s;
394  if (s) s->nextsubopplan = o;
395  else lastsubopplan = o;
396  }
397 
398  o->owner = this;
399 
400  // Update the flow and loadplans
401  update();
402 }
403 
404 
406 {
407  // Check
408  if (!o) return;
409 
410  // Adding a suboperationplan that was already added
411  if (o->owner != this)
412  throw LogicException("Operationplan isn't a suboperationplan");
413 
414  // Clear owner field
415  o->owner = NULL;
416 
417  // Remove from the list
418  if (o->prevsubopplan)
419  o->prevsubopplan->nextsubopplan = o->nextsubopplan;
420  else
421  firstsubopplan = o->nextsubopplan;
422  if (o->nextsubopplan)
423  o->nextsubopplan->prevsubopplan = o->prevsubopplan;
424  else
425  lastsubopplan = o->prevsubopplan;
426 };
427 
428 
430 {
431  // Different operations
432  if (oper != a.oper)
433  return *oper < *(a.oper);
434 
435  // Different start date
436  if (dates.getStart() != a.dates.getStart())
437  return dates.getStart() < a.dates.getStart();
438 
439  // Sort based on quantity
440  return quantity >= a.quantity;
441 }
442 
443 
445 {
446  // Has been initialized already, it seems
447  if (firstflowplan || firstloadplan) return;
448 
449  // Create setup suboperationplans and loadplans
450  for (Operation::loadlist::const_iterator g=oper->getLoads().begin();
451  g!=oper->getLoads().end(); ++g)
452  if (!g->getAlternate())
453  {
454  new LoadPlan(this, &*g);
455  if (!g->getSetup().empty() && g->getResource()->getSetupMatrix())
457  1, getDates().getStart(), getDates().getStart(), NULL, this);
458  }
459 
460  // Create flowplans for flows that are not alternates of another one
462  h!=oper->getFlows().end(); ++h)
463  if (!h->getAlternate()) new FlowPlan(this, &*h);
464 }
465 
466 
468 {
469  // If no flowplans and loadplans, the work is already done
470  if (!firstflowplan && !firstloadplan) return;
471 
473  firstflowplan = NULL; // Important to do this before the delete!
475  firstloadplan = NULL; // Important to do this before the delete!
476 
477  // Delete the flowplans
478  while (e != endFlowPlans()) delete &*(e++);
479 
480  // Delete the loadplans (including the setup suboperationplan)
481  while (f != endLoadPlans()) delete &*(f++);
482 }
483 
484 
486 {
487  // Delete the flowplans and loadplan
488  deleteFlowLoads();
489 
490  // Initialize
491  OperationPlan *x = firstsubopplan;
492  firstsubopplan = NULL;
493  lastsubopplan = NULL;
494 
495  // Delete the sub operationplans
496  while (x)
497  {
498  OperationPlan *y = x->nextsubopplan;
499  x->owner = NULL; // Need to clear before destroying the suboperationplan
500  delete x;
501  x = y;
502  }
503 
504  // Delete also the owner
505  if (owner)
506  {
507  const OperationPlan* o = owner;
508  setOwner(NULL);
509  delete o;
510  }
511 
512  // Delete from the list of deliveries
513  if (dmd) dmd->removeDelivery(this);
514 
515  // Delete from the operationplan list
517 }
518 
519 
521 {
522  // Special case: the same owner is set twice
523  if (owner == o) return;
524  // Erase the previous owner if there is one
525  if (owner) owner->eraseSubOperationPlan(this);
526  // Register with the new owner
527  if (o) o->addSubOperationPlan(this);
528 }
529 
530 
532 {
533  // Locked opplans don't move
534  if (getLocked()) return;
535 
536  if (!lastsubopplan || lastsubopplan->getOperation() == OperationSetup::setupoperation)
537  // No sub operationplans
538  oper->setOperationPlanParameters(this,quantity,d,Date::infinitePast);
539  else
540  {
541  // Move all sub-operationplans in an orderly fashion
542  for (OperationPlan* i = firstsubopplan; i; i = i->nextsubopplan)
543  {
544  if (i->getOperation() == OperationSetup::setupoperation) continue;
545  if (i->getDates().getStart() < d)
546  {
547  i->setStart(d);
548  d = i->getDates().getEnd();
549  }
550  else
551  // There is sufficient slack between the suboperationplans
552  break;
553  }
554  }
555 
556  // Update flow and loadplans
557  update();
558 }
559 
560 
562 {
563  // Locked opplans don't move
564  if (getLocked()) return;
565 
566  if (!lastsubopplan || lastsubopplan->getOperation() == OperationSetup::setupoperation)
567  // No sub operationplans
568  oper->setOperationPlanParameters(this,quantity,Date::infinitePast,d);
569  else
570  {
571  // Move all sub-operationplans in an orderly fashion
572  for (OperationPlan* i = lastsubopplan; i; i = i->prevsubopplan)
573  {
574  if (i->getOperation() == OperationSetup::setupoperation) break;
575  if (i->getDates().getEnd() > d)
576  {
577  i->setEnd(d);
578  d = i->getDates().getStart();
579  }
580  else
581  // There is sufficient slack between the suboperationplans
582  break;
583  }
584  }
585 
586  // Update flow and loadplans
587  update();
588 }
589 
590 
591 DECLARE_EXPORT double OperationPlan::setQuantity (double f, bool roundDown, bool upd, bool execute)
592 {
593  // No impact on locked operationplans
594  if (getLocked()) return quantity;
595 
596  // Invalid operationplan: the quantity must be >= 0.
597  if (f < 0)
598  throw DataException("Operationplans can't have negative quantities");
599 
600  // Setting a quantity is only allowed on a top operationplan.
601  // One exception: on alternate operations the sizing on the sub-operations is
602  // respected.
603  if (owner && owner->getOperation()->getType() != *OperationAlternate::metadata)
604  return owner->setQuantity(f,roundDown,upd,execute);
605 
606  // Compute the correct size for the operationplan
607  if (f!=0.0 && getOperation()->getSizeMinimum()>0.0
608  && f < getOperation()->getSizeMinimum())
609  {
610  if (roundDown)
611  {
612  // Smaller than the minimum quantity, rounding down means... nothing
613  if (!execute) return 0.0;
614  quantity = 0.0;
615  // Update the flow and loadplans, and mark for problem detection
616  if (upd) update();
617  // Update the parent of an alternate operationplan
618  if (owner && owner->getOperation()->getType() == *OperationAlternate::metadata)
619  {
620  owner->quantity = 0.0;
621  if (upd) owner->resizeFlowLoadPlans();
622  }
623  return 0.0;
624  }
625  f = getOperation()->getSizeMinimum();
626  }
627  if (f != 0.0 && f >= getOperation()->getSizeMaximum())
628  {
629  roundDown = true; // force rounddown to stay below the limit
630  f = getOperation()->getSizeMaximum();
631  }
632  if (f!=0.0 && getOperation()->getSizeMultiple()>0.0)
633  {
634  int mult = static_cast<int> (f / getOperation()->getSizeMultiple()
635  + (roundDown ? 0.0 : 0.99999999));
636  double q = mult * getOperation()->getSizeMultiple();
637  if (mult && (q < getOperation()->getSizeMinimum() || q > getOperation()->getSizeMaximum()))
638  throw DataException("Invalid sizing parameters for operation " + getOperation()->getName());
639  if (!execute) return q;
640  quantity = q;
641  }
642  else
643  {
644  if (!execute) return f;
645  quantity = f;
646  }
647 
648  // Update the parent of an alternate operationplan
649  if (execute && owner
651  {
652  owner->quantity = quantity;
653  if (upd) owner->resizeFlowLoadPlans();
654  }
655 
656  // Apply the same size also to its children
657  if (execute && firstsubopplan)
658  for (OperationPlan *i = firstsubopplan; i; i = i->nextsubopplan)
659  if (i->getOperation() != OperationSetup::setupoperation)
660  {
661  i->quantity = quantity;
662  if (upd) i->resizeFlowLoadPlans();
663  }
664 
665  // Update the flow and loadplans, and mark for problem detection
666  if (upd) update();
667  return quantity;
668 }
669 
670 
671 DECLARE_EXPORT void OperationPlan::resizeFlowLoadPlans()
672 {
673  // Update all flowplans
674  for (FlowPlanIterator ee = beginFlowPlans(); ee != endFlowPlans(); ++ee)
675  ee->update();
676 
677  // Update all loadplans
678  for (LoadPlanIterator e = beginLoadPlans(); e != endLoadPlans(); ++e)
679  e->update();
680 
681  // Align the end of the setup operationplan with the start of the operation
682  if (firstsubopplan && firstsubopplan->getOperation() == OperationSetup::setupoperation
683  && firstsubopplan->getDates().getEnd() != getDates().getStart())
684  firstsubopplan->setEnd(getDates().getStart());
686  && getDates().getEnd() != getOwner()->getDates().getStart())
687  getOwner()->setStart(getDates().getEnd());
688 
689  // Allow the operation length to be changed now that the quantity has changed
690  // Note that we assume that the end date remains fixed. This assumption makes
691  // sense if the operationplan was created to satisfy a demand.
692  // It is not valid though when the purpose of the operationplan was to push
693  // some material downstream.
694 
695  // Resize children
696  for (OperationPlan *j = firstsubopplan; j; j = j->nextsubopplan)
697  if (j->getOperation() != OperationSetup::setupoperation)
698  {
699  j->quantity = quantity;
700  j->resizeFlowLoadPlans();
701  }
702 
703  // Notify the demand of the changed delivery
704  if (dmd) dmd->setChanged();
705 }
706 
707 
708 DECLARE_EXPORT OperationPlan::OperationPlan(const OperationPlan& src, bool init)
709 {
710  if (src.owner)
711  throw LogicException("Can't copy suboperationplans. Copy the owner instead.");
712 
713  // Identifier can't be inherited, but a new one will be generated when we activate the operationplan
714  id = 0;
715 
716  // Copy the fields
717  quantity = src.quantity;
718  flags = src.flags;
719  dmd = src.dmd;
720  oper = src.oper;
721  firstflowplan = NULL;
722  firstloadplan = NULL;
723  dates = src.dates;
724  prev = NULL;
725  next = NULL;
726  owner = NULL;
727  firstsubopplan = NULL;
728  lastsubopplan = NULL;
729  nextsubopplan = NULL;
730  prevsubopplan = NULL;
731  motive = NULL;
733 
734  // Clone the suboperationplans
735  for (OperationPlan::iterator x(&src); x != end(); ++x)
736  new OperationPlan(*x, this);
737 
738  // Activate
739  if (init) activate();
740 }
741 
742 
743 DECLARE_EXPORT OperationPlan::OperationPlan(const OperationPlan& src,
744  OperationPlan* newOwner)
745 {
746  if (!newOwner)
747  throw LogicException("No new owner passed in private copy constructor.");
748 
749  // Identifier can't be inherited, but a new one will be generated when we activate the operationplan
750  id = 0;
751 
752  // Copy the fields
753  quantity = src.quantity;
754  flags = src.flags;
755  dmd = src.dmd;
756  oper = src.oper;
757  firstflowplan = NULL;
758  firstloadplan = NULL;
759  dates = src.dates;
760  prev = NULL;
761  next = NULL;
762  owner = NULL;
763  firstsubopplan = NULL;
764  lastsubopplan = NULL;
765  nextsubopplan = NULL;
766  prevsubopplan = NULL;
767  motive = NULL;
769 
770  // Set owner of a
771  setOwner(newOwner);
772 
773  // Clone the suboperationplans
774  for (OperationPlan::iterator x(&src); x != end(); ++x)
775  new OperationPlan(*x, this);
776 }
777 
778 
779 DECLARE_EXPORT void OperationPlan::update()
780 {
781  if (lastsubopplan && lastsubopplan->getOperation() != OperationSetup::setupoperation)
782  {
783  // Inherit the start and end date of the child operationplans
784  OperationPlan *tmp = firstsubopplan;
785  if (tmp->getOperation() == OperationSetup::setupoperation)
786  tmp = tmp->nextsubopplan;
787  dates.setStartAndEnd(
788  tmp->getDates().getStart(),
789  lastsubopplan->getDates().getEnd()
790  );
791  // If at least 1 sub-operationplan is locked, the parent must be locked
792  flags &= ~IS_LOCKED; // Clear is_locked flag
793  for (OperationPlan* i = firstsubopplan; i; i = i->nextsubopplan)
794  if (i->flags & IS_LOCKED)
795  {
796  flags |= IS_LOCKED; // Set is_locked flag
797  break;
798  }
799  }
800 
801  // Update the flow and loadplans
802  resizeFlowLoadPlans();
803 
804  // Notify the owner operationplan
805  if (owner) owner->update();
806 
807  // Mark as changed
808  setChanged();
809 }
810 
811 
813 {
814  if (!o) return;
815  for (OperationPlan *opplan = o->first_opplan; opplan; )
816  {
817  OperationPlan *tmp = opplan;
818  opplan = opplan->next;
819  // Note that the deletion of the operationplan also updates the opplan list
820  if (deleteLockedOpplans || !tmp->getLocked()) delete tmp;
821  }
822 }
823 
824 
826 {
827  double penalty = 0;
829  i != endLoadPlans(); ++i)
830  if (i->isStart() && !i->getLoad()->getSetup().empty() && i->getResource()->getSetupMatrix())
831  {
832  SetupMatrix::Rule *rule = i->getResource()->getSetupMatrix()
833  ->calculateSetup(i->getSetup(false), i->getSetup(true));
834  if (rule) penalty += rule->getCost();
835  }
836  return penalty;
837 }
838 
839 
841 {
842  // Delivery operationplans aren't excess
843  if (getDemand()) return false;
844 
845  // Recursive call for suboperationplans
846  for (OperationPlan* subopplan = firstsubopplan; subopplan; subopplan = subopplan->nextsubopplan)
847  if (!subopplan->isExcess()) return false;
848 
849  // Loop over all producing flowplans
850  bool hasFlowplans = false;
852  i != endFlowPlans(); ++i)
853  {
854  hasFlowplans = true;
855  // Skip consuming flowplans
856  if (i->getQuantity() <= 0) continue;
857 
858  // Find the total produced quantity, including all suboperationplans
859  double prod_qty = i->getQuantity();
860  for (OperationPlan* subopplan = firstsubopplan; subopplan; subopplan = subopplan->nextsubopplan)
861  for (OperationPlan::FlowPlanIterator k = subopplan->beginFlowPlans();
862  k != subopplan->endFlowPlans(); ++k)
863  if (k->getBuffer() == i->getBuffer())
864  prod_qty += k->getQuantity();
865  if (prod_qty <= 0) continue;
866 
867  // Loop over all flowplans in the buffer (starting at the end) and verify
868  // that the onhand is bigger than the flowplan quantity
869  double current_maximum(0.0);
870  double current_minimum(0.0);
871  Buffer::flowplanlist::const_iterator j = i->getBuffer()->getFlowPlans().rbegin();
872  if (!strict && j != i->getBuffer()->getFlowPlans().end())
873  {
874  current_maximum = i->getBuffer()->getFlowPlans().getMax(&*j);
875  current_minimum = i->getBuffer()->getFlowPlans().getMin(&*j);
876  }
877  for (; j != i->getBuffer()->getFlowPlans().end(); --j)
878  {
879  if ( (current_maximum > 0
880  && j->getOnhand() < prod_qty + current_maximum - ROUNDING_ERROR)
881  || j->getOnhand() < prod_qty + current_minimum - ROUNDING_ERROR )
882  return false;
883  if (j->getType() == 4 && !strict) current_maximum = j->getMax(false);
884  if (j->getType() == 3 && !strict) current_minimum = j->getMin(false);
885  if (&*j == &*i) break;
886  }
887  }
888 
889  // Handle operationplan already being deleted by a deleteOperation command
890  if (!hasFlowplans && getOperation()->getFlows().begin() != getOperation()->getFlows().end())
891  return false;
892 
893  // If we remove this operationplan the onhand in all buffers remains positive.
894  return true;
895 }
896 
897 
899 {
900  TimePeriod x;
901  DateRange y = getOperation()->calculateOperationTime(dates.getStart(), dates.getEnd(), &x);
902  return dates.getDuration() - x;
903 }
904 
905 
907 {
908  if (!empty())
909  {
910  o->BeginObject(*c->grouptag);
911  for (iterator i=begin(); i!=end(); ++i)
912  o->writeElement(*c->typetag, *i);
913  o->EndObject(*c->grouptag);
914  }
915 }
916 
917 
919 {
920  // Don't export operationplans of hidden operations
921  if (oper->getHidden()) return;
922 
923  // Writing a reference
924  if (m == REFERENCE)
925  {
926  o->writeElement
927  (tag, Tags::tag_id, id, Tags::tag_operation, oper->getName());
928  return;
929  }
930 
931  // Write the head
932  if (m != NOHEAD && m != NOHEADTAIL)
934 
935  // The demand reference is only valid for delivery operationplans,
936  // and it should only be written if this tag is not being written
937  // as part of a demand+delivery tag.
938  if (dmd && !dynamic_cast<Demand*>(o->getPreviousObject()))
940 
942  o->writeElement(Tags::tag_end, dates.getEnd());
943  o->writeElement(Tags::tag_quantity, quantity);
945  o->writeElement(Tags::tag_owner, owner);
946 
947  // Write out the flowplans and their pegging
948  if (o->getContentType() == XMLOutput::PLANDETAIL)
949  {
951  for (FlowPlanIterator qq = beginFlowPlans(); qq != endFlowPlans(); ++qq)
952  qq->writeElement(o, Tags::tag_flowplan);
954  }
955 
956  // Write the tail
957  if (m != NOHEADTAIL && m != NOTAIL) o->EndObject(tag);
958 }
959 
960 
962 {
963  if (pAttr.isA (Tags::tag_demand))
965  else if (pAttr.isA(Tags::tag_owner))
967  else if (pAttr.isA(Tags::tag_flowplans))
968  pIn.IgnoreElement();
969 }
970 
971 
972 DECLARE_EXPORT void OperationPlan::endElement (XMLInput& pIn, const Attribute& pAttr, const DataElement& pElement)
973 {
974  // Note that the fields have been ordered more or less in the order
975  // of their expected frequency.
976  // Note that id and operation are handled already during the
977  // operationplan creation. They don't need to be handled here...
978  if (pAttr.isA(Tags::tag_quantity))
979  pElement >> quantity;
980  else if (pAttr.isA(Tags::tag_start))
981  dates.setStart(pElement.getDate());
982  else if (pAttr.isA(Tags::tag_end))
983  dates.setEnd(pElement.getDate());
984  else if (pAttr.isA(Tags::tag_owner) && !pIn.isObjectEnd())
985  {
986  OperationPlan* o = dynamic_cast<OperationPlan*>(pIn.getPreviousObject());
987  if (o) setOwner(o);
988  }
989  else if (pIn.isObjectEnd())
990  {
991  // Initialize the operationplan
992  if (!activate())
993  // Initialization failed and the operationplan is deleted
995  }
996  else if (pAttr.isA (Tags::tag_demand))
997  {
998  Demand * d = dynamic_cast<Demand*>(pIn.getPreviousObject());
999  if (d) d->addDelivery(this);
1000  else throw LogicException("Incorrect object type during read operation");
1001  }
1002  else if (pAttr.isA(Tags::tag_locked))
1003  setLocked(pElement.getBool());
1004 }
1005 
1006 
1008 {
1009  if (b)
1010  flags |= IS_LOCKED;
1011  else
1012  flags &= ~IS_LOCKED;
1013  for (OperationPlan *x = firstsubopplan; x; x = x->nextsubopplan)
1014  x->setLocked(b);
1015  update();
1016 }
1017 
1018 
1020 {
1021  // No change
1022  if (l==dmd) return;
1023 
1024  // Unregister from previous demand
1025  if (dmd) dmd->removeDelivery(this);
1026 
1027  // Register at the new demand and mark it changed
1028  dmd = l;
1029  if (l)
1030  {
1031  l->addDelivery(this);
1032  l->setChanged();
1033  }
1034 }
1035 
1036 
1037 PyObject* OperationPlan::create(PyTypeObject* pytype, PyObject* args, PyObject* kwds)
1038 {
1039  try
1040  {
1041  // Find or create the C++ object
1042  PythonAttributeList atts(kwds);
1044  Py_INCREF(x);
1045 
1046  // Iterate over extra keywords, and set attributes. @todo move this responsibility to the readers...
1047  if (x)
1048  {
1049  PyObject *key, *value;
1050  Py_ssize_t pos = 0;
1051  while (PyDict_Next(kwds, &pos, &key, &value))
1052  {
1053  PythonObject field(value);
1054 #if PY_MAJOR_VERSION >= 3
1055  PyObject* key_utf8 = PyUnicode_AsUTF8String(key);
1056  Attribute attr(PyBytes_AsString(key_utf8));
1057  Py_DECREF(key_utf8);
1058 #else
1059  Attribute attr(PyString_AsString(key));
1060 #endif
1061  if (!attr.isA(Tags::tag_operation) && !attr.isA(Tags::tag_id) && !attr.isA(Tags::tag_action))
1062  {
1063  int result = x->setattro(attr, field);
1064  if (result && !PyErr_Occurred())
1065  PyErr_Format(PyExc_AttributeError,
1066 #if PY_MAJOR_VERSION >= 3
1067  "attribute '%S' on '%s' can't be updated",
1068  key, Py_TYPE(x)->tp_name);
1069 #else
1070  "attribute '%s' on '%s' can't be updated",
1071  PyString_AsString(key), Py_TYPE(x)->tp_name);
1072 #endif
1073  }
1074  };
1075  }
1076 
1077  if (x && !static_cast<OperationPlan*>(x)->activate())
1078  {
1079  PyErr_SetString(PythonRuntimeException, "operationplan activation failed");
1080  return NULL;
1081  }
1082  return x;
1083  }
1084  catch (...)
1085  {
1086  PythonType::evalException();
1087  return NULL;
1088  }
1089 }
1090 
1091 
1093 {
1094  if (attr.isA(Tags::tag_id))
1095  return PythonObject(getIdentifier());
1096  if (attr.isA(Tags::tag_operation))
1097  return PythonObject(getOperation());
1098  if (attr.isA(Tags::tag_flowplans))
1099  return new frepple::FlowPlanIterator(this);
1100  if (attr.isA(Tags::tag_loadplans))
1101  return new frepple::LoadPlanIterator(this);
1102  if (attr.isA(Tags::tag_quantity))
1103  return PythonObject(getQuantity());
1104  if (attr.isA(Tags::tag_start))
1105  return PythonObject(getDates().getStart());
1106  if (attr.isA(Tags::tag_end))
1107  return PythonObject(getDates().getEnd());
1108  if (attr.isA(Tags::tag_demand))
1109  return PythonObject(getDemand());
1110  if (attr.isA(Tags::tag_locked))
1111  return PythonObject(getLocked());
1112  if (attr.isA(Tags::tag_owner))
1113  return PythonObject(getOwner());
1114  if (attr.isA(Tags::tag_operationplans))
1115  return new OperationPlanIterator(this);
1116  if (attr.isA(Tags::tag_hidden))
1117  return PythonObject(getHidden());
1118  if (attr.isA(Tags::tag_unavailable))
1119  return PythonObject(getUnavailable());
1120  if (attr.isA(Tags::tag_motive))
1121  {
1122  // Null
1123  if (!getMotive())
1124  {
1125  Py_INCREF(Py_None);
1126  return Py_None;
1127  }
1128 
1129  // Demand
1130  Demand* d = dynamic_cast<Demand*>(getMotive());
1131  if (d) return PythonObject(d);
1132 
1133  // Buffer
1134  Buffer* b = dynamic_cast<Buffer*>(getMotive());
1135  if (b) return PythonObject(b);
1136 
1137  // Resource
1138  Resource* r = dynamic_cast<Resource*>(getMotive());
1139  if (r) return PythonObject(r);
1140 
1141  // Unknown type
1142  PyErr_SetString(PythonLogicException, "Unhandled motive type");
1143  return NULL;
1144  }
1145  return NULL;
1146 }
1147 
1148 
1150 {
1151  if (attr.isA(Tags::tag_quantity))
1152  setQuantity(field.getDouble());
1153  else if (attr.isA(Tags::tag_start))
1154  setStart(field.getDate());
1155  else if (attr.isA(Tags::tag_end))
1156  setEnd(field.getDate());
1157  else if (attr.isA(Tags::tag_locked))
1158  setLocked(field.getBool());
1159  else if (attr.isA(Tags::tag_demand))
1160  {
1161  if (!field.check(Demand::metadata))
1162  {
1163  PyErr_SetString(PythonDataException, "operationplan demand must be of type demand");
1164  return -1;
1165  }
1166  Demand* y = static_cast<Demand*>(static_cast<PyObject*>(field));
1167  setDemand(y);
1168  }
1169  else if (attr.isA(Tags::tag_owner))
1170  {
1171  if (!field.check(OperationPlan::metadata))
1172  {
1173  PyErr_SetString(PythonDataException, "operationplan demand must be of type demand");
1174  return -1;
1175  }
1176  OperationPlan* y = static_cast<OperationPlan*>(static_cast<PyObject*>(field));
1177  setOwner(y);
1178  }
1179  else if (attr.isA(Tags::tag_motive))
1180  {
1181  Plannable* y;
1182  if (static_cast<PyObject*>(field) == Py_None)
1183  y = NULL;
1184  if (field.check(Demand::metadata))
1185  y = static_cast<Demand*>(static_cast<PyObject*>(field));
1186  else if (field.check(Buffer::metadata))
1187  y = static_cast<Buffer*>(static_cast<PyObject*>(field));
1188  else if (field.check(Resource::metadata))
1189  y = static_cast<Resource*>(static_cast<PyObject*>(field));
1190  else
1191  {
1192  PyErr_SetString(PythonDataException, "operationplan motive must be of type demand, buffer or resource");
1193  return -1;
1194  }
1195  setMotive(y);
1196  }
1197  else
1198  return -1;
1199  return 0;
1200 }
1201 
1202 } // end namespace