#include "ILPTrackPlans.hpp" #include "TrackPlan.hpp" #include "gurobi_c++.h" #include #include #include #include #include #include #include #include #include "../../../General/Model/STFMockInstance.hpp" #include "gurobi_c.h" namespace solverlib { using namespace modellib; ILPTrackPlans::ILPTrackPlans(std::vector& plans): m_env(true), trackPlans(plans) { init(); } void ILPTrackPlans::init() { loggerlib::Logger::systemNotify(loggerlib::LOGGER_PROGRESS, "Initializing Gurobi env"); //m_env.set("LogFile", "ILP.log"); //m_env.set(GRB_IntParam_ThreadLimit, 1); m_env.start(); m_model = std::make_unique(m_env); m_model->set(GRB_DoubleParam_TuneTimeLimit, 3600); m_model->set(GRB_DoubleParam_TimeLimit, 600); /* MIPFocus 2 OBBT 1 Cuts 0 PrePasses 2 */ m_model->set(GRB_IntParam_MIPFocus, 2); m_model->set(GRB_IntParam_Cuts, 0); m_model->set(GRB_IntParam_PrePasses, 2); m_model->set(GRB_IntParam_OBBT, 1); loggerlib::Logger::systemNotify(loggerlib::LOGGER_PROGRESS, "Fetching swap information"); initSwaps(); loggerlib::Logger::systemNotify(loggerlib::LOGGER_PROGRESS, "Initializing variables"); initVariables(); loggerlib::Logger::systemNotify(loggerlib::LOGGER_PROGRESS, "Initializing constraints"); addAllConstraints(); /*m_model->tune(); m_model->getTuneResult(0); m_model->write("tuning.prm");*/ } void ILPTrackPlans::initSwaps() { loggerlib::Logger::systemNotify(loggerlib::LOGGER_PROGRESS, "Getting unique swaps and mandatory selections"); std::unordered_set> swapsU; unsigned int p = 0; swapsOfTrackPlans.resize(trackPlans.size()); for(auto& plan : trackPlans) { if(!plan.isTrash) { for(auto& sw : plan.mock->swaps) { swapsU.insert(sw); swap_setoftrackPlan[sw].push_back(p); } } ++p; } if(!swapsU.empty()) { std::move(swapsU.begin(), swapsU.end(), std::back_inserter(swaps)); std::sort(swaps.begin(), swaps.end(), [&](auto& s1, auto& s2){ return s1->getPotentialSwapSlot().getDebut().getRelativeDate() < s2->getPotentialSwapSlot().getDebut().getRelativeDate(); }); // Maintenant que swaps est trié et indexé, on peut remplir swapsOfTrackPlans for(unsigned int i = 0; i < swaps.size(); ++i) { for(auto& planIdx : swap_setoftrackPlan[swaps[i]]) { swapsOfTrackPlans[planIdx].push_back(i); } } loggerlib::Logger::systemNotify(loggerlib::LOGGER_PROGRESS, "Checking incompatible swaps"); for(unsigned int i = 0; i < swaps.size(); ++i) { auto& swap = swaps[i]; const auto& plansOfSwap = swap_setoftrackPlan[swap]; for(unsigned int j = 0; j < swaps.size(); ++j) { if(i == j) continue; auto& swapComp = swaps[j]; const auto& plansOfSwapComp = swap_setoftrackPlan[swapComp]; // Vérifier si une rame est en commun bool sharedRame = false; for(auto& r : swap->um_crit.ramesInfo) if(swapComp->find(r.id)) { sharedRame = true; break; } if(!sharedRame) for(auto& r : swap->um_sane.ramesInfo) if(swapComp->find(r.id)) { sharedRame = true; break; } if(!sharedRame) continue; // Vérifier si les deux croisements ont au moins un trackplan commun bool hasCommonPlan = false; for(auto& planIdx : plansOfSwap) { for(auto& planIdxComp : plansOfSwapComp) { if(planIdx == planIdxComp) { hasCommonPlan = true; break; } } if(hasCommonPlan) break; } //hasCommonPlan = plansOfSwap == plansOfSwapComp; // Incompatibles si rame commune ET aucun trackplan commun if(!hasCommonPlan) { swap_incompswap[swap].push_back(j); } } for(unsigned int j = 0; j < swaps.size(); ++j) { if(j != i) { if(swaps[i]->getPotentialSwapSlot().getDebut().getRelativeDate() > swaps[j]->getPotentialSwapSlot().getDebut().getRelativeDate()) { auto sw = swaps[i]; bool foundRame = false; for(auto& ramInfList : {sw->um_sane.ramesInfo, sw->um_crit.ramesInfo}) { for(auto& rameInf : ramInfList) { foundRame = swaps[j]->find(rameInf.id); if(foundRame) { break; } } if(foundRame) { break; } } if(foundRame && swap_setoftrackPlan[sw] == swap_setoftrackPlan[swaps[j]]) { swapDependsOnSwaps[i].push_back(j); } } } } } for(unsigned int tr = 0; tr < trackPlans.size(); ++tr) { if(!trackPlans[tr].isTrash)// && trackPlans[tr].mock->swaps.empty()) { trackPlanNoSWIncompSwap[tr]; unsigned int s = 0; for(auto& sw : swaps) { if(trackPlans[tr].mock->swaps.empty() || std::find(swap_setoftrackPlan[sw].begin(), swap_setoftrackPlan[sw].end(), tr) == swap_setoftrackPlan[sw].end()) { bool foundRame = false; for(auto& ramInfList : {sw->um_sane.ramesInfo, sw->um_crit.ramesInfo}) { for(auto& rameInf : ramInfList) { foundRame = trackPlans[tr].isRameInScheduleAfterDate(rameInf.id, sw->getPotentialSwapSlot().getDebut().getRelativeDate()); if(foundRame) { break; } } if(foundRame) { break; } } if(foundRame) { trackPlanNoSWIncompSwap[tr].push_back(s); } } ++s; } } } } } void ILPTrackPlans::initVariables() { x_ks.resize(trackPlans.size()); for(unsigned int i = 0; i < trackPlans.size(); ++i) { std::string name = "x_" + std::to_string(i); x_ks[i] = m_model->addVar(0.0,1.0,0.0, GRB_BINARY, name.c_str()); } swap_ls.resize(swaps.size()); for(unsigned int i = 0; i < swaps.size(); ++i) { std::string name = "swap_" + std::to_string(i); swap_ls[i] = m_model->addVar(0.0,1.0,0.0, GRB_BINARY, name.c_str()); } } std::optional ILPTrackPlans::solve() { m_model->optimize(); //printSol(); return buildSolution(); } std::optional ILPTrackPlans::buildSolution() { Planification planif; int status = m_model->get(GRB_IntAttr_Status); std::vector finalTrackPlans; std::vector> finalSwaps; std::vector finalVar; if(status != GRB_INFEASIBLE) { unsigned int i = 0; for(auto& var : x_ks) { if(var.get(GRB_DoubleAttr_X) > 0) { finalTrackPlans.push_back(trackPlans[i]); finalVar.push_back(i); } ++i; } unsigned int s = 0; for(auto& var : swap_ls) { if(var.get(GRB_DoubleAttr_X) > 0) { finalSwaps.push_back(swaps[s]); //std::cout << swaps[s]->um_crit.ramesInfo[0].id << " - " << swaps[s]->um_sane.ramesInfo[0].id << " at " << swaps[s]->getPotentialSwapSlot().getDebut().getRelativeDate() << std::endl; } ++s; } } else { return std::nullopt; } if(!finalTrackPlans.empty()) { // std::unordered_map> doublonsInfos; bool hasDoubles = false; unsigned int i = 0; for(auto& trSch : finalTrackPlans) { for(auto& dec : trSch.schedule) { doublonsInfos[dec.first].push_back(i); if(doublonsInfos[dec.first].size() > 1) hasDoubles= true; } ++i; } if(!hasDoubles) { loggerlib::Logger::systemNotify(loggerlib::LOGGER_PROGRESS, "Solution from ILP is valid"); } else { loggerlib::Logger::systemNotify(loggerlib::LOGGER_PROGRESS, "Removing jobs appearing multiple types"); for(auto& doublon : doublonsInfos) { std::string tracksAppear = ""; for(auto& el : doublon.second) { tracksAppear += std::to_string(!finalTrackPlans[el].isTrash ? finalTrackPlans[el].track : finalVar[el]) + " "; } loggerlib::Logger::systemNotify(loggerlib::LOGGER_PROGRESS, "Job " + std::to_string(doublon.first) + " : " + tracksAppear); if(doublon.second.size() > 1) { std::map> modifiedTrPlans; for(auto& trSchid: doublon.second) { auto trSchCop = finalTrackPlans[trSchid]; auto cost = trSchCop.cost; auto suppDec= trSchCop.schedule[doublon.first]; trSchCop.schedule.erase(doublon.first); trSchCop.shiftAndRecomputeCostsOnMachine(suppDec.empV); auto costAfter = trSchCop.cost; int gain = costAfter - cost; modifiedTrPlans[gain] = std::make_pair(trSchid,trSchCop); } unsigned int j = 0; for(auto modTrPlan = modifiedTrPlans.begin(); modTrPlan != modifiedTrPlans.end(); ++modTrPlan) { if(j == modifiedTrPlans.size()-1) break; finalTrackPlans[modTrPlan->second.first] = modTrPlan->second.second; ++j; } } } } } unsigned int totalCost = 0; unsigned int totalDiagCost = 0; unsigned int totalNbExclu = 0; std::shared_ptr newMock = STFMockInstance::copy(STFInstance::getCurrentInstance()->mockInstance); std::sort(finalSwaps.begin(), finalSwaps.end(), [&](auto& el1, auto& el2){return el1->getPotentialSwapSlot().getDebut().getRelativeDate() < el2->getPotentialSwapSlot().getDebut().getRelativeDate(); }); for(auto& sw : finalSwaps) { newMock->swap(*sw); newMock->swaps.push_back(sw); } planif.setPlanificationState(newMock); for(auto& plan : finalTrackPlans) { totalCost += plan.cost; totalDiagCost += plan.diagCost; totalNbExclu += plan.isTrash && !plan.schedule.empty(); std::cout << "Track " << (!plan.isTrash ? std::to_string(plan.track) : "trash") << " "; std::vector> decsSorted; for(auto& dec : plan.schedule) { decsSorted.push_back(dec); } std::sort(decsSorted.begin(), decsSorted.end(), [&](auto& e1, auto& e2){ return e1.second.lastCreneau.first < e2.second.lastCreneau.second;}); std::cout << "SOURCE : " << std::to_string(static_cast(plan.source)) << " "; for(auto& dec : decsSorted) { std::cout << STFMockInstance::jobs[dec.first]->getOpId() << " : " << dec.second.lastCreneau.first << "-" << dec.second.lastCreneau.second << " | "; } std::cout << std::endl; auto& decisions = plan.schedule; //auto& swaps = finalSwaps; auto& trStops = newMock->trajectoryStops; for(auto& d : decisions) { if(!d.second.excluded) { unsigned int rameId = STFMockInstance::rameOfOperations[d.first]; STFMockInstance::jobs[d.first]->setIdRame(STFMockInstance::rames[rameId]->getId()); STFMockInstance::jobs[d.first]->setNumeroEF(STFMockInstance::rames[rameId]->getNumeroEF()); bool isPCr = trStops[d.second.empR].typeDispo.first == typeStop::RLT_POST_CROISEMENT_SUBIT || trStops[d.second.empR].typeDispo.first == typeStop::RLT_POST_CROISEMENT_VOULU; std::pair slot = d.second.rejected ? std::make_pair(d.second.lastCreneau.first, d.second.lastCreneau.first + STFMockInstance::jobs[d.first]->getDureeDiag()) : std::make_pair(d.second.lastCreneau.first, d.second.lastCreneau.first + STFMockInstance::jobs[d.first]->getDuree()); OperationPlanifie opPlan(d.first, d.second.voie, d.second.site, d.second.empR, d.second.empV, slot, d.second.rejected, isPCr); planif.addOperation(opPlan); } else { unsigned int rameId = STFMockInstance::rameOfOperations[d.first]; STFMockInstance::jobs[d.first]->setIdRame(STFMockInstance::rames[rameId]->getId()); STFMockInstance::jobs[d.first]->setNumeroEF(STFMockInstance::rames[rameId]->getNumeroEF()); planif.getOpImplanifiables().push_back(d.first); } } } for(auto& s : finalSwaps) { planif.getCroisementsEffectues().push_back(*s); } std::cout << "Total cost = " << totalCost << std::endl; std::cout << "Total diagcost = " << totalDiagCost << std::endl; std::cout << "Nb exclusions = " << totalNbExclu << std::endl; return planif; } void ILPTrackPlans::warmStart(std::vector& plans) { std::vector varToSet; for(auto& plan : plans) { if(plan.isTrash) { auto find = std::find_if(trackPlans.begin(), trackPlans.end(), [&](const TrackPlan& p) { return p.isTrash && p.schedule.begin()->first == plan.schedule.begin()->first; }); if(find != trackPlans.end()) { varToSet.push_back(std::distance(trackPlans.begin(), find)); } } else { auto find = std::find_if(trackPlans.begin(), trackPlans.end(), [&](const TrackPlan& p) { return p == plan; }); if(find != trackPlans.end()) { varToSet.push_back(std::distance(trackPlans.begin(), find)); } } } for(auto& var : x_ks) { var.set(GRB_DoubleAttr_Start, 0); } for(auto& var : swap_ls) { var.set(GRB_DoubleAttr_Start, 0); } unsigned int cost = 0; for(auto j : varToSet) { x_ks[j].set(GRB_DoubleAttr_Start, 1); cost += trackPlans[j].cost; for(auto& var : swapsOfTrackPlans[j]) { swap_ls[var].set(GRB_DoubleAttr_Start, 1); } } std::cout << "Cost warm : " << cost << std::endl; } void ILPTrackPlans::addSwapsSelectedAreCompatible() { std::unordered_map> pairs; for(unsigned int p = 0; p < x_ks.size(); ++p) { for(auto& cols : swapsOfTrackPlans[p]) { std::string name = "P1: swap " +std::to_string(cols) +" of trplan " + std::to_string(p) + " must be selected if trplan is"; auto sw = swaps[cols]; bool foundRame = false; for(auto& ramInfList : {sw->um_sane.ramesInfo, sw->um_crit.ramesInfo}) { for(auto& rameInf : ramInfList) { foundRame = trackPlans[p].isRameInScheduleAfterDate(rameInf.id, sw->getPotentialSwapSlot().getDebut().getRelativeDate()); if(foundRame) { break; } } if(foundRame) { break; } } if(foundRame) m_model->addConstr(swap_ls[cols] >= x_ks[p], name.c_str()); // si constrSw=0 alors swap=0 } } for(unsigned int id_swap = 0; id_swap < swaps.size(); ++id_swap) { for(auto& cols : swap_incompswap[swaps[id_swap]]) { if(pairs[id_swap].find(cols) == pairs[id_swap].end() && pairs[cols].find(id_swap) == pairs[cols].end()) { pairs[id_swap].insert(cols); pairs[cols].insert(id_swap); std::string incomp = "swap " + std::to_string(id_swap) + " and swap " + std::to_string(cols) + " cannot be selected together"; m_model->addConstr(swap_ls[id_swap] + swap_ls[cols] <= 1, incomp.c_str()); } } } for(auto tr_incomp_swap : trackPlanNoSWIncompSwap) { for(auto& sw : tr_incomp_swap.second) { std::string incomp = "swap " + std::to_string(sw) + " and tr " + std::to_string(tr_incomp_swap.first) + " cannot be selected together"; m_model->addConstr(swap_ls[sw] + x_ks[tr_incomp_swap.first] <= 1, incomp.c_str()); } } for(auto swapComp : swapDependsOnSwaps) { for(auto& sw : swapComp.second) { std::string incomp = "if swap " + std::to_string(sw) + " is selected then swap " + std::to_string(swapComp.first) + " must be selected"; m_model->addConstr(swap_ls[sw] >= swap_ls[swapComp.first], incomp.c_str()); } } } void ILPTrackPlans::printSol() { unsigned int i = 0; unsigned int cost = 0; unsigned int nbExcl = 0; int status = m_model->get(GRB_IntAttr_Status); if(status != GRB_INFEASIBLE) { for(auto& var : x_ks) { if(var.get(GRB_DoubleAttr_X) > 0) { std::cout << "Track " + (!trackPlans[i].isTrash ? std::to_string(trackPlans[i].track) : "trash" + std::to_string(i)) << std::endl; std::cout << "Jobs : "; for(auto& job : trackPlans[i].schedule) { std::cout << job.first << " "; } std::cout << std::endl; if(trackPlans[i].isTrash) { nbExcl++; } cost += trackPlans[i].cost; } ++i; } std::cout << "Nb EXcl : " << nbExcl << std::endl; std::cout << "Total cost : " << cost << std::endl; } } void ILPTrackPlans::addAllConstraints() { addTrackAppearsOneTime(); addJobAppearsOneTime(); addSwapsSelectedAreCompatible(); addEpsilonConstraintDiagnosis(); addObjectiveFunction(); } void ILPTrackPlans::addTrackAppearsOneTime() { std::unordered_map trackExpressions; for(unsigned int id_col = 0; id_col < trackPlans.size(); ++id_col) { if(!trackPlans[id_col].isTrash) { trackExpressions[trackPlans[id_col].track] += x_ks[id_col]; } } for(auto& trackExpr : trackExpressions) { std::string name = "track " + std::to_string(trackExpr.first) + " must appear at most once"; m_model->addConstr(trackExpr.second <= 1, name.c_str()); } } void ILPTrackPlans::addJobAppearsOneTime() { for(unsigned int id_job = 0; id_job < STFMockInstance::jobs.size(); ++id_job) { GRBLinExpr job_once = 0; for(unsigned int id_col = 0; id_col < trackPlans.size(); ++id_col) { if(trackPlans[id_col].isJobOnTrack(id_job)) { job_once += x_ks[id_col]; } } std::string name = "job " + std::to_string(id_job) + " must appear at least once"; m_model->addConstr(job_once >= 1, name.c_str()); } } void ILPTrackPlans::addObjectiveFunction() { GRBLinExpr sum_T = 0; for(unsigned int id_col = 0; id_col < trackPlans.size(); ++id_col) { sum_T += x_ks[id_col]*trackPlans[id_col].cost; } m_model->setObjective(sum_T,GRB_MINIMIZE); } void ILPTrackPlans::addEpsilonConstraintDiagnosis() { GRBLinExpr sum_epsilon = 0; for(unsigned int id_col = 0; id_col < trackPlans.size(); ++id_col) { sum_epsilon += x_ks[id_col]*trackPlans[id_col].diagCost; } std::string name = "Diagnosis cost must be below or equal to epsilon = " + std::to_string(configlib::Configuration::Global.EPSILON); m_model->addConstr(sum_epsilon <= configlib::Configuration::Global.EPSILON, name.c_str()); } }