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