Surgeries scheduling¶
Principles learned¶
Add multiple list decision variables
Use the find operator
Order interval decision variables by pairing them up with a list variable
Problem¶
A hospital, with a fixed amount of rooms, has a set of surgeries to be done. Each surgery has a given processing time and a given number of nurses needed. A surgery can only start when the room and the number of nurses needed are available. The nurses’ shifts have an earliest start and latest end. They also have a maximum duration.
The goal is to find a sequence of surgeries that minimizes the makespan: the time when all surgeries have been processed.
Download the exampleData¶
The format of the data files is as follows:
First line: number of rooms, number of nurses, number of surgeries
Second line: minimum starting time of each surgery (in hours)
Third line: maximum ending time of each surgery (in hours)
Fourth line: for each surgery, its duration (in minutes)
Fifth line: for each surgery, the number of nurses needed
Sixth line: for each nurse, the earliest possible shift (in hours)
Seventh line: for each nurse, the latest possible end of the shift (in hours)
Eigth line: the maximum duration of a shift (in hours)
The following lines:
- For each surgery (representing a line), for each room (representing a
column) 0 if the room is compatible with the surgery, 1 otherwise.
Program¶
We use interval decision variables to model the time ranges for each surgery. The length of these intervals is constrained by the processing time required for each surgery.
In addition to interval decision variables representing the surgery time ranges, we also use list decision variables. These lists represent the ordering of surgeries within each room.
Every surgery must be assigned to a room, and the partition()
operator
on the list ensures that each surgery is allocated to exactly one room.
The find()
operator takes an array of lists and an integer value as
arguments. It returns the position of the list containing the specified value
within the array, if such a list exists. Here, this operator is used to identify
the ID of the room where each surgery will occur.
To ensure that no more than one surgery is performed in a room at the same time, we impose a time constraint: a surgery cannot start in a room until the previous surgery in that room has finished.
For each nurse, we create a list to represent the surgeries they are responsible for, in the order they will perform them. Nurses are restricted to starting only after their earliest possible shift and finishing before their latest possible shift. Additionally, they cannot work longer than the maximum duration of a shift. A nurse is also limited to working on one surgery at a time, which translates to the constraint that the start time of any surgery assigned to a nurse must be after the end of their previous surgery.
Each surgery has a minimum nurse requirement. This constraint is modeled by
summing, for each surgery s, the lists of nurses’ assigned surgeries in which s
appears. The contains()
operator takes a list and an integer value as
arguments and returns 1 if the integer value is in the list, and 0 otherwise.
Therefore, if a nurse is assigned to surgery s, the operator will return 1.
Summing the results of this operator across all nurses provides the total number
of nurses assigned to a surgery.
The objective is to minimize the makespan, defined as the time at which all surgeries are completed.
- Execution:
- hexaly surgeries_scheduling.hxm inFileName=instances/instancesurgery.txt [outFileName=] [hxTimeLimit=]
use io;
function input() {
local usage = "Usage: hexaly surgeries_scheduling.hxm inFileName=instanceFile"
+ " [outFileName=outputFile] [hxTimeLimit=timeLimit]";
if (inFileName == nil) throw usage;
inFile = io.openRead(inFileName);
// Number of rooms
numRooms = inFile.readInt();
// Number of nurses
numNurses = inFile.readInt();
// Number of surgeries
numSurgeries = inFile.readInt();
// Minimum start of each surgery
for [s in 0...numSurgeries] {
minStart[s] = inFile.readInt();
}
// Maximum end of each surgery
for [s in 0...numSurgeries] {
maxEnd[s] = inFile.readInt() * 60;
}
// Duration of each surgery
for [s in 0...numSurgeries] {
duration[s] = inFile.readInt();
}
// Number of nurses needed for each surgery
for [s in 0...numSurgeries] {
neededNurses[s] = inFile.readInt();
}
// Earliest starting shift for each nurse
for [n in 0...numNurses] {
shiftEarliestStart[n] = inFile.readInt() * 60;
}
// Latest ending shift for each nurse
for [n in 0...numNurses] {
shiftLatestEnd[n] = inFile.readInt() * 60;
}
// Maximum duration of each nurse's shift
maxShiftDuration = inFile.readInt() * 60;
// Incompatible rooms for each surgery
incompatibleRooms = {};
for [s in 0...numSurgeries] {
for [r in 0...numRooms] {
incompatibleRooms[s][r] = inFile.readInt();
}
}
}
/* Declare the optimization model */
function model() {
// Sequence of surgeries for each room
surgeryOrder[r in 0...numRooms] <- list(numSurgeries);
// Each surgery is scheduled in a room
constraint partition[r in 0...numRooms](surgeryOrder[r]);
// Only compatible rooms can be selected for a surgery
for [s in 0...numSurgeries][r in incompatibleRooms[s]]
constraint contains(surgeryOrder[r], s) == 0;
// For each surgery, the selected room
// This variable is only used to export the solution
selectedRoom[s in 0...numSurgeries] <- find(surgeryOrder, s);
// Interval decisions: time range of each surgery
// Each surgery cannot start before and end after a certain time
surgeries[s in 0...numSurgeries] <- interval(minStart[s], maxEnd[s]);
for [s in 0...numSurgeries] {
// Each surgery has a specific duration
constraint length(surgeries[s]) == duration[s];
}
// A room can only have one surgery at a time
for [r in 0...numRooms] {
constraint and(0...count(surgeryOrder[r])-1,
s => start(surgeries[surgeryOrder[r][s+1]])
>= end(surgeries[surgeryOrder[r][s]]));
}
// Each surgery needs a specific amount of nurse
for [n in 0...numNurses] {
// Sequence of surgery for each nurse
nurseOrder[n] <- list(numSurgeries);
firstSurgeryStart <- count(nurseOrder[n]) > 0
? start(surgeries[nurseOrder[n][0]])
: shiftEarliestStart[n];
lastSurgeryEnd <- count(nurseOrder[n]) > 0
? end(surgeries[nurseOrder[n][count(nurseOrder[n])-1]])
: shiftEarliestStart[n];
// Each nurse has an earliest starting shift and latest ending shift to be respected
constraint firstSurgeryStart >= shiftEarliestStart[n];
constraint lastSurgeryEnd <= shiftLatestEnd[n];
// Each nurse cannot work more than a certain amount of hours in a row
constraint lastSurgeryEnd - firstSurgeryStart <= maxShiftDuration;
// Each nurse can only be at one operation at a time and stays all along the surgery
constraint and(0...count(nurseOrder[n])-1,
s => surgeries[nurseOrder[n][s+1]] > surgeries[nurseOrder[n][s]]);
}
// Each surgery needs a certain amount of nurses
for [s in 0...numSurgeries] {
constraint sum(0...numNurses, n => contains(nurseOrder[n], s)) >= neededNurses[s];
}
// Minimize the makespan: end of the last surgery
makespan <- max[s in 0...numSurgeries](end(surgeries[s]));
minimize makespan;
}
/* Parameterize the solver */
function param() {
if (hxTimeLimit == nil) hxTimeLimit = 20;
}
/* Write the solution in a file with the following format:
- for each surgery, the selected room, the start and end dates
the nurses working on this operation*/
function output() {
if (outFileName != nil) {
outFile = io.openWrite(outFileName);
println("Solution written in file ", outFileName);
listNurses = {};
for [n in 0...numNurses] {
surgNurse = nurseOrder[n].value;
for [s in surgNurse] {
if (listNurses[s] == nil) {
listNurses[s] = {n};
} else {
listNurses[s].add(n);
}
}
}
for [s in 0...numSurgeries] {
outFile.print(s, "\t");
outFile.print(selectedRoom[s].value, "\t");
outFile.print(surgeries[s].value.start, "\t");
outFile.print(surgeries[s].value.end, "\t");
outFile.print(listNurses[s]);
outFile.println();
}
}
}
- Execution (Windows)
- set PYTHONPATH=%HX_HOME%\bin\pythonpython surgeries_scheduling.py instances\instancesurgery.txt
- Execution (Linux)
- export PYTHONPATH=/opt/hexaly_13_5/bin/pythonpython surgeries_scheduling.py instances/instancesurgery.txt
import hexaly.optimizer
import sys
def read_instance(filename):
with open(filename) as f:
lines = f.readlines()
first_line = lines[0].split()
# Number of rooms
num_rooms = int(first_line[0])
# Number of nurses
num_nurses = int(first_line[1])
# Number of surgeries
num_surgeries = int(first_line[2])
# Minimum start of each surgery
line_min_start = lines[1].split()
min_start = [int(line_min_start[s]) * 60 for s in range(num_surgeries)]
# Maximum end of each surgery
line_max_end = lines[2].split()
max_end = [int(line_max_end[s]) * 60 for s in range(num_surgeries)]
# Duration of each surgery
line_duration = lines[3].split()
duration = [int(line_duration[s]) for s in range(num_surgeries)]
# Number of nurses needed for each surgery
line_nurse_needed = lines[4].split()
needed_nurses = [int(line_nurse_needed[s]) for s in range(num_surgeries)]
# Earliest starting shift for each nurse
line_earliest_shift = lines[5].split()
shift_earliest_start = [int(line_earliest_shift[s]) * 60 for s in range(num_nurses)]
# Latest ending shift for each nurse
line_latest_shift = lines[6].split()
shift_latest_end = [int(line_latest_shift[s]) * 60 for s in range(num_nurses)]
# Maximum duration of each nurse's shift
max_shift_duration = int(lines[7].split()[0]) * 60
#Incompatible rooms for each surgery
incompatible_rooms = [[0 for r in range(num_rooms)] for s in range(num_surgeries)]
for s in range(num_surgeries):
line = lines[8+s].split()
for r in range(num_rooms):
incompatible_rooms[s][r] = int(line[r])
return (num_rooms, num_nurses, num_surgeries, min_start, max_end,
needed_nurses, shift_earliest_start, shift_latest_end,
max_shift_duration, incompatible_rooms, duration)
def main(instance_file, output_file, time_limit):
num_rooms, num_nurses, num_surgeries, min_start, max_end, needed_nurses, \
shift_earliest_start, shift_latest_end, max_shift_duration, \
incompatible_rooms, duration = read_instance(instance_file)
with hexaly.optimizer.HexalyOptimizer() as optimizer:
#
# Declare the optimization model
#
model = optimizer.model
# Sequence of surgery for each room
surgery_order = [model.list(num_surgeries) for _ in range(num_rooms)]
rooms = model.array(surgery_order)
# Each surgery is scheduled in a room
model.constraint(model.partition(rooms))
# Only compatible rooms can be selected for a surgery
for s in range(num_surgeries):
for r in incompatible_rooms[s]:
model.constraint(model.contains(surgery_order[r], s) == 0)
# For each surgery, the selected room
# This variable is only used to export the solution
selected_room = [model.find(rooms, s) for s in range(num_surgeries)]
# Interval decisions: time range of each surgery
# Each surgery cannot start before and end after a certain time
surgeries = [model.interval(min_start[s], max_end[s]) for s in range(num_surgeries)]
for s in range(num_surgeries):
# Each surgery has a specific duration
model.constraint(model.length(surgeries[s]) == duration[s])
surgery_array = model.array(surgeries)
# A room can only have one surgery at a time
for r in range(num_rooms):
sequence = surgery_order[r]
sequence_lambda = model.lambda_function(
lambda s: surgery_array[sequence[s]] < surgery_array[sequence[s + 1]])
model.constraint(
model.and_(model.range(0, model.count(sequence) - 1), sequence_lambda)
)
# Each surgery needs a specific amount of nurse
nurse_order = [model.list(num_surgeries) for _ in range(num_nurses)]
for n in range(num_nurses):
# Each nurse has an earliest starting shift and latest ending shift to be respected
sequence = nurse_order[n]
first_surgery_start = model.iif(
model.count(sequence) > 0,
model.start(surgery_array[sequence[0]]),
shift_earliest_start[n]
)
last_surgery_end = model.iif(
model.count(sequence) > 0,
model.end(surgery_array[sequence[model.count(sequence)-1]]),
shift_earliest_start[n]
)
model.constraint(first_surgery_start >= shift_earliest_start[n])
model.constraint(last_surgery_end <= shift_latest_end[n])
# Each nurse cannot work more than a certain amount of hours
model.constraint(last_surgery_end - first_surgery_start <= max_shift_duration)
# Each nurse can only be at one operation at a time and stays all along the surgery
sequence_lambda = model.lambda_function(
lambda s: surgery_array[sequence[s]] < surgery_array[sequence[s + 1]])
model.constraint(model.and_(
model.range(0, model.count(sequence) - 1), sequence_lambda))
# Each surgery needs a certain amount of nurses
nurse_order_array = model.array(nurse_order)
for s in range(num_surgeries):
model.constraint(
model.sum(
model.range(0, num_nurses),
model.lambda_function(lambda n : model.contains(nurse_order_array[n], s))
)
>= needed_nurses[s]
)
# Minimize the makespan: end of the last task
makespan = model.max([model.end(surgeries[s]) for s in range(num_surgeries)])
model.minimize(makespan)
model.close()
# Parameterize the optimizer
optimizer.param.time_limit = time_limit
optimizer.solve()
# Write the solution in a file with the following format:
# - for each surgery, the selected room, the start and end dates,
# the nurses working on this operation
if output_file != None:
with open(output_file, "w") as f:
print("Solution written in file", output_file)
list_nurses = {}
for n in range(num_nurses):
surg_nurse = nurse_order[n].value
for s in surg_nurse:
if s not in list_nurses:
list_nurses[s] = [n]
else:
list_nurses[s].append(n)
for s in range(num_surgeries):
f.write(str(s) + "\t"
+ "\t" + str(selected_room[s].value)
+ "\t" + str(surgeries[s].value.start())
+ "\t" + str(surgeries[s].value.end())
+ "\t" + str(list_nurses[s]) + "\n")
if __name__ == '__main__':
if len(sys.argv) < 2:
print("Usage: python surgeries_scheduling.py instance_file [output_file] [time_limit]")
sys.exit(1)
instance_file = sys.argv[1]
output_file = sys.argv[2] if len(sys.argv) >= 3 else None
time_limit = int(sys.argv[3]) if len(sys.argv) >= 4 else 20
main(instance_file, output_file, time_limit)
- Compilation / Execution (Windows)
- cl /EHsc surgeries_scheduling.cpp -I%HX_HOME%\include /link %HX_HOME%\bin\hexaly135.libsurgeries_scheduling instances\instancesurgery.txt
- Compilation / Execution (Linux)
- g++ surgeries_scheduling.cpp -I/opt/hexaly_13_5/include -lhexaly135 -lpthread -o surgeries_scheduling./surgeries_scheduling instances/instancesurgery.txt
#include "optimizer/hexalyoptimizer.h"
#include <algorithm>
#include <fstream>
#include <iostream>
#include <limits>
#include <numeric>
#include <vector>
using namespace hexaly;
class SurgeriesScheduling {
private:
// Number of surgeries
int numSurgeries;
// Number of rooms
int numRooms;
// Number of nurses
int numNurses;
// Minimum start of each surgery
std::vector<int> minStart;
// Maximum end of each surgery
std::vector<int> maxEnd;
// Duration of each surgery
std::vector<int> duration;
// Number of nurses needed for each surgery
std::vector<int> neededNurses;
// Earliest starting shift for each nurse
std::vector<int> shiftEarliestStart;
// Latest ending shift for each nurse
std::vector<int> shiftLatestEnd;
// Maximum duration of each nurse's shift
int maxShiftDuration;
// Incompatible rooms for each surgery
std::vector<std::vector<int>> incompatibleRooms;
// Hexaly Optimizer
HexalyOptimizer optimizer;
// Decision variables: time range of each surgery
std::vector<HxExpression> surgeries;
// Decision variables: sequence of surgery on each room
std::vector<HxExpression> surgeryOrder;
// For each surgery, the selected room
// This variable is only used to export the solution
std::vector<HxExpression> selectedRoom;
// Decision variables: for each nurse, their surgeries order
std::vector<HxExpression> nurseOrder;
// Objective = minimize the makespan: end of the last surgery
HxExpression makespan;
public:
SurgeriesScheduling() : optimizer() {}
void readInstance(const std::string& fileName) {
std::ifstream infile;
infile.exceptions(std::ifstream::failbit | std::ifstream::badbit);
infile.open(fileName.c_str());
infile >> numRooms;
infile >> numNurses;
infile >> numSurgeries;
minStart.resize(numSurgeries);
for (int s = 0; s < numSurgeries; ++s) {
int start;
infile >> start;
minStart[s] = start * 60;
}
maxEnd.resize(numSurgeries);
for (int s = 0; s < numSurgeries; ++s) {
int end;
infile >> end;
maxEnd[s] = end * 60;
}
duration.resize(numSurgeries);
for (int s = 0; s < numSurgeries; ++s)
infile >> duration[s];
neededNurses.resize(numSurgeries);
for (int s = 0; s < numSurgeries; ++s)
infile >> neededNurses[s];
shiftEarliestStart.resize(numNurses);
for (int s = 0; s < numNurses; ++s) {
int shift;
infile >> shift;
shiftEarliestStart[s] = shift * 60 ;
}
shiftLatestEnd.resize(numNurses);
for (int s = 0; s < numNurses; ++s) {
int shift;
infile >> shift;
shiftLatestEnd[s] = shift * 60;
}
int maxShiftDurationHeure;
infile >> maxShiftDurationHeure;
maxShiftDuration = maxShiftDurationHeure * 60;
incompatibleRooms.resize(numSurgeries);
for (int s = 0; s < numSurgeries; ++s) {
incompatibleRooms[s].resize(numRooms);
for (int r = 0; r < numRooms; ++r) {
int room;
infile >> room;
incompatibleRooms[s][r] = room;
}
}
infile.close();
}
void solve(int timeLimit) {
// Declare the optimization model
HxModel model = optimizer.getModel();
// Sequence of surgery on each room
surgeryOrder.resize(numRooms);
HxExpression room = model.array();
for (unsigned int r = 0; r < numRooms; ++r) {
surgeryOrder[r] = model.listVar(numSurgeries);
room.addOperand(surgeryOrder[r]);
}
// Each surgery is scheduled in a room
model.constraint(model.partition(room));
// Only compatible rooms can be selected for a surgery
for (int s = 0; s < numSurgeries; ++s) {
for (int r : incompatibleRooms[s]) {
model.constraint(model.contains(surgeryOrder[r], s) == 0);
}
}
// For each surgery, the selected room
selectedRoom.resize(numSurgeries);
for (int s = 0; s < numSurgeries; ++s) {
selectedRoom[s] = model.find(room, s);
}
// Interval decisions: time range of each surgery
surgeries.resize(numSurgeries);
for (int s = 0; s < numSurgeries; ++s) {
// Each surgery cannot start before and end after a certain time
surgeries[s] = model.intervalVar(minStart[s], maxEnd[s]);
// Each surgery has a specific duration
model.constraint(model.length(surgeries[s]) == duration[s]);
}
HxExpression surgeryArray = model.array(surgeries.begin(), surgeries.end());
// A room can only have one surgery at a time
for (int r = 0; r < numRooms; ++r) {
HxExpression sequence = surgeryOrder[r];
HxExpression sequenceLambda = model.createLambdaFunction([&](HxExpression s) {
return surgeryArray[sequence[s]] < surgeryArray[sequence[s + 1]]; });
model.constraint(model.and_(model.range(0, model.count(sequence) - 1), sequenceLambda));
}
// Sequence of surgery for each nurse
nurseOrder.resize(numNurses);
for (int n = 0; n < numNurses; ++n) {
nurseOrder[n] = model.listVar(numSurgeries);
HxExpression sequence = nurseOrder[n];
HxExpression firstSurgeryStart = model.iif(
model.count(sequence) > 0,
model.start(surgeryArray[sequence[0]]),
shiftEarliestStart[n]);
HxExpression lastSurgeryEnd = model.iif(
model.count(sequence) > 0,
model.end(surgeryArray[sequence[model.count(sequence)-1]]),
shiftEarliestStart[n]);
// Each nurse has an earliest starting shift and latest ending shift to be respected
model.constraint(firstSurgeryStart >= shiftEarliestStart[n]);
model.constraint(lastSurgeryEnd <= shiftLatestEnd[n]);
// Each nurse cannot work more than a certain amount of hours in a row
model.constraint(lastSurgeryEnd - firstSurgeryStart <= maxShiftDuration);
// Each nurse can only be at one operation at a time and stays all along the surgery
HxExpression sequenceLambda = model.createLambdaFunction([&](HxExpression s) {
return surgeryArray[sequence[s]] < surgeryArray[sequence[s + 1]]; });
model.constraint(model.and_(model.range(0, model.count(sequence) - 1), sequenceLambda));
}
HxExpression nurseOrderArray = model.array(nurseOrder.begin(), nurseOrder.end());
// Each surgery needs a specific amount of nurse
for (int s = 0; s < numSurgeries; ++s) {
model.constraint(model.sum(model.range(0, numNurses),
model.createLambdaFunction([&](HxExpression n) {
return model.contains(nurseOrderArray[n], s);}))
>= neededNurses[s]);
}
// Minimize the makespan: end of the last surgery
makespan = model.max();
for (int s = 0; s < numSurgeries; ++s) {
makespan.addOperand(model.end(surgeries[s]));
}
model.minimize(makespan);
model.close();
// Parameterize the optimizer
optimizer.getParam().setTimeLimit(timeLimit);
optimizer.solve();
}
/* Write the solution in a file with the following format:
* - for each surgery, the selected room, the start and end dates,
the nurses working on this operation */
void writeSolution(const std::string& fileName) {
std::ofstream outfile(fileName.c_str());
if (!outfile.is_open()) {
std::cerr << "File " << fileName << " cannot be opened." << std::endl;
exit(1);
}
std::cout << "Solution written in file " << fileName << std::endl;
std::vector<std::vector<int>> listNurses;
listNurses.resize(numSurgeries);
for (unsigned int n = 0; n < numNurses; ++n) {
HxCollection surgNurse = nurseOrder[n].getCollectionValue();
for (int s = 0; s < surgNurse.count(); ++s) {
listNurses[surgNurse.get(s)].push_back(n);
}
}
for (unsigned int s = 0; s < numSurgeries; ++s) {
outfile << s << "\t" << selectedRoom[s].getValue() << "\t"
<< surgeries[s].getIntervalValue().getStart() << "\t"
<< surgeries[s].getIntervalValue().getEnd() << "\t";
outfile << "[";
bool first = true;
for (int n : listNurses[s]) {
if (!first) {
outfile << ", ";
}
outfile << n;
first = false;
}
outfile << "]" << std::endl;
}
outfile.close();
}
};
int main(int argc, char** argv) {
if (argc < 2) {
std::cout << "Usage: surgeries_scheduling instanceFile [outputFile] [timeLimit]" << std::endl;
exit(1);
}
const char* instanceFile = argv[1];
const char* outputFile = argc > 2 ? argv[2] : NULL;
const char* strTimeLimit = argc > 3 ? argv[3] : "20";
SurgeriesScheduling model;
try {
model.readInstance(instanceFile);
const int timeLimit = atoi(strTimeLimit);
model.solve(timeLimit);
if (outputFile != NULL)
model.writeSolution(outputFile);
return 0;
} catch (const std::exception& e) {
std::cerr << "An error occurred: " << e.what() << std::endl;
return 1;
}
}
- Compilation / Execution (Windows)
- copy %HX_HOME%\bin\Hexaly.NET.dll .csc SurgeriesScheduling.cs /reference:Hexaly.NET.dllSurgeriesScheduling instances\instancesurgery.txt
using System;
using System.IO;
using System.Linq;
using Hexaly.Optimizer;
using System.Collections.Generic;
public class SurgeriesScheduling : IDisposable
{
// Number of surgeries
private int numSurgeries;
// Number of rooms
private int numRooms;
// Number of nurses
private int numNurses;
// Minimum start of each surgery
private int[] minStart;
// Maximum end of each surgery
private int[] maxEnd;
// Duration of each surgery
private int[] duration;
// Number of nurses needed for each surgery
private int [] neededNurses;
// Earliest starting shift for each nurse
private int [] shiftEarliestStart;
// Latest ending shift for each nurse
private int [] shiftLatestEnd;
// Maximum duration of each nurse's shift
private int maxShiftDuration;
// Incompatible rooms for each surgery
private int[][] incompatibleRooms;
// Hexaly Optimizer
private HexalyOptimizer optimizer;
// Decision variables: time range of each surgery
private HxExpression[] surgeries;
// Decision variables: sequence of surgery on each room
private HxExpression[] surgeryOrder;
// For each surgery, the selected room
// This variable is only used to export the solution
private HxExpression[] selectedRoom;
// Decision variables: the surgeries order of each nurse
private HxExpression[] nurseOrder;
// Objective = minimize the makespan: end of the last surgery
private HxExpression makespan;
public SurgeriesScheduling()
{
optimizer = new HexalyOptimizer();
}
public void ReadInstance(string fileName)
{
using (StreamReader input = new StreamReader(fileName))
{
char[] separators = new char[] { '\t', ' ' };
string[] splitted = input
.ReadLine()
.Split(separators, StringSplitOptions.RemoveEmptyEntries);
numRooms = int.Parse(splitted[0]);
numNurses = int.Parse(splitted[1]);
numSurgeries = int.Parse(splitted[2]);
minStart = new int[numSurgeries];
splitted = input
.ReadLine()
.Split(separators, StringSplitOptions.RemoveEmptyEntries);
for (int s = 0; s < numSurgeries; ++s)
{
minStart[s] = int.Parse(splitted[s]) * 60;
}
maxEnd = new int[numSurgeries];
splitted = input
.ReadLine()
.Split(separators, StringSplitOptions.RemoveEmptyEntries);
for (int s = 0; s < numSurgeries; ++s)
{
maxEnd[s] = int.Parse(splitted[s]) * 60;
}
duration = new int[numSurgeries];
splitted = input
.ReadLine()
.Split(separators, StringSplitOptions.RemoveEmptyEntries);
for (int s = 0; s < numSurgeries; ++s)
{
duration[s] = int.Parse(splitted[s]);
}
neededNurses = new int[numSurgeries];
splitted = input
.ReadLine()
.Split(separators, StringSplitOptions.RemoveEmptyEntries);
for (int s = 0; s < numSurgeries; ++s)
{
neededNurses[s] = int.Parse(splitted[s]);
}
shiftEarliestStart = new int[numNurses];
splitted = input
.ReadLine()
.Split(separators, StringSplitOptions.RemoveEmptyEntries);
for (int n = 0; n < numNurses; ++n)
{
shiftEarliestStart[n] = int.Parse(splitted[n]) * 60;
}
shiftLatestEnd = new int[numNurses];
splitted = input
.ReadLine()
.Split(separators, StringSplitOptions.RemoveEmptyEntries);
for (int n = 0; n < numNurses; ++n)
{
shiftLatestEnd[n] = int.Parse(splitted[n]) * 60;
}
splitted = input
.ReadLine()
.Split(separators, StringSplitOptions.RemoveEmptyEntries);
maxShiftDuration = int.Parse(splitted[0]) * 60;
incompatibleRooms = new int[numSurgeries][];
for (int s = 0; s < numSurgeries; ++s)
{
splitted = input
.ReadLine()
.Split(separators, StringSplitOptions.RemoveEmptyEntries);
incompatibleRooms[s] = new int[numRooms];
for (int r = 0; r < numRooms; ++r)
{
incompatibleRooms[s][r] = int.Parse(splitted[r]);
}
}
}
}
public void Dispose()
{
optimizer.Dispose();
}
public void Solve(int timeLimit)
{
// Declare the optimization model
HxModel model = optimizer.GetModel();
// Sequence of surgery for each room
surgeryOrder = new HxExpression[numRooms];
HxExpression room = model.Array();
for (int r = 0; r < numRooms; ++r)
{
surgeryOrder[r] = model.List(numSurgeries);
room.AddOperand(surgeryOrder[r]);
}
// Each surgery is scheduled in a room
model.Constraint(model.Partition(room));
// Only compatible rooms can be selected for a surgery
for (int s = 0; s < numSurgeries; ++s)
{
foreach (int r in incompatibleRooms[s])
{
model.Constraint(model.Contains(surgeryOrder[r], s) == 0);
}
}
// For each surgery, the selected room
selectedRoom = new HxExpression[numSurgeries];
for (int s = 0; s < numSurgeries; ++s)
{
selectedRoom[s] = model.Find(room, s);
}
// Interval decisions: time range of each surgery
surgeries = new HxExpression[numSurgeries];
for (int s = 0; s < numSurgeries; ++s)
{
// Each surgery cannot start before and end after a certain time
surgeries[s] = model.Interval(minStart[s], maxEnd[s]);
// Each surgery has a specific duration
model.Constraint(model.Length(surgeries[s]) == duration[s]);
}
HxExpression surgeryArray = model.Array(surgeries);
// A room can only have one surgery at a time
for (int r = 0; r < numRooms; ++r)
{
HxExpression sequence = surgeryOrder[r];
HxExpression sequenceLambda = model.LambdaFunction(
s => surgeryArray[sequence[s]] < surgeryArray[sequence[s + 1]]
);
model.Constraint(model.And(model.Range(0, model.Count(sequence) - 1), sequenceLambda));
}
// Sequence of surgery for each nurse
nurseOrder = new HxExpression[numNurses];
for (int n = 0; n < numNurses; ++n)
{
nurseOrder[n] = model.List(numSurgeries);
HxExpression sequence = nurseOrder[n];
HxExpression firstSurgeryStart = model.If(
model.Count(sequence) > 0,
model.Start(surgeryArray[sequence[0]]),
shiftEarliestStart[n]);
HxExpression lastSurgeryEnd = model.If(
model.Count(sequence) > 0,
model.End(surgeryArray[sequence[model.Count(sequence)-1]]),
shiftEarliestStart[n]);
// Each nurse has an earliest starting shift and latest ending shift to be respected
model.Constraint(firstSurgeryStart >= shiftEarliestStart[n]);
model.Constraint(lastSurgeryEnd <= shiftLatestEnd[n]);
// Each nurse cannot work more than a certain amount of hours in a row
model.Constraint(lastSurgeryEnd - firstSurgeryStart <= maxShiftDuration);
// Each nurse can only be at one operation at a time and stays all along the surgery
HxExpression sequenceLambda = model.LambdaFunction(
s => surgeryArray[sequence[s]] < surgeryArray[sequence[s + 1]]
);
model.Constraint(model.And(model.Range(0, model.Count(sequence) - 1), sequenceLambda));
}
HxExpression nurseOrderArray = model.Array(nurseOrder);
// Each surgery needs a specific amount of nurse
for (int s = 0; s < numSurgeries; ++s)
{
model.Constraint(model.Sum(model.Range(0, numNurses), model.LambdaFunction(
n => model.Contains(nurseOrderArray[n], s))) >= neededNurses[s]);
}
// Minimize the makespan: end of the last surgery
makespan = model.Max();
for (int s = 0; s < numSurgeries; ++s)
{
makespan.AddOperand(model.End(surgeries[s]));
}
model.Minimize(makespan);
model.Close();
// Parameterize the optimizer
optimizer.GetParam().SetTimeLimit(timeLimit);
optimizer.Solve();
}
/* Write the solution in a file with the following format:
* - for each surgery, the selected room, the start and end dates
the nurses working on this operation*/
public void WriteSolution(string fileName)
{
using (StreamWriter output = new StreamWriter(fileName))
{
Console.WriteLine("Solution written in file " + fileName);
List<List<int>> listNurses = new List<List<int>>();
for (int s = 0; s < numSurgeries; ++s)
{
listNurses.Add(new List<int>());
}
for (int n = 0; n < numNurses; ++n)
{
HxCollection surgNurse = nurseOrder[n].GetCollectionValue();
foreach (int s in surgNurse)
{
listNurses[s].Add(n);
}
}
for (int s = 0; s < numSurgeries; ++s)
{
string nurseListString = "[" + string.Join(", ", listNurses[s]) + "]";
output.WriteLine(
s
+ "\t"
+ selectedRoom[s].GetValue()
+ "\t"
+ surgeries[s].GetIntervalValue().Start()
+ "\t"
+ surgeries[s].GetIntervalValue().End()
+ "\t"
+ nurseListString
);
}
}
}
public static void Main(string[] args)
{
if (args.Length < 1)
{
Console.WriteLine("Usage: SurgeriesScheduling instanceFile [outputFile] [timeLimit]");
System.Environment.Exit(1);
}
string instanceFile = args[0];
string outputFile = args.Length > 1 ? args[1] : null;
string strTimeLimit = args.Length > 2 ? args[2] : "20";
using (SurgeriesScheduling model = new SurgeriesScheduling())
{
model.ReadInstance(instanceFile);
model.Solve(int.Parse(strTimeLimit));
if (outputFile != null)
model.WriteSolution(outputFile);
}
}
}
- Compilation / Execution (Windows)
- javac SurgeriesScheduling.java -cp %HX_HOME%\bin\hexaly.jarjava -cp %HX_HOME%\bin\hexaly.jar;. SurgeriesScheduling instances\instancesurgery.txt
- Compilation / Execution (Linux)
- javac SurgeriesScheduling.java -cp /opt/hexaly_13_5/bin/hexaly.jarjava -cp /opt/hexaly_13_5/bin/hexaly.jar:. SurgeriesScheduling instances/instancesurgery.txt
import java.util.*;
import java.io.*;
import com.hexaly.optimizer.*;
public class SurgeriesScheduling {
// Number of surgeries
private int numSurgeries;
// Number of rooms
private int numRooms;
// Number of nurses
private int numNurses;
// Minimum start of each surgery
private int[] minStart;
// Maximum end of each surgery
private int[] maxEnd;
// Duration of each surgery
private int[] duration;
// Number of nurses needed for each surgery
private int [] neededNurses;
// Earliest starting shift for each nurse
private int [] shiftEarliestStart;
// Latest ending shift for each nurse
private int [] shiftLatestEnd;
// Maximum duration of each nurse's shift
private int maxShiftDuration;
// Incompatible rooms for each surgery
private int[][] incompatibleRooms;
// Hexaly Optimizer
final HexalyOptimizer optimizer;
// Decision variables: time range of each surgery
private HxExpression[] surgeries;
// Decision variables: sequence of surgery on each room
private HxExpression[] surgeryOrder;
// For each surgery, the selected room
// This variable is only used to export the solution
private HxExpression[] selectedRoom;
// Decision variables: the surgeries order of each nurse
private HxExpression[] nurseOrder;
// Objective = minimize the makespan: end of the last surgery
private HxExpression makespan;
public SurgeriesScheduling(HexalyOptimizer optimizer) throws IOException {
this.optimizer = optimizer;
}
public void readInstance(String fileName) throws IOException {
try (Scanner input = new Scanner(new File(fileName))) {
input.useLocale(Locale.ROOT);
numRooms = input.nextInt();
numNurses = input.nextInt();
numSurgeries = input.nextInt();
minStart = new int[numSurgeries];
for (int s = 0; s < numSurgeries; ++s) {
minStart[s] = input.nextInt() * 60;
}
maxEnd = new int[numSurgeries];
for (int s = 0; s < numSurgeries; ++s) {
maxEnd[s] = input.nextInt() * 60;
}
duration = new int[numSurgeries];
for (int s = 0; s < numSurgeries; ++s) {
duration[s] = input.nextInt();
}
neededNurses = new int[numSurgeries];
for (int s = 0; s < numSurgeries; ++s) {
neededNurses[s] = input.nextInt();
}
shiftEarliestStart = new int[numNurses];
for (int n = 0; n < numNurses; ++n) {
shiftEarliestStart[n] = input.nextInt() * 60;
}
shiftLatestEnd = new int[numNurses];
for (int n = 0; n < numNurses; ++n) {
shiftLatestEnd[n] = input.nextInt() * 60;
}
maxShiftDuration = input.nextInt() * 60;
incompatibleRooms = new int[numSurgeries][];
for (int s = 0; s < numSurgeries; ++s) {
incompatibleRooms[s] = new int[numRooms];
for (int r = 0; r < numRooms; ++r) {
incompatibleRooms[s][r] = input.nextInt();
}
}
}
}
public void solve(int timeLimit) {
// Declare the optimization model
HxModel model = optimizer.getModel();
// Sequence of surgery for each room
surgeryOrder = new HxExpression[numRooms];
HxExpression room = model.array();
for (int r = 0; r < numRooms; ++r) {
surgeryOrder[r] = model.listVar(numSurgeries);
room.addOperand(surgeryOrder[r]);
}
// Each surgery is scheduled in a room
model.constraint(model.partition(room));
// Only compatible rooms can be selected for a surgery
for (int s = 0; s < numSurgeries; ++s) {
for (int r : incompatibleRooms[s]) {
model.constraint(model.eq(model.contains(surgeryOrder[r], s), 0));
}
}
// For each surgery, the selected room
selectedRoom = new HxExpression[numSurgeries];
for (int s = 0; s < numSurgeries; ++s) {
selectedRoom[s] = model.find(room, s);
}
// Interval decisions: time range of each surgery
surgeries = new HxExpression[numSurgeries];
for (int s = 0; s < numSurgeries; ++s) {
// Each surgery cannot start before and end after a certain time
surgeries[s] = model.intervalVar(minStart[s], maxEnd[s]);
// Each surgery has a specific duration
model.constraint(model.eq(model.length(surgeries[s]), duration[s]));
}
HxExpression surgeryArray = model.array(surgeries);
// A room can only have one surgery at a time
for (int r = 0; r < numRooms; ++r) {
HxExpression sequence = surgeryOrder[r];
HxExpression sequenceLambda = model.lambdaFunction(s -> model
.lt(model.at(surgeryArray, model.at(sequence, s)),
model.at(surgeryArray, model.at(sequence, model.sum(s, 1)))));
model.constraint(model.and(
model.range(0, model.sub(model.count(sequence), 1)),
sequenceLambda));
}
// Sequence of surgery for each nurse
nurseOrder = new HxExpression[numNurses];
for (int n = 0; n < numNurses; ++n) {
nurseOrder[n] = model.listVar(numSurgeries);
HxExpression sequence = nurseOrder[n];
HxExpression firstSurgeryStart = model.iif(
model.gt(model.count(sequence),0),
model.start(model.at(surgeryArray,model.at(sequence,0))),
shiftEarliestStart[n]
);
HxExpression lastSurgeryEnd = model.iif(
model.gt(model.count(sequence),0),
model.end(model.at(surgeryArray, model.at(sequence,model.sub(model.count(sequence),1)))),
shiftEarliestStart[n]
);
// Each nurse has an earliest starting shift and latest ending shift to be respected
model.constraint(model.geq(firstSurgeryStart, shiftEarliestStart[n]));
model.constraint(model.leq(lastSurgeryEnd, shiftLatestEnd[n]));
// Each nurse cannot work more than a certain amount of hours in a row
model.constraint(model.leq(
model.sub(lastSurgeryEnd, firstSurgeryStart),
maxShiftDuration));
// Each nurse can only be at one operation at a time and stays all along the surgery
HxExpression sequenceLambda = model.lambdaFunction(s -> model
.lt(model.at(surgeryArray, model.at(sequence, s)),
model.at(surgeryArray, model.at(sequence, model.sum(s, 1)))));
model.constraint(model.and(
model.range(0, model.sub(model.count(sequence), 1)), sequenceLambda));
}
HxExpression nurseOrderArray = model.array(nurseOrder);
// Each surgery needs a specific amount of nurse
for (int s = 0; s < numSurgeries; ++s) {
int surgery = s;
model.constraint(model.geq(model.sum(
model.range(0, numNurses),
model.lambdaFunction(
n -> model.contains(model.at(nurseOrderArray, n), surgery))), neededNurses[s]));
}
// Minimize the makespan: end of the last task
makespan = model.max();
for (int s = 0; s < numSurgeries; ++s) {
makespan.addOperand(model.end(surgeries[s]));
}
model.minimize(makespan);
model.close();
// Parameterize the optimizer
optimizer.getParam().setTimeLimit(timeLimit);
optimizer.solve();
}
/*
* Write the solution in a file with the following format:
* - for each operation of each job, the selected machine, the start and end
* dates, the nurses working on this operation
*/
public void writeSolution(String fileName) throws IOException {
try (PrintWriter output = new PrintWriter(fileName)) {
System.out.println("Solution written in file " + fileName);
List<List<Integer>> listNurses = new ArrayList<List<Integer>>();
for (int s = 0; s < numSurgeries; ++s) {
listNurses.add(new ArrayList<Integer>());
}
for (int n = 0; n < numNurses; ++n) {
HxCollection surgNurse = nurseOrder[n].getCollectionValue();
for (long s : surgNurse) {
listNurses.get((int) s).add(n);
}
}
for (int s = 0; s < numSurgeries; ++s) {
output.write(s + "\t"
+ "\t" + selectedRoom[s].getValue()
+ "\t" + surgeries[s].getIntervalValue().getStart()
+ "\t" + surgeries[s].getIntervalValue().getEnd()
+ "\t" + listNurses.get(s) + "\n");
}
}
}
public static void main(String[] args) {
if (args.length < 1) {
System.out.println("Usage: java SurgeriesScheduling instanceFile [outputFile] [timeLimit]");
System.exit(1);
}
String instanceFile = args[0];
String outputFile = args.length > 1 ? args[1] : null;
String strTimeLimit = args.length > 2 ? args[2] : "20";
try (HexalyOptimizer optimizer = new HexalyOptimizer()) {
SurgeriesScheduling model = new SurgeriesScheduling(optimizer);
model.readInstance(instanceFile);
model.solve(Integer.parseInt(strTimeLimit));
if (outputFile != null) {
model.writeSolution(outputFile);
}
} catch (Exception ex) {
System.err.println(ex);
ex.printStackTrace();
System.exit(1);
}
}
}