Steel mill slab design

Principles learned

  • Define the actual set of decision variables
  • Preprocess the input to create useful data for the model
  • Access an array with an “at” operator at an index that is an expression

Problem

../_images/steel.png

Steel is produced by casting molten iron into slabs. A steel mill can produce a finite number of slab sizes. An order has two properties, a color corresponding to the route required through the steel mill and a weight.

Given input orders, the problem is to assign the orders to slabs, the number and size of which are also to be determined, such that the total weight of steel produced is minimised. In other words we want to minimize the waste of steel (steel produced in addition to the actual sizes of orders). This assignment is subject to two further constraints:

  • Capacity constraints: The total weight of orders assigned to a slab cannot exceed the slab capacity.
  • Color constraints: Each slab can contain at most p of k total colors (p is usually 2). The color constraints arise because it is expensive to cut up slabs in order to send them to different parts of the mill.

For more details, see CSPLib and beCool.

Download the example

Data

The format of the data is as follows:

  • 1st line: number of slab sizes, then the sequence of sizes
  • 2nd line: number of colors
  • 3rd line: number of orders
  • Then for each order: size and color of the order

Program

The model is based on the assignment of orders to slabs. An important point is that we associate no decision variable to the selected size for a slab. Instead, the wasted steel in each slab is automatically determined by its content. The waste is zero when the content fits exactly in one of the possible slab sizes and increases linearly otherwise. We define an array of values wasteForContent representing this saw-tooth profile, which allows a very simple writing of this non-linear relationship: wastedSteel[j] <- wasteForContent[slabContent[j]]. It is pre-computed after the instance data has been read. The slab size being determined by the content, we must still ensure by a constraint that the maximum slab size is not exceeded.

It is a typical example of the importance of defining the right set of decision variables as explained in our quick start guide.

Execution:
localsolver steel_mill_slab_design.lsp inFileName=instances/12orderproblem.in [lsTimeLimit=] [solFileName=]
/********** steel_mill_slab_design.lsp **********/
use io;

/* Reads instance data. */
function input() {
    local usage = "Usage: localsolver steel_mill_slab_design.lsp "
        + "inFileName=inputFile [solFileName=outputFile] [lsTimeLimit=timeLimit]";

    if (inFileName == nil) throw usage;
    
    local inFile = openRead(inFileName);
    nbColorsMaxSlab = 2;

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

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

    ordersByColor[1..nbColors] = {};
    sumSizeOrders = 0;

    for [i in 1..nbOrders] {
        orders[i] = inFile.readInt();
        c = inFile.readInt();
		ordersByColor[c].add(i);
        sumSizeOrders += orders[i];
    }

    preComputeWasteForContent();
}

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;
}

/* Declares the optimization model. */
function model() {
    
    // x[o][s] = 1 if order o is assigned to slab s, 0 otherwise
    x[1..nbOrders][1..nbSlabs] <- bool();
    
    // Each order is assigned to a slab
    for [o in 1..nbOrders] 
        constraint sum[s in 1..nbSlabs](x[o][s]) == 1;
    
    
    // The content of each slab must not exceed the maximum size of the slab
    for [s in 1..nbSlabs] {
        slabContent[s] <- sum[o in 1..nbOrders](orders[o]*x[o][s]);
        constraint slabContent[s] <= maxSize;
    }

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

    // color[c][s] = 1 if the color c in the slab s, 0 otherwise
    color[c in 1..nbColors : count(ordersByColor[c]) > 0][s in 1..nbSlabs] <- or[o in ordersByColor[c]](x[o][s]);

    // The number of colors per slab must not exceed a specified value
    for [s in 1..nbSlabs]
        constraint sum[c in 1..nbColors : count(ordersByColor[c]) > 0](color[c][s]) <= nbColorsMaxSlab;

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

    minimize totalWastedSteel;
}

/* Parameterizes the solver. */
function param() {
    if (lsTimeLimit == nil) lsTimeLimit = 60;
    if (lsNbThreads == nil) lsNbThreads = 4;
    if (lsAnnealingLevel == nil) lsAnnealingLevel = 0;
}

