LocalSolver logo
is now
Hexaly logo

We're excited to share that we are moving forward. We're leaving behind the LocalSolver brand and transitioning to our new identity: Hexaly. This represents a leap forward in our mission to enable every organization to make better decisions faster when faced with operational and strategic challenges.

This page is for an old version of Hexaly Optimizer. We recommend that you update your version and read the documentation for the latest stable release.

Assembly line balancing

Principles learned

  • Setup an initial solution
  • Set succession constraints
  • Use intermediate variables
  • Use n-ary operator “or”

Problem

../_images/assembly_line_balancing.png

We consider a simple assembly line balancing problem (SALBP) as defined by Prof. Dr. Armin Scholl, Friedrich Schiller University Jena. We have a set of tasks that must be gathered in groups called stations. Each task requires a certain processing time. Moreover, some tasks cannot be realized if some others are not completed before. Finally, the sum of the tasks’ processing times in each station cannot exceed a given limit. Therefore, the goal is to minimize the number of stations such that the cycle time limit constraints and the tasks’ order are satisfied. On the left diagram, the tasks are the letter circles, the order constraints are represented by arrows and the grey areas are the stations.

Download the example

Data

The instances provided come from Alena Otto. They are formatted into the following format:

  • Number of tasks
  • Cycle time limit
  • Tasks’ processing times
  • Precedence relations

Program

This LocalSolver model defines a sequence of int variables called “assignedStation”. For each task, the corresponding assignedStation decision represents the station that will perform the task. Proceeding that way each task necessarily has a station.

We state an upper bound for the number of stations equals to the number of tasks according to the naive solution where each task is contained in a different station. Furthermore, we initialize the optimization with this naive solution.

The number of used stations is computed as the number of different values in “assignedStation” and should be minimized. The cycle time constraint is written with a sum over all the tasks using a boolean condition on “assignedStation” to select only the tasks which belong to the regarded station. The succession constraint verifies that for each task, its station’s number is inferior or equal to its successors’ ones.

Execution:
localsolver assembly_line_balancing.lsp inFileName=instances/instance_n20_1.alb [lsTimeLimit=] [solFileName=]
/********** assembly_line_balancing.lsp **********/

use io;

/* Read instance data. */
function input() {
    local usage = "Usage: localsolver assembly_line_balancing.lsp "
    + "inFileName=inputFile [lsTimeLimit=timeLimit] [solFileName=solFile]\n";

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

    inFile.readln();
    // Read number of tasks
    nbTasks = inFile.readInt();
    maxNbStations = nbTasks;

    inFile.readln();
    // Read the cycle time limit
    cycleTime = inFile.readInt();

    for [i in 0..4] inFile.readln();
    // Read the processing times
    for [t in 0..nbTasks-1]
        processingTime[inFile.readInt()-1] = inFile.readInt();


    // Read the successors' relations
    for [t in 0..nbTasks-1]
        successors[t] = {};
    inFile.readln();
    local line = inFile.readln().split(",");
    while(line.count() > 1) {
        local predecessor = toInt(line[0]) - 1;
        local successor = toInt(line[1]) - 1;
        successors[predecessor].add(successor);
        line = inFile.readln().split(",");
    }
    inFile.close();
}

/* Declare the optimization model. */
function model() {
    // Decision variable : assignedStation[i] is the number of the station to which task i belongs
    assignedStation[i in 0..nbTasks-1] <- int(0, maxNbStations-1);

    // Intermediate expressions : nbUsedStations is the total number of used stations
    stationUsed[s in 0..maxNbStations-1] <- or[i in 0..nbTasks-1](assignedStation[i] == s);
    nbUsedStations <- sum[s in 0..maxNbStations-1](stationUsed[s]);

    // All stations must respect the cycleTime constraint
    timeStations[s in 0..maxNbStations-1] <- sum[i in 0..nbTasks-1]((assignedStation[i] == s) * processingTime[i]);
    for[s in 0..maxNbStations-1]
        constraint timeStations[s] <= cycleTime;

    // The stations must respect the succession's order of the tasks
    for[i in 0..nbTasks-1][j in successors[i]]
        constraint assignedStation[i] <= assignedStation[j];

    // Minimization of the number of active stations
    minimize nbUsedStations;
}

