DECLARE_EXPORT void LoadPlan::setResource(Resource* newres, bool check) { // Nothing to do if (res == newres) return; // Validate the argument if (!newres) throw DataException("Can't switch to NULL resource"); if (check) { // New resource must be a subresource of the load's resource. bool ok = false; for (const Resource* i = newres; i && !ok; i = i->getOwner()) if (i == getLoad()->getResource()) ok = true; if (!ok) throw DataException("Resource isn't matching the resource specified on the load"); // New resource must have the required skill if (getLoad()->getSkill()) { ok = false; for(Resource::skilllist::const_iterator s = newres->getSkills().begin(); s != newres->getSkills().end() && !ok; s++) if (s->getSkill() == getLoad()->getSkill()) ok = true; if (!ok) throw DataException("Resource misses the skill specified on the load"); } } // Mark entities as changed if (oper) oper->getOperation()->setChanged(); if (res && res!=newres) res->setChanged(); newres->setChanged(); // Update also the setup operationplans if (oper && oper->getOperation() != OperationSetup::setupoperation) { bool oldHasSetup = ld && !ld->getSetup().empty() // TODO not fully correct. If the load is changed, it is still possible that the old load had a setup, while ld doesn't have one any more... && res && res->getSetupMatrix(); bool newHasSetup = ld && !ld->getSetup().empty() && newres->getSetupMatrix(); OperationPlan *setupOpplan = NULL; if (oldHasSetup) { for (OperationPlan::iterator i(oper); i != oper->end(); ++i) if (i->getOperation() == OperationSetup::setupoperation) { setupOpplan = &*i; break; } if (!setupOpplan) oldHasSetup = false; } if (oldHasSetup) { if (newHasSetup) { // Case 1: Both the old and new load require a setup LoadPlan *setupLdplan = NULL; for (OperationPlan::LoadPlanIterator j = setupOpplan->beginLoadPlans(); j != setupOpplan->endLoadPlans(); ++j) if (j->getLoad() == ld) { setupLdplan = &*j; break; } if (!setupLdplan) throw LogicException("Can't find loadplan on setup operationplan"); // Update the loadplan setupOpplan->setEnd(setupOpplan->getDates().getEnd()); } else { // Case 2: Delete the old setup which is not required any more oper->eraseSubOperationPlan(setupOpplan); } } else { if (newHasSetup) { // Case 3: Create a new setup operationplan OperationSetup::setupoperation->createOperationPlan( 1, Date::infinitePast, oper->getDates().getEnd(), NULL, oper); } //else: // Case 4: No setup for the old or new load } } // Find the loadplan before the setup LoadPlan *prevldplan = NULL; if (getOperationPlan()->getOperation() == OperationSetup::setupoperation) { for (TimeLine<LoadPlan>::const_iterator i = getResource()->getLoadPlans().begin(isStart() ? getOtherLoadPlan() : this); i != getResource()->getLoadPlans().end(); --i) { const LoadPlan *l = dynamic_cast<const LoadPlan*>(&*i); if (l && l->getOperationPlan() != getOperationPlan() && l->getOperationPlan() != getOperationPlan()->getOwner() && !l->isStart()) { prevldplan = const_cast<LoadPlan*>(l); break; } } if (!prevldplan) { for (TimeLine<LoadPlan>::const_iterator i = getResource()->getLoadPlans().begin(isStart() ? getOtherLoadPlan() : this); i != getResource()->getLoadPlans().end(); ++i) { const LoadPlan *l = dynamic_cast<const LoadPlan*>(&*i); if (l && l->getOperationPlan() != getOperationPlan() && l->getOperationPlan() != getOperationPlan()->getOwner() && !l->isStart()) { prevldplan = const_cast<LoadPlan*>(l); break; } } } } // Change this loadplan and its brother for (LoadPlan *ldplan = getOtherLoadPlan(); true; ) { // Remove from the old resource, if there is one if (res) { res->loadplans.erase(ldplan); res->setChanged(); } // Insert in the new resource. // This code assumes the date and quantity of the loadplan don't change // when a new resource is assigned. ldplan->res = newres; newres->loadplans.insert( ldplan, ld->getLoadplanQuantity(ldplan), ld->getLoadplanDate(ldplan) ); // Repeat for the brother loadplan or exit if (ldplan != this) ldplan = this; else break; } // Update the setups on the old resource if (prevldplan) prevldplan->res->updateSetups(prevldplan); // Change the resource newres->setChanged(); }
DECLARE_EXPORT void SolverMRP::chooseResource(const Load* l, void* v) // @todo handle unconstrained plan!!!! { if (!l->getSkill() && !l->getResource()->isGroup()) { // CASE 1: No skill involved, and no aggregate resource either l->getResource()->solve(*this, v); return; } // CASE 2: Skill involved, or aggregate resource SolverMRPdata* data = static_cast<SolverMRPdata*>(v); unsigned int loglevel = data->getSolver()->getLogLevel(); // 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; // Initialize Date min_next_date(Date::infiniteFuture); LoadPlan *lplan = data->state->q_loadplan; Resource *bestAlternateSelection = NULL; double bestCost = DBL_MAX; bool qualified_resource_exists = false; double bestAlternateValue = DBL_MAX; double bestAlternateQuantity = DBL_MIN; double beforeCost = data->state->a_cost; double beforePenalty = data->state->a_penalty; OperationPlanState originalOpplan(lplan->getOperationPlan()); double originalLoadplanQuantity = lplan->getQuantity(); data->getSolver()->setLogLevel(0); // Silence during this loop // Loop over all candidate resources stack<Resource*> res_stack; res_stack.push(l->getResource()); while (!res_stack.empty()) { // Pick next resource Resource* res = res_stack.top(); res_stack.pop(); // If it's an aggregate, push it's members on the stack if (res->isGroup()) { for (Resource::memberIterator x = res->beginMember(); x != Resource::end(); ++x) res_stack.push(&*x); continue; } // Check if the resource has the right skill if (l->getSkill()) { bool isqualified = false; for (Resource::skilllist::const_iterator i = res->getSkills().begin(); i != res->getSkills().end() && !isqualified; ++i) { if (i->getSkill() == l->getSkill() && originalOpplan.start >= i->getEffective().getStart() && originalOpplan.end <= i->getEffective().getEnd()) isqualified = true; } // Next resource in the loop if not qualified if (!isqualified) continue; // TODO if there is a date effective skill, we need to consider it in the reply } qualified_resource_exists = true; // Switch to this resource data->state->q_loadplan = lplan; // because q_loadplan can change! lplan->setResource(res); lplan->getOperationPlan()->restore(originalOpplan); data->state->q_qty = lplan->getQuantity(); data->state->q_date = lplan->getDate(); // Plan the resource CommandManager::Bookmark* topcommand = data->setBookmark(); try { res->solve(*this,data); } catch (...) { data->getSolver()->setLogLevel(loglevel); data->constrainedPlanning = originalPlanningMode; data->logConstraints = originalLogConstraints; data->rollback(topcommand); throw; } data->rollback(topcommand); // Evaluate the result if (data->state->a_qty > ROUNDING_ERROR && lplan->getOperationPlan()->getQuantity() > 0) { double deltaCost = data->state->a_cost - beforeCost; double deltaPenalty = data->state->a_penalty - beforePenalty; // Message if (loglevel>1) logger << indent(l->getOperation()->getLevel()) << " Operation '" << l->getOperation()->getName() << "' evaluates alternate '" << res << "': cost " << deltaCost << ", penalty " << deltaPenalty << endl; data->state->a_cost = beforeCost; data->state->a_penalty = beforePenalty; double val = 0.0; switch (l->getSearch()) { case PRIORITY: val = 1; // @todo skill-resource model doesn't have a priority field yet break; 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 = res; bestAlternateQuantity = lplan->getOperationPlan()->getQuantity(); } } else if (loglevel>1) logger << indent(l->getOperation()->getLevel()) << " Operation '" << l->getOperation()->getName() << "' evaluates alternate '" << lplan->getResource() << "': not available before " << data->state->a_date << endl; // Keep track of best next date if (data->state->a_date < min_next_date) min_next_date = data->state->a_date; } data->getSolver()->setLogLevel(loglevel); // Not a single resource has the appropriate skills. You're joking? if (!qualified_resource_exists) throw DataException("No qualified resource exists for load"); // Restore the best candidate we found in the loop above if (bestAlternateSelection) { // Message if (loglevel>1) logger << indent(l->getOperation()->getLevel()) << " Operation '" << l->getOperation()->getName() << "' chooses alternate '" << bestAlternateSelection << "' " << l->getSearch() << 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->getResource() != bestAlternateSelection) lplan->setResource(bestAlternateSelection); lplan->getOperationPlan()->restore(originalOpplan); data->state->q_qty = lplan->getQuantity(); data->state->q_date = lplan->getDate(); bestAlternateSelection->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) { data->planningDemand->getConstraints().push( ProblemCapacityOverload::metadata, l->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; }