/* Writes 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 ordersBySlabs;
    local solFile = io.openWrite(solFileName);
    solFile.println(totalWastedSteel.value);
    
    local actualNbSlabs = 0;
    for [s in 1..nbSlabs] {
        ordersBySlabs[s] = map[o in 1..nbOrders : x[o][s].value](o);
        if (ordersBySlabs[s].count() > 0) actualNbSlabs = actualNbSlabs + 1;
    }
    solFile.println(actualNbSlabs);
    
    for [s in 1..nbOrders] {
        if (ordersBySlabs[s].count() > 0) {
            solFile.print(ordersBySlabs[s].count() + " ");
            for [o in ordersBySlabs[s].values()] solFile.print(o - 1, " ");
            solFile.println();
        }
    }
}
Execution (Windows)
set PYTHONPATH=%LS_HOME%\bin\python27\
python steel_mill_slab_design.py instances\12orderproblem.in
Execution (Linux)
export PYTHONPATH=/opt/localsolver_XXX/bin/python27/
python steel_mill_slab_design.py instances/12orderproblem.in
########## steel_mill_slab_design.py ##########

import localsolver
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()]


#
# Computes 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 ("Usage: python steel_mill_slab_design.py inputFile [outputFile] [timeLimit]")
            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 localsolver.LocalSolver() as ls:

    #
    # Reads instance data 
    #
    nb_colors_max_slab = 2

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

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

    orders_by_color = [list() for c in range(nb_colors)]
    orders = [None]*nb_orders
    sum_size_orders = 0
    for o in range(nb_orders):
        orders[o] = file_it.next()
        c = file_it.next()
        # Note: colors are in [1..nb_colors]
        orders_by_color[c - 1].append(o)
        sum_size_orders += orders[o]

    waste_for_content = pre_compute_waste_for_content(slab_sizes, sum_size_orders)

    #
    # Declares the optimization model
    #
    model = ls.model

    # x[o, s] = 1 if order o is assigned to slab s, 0 otherwise
    x = [[model.bool() for s in range(nb_slabs)] for o in range(nb_orders)]

    # Each order is assigned to a slab
    for o in range(nb_orders):
        nb_slabs_assigned = model.sum(x[o])
        model.constraint(model.eq(nb_slabs_assigned, 1))

    # The content of each slab must not exceed the maximum size of the slab
    slab_content = [None]*nb_slabs
    for s in range(nb_slabs):
        slab_content[s] = model.sum([orders[o]*x[o][s] for o in range(nb_orders)])
        model.constraint(slab_content[s] <= max_size)

    # Create the LocalSolver array corresponding to the vector waste_for_content
    # (because "at" operators can only access LocalSolver arrays)
    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[slab_content[s]] for s in range(nb_slabs)]

    # color[c][s] = 1 if the color c in the slab s, 0 otherwise
    color = [list() for c in range(nb_colors)]
    for c in range(nb_colors):
        if len(orders_by_color[c]) == 0: continue
        color[c] = [model.or_([x[o][s] for o in orders_by_color[c]]) for s in range(nb_slabs)]

    # The number of colors per slab must not exceed a specified value
    for s in range(nb_slabs):
        nb_colors_slab = model.sum([color[c][s] for c in range(nb_colors) if len(orders_by_color[c]) > 0]);
        model.constraint(nb_colors_slab <= nb_colors_max_slab);

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

    model.close()

    #
    # Parameterizes the solver
    #
    if len(sys.argv) >= 4: ls.create_phase().time_limit = int(sys.argv[3])
    else: ls.create_phase().time_limit = 60
    ls.param.annealing_level = 0
    ls.param.nb_threads = 4

    ls.solve()

    #
    # Writes 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
            orders_by_slabs = [None]*nb_slabs
            for s in range(nb_slabs):
                orders_by_slabs[s] = [o for o in range(nb_orders) if x[o][s].value == 1]
                if len(orders_by_slabs[s]) > 0: actual_nb_slabs += 1
            f.write("%d\n" % actual_nb_slabs)

            for s in range(nb_slabs):
                nb_orders_in_slab = len(orders_by_slabs[s])
                if nb_orders_in_slab == 0: continue
                f.write("%d " % nb_orders_in_slab)
                for i in range(nb_orders_in_slab):
                    f.write("%d " % orders_by_slabs[s][i])
                f.write("\n")
Compilation / Execution (Windows)
cl /EHsc steel_mill_slab_design.cpp -I%LS_HOME%\include /link %LS_HOME%\bin\localsolver.dll.lib
steel_mill_slab_design instances\12orderproblem.in
Compilation / Execution (Linux)
g++ steel_mill_slab_design.cpp -I/opt/localsolver_XXX/include -llocalsolver -lpthread -o steel_mill_slab_design
./steel_mill_slab_design instances/12orderproblem.in
//********* steel_mill_slab_design.cpp *********

#include <iostream>
#include <fstream>
#include <vector>
#include "localsolver.h"

using namespace localsolver;
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 orders for each color 
    vector<vector<int> > ordersByColor;

    // Orders size 
    vector<lsint> orders;

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

    // Solver. 
    LocalSolver localsolver;

    // LS Program variables. 
    vector<vector<LSExpression> > x;

    // Objective 
    LSExpression totalWastedSteel;

    // Reads 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;

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

        preComputeWasteForContent(slabSizes, sumSizeOrders);
    }

private:
    // Computes the vector wasteForContent 
    void preComputeWasteForContent(const vector<int>& slabSizes, int sumSizeOrders) {       
        
        // No waste when a slab is empty.
        wasteForContent.resize(sumSizeOrders, (lsint) 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] = (lsint) (size - content);
            }
            prevSize = size;
        }
    }

public:
    void solve(int limit) {
        // Declares the optimization model. 
        LSModel model = localsolver.getModel();

        // x[o][s] = 1 if order o is assigned to slab s, 0 otherwise
        x.resize(nbOrders);
        for(int o = 0; o < nbOrders; o++) {
            x[o].resize(nbSlabs);
            for(int s = 0; s < nbSlabs; s++) {
                x[o][s] = model.boolVar();
            }
        }

        // Each order is assigned to a slab
        for(int o = 0; o < nbOrders; o++) {
            LSExpression nbSlabsAssigned = model.sum(x[o].begin(), x[o].end());
            model.constraint(nbSlabsAssigned == 1);
        }

        // The content of each slab must not exceed the maximum size of the slab
        vector<LSExpression> slabContent(nbSlabs);
        for(int s = 0; s < nbSlabs; s++) {
            slabContent[s] = model.sum();
            for(int o = 0; o < nbOrders; o++) {
                slabContent[s] += orders[o]*x[o][s];
            }
            model.constraint(slabContent[s] <= maxSize);
        }

        // Create the LocalSolver array corresponding to the vector wasteForContent
        // (because "at" operators can only access LocalSolver arrays)
        LSExpression wasteForContentArray = model.array(wasteForContent.begin(), wasteForContent.end());

        // Wasted steel is computed according to the content of the slab
        vector<LSExpression> wastedSteel(nbSlabs);
        for(int s = 0; s < nbSlabs; s++) {
            wastedSteel[s] = wasteForContentArray[slabContent[s]];
        }

        // color[c][s] = 1 if the color c in the slab s, 0 otherwise
        vector<vector<LSExpression> > color(nbColors);
        for(int c = 0; c < nbColors; c++) {
            color[c].resize(nbSlabs);
            if (ordersByColor[c].size() == 0) continue;
            for(int s = 0; s < nbSlabs; s++) {
                color[c][s] = model.or_();
                for(size_t i = 0; i < ordersByColor[c].size(); i++) {
                    int o = ordersByColor[c][i];
                    color[c][s].addOperand(x[o][s]);
                }
            }
        }

        // The number of colors per slab must not exceed a specified value
        for(int s = 0; s < nbSlabs; s++) {
            LSExpression nbColorsSlab = model.sum();
            for(int c = 0; c < nbColors; c++) {
                if (ordersByColor[c].size() == 0) continue;
                nbColorsSlab += color[c][s];
            }
            model.constraint(nbColorsSlab <= nbColorsMaxSlab);
        }

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

        model.close();

        // Parameterizes the solver. 
        LSPhase phase = localsolver.createPhase();
        phase.setTimeLimit(limit);
        localsolver.getParam().setAnnealingLevel(0);
        localsolver.getParam().setNbThreads(4);

        localsolver.solve();
    }

    // Writes 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;
        vector<vector<int> > ordersBySlabs(nbSlabs);
        for(int s = 0; s < nbSlabs; s++) {
            for(int o = 0; o < nbOrders; o++) {
                if (x[o][s].getValue() == 1) ordersBySlabs[s].push_back(o);
            }
            if (ordersBySlabs[s].size() > 0) actualNbSlabs++;
        }
        outfile << actualNbSlabs << endl;

        for(int s = 0; s < nbSlabs; s++) {
            size_t nbOrdersInSlab = ordersBySlabs[s].size();
            if (nbOrdersInSlab == 0) continue;
            outfile << nbOrdersInSlab << " ";
            for(size_t i = 0; i < nbOrdersInSlab; i++) {
                outfile << ordersBySlabs[s][i] << " ";
            }
            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 << "Error occurred: " << e.what() << endl;
        return 1;
    }
}
Compilation/Execution (Windows)
copy %LS_HOME%\bin\*net.dll .
csc SteelMillSlabDesign.cs /reference:localsolvernet.dll
SteelMillSlabDesign instances\12orderproblem.in
/********** SteelMillSlabDesign.cs **********/

