operationplan.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/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  // Extra registration step if this is a delivery operation
273  if (getDemand() && getDemand()->getDeliveryOperation() == oper)
274  dmd->addDelivery(this);
275 
276  // Mark the operation to detect its problems
277  // Note that a single operationplan thus retriggers the problem computation
278  // for all operationplans of this operation. For models with 1) a large
279  // number of operationplans per operation and 2) very frequent problem
280  // detection, this could constitute a scalability problem. This combination
281  // is expected to be unusual and rare, justifying this design choice.
282  oper->setChanged();
283 
284  // The operationplan is valid
285  return true;
286 }
287 
288 
290 {
291  // Wasn't activated anyway
292  if (!id) return;
293 
294  id = 0;
295 
296  // Delete from the list of deliveries
297  if (id && dmd) dmd->removeDelivery(this);
298 
299  // Delete from the operationplan list
301 
302  // Mark the operation to detect its problems
303  oper->setChanged();
304 }
305 
306 
308 {
309 
310  // Check if already linked
311  if (prev || oper->first_opplan == this) return;
312 
313  if (!oper->first_opplan)
314  {
315  // First operationplan in the list
316  oper->first_opplan = this;
317  oper->last_opplan = this;
318  }
319  else if (*this < *(oper->first_opplan))
320  {
321  // First in the list
322  next = oper->first_opplan;
323  next->prev = this;
324  oper->first_opplan = this;
325  }
326  else if (*(oper->last_opplan) < *this)
327  {
328  // Last in the list
329  prev = oper->last_opplan;
330  prev->next = this;
331  oper->last_opplan = this;
332  }
333  else
334  {
335  // Insert in the middle of the list
336  OperationPlan *x = oper->last_opplan;
337  OperationPlan *y = NULL;
338  while (!(*x < *this))
339  {
340  y = x;
341  x = x->prev;
342  }
343  next = y;
344  prev = x;
345  if (x) x->next = this;
346  if (y) y->prev = this;
347  }
348 }
349 
350 
352 {
353  if (prev)
354  // In the middle
355  prev->next = next;
356  else if (oper->first_opplan == this)
357  // First opplan in the list of this operation
358  oper->first_opplan = next;
359  if (next)
360  // In the middle
361  next->prev = prev;
362  else if (oper->last_opplan == this)
363  // Last opplan in the list of this operation
364  oper->last_opplan = prev;
365 }
366 
367 
369 {
370  // Check
371  if (!o) throw LogicException("Adding null suboperationplan");
372 
373  // Adding a suboperationplan that was already added
374  if (o->owner == this) return;
375 
376  // Clear the previous owner, if there is one
377  if (o->owner) o->owner->eraseSubOperationPlan(o);
378 
379  // Link in the list, keeping the right ordering
380  if (!firstsubopplan)
381  {
382  // First element
383  firstsubopplan = o;
384  lastsubopplan = o;
385  }
386  else if (firstsubopplan->getOperation() != OperationSetup::setupoperation)
387  {
388  // New head
389  o->nextsubopplan = firstsubopplan;
390  firstsubopplan->prevsubopplan = o;
391  firstsubopplan = o;
392  }
393  else
394  {
395  // Insert right after the setup operationplan
396  OperationPlan *s = firstsubopplan->nextsubopplan;
397  o->nextsubopplan = s;
398  if (s) s->nextsubopplan = o;
399  else lastsubopplan = o;
400  }
401 
402  o->owner = this;
403 
404  // Update the flow and loadplans
405  update();
406 }
407 
408 
410 {
411  // Check
412  if (!o) return;
413 
414  // Adding a suboperationplan that was already added
415  if (o->owner != this)
416  throw LogicException("Operationplan isn't a suboperationplan");
417 
418  // Clear owner field
419  o->owner = NULL;
420 
421  // Remove from the list
422  if (o->prevsubopplan)
423  o->prevsubopplan->nextsubopplan = o->nextsubopplan;
424  else
425  firstsubopplan = o->nextsubopplan;
426  if (o->nextsubopplan)
427  o->nextsubopplan->prevsubopplan = o->prevsubopplan;
428  else
429  lastsubopplan = o->prevsubopplan;
430 };
431 
432 
434 {
435  // Different operations
436  if (oper != a.oper)
437  return *oper < *(a.oper);
438 
439  // Different start date
440  if (dates.getStart() != a.dates.getStart())
441  return dates.getStart() < a.dates.getStart();
442 
443  // Sort based on quantity
444  return quantity >= a.quantity;
445 }
446 
447 
449 {
450  // Has been initialized already, it seems
451  if (firstflowplan || firstloadplan) return;
452 
453  // Create setup suboperationplans and loadplans
454  for (Operation::loadlist::const_iterator g=oper->getLoads().begin();
455  g!=oper->getLoads().end(); ++g)
456  if (!g->getAlternate())
457  {
458  new LoadPlan(this, &*g);
459  if (!g->getSetup().empty() && g->getResource()->getSetupMatrix())
461  1, getDates().getStart(), getDates().getStart(), NULL, this);
462  }
463 
464  // Create flowplans for flows that are not alternates of another one
466  h!=oper->getFlows().end(); ++h)
467  if (!h->getAlternate()) new FlowPlan(this, &*h);
468 }
469 
470 
472 {
473  // If no flowplans and loadplans, the work is already done
474  if (!firstflowplan && !firstloadplan) return;
475 
477  firstflowplan = NULL; // Important to do this before the delete!
479  firstloadplan = NULL; // Important to do this before the delete!
480 
481  // Delete the flowplans
482  while (e != endFlowPlans()) delete &*(e++);
483 
484  // Delete the loadplans (including the setup suboperationplan)
485  while (f != endLoadPlans()) delete &*(f++);
486 }
487 
488 
490 {
491  // Delete the flowplans and loadplan
492  deleteFlowLoads();
493 
494  // Initialize
495  OperationPlan *x = firstsubopplan;
496  firstsubopplan = NULL;
497  lastsubopplan = NULL;
498 
499  // Delete the sub operationplans
500  while (x)
501  {
502  OperationPlan *y = x->nextsubopplan;
503  x->owner = NULL; // Need to clear before destroying the suboperationplan
504  delete x;
505  x = y;
506  }
507 
508  // Delete also the owner
509  if (owner)
510  {
511  const OperationPlan* o = owner;
512  setOwner(NULL);
513  delete o;
514  }
515 
516  // Delete from the list of deliveries
517  if (id && dmd) dmd->removeDelivery(this);
518 
519  // Delete from the operationplan list
521 }
522 
523 
525 {
526  // Special case: the same owner is set twice
527  if (owner == o) return;
528  // Erase the previous owner if there is one
529  if (owner) owner->eraseSubOperationPlan(this);
530  // Register with the new owner
531  if (o) o->addSubOperationPlan(this);
532 }
533 
534 
536 {
537  // Locked opplans don't move
538  if (getLocked()) return;
539 
540  if (!lastsubopplan || lastsubopplan->getOperation() == OperationSetup::setupoperation)
541  // No sub operationplans
542  oper->setOperationPlanParameters(this,quantity,d,Date::infinitePast);
543  else
544  {
545  // Move all sub-operationplans in an orderly fashion
546  for (OperationPlan* i = firstsubopplan; i; i = i->nextsubopplan)
547  {
548  if (i->getOperation() == OperationSetup::setupoperation) continue;
549  if (i->getDates().getStart() < d)
550  {
551  i->setStart(d);
552  d = i->getDates().getEnd();
553  }
554  else
555  // There is sufficient slack between the suboperationplans
556  break;
557  }
558  }
559 
560  // Update flow and loadplans
561  update();
562 }
563 
564 
566 {
567  // Locked opplans don't move
568  if (getLocked()) return;
569 
570  if (!lastsubopplan || lastsubopplan->getOperation() == OperationSetup::setupoperation)
571  // No sub operationplans
572  oper->setOperationPlanParameters(this,quantity,Date::infinitePast,d);
573  else
574  {
575  // Move all sub-operationplans in an orderly fashion
576  for (OperationPlan* i = lastsubopplan; i; i = i->prevsubopplan)
577  {
578  if (i->getOperation() == OperationSetup::setupoperation) break;
579  if (i->getDates().getEnd() > d)
580  {
581  i->setEnd(d);
582  d = i->getDates().getStart();
583  }
584  else
585  // There is sufficient slack between the suboperationplans
586  break;
587  }
588  }
589 
590  // Update flow and loadplans
591  update();
592 }
593 
594 
595 DECLARE_EXPORT double OperationPlan::setQuantity (double f, bool roundDown, bool upd, bool execute)
596 {
597  // No impact on locked operationplans
598  if (getLocked()) return quantity;
599 
600  // Invalid operationplan: the quantity must be >= 0.
601  if (f < 0)
602  throw DataException("Operationplans can't have negative quantities");
603 
604  // Setting a quantity is only allowed on a top operationplan.
605  // One exception: on alternate operations the sizing on the sub-operations is
606  // respected.
607  if (owner && owner->getOperation()->getType() != *OperationAlternate::metadata)
608  return owner->setQuantity(f,roundDown,upd,execute);
609 
610  // Compute the correct size for the operationplan
611  if (f!=0.0 && getOperation()->getSizeMinimum()>0.0
612  && f < getOperation()->getSizeMinimum())
613  {
614  if (roundDown)
615  {
616  // Smaller than the minimum quantity, rounding down means... nothing
617  if (!execute) return 0.0;
618  quantity = 0.0;
619  // Update the flow and loadplans, and mark for problem detection
620  if (upd) update();
621  return 0.0;
622  }
623  f = getOperation()->getSizeMinimum();
624  }
625  if (f != 0.0 && f >= getOperation()->getSizeMaximum())
626  {
627  roundDown = true; // force rounddown to stay below the limit
628  f = getOperation()->getSizeMaximum();
629  }
630  if (f!=0.0 && getOperation()->getSizeMultiple()>0.0)
631  {
632  int mult = static_cast<int> (f / getOperation()->getSizeMultiple()
633  + (roundDown ? 0.0 : 0.99999999));
634  double q = mult * getOperation()->getSizeMultiple();
635  if (q < getOperation()->getSizeMinimum() || q > getOperation()->getSizeMaximum())
636  throw DataException("Invalid sizing parameters for operation " + getOperation()->getName());
637  if (!execute) return q;
638  quantity = q;
639  }
640  else
641  {
642  if (!execute) return f;
643  quantity = f;
644  }
645 
646  // Update the parent of an alternate operationplan
647  if (execute && owner
649  {
650  owner->quantity = quantity;
651  if (upd) owner->resizeFlowLoadPlans();
652  }
653 
654  // Apply the same size also to its children
655  if (execute && firstsubopplan)
656  for (OperationPlan *i = firstsubopplan; i; i = i->nextsubopplan)
657  if (i->getOperation() != OperationSetup::setupoperation)
658  {
659  i->quantity = quantity;
660  if (upd) i->resizeFlowLoadPlans();
661  }
662 
663  // Update the flow and loadplans, and mark for problem detection
664  if (upd) update();
665  return quantity;
666 }
667 
668 
669 DECLARE_EXPORT void OperationPlan::resizeFlowLoadPlans()
670 {
671  // Update all flowplans
672  for (FlowPlanIterator ee = beginFlowPlans(); ee != endFlowPlans(); ++ee)
673  ee->update();
674 
675  // Update all loadplans
676  for (LoadPlanIterator e = beginLoadPlans(); e != endLoadPlans(); ++e)
677  e->update();
678 
679  // Align the end of the setup operationplan with the start of the operation
680  if (firstsubopplan && firstsubopplan->getOperation() == OperationSetup::setupoperation
681  && firstsubopplan->getDates().getEnd() != getDates().getStart())
682  firstsubopplan->setEnd(getDates().getStart());
684  && getDates().getEnd() != getOwner()->getDates().getStart())
685  getOwner()->setStart(getDates().getEnd());
686 
687  // Allow the operation length to be changed now that the quantity has changed
688  // Note that we assume that the end date remains fixed. This assumption makes
689  // sense if the operationplan was created to satisfy a demand.
690  // It is not valid though when the purpose of the operationplan was to push
691  // some material downstream.
692 
693  // Resize children
694  for (OperationPlan *j = firstsubopplan; j; j = j->nextsubopplan)
695  if (j->getOperation() != OperationSetup::setupoperation)
696  {
697  j->quantity = quantity;
698  j->resizeFlowLoadPlans();
699  }
700 
701  // Notify the demand of the changed delivery
702  if (dmd) dmd->setChanged();
703 }
704 
705 
706 DECLARE_EXPORT OperationPlan::OperationPlan(const OperationPlan& src, bool init)
707 {
708  if (src.owner)
709  throw LogicException("Can't copy suboperationplans. Copy the owner instead.");
710 
711  // Identifier can't be inherited, but a new one will be generated when we activate the operationplan
712  id = 0;
713 
714  // Copy the fields
715  quantity = src.quantity;
716  flags = src.flags;
717  dmd = src.dmd;
718  oper = src.oper;
719  firstflowplan = NULL;
720  firstloadplan = NULL;
721  dates = src.dates;
722  prev = NULL;
723  next = NULL;
724  owner = NULL;
725  firstsubopplan = NULL;
726  lastsubopplan = NULL;
727  nextsubopplan = NULL;
728  prevsubopplan = NULL;
729  motive = NULL;
731 
732  // Clone the suboperationplans
733  for (OperationPlan::iterator x(&src); x != end(); ++x)
734  new OperationPlan(*x, this);
735 
736  // Activate
737  if (init) activate();
738 }
739 
740 
741 DECLARE_EXPORT OperationPlan::OperationPlan(const OperationPlan& src,
742  OperationPlan* newOwner)
743 {
744  if (!newOwner)
745  throw LogicException("No new owner passed in private copy constructor.");
746 
747  // Identifier can't be inherited, but a new one will be generated when we activate the operationplan
748  id = 0;
749 
750  // Copy the fields
751  quantity = src.quantity;
752  flags = src.flags;
753  dmd = src.dmd;
754  oper = src.oper;
755  firstflowplan = NULL;
756  firstloadplan = NULL;
757  dates = src.dates;
758  prev = NULL;
759  next = NULL;
760  owner = NULL;
761  firstsubopplan = NULL;
762  lastsubopplan = NULL;
763  nextsubopplan = NULL;
764  prevsubopplan = NULL;
765  motive = NULL;
767 
768  // Set owner of a
769  setOwner(newOwner);
770 
771  // Clone the suboperationplans
772  for (OperationPlan::iterator x(&src); x != end(); ++x)
773  new OperationPlan(*x, this);
774 }
775 
776 
777 DECLARE_EXPORT void OperationPlan::update()
778 {
779  if (lastsubopplan && lastsubopplan->getOperation() != OperationSetup::setupoperation)
780  {
781  // Inherit the start and end date of the child operationplans
782  OperationPlan *tmp = firstsubopplan;
783  if (tmp->getOperation() == OperationSetup::setupoperation)
784  tmp = tmp->nextsubopplan;
785  dates.setStartAndEnd(
786  tmp->getDates().getStart(),
787  lastsubopplan->getDates().getEnd()
788  );
789  // If at least 1 sub-operationplan is locked, the parent must be locked
790  flags &= ~IS_LOCKED; // Clear is_locked flag
791  for (OperationPlan* i = firstsubopplan; i; i = i->nextsubopplan)
792  if (i->flags & IS_LOCKED)
793  {
794  flags |= IS_LOCKED; // Set is_locked flag
795  break;
796  }
797  }
798 
799  // Update the flow and loadplans
800  resizeFlowLoadPlans();
801 
802  // Notify the owner operationplan
803  if (owner) owner->update();
804 
805  // Mark as changed
806  setChanged();
807 }
808 
809 
811 {
812  if (!o) return;
813  for (OperationPlan *opplan = o->first_opplan; opplan; )
814  {
815  OperationPlan *tmp = opplan;
816  opplan = opplan->next;
817  // Note that the deletion of the operationplan also updates the opplan list
818  if (deleteLockedOpplans || !tmp->getLocked()) delete tmp;
819  }
820 }
821 
822 
824 {
825  double penalty = 0;
827  i != endLoadPlans(); ++i)
828  if (i->isStart() && !i->getLoad()->getSetup().empty() && i->getResource()->getSetupMatrix())
829  {
830  SetupMatrix::Rule *rule = i->getResource()->getSetupMatrix()
831  ->calculateSetup(i->getSetup(false), i->getSetup(true));
832  if (rule) penalty += rule->getCost();
833  }
834  return penalty;
835 }
836 
837 
839 {
840  // Delivery operationplans aren't excess
841  if (getDemand()) return false;
842 
843  // Recursive call for suboperationplans
844  for (OperationPlan* subopplan = firstsubopplan; subopplan; subopplan = subopplan->nextsubopplan)
845  if (!subopplan->isExcess()) return false;
846 
847  // Loop over all producing flowplans
849  i != endFlowPlans(); ++i)
850  {
851  // Skip consuming flowplans
852  if (i->getQuantity() <= 0) continue;
853 
854  // Loop over all flowplans in the buffer (starting at the end) and verify
855  // that the onhand is bigger than the flowplan quantity
856  double current_maximum(0.0);
857  double current_minimum(0.0);
858  Buffer::flowplanlist::const_iterator j = i->getBuffer()->getFlowPlans().rbegin();
859  if (!strict && j != i->getBuffer()->getFlowPlans().end())
860  {
861  current_maximum = i->getBuffer()->getFlowPlans().getMax(&*j);
862  current_minimum = i->getBuffer()->getFlowPlans().getMin(&*j);
863  }
864  for (; j != i->getBuffer()->getFlowPlans().end(); --j)
865  {
866  if ( (current_maximum > 0
867  && j->getOnhand() < i->getQuantity() + current_maximum - ROUNDING_ERROR)
868  || j->getOnhand() < i->getQuantity() + current_minimum - ROUNDING_ERROR )
869  return false;
870  if (j->getType() == 4 && !strict) current_maximum = j->getMax(false);
871  if (j->getType() == 3 && !strict) current_minimum = j->getMin(false);
872  if (&*j == &*i) break;
873  }
874  }
875 
876  // If we remove this operationplan the onhand in all buffers remains positive.
877  return true;
878 }
879 
880 
882 {
883  TimePeriod x;
884  DateRange y = getOperation()->calculateOperationTime(dates.getStart(), dates.getEnd(), &x);
885  return dates.getDuration() - x;
886 }
887 
888 
890 {
891  if (!empty())
892  {
893  o->BeginObject(*c->grouptag);
894  for (iterator i=begin(); i!=end(); ++i)
895  o->writeElement(*c->typetag, *i);
896  o->EndObject(*c->grouptag);
897  }
898 }
899 
900 
902 {
903  // Don't export operationplans of hidden operations
904  if (oper->getHidden()) return;
905 
906  // Writing a reference
907  if (m == REFERENCE)
908  {
909  o->writeElement
910  (tag, Tags::tag_id, id, Tags::tag_operation, oper->getName());
911  return;
912  }
913 
914  if (m != NOHEADER)
916 
917  // The demand reference is only valid for delivery operationplans,
918  // and it should only be written if this tag is not being written
919  // as part of a demand+delivery tag.
920  if (dmd && !dynamic_cast<Demand*>(o->getPreviousObject()))
922 
924  o->writeElement(Tags::tag_end, dates.getEnd());
925  o->writeElement(Tags::tag_quantity, quantity);
927  o->writeElement(Tags::tag_owner, owner);
928 
929  // Write out the flowplans and their pegging
930  if (o->getContentType() == XMLOutput::PLANDETAIL)
931  {
933  for (FlowPlanIterator qq = beginFlowPlans(); qq != endFlowPlans(); ++qq)
934  qq->writeElement(o, Tags::tag_flowplan);
936  }
937 
938  o->EndObject(tag);
939 }
940 
941 
943 {
944  if (pAttr.isA (Tags::tag_demand))
946  else if (pAttr.isA(Tags::tag_owner))
948  else if (pAttr.isA(Tags::tag_flowplans))
949  pIn.IgnoreElement();
950 }
951 
952 
953 DECLARE_EXPORT void OperationPlan::endElement (XMLInput& pIn, const Attribute& pAttr, const DataElement& pElement)
954 {
955  // Note that the fields have been ordered more or less in the order
956  // of their expected frequency.
957  // Note that id and operation are handled already during the
958  // operationplan creation. They don't need to be handled here...
959  if (pAttr.isA(Tags::tag_quantity))
960  pElement >> quantity;
961  else if (pAttr.isA(Tags::tag_start))
962  dates.setStart(pElement.getDate());
963  else if (pAttr.isA(Tags::tag_end))
964  dates.setEnd(pElement.getDate());
965  else if (pAttr.isA(Tags::tag_owner) && !pIn.isObjectEnd())
966  {
967  OperationPlan* o = dynamic_cast<OperationPlan*>(pIn.getPreviousObject());
968  if (o) setOwner(o);
969  }
970  else if (pIn.isObjectEnd())
971  {
972  // Initialize the operationplan
973  if (!activate())
974  // Initialization failed and the operationplan is deleted
976  }
977  else if (pAttr.isA (Tags::tag_demand))
978  {
979  Demand * d = dynamic_cast<Demand*>(pIn.getPreviousObject());
980  if (d) d->addDelivery(this);
981  else throw LogicException("Incorrect object type during read operation");
982  }
983  else if (pAttr.isA(Tags::tag_locked))
984  setLocked(pElement.getBool());
985 }
986 
987 
989 {
990  if (b)
991  flags |= IS_LOCKED;
992  else
993  flags &= ~IS_LOCKED;
994  for (OperationPlan *x = firstsubopplan; x; x = x->nextsubopplan)
995  x->setLocked(b);
996  update();
997 }
998 
999 
1001 {
1002  // No change
1003  if (l==dmd) return;
1004 
1005  // Unregister from previous demand
1006  if (dmd) dmd->removeDelivery(this);
1007 
1008  // Register the new demand and mark it changed
1009  dmd = l;
1010  if (l) l->setChanged();
1011 }
1012 
1013 
1014 PyObject* OperationPlan::create(PyTypeObject* pytype, PyObject* args, PyObject* kwds)
1015 {
1016  try
1017  {
1018  // Find or create the C++ object
1019  PythonAttributeList atts(kwds);
1021  Py_INCREF(x);
1022 
1023  // Iterate over extra keywords, and set attributes. @todo move this responsibility to the readers...
1024  if (x)
1025  {
1026  PyObject *key, *value;
1027  Py_ssize_t pos = 0;
1028  while (PyDict_Next(kwds, &pos, &key, &value))
1029  {
1030  PythonObject field(value);
1031  Attribute attr(PyString_AsString(key));
1032  if (!attr.isA(Tags::tag_operation) && !attr.isA(Tags::tag_id) && !attr.isA(Tags::tag_action))
1033  {
1034  int result = x->setattro(attr, field);
1035  if (result && !PyErr_Occurred())
1036  PyErr_Format(PyExc_AttributeError,
1037  "attribute '%s' on '%s' can't be updated",
1038  PyString_AsString(key), x->ob_type->tp_name);
1039  }
1040  };
1041  }
1042 
1043  if (x && !static_cast<OperationPlan*>(x)->activate())
1044  {
1045  PyErr_SetString(PythonRuntimeException, "operationplan activation failed");
1046  return NULL;
1047  }
1048  return x;
1049  }
1050  catch (...)
1051  {
1052  PythonType::evalException();
1053  return NULL;
1054  }
1055 }
1056 
1057 
1059 {
1060  if (attr.isA(Tags::tag_id))
1061  return PythonObject(getIdentifier());
1062  if (attr.isA(Tags::tag_operation))
1063  return PythonObject(getOperation());
1064  if (attr.isA(Tags::tag_flowplans))
1065  return new frepple::FlowPlanIterator(this);
1066  if (attr.isA(Tags::tag_loadplans))
1067  return new frepple::LoadPlanIterator(this);
1068  if (attr.isA(Tags::tag_quantity))
1069  return PythonObject(getQuantity());
1070  if (attr.isA(Tags::tag_start))
1071  return PythonObject(getDates().getStart());
1072  if (attr.isA(Tags::tag_end))
1073  return PythonObject(getDates().getEnd());
1074  if (attr.isA(Tags::tag_demand))
1075  return PythonObject(getDemand());
1076  if (attr.isA(Tags::tag_locked))
1077  return PythonObject(getLocked());
1078  if (attr.isA(Tags::tag_owner))
1079  return PythonObject(getOwner());
1080  if (attr.isA(Tags::tag_operationplans))
1081  return new OperationPlanIterator(this);
1082  if (attr.isA(Tags::tag_hidden))
1083  return PythonObject(getHidden());
1084  if (attr.isA(Tags::tag_unavailable))
1085  return PythonObject(getUnavailable());
1086  if (attr.isA(Tags::tag_motive))
1087  {
1088  // Null
1089  if (!getMotive())
1090  {
1091  Py_INCREF(Py_None);
1092  return Py_None;
1093  }
1094 
1095  // Demand
1096  Demand* d = dynamic_cast<Demand*>(getMotive());
1097  if (d) return PythonObject(d);
1098 
1099  // Buffer
1100  Buffer* b = dynamic_cast<Buffer*>(getMotive());
1101  if (b) return PythonObject(b);
1102 
1103  // Resource
1104  Resource* r = dynamic_cast<Resource*>(getMotive());
1105  if (r) return PythonObject(r);
1106 
1107  // Unknown type
1108  PyErr_SetString(PythonLogicException, "Unhandled motive type");
1109  return NULL;
1110  }
1111  return NULL;
1112 }
1113 
1114 
1116 {
1117  if (attr.isA(Tags::tag_quantity))
1118  setQuantity(field.getDouble());
1119  else if (attr.isA(Tags::tag_start))
1120  setStart(field.getDate());
1121  else if (attr.isA(Tags::tag_end))
1122  setEnd(field.getDate());
1123  else if (attr.isA(Tags::tag_locked))
1124  setLocked(field.getBool());
1125  else if (attr.isA(Tags::tag_demand))
1126  {
1127  if (!field.check(Demand::metadata))
1128  {
1129  PyErr_SetString(PythonDataException, "operationplan demand must be of type demand");
1130  return -1;
1131  }
1132  Demand* y = static_cast<Demand*>(static_cast<PyObject*>(field));
1133  setDemand(y);
1134  }
1135  else if (attr.isA(Tags::tag_owner))
1136  {
1137  if (!field.check(OperationPlan::metadata))
1138  {
1139  PyErr_SetString(PythonDataException, "operationplan demand must be of type demand");
1140  return -1;
1141  }
1142  OperationPlan* y = static_cast<OperationPlan*>(static_cast<PyObject*>(field));
1143  setOwner(y);
1144  }
1145  else if (attr.isA(Tags::tag_motive))
1146  {
1147  Plannable* y;
1148  if (static_cast<PyObject*>(field) == Py_None)
1149  y = NULL;
1150  if (field.check(Demand::metadata))
1151  y = static_cast<Demand*>(static_cast<PyObject*>(field));
1152  else if (field.check(Buffer::metadata))
1153  y = static_cast<Buffer*>(static_cast<PyObject*>(field));
1154  else if (field.check(Resource::metadata))
1155  y = static_cast<Resource*>(static_cast<PyObject*>(field));
1156  else
1157  {
1158  PyErr_SetString(PythonDataException, "operationplan motive must be of type demand, buffer or resource");
1159  return -1;
1160  }
1161  setMotive(y);
1162  }
1163  else
1164  return -1;
1165  return 0;
1166 }
1167 
1168 } // end namespace