Aircraft Deicing Problem

Scheduling

Problem

The Aircraft Deicing Problem is a common case in operations research. In this context, aircraft arriving at an airport must undergo a deicing operation. One has to schedule these deicing tasks with limited resources: the locations to carry out this operation (gates) and the deicing trucks. The deicing time for an aircraft decreases with the number of trucks assigned to it. The objective is to determine, for each aircraft, an assignment to a deicing gate, a processing interval, and a number of trucks, in such a way as to minimize the completion time of the final aircraft (makespan).

Principles learned

Data

We provide randomly generated instances with the following format:

  • The number of gates.
  • The number of aircraft.
  • The size of each aircraft.
  • The arrival date of each aircraft.
  • The lower bound of the number of trucks needed by each aircraft.
  • The upper bound of the number of trucks needed by each aircraft.
  • The base duration for deicing of each aircraft (then normalized by the number of assigned trucks).
  • The setup time matrix, which indicates the minimal separation time at a given gate between two consecutive aircrafts i and j.

Program

The Hexaly model for the Aircraft Deicing Problem relies on three types of decision variables:

  • An array of list decisions representing the gates: list i corresponds to the sequence of aircraft assigned to gate i;
  • An array of interval decisions representing the processing intervals: the interval associated with aircraft j corresponds to its deicing time window;
  • An array of integer decisions representing the number of trucks assigned to each aircraft.

We define the constraints of the problem as follows. Each aircraft must be assigned to exactly one gate, ensuring a unique allocation. The processing interval associated with each aircraft must match its required deicing time, which depends on the number of trucks assigned to it. Furthermore, no two aircraft can be processed simultaneously at the same gate: the deicing of one aircraft may only begin after the previous one has finished, with an additional setup time that depends on the pair of aircraft involved. Finally, the total number of trucks in use at any given moment cannot exceed the number of trucks available at the airport.

The objective function is to minimize the makespan, i.e., the time at which all aircraft have been deiced.

Execution
hexaly aircraft-deicing.hxm inFileName=instances/60_aircrafts.txt [hxTimeLimit=] [solFileName=]
/********** aircraft_deicing.hxm **********/

use io;
use hexaly;

function input() {
    local usage = "\nUsage: hexaly aircraft_deicing.hxm inFileName=inputFile"
            + "[solFileName=outputFile] [hxTimeLimit=timeLimit]\n";
    if (inFileName == nil) throw usage;

    local inFile = io.openRead(inFileName);

    // Number of gates
    nbGates = inFile.readInt();

    // Aircrafts data
    nbAircrafts  = inFile.readInt();
    sizeAircrafts[_ in 0...nbAircrafts] = inFile.readInt();
    arrivalDates[_ in 0...nbAircrafts] = inFile.readInt();

    // Trucks data
    nbTrucks = inFile.readInt();
    minTrucks[_ in 0...nbAircrafts] = inFile.readInt(); 
    maxTrucks[_ in 0...nbAircrafts] = inFile.readInt();
    local maxTotalTrucks = max[t in maxTrucks](t);

    // Durations data
    durations[m in 0...maxTotalTrucks][i in 0...nbAircrafts] = inFile.readInt();

    // Setup Matrix: times needed between two aircrafts
    for [line in 0...nbAircrafts][column in 0...nbAircrafts] {
        setupMatrix[line][column] = inFile.readInt();
    }
    inFile.close();

    // Horizon: maximum durations
    H = max[aircraft in 0...nbAircrafts](arrivalDates[aircraft]) 
            + sum[i in 0...nbAircrafts](durations[0][i]);
}
 

function model() {
    // Decision variables: to each aircraft, we associate an interval, a gate and a number of trucks
    aircraftsIntervals[i in 0...nbAircrafts] <- interval(arrivalDates[i], H);
    aircraftsNbTrucks[i in 0...nbAircrafts] 
            <- int(minTrucks[sizeAircrafts[i]], maxTrucks[sizeAircrafts[i]]);
    gates[_ in 0...nbGates] <- list(nbAircrafts);

    // Constraint: every aircraft has a unique gate
    constraint partition(gates);

    // Constraint non-overlap: one aircraft per gate for each time, 
    // and a setup time between two aircrafts on the same gate
    for [gate in gates] {
        constraint and(0...count(gate)-1, i => end(aircraftsIntervals[gate[i]]) 
                + setupMatrix[gate[i]][gate[i+1]] <= start(aircraftsIntervals[gate[i+1]])); 
    }

    // Constraint: durations of the de-icing
    for [aircraft in 0...nbAircrafts] {
        constraint length(aircraftsIntervals[aircraft]) 
                == durations[aircraftsNbTrucks[aircraft]-1][aircraft];
    }

    // Constraint: no more than nbTrucks in total
    makespan <- max[aircraftInter in aircraftsIntervals](end(aircraftInter)); 
    constraint and(0...makespan, time => sum[aircraft in 0...nbAircrafts](
            contains(aircraftsIntervals[aircraft], time) 
            * aircraftsNbTrucks[aircraft]) <= nbTrucks);

    // Minimize makespan
    minimize makespan;
}