/* Parametrize the solver. */
function param() {
    if (lsTimeLimit == nil) lsTimeLimit = 20;
    // Initialize with a naive solution : each task belongs to one separate station
    for [i in 0..nbTasks-1]
        assignedStation[i].value = i;
}

/* Write the solution in a file following the following format:
 * - value of the objective (number of stations)
 * - number of tasks
 * - task's number, station's number */
function output() {
    if(solFileName == nil) return;
    local solFile = io.openWrite(solFileName);
    solFile.println(nbUsedStations.value);
    solFile.println(nbTasks);
    for[i in 0..nbTasks-1]
        solFile.println(i + 1, ",", assignedStation[i].value + 1);
}
Execution (Windows)
set PYTHONPATH=%LS_HOME%\bin\
python assembly_line_balancing.py instances\instance_n20_1.alb
Execution (Linux)
export PYTHONPATH=/opt/localsolver_9_5/bin/
python assembly_line_balancing.py instances/instance_n20_1.alb
########## assembly_line_balancing.py ##########

import localsolver
import sys

#
# Functions to read the instances
#
def read_elem(filename):
    with open(filename) as f:
        return [str(elem) for elem in f.read().split()]

def read_instance(instance_file):
    file_it = iter(read_elem(instance_file))
    for i in range(3):
        to_throw = next(file_it)

    # Read number of tasks
    nbTasks = int(next(file_it))
    maxNbStations = nbTasks
    for i in range(2):
        to_throw = next(file_it)

    # Read the cycle time limit
    cycleTime = int(next(file_it))
    for i in range(5):
        to_throw = next(file_it)

    # Read the processing times
    processingTime = {}
    for i in range(nbTasks):
        task = int(next(file_it)) - 1
        processingTime[task] = int(next(file_it))
    for i in range(2):
        to_throw = next(file_it)

    # Read the successors' relations
    successors = {}
    while True:
        try:
            pred, succ = next(file_it).split(',')
            pred = int(pred) -1
            succ = int(succ) -1
            if pred in successors:
                successors[pred].append(succ)
            else:
                successors[pred] = [succ]
        except:
            break
    return nbTasks, maxNbStations, cycleTime, processingTime, successors

#
# Modeling and solve
#
def main(instance_file, output_file, time_limit):
    nbTasks, maxNbStations, cycleTime, processingTime, successors = read_instance(instance_file)

    with localsolver.LocalSolver() as ls:

        # Declare the optimization model
        model = ls.model

        # Decision variable : assignedStation[i] is the number of the station to which task i belongs
        assignedStation = [model.int(0, maxNbStations-1) for i in range(nbTasks)]

        # Intermediate expressions : nbUsedStations is the total number of used stations
        stationUsed = [model.or_(model.eq(assignedStation[i] , s) for i in range(nbTasks)) for s in range(maxNbStations)]
        nbUsedStations = model.sum(stationUsed)

        # All stations must respect the cycleTime constraint
        timeStations = [model.sum([model.eq(assignedStation[i] , s) * processingTime[i] for i in range(nbTasks)]) for s in range(maxNbStations)]
        for s in range(maxNbStations):
            model.constraint(timeStations[s] <= cycleTime)

        # The stations must respect the succession's order of the tasks
        for i in range(nbTasks):
            if i in successors.keys():
                for j in successors[i]:
                    model.constraint(assignedStation[i] <= assignedStation[j])

        # Minimization of the number of stations
        model.minimize(nbUsedStations)

        model.close()

        #
        # Parameterize the solver
        #
        ls.param.time_limit = time_limit
        # Initialize with a naive solution : each task belongs to one separate station
        for i in range(nbTasks):
            assignedStation[i].value = i

        ls.solve()

        # Write the solution in a file following the format:
        # - 1st line: value of the objective
        # - 2nd line: number of tasks
        # - following lines: task's number, station's number
        if output_file is not None:
            with open(output_file, 'w') as f:
                f.write("%d\n" % nbUsedStations.value)
                f.write("%d\n" % nbTasks)
                for i in range(nbTasks):
                    f.write("{},{}\n".format(i+1, assignedStation[i].value+1))

if __name__ == '__main__':
    if len(sys.argv) < 2:
        print("Usage: python assembly_line_balancing.py instance_file [output_file] [time_limit]")
        sys.exit(1)

    instance_file = sys.argv[1]
    output_file = sys.argv[2] if len(sys.argv) >= 3 else None
    time_limit = int(sys.argv[3]) if len(sys.argv) >= 4 else 20
    main(instance_file, output_file, time_limit)
