buffer.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 #include <math.h>
24 
25 // This is the name used for the dummy operation used to represent the
26 // inventory.
27 #define INVENTORY_OPERATION "Inventory of buffer '" + string(getName()) + "'"
28 
29 // This is the name used for the dummy operation used to represent procurements
30 #define PROCURE_OPERATION "Procure for buffer '" + string(getName()) + "'"
31 
32 namespace frepple
33 {
34 
35 template<class Buffer> DECLARE_EXPORT Tree utils::HasName<Buffer>::st;
40 DECLARE_EXPORT const double Buffer::default_max = 1e37;
41 
42 
44 {
45  // Initialize the metadata
46  metadata = new MetaCategory("buffer", "buffers", reader, writer);
47 
48  // Initialize the Python class
50 }
51 
52 
54 {
55  // Initialize the metadata
57  "buffer",
58  "buffer_default",
59  Object::createString<BufferDefault>, true);
60 
61  // Initialize the Python class
63 }
64 
65 
67 {
68  // Initialize the metadata
70  "buffer",
71  "buffer_infinite",
72  Object::createString<BufferInfinite>);
73 
74  // Initialize the Python class
76 }
77 
78 
80 {
81  // Initialize the metadata
83  "buffer",
84  "buffer_procure",
85  Object::createString<BufferProcure>);
86 
87  // Initialize the Python class
89 }
90 
91 
93 {
94  // The dummy operation to model the inventory may need to be created
96  Flow *fl;
97  if (!o)
98  {
99  // Create a fixed time operation with zero leadtime, hidden from the xml
100  // output, hidden for the solver, and without problem detection.
102  Operation::add(o); // No need to check again for existance
103  o->setHidden(true);
104  o->setDetectProblems(false);
105  fl = new FlowEnd(o, this, 1);
106  }
107  else
108  // Find the flow of this operation
109  fl = const_cast<Flow*>(&*(o->getFlows().begin()));
110 
111  // Check valid pointers
112  if (!fl || !o)
113  throw LogicException("Failed creating inventory operation for '"
114  + getName() + "'");
115 
116  // Make sure the sign of the flow is correct: +1 or -1.
117  fl->setQuantity(f>=0.0 ? 1.0 : -1.0);
118 
119  // Create a dummy operationplan on the inventory operation
121  if (i == OperationPlan::end())
122  {
123  // No operationplan exists yet
124  OperationPlan *opplan = o->createOperationPlan(
125  fabs(f), Date::infinitePast, Date::infinitePast);
126  opplan->setLocked(true);
127  // Note that we use the max counter for the onhand operationplans.
128  opplan->activate(false);
129  }
130  else
131  {
132  // Update the existing operationplan
133  i->setLocked(false);
134  i->setQuantity(fabs(f));
135  i->setLocked(true);
136  }
137  setChanged();
138 }
139 
140 
142 {
143  double tmp(0.0);
144  for (flowplanlist::const_iterator oo=flowplans.begin();
145  oo!=flowplans.end(); ++oo)
146  {
147  if (oo->getDate() > d)
148  // Found a flowplan with a later date.
149  // Return the onhand after the previous flowplan.
150  return tmp;
151  tmp = oo->getOnhand();
152  }
153  // Found no flowplan: either we have specified a date later than the
154  // last flowplan, either there are no flowplans at all.
155  return tmp;
156 }
157 
158 
159 DECLARE_EXPORT double Buffer::getOnHand(Date d1, Date d2, bool min) const
160 {
161  // Swap parameters if required
162  if (d2 < d1)
163  {
164  Date x(d1);
165  d2 = d1;
166  d2 = x;
167  }
168 
169  // Loop through all flowplans
170  double tmp(0.0), record(0.0);
171  Date d, prev_Date;
172  for (flowplanlist::const_iterator oo=flowplans.begin(); true; ++oo)
173  {
174  if (oo==flowplans.end() || oo->getDate() > d)
175  {
176  // Date has now changed or we have arrived at the end
177 
178  // New max?
179  if (prev_Date < d1)
180  // Not in active Date range: we simply follow the onhand profile
181  record = tmp;
182  else
183  {
184  // In the active range
185  // New extreme?
186  if (min) {if (tmp < record) record = tmp;}
187  else {if (tmp > record) record = tmp;}
188  }
189 
190  // Are we done now?
191  if (prev_Date > d2 || oo==flowplans.end()) return record;
192 
193  // Set the variable with the new Date
194  d = oo->getDate();
195  }
196  tmp = oo->getOnhand();
197  prev_Date = oo->getDate();
198  }
199  // The above for-loop controls the exit. This line of code is never reached.
200  throw LogicException("Unreachable code reached");
201 }
202 
203 
205 {
206  // Writing a reference
207  if (m == REFERENCE)
208  {
209  o->writeElement(tag, Tags::tag_name, getName());
210  return;
211  }
212 
213  // Write the complete object
214  if (m != NOHEAD && m != NOHEADTAIL)
216 
217  // Write own fields
218  HasDescription::writeElement(o, tag);
220  o->writeElement(Tags::tag_producing, producing_operation);
223  Plannable::writeElement(o, tag);
224 
225  // Onhand
226  flowplanlist::const_iterator i = flowplans.begin();
227  // Loop through the flowplans at the start of the horizon
228  for (; i!=flowplans.end() && i->getType()!=1 && !i->getDate(); ++i) ;
229  if (i!=flowplans.end() && i->getType()==1)
230  {
231  // A flowplan has been found
232  const FlowPlan *fp = dynamic_cast<const FlowPlan*>(&*i);
233  if (fp
234  && fp->getFlow()->getOperation()->getName() == string(INVENTORY_OPERATION)
235  && fabs(fp->getQuantity()) > ROUNDING_ERROR)
237  }
238 
239  // Minimum and maximum inventory targets, carrying cost
240  if (min_val != 0) o->writeElement(Tags::tag_minimum, min_val);
242  if (max_val != default_max) o->writeElement(Tags::tag_maximum, max_val);
244  if (getCarryingCost()!= 0.0)
246 
247  // Write extra plan information
248  i = flowplans.begin();
249  if ((o->getContentType() == XMLOutput::PLAN
250  || o->getContentType() == XMLOutput::PLANDETAIL) && i!=flowplans.end())
251  {
253  for (; i!=flowplans.end(); ++i)
254  if (i->getType()==1)
255  dynamic_cast<const FlowPlan*>(&*i)->writeElement(o, Tags::tag_flowplan);
257  bool first = true;
258  for (Problem::const_iterator j = Problem::begin(const_cast<Buffer*>(this), true); j!=Problem::end(); ++j)
259  {
260  if (first)
261  {
262  first = false;
264  }
266  }
267  if (!first) o->EndObject(Tags::tag_problems);
268  }
269 
270  // Ending tag
271  if (m != NOHEADTAIL && m != NOTAIL) o->EndObject(tag);
272 }
273 
274 
276 {
277  if (pAttr.isA(Tags::tag_flow)
278  && pIn.getParentElement().first.isA(Tags::tag_flows))
279  {
280  Flow *f =
281  dynamic_cast<Flow*>(MetaCategory::ControllerDefault(Flow::metadata,pIn.getAttributes()));
282  if (f) f->setBuffer(this);
283  pIn.readto (f);
284  }
285  else if (pAttr.isA(Tags::tag_producing))
287  else if (pAttr.isA(Tags::tag_item))
289  else if (pAttr.isA(Tags::tag_minimum_calendar)
292  else if (pAttr.isA(Tags::tag_location))
294  else if (pAttr.isA(Tags::tag_flowplans))
295  pIn.IgnoreElement();
296  else
298 }
299 
300 
301 DECLARE_EXPORT void Buffer::endElement(XMLInput& pIn, const Attribute& pAttr, const DataElement& pElement)
302 {
303  if (pAttr.isA(Tags::tag_producing))
304  {
305  Operation *b = dynamic_cast<Operation*>(pIn.getPreviousObject());
306  if (b) setProducingOperation(b);
307  else throw LogicException("Incorrect object type during read operation");
308  }
309  else if (pAttr.isA(Tags::tag_item))
310  {
311  Item *a = dynamic_cast<Item*>(pIn.getPreviousObject());
312  if (a) setItem(a);
313  else throw LogicException("Incorrect object type during read operation");
314  }
315  else if (pAttr.isA(Tags::tag_onhand))
316  setOnHand(pElement.getDouble());
317  else if (pAttr.isA(Tags::tag_minimum))
318  setMinimum(pElement.getDouble());
319  else if (pAttr.isA(Tags::tag_maximum))
320  setMaximum(pElement.getDouble());
321  else if (pAttr.isA(Tags::tag_minimum_calendar))
322  {
323  CalendarDouble *mincal =
324  dynamic_cast<CalendarDouble*>(pIn.getPreviousObject());
325  if (mincal)
326  setMinimumCalendar(mincal);
327  else
328  {
329  Calendar *c = dynamic_cast<Calendar*>(pIn.getPreviousObject());
330  if (!c)
331  throw LogicException("Incorrect object type during read operation");
332  throw DataException("Calendar '" + c->getName() +
333  "' has invalid type for use as buffer min calendar");
334  }
335  }
336  else if (pAttr.isA(Tags::tag_maximum_calendar))
337  {
338  CalendarDouble *maxcal =
339  dynamic_cast<CalendarDouble*>(pIn.getPreviousObject());
340  if (maxcal)
341  setMaximumCalendar(maxcal);
342  else
343  {
344  Calendar *c = dynamic_cast<Calendar*>(pIn.getPreviousObject());
345  if (!c)
346  throw LogicException("Incorrect object type during read operation");
347  throw DataException("Calendar '" + c->getName() +
348  "' has invalid type for use as buffer max calendar");
349  }
350  }
351  else if (pAttr.isA(Tags::tag_location))
352  {
353  Location * d = dynamic_cast<Location*>(pIn.getPreviousObject());
354  if (d) setLocation(d);
355  else throw LogicException("Incorrect object type during read operation");
356  }
357  else if (pAttr.isA(Tags::tag_carrying_cost))
358  setCarryingCost(pElement.getDouble());
359  else
360  {
361  Plannable::endElement(pIn, pAttr, pElement);
362  HasDescription::endElement(pIn, pAttr, pElement);
363  HasHierarchy<Buffer>::endElement(pIn, pAttr, pElement);
364  }
365 }
366 
367 
369 {
370  // There is already a minimum calendar.
371  if (min_cal)
372  {
373  // We update the field, but don't use it yet.
374  min_val = m;
375  return;
376  }
377 
378  // Mark as changed
379  setChanged();
380 
381  // Set field
382  min_val = m;
383 
384  // Create or update a single timeline min event
385  for (flowplanlist::iterator oo=flowplans.begin(); oo!=flowplans.end(); oo++)
386  if (oo->getType() == 3)
387  {
388  // Update existing event
389  static_cast<flowplanlist::EventMinQuantity *>(&*oo)->setMin(min_val);
390  return;
391  }
392  // Create new event
393  flowplanlist::EventMinQuantity *newEvent =
394  new flowplanlist::EventMinQuantity(Date::infinitePast, min_val);
395  flowplans.insert(newEvent);
396 }
397 
398 
400 {
401  // Resetting the same calendar
402  if (min_cal == cal) return;
403 
404  // Mark as changed
405  setChanged();
406 
407  // Delete previous events.
408  for (flowplanlist::iterator oo=flowplans.begin(); oo!=flowplans.end(); )
409  if (oo->getType() == 3)
410  {
411  flowplans.erase(&(*oo));
412  delete &(*(oo++));
413  }
414  else ++oo;
415 
416  // Null pointer passed. Change back to time independent min.
417  if (!cal)
418  {
419  setMinimum(min_val);
420  return;
421  }
422 
423  // Create timeline structures for every event. A new entry is created only
424  // when the value changes.
425  min_cal = const_cast< CalendarDouble* >(cal);
426  double curMin = 0.0;
427  for (CalendarDouble::EventIterator x(min_cal); x.getDate()<Date::infiniteFuture; ++x)
428  if (curMin != x.getValue())
429  {
430  curMin = x.getValue();
431  flowplanlist::EventMinQuantity *newBucket =
432  new flowplanlist::EventMinQuantity(x.getDate(), curMin);
433  flowplans.insert(newBucket);
434  }
435 }
436 
437 
439 {
440  // There is already a maximum calendar.
441  if (max_cal)
442  {
443  // We update the field, but don't use it yet.
444  max_val = m;
445  return;
446  }
447 
448  // Mark as changed
449  setChanged();
450 
451  // Set field
452  max_val = m;
453 
454  // Create or update a single timeline max event
455  for (flowplanlist::iterator oo=flowplans.begin(); oo!=flowplans.end(); oo++)
456  if (oo->getType() == 4)
457  {
458  // Update existing event
459  static_cast<flowplanlist::EventMaxQuantity *>(&*oo)->setMax(max_val);
460  return;
461  }
462  // Create new event
463  flowplanlist::EventMaxQuantity *newEvent =
464  new flowplanlist::EventMaxQuantity(Date::infinitePast, max_val);
465  flowplans.insert(newEvent);
466 }
467 
468 
470 {
471  // Resetting the same calendar
472  if (max_cal == cal) return;
473 
474  // Mark as changed
475  setChanged();
476 
477  // Delete previous events.
478  for (flowplanlist::iterator oo=flowplans.begin(); oo!=flowplans.end(); )
479  if (oo->getType() == 4)
480  {
481  flowplans.erase(&(*oo));
482  delete &(*(oo++));
483  }
484  else ++oo;
485 
486  // Null pointer passed. Change back to time independent max.
487  if (!cal)
488  {
489  setMaximum(max_val);
490  return;
491  }
492 
493  // Create timeline structures for every bucket. A new entry is created only
494  // when the value changes.
495  max_cal = const_cast<CalendarDouble*>(cal);
496  double curMax = 0.0;
497  for (CalendarDouble::EventIterator x(max_cal); x.getDate()<Date::infiniteFuture; ++x)
498  if (curMax != x.getValue())
499  {
500  curMax = x.getValue();
501  flowplanlist::EventMaxQuantity *newBucket =
502  new flowplanlist::EventMaxQuantity(x.getDate(), curMax);
503  flowplans.insert(newBucket);
504  }
505 }
506 
507 
509 {
510  // Delete the operationplans
511  for (flowlist::iterator i=flows.begin(); i!=flows.end(); ++i)
512  OperationPlan::deleteOperationPlans(i->getOperation(),deleteLocked);
513 
514  // Mark to recompute the problems
515  setChanged();
516 }
517 
518 
520 {
521  // Delete all operationplans.
522  // An alternative logic would be to delete only the flowplans for this
523  // buffer and leave the rest of the plan untouched. The currently
524  // implemented method is way more drastic...
525  deleteOperationPlans(true);
526 
527  // The Flow objects are automatically deleted by the destructor of the
528  // Association list class.
529 
530  // Remove the inventory operation
532  if (invoper) delete invoper;
533 }
534 
535 
537 (PeggingIterator& iter, FlowPlan* curflowplan, short nextlevel, double curqty, double curfactor)
538 {
539 
540  double peggedQty(0);
541  Buffer::flowplanlist::const_iterator f = getFlowPlans().begin(curflowplan);
542 
543  if (curflowplan->getQuantity() < -ROUNDING_ERROR && !iter.isDownstream())
544  {
545  // CASE 1:
546  // This is a flowplan consuming from a buffer. Navigating upstream means
547  // finding the flowplans producing this consumed material.
548  double endQty = f->getCumulativeConsumed();
549  double startQty = endQty + f->getQuantity();
550  if (f->getCumulativeProduced() <= startQty)
551  {
552  // CASE 1A: Not produced enough yet: move forward
553  while (f!=getFlowPlans().end()
554  && f->getCumulativeProduced() <= startQty) ++f;
555  while (f!=getFlowPlans().end()
556  && ( (f->getQuantity()<=0 && f->getCumulativeProduced() < endQty)
557  || (f->getQuantity()>0
558  && f->getCumulativeProduced()-f->getQuantity() < endQty))
559  )
560  {
561  if (f->getQuantity() > ROUNDING_ERROR)
562  {
563  double newqty = f->getQuantity();
564  if (f->getCumulativeProduced()-f->getQuantity() < startQty)
565  newqty -= startQty - (f->getCumulativeProduced()-f->getQuantity());
566  if (f->getCumulativeProduced() > endQty)
567  newqty -= f->getCumulativeProduced() - endQty;
568  peggedQty += newqty;
569  const FlowPlan *x = dynamic_cast<const FlowPlan*>(&(*f));
570  iter.updateStack(nextlevel,
571  -curqty*newqty/curflowplan->getQuantity(),
572  curfactor*newqty/f->getQuantity(),
573  curflowplan, x);
574  }
575  ++f;
576  }
577  }
578  else
579  {
580  // CASE 1B: Produced too much already: move backward
581  while ( f!=getFlowPlans().end()
582  && ((f->getQuantity()<=0 && f->getCumulativeProduced() > endQty)
583  || (f->getQuantity()>0
584  && f->getCumulativeProduced()-f->getQuantity() > endQty))) --f;
585  while (f!=getFlowPlans().end() && f->getCumulativeProduced() > startQty)
586  {
587  if (f->getQuantity() > ROUNDING_ERROR)
588  {
589  double newqty = f->getQuantity();
590  if (f->getCumulativeProduced()-f->getQuantity() < startQty)
591  newqty -= startQty - (f->getCumulativeProduced()-f->getQuantity());
592  if (f->getCumulativeProduced() > endQty)
593  newqty -= f->getCumulativeProduced() - endQty;
594  peggedQty += newqty;
595  const FlowPlan *x = dynamic_cast<const FlowPlan*>(&(*f));
596  iter.updateStack(nextlevel,
597  -curqty*newqty/curflowplan->getQuantity(),
598  curfactor*newqty/f->getQuantity(),
599  curflowplan, x);
600  }
601  --f;
602  }
603  }
604  if (peggedQty < endQty - startQty - ROUNDING_ERROR)
605  // Unproduced material (i.e. material that is consumed but never
606  // produced) is handled with a special entry on the stack.
607  iter.updateStack(nextlevel,
608  curqty*(peggedQty - endQty + startQty)/curflowplan->getQuantity(),
609  curfactor,
610  curflowplan,
611  NULL,
612  false);
613  return;
614  }
615 
616  if (curflowplan->getQuantity() > ROUNDING_ERROR && iter.isDownstream())
617  {
618  // CASE 2:
619  // This is a flowplan producing in a buffer. Navigating downstream means
620  // finding the flowplans consuming this produced material.
621  double endQty = f->getCumulativeProduced();
622  double startQty = endQty - f->getQuantity();
623  if (f->getCumulativeConsumed() <= startQty)
624  {
625  // CASE 2A: Not consumed enough yet: move forward
626  while (f!=getFlowPlans().end()
627  && f->getCumulativeConsumed() <= startQty) ++f;
628  while (f!=getFlowPlans().end()
629  && ( (f->getQuantity()<=0
630  && f->getCumulativeConsumed()+f->getQuantity() < endQty)
631  || (f->getQuantity()>0 && f->getCumulativeConsumed() < endQty))
632  )
633  {
634  if (f->getQuantity() < -ROUNDING_ERROR)
635  {
636  double newqty = - f->getQuantity();
637  if (f->getCumulativeConsumed()+f->getQuantity() < startQty)
638  newqty -= startQty - (f->getCumulativeConsumed()+f->getQuantity());
639  if (f->getCumulativeConsumed() > endQty)
640  newqty -= f->getCumulativeConsumed() - endQty;
641  peggedQty += newqty;
642  const FlowPlan *x = dynamic_cast<const FlowPlan*>(&(*f));
643  iter.updateStack(nextlevel,
644  curqty*newqty/curflowplan->getQuantity(),
645  -curfactor*newqty/f->getQuantity(),
646  x, curflowplan);
647  }
648  ++f;
649  }
650  }
651  else
652  {
653  // CASE 2B: Consumed too much already: move backward
654  while ( f!=getFlowPlans().end()
655  && ((f->getQuantity()<=0 && f->getCumulativeConsumed()+f->getQuantity() < endQty)
656  || (f->getQuantity()>0 && f->getCumulativeConsumed() < endQty))) --f;
657  while (f!=getFlowPlans().end() && f->getCumulativeConsumed() > startQty)
658  {
659  if (f->getQuantity() < -ROUNDING_ERROR)
660  {
661  double newqty = - f->getQuantity();
662  if (f->getCumulativeConsumed()+f->getQuantity() < startQty)
663  newqty -= startQty - (f->getCumulativeConsumed()+f->getQuantity());
664  if (f->getCumulativeConsumed() > endQty)
665  newqty -= f->getCumulativeConsumed() - endQty;
666  peggedQty += newqty;
667  const FlowPlan *x = dynamic_cast<const FlowPlan*>(&(*f));
668  iter.updateStack(nextlevel,
669  curqty*newqty/curflowplan->getQuantity(),
670  -curfactor*newqty/f->getQuantity(),
671  x, curflowplan);
672  }
673  --f;
674  }
675  }
676  if (peggedQty < endQty - startQty)
677  // Unpegged material (i.e. material that is produced but never consumed)
678  // is handled with a special entry on the stack.
679  iter.updateStack(nextlevel,
680  curqty*(endQty - startQty - peggedQty)/curflowplan->getQuantity(),
681  curfactor,
682  NULL, curflowplan,
683  false);
684  return;
685  }
686 }
687 
688 
690 (XMLOutput *o, const Keyword &tag, mode m) const
691 {
692  // Writing a reference
693  if (m == REFERENCE)
694  {
695  o->writeElement
696  (tag, Tags::tag_name, getName(), Tags::tag_type, getType().type);
697  return;
698  }
699 
700  // Write the complete object
701  if (m != NOHEAD && m != NOHEADTAIL) o->BeginObject
702  (tag, Tags::tag_name, XMLEscape(getName()), Tags::tag_type, getType().type);
703 
704  // Write the fields and an ending tag
705  Buffer::writeElement(o, tag, NOHEAD);
706 }
707 
708 
709 DECLARE_EXPORT void BufferProcure::endElement(XMLInput& pIn, const Attribute& pAttr, const DataElement& pElement)
710 {
711  if (pAttr.isA(Tags::tag_leadtime))
712  setLeadtime(pElement.getTimeperiod());
713  else if (pAttr.isA(Tags::tag_fence))
714  setFence(pElement.getTimeperiod());
715  else if (pAttr.isA(Tags::tag_size_maximum))
716  setSizeMaximum(pElement.getDouble());
717  else if (pAttr.isA(Tags::tag_size_minimum))
718  setSizeMinimum(pElement.getDouble());
719  else if (pAttr.isA(Tags::tag_size_multiple))
720  setSizeMultiple(pElement.getDouble());
721  else if (pAttr.isA(Tags::tag_mininterval))
722  setMinimumInterval(pElement.getTimeperiod());
723  else if (pAttr.isA(Tags::tag_maxinterval))
724  setMaximumInterval(pElement.getTimeperiod());
725  else if (pAttr.isA(Tags::tag_mininventory))
726  setMinimumInventory(pElement.getDouble());
727  else if (pAttr.isA(Tags::tag_maxinventory))
728  setMaximumInventory(pElement.getDouble());
729  else
730  Buffer::endElement(pIn, pAttr, pElement);
731 }
732 
733 
735 {
736  // Writing a reference
737  if (m == REFERENCE)
738  {
739  o->writeElement
741  return;
742  }
743 
744  // Write the complete object
745  if (m != NOHEAD && m != NOHEADTAIL) o->BeginObject
747 
748  // Write the standard buffer fields
750 
751  // Write the extra fields
752  if (leadtime) o->writeElement(Tags::tag_leadtime, leadtime);
753  if (fence) o->writeElement(Tags::tag_fence, fence);
754  if (size_maximum != DBL_MAX) o->writeElement(Tags::tag_size_maximum, size_maximum);
755  if (size_minimum) o->writeElement(Tags::tag_size_minimum, size_minimum);
756  if (size_multiple) o->writeElement(Tags::tag_size_multiple, size_multiple);
757  if (min_interval) o->writeElement(Tags::tag_mininterval, min_interval);
758  if (max_interval) o->writeElement(Tags::tag_maxinterval, max_interval);
761 
762  // Write the tail
763  if (m != NOHEADTAIL && m != NOTAIL) o->EndObject(tag);
764 }
765 
766 
768 {
769  if (!oper)
770  {
772  if (!o)
773  {
774  // Create the operation if it didn't exist yet
776  static_cast<OperationFixedTime*>(o)->setDuration(leadtime);
777  o->setFence(getFence());
781  Operation::add(o); // No need to check again for existence
782  new FlowEnd(o, const_cast<BufferProcure*>(this), 1);
783  }
784  const_cast<BufferProcure*>(this)->oper = o;
785  }
786  return oper;
787 }
788 
789 
791 {
792  if (attr.isA(Tags::tag_name))
793  return PythonObject(getName());
794  if (attr.isA(Tags::tag_description))
795  return PythonObject(getDescription());
796  if (attr.isA(Tags::tag_category))
797  return PythonObject(getCategory());
798  if (attr.isA(Tags::tag_subcategory))
799  return PythonObject(getSubCategory());
800  if (attr.isA(Tags::tag_owner))
801  return PythonObject(getOwner());
802  if (attr.isA(Tags::tag_location))
803  return PythonObject(getLocation());
804  if (attr.isA(Tags::tag_producing))
806  if (attr.isA(Tags::tag_item))
807  return PythonObject(getItem());
808  if (attr.isA(Tags::tag_onhand))
809  return PythonObject(getOnHand());
810  if (attr.isA(Tags::tag_flowplans))
811  return new FlowPlanIterator(this);
812  if (attr.isA(Tags::tag_maximum))
813  return PythonObject(getMaximum());
814  if (attr.isA(Tags::tag_minimum))
815  return PythonObject(getMinimum());
820  if (attr.isA(Tags::tag_carrying_cost))
821  return PythonObject(getCarryingCost());
822  if (attr.isA(Tags::tag_hidden))
823  return PythonObject(getHidden());
824  if (attr.isA(Tags::tag_flows))
825  return new FlowIterator(this);
826  if (attr.isA(Tags::tag_level))
827  return PythonObject(getLevel());
828  if (attr.isA(Tags::tag_cluster))
829  return PythonObject(getCluster());
830  if (attr.isA(Tags::tag_members))
831  return new BufferIterator(this);
832  return NULL;
833 }
834 
835 
837 {
838  if (attr.isA(Tags::tag_name))
839  setName(field.getString());
840  else if (attr.isA(Tags::tag_description))
841  setDescription(field.getString());
842  else if (attr.isA(Tags::tag_category))
843  setCategory(field.getString());
844  else if (attr.isA(Tags::tag_subcategory))
845  setSubCategory(field.getString());
846  else if (attr.isA(Tags::tag_owner))
847  {
848  if (!field.check(Buffer::metadata))
849  {
850  PyErr_SetString(PythonDataException, "buffer owner must be of type buffer");
851  return -1;
852  }
853  Buffer* y = static_cast<Buffer*>(static_cast<PyObject*>(field));
854  setOwner(y);
855  }
856  else if (attr.isA(Tags::tag_location))
857  {
858  if (!field.check(Location::metadata))
859  {
860  PyErr_SetString(PythonDataException, "buffer location must be of type location");
861  return -1;
862  }
863  Location* y = static_cast<Location*>(static_cast<PyObject*>(field));
864  setLocation(y);
865  }
866  else if (attr.isA(Tags::tag_item))
867  {
868  if (!field.check(Item::metadata))
869  {
870  PyErr_SetString(PythonDataException, "buffer item must be of type item");
871  return -1;
872  }
873  Item* y = static_cast<Item*>(static_cast<PyObject*>(field));
874  setItem(y);
875  }
876  else if (attr.isA(Tags::tag_minimum))
877  setMinimum(field.getDouble());
878  else if (attr.isA(Tags::tag_maximum))
879  setMaximum(field.getDouble());
880  else if (attr.isA(Tags::tag_maximum_calendar))
881  {
882  if (!field.check(CalendarDouble::metadata))
883  {
884  PyErr_SetString(PythonDataException, "buffer maximum must be of type calendar_double");
885  return -1;
886  }
887  CalendarDouble* y = static_cast<CalendarDouble*>(static_cast<PyObject*>(field));
889  }
890  else if (attr.isA(Tags::tag_minimum_calendar))
891  {
892  if (!field.check(CalendarDouble::metadata))
893  {
894  PyErr_SetString(PythonDataException, "buffer minimum must be of type calendar_double");
895  return -1;
896  }
897  CalendarDouble* y = static_cast<CalendarDouble*>(static_cast<PyObject*>(field));
899  }
900  else if (attr.isA(Tags::tag_onhand))
901  setOnHand(field.getDouble());
902  else if (attr.isA(Tags::tag_carrying_cost))
903  setCarryingCost(field.getDouble());
904  else if (attr.isA(Tags::tag_producing))
905  {
906  if (!field.check(Operation::metadata))
907  {
908  PyErr_SetString(PythonDataException, "buffer producing must be of type operation");
909  return -1;
910  }
911  Operation* y = static_cast<Operation*>(static_cast<PyObject*>(field));
913  }
914  else if (attr.isA(Tags::tag_hidden))
915  setHidden(field.getBool());
916  else
917  return -1; // Error
918  return 0; // OK
919 }
920 
921 
923 {
924  if (attr.isA(Tags::tag_leadtime))
925  return PythonObject(getLeadtime());
926  if (attr.isA(Tags::tag_mininventory))
928  if (attr.isA(Tags::tag_maxinventory))
930  if (attr.isA(Tags::tag_mininterval))
932  if (attr.isA(Tags::tag_maxinterval))
934  if (attr.isA(Tags::tag_fence))
935  return PythonObject(getFence());
936  if (attr.isA(Tags::tag_size_minimum))
937  return PythonObject(getSizeMinimum());
938  if (attr.isA(Tags::tag_size_multiple))
939  return PythonObject(getSizeMultiple());
940  if (attr.isA(Tags::tag_size_maximum))
941  return PythonObject(getSizeMaximum());
942  if (attr.isA(Tags::tag_producing))
943  return PythonObject(getOperation());
944  return Buffer::getattro(attr);
945 }
946 
947 
949 {
950  if (attr.isA(Tags::tag_leadtime))
951  setLeadtime(field.getTimeperiod());
952  else if (attr.isA(Tags::tag_mininventory))
954  else if (attr.isA(Tags::tag_maxinventory))
956  else if (attr.isA(Tags::tag_mininterval))
958  else if (attr.isA(Tags::tag_maxinterval))
960  else if (attr.isA(Tags::tag_size_minimum))
961  setSizeMinimum(field.getDouble());
962  else if (attr.isA(Tags::tag_size_multiple))
963  setSizeMultiple(field.getDouble());
964  else if (attr.isA(Tags::tag_size_maximum))
965  setSizeMaximum(field.getDouble());
966  else if (attr.isA(Tags::tag_fence))
967  setFence(field.getTimeperiod());
968  else if (attr.isA(Tags::tag_producing))
969  return 0;
970  else
971  return Buffer::setattro(attr, field);
972  return 0;
973 }
974 
975 } // end namespace