buffer.cpp

Go to the documentation of this file.
00001 /***************************************************************************
00002   file : $URL: https://frepple.svn.sourceforge.net/svnroot/frepple/tags/0.8.0/src/model/buffer.cpp $
00003   version : $LastChangedRevision: 1111 $  $LastChangedBy: jdetaeye $
00004   date : $LastChangedDate: 2009-12-13 17:41:27 +0100 (Sun, 13 Dec 2009) $
00005  ***************************************************************************/
00006 
00007 /***************************************************************************
00008  *                                                                         *
00009  * Copyright (C) 2007 by Johan De Taeye                                    *
00010  *                                                                         *
00011  * This library is free software; you can redistribute it and/or modify it *
00012  * under the terms of the GNU Lesser General Public License as published   *
00013  * by the Free Software Foundation; either version 2.1 of the License, or  *
00014  * (at your option) any later version.                                     *
00015  *                                                                         *
00016  * This library is distributed in the hope that it will be useful,         *
00017  * but WITHOUT ANY WARRANTY; without even the implied warranty of          *
00018  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser *
00019  * General Public License for more details.                                *
00020  *                                                                         *
00021  * You should have received a copy of the GNU Lesser General Public        *
00022  * License along with this library; if not, write to the Free Software     *
00023  * Foundation Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 *
00024  * USA                                                                     *
00025  *                                                                         *
00026  ***************************************************************************/
00027 
00028 #define FREPPLE_CORE
00029 #include "frepple/model.h"
00030 #include <math.h>
00031 
00032 // This is the name used for the dummy operation used to represent the
00033 // inventory.
00034 #define INVENTORY_OPERATION "Inventory of buffer '" + string(getName()) + "'"
00035 
00036 // This is the name used for the dummy operation used to represent procurements
00037 #define PROCURE_OPERATION "Procure for buffer '" + string(getName()) + "'"
00038 
00039 namespace frepple
00040 {
00041 
00042 template<class Buffer> DECLARE_EXPORT Tree utils::HasName<Buffer>::st;
00043 DECLARE_EXPORT const MetaCategory* Buffer::metadata;
00044 DECLARE_EXPORT const MetaClass* BufferDefault::metadata,
00045   *BufferInfinite::metadata,
00046   *BufferProcure::metadata;
00047 
00048 
00049 int Buffer::initialize()
00050 {
00051   // Initialize the metadata
00052   metadata = new MetaCategory("buffer", "buffers", reader, writer);
00053 
00054   // Initialize the Python class
00055   return FreppleCategory<Buffer>::initialize();
00056 }
00057 
00058 
00059 int BufferDefault::initialize()
00060 {
00061   // Initialize the metadata
00062   BufferDefault::metadata = new MetaClass(
00063     "buffer",
00064     "buffer_default",
00065     Object::createString<BufferDefault>, true);
00066 
00067   // Initialize the Python class
00068   return FreppleClass<BufferDefault,Buffer>::initialize();
00069 }
00070 
00071 
00072 int BufferInfinite::initialize()
00073 {
00074   // Initialize the metadata
00075   BufferInfinite::metadata = new MetaClass(
00076     "buffer",
00077     "buffer_infinite",
00078     Object::createString<BufferInfinite>);
00079 
00080   // Initialize the Python class
00081   return FreppleClass<BufferInfinite,Buffer>::initialize();
00082 }
00083 
00084 
00085 int BufferProcure::initialize()
00086 {
00087   // Initialize the metadata
00088   BufferProcure::metadata = new MetaClass(
00089     "buffer",
00090     "buffer_procure",
00091     Object::createString<BufferProcure>);
00092 
00093   // Initialize the Python class
00094   return FreppleClass<BufferProcure,Buffer>::initialize();
00095 }
00096 
00097 
00098 DECLARE_EXPORT void Buffer::setOnHand(double f)
00099 {
00100   // The dummy operation to model the inventory may need to be created
00101   Operation *o = Operation::find(INVENTORY_OPERATION);
00102   Flow *fl;
00103   if (!o)
00104   {
00105     // Create a fixed time operation with zero leadtime, hidden from the xml
00106     // output, hidden for the solver, and without problem detection.
00107     o = new OperationFixedTime(INVENTORY_OPERATION);
00108     Operation::add(o);  // No need to check again for existance
00109     o->setHidden(true);
00110     o->setDetectProblems(false);
00111     fl = new FlowEnd(o, this, 1);
00112   }
00113   else
00114     // Find the flow of this operation
00115     fl = const_cast<Flow*>(&*(o->getFlows().begin()));
00116 
00117   // Check valid pointers
00118   if (!fl || !o)
00119     throw LogicException("Failed creating inventory operation for '"
00120         + getName() + "'");
00121 
00122   // Make sure the sign of the flow is correct: +1 or -1.
00123   fl->setQuantity(f>=0.0 ? 1.0 : -1.0);
00124 
00125   // Create a dummy operationplan on the inventory operation
00126   OperationPlan::iterator i(o);
00127   if (i == OperationPlan::end())
00128   {
00129     // No operationplan exists yet
00130     OperationPlan *opplan = o->createOperationPlan(
00131       fabs(f), Date::infinitePast, Date::infinitePast);
00132     opplan->setLocked(true);
00133     opplan->instantiate();
00134   }
00135   else
00136   {
00137     // Update the existing operationplan
00138     i->setLocked(false);
00139     i->setQuantity(fabs(f));
00140     i->setLocked(true);
00141   }
00142   setChanged();
00143 }
00144 
00145 
00146 DECLARE_EXPORT double Buffer::getOnHand(Date d) const
00147 {
00148   double tmp(0.0);
00149   for (flowplanlist::const_iterator oo=flowplans.begin();
00150       oo!=flowplans.end(); ++oo)
00151   {
00152     if (oo->getDate() > d)
00153       // Found a flowplan with a later date.
00154       // Return the onhand after the previous flowplan.
00155       return tmp;
00156     tmp = oo->getOnhand();
00157   }
00158   // Found no flowplan: either we have specified a date later than the
00159   // last flowplan, either there are no flowplans at all.
00160   return tmp;
00161 }
00162 
00163 
00164 DECLARE_EXPORT double Buffer::getOnHand(Date d1, Date d2, bool min) const
00165 {
00166   // Swap parameters if required
00167   if (d2 < d1)
00168   {
00169     Date x(d1);
00170     d2 = d1;
00171     d2 = x;
00172   }
00173 
00174   // Loop through all flowplans
00175   double tmp(0.0), record(0.0);
00176   Date d, prev_Date;
00177   for (flowplanlist::const_iterator oo=flowplans.begin(); true; ++oo)
00178   {
00179     if (oo==flowplans.end() || oo->getDate() > d)
00180     {
00181       // Date has now changed or we have arrived at the end
00182 
00183       // New max?
00184       if (prev_Date < d1)
00185         // Not in active Date range: we simply follow the onhand profile
00186         record = tmp;
00187       else
00188       {
00189         // In the active range
00190         // New extreme?
00191         if (min) {if (tmp < record) record = tmp;}
00192         else {if (tmp > record) record = tmp;}
00193       }
00194 
00195       // Are we done now?
00196       if (prev_Date > d2 || oo==flowplans.end()) return record;
00197 
00198       // Set the variable with the new Date
00199       d = oo->getDate();
00200     }
00201     tmp = oo->getOnhand();
00202     prev_Date = oo->getDate();
00203   }
00204   // The above for-loop controls the exit. This line of code is never reached.
00205   throw LogicException("Unreachable code reached");
00206 }
00207 
00208 
00209 DECLARE_EXPORT void Buffer::writeElement(XMLOutput *o, const Keyword &tag, mode m) const
00210 {
00211   // Writing a reference
00212   if (m == REFERENCE)
00213   {
00214     o->writeElement(tag, Tags::tag_name, getName());
00215     return;
00216   }
00217 
00218   // Write the complete object
00219   if (m!= NOHEADER) o->BeginObject(tag, Tags::tag_name, getName());
00220 
00221   // Write own fields
00222   HasDescription::writeElement(o, tag);
00223   HasHierarchy<Buffer>::writeElement(o, tag);
00224   o->writeElement(Tags::tag_producing, producing_operation);
00225   o->writeElement(Tags::tag_item, it);
00226   o->writeElement(Tags::tag_location, loc);
00227   Plannable::writeElement(o, tag);
00228 
00229   // Onhand
00230   flowplanlist::const_iterator i = flowplans.begin();
00231   // Loop through the flowplans at the start of the horizon
00232   for (; i!=flowplans.end() && i->getType()!=1 && !i->getDate(); ++i) ;
00233   if (i!=flowplans.end() && i->getType()==1)
00234   {
00235     // A flowplan has been found
00236     const FlowPlan *fp = dynamic_cast<const FlowPlan*>(&*i);
00237     if (fp
00238         && fp->getFlow()->getOperation()->getName() == string(INVENTORY_OPERATION)
00239         && fabs(fp->getQuantity()) > ROUNDING_ERROR)
00240       o->writeElement(Tags::tag_onhand, fp->getQuantity());
00241   }
00242 
00243   // Minimum and maximum inventory targets, carrying cost
00244   o->writeElement(Tags::tag_minimum, min_cal);
00245   o->writeElement(Tags::tag_maximum, max_cal);
00246   if (getCarryingCost()!= 0.0)
00247     o->writeElement(Tags::tag_carrying_cost, getCarryingCost());
00248 
00249   // Write extra plan information
00250   i = flowplans.begin();
00251   if ((o->getContentType() == XMLOutput::PLAN
00252       || o->getContentType() == XMLOutput::PLANDETAIL) && i!=flowplans.end())
00253   {
00254     o->BeginObject(Tags::tag_flowplans);
00255     for (; i!=flowplans.end(); ++i)
00256       if (i->getType()==1)
00257         dynamic_cast<const FlowPlan*>(&*i)->writeElement(o, Tags::tag_flowplan);
00258     o->EndObject(Tags::tag_flowplans);
00259   }
00260 
00261   // Ending tag
00262   o->EndObject(tag);
00263 }
00264 
00265 
00266 DECLARE_EXPORT void Buffer::beginElement(XMLInput& pIn, const Attribute& pAttr)
00267 {
00268   if (pAttr.isA(Tags::tag_flow)
00269       && pIn.getParentElement().first.isA(Tags::tag_flows))
00270   {
00271     Flow *f =
00272       dynamic_cast<Flow*>(MetaCategory::ControllerDefault(Flow::metadata,pIn.getAttributes()));
00273     if (f) f->setBuffer(this);
00274     pIn.readto (f);
00275   }
00276   else if (pAttr.isA(Tags::tag_producing))
00277     pIn.readto( Operation::reader(Operation::metadata,pIn.getAttributes()) );
00278   else if (pAttr.isA(Tags::tag_item))
00279     pIn.readto( Item::reader(Item::metadata,pIn.getAttributes()) );
00280   else if (pAttr.isA(Tags::tag_minimum) || pAttr.isA(Tags::tag_maximum))
00281     pIn.readto( Calendar::reader(Calendar::metadata,pIn.getAttributes()) );
00282   else if (pAttr.isA(Tags::tag_location))
00283     pIn.readto( Location::reader(Location::metadata,pIn.getAttributes()) );
00284   else if (pAttr.isA(Tags::tag_flowplans))
00285     pIn.IgnoreElement();
00286   else
00287     HasHierarchy<Buffer>::beginElement(pIn, pAttr);
00288 }
00289 
00290 
00291 DECLARE_EXPORT void Buffer::endElement(XMLInput& pIn, const Attribute& pAttr, const DataElement& pElement)
00292 {
00293   if (pAttr.isA(Tags::tag_producing))
00294   {
00295     Operation *b = dynamic_cast<Operation*>(pIn.getPreviousObject());
00296     if (b) setProducingOperation(b);
00297     else throw LogicException("Incorrect object type during read operation");
00298   }
00299   else if (pAttr.isA(Tags::tag_item))
00300   {
00301     Item *a = dynamic_cast<Item*>(pIn.getPreviousObject());
00302     if (a) setItem(a);
00303     else throw LogicException("Incorrect object type during read operation");
00304   }
00305   else if (pAttr.isA(Tags::tag_onhand))
00306     setOnHand(pElement.getDouble());
00307   else if (pAttr.isA(Tags::tag_minimum))
00308   {
00309     CalendarDouble *mincal =
00310       dynamic_cast<CalendarDouble*>(pIn.getPreviousObject());
00311     if (mincal)
00312       setMinimum(mincal);
00313     else
00314     {
00315       Calendar *c = dynamic_cast<Calendar*>(pIn.getPreviousObject());
00316       if (!c)
00317         throw LogicException("Incorrect object type during read operation");
00318       throw DataException("Calendar '" + c->getName() +
00319           "' has invalid type for use as buffer min calendar");
00320     }
00321   }
00322   else if (pAttr.isA(Tags::tag_maximum))
00323   {
00324     CalendarDouble *maxcal =
00325       dynamic_cast<CalendarDouble*>(pIn.getPreviousObject());
00326     if (maxcal)
00327       setMaximum(maxcal);
00328     else
00329     {
00330       Calendar *c = dynamic_cast<Calendar*>(pIn.getPreviousObject());
00331       if (!c)
00332         throw LogicException("Incorrect object type during read operation");
00333       throw DataException("Calendar '" + c->getName() +
00334           "' has invalid type for use as buffer max calendar");
00335     }
00336   }
00337   else if (pAttr.isA(Tags::tag_location))
00338   {
00339     Location * d = dynamic_cast<Location*>(pIn.getPreviousObject());
00340     if (d) setLocation(d);
00341     else throw LogicException("Incorrect object type during read operation");
00342   }
00343   else if (pAttr.isA(Tags::tag_carrying_cost))
00344     setCarryingCost(pElement.getDouble());
00345   else
00346   {
00347     Plannable::endElement(pIn, pAttr, pElement);
00348     HasDescription::endElement(pIn, pAttr, pElement);
00349     HasHierarchy<Buffer>::endElement(pIn, pAttr, pElement);
00350   }
00351 }
00352 
00353 
00354 DECLARE_EXPORT void Buffer::setMinimum(CalendarDouble *cal)
00355 {
00356   // Resetting the same calendar
00357   if (min_cal == cal) return;
00358 
00359   // Mark as changed
00360   setChanged();
00361 
00362   // Calendar is already set: delete previous events.
00363   if (min_cal)
00364   {
00365     for (flowplanlist::iterator oo=flowplans.begin(); oo!=flowplans.end(); )
00366       if (oo->getType() == 3)
00367       {
00368         flowplans.erase(&(*oo));
00369         delete &(*(oo++));
00370       }
00371       else ++oo;
00372   }
00373 
00374   // Null pointer passed
00375   if (!cal) return;
00376 
00377   // Create timeline structures for every event. A new entry is created only
00378   // when the value changes.
00379   min_cal = const_cast< CalendarDouble* >(cal);
00380   double curMin = 0.0;
00381   for (CalendarDouble::EventIterator x(min_cal); x.getDate()<Date::infiniteFuture; ++x)
00382     if (curMin != x.getValue())
00383     {
00384       curMin = x.getValue();
00385       flowplanlist::EventMinQuantity *newBucket =
00386         new flowplanlist::EventMinQuantity(x.getDate(), curMin);
00387       flowplans.insert(newBucket);
00388     }
00389 }
00390 
00391 
00392 DECLARE_EXPORT void Buffer::setMaximum(CalendarDouble *cal)
00393 {
00394   // Resetting the same calendar
00395   if (max_cal == cal) return;
00396 
00397   // Mark as changed
00398   setChanged();
00399 
00400   // Calendar is already set: delete previous events.
00401   if (max_cal)
00402   {
00403     for (flowplanlist::iterator oo=flowplans.begin(); oo!=flowplans.end(); )
00404       if (oo->getType() == 4)
00405       {
00406         flowplans.erase(&(*oo));
00407         delete &(*(oo++));
00408       }
00409       else ++oo;
00410   }
00411 
00412   // Null pointer passed
00413   if (!cal) return;
00414 
00415   // Create timeline structures for every bucket. A new entry is created only
00416   // when the value changes.
00417   max_cal = const_cast<CalendarDouble*>(cal);
00418   double curMax = 0.0;
00419   for (CalendarDouble::EventIterator x(min_cal); x.getDate()<Date::infiniteFuture; ++x)
00420     if (curMax != x.getValue())
00421     {
00422       curMax = x.getValue();
00423       flowplanlist::EventMaxQuantity *newBucket =
00424         new flowplanlist::EventMaxQuantity(x.getDate(), curMax);
00425       flowplans.insert(newBucket);
00426     }
00427 }
00428 
00429 
00430 DECLARE_EXPORT void Buffer::deleteOperationPlans(bool deleteLocked)
00431 {
00432   // Delete the operationplans
00433   for (flowlist::iterator i=flows.begin(); i!=flows.end(); ++i)
00434     OperationPlan::deleteOperationPlans(i->getOperation(),deleteLocked);
00435 
00436   // Mark to recompute the problems
00437   setChanged();
00438 }
00439 
00440 
00441 DECLARE_EXPORT Buffer::~Buffer()
00442 {
00443   // Delete all operationplans.
00444   // An alternative logic would be to delete only the flowplans for this
00445   // buffer and leave the rest of the plan untouched. The currently
00446   // implemented method is way more drastic...
00447   deleteOperationPlans(true);
00448 
00449   // The Flow objects are automatically deleted by the destructor of the
00450   // Association list class.
00451 
00452   // Remove the inventory operation
00453   Operation *invoper = Operation::find(INVENTORY_OPERATION);
00454   if (invoper) delete invoper;
00455 }
00456 
00457 
00458 DECLARE_EXPORT void Buffer::followPegging
00459   (PeggingIterator& iter, FlowPlan* curflowplan, short nextlevel, double curqty, double curfactor)
00460 {
00461 
00462   double peggedQty(0);
00463   Buffer::flowplanlist::const_iterator f = getFlowPlans().begin(curflowplan);
00464 
00465   if (curflowplan->getQuantity() < -ROUNDING_ERROR && !iter.isDownstream())
00466   {
00467     // CASE 1:
00468     // This is a flowplan consuming from a buffer. Navigating upstream means
00469     // finding the flowplans producing this consumed material.
00470     double endQty = f->getCumulativeConsumed();
00471     double startQty = endQty + f->getQuantity();
00472     if (f->getCumulativeProduced() <= startQty)
00473     {
00474       // CASE 1A: Not produced enough yet: move forward
00475       while (f!=getFlowPlans().end()
00476           && f->getCumulativeProduced() <= startQty) ++f;
00477       while (f!=getFlowPlans().end()
00478           && ( (f->getQuantity()<=0 && f->getCumulativeProduced() < endQty)
00479               || (f->getQuantity()>0
00480                   && f->getCumulativeProduced()-f->getQuantity() < endQty))
00481             )
00482       {
00483         if (f->getQuantity() > ROUNDING_ERROR)
00484         {
00485           double newqty = f->getQuantity();
00486           if (f->getCumulativeProduced()-f->getQuantity() < startQty)
00487             newqty -= startQty - (f->getCumulativeProduced()-f->getQuantity());
00488           if (f->getCumulativeProduced() > endQty)
00489             newqty -= f->getCumulativeProduced() - endQty;
00490           peggedQty += newqty;
00491           const FlowPlan *x = dynamic_cast<const FlowPlan*>(&(*f));
00492           iter.updateStack(nextlevel,
00493             -curqty*newqty/curflowplan->getQuantity(),
00494             curfactor*newqty/f->getQuantity(),
00495             curflowplan, x);
00496         }
00497         ++f;
00498       }
00499     }
00500     else
00501     {
00502       // CASE 1B: Produced too much already: move backward
00503       while ( f!=getFlowPlans().end()
00504           && ((f->getQuantity()<=0 && f->getCumulativeProduced() > endQty)
00505               || (f->getQuantity()>0
00506                   && f->getCumulativeProduced()-f->getQuantity() > endQty))) --f;
00507       while (f!=getFlowPlans().end() && f->getCumulativeProduced() > startQty)
00508       {
00509         if (f->getQuantity() > ROUNDING_ERROR)
00510         {
00511           double newqty = f->getQuantity();
00512           if (f->getCumulativeProduced()-f->getQuantity() < startQty)
00513             newqty -= startQty - (f->getCumulativeProduced()-f->getQuantity());
00514           if (f->getCumulativeProduced() > endQty)
00515             newqty -= f->getCumulativeProduced() - endQty;
00516           peggedQty += newqty;
00517           const FlowPlan *x = dynamic_cast<const FlowPlan*>(&(*f));
00518           iter.updateStack(nextlevel,
00519               -curqty*newqty/curflowplan->getQuantity(),
00520               curfactor*newqty/f->getQuantity(),
00521               curflowplan, x);
00522         }
00523         --f;
00524       }
00525     }
00526     if (peggedQty < endQty - startQty - ROUNDING_ERROR)
00527       // Unproduced material (i.e. material that is consumed but never
00528       // produced) is handled with a special entry on the stack.
00529       iter.updateStack(nextlevel,
00530           curqty*(peggedQty - endQty + startQty)/curflowplan->getQuantity(),
00531           curfactor,
00532           curflowplan,
00533           NULL,
00534           false);
00535     return;
00536   }
00537 
00538   if (curflowplan->getQuantity() > ROUNDING_ERROR && iter.isDownstream())
00539   {
00540     // CASE 2:
00541     // This is a flowplan producing in a buffer. Navigating downstream means
00542     // finding the flowplans consuming this produced material.
00543     double endQty = f->getCumulativeProduced();
00544     double startQty = endQty - f->getQuantity();
00545     if (f->getCumulativeConsumed() <= startQty)
00546     {
00547       // CASE 2A: Not consumed enough yet: move forward
00548       while (f!=getFlowPlans().end()
00549           && f->getCumulativeConsumed() <= startQty) ++f;
00550       while (f!=getFlowPlans().end()
00551           && ( (f->getQuantity()<=0
00552               && f->getCumulativeConsumed()+f->getQuantity() < endQty)
00553               || (f->getQuantity()>0 && f->getCumulativeConsumed() < endQty))
00554             )
00555       {
00556         if (f->getQuantity() < -ROUNDING_ERROR)
00557         {
00558           double newqty = - f->getQuantity();
00559           if (f->getCumulativeConsumed()+f->getQuantity() < startQty)
00560             newqty -= startQty - (f->getCumulativeConsumed()+f->getQuantity());
00561           if (f->getCumulativeConsumed() > endQty)
00562             newqty -= f->getCumulativeConsumed() - endQty;
00563           peggedQty += newqty;
00564           const FlowPlan *x = dynamic_cast<const FlowPlan*>(&(*f));
00565           iter.updateStack(nextlevel,
00566             curqty*newqty/curflowplan->getQuantity(),
00567             -curfactor*newqty/f->getQuantity(),
00568             x, curflowplan);
00569         }
00570         ++f;
00571       }
00572     }
00573     else
00574     {
00575       // CASE 2B: Consumed too much already: move backward
00576       while ( f!=getFlowPlans().end()
00577           && ((f->getQuantity()<=0 && f->getCumulativeConsumed()+f->getQuantity() < endQty)
00578               || (f->getQuantity()>0 && f->getCumulativeConsumed() < endQty))) --f;
00579       while (f!=getFlowPlans().end() && f->getCumulativeConsumed() > startQty)
00580       {
00581         if (f->getQuantity() < -ROUNDING_ERROR)
00582         {
00583           double newqty = - f->getQuantity();
00584           if (f->getCumulativeConsumed()+f->getQuantity() < startQty)
00585             newqty -= startQty - (f->getCumulativeConsumed()+f->getQuantity());
00586           if (f->getCumulativeConsumed() > endQty)
00587             newqty -= f->getCumulativeConsumed() - endQty;
00588           peggedQty += newqty;
00589           const FlowPlan *x = dynamic_cast<const FlowPlan*>(&(*f));
00590           iter.updateStack(nextlevel,
00591             curqty*newqty/curflowplan->getQuantity(),
00592             -curfactor*newqty/f->getQuantity(),
00593             x, curflowplan);
00594         }
00595         --f;
00596       }
00597     }
00598     if (peggedQty < endQty - startQty)
00599       // Unpegged material (i.e. material that is produced but never consumed)
00600       // is handled with a special entry on the stack.
00601       iter.updateStack(nextlevel,
00602         curqty*(endQty - startQty - peggedQty)/curflowplan->getQuantity(),
00603         curfactor,
00604         NULL, curflowplan,
00605         false);
00606     return;
00607   }
00608 }
00609 
00610 
00611 DECLARE_EXPORT void BufferInfinite::writeElement
00612 (XMLOutput *o, const Keyword &tag, mode m) const
00613 {
00614   // Writing a reference
00615   if (m == REFERENCE)
00616   {
00617     o->writeElement
00618       (tag, Tags::tag_name, getName(), Tags::tag_type, getType().type);
00619     return;
00620   }
00621 
00622   // Write the complete object
00623   if (m != NOHEADER) o->BeginObject
00624     (tag, Tags::tag_name, getName(), Tags::tag_type, getType().type);
00625 
00626   // Write the fields and an ending tag
00627   Buffer::writeElement(o, tag, NOHEADER);
00628 }
00629 
00630 
00631 DECLARE_EXPORT void BufferProcure::endElement(XMLInput& pIn, const Attribute& pAttr, const DataElement& pElement)
00632 {
00633   if (pAttr.isA(Tags::tag_leadtime))
00634     setLeadtime(pElement.getTimeperiod());
00635   else if (pAttr.isA(Tags::tag_fence))
00636     setFence(pElement.getTimeperiod());
00637   else if (pAttr.isA(Tags::tag_size_maximum))
00638     setSizeMaximum(pElement.getDouble());
00639   else if (pAttr.isA(Tags::tag_size_minimum))
00640     setSizeMinimum(pElement.getDouble());
00641   else if (pAttr.isA(Tags::tag_size_multiple))
00642     setSizeMultiple(pElement.getDouble());
00643   else if (pAttr.isA(Tags::tag_mininterval))
00644     setMinimumInterval(pElement.getTimeperiod());
00645   else if (pAttr.isA(Tags::tag_maxinterval))
00646     setMaximumInterval(pElement.getTimeperiod());
00647   else if (pAttr.isA(Tags::tag_mininventory))
00648     setMinimumInventory(pElement.getDouble());
00649   else if (pAttr.isA(Tags::tag_maxinventory))
00650     setMaximumInventory(pElement.getDouble());
00651   else
00652     Buffer::endElement(pIn, pAttr, pElement);
00653 }
00654 
00655 
00656 DECLARE_EXPORT void BufferProcure::writeElement(XMLOutput *o, const Keyword &tag, mode m) const
00657 {
00658   // Writing a reference
00659   if (m == REFERENCE)
00660   {
00661     o->writeElement
00662       (tag, Tags::tag_name, getName(), Tags::tag_type, getType().type);
00663     return;
00664   }
00665 
00666   // Write the complete object
00667   if (m != NOHEADER) o->BeginObject
00668     (tag, Tags::tag_name, getName(), Tags::tag_type, getType().type);
00669 
00670   // Write the extra fields
00671   if (leadtime) o->writeElement(Tags::tag_leadtime, leadtime);
00672   if (fence) o->writeElement(Tags::tag_fence, fence);
00673   if (size_maximum != DBL_MAX) o->writeElement(Tags::tag_size_maximum, size_maximum);
00674   if (size_minimum) o->writeElement(Tags::tag_size_minimum, size_minimum);
00675   if (size_multiple) o->writeElement(Tags::tag_size_multiple, size_multiple);
00676   if (min_interval) o->writeElement(Tags::tag_mininterval, min_interval);
00677   if (max_interval) o->writeElement(Tags::tag_maxinterval, max_interval);
00678   if (min_inventory) o->writeElement(Tags::tag_mininventory, min_inventory);
00679   if (max_inventory) o->writeElement(Tags::tag_maxinventory, max_inventory);
00680 
00681   // Write the fields and an ending tag
00682   Buffer::writeElement(o, tag, NOHEADER);
00683 }
00684 
00685 
00686 DECLARE_EXPORT Operation* BufferProcure::getOperation() const
00687 {
00688   if (!oper)
00689   {
00690     Operation *o = Operation::find(PROCURE_OPERATION);
00691     if (!o)
00692     {
00693       // Create the operation if it didn't exist yet
00694       o = new OperationFixedTime(PROCURE_OPERATION);
00695       static_cast<OperationFixedTime*>(o)->setDuration(leadtime);
00696       // Ideally we would like to hide the procurement operation itself.
00697       // But in that case we need a different way to show the procurements
00698       // to the outside world.
00699       // o->setHidden(true);
00700       Operation::add(o);  // No need to check again for existance
00701       new FlowEnd(o, const_cast<BufferProcure*>(this), 1);
00702     }
00703     const_cast<BufferProcure*>(this)->oper = o;
00704   }
00705   return oper;
00706 }
00707 
00708 
00709 DECLARE_EXPORT PyObject* Buffer::getattro(const Attribute& attr)
00710 {
00711   if (attr.isA(Tags::tag_name))
00712     return PythonObject(getName());
00713   if (attr.isA(Tags::tag_description))
00714     return PythonObject(getDescription());
00715   if (attr.isA(Tags::tag_category))
00716     return PythonObject(getCategory());
00717   if (attr.isA(Tags::tag_subcategory))
00718     return PythonObject(getSubCategory());
00719   if (attr.isA(Tags::tag_owner))
00720     return PythonObject(getOwner());
00721   if (attr.isA(Tags::tag_location))
00722     return PythonObject(getLocation());
00723   if (attr.isA(Tags::tag_producing))
00724     return PythonObject(getProducingOperation());
00725   if (attr.isA(Tags::tag_item))
00726     return PythonObject(getItem());
00727   if (attr.isA(Tags::tag_onhand))
00728     return PythonObject(getOnHand());
00729   if (attr.isA(Tags::tag_flowplans))
00730     return new FlowPlanIterator(this);
00731   if (attr.isA(Tags::tag_maximum))
00732     return PythonObject(getMaximum());
00733   if (attr.isA(Tags::tag_minimum))
00734     return PythonObject(getMinimum());
00735   if (attr.isA(Tags::tag_carrying_cost))
00736     return PythonObject(getCarryingCost());
00737   if (attr.isA(Tags::tag_hidden))
00738     return PythonObject(getHidden());
00739   if (attr.isA(Tags::tag_flows))
00740     return new FlowIterator(this);
00741   if (attr.isA(Tags::tag_level))
00742     return PythonObject(getLevel());
00743   if (attr.isA(Tags::tag_cluster))
00744     return PythonObject(getCluster());
00745   // @todo support member iteration for buffer, res, dem, item, ...
00746   // PythonBufferIterator becomes an abstract class: defines the pytype and an abstract iternext.
00747   // 2 subclasses then implement it: an iterator over all buffers, and another one over all members.
00748   return NULL;
00749 }
00750 
00751 
00752 DECLARE_EXPORT int Buffer::setattro(const Attribute& attr, const PythonObject& field)
00753 {
00754   if (attr.isA(Tags::tag_name))
00755     setName(field.getString());
00756   else if (attr.isA(Tags::tag_description))
00757     setDescription(field.getString());
00758   else if (attr.isA(Tags::tag_category))
00759     setCategory(field.getString());
00760   else if (attr.isA(Tags::tag_subcategory))
00761     setSubCategory(field.getString());
00762   else if (attr.isA(Tags::tag_owner))
00763   {
00764     if (!field.check(Buffer::metadata))
00765     {
00766       PyErr_SetString(PythonDataException, "buffer owner must be of type buffer");
00767       return -1;
00768     }
00769     Buffer* y = static_cast<Buffer*>(static_cast<PyObject*>(field));
00770     setOwner(y);
00771   }
00772   else if (attr.isA(Tags::tag_location))
00773   {
00774     if (!field.check(Location::metadata))
00775     {
00776       PyErr_SetString(PythonDataException, "buffer location must be of type location");
00777       return -1;
00778     }
00779     Location* y = static_cast<Location*>(static_cast<PyObject*>(field));
00780     setLocation(y);
00781   }
00782   else if (attr.isA(Tags::tag_item))
00783   {
00784     if (!field.check(Item::metadata))
00785     {
00786       PyErr_SetString(PythonDataException, "buffer item must be of type item");
00787       return -1;
00788     }
00789     Item* y = static_cast<Item*>(static_cast<PyObject*>(field));
00790     setItem(y);
00791   }
00792   else if (attr.isA(Tags::tag_maximum))
00793   {
00794     if (!field.check(CalendarDouble::metadata))
00795     {
00796       PyErr_SetString(PythonDataException, "buffer maximum must be of type calendar_double");
00797       return -1;
00798     }
00799     CalendarDouble* y = static_cast<CalendarDouble*>(static_cast<PyObject*>(field));
00800     setMaximum(y);
00801   }
00802   else if (attr.isA(Tags::tag_minimum))
00803   {
00804     if (!field.check(CalendarDouble::metadata))
00805     {
00806       PyErr_SetString(PythonDataException, "buffer minimum must be of type calendar_double");
00807       return -1;
00808     }
00809     CalendarDouble* y = static_cast<CalendarDouble*>(static_cast<PyObject*>(field));
00810     setMinimum(y);
00811   }
00812   else if (attr.isA(Tags::tag_onhand))
00813     setOnHand(field.getDouble());
00814   else if (attr.isA(Tags::tag_carrying_cost))
00815     setCarryingCost(field.getDouble());
00816   else if (attr.isA(Tags::tag_producing))
00817   {
00818     if (!field.check(Operation::metadata))
00819     {
00820       PyErr_SetString(PythonDataException, "buffer producing must be of type operation");
00821       return -1;
00822     }
00823     Operation* y = static_cast<Operation*>(static_cast<PyObject*>(field));
00824     setProducingOperation(y);
00825   }
00826   else if (attr.isA(Tags::tag_hidden))
00827     setHidden(field.getBool());
00828   else
00829     return -1;  // Error
00830   return 0;  // OK
00831 }
00832 
00833 
00834 DECLARE_EXPORT PyObject* BufferProcure::getattro(const Attribute& attr)
00835 {
00836   if (attr.isA(Tags::tag_leadtime))
00837     return PythonObject(getLeadtime());
00838   if (attr.isA(Tags::tag_mininventory))
00839     return PythonObject(getMinimumInventory());
00840   if (attr.isA(Tags::tag_maxinventory))
00841     return PythonObject(getMaximumInventory());
00842   if (attr.isA(Tags::tag_mininterval))
00843     return PythonObject(getMinimumInterval());
00844   if (attr.isA(Tags::tag_maxinterval))
00845     return PythonObject(getMaximumInterval());
00846   if (attr.isA(Tags::tag_fence))
00847     return PythonObject(getFence());
00848   if (attr.isA(Tags::tag_size_minimum))
00849     return PythonObject(getSizeMinimum());
00850   if (attr.isA(Tags::tag_size_multiple))
00851     return PythonObject(getSizeMultiple());
00852   if (attr.isA(Tags::tag_size_maximum))
00853     return PythonObject(getSizeMaximum());
00854   return Buffer::getattro(attr);
00855 }
00856 
00857 
00858 DECLARE_EXPORT int BufferProcure::setattro(const Attribute& attr, const PythonObject& field)
00859 {
00860   if (attr.isA(Tags::tag_leadtime))
00861     setLeadtime(field.getTimeperiod());
00862   else if (attr.isA(Tags::tag_mininventory))
00863     setMinimumInventory(field.getDouble());
00864   else if (attr.isA(Tags::tag_maxinventory))
00865     setMaximumInventory(field.getDouble());
00866   else if (attr.isA(Tags::tag_mininterval))
00867     setMinimumInterval(field.getTimeperiod());
00868   else if (attr.isA(Tags::tag_maxinterval))
00869     setMaximumInterval(field.getTimeperiod());
00870   else if (attr.isA(Tags::tag_size_minimum))
00871     setSizeMinimum(field.getDouble());
00872   else if (attr.isA(Tags::tag_size_multiple))
00873     setSizeMultiple(field.getDouble());
00874   else if (attr.isA(Tags::tag_size_maximum))
00875     setSizeMaximum(field.getDouble());
00876   else if (attr.isA(Tags::tag_fence))
00877     setFence(field.getTimeperiod());
00878   else
00879     return Buffer::setattro(attr, field);
00880   return 0;
00881 }
00882 
00883 } // end namespace