operationplan.cpp
Go to the documentation of this file.
00001 /***************************************************************************
00002   file : $URL: https://frepple.svn.sourceforge.net/svnroot/frepple/tags/0.9.1/src/model/operationplan.cpp $
00003   version : $LastChangedRevision: 1656 $  $LastChangedBy: jdetaeye $
00004   date : $LastChangedDate: 2012-03-27 19:05:34 +0200 (Tue, 27 Mar 2012) $
00005  ***************************************************************************/
00006 
00007 /***************************************************************************
00008  *                                                                         *
00009  * Copyright (C) 2007-2012 by Johan De Taeye, frePPLe bvba                 *
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 
00031 namespace frepple
00032 {
00033 
00034 DECLARE_EXPORT const MetaClass* OperationPlan::metadata;
00035 DECLARE_EXPORT const MetaCategory* OperationPlan::metacategory;
00036 DECLARE_EXPORT unsigned long OperationPlan::counterMin = 1;
00037 // The value of the max counter is hard-coded to 2^31 - 1. This value is the
00038 // highest positive integer number that can safely be used on 32-bit platforms.
00039 // An alternative approach is to use the value ULONG_MAX, but this has the
00040 // disadvantage of not being portable across platforms and tools.
00041 DECLARE_EXPORT unsigned long OperationPlan::counterMax = 2147483647;
00042 
00043 
00044 int OperationPlan::initialize()
00045 {
00046   // Initialize the metadata
00047   OperationPlan::metacategory = new MetaCategory("operationplan", "operationplans",
00048       OperationPlan::createOperationPlan, OperationPlan::writer);
00049   OperationPlan::metadata = new MetaClass("operationplan", "operationplan");
00050 
00051   // Initialize the Python type
00052   PythonType& x = FreppleCategory<OperationPlan>::getType();
00053   x.setName("operationplan");
00054   x.setDoc("frePPLe operationplan");
00055   x.supportgetattro();
00056   x.supportsetattro();
00057   x.supportstr();
00058   x.supportcreate(create);
00059   x.addMethod("toXML", toXML, METH_VARARGS, "return a XML representation");
00060   const_cast<MetaClass*>(metadata)->pythonClass = x.type_object();
00061   return x.typeReady();
00062 }
00063 
00064 
00065 void DECLARE_EXPORT OperationPlan::setChanged(bool b)
00066 {
00067   if (owner)
00068     owner->setChanged(b);
00069   else
00070   {
00071     oper->setChanged(b);
00072     if (dmd) dmd->setChanged();
00073   }
00074 }
00075 
00076 
00077 DECLARE_EXPORT Object* OperationPlan::createOperationPlan
00078 (const MetaClass* cat, const AttributeList& in)
00079 {
00080   // Pick up the action attribute
00081   Action action = MetaClass::decodeAction(in);
00082 
00083   // Decode the attributes
00084   const DataElement* opnameElement = in.get(Tags::tag_operation);
00085   if (!*opnameElement && action==ADD)
00086     // Operation name required
00087     throw DataException("Missing operation attribute");
00088   string opname = *opnameElement ? opnameElement->getString() : "";
00089 
00090   // Decode the operationplan identifier
00091   unsigned long id = 0;
00092   const DataElement* idfier = in.get(Tags::tag_id);
00093   if (*idfier) id = idfier->getUnsignedLong();
00094   if (!id && (action==CHANGE || action==REMOVE))
00095     // Identifier is required
00096     throw DataException("Missing operationplan identifier");
00097 
00098   // If an identifier is specified, we look up this operation plan
00099   OperationPlan* opplan = NULL;
00100   if (id)
00101   {
00102     opplan = OperationPlan::findId(id);
00103     if (opplan && !opname.empty()
00104         && opplan->getOperation()->getName()==opname)
00105     {
00106       // Previous and current operations don't match.
00107       ostringstream ch;
00108       ch << "Operationplan identifier " << id
00109           << " defined multiple times with different operations: '"
00110           << opplan->getOperation() << "' & '" << opname << "'";
00111       throw DataException(ch.str());
00112     }
00113   }
00114 
00115   // Execute the proper action
00116   switch (action)
00117   {
00118     case REMOVE:
00119       if (opplan)
00120       {
00121         // Send out the notification to subscribers
00122         if (opplan->getType().raiseEvent(opplan, SIG_REMOVE))
00123           // Delete it
00124           delete opplan;
00125         else
00126         {
00127           // The callbacks disallowed the deletion!
00128           ostringstream ch;
00129           ch << "Can't delete operationplan with identifier " << id;
00130           throw DataException(ch.str());
00131         }
00132       }
00133       else
00134       {
00135         ostringstream ch;
00136         ch << "Operationplan with identifier " << id << " doesn't exist";
00137         throw DataException(ch.str());
00138       }
00139       return NULL;
00140     case ADD:
00141       if (opplan)
00142       {
00143         ostringstream ch;
00144         ch << "Operationplan with identifier " << id
00145             << " already exists and can't be added again";
00146         throw DataException(ch.str());
00147       }
00148       if (opname.empty())
00149         throw DataException
00150         ("Operation name missing for creating an operationplan");
00151       break;
00152     case CHANGE:
00153       if (!opplan)
00154       {
00155         ostringstream ch;
00156         ch << "Operationplan with identifier " << id << " doesn't exist";
00157         throw DataException(ch.str());
00158       }
00159       break;
00160     case ADD_CHANGE: ;
00161   }
00162 
00163   // Return the existing operationplan
00164   if (opplan) return opplan;
00165 
00166   // Create a new operation plan
00167   Operation* oper = Operation::find(opname);
00168   if (!oper)
00169   {
00170     // Can't create operationplan because the operation doesn't exist
00171     throw DataException("Operation '" + opname + "' doesn't exist");
00172   }
00173   else
00174   {
00175     // Create an operationplan
00176     opplan = oper->createOperationPlan(0.0,Date::infinitePast,Date::infinitePast,NULL,NULL,id,false);
00177     if (!opplan->getType().raiseEvent(opplan, SIG_ADD))
00178     {
00179       delete opplan;
00180       throw DataException("Can't create operationplan");
00181     }
00182     return opplan;
00183   }
00184 }
00185 
00186 
00187 DECLARE_EXPORT OperationPlan* OperationPlan::findId(unsigned long l)
00188 {
00189   // We are garantueed that there are no operationplans that have an id equal
00190   // or higher than the current counter. This is garantueed by the
00191   // instantiate() method.
00192   if (l >= counterMin && l <= counterMax) return NULL;
00193 
00194   // Loop through all operationplans.
00195   for (OperationPlan::iterator i = begin(); i != end(); ++i)
00196     if (i->id == l) return &*i;
00197 
00198   // This ID was not found
00199   return NULL;
00200 }
00201 
00202 
00203 DECLARE_EXPORT bool OperationPlan::activate(bool useMinCounter)
00204 {
00205   // At least a valid operation pointer must exist
00206   if (!oper) throw LogicException("Initializing an invalid operationplan");
00207 
00208   // Avoid zero quantity on top-operationplans
00209   if (getQuantity() <= 0.0 && !owner)
00210   {
00211     delete this;
00212     return false;
00213   }
00214 
00215   // Call any operation specific initialisation logic
00216   if (!oper->extraInstantiate(this))
00217   {
00218     delete this;
00219     return false;
00220   }
00221 
00222   // Instantiate all suboperationplans as well
00223   for (OperationPlan::iterator x(this); x != end(); ++x)
00224     x->activate();
00225 
00226   // Create unique identifier
00227   // Having an identifier assigned is an important flag.
00228   // Only operation plans with an id :
00229   //   - can be linked in the global operation plan list.
00230   //   - can have problems (this results from the previous point).
00231   //   - can be linked with a demand.
00232   // These properties allow us to delete operation plans without an id faster.
00233   static Mutex onlyOne;
00234   {
00235     ScopeMutexLock l(onlyOne);  // Need to assure that ids are unique!
00236     if (id)
00237     {
00238       // An identifier was read in from input
00239       if (id < counterMin || id > counterMax)
00240       {
00241         // The assigned id potentially clashes with an existing operationplan.
00242         // Check whether it clashes with existing operationplans
00243         OperationPlan* opplan = findId(id);
00244         if (opplan && opplan->getOperation()!=oper)
00245         {
00246           ostringstream ch;
00247           ch << "Operationplan id " << id
00248               << " defined multiple times with different operations: '"
00249               << opplan->getOperation() << "' & '" << oper << "'";
00250           delete this;
00251           throw DataException(ch.str());
00252         }
00253       }
00254       // The new operationplan definately doesn't clash with existing id's.
00255       // The counter need updating to garantuee that counter is always
00256       // a safe starting point for tagging new operationplans.
00257       else if (useMinCounter)
00258         counterMin = id+1;
00259       else
00260         counterMax = id-1;
00261     }
00262     // Fresh operationplan with blank id
00263     else if (useMinCounter)
00264       id = counterMin++;
00265     else
00266       id = counterMax--;
00267     // Check whether the counters are still okay
00268     if (counterMin >= counterMax)
00269       throw RuntimeException("Exhausted the range of available operationplan identifiers");
00270   }
00271 
00272   // Insert into the doubly linked list of operationplans.
00273   insertInOperationplanList();
00274 
00275   // If we used the lazy creator, the flow- and loadplans have not been
00276   // created yet. We do it now...
00277   createFlowLoads();
00278 
00279   // Extra registration step if this is a delivery operation
00280   if (getDemand() && getDemand()->getDeliveryOperation() == oper)
00281     dmd->addDelivery(this);
00282 
00283   // Mark the operation to detect its problems
00284   // Note that a single operationplan thus retriggers the problem computation
00285   // for all operationplans of this operation. For models with 1) a large
00286   // number of operationplans per operation and 2) very frequent problem
00287   // detection, this could constitute a scalability problem. This combination
00288   // is expected to be unusual and rare, justifying this design choice.
00289   oper->setChanged();
00290 
00291   // The operationplan is valid
00292   return true;
00293 }
00294 
00295 
00296 DECLARE_EXPORT void OperationPlan::deactivate()
00297 {
00298   // Wasn't activated anyway
00299   if (!id) return;
00300 
00301   id = 0;
00302 
00303   // Delete from the list of deliveries
00304   if (id && dmd) dmd->removeDelivery(this);
00305 
00306   // Delete from the operationplan list
00307   removeFromOperationplanList();
00308 
00309   // Mark the operation to detect its problems
00310   oper->setChanged();
00311 }
00312 
00313 
00314 DECLARE_EXPORT void OperationPlan::insertInOperationplanList()
00315 {
00316 
00317   // Check if already linked
00318   if (prev || oper->first_opplan == this) return;
00319 
00320   if (!oper->first_opplan)
00321   {
00322     // First operationplan in the list
00323     oper->first_opplan = this;
00324     oper->last_opplan = this;
00325   }
00326   else if (*this < *(oper->first_opplan))
00327   {
00328     // First in the list
00329     next = oper->first_opplan;
00330     next->prev = this;
00331     oper->first_opplan = this;
00332   }
00333   else if (*(oper->last_opplan) < *this)
00334   {
00335     // Last in the list
00336     prev = oper->last_opplan;
00337     prev->next = this;
00338     oper->last_opplan = this;
00339   }
00340   else
00341   {
00342     // Insert in the middle of the list
00343     OperationPlan *x = oper->last_opplan;
00344     OperationPlan *y = NULL;
00345     while (!(*x < *this))
00346     {
00347       y = x;
00348       x = x->prev;
00349     }
00350     next = y;
00351     prev = x;
00352     if (x) x->next = this;
00353     if (y) y->prev = this;
00354   }
00355 }
00356 
00357 
00358 DECLARE_EXPORT void OperationPlan::removeFromOperationplanList()
00359 {
00360   if (prev)
00361     // In the middle
00362     prev->next = next;
00363   else if (oper->first_opplan == this)
00364     // First opplan in the list of this operation
00365     oper->first_opplan = next;
00366   if (next)
00367     // In the middle
00368     next->prev = prev;
00369   else if (oper->last_opplan == this)
00370     // Last opplan in the list of this operation
00371     oper->last_opplan = prev;
00372 }
00373 
00374 
00375 DECLARE_EXPORT void OperationPlan::addSubOperationPlan(OperationPlan* o)
00376 {
00377   // Check
00378   if (!o) throw LogicException("Adding null suboperationplan");
00379 
00380   // Adding a suboperationplan that was already added
00381   if (o->owner == this)  return;
00382 
00383   // Clear the previous owner, if there is one
00384   if (o->owner) o->owner->eraseSubOperationPlan(o);
00385 
00386   // Link in the list, keeping the right ordering
00387   if (!firstsubopplan)
00388   {
00389     // First element
00390     firstsubopplan = o;
00391     lastsubopplan = o;
00392   }
00393   else if (firstsubopplan->getOperation() != OperationSetup::setupoperation)
00394   {
00395     // New head
00396     o->nextsubopplan = firstsubopplan;
00397     firstsubopplan->prevsubopplan = o;
00398     firstsubopplan = o;
00399   }
00400   else
00401   {
00402     // Insert right after the setup operationplan
00403     OperationPlan *s = firstsubopplan->nextsubopplan;
00404     o->nextsubopplan = s;
00405     if (s) s->nextsubopplan = o;
00406     else lastsubopplan = o;
00407   }
00408 
00409   o->owner = this;
00410 
00411   // Update the flow and loadplans
00412   update();
00413 }
00414 
00415 
00416 DECLARE_EXPORT void OperationPlan::eraseSubOperationPlan(OperationPlan* o)
00417 {
00418   // Check
00419   if (!o) return;
00420 
00421   // Adding a suboperationplan that was already added
00422   if (o->owner != this)
00423     throw LogicException("Operationplan isn't a suboperationplan");
00424 
00425   // Clear owner field
00426   o->owner = NULL;
00427 
00428   // Remove from the list
00429   if (o->prevsubopplan)
00430     o->prevsubopplan->nextsubopplan = o->nextsubopplan;
00431   else
00432     firstsubopplan = o->nextsubopplan;
00433   if (o->nextsubopplan)
00434     o->nextsubopplan->prevsubopplan = o->prevsubopplan;
00435   else
00436     lastsubopplan = o->prevsubopplan;
00437 };
00438 
00439 
00440 DECLARE_EXPORT bool OperationPlan::operator < (const OperationPlan& a) const
00441 {
00442   // Different operations
00443   if (oper != a.oper)
00444     return *oper < *(a.oper);
00445 
00446   // Different start date
00447   if (dates.getStart() != a.dates.getStart())
00448     return dates.getStart() < a.dates.getStart();
00449 
00450   // Sort based on quantity
00451   return quantity >= a.quantity;
00452 }
00453 
00454 
00455 DECLARE_EXPORT void OperationPlan::createFlowLoads()
00456 {
00457   // Has been initialized already, it seems
00458   if (firstflowplan || firstloadplan) return;
00459 
00460   // Create setup suboperationplans and loadplans
00461   for (Operation::loadlist::const_iterator g=oper->getLoads().begin();
00462       g!=oper->getLoads().end(); ++g)
00463     if (!g->getAlternate())
00464     {
00465       new LoadPlan(this, &*g);
00466       if (!g->getSetup().empty() && g->getResource()->getSetupMatrix())
00467         OperationSetup::setupoperation->createOperationPlan(
00468           1, getDates().getStart(), getDates().getStart(), NULL, this);
00469     }
00470 
00471   // Create flowplans for flows that are not alternates of another one
00472   for (Operation::flowlist::const_iterator h=oper->getFlows().begin();
00473       h!=oper->getFlows().end(); ++h)
00474     if (!h->getAlternate()) new FlowPlan(this, &*h);
00475 }
00476 
00477 
00478 DECLARE_EXPORT void OperationPlan::deleteFlowLoads()
00479 {
00480   // If no flowplans and loadplans, the work is already done
00481   if (!firstflowplan && !firstloadplan) return;
00482 
00483   FlowPlanIterator e = beginFlowPlans();
00484   firstflowplan = NULL;    // Important to do this before the delete!
00485   LoadPlanIterator f = beginLoadPlans();
00486   firstloadplan = NULL;  // Important to do this before the delete!
00487 
00488   // Delete the flowplans
00489   while (e != endFlowPlans()) delete &*(e++);
00490 
00491   // Delete the loadplans (including the setup suboperationplan)
00492   while (f != endLoadPlans()) delete &*(f++);
00493 }
00494 
00495 
00496 DECLARE_EXPORT OperationPlan::~OperationPlan()
00497 {
00498   // Delete the flowplans and loadplan
00499   deleteFlowLoads();
00500 
00501   // Initialize
00502   OperationPlan *x = firstsubopplan;
00503   firstsubopplan = NULL;
00504   lastsubopplan = NULL;
00505 
00506   // Delete the sub operationplans
00507   while (x)
00508   {
00509     OperationPlan *y = x->nextsubopplan;
00510     x->owner = NULL; // Need to clear before destroying the suboperationplan
00511     delete x;
00512     x = y;
00513   }
00514 
00515   // Delete also the owner
00516   if (owner)
00517   {
00518     const OperationPlan* o = owner;
00519     setOwner(NULL);
00520     delete o;
00521   }
00522 
00523   // Delete from the list of deliveries
00524   if (id && dmd) dmd->removeDelivery(this);
00525 
00526   // Delete from the operationplan list
00527   removeFromOperationplanList();
00528 }
00529 
00530 
00531 void DECLARE_EXPORT OperationPlan::setOwner(OperationPlan* o)
00532 {
00533   // Special case: the same owner is set twice
00534   if (owner == o) return;
00535   // Erase the previous owner if there is one
00536   if (owner) owner->eraseSubOperationPlan(this);
00537   // Register with the new owner
00538   if (o) o->addSubOperationPlan(this);
00539 }
00540 
00541 
00542 void DECLARE_EXPORT OperationPlan::setStart (Date d)
00543 {
00544   // Locked opplans don't move
00545   if (getLocked()) return;
00546 
00547   if (!lastsubopplan || lastsubopplan->getOperation() == OperationSetup::setupoperation)
00548     // No sub operationplans
00549     oper->setOperationPlanParameters(this,quantity,d,Date::infinitePast);
00550   else
00551   {
00552     // Move all sub-operationplans in an orderly fashion
00553     for (OperationPlan* i = firstsubopplan; i; i = i->nextsubopplan)
00554     {
00555       if (i->getOperation() == OperationSetup::setupoperation) continue;
00556       if (i->getDates().getStart() < d)
00557       {
00558         i->setStart(d);
00559         d = i->getDates().getEnd();
00560       }
00561       else
00562         // There is sufficient slack between the suboperationplans
00563         break;
00564     }
00565   }
00566 
00567   // Update flow and loadplans
00568   update();
00569 }
00570 
00571 
00572 void DECLARE_EXPORT OperationPlan::setEnd(Date d)
00573 {
00574   // Locked opplans don't move
00575   if (getLocked()) return;
00576 
00577   if (!lastsubopplan || lastsubopplan->getOperation() == OperationSetup::setupoperation)
00578     // No sub operationplans
00579     oper->setOperationPlanParameters(this,quantity,Date::infinitePast,d);
00580   else
00581   {
00582     // Move all sub-operationplans in an orderly fashion
00583     for (OperationPlan* i = lastsubopplan; i; i = i->prevsubopplan)
00584     {
00585       if (i->getOperation() == OperationSetup::setupoperation) break;
00586       if (i->getDates().getEnd() > d)
00587       {
00588         i->setEnd(d);
00589         d = i->getDates().getStart();
00590       }
00591       else
00592         // There is sufficient slack between the suboperationplans
00593         break;
00594     }
00595   }
00596 
00597   // Update flow and loadplans
00598   update();
00599 }
00600 
00601 
00602 DECLARE_EXPORT double OperationPlan::setQuantity (double f, bool roundDown, bool upd, bool execute)
00603 {
00604   // No impact on locked operationplans
00605   if (getLocked()) return quantity;
00606 
00607   // Invalid operationplan: the quantity must be >= 0.
00608   if (f < 0)
00609     throw DataException("Operationplans can't have negative quantities");
00610 
00611   // Setting a quantity is only allowed on a top operationplan.
00612   // One exception: on alternate operations the sizing on the sub-operations is
00613   // respected.
00614   if (owner && owner->getOperation()->getType() != *OperationAlternate::metadata)
00615     return owner->setQuantity(f,roundDown,upd,execute);
00616 
00617   // Compute the correct size for the operationplan
00618   if (f!=0.0 && getOperation()->getSizeMinimum()>0.0
00619       && f < getOperation()->getSizeMinimum())
00620   {
00621     if (roundDown)
00622     {
00623       // Smaller than the minimum quantity, rounding down means... nothing
00624       if (!execute) return 0.0;
00625       quantity = 0.0;
00626       // Update the flow and loadplans, and mark for problem detection
00627       if (upd) update();
00628       return 0.0;
00629     }
00630     f = getOperation()->getSizeMinimum();
00631   }
00632   if (f != 0.0 && f >= getOperation()->getSizeMaximum())
00633   {
00634     roundDown = true; // force rounddown to stay below the limit
00635     f = getOperation()->getSizeMaximum();
00636   }
00637   if (f!=0.0 && getOperation()->getSizeMultiple()>0.0)
00638   {
00639     int mult = static_cast<int> (f / getOperation()->getSizeMultiple()
00640         + (roundDown ? 0.0 : 0.99999999));
00641     if (!execute) return mult * getOperation()->getSizeMultiple();
00642     quantity = mult * getOperation()->getSizeMultiple();
00643   }
00644   else
00645   {
00646     if (!execute) return f;
00647     quantity = f;
00648   }
00649 
00650   // Update the parent of an alternate operationplan
00651   if (execute && owner
00652       && owner->getOperation()->getType() == *OperationAlternate::metadata)
00653   {
00654     owner->quantity = quantity;
00655     if (upd) owner->resizeFlowLoadPlans();
00656   }
00657 
00658   // Apply the same size also to its children
00659   if (execute && firstsubopplan)
00660     for (OperationPlan *i = firstsubopplan; i; i = i->nextsubopplan)
00661       if (i->getOperation() != OperationSetup::setupoperation)
00662       {
00663         i->quantity = quantity;
00664         if (upd) i->resizeFlowLoadPlans();
00665       }
00666 
00667   // Update the flow and loadplans, and mark for problem detection
00668   if (upd) update();
00669   return quantity;
00670 }
00671 
00672 
00673 DECLARE_EXPORT void OperationPlan::resizeFlowLoadPlans()
00674 {
00675   // Update all flowplans
00676   for (FlowPlanIterator ee = beginFlowPlans(); ee != endFlowPlans(); ++ee)
00677     ee->update();
00678 
00679   // Update all loadplans
00680   for (LoadPlanIterator e = beginLoadPlans(); e != endLoadPlans(); ++e)
00681     e->update();
00682 
00683   // Align the end of the setup operationplan with the start of the operation
00684   if (firstsubopplan && firstsubopplan->getOperation() == OperationSetup::setupoperation
00685       && firstsubopplan->getDates().getEnd() != getDates().getStart())
00686     firstsubopplan->setEnd(getDates().getStart());
00687   else if (getOperation() == OperationSetup::setupoperation
00688       && getDates().getEnd() != getOwner()->getDates().getStart())
00689     getOwner()->setStart(getDates().getEnd());
00690 
00691   // Allow the operation length to be changed now that the quantity has changed
00692   // Note that we assume that the end date remains fixed. This assumption makes
00693   // sense if the operationplan was created to satisfy a demand.
00694   // It is not valid though when the purpose of the operationplan was to push
00695   // some material downstream.
00696 
00697   // Resize children
00698   for (OperationPlan *j = firstsubopplan; j; j = j->nextsubopplan)
00699     if (j->getOperation() != OperationSetup::setupoperation)
00700     {
00701       j->quantity = quantity;
00702       j->resizeFlowLoadPlans();
00703     }
00704 
00705   // Notify the demand of the changed delivery
00706   if (dmd) dmd->setChanged();
00707 }
00708 
00709 
00710 DECLARE_EXPORT OperationPlan::OperationPlan(const OperationPlan& src, bool init)
00711 {
00712   if (src.owner)
00713     throw LogicException("Can't copy suboperationplans. Copy the owner instead.");
00714 
00715   // Identifier can't be inherited, but a new one will be generated when we activate the operationplan
00716   id = 0;
00717 
00718   // Copy the fields
00719   quantity = src.quantity;
00720   flags = src.flags;
00721   dmd = src.dmd;
00722   oper = src.oper;
00723   firstflowplan = NULL;
00724   firstloadplan = NULL;
00725   dates = src.dates;
00726   prev = NULL;
00727   next = NULL;
00728   owner = NULL;
00729   firstsubopplan = NULL;
00730   lastsubopplan = NULL;
00731   nextsubopplan = NULL;
00732   prevsubopplan = NULL;
00733   motive = NULL;
00734   initType(metadata);
00735 
00736   // Clone the suboperationplans
00737   for (OperationPlan::iterator x(&src); x != end(); ++x)
00738     new OperationPlan(*x, this);
00739 
00740   // Activate
00741   if (init) activate();
00742 }
00743 
00744 
00745 DECLARE_EXPORT OperationPlan::OperationPlan(const OperationPlan& src,
00746     OperationPlan* newOwner)
00747 {
00748   if (!newOwner)
00749     throw LogicException("No new owner passed in private copy constructor.");
00750 
00751   // Identifier can't be inherited, but a new one will be generated when we activate the operationplan
00752   id = 0;
00753 
00754   // Copy the fields
00755   quantity = src.quantity;
00756   flags = src.flags;
00757   dmd = src.dmd;
00758   oper = src.oper;
00759   firstflowplan = NULL;
00760   firstloadplan = NULL;
00761   dates = src.dates;
00762   prev = NULL;
00763   next = NULL;
00764   owner = NULL;
00765   firstsubopplan = NULL;
00766   lastsubopplan = NULL;
00767   nextsubopplan = NULL;
00768   prevsubopplan = NULL;
00769   motive = NULL;
00770   initType(metadata);
00771 
00772   // Set owner of a
00773   setOwner(newOwner);
00774 
00775   // Clone the suboperationplans
00776   for (OperationPlan::iterator x(&src); x != end(); ++x)
00777     new OperationPlan(*x, this);
00778 }
00779 
00780 
00781 DECLARE_EXPORT void OperationPlan::update()
00782 {
00783   if (lastsubopplan && lastsubopplan->getOperation() != OperationSetup::setupoperation)
00784   {
00785     // Inherit the start and end date of the child operationplans
00786     OperationPlan *tmp = firstsubopplan;
00787     if (tmp->getOperation() == OperationSetup::setupoperation)
00788       tmp = tmp->nextsubopplan;
00789     dates.setStartAndEnd(
00790       tmp->getDates().getStart(),
00791       lastsubopplan->getDates().getEnd()
00792     );
00793     // If at least 1 sub-operationplan is locked, the parent must be locked
00794     flags &= ~IS_LOCKED; // Clear is_locked flag
00795     for (OperationPlan* i = firstsubopplan; i; i = i->nextsubopplan)
00796       if (i->flags & IS_LOCKED)
00797       {
00798         flags |= IS_LOCKED;  // Set is_locked flag
00799         break;
00800       }
00801   }
00802 
00803   // Update the flow and loadplans
00804   resizeFlowLoadPlans();
00805 
00806   // Notify the owner operationplan
00807   if (owner) owner->update();
00808 
00809   // Mark as changed
00810   setChanged();
00811 }
00812 
00813 
00814 DECLARE_EXPORT void OperationPlan::deleteOperationPlans(Operation* o, bool deleteLockedOpplans)
00815 {
00816   if (!o) return;
00817   for (OperationPlan *opplan = o->first_opplan; opplan; )
00818   {
00819     OperationPlan *tmp = opplan;
00820     opplan = opplan->next;
00821     // Note that the deletion of the operationplan also updates the opplan list
00822     if (deleteLockedOpplans || !tmp->getLocked()) delete tmp;
00823   }
00824 }
00825 
00826 
00827 DECLARE_EXPORT double OperationPlan::getPenalty() const
00828 {
00829   double penalty = 0;
00830   for (OperationPlan::LoadPlanIterator i = beginLoadPlans();
00831       i != endLoadPlans(); ++i)
00832     if (i->isStart() && !i->getLoad()->getSetup().empty() && i->getResource()->getSetupMatrix())
00833     {
00834       SetupMatrix::Rule *rule = i->getResource()->getSetupMatrix()
00835           ->calculateSetup(i->getSetup(false), i->getSetup(true));
00836       if (rule) penalty += rule->getCost();
00837     }
00838   return penalty;
00839 }
00840 
00841 
00842 DECLARE_EXPORT bool OperationPlan::isExcess(bool strict) const
00843 {
00844   // Delivery operationplans aren't excess
00845   if (getDemand()) return false;
00846 
00847   // Recursive call for suboperationplans
00848   for (OperationPlan* subopplan = firstsubopplan; subopplan; subopplan = subopplan->nextsubopplan)
00849     if (!subopplan->isExcess()) return false;
00850 
00851   // Loop over all producing flowplans
00852   for (OperationPlan::FlowPlanIterator i = beginFlowPlans();
00853       i != endFlowPlans(); ++i)
00854   {
00855     // Skip consuming flowplans
00856     if (i->getQuantity() <= 0) continue;
00857 
00858     // Loop over all flowplans in the buffer (starting at the end) and verify
00859     // that the onhand is bigger than the flowplan quantity
00860     double current_maximum(0.0);
00861     double current_minimum(0.0);
00862     Buffer::flowplanlist::const_iterator j = i->getBuffer()->getFlowPlans().rbegin();
00863     if (!strict && j != i->getBuffer()->getFlowPlans().end())
00864     {
00865       current_maximum = i->getBuffer()->getFlowPlans().getMax(&*j);
00866       current_minimum = i->getBuffer()->getFlowPlans().getMin(&*j);
00867     }
00868     for (; j != i->getBuffer()->getFlowPlans().end(); --j)
00869     {
00870       if ( (current_maximum > 0
00871           && j->getOnhand() < i->getQuantity() + current_maximum - ROUNDING_ERROR)
00872           || j->getOnhand() < i->getQuantity() + current_minimum - ROUNDING_ERROR )
00873         return false;
00874       if (j->getType() == 4 && !strict) current_maximum = j->getMax(false);
00875       if (j->getType() == 3 && !strict) current_minimum = j->getMin(false);
00876       if (&*j == &*i) break;
00877     }
00878   }
00879 
00880   // If we remove this operationplan the onhand in all buffers remains positive.
00881   return true;
00882 }
00883 
00884 
00885 DECLARE_EXPORT TimePeriod OperationPlan::getUnavailable() const
00886 {
00887   TimePeriod x;
00888   DateRange y = getOperation()->calculateOperationTime(dates.getStart(), dates.getEnd(), &x);
00889   return dates.getDuration() - x;
00890 }
00891 
00892 
00893 DECLARE_EXPORT void OperationPlan::writer(const MetaCategory* c, XMLOutput* o)
00894 {
00895   if (!empty())
00896   {
00897     o->BeginObject(*c->grouptag);
00898     for (iterator i=begin(); i!=end(); ++i)
00899       o->writeElement(*c->typetag, *i);
00900     o->EndObject(*c->grouptag);
00901   }
00902 }
00903 
00904 
00905 DECLARE_EXPORT void OperationPlan::writeElement(XMLOutput *o, const Keyword& tag, mode m) const
00906 {
00907   // Don't export operationplans of hidden operations
00908   if (oper->getHidden()) return;
00909 
00910   // Writing a reference
00911   if (m == REFERENCE)
00912   {
00913     o->writeElement
00914     (tag, Tags::tag_id, id, Tags::tag_operation, oper->getName());
00915     return;
00916   }
00917 
00918   if (m != NOHEADER)
00919     o->BeginObject(tag, Tags::tag_id, id, Tags::tag_operation,oper->getName());
00920 
00921   // The demand reference is only valid for delivery operationplans,
00922   // and it should only be written if this tag is not being written
00923   // as part of a demand+delivery tag.
00924   if (dmd && !dynamic_cast<Demand*>(o->getPreviousObject()))
00925     o->writeElement(Tags::tag_demand, dmd);
00926 
00927   o->writeElement(Tags::tag_start, dates.getStart());
00928   o->writeElement(Tags::tag_end, dates.getEnd());
00929   o->writeElement(Tags::tag_quantity, quantity);
00930   if (getLocked()) o->writeElement (Tags::tag_locked, getLocked());
00931   o->writeElement(Tags::tag_owner, owner);
00932 
00933   // Write out the flowplans and their pegging
00934   if (o->getContentType() == XMLOutput::PLANDETAIL)
00935   {
00936     o->BeginObject(Tags::tag_flowplans);
00937     for (FlowPlanIterator qq = beginFlowPlans(); qq != endFlowPlans(); ++qq)
00938       qq->writeElement(o, Tags::tag_flowplan);
00939     o->EndObject(Tags::tag_flowplans);
00940   }
00941 
00942   o->EndObject(tag);
00943 }
00944 
00945 
00946 DECLARE_EXPORT void OperationPlan::beginElement(XMLInput& pIn, const Attribute& pAttr)
00947 {
00948   if (pAttr.isA (Tags::tag_demand))
00949     pIn.readto( Demand::reader(Demand::metadata,pIn.getAttributes()) );
00950   else if (pAttr.isA(Tags::tag_owner))
00951     pIn.readto(createOperationPlan(metadata,pIn.getAttributes()));
00952   else if (pAttr.isA(Tags::tag_flowplans))
00953     pIn.IgnoreElement();
00954 }
00955 
00956 
00957 DECLARE_EXPORT void OperationPlan::endElement (XMLInput& pIn, const Attribute& pAttr, const DataElement& pElement)
00958 {
00959   // Note that the fields have been ordered more or less in the order
00960   // of their expected frequency.
00961   // Note that id and operation are handled already during the
00962   // operationplan creation. They don't need to be handled here...
00963   if (pAttr.isA(Tags::tag_quantity))
00964     pElement >> quantity;
00965   else if (pAttr.isA(Tags::tag_start))
00966     dates.setStart(pElement.getDate());
00967   else if (pAttr.isA(Tags::tag_end))
00968     dates.setEnd(pElement.getDate());
00969   else if (pAttr.isA(Tags::tag_owner) && !pIn.isObjectEnd())
00970   {
00971     OperationPlan* o = dynamic_cast<OperationPlan*>(pIn.getPreviousObject());
00972     if (o) setOwner(o);
00973   }
00974   else if (pIn.isObjectEnd())
00975   {
00976     // Initialize the operationplan
00977     if (!activate())
00978       // Initialization failed and the operationplan is deleted
00979       pIn.invalidateCurrentObject();
00980   }
00981   else if (pAttr.isA (Tags::tag_demand))
00982   {
00983     Demand * d = dynamic_cast<Demand*>(pIn.getPreviousObject());
00984     if (d) d->addDelivery(this);
00985     else throw LogicException("Incorrect object type during read operation");
00986   }
00987   else if (pAttr.isA(Tags::tag_locked))
00988     setLocked(pElement.getBool());
00989 }
00990 
00991 
00992 DECLARE_EXPORT void OperationPlan::setLocked(bool b)
00993 {
00994   if (b)
00995     flags |= IS_LOCKED;
00996   else
00997     flags &= ~IS_LOCKED;
00998   for (OperationPlan *x = firstsubopplan; x; x = x->nextsubopplan)
00999     x->setLocked(b);
01000   update();
01001 }
01002 
01003 
01004 DECLARE_EXPORT void OperationPlan::setDemand(Demand* l)
01005 {
01006   // No change
01007   if (l==dmd) return;
01008 
01009   // Unregister from previous demand
01010   if (dmd) dmd->removeDelivery(this);
01011 
01012   // Register the new demand and mark it changed
01013   dmd = l;
01014   if (l) l->setChanged();
01015 }
01016 
01017 
01018 PyObject* OperationPlan::create(PyTypeObject* pytype, PyObject* args, PyObject* kwds)
01019 {
01020   try
01021   {
01022     // Find or create the C++ object
01023     PythonAttributeList atts(kwds);
01024     Object* x = createOperationPlan(OperationPlan::metadata,atts);
01025     Py_INCREF(x);
01026 
01027     // Iterate over extra keywords, and set attributes.   @todo move this responsibility to the readers...
01028     if (x)
01029     {
01030       PyObject *key, *value;
01031       Py_ssize_t pos = 0;
01032       while (PyDict_Next(kwds, &pos, &key, &value))
01033       {
01034         PythonObject field(value);
01035         Attribute attr(PyString_AsString(key));
01036         if (!attr.isA(Tags::tag_operation) && !attr.isA(Tags::tag_id) && !attr.isA(Tags::tag_action))
01037         {
01038           int result = x->setattro(attr, field);
01039           if (result && !PyErr_Occurred())
01040             PyErr_Format(PyExc_AttributeError,
01041                 "attribute '%s' on '%s' can't be updated",
01042                 PyString_AsString(key), x->ob_type->tp_name);
01043         }
01044       };
01045     }
01046 
01047     if (x && !static_cast<OperationPlan*>(x)->activate())
01048     {
01049       PyErr_SetString(PythonRuntimeException, "operationplan activation failed");
01050       return NULL;
01051     }
01052     return x;
01053   }
01054   catch (...)
01055   {
01056     PythonType::evalException();
01057     return NULL;
01058   }
01059 }
01060 
01061 
01062 DECLARE_EXPORT PyObject* OperationPlan::getattro(const Attribute& attr)
01063 {
01064   if (attr.isA(Tags::tag_id))
01065     return PythonObject(getIdentifier());
01066   if (attr.isA(Tags::tag_operation))
01067     return PythonObject(getOperation());
01068   if (attr.isA(Tags::tag_flowplans))
01069     return new frepple::FlowPlanIterator(this);
01070   if (attr.isA(Tags::tag_loadplans))
01071     return new frepple::LoadPlanIterator(this);
01072   if (attr.isA(Tags::tag_quantity))
01073     return PythonObject(getQuantity());
01074   if (attr.isA(Tags::tag_start))
01075     return PythonObject(getDates().getStart());
01076   if (attr.isA(Tags::tag_end))
01077     return PythonObject(getDates().getEnd());
01078   if (attr.isA(Tags::tag_demand))
01079     return PythonObject(getDemand());
01080   if (attr.isA(Tags::tag_locked))
01081     return PythonObject(getLocked());
01082   if (attr.isA(Tags::tag_owner))
01083     return PythonObject(getOwner());
01084   if (attr.isA(Tags::tag_operationplans))
01085     return new OperationPlanIterator(this);
01086   if (attr.isA(Tags::tag_hidden))
01087     return PythonObject(getHidden());
01088   if (attr.isA(Tags::tag_unavailable))
01089     return PythonObject(getUnavailable());
01090   if (attr.isA(Tags::tag_motive))
01091   {
01092     // Null
01093     if (!getMotive())
01094     {
01095       Py_INCREF(Py_None);
01096       return Py_None;
01097     }
01098 
01099     // Demand
01100     Demand* d = dynamic_cast<Demand*>(getMotive());
01101     if (d) return PythonObject(d);
01102 
01103     // Buffer
01104     Buffer* b = dynamic_cast<Buffer*>(getMotive());
01105     if (b) return PythonObject(b);
01106 
01107     // Resource
01108     Resource* r = dynamic_cast<Resource*>(getMotive());
01109     if (r) return PythonObject(r);
01110 
01111     // Unknown type
01112     PyErr_SetString(PythonLogicException, "Unhandled motive type");
01113     return NULL;
01114   }
01115   return NULL;
01116 }
01117 
01118 
01119 DECLARE_EXPORT int OperationPlan::setattro(const Attribute& attr, const PythonObject& field)
01120 {
01121   if (attr.isA(Tags::tag_quantity))
01122     setQuantity(field.getDouble());
01123   else if (attr.isA(Tags::tag_start))
01124     setStart(field.getDate());
01125   else if (attr.isA(Tags::tag_end))
01126     setEnd(field.getDate());
01127   else if (attr.isA(Tags::tag_locked))
01128     setLocked(field.getBool());
01129   else if (attr.isA(Tags::tag_demand))
01130   {
01131     if (!field.check(Demand::metadata))
01132     {
01133       PyErr_SetString(PythonDataException, "operationplan demand must be of type demand");
01134       return -1;
01135     }
01136     Demand* y = static_cast<Demand*>(static_cast<PyObject*>(field));
01137     setDemand(y);
01138   }
01139   else if (attr.isA(Tags::tag_owner))
01140   {
01141     if (!field.check(OperationPlan::metadata))
01142     {
01143       PyErr_SetString(PythonDataException, "operationplan demand must be of type demand");
01144       return -1;
01145     }
01146     OperationPlan* y = static_cast<OperationPlan*>(static_cast<PyObject*>(field));
01147     setOwner(y);
01148   }
01149   else if (attr.isA(Tags::tag_motive))
01150   {
01151     Plannable* y;
01152     if (static_cast<PyObject*>(field) == Py_None)
01153       y = NULL;
01154     if (field.check(Demand::metadata))
01155       y = static_cast<Demand*>(static_cast<PyObject*>(field));
01156     else if (field.check(Buffer::metadata))
01157       y = static_cast<Buffer*>(static_cast<PyObject*>(field));
01158     else if (field.check(Resource::metadata))
01159       y = static_cast<Resource*>(static_cast<PyObject*>(field));
01160     else
01161     {
01162       PyErr_SetString(PythonDataException, "operationplan motive must be of type demand, buffer or resource");
01163       return -1;
01164     }
01165     setMotive(y);
01166   }
01167   else
01168     return -1;
01169   return 0;
01170 }
01171 
01172 } // end namespace

Documentation generated for frePPLe by  doxygen