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

Documentation generated for frePPLe by  doxygen