Compilation / Execution (Windows)
cl /EHsc assembly_line_balancing.cpp -I%LS_HOME%\include /link %LS_HOME%\bin\localsolver95.lib
assembly_line_balancing instances\instance_n20_1.alb
Compilation / Execution (Linux)
g++ assembly_line_balancing.cpp -I/opt/localsolver_9_5/include -llocalsolver95 -lpthread -o assembly_line_balancing
./assembly_line_balancing instances/instance_n20_1.alb
/********** assembly_line_balancing.cpp **********/

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

using namespace localsolver;
using namespace std;

class ALBInstance {
public:
    int nbTasks;
    int nbMaxStations;
    int cycleTime;
    string to_throw;
    vector<int> processingTime;
    vector<vector<int>> successors;

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

        for (int i = 0; i < 3; ++i)
            infile >> to_throw;

        // Read number of tasks
        infile >> nbTasks;
        nbMaxStations = nbTasks;
        processingTime.resize(nbTasks);
        successors.resize(nbTasks);
        for (int i = 0; i < 2; ++i)
            infile >> to_throw;

        // Read the cycle time limit
        infile >> cycleTime;
        for (int i = 0; i < 5; ++i)
            infile >> to_throw;

        // Read the processing times
        for (int i = 0; i < nbTasks; ++i) {
            int task;
            infile >> task;
            infile >> processingTime[task - 1];
        }
        for (int i = 0; i < 2; ++i)
            infile >> to_throw;

        // Read the successors' relations
        string delimiter = ",";
        while (infile.eof() != true) {
            string relation;
            infile >> relation;
            string predecessor = relation.substr(0, relation.find(delimiter));
            if(predecessor == relation)
                break;
            string successor = relation.substr(relation.find(delimiter)+1, relation.size());
            successors[stoi(predecessor)-1].push_back(stoi(successor)-1);
        }
        infile.close();
    }

    ALBInstance(const string& fileName) {
        readInstance(fileName);
    }
};

class AssemblyLineBalancing {
private:
    // LocalSolver
    LocalSolver localsolver;

    // Instance data
    const ALBInstance* instance;

    // Decision variable
    vector<LSExpression> assignedStation;

    // Intermediate expressions
    vector<LSExpression> stationUsed;
    vector<LSExpression> timeStations;

    // Objective
    LSExpression nbUsedStations;

public:
    // Constructor
    AssemblyLineBalancing(const ALBInstance* albi) : instance(albi) {
    }

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

        // assignedStation[i] is the number of the station to which task i belongs
        assignedStation.resize(instance->nbTasks);
        for(int i = 0; i < instance->nbTasks; ++i)
            assignedStation[i] = model.intVar(0, instance->nbMaxStations-1);

        // nbUsedStations is the total number of used stations
        stationUsed.resize(instance->nbMaxStations);
        for (int s = 0; s < instance->nbMaxStations; ++s) {
            stationUsed[s] = model.or_();
            for (int i = 0; i< instance->nbTasks; ++i)
                stationUsed[s].addOperand(model.eq(assignedStation[i], s));
        }
        nbUsedStations = model.sum(stationUsed.begin(), stationUsed.end());

        // All stations must respect the cycleTime constraint
        timeStations.resize(instance->nbMaxStations);
        for (int s = 0; s < instance->nbMaxStations; ++s) {
            timeStations[s] = model.sum();
            for (int i = 0; i < instance->nbTasks; ++i) {
                LSExpression isTaskInStation = model.eq(assignedStation[i], s);
                timeStations[s].addOperand(isTaskInStation * instance->processingTime[i]);
            }
            model.constraint(timeStations[s] <= instance->cycleTime);
        }

        // The stations must respect the succession's order of the tasks
        for (int i = 0; i < instance->nbTasks; ++i) {
            for (int j : instance->successors[i])
                model.constraint(assignedStation[i] <= assignedStation[j]);
        }

        // Minimization of the number of stations
        model.minimize(nbUsedStations);

        model.close();

        // Parametrize the solver
        localsolver.getParam().setTimeLimit(limit);
        // Initialize with a naive solution : each task belongs to one separate station
        for (int i = 0; i < instance->nbTasks; ++i)
            assignedStation[i].setIntValue(i);