using System;
using System.IO;
using System.Collections.Generic;
using localsolver;

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 orders for each color
    List<int>[] ordersByColor;

    // Orders size
    int[] orders;

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

    // Solver.
    LocalSolver localsolver;

    // LS Program variables.
    LSExpression[,] x;

    // Objective
    LSExpression totalWastedSteel;

    public SteelMillSlabDesign()
    {
        localsolver = new LocalSolver();
    }

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

    // Reads 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;

            ordersByColor = new List<int>[nbColors];
            for (int c = 0; c < nbColors; c++)
            {
                ordersByColor[c] = new List<int>();
            }
            orders = new int[nbOrders];
            int sumSizeOrders = 0;
            for (int o = 0; o < nbOrders; o++)
            {
                splitted = input.ReadLine().Split();
                orders[o] = int.Parse(splitted[0]);
                int c = int.Parse(splitted[1]);
                // Note: colors are in [1..nbColors]
                ordersByColor[c - 1].Add(o);
                sumSizeOrders += orders[o];
            }

            PreComputeWasteForContent(slabSizes, sumSizeOrders);
        }
    }

    // Computes 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)
    {
        // Declares the optimization model.
        LSModel model = localsolver.GetModel();

        // x[o, s] = 1 if order o is assigned to slab s, 0 otherwise
        x = new LSExpression[nbOrders, nbSlabs];
        for (int o = 0; o < nbOrders; o++)
        {
            for (int s = 0; s < nbSlabs; s++)
            {
                x[o, s] = model.Bool();
            }
        }

        // Each order is assigned to a slab
        for (int o = 0; o < nbOrders; o++)
        {
            LSExpression nbSlabsAssigned = model.Sum();
            for (int s = 0; s < nbSlabs; s++)
            {
                nbSlabsAssigned.AddOperand(x[o, s]);
            }
            model.Constraint(nbSlabsAssigned == 1);
        }

        // The content of each slab must not exceed the maximum size of the slab
        LSExpression[] slabContent = new LSExpression[nbSlabs];
        for (int s = 0; s < nbSlabs; s++)
        {
            slabContent[s] = model.Sum();
            for (int o = 0; o < nbOrders; o++)
            {
                slabContent[s].AddOperand(orders[o] * x[o, s]);
            }
            model.Constraint(slabContent[s] <= maxSize);
        }

        // Create the LocalSolver array corresponding to the vector wasteForContent
        // (because "at" operators can only access LocalSolver arrays)
        LSExpression wasteForContentArray = model.Array(wasteForContent);

        // Wasted steel is computed according to the content of the slab
        LSExpression[] wastedSteel = new LSExpression[nbSlabs];
        for (int s = 0; s < nbSlabs; s++)
        {
            wastedSteel[s] = wasteForContentArray[slabContent[s]];
        }

        // color[c][s] = 1 if the color c in the slab s, 0 otherwise
        LSExpression[,] color = new LSExpression[nbColors, nbSlabs];
        for (int c = 0; c < nbColors; c++)
        {
            if (ordersByColor[c].Count == 0) continue;
            for (int s = 0; s < nbSlabs; s++)
            {
                color[c, s] = model.Or();
                for (int i = 0; i < ordersByColor[c].Count; i++)
                {
                    int o = ordersByColor[c][i];
                    color[c, s].AddOperand(x[o, s]);
                }
            }
        }

        // The number of colors per slab must not exceed a specified value
        for (int s = 0; s < nbSlabs; s++)
        {
            LSExpression nbColorsSlab = model.Sum();
            for (int c = 0; c < nbColors; c++)
            {
                if (ordersByColor[c].Count == 0) continue;
                nbColorsSlab.AddOperand(color[c, s]);
            }
            model.Constraint(nbColorsSlab <= nbColorsMaxSlab);
        }

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

        model.Close();

        // Parameterizes the solver.
        LSPhase phase = localsolver.CreatePhase();
        phase.SetTimeLimit(limit);
        localsolver.GetParam().SetAnnealingLevel(0);
        localsolver.GetParam().SetNbThreads(4);

        localsolver.Solve();
    }

    // Writes 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;
            List<int>[] ordersBySlabs = new List<int>[nbSlabs];
            for (int s = 0; s < nbSlabs; s++)
            {
                ordersBySlabs[s] = new List<int>();
                for (int o = 0; o < nbOrders; o++)
                {
                    if (x[o, s].GetValue() == 1) ordersBySlabs[s].Add(o);
                }
                if (ordersBySlabs[s].Count > 0) actualNbSlabs++;
            }
            output.WriteLine(actualNbSlabs);

            for (int s = 0; s < nbSlabs; s++)
            {
                int nbOrdersInSlab = ordersBySlabs[s].Count;
                if (nbOrdersInSlab == 0) continue;
                output.Write(nbOrdersInSlab + " ");
                for (int i = 0; i < nbOrdersInSlab; i++)
                {
                    output.Write(ordersBySlabs[s][i] + " ");
                }
                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 %LS_HOME%\bin\localsolver.jar
java -cp %LS_HOME%\bin\localsolver.jar;. SteelMillSlabDesign instances\12orderproblem.in
Compilation/Execution (Linux)
javac SteelMillSlabDesign.java -cp /opt/localsolver_XXX/bin/localsolver.jar
java -cp /opt/localsolver_XXX/bin/localsolver.jar:. SteelMillSlabDesign instances/12orderproblem.in
/********** SteelMillSlabDesign.java **********/

import java.util.*;
import java.io.*;
import localsolver.*;

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 orders for each color
    private ArrayList<ArrayList<Integer>> ordersByColor;

    // Orders size
    private int[] orders;

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

    // Solver.
    private LocalSolver localsolver;

    // LS Program variables.
    private LSExpression[][] x;

    // Objective
    private LSExpression totalWastedSteel;

    // Reads instance data.
    private void readInstance(String fileName) throws IOException {
        try (Scanner input = new Scanner(new File(fileName))) {
            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;

            ordersByColor = new ArrayList<ArrayList<Integer>>(nbColors);
            for (int c = 0; c < nbColors; c++) {
                ordersByColor.add(new ArrayList<Integer>());
            }
            orders = new int[nbOrders];
            int sumSizeOrders = 0;
            for (int o = 0; o < nbOrders; o++) {
                orders[o] = input.nextInt();
                int c = input.nextInt();
                // Note: colors are in [1..nbColors]
                ordersByColor.get(c - 1).add(o);
                sumSizeOrders += orders[o];
            }

            preComputeWasteForContent(slabSizes, sumSizeOrders);
        }
    }

    private void preComputeWasteForContent(int[] slabSizes, int sumSizeOrders) {
        
        wasteForContent = 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++) {
                wasteForContent[content] = size - content;
            }
            prevSize = size;
        }
    }

    private void solve(int limit) {
        localsolver = new LocalSolver();

        // Declares the optimization model.
        LSModel model = localsolver.getModel();

        // x[o][s] = 1 if order o is assigned to slab s, 0 otherwise
        x = new LSExpression[nbOrders][nbSlabs];
        for (int o = 0; o < nbOrders; o++) {
            for (int s = 0; s < nbSlabs; s++) {
                x[o][s] = model.boolVar();
            }
        }

        // Each order is assigned to a slab
        for (int o = 0; o < nbOrders; o++) {
            LSExpression nbSlabsAssigned = model.sum(x[o]);
            model.constraint(model.eq(nbSlabsAssigned, 1));
        }

        // The content of each slab must not exceed the maximum size of the slab
        LSExpression[] slabContent = new LSExpression[nbSlabs];
        for (int s = 0; s < nbSlabs; s++) {
            slabContent[s] = model.sum();
            for (int o = 0; o < nbOrders; o++) {
                slabContent[s].addOperand(model.prod(orders[o], x[o][s]));
            }
            model.constraint(model.leq(slabContent[s], maxSize));
        }

        // Create the LocalSolver array corresponding to the vector wasteForContent
        // (because "at" operators can only access LocalSolver arrays)
        LSExpression wasteForContentArray = model.array(wasteForContent);

        // Wasted steel is computed according to the content of the slab
        LSExpression[] wastedSteel = new LSExpression[nbSlabs];
        for (int s = 0; s < nbSlabs; s++) {
            wastedSteel[s] = model.at(wasteForContentArray, slabContent[s]);
        }

        // color[c][s] = 1 if the color c in the slab s, 0 otherwise
        LSExpression[][] color = new LSExpression[nbColors][nbSlabs];
        for (int c = 0; c < nbColors; c++) {
            if (ordersByColor.get(c).size() == 0) continue;
            for (int s = 0; s < nbSlabs; s++) {
                color[c][s] = model.or();
                for (int i = 0; i < ordersByColor.get(c).size(); i++) {
                    int o = ordersByColor.get(c).get(i);
                    color[c][s].addOperand(x[o][s]);
                }
            }
        }

        // The number of colors per slab must not exceed a specified value
        for (int s = 0; s < nbSlabs; s++) {
            LSExpression nbColorsSlab = model.sum();
            for (int c = 0; c < nbColors; c++) {
                if (ordersByColor.get(c).size() == 0) continue;
                nbColorsSlab.addOperand(color[c][s]);
            }
            model.constraint(model.leq(nbColorsSlab, nbColorsMaxSlab));
        }

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

        model.close();

        // Parameterizes the solver.
        LSPhase phase = localsolver.createPhase();
        phase.setTimeLimit(limit);
        localsolver.getParam().setAnnealingLevel(0);
        localsolver.getParam().setNbThreads(4);

        localsolver.solve();
    }

    // Writes 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;
            ArrayList<ArrayList<Integer>> ordersBySlabs = new ArrayList<ArrayList<Integer>>(nbSlabs);
            for (int s = 0; s < nbSlabs; s++) {
                ordersBySlabs.add(new ArrayList<Integer>());
                for (int o = 0; o < nbOrders; o++) {
                    if (x[o][s].getValue() == 1)
                        ordersBySlabs.get(s).add(o);
                }
                if (ordersBySlabs.get(s).size() > 0)
                    actualNbSlabs++;
            }
            output.println(actualNbSlabs);

            for (int s = 0; s < nbSlabs; s++) {
                int nbOrdersInSlab = ordersBySlabs.get(s).size();
                if (nbOrdersInSlab == 0) continue;
                output.print(nbOrdersInSlab + " ");
                for (int i = 0; i < nbOrdersInSlab; i++) {
                    output.print(ordersBySlabs.get(s).get(i) + " ");
                }
                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 {
            SteelMillSlabDesign model = new SteelMillSlabDesign();
            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);
        }
    }
}