https://github.com/legraina/DynamicNurseScheduler
Tip revision: aef751362239e9ade6448f3731a625c29c9a9f0f authored by Antoine Legrain on 25 October 2023, 20:54:55 UTC
Only publish docker image on version tag. (#33)
Only publish docker image on version tag. (#33)
Tip revision: aef7513
DynamicMain.cpp
/*
* Copyright (C) 2020 Antoine Legrain, Jeremy Omer, and contributors.
* All Rights Reserved.
*
* You may use, distribute and modify this code under the terms of the MIT
* license.
*
* Please see the LICENSE file or visit https://opensource.org/licenses/MIT for
* full license detail.
*/
#include "ParseArguments.h"
#include "parsing/ParseINRC2.h"
#include "tools/DemandGenerator.h"
#include "solvers/StochasticSolver.h"
#include "tools/Tools.h"
#include "InitializeInstance.h"
using std::string;
using std::vector;
using std::pair;
/******************************************************************************
* Solve one week inside the stochastic process
******************************************************************************/
void solveOneWeek(InputPaths *pInputPaths) {
const std::string &solPath = pInputPaths->solutionPath();
string::size_type found = solPath.find_last_of('.');
string logPathIni = solPath.substr(0, found),
logPath = logPathIni + "Log.txt";
Tools::LogOutput logStream(logPath, false, true);
// set the scenario
logStream << "# Initialize the scenario" << std::endl;
PScenario pScen = buildInstance(*pInputPaths);
// set the options of the stochastic solver
// (the corresponding method needs to be change manually for tests)
logStream << "# Set the options" << std::endl;
StochasticSolverOptions options;
options.setStochasticSolverOptions(
pScen, solPath, logPathIni, pInputPaths->timeOut());
// check if a parameters file in the log path ini
// This files are hard coded as there are no options to give them
// to the simulator of the INRCII
string pathIni;
found = solPath.find_last_of('/');
if (found != string::npos)
pathIni = solPath.substr(0, found + 1);
string stochasticOptions = pathIni + "solverOptions.txt",
generationOptions = pathIni + "generationOptions.txt",
evaluationOptions = pathIni + "evaluationOptions.txt";
std::cout << "Check if one of those configuration files exists "
"to be loaded: " << std::endl;
std::cout << stochasticOptions << ", "
<< generationOptions << ", and "
<< evaluationOptions << std::endl;
try {
logStream << "Load stochastic options:" << std::endl;
options.read(stochasticOptions);
} catch (const char *ex) {}
try {
logStream << "Load generation options:" << std::endl;
options.generationParameters_.read(generationOptions);
} catch (const char *ex) {}
try {
logStream << "Load evaluation options:" << std::endl;
options.evaluationParameters_.read(stochasticOptions);
} catch (const char *ex) {}
// get history demands by reading the custom file
//
vector2D<PDemand> pDemandsHis = {pScen->pDemands()};
if (!pInputPaths->customInputFile().empty())
readCustom(pInputPaths->customInputFile(), pScen, &pDemandsHis);
Solver *pSolver = new StochasticSolver(pScen, options, pDemandsHis);
logStream << "# Solve the week" << std::endl;
double obj = pSolver->solve();
int solutionStatus = pSolver->status();
logStream << "# Solution status = " << solutionStatus << std::endl;
// pSolver->solutionToTxt(solPath);
logStream << pSolver->writeResourceCostsPerNurse() << std::endl;
// release memory
delete pSolver;
logStream.close();
// Write the solution in the required output format
//
if (!pInputPaths->customOutputFile().empty()) {
writeCustom(pInputPaths->customOutputFile(),
pInputPaths->week(0),
pInputPaths->customInputFile());
}
std::cout << "Custom output file : "
<< pInputPaths->customOutputFile() << std::endl;
}
/******************************************************************************
* Test a solution on multiple weeks
* In this method, the weeks are solved sequentially without knowledge of future
* demand
******************************************************************************/
pair<double, int> testMultipleWeeksStochastic(
const string &dataDir,
const string &instanceName,
int historyIndex,
const vector<int> &weekIndices,
StochasticSolverOptions stochasticSolverOptions,
const string &outdir,
std::vector<int> seeds) {
// build the paths of the input files
InputPaths inputPaths(dataDir, instanceName, historyIndex, weekIndices);
// initialize the scenario object of the first week
PScenario pScen = buildInstance(inputPaths);
stochasticSolverOptions.setStochasticSolverOptions(
pScen, outdir, "", stochasticSolverOptions.totalTimeLimitSeconds_, true);
// solve the problem for each week and store the solution in the vector below
vector<Roster> solution;
int nbWeeks = weekIndices.size();
Status solutionStatus;
// whole scenario for the whole horizon
PScenario pWholeScen = buildInstance(inputPaths);
vector2D<PDemand> pDemandsHistory;
double partialCost = 0, totalCost = 0;
int nbSched = 0;
for (int week = 0; week < nbWeeks; week++) {
auto rdm = Tools::getANewRandomGenerator(true);
if (week >= seeds.size())
seeds.emplace_back(rdm());
Tools::initializeRandomGenerator(seeds[week]);
pDemandsHistory.push_back(pScen->pDemands());
std::cout << pScen->toString() << std::endl;
auto *pSolver = new StochasticSolver(pScen,
stochasticSolverOptions,
pDemandsHistory,
partialCost);
totalCost = pSolver->solve();
nbSched += pSolver->getNGeneratedSolutions();
partialCost += pSolver->computeSolutionCost(false);
printf("Current cost = %.2f (partial cost = %.2f) \n\n",
totalCost, partialCost);
solutionStatus = pSolver->status();
if (solutionStatus == INFEASIBLE) {
delete pSolver;
break;
}
// update the overall solution with the solution of the week that was just
// treated
// warning: we must make sure that the main solution in pSolver applies only
// to the demand of one week
vector<Roster> weekSolution = pSolver->solutionAtDay(6);
if (solution.empty()) {
solution = weekSolution;
} else {
for (int n = 0; n < pScen->nNurses(); n++)
solution[n].pushBack(weekSolution[n]);
}
std::cout << "============== Current solver ===============" << std::endl;
std::cout << pSolver->writeResourceCosts() << std::endl;
// compute whole cost and compare to current cost
std::cout << "============== Whole solver ===============" << std::endl;
Solver wholeSolver(pWholeScen);
wholeSolver.loadSolution(solution);
double wholeCost = wholeSolver.computeSolutionCost();
std::cout << wholeSolver.writeResourceCosts() << std::endl;
if (abs(wholeCost - totalCost) >= 1) {
std::cout << "Current solver:" << std::endl;
std::cout << pSolver->writeResourceCostsINRC2() << std::endl;
std::cout << "============================================" << std::endl;
std::cout << "Whole solver:" << std::endl;
std::cout << wholeSolver.writeResourceCostsINRC2() << std::endl;
std::cout << wholeSolver.solutionToLogString() << std::endl;
Tools::throwError("Issue with current cost (%.1f) that is different of "
"the real cost (%.1f).", totalCost, wholeCost);
}
// prepare the scenario for next week if we did not reach the last week yet
if (week < nbWeeks - 1) {
// Initialize preferences
PPreferences pPref(nullptr);
// Read the demand and preferences and link them with the scenario
vector<PDemand> pDemands =
readWeekINRC2(inputPaths.week(week + 1), pScen, &pPref);
// read the initial state of the new week from the last state of the
// last week
// modify the dayId_ to show that this is the first day of the new week
vector<State> initialStates = pSolver->statesOfDay(7);
for (int i = 0; i < pScen->nNurses(); i++) {
initialStates[i].dayId_ = 0;
}
// update the scenario to treat next week
pScen->updateNewWeek(pDemands, pPref, initialStates);
// append demand and preferences
pWholeScen->pushBack(pDemands, pPref);
} else {
std::cout << wholeSolver.writeResourceCostsPerNurse() << std::endl;
}
delete pSolver;
}
printf("Total cost = %.2f \n", totalCost);
string seedOutfile = outdir + "seeds.txt";
Tools::LogOutput seedStream(seedOutfile, true);
char str[50];
snprintf(str, sizeof(str),
"Cost %.2f; NbGene %d; Seeds", totalCost, nbSched);
seedStream << str;
for (int s : seeds)
seedStream << " " << s;
seedStream << std::endl;
// Display the solution
// displaySolutionMultipleWeeks(dataDir, instanceName, historyIndex,
// weekIndices, solution, solutionStatus, outDir);
return {totalCost, nbSched};
}
/******************************************************************************
* Main method
******************************************************************************/
int main(int argc, char **argv) {
std::cout << "Number of arguments= " << argc << std::endl;
// Detect errors in the number of arguments
if (argc % 2 != 1) {
Tools::throwError("main: There should be an even number of arguments!");
} else if (argc > 1 && (argc < 9 || argc > 17)) {
Tools::throwError(
"main: There is either too many or not enough arguments!");
}
// Simulate default behavior for a test instance
if (argc == 1) {
std::cout << "Running the default method..." << std::endl;
string dataDir = "datasets/INRC2/";
string instanceName = "n030w4_1_2-7-0-9";
// instanceName = "n060w4_1_6-1-1-5";
// instanceName = "n030w8_1_2-7-0-9-3-6-0-6";
// instanceName = "n110w8_0_2-1-1-7-2-6-4-7";
std::vector<string> inst = Tools::tokenize<string>(instanceName, '_');
int historyIndex = std::stoi(inst[1]);
vector<int> weekIndices = Tools::tokenize<int>(inst[2], '-');
instanceName = inst[0];
std::vector<int> seeds = {68, 54, 78, 98, 68, 54, 78, 98};
Tools::ThreadsPool::setMaxGlobalThreadsToMax();
StochasticSolverOptions stochasticSolverOptions;
stochasticSolverOptions.totalTimeLimitSeconds_ = 20;
string outdir = "outfiles/" + instanceName + "/";
testMultipleWeeksStochastic(dataDir,
instanceName,
historyIndex,
weekIndices,
stochasticSolverOptions,
outdir,
seeds);
} else {
// Nominal behavior of the executable, as required by INRCII
// Retrieve the file names in arguments
InputPaths *pInputPaths = readArguments(argc, argv);
if (pInputPaths->inrc2()) PrintSolution::writeMultiWeeks = true;
// Initialize the random seed
Tools::initializeRandomGenerator(pInputPaths->randSeed());
// Solve the week
solveOneWeek(pInputPaths);
delete pInputPaths;
}
}