Steel Mill Slab Design Problem

Problem

In the Steel Mill Slab Design Problem, we must organize the production of steel orders into slabs. Steel is produced by casting molten iron into slabs. The mill can produce a finite number of slab sizes. Each order has two properties: a color, which corresponds to a certain path through the mill, and a weight. Slabs have a maximum capacity: the total weight of orders assigned to a slab cannot exceed this capacity. In addition, since cutting up slabs in order to send them to different parts of the mill is expensive, there is a limit on the number of different colors in each slab (usually two). The objective is to minimize the waste of steel, that is, the amount of steel produced but not used for any order. For more details, see CSPLib.

Principles learned

Data

The format of the data files is as follows:

  • First line: number of slab sizes, then all possible sizes
  • Second line: number of colors
  • Third line: number of orders
  • Then, for each order: the size and color of the order

We assume each slab can contain orders of at most two different colors.

Program

The Hexaly model for the Steel Mill Slab Design Problem uses set decision variables, representing the set of orders assigned to each slab. Using the ‘partition‘ operator on the set variables, we constrain each order to be assigned to exactly one slab.

We compute the total quantity of steel used in each slab using a variadic ‘sum’ operator on the set and a lambda function returning the size of an order. Note that the number of terms in this sum varies during the search, along with the size of the set. We can then constrain the total steel quantity to be lower than the maximum size of a slab.

Using the ‘distinct‘ operator and another lambda function, we compute the number of different colors in each slab. We can then use the ‘count’ operator to ensure that the maximum number of different colors in each slab is respected.

Finally, we compute the quantity of wasted steel for each slab, which is equal to the smallest slab size that can contain all the orders assigned to this slab minus the total steel quantity used for these orders.

Execution
hexaly steel_mill_slab_design.hxm inFileName=instances/12orderproblem.in [hxTimeLimit=] [solFileName=]
use io;

/* Read instance data */
function input() {
    local usage = "Usage: hexaly steel_mill_slab_design.hxm "
            + "inFileName=inputFile [solFileName=outputFile] [hxTimeLimit=timeLimit]";

    if (inFileName == nil) throw usage;
    local inFile = io.openRead(inFileName);

    nbColorsMaxSlab = 2;
    nbSlabSizes = inFile.readInt();
    slabSizes[1..nbSlabSizes] = inFile.readInt();
    maxSize = slabSizes[nbSlabSizes];

    nbColors = inFile.readInt();
    nbOrders = inFile.readInt();
    nbSlabs = nbOrders;

    sumSizeOrders = 0;
    // List of quantities and colors for each order
    for [i in 0...nbOrders] {
        quantities[i] = inFile.readInt();
        colors[i] = inFile.readInt();
        sumSizeOrders += quantities[i];
    }
    preComputeWasteForContent();
}

// Compute the vector wasteForContent
function preComputeWasteForContent() {
    // No waste when a slab is empty
    wasteForContent[0] = 0;

    // The waste for each content is the difference between the minimum slab size
    // able to contain this content and the content
    prevSize = 0;
    for [size in slabSizes] {
        if (size < prevSize) throw "Slab sizes should be sorted in ascending order";
        wasteForContent[content in prevSize + 1..size] = size - content;
        prevSize = size;
    }
    wasteForContent[prevSize+1..sumSizeOrders] = 0;
}

/* Declare the optimization model */
function model() {
    
    // Set decisions: slab[k] represents the orders in slab k
    slabs[0...nbSlabs] <- set(nbOrders);

    // Each order must be in one slab and one slab only
    constraint partition[s in 0...nbSlabs](slabs[s]);

    for [s in 0...nbSlabs] {
        
        local orders <- slabs[s];

        // The number of colors per slab must not exceed a specified value
        constraint count(distinct(orders, o => colors[o])) <= nbColorsMaxSlab;

        // The content of each slab must not exceed the maximum size of the slab
        slabContent[s] <- sum(orders, o => quantities[o]);
        constraint slabContent[s] <= maxSize;
    }

    // Wasted steel is computed according to the content of the slab
    wastedSteel[s in 0...nbSlabs] <- wasteForContent[slabContent[s]];

    // Minimize the total wasted steel
    totalWastedSteel <- sum[s in 0...nbSlabs](wastedSteel[s]);

    minimize totalWastedSteel;
}

