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

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\python
python surgeries_scheduling.py instances\instancesurgery.txt
Execution (Linux)
export PYTHONPATH=/opt/hexaly_13_0/bin/python
python 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.lib
surgeries_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.dll
SurgeriesScheduling 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.jar
java -cp %HX_HOME%\bin\hexaly.jar;. SurgeriesScheduling instances\instancesurgery.txt
Compilation / Execution (Linux)
javac SurgeriesScheduling.java -cp /opt/hexaly_13_0/bin/hexaly.jar
java -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);
        }
    }
}