DECLARE_EXPORT Skill::~Skill() { // The ResourceSkill objects are automatically deleted by the destructor // of the Association list class. // Clean up the references on the load models for (Operation::iterator o = Operation::begin(); o != Operation::end(); ++o) for(Operation::loadlist::const_iterator l = o->getLoads().begin(); l != o->getLoads().end(); ++l) if (l->getSkill() == this) const_cast<Load&>(*l).setSkill(NULL); }
DECLARE_EXPORT Object* Load::finder(const DataValueDict& d) { // Check operation const DataValue* tmp = d.get(Tags::operation); if (!tmp) return NULL; Operation* oper = static_cast<Operation*>(tmp->getObject()); // Check resource field tmp = d.get(Tags::resource); if (!tmp) return NULL; Resource* res = static_cast<Resource*>(tmp->getObject()); // Walk over all loads of the operation, and return // the first one with matching const DataValue* hasEffectiveStart = d.get(Tags::effective_start); Date effective_start; if (hasEffectiveStart) effective_start = hasEffectiveStart->getDate(); const DataValue* hasEffectiveEnd = d.get(Tags::effective_end); Date effective_end; if (hasEffectiveEnd) effective_end = hasEffectiveEnd->getDate(); const DataValue* hasPriority = d.get(Tags::priority); int priority; if (hasPriority) priority = hasPriority->getInt(); const DataValue* hasName = d.get(Tags::name); string name; if (hasName) name = hasName->getString(); for (Operation::loadlist::const_iterator fl = oper->getLoads().begin(); fl != oper->getLoads().end(); ++fl) { if (fl->getResource() != res) continue; if (hasEffectiveStart && fl->getEffectiveStart() != effective_start) continue; if (hasEffectiveEnd && fl->getEffectiveEnd() != effective_end) continue; if (hasPriority && fl->getPriority() != priority) continue; if (hasName && fl->getName() != name) continue; return const_cast<Load*>(&*fl); } return NULL; }
DECLARE_EXPORT PyObject* printModelSize(PyObject* self, PyObject* args) { // Free Python interpreter for other threads Py_BEGIN_ALLOW_THREADS // Execute and catch exceptions size_t count, memsize; try { // Intro logger << endl << "Size information of frePPLe " << PACKAGE_VERSION << " (" << __DATE__ << ")" << endl << endl; // Print current locale #if defined(HAVE_SETLOCALE) || defined(_MSC_VER) logger << "Locale: " << setlocale(LC_ALL,NULL) << endl << endl; #else logger << endl; #endif // Print loaded modules Environment::printModules(); // Print the number of clusters logger << "Clusters: " << HasLevel::getNumberOfClusters() << " (hanging: " << HasLevel::getNumberOfHangingClusters() << ")" << endl << endl; // Header for memory size logger << "Memory usage:" << endl; logger << "Model \tNumber\tMemory" << endl; logger << "----- \t------\t------" << endl; // Plan size_t total = Plan::instance().getSize(); logger << "Plan \t1\t"<< Plan::instance().getSize() << endl; // Locations memsize = 0; for (Location::iterator l = Location::begin(); l != Location::end(); ++l) memsize += l->getSize(); logger << "Location \t" << Location::size() << "\t" << memsize << endl; total += memsize; // Customers memsize = 0; for (Customer::iterator c = Customer::begin(); c != Customer::end(); ++c) memsize += c->getSize(); logger << "Customer \t" << Customer::size() << "\t" << memsize << endl; total += memsize; // Buffers memsize = 0; for (Buffer::iterator b = Buffer::begin(); b != Buffer::end(); ++b) memsize += b->getSize(); logger << "Buffer \t" << Buffer::size() << "\t" << memsize << endl; total += memsize; // Setup matrices memsize = 0; for (SetupMatrix::iterator s = SetupMatrix::begin(); s != SetupMatrix::end(); ++s) memsize += s->getSize(); logger << "Setup matrix \t" << SetupMatrix::size() << "\t" << memsize << endl; total += memsize; // Resources memsize = 0; for (Resource::iterator r = Resource::begin(); r != Resource::end(); ++r) memsize += r->getSize(); logger << "Resource \t" << Resource::size() << "\t" << memsize << endl; total += memsize; // Skills and resourceskills size_t countResourceSkills(0), memResourceSkills(0); memsize = 0; for (Skill::iterator sk = Skill::begin(); sk != Skill::end(); ++sk) { memsize += sk->getSize(); for (Skill::resourcelist::const_iterator rs = sk->getResources().begin(); rs != sk->getResources().end(); ++rs) { ++countResourceSkills; memResourceSkills += rs->getSize(); } } logger << "Skill \t" << Skill::size() << "\t" << memsize << endl; logger << "ResourceSkill \t" << countResourceSkills << "\t" << memResourceSkills << endl; total += memsize; // Operations, flows and loads size_t countFlows(0), memFlows(0), countLoads(0), memLoads(0); memsize = 0; for (Operation::iterator o = Operation::begin(); o != Operation::end(); ++o) { memsize += o->getSize(); for (Operation::flowlist::const_iterator fl = o->getFlows().begin(); fl != o->getFlows().end(); ++ fl) { ++countFlows; memFlows += fl->getSize(); } for (Operation::loadlist::const_iterator ld = o->getLoads().begin(); ld != o->getLoads().end(); ++ ld) { ++countLoads; memLoads += ld->getSize(); } } logger << "Operation \t" << Operation::size() << "\t" << memsize << endl; logger << "Flow \t" << countFlows << "\t" << memFlows << endl; logger << "Load \t" << countLoads << "\t" << memLoads << endl; total += memsize + memFlows + memLoads; // Calendars (which includes the buckets) memsize = 0; for (Calendar::iterator cl = Calendar::begin(); cl != Calendar::end(); ++cl) memsize += cl->getSize(); logger << "Calendar \t" << Calendar::size() << "\t" << memsize << endl; total += memsize; // Items memsize = 0; for (Item::iterator i = Item::begin(); i != Item::end(); ++i) memsize += i->getSize(); logger << "Item \t" << Item::size() << "\t" << memsize << endl; total += memsize; // Demands memsize = 0; size_t c_count = 0, c_memsize = 0; for (Demand::iterator dm = Demand::begin(); dm != Demand::end(); ++dm) { memsize += dm->getSize(); for (Problem::const_iterator cstrnt(dm->getConstraints().begin()); cstrnt != dm->getConstraints().end(); ++cstrnt) { ++c_count; c_memsize += cstrnt->getSize(); } } logger << "Demand \t" << Demand::size() << "\t" << memsize << endl; logger << "Constraints \t" << c_count << "\t" << c_memsize << endl; total += memsize + c_memsize; // Operationplans size_t countloadplans(0), countflowplans(0); memsize = count = 0; for (OperationPlan::iterator j = OperationPlan::begin(); j!=OperationPlan::end(); ++j) { ++count; memsize += sizeof(*j); countloadplans += j->sizeLoadPlans(); countflowplans += j->sizeFlowPlans(); } total += memsize; logger << "OperationPlan\t" << count << "\t" << memsize << endl; // Flowplans memsize = countflowplans * sizeof(FlowPlan); total += memsize; logger << "FlowPlan \t" << countflowplans << "\t" << memsize << endl; // Loadplans memsize = countloadplans * sizeof(LoadPlan); total += memsize; logger << "LoadPlan \t" << countloadplans << "\t" << memsize << endl; // Problems memsize = count = 0; for (Problem::const_iterator pr = Problem::begin(); pr!=Problem::end(); ++pr) { ++count; memsize += pr->getSize(); } total += memsize; logger << "Problem \t" << count << "\t" << memsize << endl; // TOTAL logger << "Total \t\t" << total << endl << endl; } catch (...) { Py_BLOCK_THREADS; PythonType::evalException(); return NULL; } Py_END_ALLOW_THREADS // Reclaim Python interpreter return Py_BuildValue(""); }
DECLARE_EXPORT void HasLevel::computeLevels() { computationBusy = true; // Get exclusive access to this function in a multi-threaded environment. static Mutex levelcomputationbusy; ScopeMutexLock l(levelcomputationbusy); // Another thread may already have computed the levels while this thread was // waiting for the lock. In that case the while loop will be skipped. while (recomputeLevels) { // Reset the recomputation flag. Note that during the computation the flag // could be switched on again by some model change in a different thread. // In that case, the while loop will be rerun. recomputeLevels = false; // Force creation of all delivery operations f for (Demand::iterator gdem = Demand::begin(); gdem != Demand::end(); ++gdem) gdem->getDeliveryOperation(); // Reset current levels on buffers, resources and operations. // Also force the creation of all producing operations on the buffers. size_t numbufs = Buffer::size(); // Creating the producing operations of the buffers can cause new buffers // to be created. We repeat this loop until no new buffers are being added. // This isn't the most efficient loop, but it remains cheap and fast... while (true) { for (Buffer::iterator gbuf = Buffer::begin(); gbuf != Buffer::end(); ++gbuf) { gbuf->cluster = 0; gbuf->lvl = -1; gbuf->getProducingOperation(); } size_t numbufs_after = Buffer::size(); if (numbufs == numbufs_after) break; else numbufs = numbufs_after; } for (Resource::iterator gres = Resource::begin(); gres != Resource::end(); ++gres) { gres->cluster = 0; gres->lvl = -1; } for (Operation::iterator gop = Operation::begin(); gop != Operation::end(); ++gop) { gop->cluster = 0; gop->lvl = -1; } // Loop through all operations stack< pair<Operation*,int> > stack; Operation* cur_oper; int cur_level; Buffer *cur_buf; const Flow* cur_Flow; bool search_level; int cur_cluster; numberOfLevels = 0; numberOfClusters = 0; map<Operation*,short> visited; for (Operation::iterator g = Operation::begin(); g != Operation::end(); ++g) { // Select a new cluster number if (g->cluster) cur_cluster = g->cluster; else { // Detect hanging operations if (g->getFlows().empty() && g->getLoads().empty() && g->getSuperOperations().empty() && g->getSubOperations().empty() ) { // Cluster 0 keeps all dangling operations g->lvl = 0; continue; } cur_cluster = ++numberOfClusters; if (numberOfClusters >= UINT_MAX) throw LogicException("Too many clusters"); } #ifdef CLUSTERDEBUG logger << "Investigating operation '" << &*g << "' - current cluster " << g->cluster << endl; #endif // Do we need to activate the level search? // Criterion are: // - Not used in a super operation // - Have a producing flow on the operation itself // or on any of its sub operations search_level = false; if (g->getSuperOperations().empty()) { search_level = true; // Does the operation itself have producing flows? for (Operation::flowlist::const_iterator fl = g->getFlows().begin(); fl != g->getFlows().end() && search_level; ++fl) if (fl->isProducer()) search_level = false; if (search_level) { // Do suboperations have a producing flow? for (Operation::Operationlist::const_reverse_iterator i = g->getSubOperations().rbegin(); i != g->getSubOperations().rend() && search_level; ++i) for (Operation::flowlist::const_iterator fl = (*i)->getOperation()->getFlows().begin(); fl != (*i)->getOperation()->getFlows().end() && search_level; ++fl) if (fl->isProducer()) search_level = false; } } // If both the level and the cluster are de-activated, then we can move on if (!search_level && g->cluster) continue; // Start recursing // Note that as soon as push an operation on the stack we set its // cluster and/or level. This is avoid that operations are needlessly // pushed a second time on the stack. stack.push(make_pair(&*g, search_level ? 0 : -1)); visited.clear(); g->cluster = cur_cluster; if (search_level) g->lvl = 0; while (!stack.empty()) { // Take the top of the stack cur_oper = stack.top().first; cur_level = stack.top().second; stack.pop(); // Keep track of the maximum number of levels if (cur_level > numberOfLevels) numberOfLevels = cur_level; #ifdef CLUSTERDEBUG logger << " Recursing in Operation '" << *(cur_oper) << "' - current level " << cur_level << endl; #endif // Detect loops in the supply chain map<Operation*,short>::iterator detectloop = visited.find(cur_oper); if (detectloop == visited.end()) // Keep track of operations already visited visited.insert(make_pair(cur_oper,0)); else if (++(detectloop->second) > 1) // Already visited this operation enough times - don't repeat continue; // Push sub operations on the stack for (Operation::Operationlist::const_reverse_iterator i = cur_oper->getSubOperations().rbegin(); i != cur_oper->getSubOperations().rend(); ++i) { if ((*i)->getOperation()->lvl < cur_level) { // Search level and cluster stack.push(make_pair((*i)->getOperation(),cur_level)); (*i)->getOperation()->lvl = cur_level; (*i)->getOperation()->cluster = cur_cluster; } else if (!(*i)->getOperation()->cluster) { // Search for clusters information only stack.push(make_pair((*i)->getOperation(),-1)); (*i)->getOperation()->cluster = cur_cluster; } // else: no search required } // Push super operations on the stack for (list<Operation*>::const_reverse_iterator j = cur_oper->getSuperOperations().rbegin(); j != cur_oper->getSuperOperations().rend(); ++j) { if ((*j)->lvl < cur_level) { // Search level and cluster stack.push(make_pair(*j,cur_level)); (*j)->lvl = cur_level; (*j)->cluster = cur_cluster; } else if (!(*j)->cluster) { // Search for clusters information only stack.push(make_pair(*j,-1)); (*j)->cluster = cur_cluster; } // else: no search required } // Update level of resources linked to current operation for (Operation::loadlist::const_iterator gres = cur_oper->getLoads().begin(); gres != cur_oper->getLoads().end(); ++gres) { Resource *resptr = gres->getResource(); // Update the level of the resource if (resptr->lvl < cur_level) resptr->lvl = cur_level; // Update the cluster of the resource and operations using it if (!resptr->cluster) { resptr->cluster = cur_cluster; // Find more operations connected to this cluster by the resource for (Resource::loadlist::const_iterator resops = resptr->getLoads().begin(); resops != resptr->getLoads().end(); ++resops) if (!resops->getOperation()->cluster) { stack.push(make_pair(resops->getOperation(),-1)); resops->getOperation()->cluster = cur_cluster; } } } // Now loop through all flows of the operation for (Operation::flowlist::const_iterator gflow = cur_oper->getFlows().begin(); gflow != cur_oper->getFlows().end(); ++gflow) { cur_Flow = &*gflow; cur_buf = cur_Flow->getBuffer(); // Check whether the level search needs to continue search_level = cur_level!=-1 && cur_buf->lvl<cur_level+1; // Check if the buffer needs processing if (search_level || !cur_buf->cluster) { // Update the cluster of the current buffer cur_buf->cluster = cur_cluster; // Loop through all flows of the buffer for (Buffer::flowlist::const_iterator buffl = cur_buf->getFlows().begin(); buffl != cur_buf->getFlows().end(); ++buffl) { // Check level recursion if (cur_Flow->isConsumer() && search_level) { if (buffl->getOperation()->lvl < cur_level+1 && &*buffl != cur_Flow && buffl->isProducer()) { stack.push(make_pair(buffl->getOperation(),cur_level+1)); buffl->getOperation()->lvl = cur_level+1; buffl->getOperation()->cluster = cur_cluster; } else if (!buffl->getOperation()->cluster) { stack.push(make_pair(buffl->getOperation(),-1)); buffl->getOperation()->cluster = cur_cluster; } if (cur_level+1 > numberOfLevels) numberOfLevels = cur_level+1; cur_buf->lvl = cur_level+1; } // Check cluster recursion else if (!buffl->getOperation()->cluster) { stack.push(make_pair(buffl->getOperation(),-1)); buffl->getOperation()->cluster = cur_cluster; } } } // End of needs-procssing if statement } // End of flow loop } // End while stack not empty } // End of Operation loop // The above loop will visit ALL operations and recurse through the // buffers and resources connected to them. // Missing from the loop are buffers and resources that have no flows or // loads at all. We catch those poor lonely fellows now... for (Buffer::iterator gbuf2 = Buffer::begin(); gbuf2 != Buffer::end(); ++gbuf2) if (gbuf2->getFlows().empty()) gbuf2->cluster = 0; for (Resource::iterator gres2 = Resource::begin(); gres2 != Resource::end(); ++gres2) if (gres2->getLoads().empty()) gres2->cluster = 0; } // End of while recomputeLevels. The loop will be repeated as long as model // changes are done during the recomputation. // Unlock the exclusive access to this function computationBusy = false; }
DECLARE_EXPORT void Load::validate(Action action) { // Catch null operation and resource pointers Operation *oper = getOperation(); Resource *res = getResource(); if (!oper || !res) { // Invalid load model if (!oper && !res) throw DataException("Missing operation and resource on a load"); else if (!oper) throw DataException("Missing operation on a load on resource '" + res->getName() + "'"); else if (!res) throw DataException("Missing resource on a load on operation '" + oper->getName() + "'"); } // Check if a load with 1) identical resource, 2) identical operation and // 3) overlapping effectivity dates already exists Operation::loadlist::const_iterator i = oper->getLoads().begin(); for (; i != oper->getLoads().end(); ++i) if (i->getResource() == res && i->getEffective().overlap(getEffective()) && &*i != this) break; // Apply the appropriate action switch (action) { case ADD: if (i != oper->getLoads().end()) { throw DataException("Load of '" + oper->getName() + "' and '" + res->getName() + "' already exists"); } break; case CHANGE: throw DataException("Can't update a load"); case ADD_CHANGE: // ADD is handled in the code after the switch statement if (i == oper->getLoads().end()) break; throw DataException("Can't update a load"); case REMOVE: // This load was only used temporarily during the reading process delete this; if (i == oper->getLoads().end()) // Nothing to delete throw DataException("Can't remove nonexistent load of '" + oper->getName() + "' and '" + res->getName() + "'"); delete &*i; // Set a flag to make sure the level computation is triggered again HasLevel::triggerLazyRecomputation(); return; } // The statements below should be executed only when a new load is created. // Set a flag to make sure the level computation is triggered again HasLevel::triggerLazyRecomputation(); }
void SolverMRP::solve(const Load* l, void* v) { // Note: This method is only called for decrease loadplans and for the leading // load of an alternate group. See SolverMRP::checkOperation SolverMRPdata* data = static_cast<SolverMRPdata*>(v); unsigned int loglevel = data->getSolver()->getLogLevel(); if (data->state->q_qty >= 0.0) { // The loadplan is an increase in size, and the resource solver only needs // the decreases. data->state->a_qty = data->state->q_qty; data->state->a_date = data->state->q_date; return; } if (!l->hasAlternates() && !l->getAlternate()) { // CASE I: It is not an alternate load. // Delegate the answer immediately to the resource chooseResource(l, data); return; } // CASE II: It is an alternate load. // We ask each alternate load in order of priority till we find a load // that has a non-zero reply. // 1) collect a list of alternates list<const Load*> thealternates; const Load *x = l->hasAlternates() ? l : l->getAlternate(); SearchMode search = l->getSearch(); for (Operation::loadlist::const_iterator i = l->getOperation()->getLoads().begin(); i != l->getOperation()->getLoads().end(); ++i) if ((i->getAlternate() == x || &*i == x) && i->getEffective().within(data->state->q_loadplan->getDate())) thealternates.push_back(&*i); // 2) Sort the list thealternates.sort(sortLoad); // @todo cpu-intensive - better is to maintain the list in the correct order // 3) Control the planning mode bool originalPlanningMode = data->constrainedPlanning; data->constrainedPlanning = true; // Don't keep track of the constraints right now bool originalLogConstraints = data->logConstraints; data->logConstraints = false; // 4) Loop through all alternates or till we find a non-zero // reply (priority search) Date min_next_date(Date::infiniteFuture); LoadPlan *lplan = data->state->q_loadplan; double bestAlternateValue = DBL_MAX; double bestAlternateQuantity = DBL_MIN; const Load* bestAlternateSelection = NULL; double beforeCost = data->state->a_cost; double beforePenalty = data->state->a_penalty; OperationPlanState originalOpplan(lplan->getOperationPlan()); double originalLoadplanQuantity = data->state->q_loadplan->getQuantity(); for (list<const Load*>::const_iterator i = thealternates.begin(); i != thealternates.end();) { const Load *curload = *i; data->state->q_loadplan = lplan; // because q_loadplan can change! // 4a) Switch to this load if (lplan->getLoad() != curload) lplan->setLoad(curload); lplan->getOperationPlan()->restore(originalOpplan); data->state->q_qty = lplan->getQuantity(); data->state->q_date = lplan->getDate(); // 4b) Ask the resource // TODO XXX Need to insert another loop here! It goes over all resources qualified for the required skill. // The qualified resources need to be sorted based on their cost. If the cost is the same we should use a decent tie breaker, eg number of skills or number of loads. // The first resource with the qualified skill that is available will be used. CommandManager::Bookmark* topcommand = data->setBookmark(); if (search == PRIORITY) curload->getResource()->solve(*this,data); else { data->getSolver()->setLogLevel(0); try {curload->getResource()->solve(*this,data);} catch (...) { data->getSolver()->setLogLevel(loglevel); // Restore the planning mode data->constrainedPlanning = originalPlanningMode; data->logConstraints = originalLogConstraints; throw; } data->getSolver()->setLogLevel(loglevel); } // 4c) Evaluate the result if (data->state->a_qty > ROUNDING_ERROR && lplan->getOperationPlan()->getQuantity() > 0) { if (search == PRIORITY) { // Priority search: accept any non-zero reply // Restore the planning mode data->constrainedPlanning = originalPlanningMode; data->logConstraints = originalLogConstraints; return; } else { // Other search modes: evaluate all double deltaCost = data->state->a_cost - beforeCost; double deltaPenalty = data->state->a_penalty - beforePenalty; // Message if (loglevel>1 && search != PRIORITY) logger << indent(l->getOperation()->getLevel()) << " Operation '" << l->getOperation()->getName() << "' evaluates alternate '" << curload->getResource() << "': cost " << deltaCost << ", penalty " << deltaPenalty << endl; if (deltaCost < ROUNDING_ERROR && deltaPenalty < ROUNDING_ERROR) { // Zero cost and zero penalty on this alternate. It won't get any better // than this, so we accept this alternate. if (loglevel>1) logger << indent(l->getOperation()->getLevel()) << " Operation '" << l->getOperation()->getName() << "' chooses alternate '" << curload->getResource() << "' " << search << endl; // Restore the planning mode data->constrainedPlanning = originalPlanningMode; data->logConstraints = originalLogConstraints; return; } data->state->a_cost = beforeCost; data->state->a_penalty = beforePenalty; double val = 0.0; switch (search) { case MINCOST: val = deltaCost / lplan->getOperationPlan()->getQuantity(); break; case MINPENALTY: val = deltaPenalty / lplan->getOperationPlan()->getQuantity(); break; case MINCOSTPENALTY: val = (deltaCost + deltaPenalty) / lplan->getOperationPlan()->getQuantity(); break; default: LogicException("Unsupported search mode for alternate load"); } if (val + ROUNDING_ERROR < bestAlternateValue || (fabs(val - bestAlternateValue) < ROUNDING_ERROR && lplan->getOperationPlan()->getQuantity() > bestAlternateQuantity)) { // Found a better alternate bestAlternateValue = val; bestAlternateSelection = curload; bestAlternateQuantity = lplan->getOperationPlan()->getQuantity(); } } } else if (loglevel>1 && search != PRIORITY) logger << indent(l->getOperation()->getLevel()) << " Operation '" << l->getOperation()->getName() << "' evaluates alternate '" << curload->getResource() << "': not available before " << data->state->a_date << endl; // 4d) Undo the plan on the alternate data->rollback(topcommand); // 4e) Prepare for the next alternate if (data->state->a_date < min_next_date) min_next_date = data->state->a_date; if (++i != thealternates.end() && loglevel>1 && search == PRIORITY) logger << indent(curload->getOperation()->getLevel()) << " Alternate load switches from '" << curload->getResource()->getName() << "' to '" << (*i)->getResource()->getName() << "'" << endl; } // 5) Unconstrained plan: plan on the first alternate if (!originalPlanningMode && !(search != PRIORITY && bestAlternateSelection)) { // Switch to unconstrained planning data->constrainedPlanning = false; bestAlternateSelection = *(thealternates.begin()); } // 6) Finally replan on the best alternate if (!originalPlanningMode || (search != PRIORITY && bestAlternateSelection)) { // Message if (loglevel>1) logger << indent(l->getOperation()->getLevel()) << " Operation '" << l->getOperation()->getName() << "' chooses alternate '" << bestAlternateSelection->getResource() << "' " << search << endl; // Switch back data->state->q_loadplan = lplan; // because q_loadplan can change! data->state->a_cost = beforeCost; data->state->a_penalty = beforePenalty; if (lplan->getLoad() != bestAlternateSelection) lplan->setLoad(bestAlternateSelection); lplan->getOperationPlan()->restore(originalOpplan); // TODO XXX need to restore also the selected resource with the right skill! data->state->q_qty = lplan->getQuantity(); data->state->q_date = lplan->getDate(); bestAlternateSelection->getResource()->solve(*this,data); // Restore the planning mode data->constrainedPlanning = originalPlanningMode; data->logConstraints = originalLogConstraints; return; } // 7) No alternate gave a good result data->state->a_date = min_next_date; data->state->a_qty = 0; // Restore the planning mode data->constrainedPlanning = originalPlanningMode; // Maintain the constraint list if (originalLogConstraints) { const Load *primary = *(thealternates.begin()); data->planningDemand->getConstraints().push( ProblemCapacityOverload::metadata, primary->getResource(), originalOpplan.start, originalOpplan.end, -originalLoadplanQuantity); } data->logConstraints = originalLogConstraints; if (loglevel>1) logger << indent(lplan->getOperationPlan()->getOperation()->getLevel()) << " Alternate load doesn't find supply on any alternate : " << data->state->a_qty << " " << data->state->a_date << endl; }