/* Parametrize the solver */
function param() {
    if (hxTimeLimit == nil) hxTimeLimit = 60;
}

/* Write the solution in a file with the following format: 
 *  - total wasted steel
 *  - number of slabs used
 *  - for each slab used, the number of orders in the slab and the list of orders */
function output() {
    if (solFileName == nil) return;

    local solFile = io.openWrite(solFileName);
    solFile.println(totalWastedSteel.value);
    actualNbSlabs = 0;
    for [s in 0...nbSlabs] {
        if (slabs[s].value.count() > 0) actualNbSlabs += 1;
    }
    solFile.println(actualNbSlabs);

    for [s in 0...nbOrders] {
        nbOrdersInSlab = slabs[s].value.count();
        if (nbOrdersInSlab == 0) continue;
        solFile.print(nbOrdersInSlab);
        for [o in slabs[s].value] solFile.print(" ", o + 1);
        solFile.println();
    }
}
Execution (Windows)
set PYTHONPATH=%HX_HOME%\bin\python
python steel_mill_slab_design.py instances\12orderproblem.in
 
Execution (Linux)
export PYTHONPATH=/opt/hexaly_13_0/bin/python
python steel_mill_slab_design.py instances/12orderproblem.in
import hexaly.optimizer
import sys

if len(sys.argv) < 2:
    print("Usage: python steel_mill_slab_design.py inputFile [outputFile] [timeLimit]")
    sys.exit(1)

def read_integers(filename):
    with open(filename) as f:
        return [int(elem) for elem in f.read().split()]


# Compute the vector waste_for_content
def pre_compute_waste_for_content(slab_sizes, sum_size_orders):
    # No waste when a slab is empty
    waste_for_content = [0] * sum_size_orders

    prev_size = 0
    for size in slab_sizes:
        if size < prev_size:
            print("Slab sizes should be sorted in ascending order")
            sys.exit(1)
        for content in range(prev_size + 1, size):
            waste_for_content[content] = size - content
        prev_size = size
    return waste_for_content


with hexaly.optimizer.HexalyOptimizer() as optimizer:
    #
    # Read instance data
    #
    nb_colors_max_slab = 2

    file_it = iter(read_integers(sys.argv[1]))
    nb_slab_sizes = next(file_it)
    slab_sizes = [next(file_it) for i in range(nb_slab_sizes)]
    max_size = slab_sizes[nb_slab_sizes - 1]

    nb_colors = next(file_it)
    nb_orders = next(file_it)
    nb_slabs = nb_orders

    sum_size_orders = 0

    # List of quantities and colors for each order
    quantities_data = []
    colors_data = []
    for o in range(nb_orders):
        quantities_data.append(next(file_it))
        colors_data.append(next(file_it))
        sum_size_orders += quantities_data[o]

    waste_for_content = pre_compute_waste_for_content(slab_sizes, sum_size_orders)

    #
    # Declare the optimization model
    #
    model = optimizer.model

    # Create array and function to retrieve the orders's colors and quantities
    colors = model.array(colors_data)
    color_lambda = model.lambda_function(lambda l: colors[l])
    quantities = model.array(quantities_data)
    quantity_lambda = model.lambda_function(lambda o: quantities[o])

    # Set decisions: slab[k] represents the orders in slab k
    slabs = [model.set(nb_orders) for s in range(nb_slabs)]

    # Each order must be in one slab and one slab only
    model.constraint(model.partition(slabs))

    slabContent = []
    
    for s in range(nb_slabs):

        # The number of colors per slab must not exceed a specified value
        model.constraint(model.count(model.distinct(slabs[s], color_lambda)) <= nb_colors_max_slab)

        # The content of each slab must not exceed the maximum size of the slab
        slabContent.append(model.sum(slabs[s], quantity_lambda))
        model.constraint(slabContent[s] <= max_size)

    waste_for_content_array = model.array(waste_for_content)

    # Wasted steel is computed according to the content of the slab
    wasted_steel = [waste_for_content_array[slabContent[s]] for s in range(nb_slabs)]

    # Minimize the total wasted steel
    total_wasted_steel = model.sum(wasted_steel)
    model.minimize(total_wasted_steel)

    model.close()

    # Parameterize the optimizer
    if len(sys.argv) >= 4:
        optimizer.param.time_limit = int(sys.argv[3])
    else:
        optimizer.param.time_limit = 60
    optimizer.solve()

    #
    # Write the solution in a file with the following format:
    #  - total wasted steel
    #  - number of slabs used
    #  - for each slab used, the number of orders in the slab and the list of orders
    #
    if len(sys.argv) >= 3:
        with open(sys.argv[2], 'w') as f:
            f.write("%d\n" % total_wasted_steel.value)
            actual_nb_slabs = 0
            for s in range(nb_slabs):
                if slabs[s].value.count() > 0:
                    actual_nb_slabs += 1
            f.write("%d\n" % actual_nb_slabs)

            for s in range(nb_slabs):
                nb_orders_in_slab = slabs[s].value.count()
                if nb_orders_in_slab == 0:
                    continue
                f.write("%d" % nb_orders_in_slab)
                for o in slabs[s].value:
                    f.write(" %d" % (o + 1))
                f.write("\n")
