Car Sequencing Color

Principles learned

  • Differenciate structural constraints from first priority objectives

  • Use a list variable

  • Set an initial solution

  • Create and access arrays with an “at” operator

Problem

../_images/car_seq_color.svg

The Car Sequencing Problem with Paint-Shop Batching Constraints was initially submitted by the car manufacturer Renault at the 2005 Challenge of the French Society of Operations Research and Decision Support (ROADEF). The problem is to schedule the production of cars along production lines while respecting the assembly line and paint shop requirements. Cars with the most complicated options must be evenly distributed throughout the total processed cars. Spray guns must be washed with paint solvent regularly and in-between two different car colors.

Download the example


Data

The format of the data files is as follows:

  • 1st line: number of cars; number of options; number of classes; maximum paint batch size; objective order; start position for planification.

  • For each option: the maximum number of cars with that option in a block; the size of the block; whether or not this option is of high priority.

  • For each class: color; no. of cars in this class; for each option, whether or not this class requires it (1 or 0).

  • For each position: the class initially planned to be produced

For more details, see the challenge website.

Program

The difference between this problem and the original Car Sequencing Problem is the consideration of car colors along the line. Indeed, it is required that the spray guns be washed often enough and between two different colors. This requirement is modeled with an or function stating that at the color must change at least one every k positions, with k being the paint batch limit.

Just like in the original problem, this problem is a satisfactory problem, with non structural constraints. We define three objectives, which correspond to the degree of violations of the following requirements:

  • Window capacity for high priority options

  • Window capacity for low priority options

  • Paint batch size

In the data, an objective order is given, traducing the order of importance between these three objectives.

Execution:
localsolver car_sequencing_color.lsp inFileName=instances/022_3_4_EP_RAF_ENP.in [lsTimeLimit=] [solFileName=]
/********** car_sequencing_color.lsp **********/

use io;

/* Reads instance data. */
function input() {
    COLOR_HIGH_LOW = 0;
    HIGH_LOW_COLOR = 1;
    HIGH_COLOR_LOW = 2;
    COLOR_HIGH = 3;
    HIGH_COLOR = 4;
    
    local usage = "Usage: localsolver car_sequencing_color.lsp "
            + "inFileName=inputFile [solFileName=outputFile] [lsTimeLimit=timeLimit]";

    if (inFileName == nil) throw usage;
    local inFile = io.openRead(inFileName);
    nbPositions = inFile.readInt();
    nbOptions = inFile.readInt();
    nbClasses = inFile.readInt();
    paintBatchLimit = inFile.readInt();
    objectiveOrder = inFile.readInt();
    startPosition = inFile.readInt();

    hasLowPriorityOptions = false;
    for[o in 0..nbOptions-1] {
        maxCarsPerWindow[o] = inFile.readInt();
        windowSize[o] = inFile.readInt();
        isPriorityOption[o] = (inFile.readInt() == 1);
        if (!isPriorityOption[o]) hasLowPriorityOptions = true;
    }

    if (!hasLowPriorityOptions) {
        if (objectiveOrder == COLOR_HIGH_LOW) objectiveOrder = COLOR_HIGH;
        else if (objectiveOrder == HIGH_COLOR_LOW) objectiveOrder = HIGH_COLOR;
        else if (objectiveOrder == HIGH_LOW_COLOR) objectiveOrder = HIGH_COLOR;
    }
    
    for[c in 0..nbClasses-1] {
        colorClass[c] = inFile.readInt();
        nbCars[c] = inFile.readInt();
        options[c][o in 0..nbOptions-1] = (inFile.readInt()==1);
    }
    
    initialSequence[p in 0..nbPositions-1] = inFile.readInt();
}

