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