Compilation / Execution (Windows)
cl /EHsc steel_mill_slab_design.cpp -I%HX_HOME%\include /link %HX_HOME%\bin\hexaly130.lib
steel_mill_slab_design instances\12orderproblem.in
 
Compilation / Execution (Linux)
g++ steel_mill_slab_design.cpp -I/opt/hexaly_13_0/include -lhexaly130 -lpthread -o steel_mill_slab_design
./steel_mill_slab_design instances/12orderproblem.in
#include "optimizer/hexalyoptimizer.h"
#include <fstream>
#include <iostream>
#include <vector>

using namespace hexaly;
using namespace std;

class SteelMillSlabDesign {
public:
    // Number of available slabs
    int nbSlabs;
    
    // Number of orders
    int nbOrders;

    // Number of colors
    int nbColors;

    // Maximum number of colors per slab
    int nbColorsMaxSlab;

    // Maximum size of a slab
    int maxSize;

    // List of colors for each order
    vector<int> colors;

    // List of quantities for each order
    vector<int> quantities;    

    // Steel waste computed for each content value
    vector<int> wasteForContent;

    // Hexaly Optimizer
    HexalyOptimizer optimizer;

    // Hexaly Program variables
    vector<HxExpression> slabs;

    // Objective
    HxExpression totalWastedSteel;

    /* Read instance data */
    void readInstance(const string& fileName) {
        ifstream infile;
        infile.exceptions(ifstream::failbit | ifstream::badbit);
        infile.open(fileName.c_str());

        nbColorsMaxSlab = 2;

        int nbSlabSizes;
        infile >> nbSlabSizes;

        vector<int> slabSizes(nbSlabSizes);
        for (int i = 0; i < nbSlabSizes; ++i) {
            infile >> slabSizes[i];
        }
        maxSize = slabSizes[nbSlabSizes - 1];

        infile >> nbColors;
        infile >> nbOrders;
        nbSlabs = nbOrders;

        quantities.resize(nbOrders);
        colors.resize(nbOrders);
        int sumSizeOrders = 0;
        for (int o = 0; o < nbOrders; ++o) {
            infile >> quantities[o];
            // Note: colors are in 1..nbColors
            infile >> colors[o];
            sumSizeOrders += quantities[o];
        }

        preComputeWasteForContent(slabSizes, sumSizeOrders);
    }

private:
    // Compute the vector wasteForContent
    void preComputeWasteForContent(const vector<int>& slabSizes, int sumSizeOrders) {

        // No waste when a slab is empty
        wasteForContent.resize(sumSizeOrders, (int)0);

        int prevSize = 0;
        for (size_t i = 0; i < slabSizes.size(); ++i) {
            int size = slabSizes[i];
            if (size < prevSize) {
                cerr << "Slab sizes should be sorted in ascending order" << endl;
                exit(1);
            }
            for (int content = prevSize + 1; content < size; ++content) {
                wasteForContent[content] = (int)(size - content);
            }
            prevSize = size;
        }
    }

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