/* Declares the optimization model. */
function model() {
    // sequence[i] = j if class initially planned on position j is produced on position i
    sequence <- list(nbPositions);
    
    // sequence is a permutation of the initial production plan, all indexes must appear exactly once
    constraint partition(sequence);

    // Past classes (before startPosition) can not move
    for[p in 0..startPosition-1]
        constraint sequence[p] == p;

    // Number of cars with option o in each window
    nbCarsWindows[o in 0..nbOptions-1][j in startPosition-windowSize[o]+1..nbPositions-1] 
            <- sum[k in 0..windowSize[o]-1 : j + k >= 0 && j + k < nbPositions]
            (options[initialSequence[sequence[j + k]]][o]);
    
    // Number of violations of option o capacity in each window
    nbViolationsWindows[o in 0..nbOptions-1] 
            <- sum[j in startPosition-windowSize[o]+1..nbPositions-1]
            (max(0, nbCarsWindows[o][j] - maxCarsPerWindow[o]));

    objectiveHighPriority <- sum[o in 0..nbOptions-1 : isPriorityOption[o]](nbViolationsWindows[o]);
    objectiveLowPriority <- sum[o in 0..nbOptions-1 : !isPriorityOption[o]](nbViolationsWindows[o]);

    // Color change between position p and position p+1
    colorChange[p in startPosition-1..nbPositions-2] 
            <- colorClass[initialSequence[sequence[p]]] != colorClass[initialSequence[sequence[p+1]]];
    objectiveColor <- sum[p in startPosition-1..nbPositions-2](colorChange[p]);

    // Paint limit constraints : at least one change every paintBatchLimit positions
    for[p in startPosition..nbPositions-paintBatchLimit-2]
        constraint or[p2 in 0..paintBatchLimit-1](colorChange[p + p2]);

    if(objectiveOrder == COLOR_HIGH_LOW) {
        obj[0] <- objectiveColor;
        obj[1] <- objectiveHighPriority;
        obj[2] <- objectiveLowPriority;
    } else if(objectiveOrder == HIGH_COLOR_LOW) {
        obj[0] <- objectiveHighPriority;
        obj[1] <- objectiveColor;
        obj[2] <- objectiveLowPriority;
    } else if(objectiveOrder == HIGH_LOW_COLOR) {
        obj[0] <- objectiveHighPriority;
        obj[1] <- objectiveLowPriority;
        obj[2] <- objectiveColor;       
    } else if(objectiveOrder == COLOR_HIGH) {
        obj[0] <- objectiveColor;
        obj[1] <- objectiveHighPriority;
    } else if(objectiveOrder == HIGH_COLOR) {
        obj[0] <- objectiveHighPriority;
        obj[1] <- objectiveColor;
    }
    
    for[o in obj] minimize o;
}

/* Parameterizes the solver. */
function param() {
    sequence.value.clear();
    for [p in 0..nbPositions-1]
        sequence.value.add(p);
    if (lsTimeLimit == nil) lsTimeLimit = 60;
}

/* Writes the solution in a file following the following format: 
 * - 1st line: value of the objectives;
 * - 2nd line: for each position p, index of class at positions p. */
function output() {
    if (solFileName == nil) return;
    local solFile = io.openWrite(solFileName);
    solFile.print(objectiveColor.value, " ");
    solFile.print(objectiveHighPriority.value, " ");
    solFile.println(objectiveLowPriority.value);
    local listSolution = sequence.value;
    for [p in 0..nbPositions-1]
        solFile.print(initialSequence[listSolution[p]], " ");
    solFile.println();
}
Execution (Windows)
set PYTHONPATH=%LS_HOME%\bin\python
python car_sequencing_color.py instances\022_3_4_EP_RAF_ENP.in
Execution (Linux)
export PYTHONPATH=/opt/localsolver_11_0/bin/python
python car_sequencing_color.py instances/022_3_4_EP_RAF_ENP.in
########## car_sequencing_color.py ##########

import localsolver
import sys

COLOR_HIGH_LOW = 0
HIGH_LOW_COLOR = 1
HIGH_COLOR_LOW = 2
COLOR_HIGH = 3
HIGH_COLOR = 4