function param() {
    if (hxTimeLimit is nil) hxTimeLimit = 60;
}

function output() {
    if (solFileName == nil) return;
    local outfile = io.openWrite(solFileName);

    /* Write the solution in a file with the following format:
     *  - the total makespan
     *  - for each aircraft, the bay, the starting date of the interval, 
            the ending date of the interval, the number of trucks attributed */
    outfile.println(makespan.value);
    for [b in 0...nbGates] {
        gate = gates[b].value;
        for [aircraft in gate] {
            outfile.print(
                    b, " ", 
                    aircraftsIntervals[aircraft].value.start, " ", 
                    aircraftsIntervals[aircraft].value.end, " ", 
                    aircraftsNbTrucks[aircraft].value
            );
            outfile.println();
        }
    }
    println("Solution written in " + solFileName);
}
Execution (Windows)
set PYTHONPATH=%HX_HOME%\bin\python
python  aircraft_deicing.py instances\60_aircrafts.txt
Execution (Linux)
export PYTHONPATH=/opt/hexaly_14_0/bin/python
python aircraft_deicing.py instances/60_aircrafts.txt
import hexaly.optimizer
import sys
import math

def main(instance_file, output_file, time_limit):
    with hexaly.optimizer.HexalyOptimizer() as optimizer:
        #
        # Declare the optimization model
        #
        model = optimizer.model

        with open(instance_file) as f:
            lines = f.readlines()

        # Number of gates
        nb_gates = int(lines[0])

        # Aircrafts data
        nb_aircrafts = int(lines[1])
        size_aircraft = [int(i) for i in lines[2].split()]
        arrival_date = [int(i) for i in lines[3].split()]

        # Trucks data
        nb_trucks = int(lines[4])
        min_trucks = [int(i) for i in lines[5].split()]
        max_trucks = [int(i) for i in lines[6].split()]
        max_total_trucks = max(max_trucks)

        # Duration data
        # base_line_duration = [int(i) for i in lines[7].split()]
        # ratio = [math.sqrt(k) for k in range(1, max_total_trucks + 1)]
        # duration = model.array([[math.ceil(base_line_duration[i]/ratio[m]) \
        #                     for m in range(max_total_trucks)] for i in range(nb_aircrafts)])
        
        duration = []
        for line in lines[7:12]:
            line_split = [int(i) for i in line.split()]
            duration.append(line_split)

        # Horizon: maximum duration
        H = max(arrival_date) + sum(duration[0])

        duration = model.array(duration)

        # Setup Matrix: times needed between two aircrafts in a gate
        setup_matrix = []
        for line in lines[12:]:
            line_split = [int(i) for i in line.split()]
            setup_matrix.append(line_split)
        setup_matrix = model.array(setup_matrix)

        # Decision variables: to each aircraft, we associate an interval of de-icing 
        # and a number of trucks.
        # A aircraft cannot be de-iced before its arrival
        aircraft_intervals = model.array( \
                [model.interval(arrival_date[aircraft], H) for aircraft in range(nb_aircrafts)])
        aircraft_nb_trucks = model.array( \
                [model.int(min_trucks[size_aircraft[i]], max_trucks[size_aircraft[i]]) \
                for i in range(nb_aircrafts)])

        # Decision variables: to each gate, we associate the list of 
        # the aircrafts that will be de-iced there.
        gates = model.array([model.list(nb_aircrafts) for _ in range(nb_gates)])

        # Constraint: every aircraft has a unique gate
        model.constraint(model.partition(gates))

        # Constraint non-overlap: one aircraft per gate for each time, and a setup time
        # between two aircrafts on the same gate
        for i in range(nb_gates):
            gate = gates[i]
            constraint_overlapping = model.lambda_function(
                lambda i : model.end(aircraft_intervals[gate[i]]) + setup_matrix[gate[i]][gate[i+1]] \
                        <= model.start(aircraft_intervals[gate[i+1]]))
            model.constraint(model.and_( \
                    model.range(0, model.count(gate)-1), constraint_overlapping))

        # Constraint: duration of the de-icing
        for aircraft in range(nb_aircrafts):
            constraint_duration = model.length(aircraft_intervals[aircraft]) \
                                    == duration[aircraft_nb_trucks[aircraft]-1][aircraft]
            model.constraint(constraint_duration)

        # Makespan: time of the end of all the de-icing tasks
        makespan = model.max([model.end(aircraft_intervals[aircraft]) \
                                    for aircraft in range(nb_aircrafts)])

        # Constraint: no more than nb_trucks in total
        constraint_capacity = model.lambda_function(
            lambda time : model.sum(model.contains(aircraft_intervals[aircraft], time) \
                    * aircraft_nb_trucks[aircraft] for aircraft in range(0, nb_aircrafts)) \
                    <= nb_trucks
        )
        model.constraint(model.and_(model.range(0, makespan), constraint_capacity))
        
        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:
        #  -the total makespan
        #  - for each aircraft, the bay, the starting date of the interval, 
        #       the ending date of the interval, the number of trucks attributed
        if output_file != None:
            with open(output_file, "w") as f:
                print("Solution written in file ", output_file)
                f.write(str(makespan.value))
                f.write("\n")
                for b in range(nb_gates):
                        gate = gates.value[b]
                        for aircraft in gate:
                            f.write(str(b) + " " \
                                    + str(aircraft_intervals.value[aircraft].start()) + " " \
                                    + str(aircraft_intervals.value[aircraft].end()) + " " \
                                    + str(aircraft_nb_trucks.value[aircraft]))
                            f.write("\n")