        // Create a HexalyOptimizer array and a function to retrieve the orders's colors and quantities
        HxExpression colorsArray = model.array(colors.begin(), colors.end());
        HxExpression colorLambda = model.createLambdaFunction([&](HxExpression i) { return colorsArray[i]; });
        HxExpression quantitiesArray = model.array(quantities.begin(), quantities.end());
        HxExpression quantitiesLambda = model.createLambdaFunction([&](HxExpression i) { return quantitiesArray[i]; });

        vector<HxExpression> slabContent(nbSlabs);
        vector<HxExpression> wastedSteel(nbSlabs);
        // Create a HexalyOptimizer array to be able to access it with "at" operators
        HxExpression wasteForContentArray = model.array(wasteForContent.begin(), wasteForContent.end());

        // Set decisions: slab[k] represents the orders in slab k
        slabs.resize(nbSlabs);
        for (int s = 0; s < nbSlabs; ++s)  {
            slabs[s] = model.setVar(nbOrders);
        }

        // Each order must be in one slab and one slab only
        model.constraint(model.partition(slabs.begin(), slabs.end()));

        for (int s = 0; s < nbSlabs; ++s) {

            HxExpression orders = slabs[s];

            // The number of colors per slab must not exceed a specified value
            model.constraint(model.count(model.distinct(orders, colorLambda)) <= nbColorsMaxSlab);

            // The content of each slab must not exceed the maximum size of the slab
            slabContent[s] = model.sum(orders, quantitiesLambda);
            model.constraint(slabContent[s] <= maxSize);

            // Wasted steel is computed according to the content of the slab
            wastedSteel[s] = wasteForContentArray[slabContent[s]];
        }

    // Minimize the total wasted steel
    totalWastedSteel = model.sum(wastedSteel.begin(), wastedSteel.end());
    model.minimize(totalWastedSteel);

    model.close();

    // Parametrize the optimizer
    optimizer.getParam().setTimeLimit(limit);
    optimizer.solve();
    }

    /* Write the solution in a file with the following format:
     *  - total wasted steel
     *  - number of slabs used
     *  - for each slab used, the number of orders in the slab and the list of orders */
    void writeSolution(const string& fileName) {
        ofstream outfile;
        outfile.exceptions(ofstream::failbit | ofstream::badbit);
        outfile.open(fileName.c_str());
        outfile << totalWastedSteel.getValue() << endl;

        int actualNbSlabs = 0;
        for(int s = 0; s < nbSlabs; ++s) {
            if (slabs[s].getCollectionValue().count() > 0) actualNbSlabs++;
        }
        outfile << actualNbSlabs << endl;

        for (int s = 0; s < nbSlabs; ++s) {
            HxCollection slabCollection = slabs[s].getCollectionValue();
            int nbOrdersInSlab = slabCollection.count();
            if (nbOrdersInSlab == 0) continue;
            outfile << nbOrdersInSlab;
            for(int o = 0; o < nbOrdersInSlab; ++o) {
                outfile << " " << slabCollection.get(o) + 1;
            }
            outfile << endl;
        }
    }
};