if len(sys.argv) < 2:
    print("Usage: python car_sequencing_color.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()]


with localsolver.LocalSolver() as ls:
    #
    # Reads instance data
    #

    file_it = iter(read_integers(sys.argv[1]))
    nb_positions = next(file_it)
    nb_options = next(file_it)
    nb_classes = next(file_it)
    paint_batch_limit = next(file_it)
    objective_order = next(file_it)
    start_position = next(file_it)

    max_cars_per_window = []
    window_size = []
    is_priority_option = []
    has_low_priority_options = False

    for o in range(nb_options):
        max_cars_per_window.append(next(file_it))
        window_size.append(next(file_it))
        is_prio = next(file_it) == 1
        is_priority_option.append(is_prio)
        if (not is_prio):
            has_low_priority_options = True

    if (not has_low_priority_options):
        if (objective_order == COLOR_HIGH_LOW):
            objective_order = COLOR_HIGH
        elif (objective_order == HIGH_COLOR_LOW):
            objective_order = HIGH_COLOR
        elif (objective_order == HIGH_LOW_COLOR):
            objective_order = HIGH_COLOR

    color_class = []
    nb_cars = []
    options = []

    for c in range(nb_classes):
        color_class.append(next(file_it))
        nb_cars.append(next(file_it))
        options.append([next(file_it) == 1 for i in range(nb_options)])

    initial_sequence = [next(file_it) for p in range(nb_positions)]

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

    # sequence[i] = j if class initially planned on position j is produced on position i
    sequence = model.list(nb_positions)

    # sequence is a permutation of the initial production plan, all indexes must appear exactly once
    model.constraint(model.partition(sequence))

    # Past classes (before startPosition) can not move
    [model.constraint(sequence[p] == p) for p in range(start_position)]

    # Create LocalSolver arrays to be able to access them with "at" operators
    initial_array = model.array(initial_sequence)
    color_array = model.array(color_class)
    option_array = model.array(options)

    # Number of cars with option o in each window
    nb_cars_windows = [None] * nb_options
    for o in range(nb_options):
        nb_cars_windows[o] = [None] * nb_positions
        for j in range(start_position - window_size[o] + 1, nb_positions):
            nb_cars_windows[o][j] = model.sum()
            for k in range(window_size[o]):
                if (j + k >= 0 and j + k < nb_positions):
                    class_at_position = initial_array[sequence[j + k]]
                    nb_cars_windows[o][j].add_operand(model.at(option_array, class_at_position, o))

    # Number of violations of option o capacity in each window
    objective_high_priority = model.sum()
    if (has_low_priority_options):
        objective_low_priority = model.sum()

    for o in range(nb_options):
        nb_violations_windows = model.sum(
            model.max(
                nb_cars_windows[o][p] - max_cars_per_window[o], 0)
                for p in range(start_position - window_size[o] + 1, nb_positions))
        if (is_priority_option[o]):
            objective_high_priority.add_operand(nb_violations_windows)
        else:
            objective_low_priority.add_operand(nb_violations_windows)

    # Color change between position p and position p+1
    color_change = [None] * (nb_positions - 1)
    objective_color = model.sum()
    for p in range(start_position - 1, nb_positions - 1):
        current_class = initial_array[sequence[p]]
        next_class = initial_array[sequence[p + 1]]
        color_change[p] = color_array[current_class] != color_array[next_class]
        objective_color.add_operand(color_change[p])
    
    # Paint limit constraints : at least one change every paintBatchLimit positions
    for p in range(start_position, nb_positions - paint_batch_limit - 1):
        node_or = model.or_(color_change[p + p2] for p2 in range(paint_batch_limit))
        model.constraint(node_or)

    # Declare the objectives in the correct order
    if (objective_order == COLOR_HIGH_LOW):
        model.minimize(objective_color)
        model.minimize(objective_high_priority)
        model.minimize(objective_low_priority)
    elif (objective_order == HIGH_COLOR_LOW):
        model.minimize(objective_high_priority)
        model.minimize(objective_color)
        model.minimize(objective_low_priority)
    elif (objective_order == HIGH_LOW_COLOR):
        model.minimize(objective_high_priority)
        model.minimize(objective_low_priority)
        model.minimize(objective_color)
    elif (objective_order == COLOR_HIGH):
        model.minimize(objective_color)
        model.minimize(objective_high_priority)
    elif (objective_order == HIGH_COLOR):
        model.minimize(objective_high_priority)
        model.minimize(objective_color)

    model.close()

    #
    # Set the initial solution
    #
    sequence.get_value().clear()
    for p in range(nb_positions):
        sequence.get_value().add(p)

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

    ls.solve()

    #
    # Writes the solution in a file
    #
    if len(sys.argv) >= 3:
        # Writes the solution in a file following the following format:
        # * 1st line: value of the objectives;
        # * 2nd line: for each position p, index of class at positions p.
        with open(sys.argv[2], 'w') as f:
            f.write("%d " % objective_color.value)
            f.write("%d " % objective_high_priority.value)
            f.write("%d\n" % objective_low_priority.value)
            for p in range(nb_positions):
                f.write("%d " % sequence.value[p])

            f.write("\n")
Compilation / Execution (Windows)
cl /EHsc car_sequencing_color.cpp -I%LS_HOME%\include /link %LS_HOME%\bin\localsolver110.lib
car_sequencing_color instances\022_3_4_EP_RAF_ENP.in
Compilation / Execution (Linux)
g++ car_sequencing_color.cpp -I/opt/localsolver_11_0/include -llocalsolver110 -lpthread -o car_sequencing_color
./car_sequencing_color instances/022_3_4_EP_RAF_ENP.in
//********* car_sequencing_color.cpp *********

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

using namespace localsolver;
using namespace std;

#define COLOR_HIGH_LOW (0)
#define HIGH_LOW_COLOR (1)
#define HIGH_COLOR_LOW (2)
#define COLOR_HIGH (3)
#define HIGH_COLOR (4)

class CarSequencingColor {
public:

    // Number of vehicles. 
    int nbPositions;

    // Number of options. 
    int nbOptions;

    // Number of classes. 
    int nbClasses;

    // Paint batch limit.
    int paintBatchLimit;

    // Objective order.
    int objectiveOrder;

    // Start position.
    int startPosition;

    // Options properties. 
    vector<lsint> maxCarsPerWindow;
    vector<lsint> windowSize;
    vector<bool> isPriorityOption;
    bool hasLowPriorityOptions;

    // Classes properties. 
    vector<lsint> colorClass;
    vector<lsint> nbCars;
    vector<vector<bool> > options;

    // Initial sequence.
    vector<lsint> initialSequence;

    // Solver. 
    LocalSolver localsolver;

    // LS Program variable. 
    LSExpression sequence;

    // Objectives
    LSExpression objectiveColor;
    LSExpression objectiveHighPriority;
    LSExpression objectiveLowPriority;

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

        infile >> nbPositions;
        infile >> nbOptions;
        infile >> nbClasses;
        infile >> paintBatchLimit;
        infile >> objectiveOrder;
        infile >> startPosition;

        maxCarsPerWindow.resize(nbOptions);
        windowSize.resize(nbOptions);
        isPriorityOption.resize(nbOptions);
        hasLowPriorityOptions = false;
        for (int o = 0; o < nbOptions; o++) {
            infile >> maxCarsPerWindow[o];
            infile >> windowSize[o];
            int tmp;
            infile >> tmp;
            isPriorityOption[o] = (tmp == 1);
            if (!isPriorityOption[o]) hasLowPriorityOptions = true;
        }

        if (!hasLowPriorityOptions) {
            if (objectiveOrder == COLOR_HIGH_LOW) objectiveOrder = COLOR_HIGH;
            else if (objectiveOrder == HIGH_COLOR_LOW) objectiveOrder = HIGH_COLOR;
            else if (objectiveOrder == HIGH_LOW_COLOR) objectiveOrder = HIGH_COLOR;
        }

        options.resize(nbClasses);
        nbCars.resize(nbClasses);
        colorClass.resize(nbClasses);
        for (int c = 0; c < nbClasses; c++) {
            infile >> colorClass[c];
            infile >> nbCars[c];
            options[c].resize(nbOptions);
            for (int o = 0; o < nbOptions; o++) {
                int v;
                infile >> v;
                options[c][o] = (v == 1);
            }
        }

        initialSequence.resize(nbPositions);
        for (int p = 0; p < nbPositions; p++)
            infile >> initialSequence[p];
    }

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

        // sequence[i] = j if class initially planned on position j is produced on position i
        sequence = model.listVar(nbPositions);

        // sequence is a permutation of the initial production plan, all indexes must appear exactly once
        model.addConstraint(model.partition(sequence));

        // Past classes (before startPosition) can not move
        for (int p = 0; p < startPosition; p++)
            model.addConstraint(sequence[p] == p);

        // Create LocalSolver arrays to be able to access them with "at" operators
        LSExpression initialArray = model.array(initialSequence.begin(), initialSequence.end());
        LSExpression colorArray = model.array(colorClass.begin(), colorClass.end());
        LSExpression optionArray = model.array();
        for (int c = 0; c < nbClasses; c++) {
            LSExpression classOptions = model.array(options[c].begin(), options[c].end());
            optionArray.addOperand(classOptions);
        }

        // Number of cars with option o in each window
        vector<vector<LSExpression>> nbCarsWindows;
        nbCarsWindows.resize(nbOptions);
        for (int o = 0; o < nbOptions; o++) {
            nbCarsWindows[o].resize(nbPositions);
            for (int j = startPosition - windowSize[o] + 1; j < nbPositions; j++) {
                nbCarsWindows[o][j] = model.sum();
                for (int k = 0; k < windowSize[o]; k++) {
                    if (j + k >= 0 && j + k < nbPositions) {
                        LSExpression classAtPosition = initialArray[sequence[j + k]];
                        nbCarsWindows[o][j].addOperand(model.at(optionArray, classAtPosition, o));
                    }
                }
            }
        }

        // Number of violations of option o capacity in each window
        objectiveHighPriority = model.sum();
        if (hasLowPriorityOptions) objectiveLowPriority = model.sum();
        for (int o = 0; o < nbOptions; o++) {
            LSExpression nbViolationsWindows = model.sum();
            for (int j = startPosition - windowSize[o] + 1; j < nbPositions; j++) {
                nbViolationsWindows.addOperand(model.max(0, nbCarsWindows[o][j] - maxCarsPerWindow[o]));
            }
            if (isPriorityOption[o])
                objectiveHighPriority.addOperand(nbViolationsWindows);
            else
                objectiveLowPriority.addOperand(nbViolationsWindows);
        }

        // Color change between position p and position p+1
        vector<LSExpression> colorChange;
        colorChange.resize(nbPositions - 1);
        objectiveColor = model.sum();
        for (int p = startPosition - 1; p < nbPositions - 1; p++) {
            LSExpression currentClass = initialArray[sequence[p]];
            LSExpression nextClass = initialArray[sequence[p+1]];
            colorChange[p] = colorArray[currentClass] != colorArray[nextClass];
            objectiveColor.addOperand(colorChange[p]);
        }

        // Paint limit constraints : at least one change every paintBatchLimit positions
        for (int p = startPosition; p < nbPositions - paintBatchLimit - 1; p++) {
            LSExpression nodeOr = model.or_();
            for (int p2 = 0; p2 < paintBatchLimit; p2++)
                nodeOr.addOperand(colorChange[p + p2]);
            model.addConstraint(nodeOr);
        }

        // Declare the objectives in the correct order
        switch (objectiveOrder) {
            case COLOR_HIGH_LOW:
                model.minimize(objectiveColor);
                model.minimize(objectiveHighPriority);
                model.minimize(objectiveLowPriority);
                break;
            case HIGH_COLOR_LOW:
                model.minimize(objectiveHighPriority);
                model.minimize(objectiveColor);
                model.minimize(objectiveLowPriority);
                break;
            case HIGH_LOW_COLOR:
                model.minimize(objectiveHighPriority);
                model.minimize(objectiveLowPriority);
                model.minimize(objectiveColor);
                break;
            case COLOR_HIGH:
                model.minimize(objectiveColor);
                model.minimize(objectiveHighPriority);
                break;
            case HIGH_COLOR:
                model.minimize(objectiveHighPriority);
                model.minimize(objectiveColor);
                break;
        }

        model.close();

        // Set the initial solution
        sequence.getCollectionValue().clear();
        for (int p = 0; p < nbPositions; p++)
            sequence.getCollectionValue().add(p);

        // Parameterizes the solver. 
        localsolver.getParam().setTimeLimit(limit);

        localsolver.solve();
    }

    // Writes the solution in a file following the following format: 
    // - 1st line: value of the objectives;
    // - 2nd line: for each position p, index of class at positions p.
    void writeSolution(const string& fileName) {
        ofstream outfile;
        outfile.exceptions(ofstream::failbit | ofstream::badbit);
        outfile.open(fileName.c_str());

        outfile << objectiveColor.getValue() << " ";
        outfile << objectiveHighPriority.getValue() << " ";
        outfile << objectiveLowPriority.getValue() << endl;
        for (int p = 0; p < nbPositions; p++) {
            outfile << initialSequence[sequence.getCollectionValue().get(p)] << " ";
        }
        outfile << endl;
    }
};

