Surgery Scheduling Problem
Problem
In the Surgery Scheduling Problem, we consider a hospital with a fixed number of available rooms and a set of surgeries to schedule. Each surgery has a given processing time and a given number of nurses needed. A surgery can only start when a room and a sufficient set of nurses are available. In addition, there are constraints on the earliest start, latest end, and maximum duration of the nurses’ shifts. The goal of the Surgery Scheduling Problem is to find a sequence of surgeries that minimizes the makespan: the time when all surgeries have been processed.
Principles learned
- Add interval decision variables to model the surgeries
- Add list decision variables to model the assignment of surgeries to rooms and nurses, as well as their order
- Define lambda functions to link the interval and list variables together
Data
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)
- Eighth line: the maximum duration of a shift (in hours)
- The following lines:
- For each surgery and for each room: 1 if the room is incompatible with the surgery, 0 otherwise.
Program
The Hexaly model for the Surgery Scheduling Problem uses two kinds of decision variables: intervals and lists. Interval variables model the time ranges of the surgeries, while list variables represent the order of the surgeries scheduled in each room and for each nurse. We constrain the length of each interval to be equal to the duration of the corresponding surgery.
Using the ‘partition‘ operator on the lists representing the rooms, we ensure that each surgery is assigned to exactly one room.
To ensure that no more than one surgery is performed in a room at the same time, we impose a no-overlap constraint: no surgery can start in a room until the previous surgery in that room has finished. To model this constraint, we define a lambda function expressing the relationship between two consecutive surgeries. This function is then used within a variadic ‘and’ operator over all surgeries scheduled in each room. Note that the number of terms inside these ‘and’ expressions varies during the search, along with the size of the lists (the number of surgeries assigned to each room).
By accessing the first and last element in the list variable associated with each nurse, we compute the start time of their first surgery and the end time of their last surgery. We can then enforce the constraints on their shifts’ earliest start times, latest end times, and maximum durations. Since each nurse can only work on one surgery at a time, we also add no-overlap constraints for the nurses.
Each surgery has a minimum nurse requirement. Using the ‘contains’ operator, we can check whether each nurse is working on each surgery. Summing the results of this operator then returns the number of nurses for each surgery.
The objective is to minimize the makespan: the maximum end time among all surgeries.
- 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_0/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\hexaly130.libsurgeries_scheduling instances\instancesurgery.txt
- Compilation / Execution (Linux)
-
g++ surgeries_scheduling.cpp -I/opt/hexaly_13_0/include -lhexaly130 -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_0/bin/hexaly.jarjava -cp /opt/hexaly_13_0/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);
}
}
}