int main(int argc, char** argv) {
    if (argc < 2) {
        cerr << "Usage: steel_mill_slab_design inputFile [outputFile] [timeLimit]" << endl;
        return 1;
    }

    const char* instanceFile = argv[1];
    const char* outputFile = argc >= 3 ? argv[2] : NULL;
    const char* strTimeLimit = argc >= 4 ? argv[3] : "60";

    try {
        SteelMillSlabDesign model;
        model.readInstance(instanceFile);
        model.solve(atoi(strTimeLimit));
        if (outputFile != NULL)
            model.writeSolution(outputFile);
        return 0;
    } catch (const exception& e) {
        cerr << "An error occurred: " << e.what() << endl;
        return 1;
    }
}
Compilation / Execution (Windows)
copy %HX_HOME%\bin\Hexaly.NET.dll .
csc SteelMillSlabDesign.cs /reference:Hexaly.NET.dll
SteelMillSlabDesign instances\12orderproblem.in
using System;
using System.IO;
using System.Collections.Generic;
using Hexaly.Optimizer;

public class SteelMillSlabDesign : IDisposable
{
    // Number of available slabs
    int nbSlabs;

    // Number of orders
    int nbOrders;

    // Number of colors
    int nbColors;

    // Maximum number of colors per slab
    int nbColorsMaxSlab;

    // Maximum size of a slab
    int maxSize;

    // List of colors for each order
    int[] colorsData;

    // List of quantities for each order
    int[] quantitiesData;

    // Steel waste computed for each content value
    long[] wasteForContent;

    // Hexaly Optimizer
    HexalyOptimizer optimizer;

    // Hexaly Program variables
    HxExpression[] slabs;

    // Objective
    HxExpression totalWastedSteel;

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

    public void Dispose()
    {
        if (optimizer != null)
            optimizer.Dispose();
    }

    /* Read instance data */
    void ReadInstance(string fileName)
    {
        using (StreamReader input = new StreamReader(fileName))
        {
            nbColorsMaxSlab = 2;
            string[] splitted = input.ReadLine().Split();
            int nbSlabSizes = int.Parse(splitted[0]);
            int[] slabSizes = new int[nbSlabSizes];
            for (int i = 0; i < nbSlabSizes; ++i)
                slabSizes[i] = int.Parse(splitted[i + 1]);
            maxSize = slabSizes[nbSlabSizes - 1];

            nbColors = int.Parse(input.ReadLine());
            nbOrders = int.Parse(input.ReadLine());
            nbSlabs = nbOrders;

            int sumSizeOrders = 0;
            
            quantitiesData = new int[nbOrders];
            colorsData = new int[nbOrders];
            for (int o = 0; o < nbOrders; ++o)
            {
                splitted = input.ReadLine().Split();
                quantitiesData[o] = int.Parse(splitted[0]);
                int c = int.Parse(splitted[1]);
                // Note: colors are in 1..nbColors
                colorsData[o] = c;
                sumSizeOrders += quantitiesData[o];
            }
            PreComputeWasteForContent(slabSizes, sumSizeOrders);
        }
    }

    // Compute the vector wasteForContent
    private void PreComputeWasteForContent(int[] slabSizes, int sumSizeOrders)
    {
        // No waste when a slab is empty
        wasteForContent = new long[sumSizeOrders];

        int prevSize = 0;
        for (int i = 0; i < slabSizes.Length; ++i)
        {
            int size = slabSizes[i];
            if (size < prevSize)
                throw new Exception("Slab sizes should be sorted in ascending order");

            for (int content = prevSize + 1; content < size; ++content)
                wasteForContent[content] = size - content;
            prevSize = size;
        }
    }

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

        // Create a HexalyOptimizer array and a function to retrieve the orders's colors and quantities
        HxExpression colors = model.Array(colorsData);
        HxExpression colorsLambda = model.LambdaFunction(i => colors[i]);
        HxExpression quantities = model.Array(quantitiesData);
        HxExpression quantitiesLambda = model.LambdaFunction(i => quantities[i]);

        HxExpression[] slabContent = new HxExpression[nbSlabs];
        HxExpression[] wastedSteel = new HxExpression[nbSlabs];
        // Create a HexalyOptimizer array to be able to access it with "at" operators
        HxExpression wasteForContentArray = model.Array(wasteForContent);

        // Set decisions: slabs[k] represents the orders in slab k
        slabs = new HxExpression[nbSlabs];
        for (int s = 0; s < nbSlabs; ++s)
        {
            slabs[s] = model.Set(nbOrders);
        }