if __name__ == '__main__':
    if len(sys.argv) < 2:
        print("Usage: python aircraft_deicing.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 60
    main(instance_file, output_file, time_limit)
Compilation / Execution (Windows)
cl /EHsc aircraft_deicing.cpp -I%HX_HOME%\include /link %HX_HOME%\bin\hexaly140.lib
aircraft_deicing instances/60_aircrafts.txt
Compilation / Execution (Linux)
g++ deicing_planes.cpp -I/opt/hexaly_14_0/include -lhexaly140 -lpthread -o deicing_planes
./deicing_planes instances/60_aircrafts.txt
#include "optimizer/hexalyoptimizer.h"
#include <fstream>
#include <iostream>
#include <vector>
#include <numeric>
#include <algorithm>
#include <cmath>

using namespace hexaly;
using namespace std;

class AircraftDeicing {
private:
    // Number of gates
    int nbGates;

    // Number of aircrafts
    int nbAircrafts;

    // Size of the aircrafts
    vector<int> aircraftsSize;

    // Arrival date of the aircrafts
    vector<int> arrivalDates;

    // Number of trucks
    int nbTrucks;

    // Number of trucks possible per aircraft
    vector<int> minTrucks;
    vector<int> maxTrucks;
    int maxGlobalTrucks;

    // Durations normalised by the truck number of the aircraft
    vector<vector<int>> durationsData;

    // Setup matrix: the time needed between two aircrafts to clean the gate
    vector<vector<int>> setupMatrixData;

    // Horizon: trivial upper bound for the end times of the tasks
    int H;

    // Hexaly Optimizer
    HexalyOptimizer optimizer;

    // Decision variables: to each aircraft, we associate a de-icing interval and a number of trucks
    vector<HxExpression> aircraftsIntervals;
    vector<HxExpression> aircraftsNbTrucks;

    // To each gate, we associate the list of the aircrafts that will be de-iced there
    vector<HxExpression> gates;

    // Objective = minimize the makespan: end of the de-icing of the last aircraft
    HxExpression makespan; 

public:
        void readInstance(const string& inFileName);
        void solve(int timeLimit);
        void writeSolution(const string& fileName);
};


void AircraftDeicing::readInstance (const string& inFileName){
    ifstream infile;
    infile.exceptions(ifstream::failbit | ifstream::badbit);
    infile.open(inFileName.c_str());
    
    infile >> nbGates;
    infile >> nbAircrafts;

    int value;
    for (int i = 0; i < nbAircrafts; ++i) {
        infile >> value;
        aircraftsSize.push_back(value);
    }
    for (int i = 0; i < nbAircrafts; ++i) {
        infile >> value;
        arrivalDates.push_back(value);
    }

    infile >> nbTrucks;

    for (int i = 0; i < nbAircrafts; ++i) {
        infile >> value;
        minTrucks.push_back(value);
    }

    for (int i = 0; i < nbAircrafts; ++i) {
        infile >> value;
        maxTrucks.push_back(value);
    }

    maxGlobalTrucks = *max_element(maxTrucks.begin(), maxTrucks.end());

    for (unsigned int m=0; m<maxGlobalTrucks; m++) {
        durationsData.push_back(vector<int> {});
        for (unsigned int i = 0; i < nbAircrafts; i++) {
            infile >> value;
            durationsData[m].push_back(value);
        }
    }

    H = *max_element(arrivalDates.begin(), arrivalDates.end()) 
            + accumulate(durationsData[0].begin(), durationsData[0].end(), 0);

    for (int i = 0; i < nbAircrafts; ++i) {
        setupMatrixData.push_back(vector<int> {});
        for (int j = 0; j < nbAircrafts; ++j) {
            infile >> value;
            setupMatrixData[i].push_back(value);
        }
    }
    
    infile.close();
}


void AircraftDeicing::solve(int timeLimit) {
    // Declare the optimization model
    HxModel model = optimizer.getModel();

    // Decision Variable: time range for each aircraft
    for (int aircraft = 0; aircraft < nbAircrafts; ++aircraft) {
        aircraftsIntervals.push_back(model.intervalVar(arrivalDates[aircraft], H));
    }
    HxExpression aircraftsIntervalsArray 
            = model.array(aircraftsIntervals.begin(), aircraftsIntervals.end());

    // Decision Variable: number of trucks used for each aircraft
    for (int aircraft = 0; aircraft < nbAircrafts; aircraft++) {
        aircraftsNbTrucks.push_back(
                model.intVar(minTrucks[aircraftsSize[aircraft]], 
                             maxTrucks[aircraftsSize[aircraft]]));
    }
    HxExpression aircraftsNbTrucksArray 
            = model.array(aircraftsNbTrucks.begin(), aircraftsNbTrucks.end());

    // Decision Variable: attribution of a gate to each aircraft
    for (int i = 0; i < nbGates; ++i) gates.push_back(model.listVar(nbAircrafts));
    HxExpression gatesArray = model.array(gates.begin(), gates.end());

    // Makespan: end of the de-icing of the last aircraft
    makespan = model.max();
    for (int i = 0; i < nbAircrafts; ++i) makespan.addOperand(model.end(aircraftsIntervals[i]));

    // Conversion of durations from vector<vector<int>> to HxExpression (array) 
    // to be able to access it with "at" operators
    HxExpression durations = model.array();
    for (int m = 0; m < maxGlobalTrucks; ++m) {
        durations.addOperand(
            model.array(durationsData[m].begin(), durationsData[m].end())
        );
    }

    // Conversion of setupMatrix from vector<vector<int>> to HxExpression (array)
    // to be able to access it with "at" operators
    HxExpression setupMatrix = model.array();
    for (int i = 0; i < nbAircrafts; ++i) {
        setupMatrix.addOperand(
            model.array(setupMatrixData[i].begin(), setupMatrixData[i].end())
        );
    }

    // Constraint: every aircraft has a unique gate
    model.constraint(model.partition(gates.begin(), gates.end()));

    // Constraint: duration of the de-icing
    for (int aircraft = 0; aircraft < nbAircrafts; aircraft++) {
        HxExpression constraintDuration = 
                model.length(aircraftsIntervals[aircraft]) 
                        == durations[aircraftsNbTrucks[aircraft]-1][aircraft];
        model.constraint(constraintDuration);
    }

    // Constraint no-overlapping: one aircraft per gate for each time, and a setup time
    // between two aircrafts on the same gate
    for (int j = 0; j < nbGates; j++) {
        HxExpression gate = gates[j];
        HxExpression constraintOverlapping = model.lambdaFunction([&](HxExpression i) {
            return (model.end(aircraftsIntervalsArray[gate[i]])
                    + setupMatrix[gate[i]][gate[i+1]]
                    <= model.start(aircraftsIntervalsArray[gate[i+1]]));
        }
        );
        model.constraint(model.and_(model.range(0, model.count(gate)-1), constraintOverlapping));
    }

    // Constraint: trucks capacity
    
    HxExpression respectCapacity = model.lambdaFunction([&](HxExpression time) {
        HxExpression currNbTrucks = model.sum();;
        for (int i = 0; i < nbAircrafts; i++){
            currNbTrucks.addOperand(model.contains(aircraftsIntervalsArray[i], time) * aircraftsNbTrucksArray[i]);
        }
        return currNbTrucks <= nbTrucks;
    });
    model.constraint(model.and_(model.range(0, makespan), respectCapacity));

    // Minimize the Makespan
    model.minimize(makespan);

    
    model.close();
    
    // Parameterize the optimizer
    optimizer.getParam().setTimeLimit(timeLimit);

    optimizer.solve();
}


/* Write the solution in a file with the following format:
     *  - the total makespan
     *  - for each aircraft, the gate, the starting date of the interval,
     *      the ending date of the interval,the number of trucks attributed */
void AircraftDeicing::writeSolution(const string& fileName) {
    ofstream outfile;
    outfile.exceptions(ofstream::failbit | ofstream::badbit);
    outfile.open(fileName.c_str());

    outfile << makespan.getValue() << endl;

    for (int gate = 0; gate < nbGates; ++gate) {
        HxCollection aircraftsCollection = gates[gate].getCollectionValue();
        for (int i = 0; i < aircraftsCollection.count(); ++i) {
            int aircraft = aircraftsCollection[i];
            outfile << gate << "\t" 
                    << aircraftsIntervals[aircraft].getIntervalValue().getStart() << "\t" 
                    << aircraftsIntervals[aircraft].getIntervalValue().getEnd() <<"\t" 
                    << aircraftsNbTrucks[aircraft].getIntValue() << endl;
        }
    }
}



int main(int argc, char** argv) {
    if (argc < 2) {
        std::cout << "Usage: aircraft_deicing instanceFile [outputFile] [timeLimit]" << std::endl;
        exit(1);
    }
    
    const char* instanceFile = argv[1];
    string inFileName = string(instanceFile);
    const char* outputFile = argc > 2 ? argv[2] : NULL;
    const char* strTimeLimit = argc > 3 ? argv[3] : "60";

    try {
        AircraftDeicing model;
        model.readInstance(inFileName);
        int TimeLimit = atoi(strTimeLimit);
        model.solve(TimeLimit);
        if (outputFile != NULL) {
            cout << "output file : " << outputFile << endl;
            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 aircraft_deicing.cs /reference:Hexaly.NET.dll
aircraft_deicing instances/60_aircrafts.txt
using System;
using System.IO;
using System.Linq;
using Hexaly.Optimizer;
using System.Collections.Generic;

public class AircraftDeicing : IDisposable
{
    // Number of gates
    private int nbGates;

    // Number of aircrafts
    private int nbAircrafts;

    // Size of the aircrafts
    private int[] aircraftsSize;

    // Arrival date of the aircrafts
    private int[] arrivalDates;

    // Number of trucks
    private int nbTrucks;

    // Bounds of the number trucks authorized per aircraft
    private int[] minTrucks;
    private int[] maxTrucks;

    // Durations normalised by the truck number of the aircraft
    private int[,] durations;

    // Setup Matrix: the time needed between two aircrafts to clean the bay
    private int[,] setupMatrix;

    // Horizon: trivial upper bound for the end times of the tasks
    private int H;

    // Hexaly Optimizer
    private HexalyOptimizer optimizer;

    // Decision variables: to each aircraft, we associate an interval of de-icing
    private HxExpression[] aircraftsIntervals;

    // Decision variable: to each aircraft, we associate a number of trucks
    private HxExpression[] aircraftsNbTrucks;

    // Decision variables: to each gate, we associate the 
    // list of the aircrafts that will be de-iced there
    private HxExpression[] gates;

    // Objective = minimize the makespan: end of the de-icing of the last aircraft
    private HxExpression makespan;

    public AircraftDeicing()
    {
        optimizer = new HexalyOptimizer();
    }

    public void ReadInstance(string fileName)
    {
        using (StreamReader input = new StreamReader(fileName))
        {
            nbGates = int.Parse(input.ReadLine());

            nbAircrafts = int.Parse(input.ReadLine());

            string[] line_sp = input.ReadLine().Split(' ', StringSplitOptions.RemoveEmptyEntries);
            aircraftsSize = Array.ConvertAll(line_sp, int.Parse);

            line_sp = input.ReadLine().Split(' ', StringSplitOptions.RemoveEmptyEntries);
            arrivalDates = Array.ConvertAll(line_sp, int.Parse);

            nbTrucks = int.Parse(input.ReadLine());

            line_sp = input.ReadLine().Split(' ', StringSplitOptions.RemoveEmptyEntries);
            minTrucks = Array.ConvertAll(line_sp, int.Parse);

            line_sp = input.ReadLine().Split(' ', StringSplitOptions.RemoveEmptyEntries);
            maxTrucks = Array.ConvertAll(line_sp, int.Parse);

            int maxGlobalTrucks = maxTrucks.Max();

            durations = new int[maxGlobalTrucks, nbAircrafts];
            for (int i=0; i<maxGlobalTrucks; ++i)
            {
                line_sp = input.ReadLine().Split(' ', StringSplitOptions.RemoveEmptyEntries);
                int[] lineDuration = Array.ConvertAll(line_sp, int.Parse);
                for (int j = 0; j < nbAircrafts; ++j) durations[i, j] = lineDuration[j];
            }

            int baseLineDurationSum = 0;
            for (int i = 0; i < nbAircrafts; ++i) baseLineDurationSum += durations[0, i];
            H = arrivalDates.Max() + baseLineDurationSum;



            List<int[]> setupMatrixTemp = new List<int[]>();
            string line;
            while ((line = input.ReadLine()) != null)
            {
                string[] valuesStr = line.Split(new char[] { ' ', '\t' },
                        StringSplitOptions.RemoveEmptyEntries);

                int[] values = Array.ConvertAll(valuesStr, int.Parse);

                setupMatrixTemp.Add(values);
            }

            int nbLines = setupMatrixTemp.Count;
            setupMatrix = new int[nbLines, nbLines];

            for (int i = 0; i < nbLines; ++i)
            {
                for (int j = 0; j < nbLines; ++j) setupMatrix[i, j] = setupMatrixTemp[i][j];
            }
        }
    }

    public void Dispose()
    {
        optimizer.Dispose();
    }

    public void Solve(int timeLimit)
    {
        // Declare the optimization model
        HxModel model = optimizer.GetModel();

        // Decision Variable: time range for each aircraft
        aircraftsIntervals = new HxExpression[nbAircrafts];
        for (int aircraft = 0; aircraft < nbAircrafts; ++aircraft)
        {
            aircraftsIntervals[aircraft] = model.Interval(arrivalDates[aircraft], H);
        }
        // Create a HexalyOptimizer array in order to be able to access it with "at" operators
        HxExpression aircraftsIntervalsArray = model.Array(aircraftsIntervals);

        // Decision Variable: number of trucks used for each aircraft
        aircraftsNbTrucks = new HxExpression[nbAircrafts];
        for (int aircraft = 0; aircraft < nbAircrafts; ++aircraft)
        {
            aircraftsNbTrucks[aircraft] = model.Int(minTrucks[aircraftsSize[aircraft]],
                    maxTrucks[aircraftsSize[aircraft]]);
        }
        // Create a HexalyOptimizer array in order to be able to access it with "at" operators
        HxExpression aircraftsNbTrucksArray = model.Array(aircraftsNbTrucks);

        // Decision Variable: number of trucks used for each aircraft
        gates = new HxExpression[nbGates];
        for (int i = 0; i < nbGates; ++i) gates[i] = model.List(nbAircrafts);

        // Create a HexalyOptimizer array in order to be able to access it with "at" operators
        HxExpression gatesArray = model.Array(gates);

        // Makespan: end of the de-icing of the last aircraft
        makespan = model.Max();
        for (int i = 0; i < nbAircrafts; ++i) makespan.AddOperand(model.End(aircraftsIntervals[i]));

        // Create a HexalyOptimizer array in order to be able to access it with "at" operators 
        HxExpression setupMatrixArray = model.Array(setupMatrix);

        // Create a HexalyOptimizer array in order to be able to access it with "at" operators
        HxExpression durationsArray = model.Array(durations);

        // Constraint: every aircraft has a unique gate
        model.Constraint(model.Partition(gatesArray));

        // Constraint: duration of the de-icing
        for (int aircraft = 0; aircraft < nbAircrafts; ++aircraft)
        {
            HxExpression constraintDuration = (model.Length(aircraftsIntervals[aircraft])
                    == durationsArray[aircraftsNbTrucks[aircraft]-1][aircraft]);
            model.Constraint(constraintDuration);
        }

        // Constraint no-overlapping: one aircraft per gate for each time, and a setup time
        // between two aircrafts on the same gate
        for (int j = 0; j < nbGates; ++j)
        {
            HxExpression gate = gates[j];
            HxExpression constraintOverlapping = model.LambdaFunction(i =>
                    model.End(aircraftsIntervalsArray[gate[i]])
                            + setupMatrixArray[gate[i]][gate[i+1]]
                    <= model.Start(aircraftsIntervalsArray[gate[i+1]])
            );
            model.Constraint(model.And(model.Range(0, model.Count(gate)-1), constraintOverlapping));
        }

        // Constraint: trucks capacity
        HxExpression respectCapacity = model.LambdaFunction(time =>
        {
            HxExpression currNbTrucks = model.Sum();
            for (int i = 0; i< nbAircrafts; i++)
            {
                currNbTrucks.AddOperand(model.Contains(aircraftsIntervalsArray[i], time)
                    * aircraftsNbTrucksArray[i]
                    );
            }
            return currNbTrucks <= nbTrucks;
        });
        model.Constraint(model.And(model.Range(0, makespan), respectCapacity));

        // Minimize the makespan
        model.Minimize(makespan);

        model.Close();

        // Parameterize the optimizer
        optimizer.GetParam().SetTimeLimit(timeLimit);

        optimizer.Solve();
    }

/* Write the solution in a file with the following format:
     *  - the total makespan
     *  - for each aircraft, the bay, the starting date of the interval, 
            the ending date of the interval, the number of trucks attributed */
    public void WriteSolution(string fileName)
    {
        using (StreamWriter output = new StreamWriter(fileName))
        {
            Console.WriteLine("Solution written in file " + fileName);

            output.Write(makespan.GetValue);
            output.WriteLine();
            for (int gate = 0; gate < nbGates; ++gate)
            {
                HxCollection aircraftsCollection = gates[gate].GetCollectionValue();
                for (int i = 0; i < aircraftsCollection.Count(); ++i)
                {
                    long aircraft = aircraftsCollection[i];
                    output.Write(gate + "\t"
                            + aircraftsIntervals[aircraft].GetIntervalValue().Start() + "\t"
                            + aircraftsIntervals[aircraft].GetIntervalValue().End() + "\t"
                            + aircraftsNbTrucks[aircraft].GetIntValue());
                    output.WriteLine();
                }
            }
        }
    }

    public static void Main(string[] args)
    {
        if (args.Length < 1)
        {
            Console.WriteLine("Usage: AircraftDeicing 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] : "60";

        using (AircraftDeicing model = new AircraftDeicing())
        {
            model.ReadInstance(instanceFile);
            model.Solve(int.Parse(strTimeLimit));
            if (outputFile != null) model.WriteSolution(outputFile);
        }
    }
}
Compilation / Execution (Windows)
javac AircraftDeicing.java -cp %HX_HOME%\bin\hexaly.jar
java -cp %HX_HOME%\bin\hexaly.jar;. AircraftDeicing instances/60_aircrafts.txt
Compilation / Execution (Linux)
javac AircraftDeicing.java -cp /opt/hexaly_14_0/bin/hexaly.jar
java -cp /opt/hexaly_14_0/bin/hexaly.jar:. AircraftDeicing instances/60_aircrafts.txt
import java.util.*;
import java.io.*;
import com.hexaly.optimizer.*;

public class AircraftDeicing {
    // Number of gates
    private int nbGates;

    // Number of aircrafts
    private int nbAircrafts;

    // Size of the aircrafts
    private int[] aircraftsSize;

    // Arrival date of the aircrafts
    private int[] arrivalDates;

    // Number of trucks
    private int nbTrucks;

    // Number of trucks possible per aircraft
    private int[] minTrucks;
    private int[] maxTrucks;

    // Durations normaised by the truck number of the aircraft
    private int[][] durations;

    // Setup Matirx: the time needed between two aircrafts to clean the bay
    private int[][] setupMatrix;

    // Horizon: trivial upper bound for the end times of the tasks
    private int H;

    // Hexaly Optimizer
    final HexalyOptimizer optimizer;
    // Decision variables: to each aircraft, we associate an interval of de-icing
    private HxExpression[] aircraftsIntervals;
    // Decision variable: to each aircraft, we associate a number of trucks
    private HxExpression[] aircraftsNbTrucks;
    // Decision variables: to each gate, we associate the list of aircrafts to be de-iced there
    private HxExpression[] gates;
    // Objective = minimize the makespan: end of the de-icing of the last aircraft
    private HxExpression makespan;

    public AircraftDeicing(HexalyOptimizer optimizer) throws IOException {
        this.optimizer = optimizer;
    }

    public static int maxInt(int[] v) {
        return Arrays.stream(v).max().getAsInt();
    }

    public void readInstance(String fileName) throws IOException {
        try (Scanner input = new Scanner(new File(fileName))) {
            input.useLocale(Locale.ROOT);

            nbGates = input.nextInt();
            nbAircrafts = input.nextInt();

            aircraftsSize = new int[nbAircrafts];
            for (int aircraft = 0; aircraft < nbAircrafts; ++aircraft) {
                aircraftsSize[aircraft] = input.nextInt();
            }

            arrivalDates = new int[nbAircrafts];
            for (int aircraft = 0; aircraft < nbAircrafts; ++aircraft) {
                arrivalDates[aircraft] = input.nextInt();
            }

            nbTrucks = input.nextInt();

            minTrucks = new int[nbAircrafts];
            for (int i = 0; i < nbAircrafts; ++i) minTrucks[i] = input.nextInt();

            maxTrucks = new int[nbAircrafts];
            for (int i = 0; i < nbAircrafts; ++i) maxTrucks[i] = input.nextInt();

            int maxGlobalTrucks = maxInt(maxTrucks);

            durations = new int[maxGlobalTrucks][nbAircrafts];
            for (int m = 0; m < maxGlobalTrucks; ++m) {
                for (int aircraft = 0; aircraft < nbAircrafts; ++aircraft) {
                    durations[m][aircraft] = input.nextInt();
                }
            }

            H = maxInt(arrivalDates) + Arrays.stream(durations[0]).sum();

            setupMatrix = new int[nbAircrafts][nbAircrafts];
            for (int i = 0; i < nbAircrafts; ++i) {
                int[] col = new int[nbAircrafts];
                for (int j = 0; j < nbAircrafts; ++j) col[j] = (input.nextInt());
                setupMatrix[i] = col;
            }
        }
    }


    public void solve(int timeLimit) {
        // Declare the optimization model
        HxModel model = optimizer.getModel();

        // Decision Variable: time range for each aircraft
        aircraftsIntervals = new HxExpression[nbAircrafts];
        for (int aircraft = 0; aircraft < nbAircrafts; ++aircraft) {
            aircraftsIntervals[aircraft] = model.intervalVar(arrivalDates[aircraft], H);
        }
        // Create a HexalyOptimizer array in order to be able to access it with "at" operators
        HxExpression aircraftsIntervalsArray = model.array(aircraftsIntervals);

        // Decision Variable: number of trucks used for each aircraft
        aircraftsNbTrucks = new HxExpression[nbAircrafts];
        for (int aircraft = 0; aircraft < nbAircrafts; ++aircraft) {
            aircraftsNbTrucks[aircraft] = model.intVar(
                    minTrucks[aircraftsSize[aircraft]], maxTrucks[aircraftsSize[aircraft]]);
        }
        // Create a HexalyOptimizer array in order to be able to access it with "at" operators
        HxExpression aircraftsNbTrucksArray = model.array(aircraftsNbTrucks);

        // Decision Variable: number of trucks used for each aircraft
        gates = new HxExpression[nbGates];
        for (int i = 0; i < nbGates; ++i) gates[i] = model.listVar(nbAircrafts);

        // Create a HexalyOptimizer array in order to be able to access it with "at" operators
        HxExpression gatesArray = model.array(gates);

        // Create a HexalyOptimizer array in order to be able to access it with "at" operators 
        HxExpression setupMatrixArray = model.array(setupMatrix);

        // Create a HexalyOptimizer array in order to be able to access it with "at" operators
        HxExpression durationsArray = model.array(durations);

        // Makespan: end of the de-icing of the last aircraft
        makespan = model.max();
        for (int i = 0; i < nbAircrafts; ++i) makespan.addOperand(model.end(aircraftsIntervals[i]));

        // Constraint: every aircraft has a unique gate
        model.constraint(model.partition(gatesArray));

        // Constraint: duration of the de-icing
        for (int aircraft = 0; aircraft < nbAircrafts; ++aircraft) {
            HxExpression constraintDuration = model.eq(model.length(aircraftsIntervals[aircraft]),
                    model.at(model.at(durationsArray, model.sum(aircraftsNbTrucks[aircraft], -1)), aircraft));
            model.constraint(constraintDuration);
        }

        // Constraint no-overlapping: one aircraft per gate for each time, and a setup time
        // between two aircrafts on the same gate
        for (int j = 0; j < nbGates; ++j) {
            HxExpression gate = gates[j];
            HxExpression constraintOverlapping = model.lambdaFunction(i -> {
                HxExpression nextAvailability = model.sum();
                nextAvailability.addOperand(
                        model.end(model.at(aircraftsIntervalsArray, model.at(gate,i))));
                nextAvailability.addOperand(
                        model.at(setupMatrixArray, model.at(gate, i), 
                        model.at(gate, model.sum(i, 1))));
                return model.leq(nextAvailability, model.start(
                        model.at(aircraftsIntervalsArray, model.at(gate, model.sum(i, 1)))));
        });
            model.constraint(model.and(
                    model.range(0, model.sum(model.count(gate), -1)), constraintOverlapping));
        }

        // Constraint: trucks capacity
        HxExpression respectCapacity = model.lambdaFunction(time -> {
            HxExpression currNbTrucks = model.sum();
            for (int i = 0; i < nbAircrafts; i++) {
                currNbTrucks.addOperand(model.prod(model.contains(model.at(aircraftsIntervalsArray, i), time), 
                        model.at(aircraftsNbTrucksArray, i)));
            }
            return model.leq(currNbTrucks, nbTrucks);
        }
        );
        model.constraint(model.and(model.range(0, makespan), respectCapacity));

        // Minimization of the makespan
        model.minimize(makespan);

        model.close();

        // Parameterize the optimizer
        optimizer.getParam().setTimeLimit(timeLimit);

        optimizer.solve();
    }

/* Write the solution in a file with the following format:
     *  - the total makespan
     *  - for each aircraft, the bay, the starting date of the interval,
     *        the ending date of the intrval, the number of trucks attributed */
    public void writeSolution(String fileName) throws IOException {
        try (PrintWriter output = new PrintWriter(fileName)) {
            System.out.println("Solution written in file " + fileName);

            output.write((int)makespan.getValue());
            output.write("\n");
            for (int gate = 0; gate < nbGates; ++gate) {
                HxCollection aircraftsCollection = gates[gate].getCollectionValue();
                for (int i = 0; i < aircraftsCollection.count(); ++i) {
                    int aircraft = (int)aircraftsCollection.get(i);
                    output.write(gate + "\t" 
                            + aircraftsIntervals[aircraft].getIntervalValue().getStart() + "\t"
                            + aircraftsIntervals[aircraft].getIntervalValue().getEnd() + "\t"
                            + aircraftsNbTrucks[aircraft].getIntValue());
                    output.write("\n");
                }
            }
        }
    }

    public static void main(String[] args) {
        if (args.length < 1) {
            System.out.println("Usage: AircraftDeicing 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] : "60";

        try (HexalyOptimizer optimizer = new HexalyOptimizer()) {
            AircraftDeicing model = new AircraftDeicing(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);
        }
    }
}