        localsolver.solve();
    }

    /* Write the solution in a file following the format:
    * - 1st line: value of the objective
    * - 2nd line: number of tasks
    * - following lines: task's number, station's number */
    void writeSolution(const string& fileName) {
        ofstream outfile;
        outfile.exceptions(ofstream::failbit | ofstream::badbit);
        outfile.open(fileName.c_str());
        outfile << nbUsedStations.getIntValue() << endl;
        outfile << instance->nbTasks << endl;
        for (int i = 0; i < instance->nbTasks; ++i)
            outfile << i + 1 << "," << assignedStation[i].getIntValue() + 1 << endl;
    }
};

int main(int argc, char** argv) {
    if (argc < 2) {
        cerr << "Usage: assembly_line_balancing inputFile [outputFile] [timeLimit]" << endl;
        return 1;
    }
    const char* instanceFile = argv[1];
    const char* solFile = argc > 2 ? argv[2] : NULL;
    const char* strTimeLimit = argc > 3 ? argv[3] : "20";
    ALBInstance instance(instanceFile);
    AssemblyLineBalancing model(&instance);
    try {
        model.solve(atoi(strTimeLimit));
        if (solFile != NULL) model.writeSolution(solFile);
        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 AssemblyLineBalancing.cs /reference:localsolvernet.dll
AssemblyLineBalancing instances\instance_n20_1.alb
/********** AssemblyLineBalancing.cs **********/

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

public class ALBInstance
{
    public int nbTasks;
    public int nbMaxStations;
    public int cycleTime;
    public int[] processingTime;
    public List<int>[] successors;

    // Constructor
    public ALBInstance(string fileName)
    {
        ReadInstance(fileName);
    }

    /* Read instance data */
    void ReadInstance(string fileName)
    {
        using (StreamReader input = new StreamReader(fileName))
        {
            string[] line;
            input.ReadLine();

            // Read number of tasks
            nbTasks = int.Parse(input.ReadLine());
            nbMaxStations = nbTasks;
            processingTime = new int[nbTasks];
            successors = new List<int>[nbTasks];
            for (int i = 0; i < 2; ++i)
                input.ReadLine();

            // Read the cycle time limit
            cycleTime = int.Parse(input.ReadLine());
            for (int i = 0; i < 6; ++i)
                input.ReadLine();

            // Read the processing times
            for (int i = 0; i < nbTasks; ++i)
            {
                line = input.ReadLine().Split();
                processingTime[i] = int.Parse(line[1]);
            }
            for (int i = 0; i < 2; ++i)
                input.ReadLine();

            // Read the successors' relations
            while (true)
            {
                line = input.ReadLine().Split(',');
                if (line[0] == "")
                    break;
                int predecessor = int.Parse(line[0]) -1;
                int successor = int.Parse(line[1]) -1;
                if (successors[predecessor] == null)
                    successors[predecessor] = new List<int>();
                successors[predecessor].Add(successor);
            }
        }
    }
}

public class AssemblyLineBalancing : IDisposable
{
    // LocalSolver
    LocalSolver localsolver;

    // Instance data
    ALBInstance instance;

    // Decision variable
    LSExpression[] assignedStation;

    // Intermediate expressions
    LSExpression[] stationUsed;
    LSExpression[] timeStations;

    // Objective
    LSExpression nbUsedStations;

    // Constructor
    public AssemblyLineBalancing(ALBInstance instance)
    {
        this.localsolver = new LocalSolver();
        this.instance = instance;
    }

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

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

        // assignedStation[i] is the number of the station to which task i belongs
        assignedStation = new LSExpression[instance.nbTasks];
        for (int i = 0; i < instance.nbTasks; ++i)
            assignedStation[i] = model.Int(0, instance.nbMaxStations-1);

        // nbUsedStations is the total number of used stations
        stationUsed = new LSExpression[instance.nbMaxStations];
        nbUsedStations = model.Sum();
        for (int s = 0; s < instance.nbMaxStations; ++s)
        {
            stationUsed[s] = model.Or();
            for (int i = 0; i < instance.nbTasks; ++i)
                stationUsed[s].AddOperand(model.Eq(assignedStation[i], s));
            nbUsedStations.AddOperand(stationUsed[s]);
        }

        // All stations must respect the cycleTime constraint
        timeStations = new LSExpression[instance.nbMaxStations];
        for (int s = 0; s < instance.nbMaxStations; ++s)
        {
            timeStations[s] = model.Sum();
            for (int i = 0; i < instance.nbTasks; ++i)
            {
                LSExpression isTaskInStation = model.Eq(assignedStation[i],s);
                timeStations[s].AddOperand(isTaskInStation * instance.processingTime[i]);
            }
            model.Constraint(timeStations[s] <= instance.cycleTime);
        }

        // The stations must respect the succession's order of the tasks
        for (int i = 0; i < instance.nbTasks; ++i)
            if(instance.successors[i] != null)
                foreach (int j in instance.successors[i])
                    model.Constraint(assignedStation[i] <= assignedStation[j]);

        // Minimization of the number of stations
        model.Minimize(nbUsedStations);

        model.Close();

        // Parametrize the solver
        localsolver.GetParam().SetTimeLimit(limit);
        // Set an initialization on a naive solution : each task belongs to one separate station
        for (int i = 0; i < instance.nbTasks; ++i)
            assignedStation[i].SetIntValue(i);

        localsolver.Solve();
    }

    /* Write the solution in a file following the format:
    * - 1st line: value of the objective
    * - 2nd line: number of tasks
    * - following lines: task's number, station's number */
    void WriteSolution(string fileName)
    {
        using (StreamWriter output = new StreamWriter(fileName))
        {
            output.WriteLine(nbUsedStations.GetIntValue());
            output.WriteLine(instance.nbTasks);
            for (int i = 0; i < instance.nbTasks; ++i)
            {
                output.Write(i + 1);
                output.Write(',');
                output.WriteLine(assignedStation[i].GetIntValue() + 1);
            }
        }

    }

    public static void Main(string[] args)
    {
        if (args.Length < 1)
        {
            Console.WriteLine("Usage: AssemblyLineBalancing inputFile [solFile] [timeLimit]");
            Environment.Exit(1);
        }
        string instanceFile = args[0];
        string outputFile = args.Length > 1 ? args[1] : null;
        string strTimeLimit = args.Length > 2 ? args[2] : "20";
        ALBInstance instance = new ALBInstance(instanceFile);
        using (AssemblyLineBalancing model = new AssemblyLineBalancing(instance))
        {
            model.Solve(int.Parse(strTimeLimit));
            if (outputFile != null)
              model.WriteSolution(outputFile);
        }
    }
}
Compilation / Execution (Windows)
javac AssemblyLineBalancing.java -cp %LS_HOME%\bin\localsolver.jar
java -cp %LS_HOME%\bin\localsolver.jar;. AssemblyLineBalancing instances\instance_n20_1.alb
Compilation / Execution (Linux)
javac AssemblyLineBalancing.java -cp /opt/localsolver_9_5/bin/localsolver.jar
java -cp /opt/localsolver_9_5/bin/localsolver.jar:. AssemblyLineBalancing instances/instance_n20_1.alb
/********** AssemblyLineBalancing.java **********/

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

public class AssemblyLineBalancing {

    private static class ALBInstance {
        int nbTasks;
        int nbMaxStations;
        int cycleTime;
        int[] processingTime;
        ArrayList<ArrayList<Integer>> successors;

        // Constructor
        private ALBInstance(String fileName) throws IOException {
            readInput(fileName);
        }

        // Read instance data
        private void readInput(String fileName) throws IOException {
            try (Scanner input = new Scanner(new File(fileName))) {

                input.nextLine();
                // Read number of tasks
                nbTasks = input.nextInt();
                nbMaxStations = nbTasks;
                processingTime = new int[nbTasks];
                successors = new ArrayList<ArrayList<Integer>>(nbTasks);
                for (int i = 0; i < nbTasks; i ++)
                    successors.add(i, new ArrayList<Integer>());
                for (int i = 0; i < 3; i++)
                    input.nextLine();

                // Read the cycle time limit
                cycleTime = input.nextInt();
                for (int i = 0; i < 7; ++i)
                    input.nextLine();

                // Read the processing times
                for (int i = 0; i < nbTasks; i++)
                    processingTime[input.nextInt()-1] = input.nextInt();
                for (int i = 0; i < 3; ++i)
                    input.nextLine();

                // Read the successors' relations
                String line = input.nextLine();
                while (!line.isEmpty()) {
                    String lineSplit[] = line.split(",");
                    int predecessor = Integer.parseInt(lineSplit[0]) -1;
                    int successor = Integer.parseInt(lineSplit[1]) -1;
                    successors.get(predecessor).add(successor);
                    line = input.nextLine();
                }
            }
        }
    }

    private static class ALBProblem {

        // LocalSolver
        private final LocalSolver localsolver;

        // Instance data
        private final ALBInstance instance;

        // Decision variable
        private LSExpression[] assignedStation;

        // Intermediate expressions
        private LSExpression[] stationUsed;
        private LSExpression[] timeStations;

        // Objective
        private LSExpression nbUsedStations;

        // Constructor
        private ALBProblem(LocalSolver localsolver, ALBInstance instance) {
            this.localsolver = localsolver;
            this.instance = instance;
        }

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

            // assignedStation[i] is the number of the station to which task i belongs
            assignedStation = new LSExpression[instance.nbTasks];
            for (int i = 0; i < instance.nbTasks; i++)
                assignedStation[i] = model.intVar(0, instance.nbMaxStations-1);

            // nbUsedStations is the total number of used stations
            stationUsed = new LSExpression[instance.nbMaxStations];
            nbUsedStations = model.sum();
            for (int s = 0; s < instance.nbMaxStations; s++) {
                stationUsed[s] = model.or();
                for (int i = 0; i < instance.nbTasks; i++) {
                    LSExpression isTaskInStation = model.eq(assignedStation[i], s);
                    stationUsed[s].addOperand(isTaskInStation);
                }
                nbUsedStations.addOperand(stationUsed[s]);
            }

            // All stations must respect the cycleTime constraint
            timeStations = new LSExpression[instance.nbMaxStations];
            for (int s = 0; s < instance.nbMaxStations; s++) {
                timeStations[s] = model.sum();
                for (int i = 0; i < instance.nbTasks; i++) {
                    LSExpression isTaskInStation = model.eq(assignedStation[i],s);
                    LSExpression timeForOneTask = model.prod(isTaskInStation, instance.processingTime[i]);
                    timeStations[s].addOperand(timeForOneTask);
                }
                LSExpression cycleTimeConstraint = model.leq(timeStations[s], instance.cycleTime);
                model.constraint(cycleTimeConstraint);
            }

            // The stations must respect the succession's order of the tasks
            for (int i = 0; i < instance.nbTasks; i++) {
                ArrayList<Integer> successors_i = instance.successors.get(i);
                for (int j : successors_i) {
                    LSExpression order = model.leq(assignedStation[i], assignedStation[j]);
                    model.constraint(order);
                }
            }

            // Minimization of the number of stations
            model.minimize(nbUsedStations);

            model.close();

            // Parametrize the solver
            localsolver.getParam().setTimeLimit(limit);
            // Initialize with a naive solution : each task belongs to one separate station
            for (int i = 0; i < instance.nbTasks; i++)
                assignedStation[i].setIntValue(i);

            localsolver.solve();
        }

        /* Write the solution in a file following the format:
        * - 1st line: value of the objective
        * - 2nd line: number of tasks
        * - following lines: task's number, station's number */
        void writeSolution(String fileName) throws IOException {
            try(PrintWriter output = new PrintWriter(new FileWriter(fileName))) {
                output.println(nbUsedStations.getIntValue());
                output.println(instance.nbTasks);
                for (int i = 0; i < instance.nbTasks; i++) {
                    output.print(i + 1);
                    output.print(",");
                    output.println(assignedStation[i].getIntValue() + 1);
                }
            }
        }
    }
    public static void main(String [] args) {
        if (args.length < 1) {
            System.err.println("Usage: AssemblyLineBalancing 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] : "20";
        try (LocalSolver localsolver = new LocalSolver()) {
            ALBInstance instance = new ALBInstance(instanceFile);
            ALBProblem model = new ALBProblem(localsolver, instance);
            model.solve(Integer.parseInt(strTimeLimit));
            if (outputFile != null)
                model.writeSolution(outputFile);
        }
        catch (Exception ex) {
            System.err.println(ex);
            ex.printStackTrace();
            System.exit(1);
        }
    }
}