operationplan.cpp
Go to the documentation of this file.
00001 /***************************************************************************
00002   file : $URL: https://frepple.svn.sourceforge.net/svnroot/frepple/trunk/src/model/operationplan.cpp $
00003   version : $LastChangedRevision: 1511 $  $LastChangedBy: jdetaeye $
00004   date : $LastChangedDate: 2011-09-11 11:08:07 +0200 (Sun, 11 Sep 2011) $
00005  ***************************************************************************/
00006 
00007 /***************************************************************************
00008  *                                                                         *
00009  * Copyright (C) 2007-2011 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   initType(metadata);
00734 
00735   // Clone the suboperationplans
00736   for (OperationPlan::iterator x(&src); x != end(); ++x)
00737     new OperationPlan(*x, this);
00738 
00739   // Activate
00740   if (init) activate();
00741 }
00742 
00743 
00744 DECLARE_EXPORT OperationPlan::OperationPlan(const OperationPlan& src,
00745   OperationPlan* newOwner)
00746 {
00747   if (!newOwner)
00748     throw LogicException("No new owner passed in private copy constructor.");
00749 
00750   // Identifier can't be inherited, but a new one will be generated when we activate the operationplan
00751   id = 0;
00752 
00753   // Copy the fields
00754   quantity = src.quantity;
00755   flags = src.flags;
00756   dmd = src.dmd;
00757   oper = src.oper;
00758   firstflowplan = NULL;
00759   firstloadplan = NULL;
00760   dates = src.dates;
00761   prev = NULL;
00762   next = NULL;
00763   owner = NULL;
00764   firstsubopplan = NULL;
00765   lastsubopplan = NULL;
00766   nextsubopplan = NULL;
00767   prevsubopplan = NULL;
00768   initType(metadata);
00769 
00770   // Set owner of a
00771   setOwner(newOwner);
00772 
00773   // Clone the suboperationplans
00774   for (OperationPlan::iterator x(&src); x != end(); ++x)
00775     new OperationPlan(*x, this);
00776 }
00777 
00778 
00779 DECLARE_EXPORT void OperationPlan::update()
00780 {
00781   if (lastsubopplan && lastsubopplan->getOperation() != OperationSetup::setupoperation)
00782   {
00783     // Inherit the start and end date of the child operationplans
00784     OperationPlan *tmp = firstsubopplan;
00785     if (tmp->getOperation() == OperationSetup::setupoperation)
00786       tmp = tmp->nextsubopplan;
00787     dates.setStartAndEnd(
00788       tmp->getDates().getStart(),
00789       lastsubopplan->getDates().getEnd()
00790     );
00791     // If at least 1 sub-operationplan is locked, the parent must be locked
00792     flags &= ~IS_LOCKED; // Clear is_locked flag
00793     for (OperationPlan* i = firstsubopplan; i; i = i->nextsubopplan)
00794       if (i->flags & IS_LOCKED)
00795       {
00796         flags |= IS_LOCKED;  // Set is_locked flag
00797         break;
00798       }
00799   }
00800 
00801   // Update the flow and loadplans
00802   resizeFlowLoadPlans();
00803 
00804   // Notify the owner operationplan
00805   if (owner) owner->update();
00806 
00807   // Mark as changed
00808   setChanged();
00809 }
00810 
00811 
00812 DECLARE_EXPORT void OperationPlan::deleteOperationPlans(Operation* o, bool deleteLockedOpplans)
00813 {
00814   if (!o) return;
00815   for (OperationPlan *opplan = o->first_opplan; opplan; )
00816   {
00817     OperationPlan *tmp = opplan;
00818     opplan = opplan->next;
00819     // Note that the deletion of the operationplan also updates the opplan list
00820     if (deleteLockedOpplans || !tmp->getLocked()) delete tmp;
00821   }
00822 }
00823 
00824 
00825 DECLARE_EXPORT double OperationPlan::getPenalty() const
00826 {
00827   double penalty = 0;
00828   for (OperationPlan::LoadPlanIterator i = beginLoadPlans();
00829     i != endLoadPlans(); ++i)
00830     if (i->isStart() && !i->getLoad()->getSetup().empty() && i->getResource()->getSetupMatrix())
00831     {
00832       SetupMatrix::Rule *rule = i->getResource()->getSetupMatrix()
00833         ->calculateSetup(i->getSetup(false), i->getSetup(true));
00834       if (rule) penalty += rule->getCost();
00835     }
00836   return penalty;
00837 }
00838 
00839 
00840 DECLARE_EXPORT bool OperationPlan::isExcess(bool strict) const
00841 {
00842   // Delivery operationplans aren't excess
00843   if (getDemand()) return false;
00844 
00845   // Recursive call for suboperationplans
00846   for (OperationPlan* subopplan = firstsubopplan; subopplan; subopplan = subopplan->nextsubopplan)
00847     if (!subopplan->isExcess()) return false;
00848 
00849   // Loop over all producing flowplans
00850   for (OperationPlan::FlowPlanIterator i = beginFlowPlans();
00851         i != endFlowPlans(); ++i)
00852   {
00853     // Skip consuming flowplans
00854     if (i->getQuantity() <= 0) continue;
00855 
00856     // Loop over all flowplans in the buffer (starting at the end) and verify
00857     // that the onhand is bigger than the flowplan quantity
00858     double current_maximum(0.0);
00859     double current_minimum(0.0);
00860     Buffer::flowplanlist::const_iterator j = i->getBuffer()->getFlowPlans().rbegin();
00861     if (!strict && j != i->getBuffer()->getFlowPlans().end())
00862     {
00863       current_maximum = i->getBuffer()->getFlowPlans().getMax(&*j);
00864       current_minimum = i->getBuffer()->getFlowPlans().getMin(&*j);
00865     }
00866     for (; j != i->getBuffer()->getFlowPlans().end(); --j)
00867     {
00868       if ( (current_maximum > 0
00869              && j->getOnhand() < i->getQuantity() + current_maximum - ROUNDING_ERROR)
00870         || j->getOnhand() < i->getQuantity() + current_minimum - ROUNDING_ERROR )
00871         return false;
00872       if (j->getType() == 4 && !strict) current_maximum = j->getMax(false);
00873       if (j->getType() == 3 && !strict) current_minimum = j->getMin(false);
00874       if (&*j == &*i) break;
00875     }
00876   }
00877 
00878   // If we remove this operationplan the onhand in all buffers remains positive.
00879   return true;
00880 }
00881 
00882 
00883 DECLARE_EXPORT TimePeriod OperationPlan::getUnavailable() const
00884 {
00885   TimePeriod x;
00886   DateRange y = getOperation()->calculateOperationTime(dates.getStart(), dates.getEnd(), &x);
00887   return dates.getDuration() - x;
00888 }
00889 
00890 
00891 DECLARE_EXPORT void OperationPlan::writer(const MetaCategory* c, XMLOutput* o)
00892 {
00893   if (!empty())
00894   {
00895     o->BeginObject(*c->grouptag);
00896     for (iterator i=begin(); i!=end(); ++i)
00897       o->writeElement(*c->typetag, *i);
00898     o->EndObject(*c->grouptag);
00899   }
00900 }
00901 
00902 
00903 DECLARE_EXPORT void OperationPlan::writeElement(XMLOutput *o, const Keyword& tag, mode m) const
00904 {
00905   // Don't export operationplans of hidden operations
00906   if (oper->getHidden()) return;
00907 
00908   // Writing a reference
00909   if (m == REFERENCE)
00910   {
00911     o->writeElement
00912       (tag, Tags::tag_id, id, Tags::tag_operation, oper->getName());
00913     return;
00914   }
00915 
00916   if (m != NOHEADER)
00917     o->BeginObject(tag, Tags::tag_id, id, Tags::tag_operation,oper->getName());
00918 
00919   // The demand reference is only valid for delivery operationplans,
00920   // and it should only be written if this tag is not being written
00921   // as part of a demand+delivery tag.
00922   if (dmd && !dynamic_cast<Demand*>(o->getPreviousObject()))
00923     o->writeElement(Tags::tag_demand, dmd);
00924 
00925   o->writeElement(Tags::tag_start, dates.getStart());
00926   o->writeElement(Tags::tag_end, dates.getEnd());
00927   o->writeElement(Tags::tag_quantity, quantity);
00928   if (getLocked()) o->writeElement (Tags::tag_locked, getLocked());
00929   o->writeElement(Tags::tag_owner, owner);
00930 
00931   // Write out the flowplans and their pegging
00932   if (o->getContentType() == XMLOutput::PLANDETAIL)
00933   {
00934     o->BeginObject(Tags::tag_flowplans);
00935     for (FlowPlanIterator qq = beginFlowPlans(); qq != endFlowPlans(); ++qq)
00936       qq->writeElement(o, Tags::tag_flowplan);
00937     o->EndObject(Tags::tag_flowplans);
00938   }
00939 
00940   o->EndObject(tag);
00941 }
00942 
00943 
00944 DECLARE_EXPORT void OperationPlan::beginElement(XMLInput& pIn, const Attribute& pAttr)
00945 {
00946   if (pAttr.isA (Tags::tag_demand))
00947     pIn.readto( Demand::reader(Demand::metadata,pIn.getAttributes()) );
00948   else if (pAttr.isA(Tags::tag_owner))
00949     pIn.readto(createOperationPlan(metadata,pIn.getAttributes()));
00950   else if (pAttr.isA(Tags::tag_flowplans))
00951     pIn.IgnoreElement();
00952 }
00953 
00954 
00955 DECLARE_EXPORT void OperationPlan::endElement (XMLInput& pIn, const Attribute& pAttr, const DataElement& pElement)
00956 {
00957   // Note that the fields have been ordered more or less in the order
00958   // of their expected frequency.
00959   // Note that id and operation are handled already during the
00960   // operationplan creation. They don't need to be handled here...
00961   if (pAttr.isA(Tags::tag_quantity))
00962     pElement >> quantity;
00963   else if (pAttr.isA(Tags::tag_start))
00964     dates.setStart(pElement.getDate());
00965   else if (pAttr.isA(Tags::tag_end))
00966     dates.setEnd(pElement.getDate());
00967   else if (pAttr.isA(Tags::tag_owner) && !pIn.isObjectEnd())
00968   {
00969     OperationPlan* o = dynamic_cast<OperationPlan*>(pIn.getPreviousObject());
00970     if (o) setOwner(o);
00971   }
00972   else if (pIn.isObjectEnd())
00973   {
00974     // Initialize the operationplan
00975     if (!activate())
00976       // Initialization failed and the operationplan is deleted
00977       pIn.invalidateCurrentObject();
00978   }
00979   else if (pAttr.isA (Tags::tag_demand))
00980   {
00981     Demand * d = dynamic_cast<Demand*>(pIn.getPreviousObject());
00982     if (d) d->addDelivery(this);
00983     else throw LogicException("Incorrect object type during read operation");
00984   }
00985   else if (pAttr.isA(Tags::tag_locked))
00986     setLocked(pElement.getBool());
00987 }
00988 
00989 
00990 DECLARE_EXPORT void OperationPlan::setLocked(bool b)
00991 {
00992   if (b)
00993     flags |= IS_LOCKED;
00994   else
00995     flags &= ~IS_LOCKED;
00996   for (OperationPlan *x = firstsubopplan; x; x = x->nextsubopplan)
00997     x->setLocked(b);
00998   update();
00999 }
01000 
01001 
01002 DECLARE_EXPORT void OperationPlan::setDemand(Demand* l)
01003 {
01004   // No change
01005   if (l==dmd) return;
01006 
01007   // Unregister from previous lot
01008   if (dmd) dmd->removeDelivery(this);
01009 
01010   // Register the new demand and mark it changed
01011   dmd = l;
01012   if (l) l->setChanged();
01013 }
01014 
01015 
01016 PyObject* OperationPlan::create(PyTypeObject* pytype, PyObject* args, PyObject* kwds)
01017 {
01018   try
01019   {
01020     // Find or create the C++ object
01021     PythonAttributeList atts(kwds);
01022     Object* x = createOperationPlan(OperationPlan::metadata,atts);
01023     Py_INCREF(x);
01024 
01025     // Iterate over extra keywords, and set attributes.   @todo move this responsibility to the readers...
01026     if (x)
01027     {
01028       PyObject *key, *value;
01029       Py_ssize_t pos = 0;
01030       while (PyDict_Next(kwds, &pos, &key, &value))
01031       {
01032         PythonObject field(value);
01033         Attribute attr(PyString_AsString(key));
01034         if (!attr.isA(Tags::tag_operation) && !attr.isA(Tags::tag_id) && !attr.isA(Tags::tag_action))
01035         {
01036           int result = x->setattro(attr, field);
01037           if (result && !PyErr_Occurred())
01038             PyErr_Format(PyExc_AttributeError,
01039               "attribute '%s' on '%s' can't be updated",
01040               PyString_AsString(key), x->ob_type->tp_name);
01041         }
01042       };
01043     }
01044 
01045     if (x && !static_cast<OperationPlan*>(x)->activate())
01046     {
01047       PyErr_SetString(PythonRuntimeException, "operationplan activation failed");
01048       return NULL;
01049     }
01050     return x;
01051   }
01052   catch (...)
01053   {
01054     PythonType::evalException();
01055     return NULL;
01056   }
01057 }
01058 
01059 
01060 DECLARE_EXPORT PyObject* OperationPlan::getattro(const Attribute& attr)
01061 {
01062   if (attr.isA(Tags::tag_id))
01063     return PythonObject(getIdentifier());
01064   if (attr.isA(Tags::tag_operation))
01065     return PythonObject(getOperation());
01066   if (attr.isA(Tags::tag_flowplans))
01067     return new frepple::FlowPlanIterator(this);
01068   if (attr.isA(Tags::tag_loadplans))
01069     return new frepple::LoadPlanIterator(this);
01070   if (attr.isA(Tags::tag_quantity))
01071     return PythonObject(getQuantity());
01072   if (attr.isA(Tags::tag_start))
01073     return PythonObject(getDates().getStart());
01074   if (attr.isA(Tags::tag_end))
01075     return PythonObject(getDates().getEnd());
01076   if (attr.isA(Tags::tag_demand))
01077     return PythonObject(getDemand());
01078   if (attr.isA(Tags::tag_locked))
01079     return PythonObject(getLocked());
01080   if (attr.isA(Tags::tag_owner))
01081     return PythonObject(getOwner());
01082   if (attr.isA(Tags::tag_operationplans))
01083     return new OperationPlanIterator(this);
01084   if (attr.isA(Tags::tag_hidden))
01085     return PythonObject(getHidden());
01086   if (attr.isA(Tags::tag_unavailable))
01087     return PythonObject(getUnavailable());
01088   return NULL;
01089 }
01090 
01091 
01092 DECLARE_EXPORT int OperationPlan::setattro(const Attribute& attr, const PythonObject& field)
01093 {
01094   if (attr.isA(Tags::tag_quantity))
01095     setQuantity(field.getDouble());
01096   else if (attr.isA(Tags::tag_start))
01097     setStart(field.getDate());
01098   else if (attr.isA(Tags::tag_end))
01099     setEnd(field.getDate());
01100   else if (attr.isA(Tags::tag_locked))
01101     setLocked(field.getBool());
01102   else if (attr.isA(Tags::tag_demand))
01103   {
01104     if (!field.check(Demand::metadata))
01105     {
01106       PyErr_SetString(PythonDataException, "operationplan demand must be of type demand");
01107       return -1;
01108     }
01109     Demand* y = static_cast<Demand*>(static_cast<PyObject*>(field));
01110     setDemand(y);
01111   }
01112   else if (attr.isA(Tags::tag_owner))
01113   {
01114     if (!field.check(OperationPlan::metadata))
01115     {
01116       PyErr_SetString(PythonDataException, "operationplan demand must be of type demand");
01117       return -1;
01118     }
01119     OperationPlan* y = static_cast<OperationPlan*>(static_cast<PyObject*>(field));
01120     setOwner(y);
01121   }
01122   else
01123     return -1;
01124   return 0;
01125 }
01126 
01127 } // end namespace

Documentation generated for frePPLe by  doxygen