int main(int argc, char** argv) {
    if (argc < 2) {
        cerr << "Usage: car_sequencing_color 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 {
        CarSequencingColor 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 %LS_HOME%\bin\localsolvernet.dll .
csc CarSequencingColor.cs /reference:localsolvernet.dll
CarSequencingColor instances\022_3_4_EP_RAF_ENP.in
/********** CarSequencingColor.cs **********/

using System;
using System.IO;
using localsolver;

public class CarSequencingColor : IDisposable
{
    // Constantes for objective order
    const int COLOR_HIGH_LOW = 0;
    const int HIGH_LOW_COLOR = 1;
    const int HIGH_COLOR_LOW = 2;
    const int COLOR_HIGH = 3;
    const int HIGH_COLOR = 4;

    // Number of vehicles
    int nbPositions;

    // Number of options. 
    int nbOptions;

    // Number of classes. 
    int nbClasses;

    // Paint batch limit.
    int paintBatchLimit;

    // Objective order.
    int objectiveOrder;

    // Start position.
    int startPosition;

    // Options properties. 
    int[] maxCarsPerWindow;
    int[] windowSize;
    bool[] isPriorityOption;
    bool hasLowPriorityOptions;

    // Classes properties.
    int[] colorClass;
    int[] nbCars;
    int[][] options;

    // Initial sequence.
    int[] initialSequence;

    // Solver. 
    LocalSolver localsolver;

    // LS Program variable. 
    LSExpression sequence;

    // Objectives.
    LSExpression objectiveColor;
    LSExpression objectiveHighPriority;
    LSExpression objectiveLowPriority;

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

    // Reads instance data.
    void ReadInstance(string fileName)
    {
        using (StreamReader input = new StreamReader(fileName))
        {
            string[] splitted = input.ReadLine().Split(' ');
            nbPositions = int.Parse(splitted[0]);
            nbOptions = int.Parse(splitted[1]);
            nbClasses = int.Parse(splitted[2]);
            paintBatchLimit = int.Parse(splitted[3]);
            objectiveOrder = int.Parse(splitted[4]);
            startPosition = int.Parse(splitted[5]);

            maxCarsPerWindow = new int[nbOptions];
            windowSize = new int[nbOptions];
            isPriorityOption = new bool[nbOptions];
            hasLowPriorityOptions = false;

            for (int o = 0; o < nbOptions; o++)
            {
                splitted = input.ReadLine().Split(' ');
                maxCarsPerWindow[o] = int.Parse(splitted[0]);
                windowSize[o] = int.Parse(splitted[1]);
                isPriorityOption[o] = int.Parse(splitted[2]) == 1;
                if (!isPriorityOption[o]) hasLowPriorityOptions = true;
            }

            if (!hasLowPriorityOptions)
            {
                if (objectiveOrder == COLOR_HIGH_LOW) objectiveOrder = COLOR_HIGH;
                else if (objectiveOrder == HIGH_COLOR_LOW) objectiveOrder = HIGH_COLOR;
                else if (objectiveOrder == HIGH_LOW_COLOR) objectiveOrder = HIGH_COLOR;
            }

            options = new int[nbClasses][];
            nbCars = new int[nbClasses];
            colorClass = new int[nbClasses];

            for (int c = 0; c < nbClasses; c++)
            {
                splitted = input.ReadLine().Split(' ');
                colorClass[c] = int.Parse(splitted[0]);
                nbCars[c] = int.Parse(splitted[1]);
                options[c] = new int[nbOptions];
                for (int o = 0; o < nbOptions; o++)
                {
                    int v = int.Parse(splitted[o + 2]);
                    options[c][o] = (v == 1) ? 1 : 0;
                }
            }

            initialSequence = new int[nbPositions];
            for (int p = 0; p < nbPositions; p++)
            {
                initialSequence[p] = int.Parse(input.ReadLine());
            }
        }
    }

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

    void Solve(int limit)
    {
        localsolver = new LocalSolver();

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

        // sequence[i] = j if class initially planned on position j is produced on position i
        sequence = model.List(nbPositions);

        // sequence is a permutation of the initial production plan, all indexes must appear exactly once
        model.Constraint(model.Partition(sequence));

        // Past classes (before startPosition) can not move
        for (int p = 0; p < startPosition; p++)
            model.Constraint(sequence[p] == p);

        // Create LocalSolver arrays to be able to access them with "at" operators
        LSExpression initialArray = model.Array(initialSequence);
        LSExpression colorArray = model.Array(colorClass);
        LSExpression optionArray = model.Array(options);

        // Number of cars with option o in each window
        LSExpression[][] nbCarsWindows = new LSExpression[nbOptions][];
        for (int o = 0; o < nbOptions; o++)
        {
            nbCarsWindows[o] = new LSExpression[nbPositions];
            for (int j = startPosition - windowSize[o] + 1; j < nbPositions; j++)
            {
                nbCarsWindows[o][j] = model.Sum();
                for (int k = 0; k < windowSize[o]; k++)
                {
                    if (j + k >= 0 && j + k < nbPositions)
                    {
                        LSExpression classAtPosition = initialArray[sequence[j + k]];
                        nbCarsWindows[o][j].AddOperand(optionArray[classAtPosition, o]);
                    }
                }
            }
        }

        // Number of violations of option o capacity in each window
        objectiveHighPriority = model.Sum();
        if (hasLowPriorityOptions) objectiveLowPriority = model.Sum();
        for (int o = 0; o < nbOptions; o++)
        {
            LSExpression nbViolationsWindows = model.Sum();
            for (int j = startPosition - windowSize[o] + 1; j < nbPositions; j++)
            {
                nbViolationsWindows.AddOperand(model.Max(0, nbCarsWindows[o][j] - maxCarsPerWindow[o]));
            }
            if (isPriorityOption[o])
                objectiveHighPriority.AddOperand(nbViolationsWindows);
            else
                objectiveLowPriority.AddOperand(nbViolationsWindows);
        }

        // Color change between position p and position p+1
        LSExpression[] colorChange = new LSExpression[nbPositions - 1];
        objectiveColor = model.Sum();
        for (int p = startPosition - 1; p < nbPositions - 1; p++)
        {
            LSExpression currentClass = initialArray[sequence[p]];
            LSExpression nextClass = initialArray[sequence[p + 1]];
            colorChange[p] = colorArray[currentClass] != colorArray[nextClass];
            objectiveColor.AddOperand(colorChange[p]);
        }

        // Paint limit constraints : at least one change every paintBatchLimit positions
        for (int p = startPosition; p < nbPositions - paintBatchLimit - 1; p++)
        {
            LSExpression nodeOr = model.Or();
            for (int p2 = 0; p2 < paintBatchLimit; p2++)
                nodeOr.AddOperand(colorChange[p + p2]);
            model.Constraint(nodeOr);
        }

        // Declare the objectives in the correct order
        switch (objectiveOrder)
        {
            case COLOR_HIGH_LOW:
                model.Minimize(objectiveColor);
                model.Minimize(objectiveHighPriority);
                model.Minimize(objectiveLowPriority);
                break;
            case HIGH_COLOR_LOW:
                model.Minimize(objectiveHighPriority);
                model.Minimize(objectiveColor);
                model.Minimize(objectiveLowPriority);
                break;
            case HIGH_LOW_COLOR:
                model.Minimize(objectiveHighPriority);
                model.Minimize(objectiveLowPriority);
                model.Minimize(objectiveColor);
                break;
            case COLOR_HIGH:
                model.Minimize(objectiveColor);
                model.Minimize(objectiveHighPriority);
                break;
            case HIGH_COLOR:
                model.Minimize(objectiveHighPriority);
                model.Minimize(objectiveColor);
                break;
        }

        model.Close();

        // Set the initial solution
        sequence.GetCollectionValue().Clear();
        for (int p = 0; p < nbPositions; p++)
            sequence.GetCollectionValue().Add(p);

        // Parameterizes the solver.
        localsolver.GetParam().SetTimeLimit(limit);

        localsolver.Solve();
    }

    // Writes the solution in a file following the following format: 
    // - 1st line: value of the objectives;
    // - 2nd line: for each position p, index of class at positions p.
    void WriteSolution(string fileName)
    {
        using (StreamWriter output = new StreamWriter(fileName))
        {
            output.Write(objectiveColor.GetValue() + " ");
            output.Write(objectiveHighPriority.GetValue() + " ");
            output.WriteLine(objectiveLowPriority.GetValue());
            for (int p = 0; p < nbPositions; p++)
            {
                output.Write(initialSequence[sequence.GetCollectionValue().Get(p)] + " ");
            }
            output.WriteLine();
        }
    }

    public static void Main(string[] args)
    {
        if (args.Length < 1)
        {
            Console.WriteLine("Usage: CarSequencingColor 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 (CarSequencingColor model = new CarSequencingColor())
        {
            model.ReadInstance(instanceFile);
            model.Solve(int.Parse(strTimeLimit));
            if (outputFile != null)
                model.WriteSolution(outputFile);
        }
    }
}
Compilation / Execution (Windows)
javac CarSequencingColor.java -cp %LS_HOME%\bin\localsolver.jar
java -cp %LS_HOME%\bin\localsolver.jar;. CarSequencingColor instances\022_3_4_EP_RAF_ENP.in
Compilation / Execution (Linux)
javac CarSequencingColor.java -cp /opt/localsolver_11_0/bin/localsolver.jar
java -cp /opt/localsolver_11_0/bin/localsolver.jar:. CarSequencingColor instances/022_3_4_EP_RAF_ENP.in
/******** CarSequencingColor.java *********/

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

public class CarSequencingColor {
    // Constantes for objective order
    private final int COLOR_HIGH_LOW = 0;
    private final int HIGH_LOW_COLOR = 1;
    private final int HIGH_COLOR_LOW = 2;
    private final int COLOR_HIGH = 3;
    private final int HIGH_COLOR = 4;

    // Number of vehicles.
    private int nbPositions;

    // Number of options.
    private int nbOptions;

    // Number of classes.
    private int nbClasses;

    // Paint batch limit.
    int paintBatchLimit;

    // Objective order.
    int objectiveOrder;

    // Start position.
    int startPosition;

    // Options properties.
    private int[] maxCarsPerWindow;
    private int[] windowSize;
    private boolean[] isPriorityOption;
    boolean hasLowPriorityOptions;

    // Classes properties.
    private int[] colorClass;
    private int[] nbCars;
    private int[][] options;

    // Initial sequence.
    private int[] initialSequence;

    // Solver.
    private final LocalSolver localsolver;

    // LS Program variables.
    private LSExpression sequence;

    // Objectives
    private LSExpression objectiveColor;
    private LSExpression objectiveHighPriority;
    private LSExpression objectiveLowPriority;

    private CarSequencingColor(LocalSolver localsolver) {
        this.localsolver = localsolver;
    }

    // Reads instance data.
    private void readInstance(String fileName) throws IOException {
        try (Scanner input = new Scanner(new File(fileName))) {
            nbPositions = input.nextInt();
            nbOptions = input.nextInt();
            nbClasses = input.nextInt();
            paintBatchLimit = input.nextInt();
            objectiveOrder = input.nextInt();
            startPosition = input.nextInt();

            maxCarsPerWindow = new int[nbOptions];
            windowSize = new int[nbOptions];
            isPriorityOption = new boolean[nbOptions];
            hasLowPriorityOptions = false;
            for (int o = 0; o < nbOptions; o++) {
                maxCarsPerWindow[o] = input.nextInt();
                windowSize[o] = input.nextInt();
                isPriorityOption[o] = input.nextInt() == 1;
                if (!isPriorityOption[o]) hasLowPriorityOptions = true;
            }

            if (!hasLowPriorityOptions) {
                if (objectiveOrder == COLOR_HIGH_LOW) objectiveOrder = COLOR_HIGH;
                else if (objectiveOrder == HIGH_COLOR_LOW) objectiveOrder = HIGH_COLOR;
                else if (objectiveOrder == HIGH_LOW_COLOR) objectiveOrder = HIGH_COLOR;
            }

            options = new int[nbClasses][nbOptions];
            nbCars = new int[nbClasses];
            colorClass = new int[nbClasses];
            for (int c = 0; c < nbClasses; c++) {
                colorClass[c] = input.nextInt();
                nbCars[c] = input.nextInt();
                for (int o = 0; o < nbOptions; o++) {
                    int v = input.nextInt();
                    options[c][o] = (v == 1) ? 1 : 0;
                }
            }

            initialSequence = new int[nbPositions];
            for (int p = 0; p < nbPositions; p++) {
                initialSequence[p] = input.nextInt();
            }
        }
    }

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

        // sequence[i] = j if class initially planned on position j is produced on position i
        sequence = model.listVar(nbPositions);

        // sequence is a permutation of the initial production plan, all indexes must appear exactly once
        model.constraint(model.partition(sequence));

        // Past classes (before startPosition) can not move
        for (int p = 0; p < startPosition; p++)
            model.addConstraint(model.eq(model.at(sequence, p), p));

        // Create LocalSolver arrays to be able to access them with "at" operators
        LSExpression initialArray = model.array(initialSequence);
        LSExpression colorArray = model.array(colorClass);
        LSExpression optionArray = model.array(options);

        // Number of cars with option o in each window
        LSExpression[][] nbCarsWindows = new LSExpression[nbOptions][];
        for (int o = 0; o < nbOptions; o++) {
            LSExpression oExpr = model.createConstant(o);
            nbCarsWindows[o] = new LSExpression[nbPositions];
            for (int j = startPosition - windowSize[o] + 1; j < nbPositions; j++) {
                nbCarsWindows[o][j] = model.sum();
                for (int k = 0; k < windowSize[o]; k++) {
                    if (j + k >= 0 && j + k < nbPositions) {
                        LSExpression classAtPosition = model.at(initialArray, model.at(sequence, j + k));
                        nbCarsWindows[o][j].addOperand(model.at(optionArray, classAtPosition, oExpr));
                    }
                }
            }
        }

        // Number of violations of option o capacity in each window
        objectiveHighPriority = model.sum();
        if (hasLowPriorityOptions) objectiveLowPriority = model.sum();
        for (int o = 0; o < nbOptions; o++) {
            LSExpression nbViolationsWindows = model.sum();
            for (int j = startPosition - windowSize[o] + 1; j < nbPositions; j++) {
                LSExpression delta = model.sub(nbCarsWindows[o][j], maxCarsPerWindow[o]);
                nbViolationsWindows.addOperand(model.max(0, delta));
            }
            if (isPriorityOption[o])
                objectiveHighPriority.addOperand(nbViolationsWindows);
            else
                objectiveLowPriority.addOperand(nbViolationsWindows);
        }

        // Color change between position p and position p+1
        LSExpression[] colorChange = new LSExpression[nbPositions - 1];
        objectiveColor = model.sum();
        for (int p = startPosition - 1; p < nbPositions - 1; p++) {
            LSExpression currentClass = model.at(initialArray, model.at(sequence, p));
            LSExpression nextClass = model.at(initialArray, model.at(sequence, p + 1));
            colorChange[p] = model.neq(model.at(colorArray, currentClass), model.at(colorArray, nextClass));
            objectiveColor.addOperand(colorChange[p]);
        }

        // Paint limit constraints : at least one change every paintBatchLimit positions
        for (int p = startPosition; p < nbPositions - paintBatchLimit - 1; p++) {
            LSExpression nodeOr = model.or();
            for (int p2 = 0; p2 < paintBatchLimit; p2++)
                nodeOr.addOperand(colorChange[p + p2]);
            model.addConstraint(nodeOr);
        }

        // Declare the objectives in the correct order
        switch (objectiveOrder) {
            case COLOR_HIGH_LOW:
                model.minimize(objectiveColor);
                model.minimize(objectiveHighPriority);
                model.minimize(objectiveLowPriority);
                break;
            case HIGH_COLOR_LOW:
                model.minimize(objectiveHighPriority);
                model.minimize(objectiveColor);
                model.minimize(objectiveLowPriority);
                break;
            case HIGH_LOW_COLOR:
                model.minimize(objectiveHighPriority);
                model.minimize(objectiveLowPriority);
                model.minimize(objectiveColor);
                break;
            case COLOR_HIGH:
                model.minimize(objectiveColor);
                model.minimize(objectiveHighPriority);
                break;
            case HIGH_COLOR:
                model.minimize(objectiveHighPriority);
                model.minimize(objectiveColor);
                break;
        }

        model.close();

        // Set the initial solution
        sequence.getCollectionValue().clear();
        for (int p = 0; p < nbPositions; p++)
            sequence.getCollectionValue().add(p);

        // Parameterizes the solver.
        localsolver.getParam().setTimeLimit(limit);

        localsolver.solve();
    }

    // Writes the solution in a file following the following format:
    // - 1st line: value of the objectives;
    // - 2nd line: for each position p, index of class at positions p.
    private void writeSolution(String fileName) throws IOException {
        try (PrintWriter output = new PrintWriter(fileName)) {
            output.print(objectiveColor.getValue() + " ");
            output.print(objectiveHighPriority.getValue() + " ");
            output.println(objectiveLowPriority.getValue());
            for (int p = 0; p < nbPositions; p++) {
                output.print(initialSequence[(int)sequence.getCollectionValue().get(p)] + " ");
            }
            output.println();
        }
    }

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