buffer.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 #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!= NOHEADER) o->BeginObject(tag, Tags::tag_name, XMLEscape(getName()));
215 
216  // Write own fields
219  o->writeElement(Tags::tag_producing, producing_operation);
222  Plannable::writeElement(o, tag);
223 
224  // Onhand
225  flowplanlist::const_iterator i = flowplans.begin();
226  // Loop through the flowplans at the start of the horizon
227  for (; i!=flowplans.end() && i->getType()!=1 && !i->getDate(); ++i) ;
228  if (i!=flowplans.end() && i->getType()==1)
229  {
230  // A flowplan has been found
231  const FlowPlan *fp = dynamic_cast<const FlowPlan*>(&*i);
232  if (fp
233  && fp->getFlow()->getOperation()->getName() == string(INVENTORY_OPERATION)
234  && fabs(fp->getQuantity()) > ROUNDING_ERROR)
236  }
237 
238  // Minimum and maximum inventory targets, carrying cost
239  if (min_val != 0) o->writeElement(Tags::tag_minimum, min_val);
241  if (max_val != default_max) o->writeElement(Tags::tag_maximum, max_val);
243  if (getCarryingCost()!= 0.0)
245 
246  // Write extra plan information
247  i = flowplans.begin();
248  if ((o->getContentType() == XMLOutput::PLAN
249  || o->getContentType() == XMLOutput::PLANDETAIL) && i!=flowplans.end())
250  {
252  for (; i!=flowplans.end(); ++i)
253  if (i->getType()==1)
254  dynamic_cast<const FlowPlan*>(&*i)->writeElement(o, Tags::tag_flowplan);
256  }
257 
258  // Ending tag
259  o->EndObject(tag);
260 }
261 
262 
264 {
265  if (pAttr.isA(Tags::tag_flow)
266  && pIn.getParentElement().first.isA(Tags::tag_flows))
267  {
268  Flow *f =
269  dynamic_cast<Flow*>(MetaCategory::ControllerDefault(Flow::metadata,pIn.getAttributes()));
270  if (f) f->setBuffer(this);
271  pIn.readto (f);
272  }
273  else if (pAttr.isA(Tags::tag_producing))
275  else if (pAttr.isA(Tags::tag_item))
277  else if (pAttr.isA(Tags::tag_minimum_calendar)
280  else if (pAttr.isA(Tags::tag_location))
282  else if (pAttr.isA(Tags::tag_flowplans))
283  pIn.IgnoreElement();
284  else
286 }
287 
288 
289 DECLARE_EXPORT void Buffer::endElement(XMLInput& pIn, const Attribute& pAttr, const DataElement& pElement)
290 {
291  if (pAttr.isA(Tags::tag_producing))
292  {
293  Operation *b = dynamic_cast<Operation*>(pIn.getPreviousObject());
294  if (b) setProducingOperation(b);
295  else throw LogicException("Incorrect object type during read operation");
296  }
297  else if (pAttr.isA(Tags::tag_item))
298  {
299  Item *a = dynamic_cast<Item*>(pIn.getPreviousObject());
300  if (a) setItem(a);
301  else throw LogicException("Incorrect object type during read operation");
302  }
303  else if (pAttr.isA(Tags::tag_onhand))
304  setOnHand(pElement.getDouble());
305  else if (pAttr.isA(Tags::tag_minimum))
306  setMinimum(pElement.getDouble());
307  else if (pAttr.isA(Tags::tag_maximum))
308  setMaximum(pElement.getDouble());
309  else if (pAttr.isA(Tags::tag_minimum_calendar))
310  {
311  CalendarDouble *mincal =
312  dynamic_cast<CalendarDouble*>(pIn.getPreviousObject());
313  if (mincal)
314  setMinimumCalendar(mincal);
315  else
316  {
317  Calendar *c = dynamic_cast<Calendar*>(pIn.getPreviousObject());
318  if (!c)
319  throw LogicException("Incorrect object type during read operation");
320  throw DataException("Calendar '" + c->getName() +
321  "' has invalid type for use as buffer min calendar");
322  }
323  }
324  else if (pAttr.isA(Tags::tag_maximum_calendar))
325  {
326  CalendarDouble *maxcal =
327  dynamic_cast<CalendarDouble*>(pIn.getPreviousObject());
328  if (maxcal)
329  setMaximumCalendar(maxcal);
330  else
331  {
332  Calendar *c = dynamic_cast<Calendar*>(pIn.getPreviousObject());
333  if (!c)
334  throw LogicException("Incorrect object type during read operation");
335  throw DataException("Calendar '" + c->getName() +
336  "' has invalid type for use as buffer max calendar");
337  }
338  }
339  else if (pAttr.isA(Tags::tag_location))
340  {
341  Location * d = dynamic_cast<Location*>(pIn.getPreviousObject());
342  if (d) setLocation(d);
343  else throw LogicException("Incorrect object type during read operation");
344  }
345  else if (pAttr.isA(Tags::tag_carrying_cost))
346  setCarryingCost(pElement.getDouble());
347  else
348  {
349  Plannable::endElement(pIn, pAttr, pElement);
350  HasDescription::endElement(pIn, pAttr, pElement);
351  HasHierarchy<Buffer>::endElement(pIn, pAttr, pElement);
352  }
353 }
354 
355 
357 {
358  // There is already a minimum calendar.
359  if (min_cal)
360  {
361  // We update the field, but don't use it yet.
362  min_val = m;
363  return;
364  }
365 
366  // Mark as changed
367  setChanged();
368 
369  // Set field
370  min_val = m;
371 
372  // Create or update a single timeline min event
373  for (flowplanlist::iterator oo=flowplans.begin(); oo!=flowplans.end(); oo++)
374  if (oo->getType() == 3)
375  {
376  // Update existing event
377  static_cast<flowplanlist::EventMinQuantity *>(&*oo)->setMin(min_val);
378  return;
379  }
380  // Create new event
381  flowplanlist::EventMinQuantity *newEvent =
382  new flowplanlist::EventMinQuantity(Date::infinitePast, min_val);
383  flowplans.insert(newEvent);
384 }
385 
386 
388 {
389  // Resetting the same calendar
390  if (min_cal == cal) return;
391 
392  // Mark as changed
393  setChanged();
394 
395  // Delete previous events.
396  for (flowplanlist::iterator oo=flowplans.begin(); oo!=flowplans.end(); )
397  if (oo->getType() == 3)
398  {
399  flowplans.erase(&(*oo));
400  delete &(*(oo++));
401  }
402  else ++oo;
403 
404  // Null pointer passed. Change back to time independent min.
405  if (!cal)
406  {
407  setMinimum(min_val);
408  return;
409  }
410 
411  // Create timeline structures for every event. A new entry is created only
412  // when the value changes.
413  min_cal = const_cast< CalendarDouble* >(cal);
414  double curMin = 0.0;
415  for (CalendarDouble::EventIterator x(min_cal); x.getDate()<Date::infiniteFuture; ++x)
416  if (curMin != x.getValue())
417  {
418  curMin = x.getValue();
419  flowplanlist::EventMinQuantity *newBucket =
420  new flowplanlist::EventMinQuantity(x.getDate(), curMin);
421  flowplans.insert(newBucket);
422  }
423 }
424 
425 
427 {
428  // There is already a maximum calendar.
429  if (max_cal)
430  {
431  // We update the field, but don't use it yet.
432  max_val = m;
433  return;
434  }
435 
436  // Mark as changed
437  setChanged();
438 
439  // Set field
440  max_val = m;
441 
442  // Create or update a single timeline max event
443  for (flowplanlist::iterator oo=flowplans.begin(); oo!=flowplans.end(); oo++)
444  if (oo->getType() == 4)
445  {
446  // Update existing event
447  static_cast<flowplanlist::EventMaxQuantity *>(&*oo)->setMax(max_val);
448  return;
449  }
450  // Create new event
451  flowplanlist::EventMaxQuantity *newEvent =
452  new flowplanlist::EventMaxQuantity(Date::infinitePast, max_val);
453  flowplans.insert(newEvent);
454 }
455 
456 
458 {
459  // Resetting the same calendar
460  if (max_cal == cal) return;
461 
462  // Mark as changed
463  setChanged();
464 
465  // Delete previous events.
466  for (flowplanlist::iterator oo=flowplans.begin(); oo!=flowplans.end(); )
467  if (oo->getType() == 4)
468  {
469  flowplans.erase(&(*oo));
470  delete &(*(oo++));
471  }
472  else ++oo;
473 
474  // Null pointer passed. Change back to time independent max.
475  if (!cal)
476  {
477  setMaximum(max_val);
478  return;
479  }
480 
481  // Create timeline structures for every bucket. A new entry is created only
482  // when the value changes.
483  max_cal = const_cast<CalendarDouble*>(cal);
484  double curMax = 0.0;
485  for (CalendarDouble::EventIterator x(max_cal); x.getDate()<Date::infiniteFuture; ++x)
486  if (curMax != x.getValue())
487  {
488  curMax = x.getValue();
489  flowplanlist::EventMaxQuantity *newBucket =
490  new flowplanlist::EventMaxQuantity(x.getDate(), curMax);
491  flowplans.insert(newBucket);
492  }
493 }
494 
495 
497 {
498  // Delete the operationplans
499  for (flowlist::iterator i=flows.begin(); i!=flows.end(); ++i)
500  OperationPlan::deleteOperationPlans(i->getOperation(),deleteLocked);
501 
502  // Mark to recompute the problems
503  setChanged();
504 }
505 
506 
508 {
509  // Delete all operationplans.
510  // An alternative logic would be to delete only the flowplans for this
511  // buffer and leave the rest of the plan untouched. The currently
512  // implemented method is way more drastic...
513  deleteOperationPlans(true);
514 
515  // The Flow objects are automatically deleted by the destructor of the
516  // Association list class.
517 
518  // Remove the inventory operation
520  if (invoper) delete invoper;
521 }
522 
523 
525 (PeggingIterator& iter, FlowPlan* curflowplan, short nextlevel, double curqty, double curfactor)
526 {
527 
528  double peggedQty(0);
529  Buffer::flowplanlist::const_iterator f = getFlowPlans().begin(curflowplan);
530 
531  if (curflowplan->getQuantity() < -ROUNDING_ERROR && !iter.isDownstream())
532  {
533  // CASE 1:
534  // This is a flowplan consuming from a buffer. Navigating upstream means
535  // finding the flowplans producing this consumed material.
536  double endQty = f->getCumulativeConsumed();
537  double startQty = endQty + f->getQuantity();
538  if (f->getCumulativeProduced() <= startQty)
539  {
540  // CASE 1A: Not produced enough yet: move forward
541  while (f!=getFlowPlans().end()
542  && f->getCumulativeProduced() <= startQty) ++f;
543  while (f!=getFlowPlans().end()
544  && ( (f->getQuantity()<=0 && f->getCumulativeProduced() < endQty)
545  || (f->getQuantity()>0
546  && f->getCumulativeProduced()-f->getQuantity() < endQty))
547  )
548  {
549  if (f->getQuantity() > ROUNDING_ERROR)
550  {
551  double newqty = f->getQuantity();
552  if (f->getCumulativeProduced()-f->getQuantity() < startQty)
553  newqty -= startQty - (f->getCumulativeProduced()-f->getQuantity());
554  if (f->getCumulativeProduced() > endQty)
555  newqty -= f->getCumulativeProduced() - endQty;
556  peggedQty += newqty;
557  const FlowPlan *x = dynamic_cast<const FlowPlan*>(&(*f));
558  iter.updateStack(nextlevel,
559  -curqty*newqty/curflowplan->getQuantity(),
560  curfactor*newqty/f->getQuantity(),
561  curflowplan, x);
562  }
563  ++f;
564  }
565  }
566  else
567  {
568  // CASE 1B: Produced too much already: move backward
569  while ( f!=getFlowPlans().end()
570  && ((f->getQuantity()<=0 && f->getCumulativeProduced() > endQty)
571  || (f->getQuantity()>0
572  && f->getCumulativeProduced()-f->getQuantity() > endQty))) --f;
573  while (f!=getFlowPlans().end() && f->getCumulativeProduced() > startQty)
574  {
575  if (f->getQuantity() > ROUNDING_ERROR)
576  {
577  double newqty = f->getQuantity();
578  if (f->getCumulativeProduced()-f->getQuantity() < startQty)
579  newqty -= startQty - (f->getCumulativeProduced()-f->getQuantity());
580  if (f->getCumulativeProduced() > endQty)
581  newqty -= f->getCumulativeProduced() - endQty;
582  peggedQty += newqty;
583  const FlowPlan *x = dynamic_cast<const FlowPlan*>(&(*f));
584  iter.updateStack(nextlevel,
585  -curqty*newqty/curflowplan->getQuantity(),
586  curfactor*newqty/f->getQuantity(),
587  curflowplan, x);
588  }
589  --f;
590  }
591  }
592  if (peggedQty < endQty - startQty - ROUNDING_ERROR)
593  // Unproduced material (i.e. material that is consumed but never
594  // produced) is handled with a special entry on the stack.
595  iter.updateStack(nextlevel,
596  curqty*(peggedQty - endQty + startQty)/curflowplan->getQuantity(),
597  curfactor,
598  curflowplan,
599  NULL,
600  false);
601  return;
602  }
603 
604  if (curflowplan->getQuantity() > ROUNDING_ERROR && iter.isDownstream())
605  {
606  // CASE 2:
607  // This is a flowplan producing in a buffer. Navigating downstream means
608  // finding the flowplans consuming this produced material.
609  double endQty = f->getCumulativeProduced();
610  double startQty = endQty - f->getQuantity();
611  if (f->getCumulativeConsumed() <= startQty)
612  {
613  // CASE 2A: Not consumed enough yet: move forward
614  while (f!=getFlowPlans().end()
615  && f->getCumulativeConsumed() <= startQty) ++f;
616  while (f!=getFlowPlans().end()
617  && ( (f->getQuantity()<=0
618  && f->getCumulativeConsumed()+f->getQuantity() < endQty)
619  || (f->getQuantity()>0 && f->getCumulativeConsumed() < endQty))
620  )
621  {
622  if (f->getQuantity() < -ROUNDING_ERROR)
623  {
624  double newqty = - f->getQuantity();
625  if (f->getCumulativeConsumed()+f->getQuantity() < startQty)
626  newqty -= startQty - (f->getCumulativeConsumed()+f->getQuantity());
627  if (f->getCumulativeConsumed() > endQty)
628  newqty -= f->getCumulativeConsumed() - endQty;
629  peggedQty += newqty;
630  const FlowPlan *x = dynamic_cast<const FlowPlan*>(&(*f));
631  iter.updateStack(nextlevel,
632  curqty*newqty/curflowplan->getQuantity(),
633  -curfactor*newqty/f->getQuantity(),
634  x, curflowplan);
635  }
636  ++f;
637  }
638  }
639  else
640  {
641  // CASE 2B: Consumed too much already: move backward
642  while ( f!=getFlowPlans().end()
643  && ((f->getQuantity()<=0 && f->getCumulativeConsumed()+f->getQuantity() < endQty)
644  || (f->getQuantity()>0 && f->getCumulativeConsumed() < endQty))) --f;
645  while (f!=getFlowPlans().end() && f->getCumulativeConsumed() > startQty)
646  {
647  if (f->getQuantity() < -ROUNDING_ERROR)
648  {
649  double newqty = - f->getQuantity();
650  if (f->getCumulativeConsumed()+f->getQuantity() < startQty)
651  newqty -= startQty - (f->getCumulativeConsumed()+f->getQuantity());
652  if (f->getCumulativeConsumed() > endQty)
653  newqty -= f->getCumulativeConsumed() - endQty;
654  peggedQty += newqty;
655  const FlowPlan *x = dynamic_cast<const FlowPlan*>(&(*f));
656  iter.updateStack(nextlevel,
657  curqty*newqty/curflowplan->getQuantity(),
658  -curfactor*newqty/f->getQuantity(),
659  x, curflowplan);
660  }
661  --f;
662  }
663  }
664  if (peggedQty < endQty - startQty)
665  // Unpegged material (i.e. material that is produced but never consumed)
666  // is handled with a special entry on the stack.
667  iter.updateStack(nextlevel,
668  curqty*(endQty - startQty - peggedQty)/curflowplan->getQuantity(),
669  curfactor,
670  NULL, curflowplan,
671  false);
672  return;
673  }
674 }
675 
676 
678 (XMLOutput *o, const Keyword &tag, mode m) const
679 {
680  // Writing a reference
681  if (m == REFERENCE)
682  {
683  o->writeElement
684  (tag, Tags::tag_name, getName(), Tags::tag_type, getType().type);
685  return;
686  }
687 
688  // Write the complete object
689  if (m != NOHEADER) o->BeginObject
690  (tag, Tags::tag_name, XMLEscape(getName()), Tags::tag_type, getType().type);
691 
692  // Write the fields and an ending tag
694 }
695 
696 
697 DECLARE_EXPORT void BufferProcure::endElement(XMLInput& pIn, const Attribute& pAttr, const DataElement& pElement)
698 {
699  if (pAttr.isA(Tags::tag_leadtime))
700  setLeadtime(pElement.getTimeperiod());
701  else if (pAttr.isA(Tags::tag_fence))
702  setFence(pElement.getTimeperiod());
703  else if (pAttr.isA(Tags::tag_size_maximum))
704  setSizeMaximum(pElement.getDouble());
705  else if (pAttr.isA(Tags::tag_size_minimum))
706  setSizeMinimum(pElement.getDouble());
707  else if (pAttr.isA(Tags::tag_size_multiple))
708  setSizeMultiple(pElement.getDouble());
709  else if (pAttr.isA(Tags::tag_mininterval))
710  setMinimumInterval(pElement.getTimeperiod());
711  else if (pAttr.isA(Tags::tag_maxinterval))
712  setMaximumInterval(pElement.getTimeperiod());
713  else if (pAttr.isA(Tags::tag_mininventory))
714  setMinimumInventory(pElement.getDouble());
715  else if (pAttr.isA(Tags::tag_maxinventory))
716  setMaximumInventory(pElement.getDouble());
717  else
718  Buffer::endElement(pIn, pAttr, pElement);
719 }
720 
721 
723 {
724  // Writing a reference
725  if (m == REFERENCE)
726  {
727  o->writeElement
729  return;
730  }
731 
732  // Write the complete object
733  if (m != NOHEADER) o->BeginObject
735 
736  // Write the extra fields
737  if (leadtime) o->writeElement(Tags::tag_leadtime, leadtime);
738  if (fence) o->writeElement(Tags::tag_fence, fence);
739  if (size_maximum != DBL_MAX) o->writeElement(Tags::tag_size_maximum, size_maximum);
740  if (size_minimum) o->writeElement(Tags::tag_size_minimum, size_minimum);
741  if (size_multiple) o->writeElement(Tags::tag_size_multiple, size_multiple);
742  if (min_interval) o->writeElement(Tags::tag_mininterval, min_interval);
743  if (max_interval) o->writeElement(Tags::tag_maxinterval, max_interval);
746 
747  // Write the fields and an ending tag
749 }
750 
751 
753 {
754  if (!oper)
755  {
757  if (!o)
758  {
759  // Create the operation if it didn't exist yet
761  static_cast<OperationFixedTime*>(o)->setDuration(leadtime);
762  o->setFence(getFence());
766  Operation::add(o); // No need to check again for existence
767  new FlowEnd(o, const_cast<BufferProcure*>(this), 1);
768  }
769  const_cast<BufferProcure*>(this)->oper = o;
770  }
771  return oper;
772 }
773 
774 
776 {
777  if (attr.isA(Tags::tag_name))
778  return PythonObject(getName());
779  if (attr.isA(Tags::tag_description))
780  return PythonObject(getDescription());
781  if (attr.isA(Tags::tag_category))
782  return PythonObject(getCategory());
783  if (attr.isA(Tags::tag_subcategory))
784  return PythonObject(getSubCategory());
785  if (attr.isA(Tags::tag_owner))
786  return PythonObject(getOwner());
787  if (attr.isA(Tags::tag_location))
788  return PythonObject(getLocation());
789  if (attr.isA(Tags::tag_producing))
791  if (attr.isA(Tags::tag_item))
792  return PythonObject(getItem());
793  if (attr.isA(Tags::tag_onhand))
794  return PythonObject(getOnHand());
795  if (attr.isA(Tags::tag_flowplans))
796  return new FlowPlanIterator(this);
797  if (attr.isA(Tags::tag_maximum))
798  return PythonObject(getMaximum());
799  if (attr.isA(Tags::tag_minimum))
800  return PythonObject(getMinimum());
805  if (attr.isA(Tags::tag_carrying_cost))
806  return PythonObject(getCarryingCost());
807  if (attr.isA(Tags::tag_hidden))
808  return PythonObject(getHidden());
809  if (attr.isA(Tags::tag_flows))
810  return new FlowIterator(this);
811  if (attr.isA(Tags::tag_level))
812  return PythonObject(getLevel());
813  if (attr.isA(Tags::tag_cluster))
814  return PythonObject(getCluster());
815  if (attr.isA(Tags::tag_members))
816  return new BufferIterator(this);
817  return NULL;
818 }
819 
820 
822 {
823  if (attr.isA(Tags::tag_name))
824  setName(field.getString());
825  else if (attr.isA(Tags::tag_description))
826  setDescription(field.getString());
827  else if (attr.isA(Tags::tag_category))
828  setCategory(field.getString());
829  else if (attr.isA(Tags::tag_subcategory))
830  setSubCategory(field.getString());
831  else if (attr.isA(Tags::tag_owner))
832  {
833  if (!field.check(Buffer::metadata))
834  {
835  PyErr_SetString(PythonDataException, "buffer owner must be of type buffer");
836  return -1;
837  }
838  Buffer* y = static_cast<Buffer*>(static_cast<PyObject*>(field));
839  setOwner(y);
840  }
841  else if (attr.isA(Tags::tag_location))
842  {
843  if (!field.check(Location::metadata))
844  {
845  PyErr_SetString(PythonDataException, "buffer location must be of type location");
846  return -1;
847  }
848  Location* y = static_cast<Location*>(static_cast<PyObject*>(field));
849  setLocation(y);
850  }
851  else if (attr.isA(Tags::tag_item))
852  {
853  if (!field.check(Item::metadata))
854  {
855  PyErr_SetString(PythonDataException, "buffer item must be of type item");
856  return -1;
857  }
858  Item* y = static_cast<Item*>(static_cast<PyObject*>(field));
859  setItem(y);
860  }
861  else if (attr.isA(Tags::tag_minimum))
862  setMinimum(field.getDouble());
863  else if (attr.isA(Tags::tag_maximum))
864  setMaximum(field.getDouble());
865  else if (attr.isA(Tags::tag_maximum_calendar))
866  {
867  if (!field.check(CalendarDouble::metadata))
868  {
869  PyErr_SetString(PythonDataException, "buffer maximum must be of type calendar_double");
870  return -1;
871  }
872  CalendarDouble* y = static_cast<CalendarDouble*>(static_cast<PyObject*>(field));
874  }
875  else if (attr.isA(Tags::tag_minimum_calendar))
876  {
877  if (!field.check(CalendarDouble::metadata))
878  {
879  PyErr_SetString(PythonDataException, "buffer minimum must be of type calendar_double");
880  return -1;
881  }
882  CalendarDouble* y = static_cast<CalendarDouble*>(static_cast<PyObject*>(field));
884  }
885  else if (attr.isA(Tags::tag_onhand))
886  setOnHand(field.getDouble());
887  else if (attr.isA(Tags::tag_carrying_cost))
888  setCarryingCost(field.getDouble());
889  else if (attr.isA(Tags::tag_producing))
890  {
891  if (!field.check(Operation::metadata))
892  {
893  PyErr_SetString(PythonDataException, "buffer producing must be of type operation");
894  return -1;
895  }
896  Operation* y = static_cast<Operation*>(static_cast<PyObject*>(field));
898  }
899  else if (attr.isA(Tags::tag_hidden))
900  setHidden(field.getBool());
901  else
902  return -1; // Error
903  return 0; // OK
904 }
905 
906 
908 {
909  if (attr.isA(Tags::tag_leadtime))
910  return PythonObject(getLeadtime());
911  if (attr.isA(Tags::tag_mininventory))
913  if (attr.isA(Tags::tag_maxinventory))
915  if (attr.isA(Tags::tag_mininterval))
917  if (attr.isA(Tags::tag_maxinterval))
919  if (attr.isA(Tags::tag_fence))
920  return PythonObject(getFence());
921  if (attr.isA(Tags::tag_size_minimum))
922  return PythonObject(getSizeMinimum());
923  if (attr.isA(Tags::tag_size_multiple))
924  return PythonObject(getSizeMultiple());
925  if (attr.isA(Tags::tag_size_maximum))
926  return PythonObject(getSizeMaximum());
927  return Buffer::getattro(attr);
928 }
929 
930 
932 {
933  if (attr.isA(Tags::tag_leadtime))
934  setLeadtime(field.getTimeperiod());
935  else if (attr.isA(Tags::tag_mininventory))
937  else if (attr.isA(Tags::tag_maxinventory))
939  else if (attr.isA(Tags::tag_mininterval))
941  else if (attr.isA(Tags::tag_maxinterval))
943  else if (attr.isA(Tags::tag_size_minimum))
944  setSizeMinimum(field.getDouble());
945  else if (attr.isA(Tags::tag_size_multiple))
946  setSizeMultiple(field.getDouble());
947  else if (attr.isA(Tags::tag_size_maximum))
948  setSizeMaximum(field.getDouble());
949  else if (attr.isA(Tags::tag_fence))
950  setFence(field.getTimeperiod());
951  else
952  return Buffer::setattro(attr, field);
953  return 0;
954 }
955 
956 } // end namespace