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

Documentation generated for frePPLe by  doxygen