        // Each order must be in one slab and one slab only
        model.Constraint(model.Partition(slabs));

        for (int s = 0; s < nbSlabs; ++s)
        {
            // The number of colors per slab must not exceed a specified value
            model.Constraint(model.Count(model.Distinct(slabs[s], colorsLambda)) <=nbColorsMaxSlab);

            // The content of each slab must not exceed the maximum size of the slab
            slabContent[s] = model.Sum(slabs[s], quantitiesLambda);
            model.Constraint(slabContent[s] <= maxSize);

            // Wasted steel is computed according to the content of the slab
            wastedSteel[s] = wasteForContentArray[slabContent[s]];
        }

        // Minimize the total wasted steel
        totalWastedSteel = model.Sum(wastedSteel);
        model.Minimize(totalWastedSteel);

        model.Close();

        // Parametrize the optimizer
        optimizer.GetParam().SetTimeLimit(limit);
        optimizer.Solve();
    }

    /* Write the solution in a file with the following format:
     *  - total wasted steel
     *  - number of slabs used
     *  - for each slab used, the number of orders in the slab and the list of orders */
    void WriteSolution(string fileName)
    {
        using (StreamWriter output = new StreamWriter(fileName))
        {
            output.WriteLine(totalWastedSteel.GetValue());
            int actualNbSlabs = 0;
            for (int s = 0; s < nbSlabs; ++s)
            {
                if (slabs[s].GetCollectionValue().Count() > 0)
                {
                    actualNbSlabs++;
                }
            }
            output.WriteLine(actualNbSlabs);

            for (int s = 0; s < nbSlabs; ++s)
            {
                HxCollection slabCollection = slabs[s].GetCollectionValue();
                int nbOrdersInSlab = slabCollection.Count();
                if (nbOrdersInSlab == 0) continue;
                output.Write(nbOrdersInSlab);
                for (int o = 0; o < nbOrdersInSlab; ++o)
                {
                    output.Write(" " + (slabCollection.Get(o) + 1));
                }
                output.WriteLine();
            }
        }
    }

    public static void Main(string[] args)
    {
        if (args.Length < 1)
        {
            Console.WriteLine("Usage: SteelMillSlabDesign inputFile [outputFile] [timeLimit]");
            Environment.Exit(1);
        }

        string instanceFile = args[0];
        string outputFile = args.Length > 1 ? args[1] : null;
        string strTimeLimit = args.Length > 2 ? args[2] : "60";

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

public class SteelMillSlabDesign {
    // Number of available slabs
    private int nbSlabs;

    // Number of orders
    private int nbOrders;

    // Number of colors
    private int nbColors;

    // Maximum number of colors per slab
    private int nbColorsMaxSlab;

    // Maximum size of a slab
    private int maxSize;

    // List of colors for each order
    private int[] colorsData;

    // List of quantities for each order
    private int[] quantitiesData;

    // Steel waste computed for each content value
    private long[] wasteForContentData;

    // Hexaly Optimizer
    private final HexalyOptimizer optimizer;

    // Objective
    private HxExpression totalWastedSteel;

    // Hexaly Program variables
    private HxExpression[] slabs;

    private SteelMillSlabDesign(HexalyOptimizer optimizer) {
        this.optimizer = optimizer;
    }

    /* Read instance data */
    private void readInstance(String fileName) throws IOException {
        try (Scanner input = new Scanner(new File(fileName))) {
            input.useLocale(Locale.ROOT);
            nbColorsMaxSlab = 2;
            int nbSlabSizes = input.nextInt();

            int[] slabSizes = new int[nbSlabSizes];
            for (int i = 0; i < nbSlabSizes; ++i) {
                slabSizes[i] = input.nextInt();
            }
            maxSize = slabSizes[nbSlabSizes - 1];

            nbColors = input.nextInt();
            nbOrders = input.nextInt();
            nbSlabs = nbOrders;

            int sumSizeOrders = 0;

            colorsData = new int[nbOrders];
            quantitiesData = new int[nbOrders];
            for (int o = 0; o < nbOrders; ++o) {
                quantitiesData[o] = input.nextInt();
                int c = input.nextInt();
                // Note: colors are in 1..nbColors
                colorsData[o] = c;
                sumSizeOrders += quantitiesData[o];
            }

            preComputeWasteForContent(slabSizes, sumSizeOrders);
        }
    }

    // Compute the vector wasteForContent
    private void preComputeWasteForContent(int[] slabSizes, int sumSizeOrders) {
        // No waste when a slab is empty
        wasteForContentData = new long[sumSizeOrders];

        int prevSize = 0;
        for (int i = 0; i < slabSizes.length; ++i) {
            int size = slabSizes[i];
            if (size < prevSize)
                throw new RuntimeException("Slab sizes should be sorted in ascending order");
            for (int content = prevSize + 1; content < size; ++content) {
                wasteForContentData[content] = size - content;
            }
            prevSize = size;
        }
    }

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

        // Create a HexalyOptimizer array and a function to retrieve the orders's colors and quantities
        HxExpression colors = model.array(colorsData);
        HxExpression colorLambda = model.lambdaFunction(i -> model.at(colors, i));
        HxExpression quantities = model.array(quantitiesData);
        HxExpression quantitiesLambda = model.lambdaFunction(i -> model.at(quantities, i));
        
        HxExpression[] slabContent = new HxExpression[nbSlabs];
        HxExpression[] wastedSteel = new HxExpression[nbSlabs];
        // Create a HexalyOptimizer array to be able to access it with "at" operators
        HxExpression wasteForContent = model.array(wasteForContentData);

        // Set decisions: slabs[k] represents the orders in slab k
        slabs = new HxExpression[nbSlabs];
        for (int s = 0; s < nbSlabs; ++s) {
            slabs[s] = model.setVar(nbOrders);
        }

        // Each order must be in one slab and one slab only
        model.constraint(model.partition(slabs));

        for (int s = 0; s < nbSlabs; ++s)  {

            HxExpression orders = slabs[s];

            // The number of colors per slab must not exceed a specified value
            model.constraint(model.leq(model.count(model.distinct(orders, colorLambda)), nbColorsMaxSlab));

            // The content of each slab must not exceed the maximum size of the slab
            slabContent[s] = model.sum(orders, quantitiesLambda);
            model.constraint(model.leq(slabContent[s], maxSize));

            // Wasted steel is computed according to the content of the slab
            wastedSteel[s] = model.at(wasteForContent, slabContent[s]);
        }

        // Minimize the total wasted steel
        totalWastedSteel = model.sum(wastedSteel);
        model.minimize(totalWastedSteel);

        model.close();

        // Parametrize the optimizer
        optimizer.getParam().setTimeLimit(limit);
        optimizer.solve();
    }

    /*
     * Write the solution in a file with the following format:
     * - total wasted steel
     * - number of slabs used
     * - for each slab used, the number of orders in the slab and the list of orders
     */
    private void writeSolution(String fileName) throws IOException {
        try (PrintWriter output = new PrintWriter(new FileWriter(fileName))) {
            output.println(totalWastedSteel.getValue());

            int actualNbSlabs = 0;
            for(int s = 0; s < nbSlabs; ++s) {
                if (slabs[s].getCollectionValue().count() > 0) {
                    actualNbSlabs++;
                }
            }
            output.println(actualNbSlabs);

            for (int s = 0; s < nbSlabs; ++s) {
                int nbOrdersInSlab = slabs[s].getCollectionValue().count();
                if (nbOrdersInSlab == 0) continue;
                output.print(nbOrdersInSlab);

                for(int o = 0;  o < nbOrdersInSlab; ++o) {
                    output.print(" " + (slabs[s].getCollectionValue().get(o) + 1));
                }
                output.println();
            }
        }
    }

    public static void main(String[] args) {
        if (args.length < 1) {
            System.err.println("Usage: java SteelMillSlabDesign inputFile [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()) {
            SteelMillSlabDesign model = new SteelMillSlabDesign(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);
        }
    }
}