Order Picking Problem

Problem

The Order Picking Problem consists in finding the picking order which minimizes the distance (or the time) needed to pick up a list of orders in a warehouse and then go back to the initial position. We consider an ordinary rectangular warehouse with a single depot used for order drop-off. The depot also acts as the initial position of the picker. The orders to pick up are located on either side of vertical aisles, both accessible. The vertical aisles are surrounded by horizontal cross aisles which allow the picker to navigate through the warehouse. The picker can move vertically and horizontally. The distance between any two orders is therefore computed using a Manhattan distance.

Principles learned

Data

The instances we provide come from the Theys et al. benchmark. The format of the data files is as follows:

  • First line: number of orders
  • Next lines: distance matrix between any two orders (including the initial position, at index 0).

Program

In the Hexaly model for the Order Picking Problem, we use a list decision variable representing the picking order. We also consider the initial point as an order to pick up. A constraint on the size of the list ensures that all orders are picked up.

The objective is to minimize the distance required to pick up all the orders. To compute this distance, we use a lambda function that returns the distance from one order to the next. We compute the objective value by summing the lambda function over all order positions.

Since the path traveled by the picker is a tour, there is no need to constrain the first element in the list to be the initial point. That can be done in a post-processing phase after the resolution. To do so, we use the ‘indexOf’ operator in the model declaration to recover the position of element 0 in the list. We can then save the picking order in a file starting from the initial point.

Execution
hexaly order_picking.hxm inFileName=instances/Instance_5_3_15_Random_Central_0.txt [hxTimeLimit=] [solFileName=]
use io;

function input() {
    local usage = "Usage: hexaly order_picking.hxm inFileName=instanceFile"
            + " [outFileName=outputFile] [hxTimeLimit=timeLimit]";
    if (inFileName == nil) throw usage;
    local inFile = io.openRead(inFileName);
    nbOrders = inFile.readInt() + 1;
    for [i in 0...nbOrders][j in 0...nbOrders]
        distances[i][j] = inFile.readInt();
    inFile.close();
}

function model() {
    // Declare the list containing the picking order
    pickingList <- list(nbOrders);

    // All orders must be picked
    constraint count(pickingList) == nbOrders;

    // The objective is to minimize the total distance required to pick 
    // all the orders and to go back to the initial position
    objective <- sum(0...nbOrders - 1, i => distances[pickingList[i]][pickingList[i + 1]]) 
        + distances[pickingList[nbOrders - 1]][pickingList[0]];
    
    // Store the index of the initial position in the list.
    // It will be used at the end to write the solution starting from the initial point.
    indexInitialPosition <- indexOf(pickingList, 0);

    minimize objective;
}

function param() {
    if (hxTimeLimit == nil) hxTimeLimit = 10;
}

function output() {
    if (outFileName != nil) {
        outFile = io.openWrite(outFileName);
        outFile.println(objective.value);
        println("Solution written in file ", outFileName);
        for [i in 0...nbOrders] {
            local index = (indexInitialPosition.value + i) % nbOrders;
            outFile.print(pickingList.value[index], " ");
        }
    }
}
Execution (Windows)
set PYTHONPATH=%HX_HOME%\bin\python
python order_picking.py instances\Instance_5_3_15_Random_Central_0.txt
Execution (Linux)
export PYTHONPATH=/opt/hexaly_13_0/bin/python
python order_picking.py instances/Instance_5_3_15_Random_Central_0.txt
import hexaly.optimizer
import sys

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

def read_instance(filename) :
    file_iterator = iter(read_elem(filename))
    nb_orders = int(next(file_iterator)) + 1
    distances_data = [None] * nb_orders
    for i in range(nb_orders) :
        distances_data[i] = [None] * nb_orders
        for j in range(nb_orders) :
            distances_data[i][j] = int(next(file_iterator))
    return nb_orders, distances_data

def main(input_file, output_file, time_limit) :
    # Read the instance from input_file
    nb_orders, distances_data = read_instance(input_file)
    
    with hexaly.optimizer.HexalyOptimizer() as optimizer :
        # Declare the model
        model = optimizer.model

        # Declare the list containing the picking order
        picking_list = model.list(nb_orders)

        # All orders must be picked
        model.constraint(model.count(picking_list) == nb_orders)

        # Create an Hexaly array for the distance matrix in order to access it using the "at" operator
        distances_matrix = model.array(distances_data)

        # Lambda expression to compute the distance to the next order
        distance_to_next_order_lambda = model.lambda_function( 
            lambda i : model.at(distances_matrix, picking_list[i], picking_list[i + 1]))

        # The objective is to minimize the total distance required to pick 
        # all the orders and to go back to the initial position
        objective = model.sum(model.range(0, nb_orders - 1), distance_to_next_order_lambda) \
            + model.at(distances_matrix, picking_list[nb_orders - 1], picking_list[0])

        # Store the index of the initial position in the list.
        # It will be used at the end to write the solution starting from the initial point.
        index_initial_position = model.index(picking_list, 0)

        model.minimize(objective)

        # End of the model declaration
        model.close()

        optimizer.param.time_limit = time_limit

        optimizer.solve()

        if output_file != None :
            with open(output_file, 'w') as f:
                f.write("%i\n" % objective.value)
                for i in range(nb_orders):
                    index = (index_initial_position.get_value() + i) % nb_orders
                    f.write("%i " % picking_list.value[index])


if __name__ == '__main__' :
    if len(sys.argv) < 2:
        print("Usage: python order_picking.py input_file [output_file] [time_limit]")
        sys.exit(1)
    input_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 10
    main(input_file, output_file, time_limit)
Compilation / Execution (Windows)
cl /EHsc order_picking.cpp -I%HX_HOME%\include /link %HX_HOME%\bin\hexaly130.lib
order_picking instances\Instance_5_3_15_Random_Central_0.txt
Compilation / Execution (Linux)
g++ order_picking.cpp -I/opt/hexaly_13_0/include -lhexaly130 -lpthread -o order_picking
./order_picking instancesInstance_5_3_15_Random_Central_0.txt
#include "optimizer/hexalyoptimizer.h"
#include <fstream>
#include <iostream>
#include <vector>

using namespace hexaly;
using namespace std;

class OrderPicking {
public:
    int nbOrders;
    vector<vector<int>> distancesData;
    HexalyOptimizer optimizer;
    HxExpression pickingList;
    HxExpression objective;
    HxExpression indexInitialPosition;

    void readInstance(const string& inputFile) {
        ifstream infile(inputFile);
        if (!infile.is_open()) {
            throw std::runtime_error("Cannot open the file.");
        }
        infile >> nbOrders;
        ++nbOrders;
        distancesData.resize(nbOrders);
        for (int i = 0; i < nbOrders; ++i) {
            distancesData[i].resize(nbOrders);
            for (int j = 0; j < nbOrders; ++j) {
                infile >> distancesData[i][j];
            }
        }
    }

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

        // Declare the list containing the picking order
        pickingList = model.listVar(nbOrders);

        // All orders must be picked
        model.constraint(model.count(pickingList) == nbOrders);

        // Create an Hexaly array for the distance matrix in order to access it using the "at" operator
        HxExpression distancesMatrix = model.array();
        for (int i = 0; i < nbOrders; ++i) {
            distancesMatrix.addOperand(model.array(distancesData[i].begin(), distancesData[i].end()));
        }

        // Lambda expression to compute the distance to the next order
        HxExpression distanceToNextOrderLambda = model.createLambdaFunction([&](HxExpression i) {
            return model.at(distancesMatrix,  pickingList[i], pickingList[i + 1]);
        });

        // The objective is to minimize the total distance required to pick 
        // all the orders and to go back to the initial position
        objective = model.sum(model.range(0, nbOrders - 1), distanceToNextOrderLambda) 
            + model.at(distancesMatrix, pickingList[nbOrders - 1], pickingList[0]);

        // Store the index of the initial position in the list.
        // It will be used at the end to write the solution starting from the initial point.
        indexInitialPosition = model.indexOf(pickingList, 0);

        model.minimize(objective);

        // End of the model declaration
        model.close();

        optimizer.getParam().setTimeLimit(timeLimit);

        optimizer.solve();
    }

    void writeSolution(const string& outputFile) {
        ofstream outfile;
        outfile.exceptions(ofstream::failbit | ofstream::badbit);
        outfile.open(outputFile.c_str());
        outfile << objective.getValue() << endl;
        HxCollection pickingListCollection = pickingList.getCollectionValue();
        for (int i = 0; i < nbOrders; ++i) {
            int index = ((int)indexInitialPosition.getValue() + i) % nbOrders;
            outfile << pickingListCollection[index] << " ";
        }
        outfile << endl;
    }
};

int main(int argc, char** argv) {
    if (argc < 2) {
        cerr << "order_picking inputFile [outputFile] [timeLimit]" << endl;
        return 1;
    }
    const char* inputFile = argv[1];
    const char* outputFile = argc > 2 ? argv[2] : NULL;
    const char* strTimeLimit = argc > 3 ? argv[3] : "10";
    OrderPicking model;
    model.readInstance(inputFile);
    model.solve(atoi(strTimeLimit));
    if (outputFile != NULL)
        model.writeSolution(outputFile);
    return 0;
}
Compilation / Execution (Windows)
copy %HX_HOME%\bin\Hexaly.NET.dll .
csc OrderPicking.cs /reference:Hexaly.NET.dll
OrderPicking instances\Instance_5_3_15_Random_Central_0.txt
using System;
using System.IO;
using System.Globalization;
using Hexaly.Optimizer;

public class OrderPicking : IDisposable
{
    int nbOrders;
    int[][] distancesData;

    HexalyOptimizer optimizer;

    HxExpression pickingList;

    HxExpression objective;

    HxExpression indexInitialPosition;

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

    public void ReadInstance(string filename) 
    {
        using(StreamReader input = new StreamReader(filename)) 
        {
            string[] splittedLine = input.ReadLine().Split();
            nbOrders = int.Parse(splittedLine[0]) + 1;
            distancesData = new int[nbOrders][];
            for (int i = 0; i < nbOrders; ++i) 
            {
                splittedLine = input.ReadLine().Split();
                distancesData[i] = new int[nbOrders];
                for (int j = 0; j < nbOrders; ++j) 
                {
                    distancesData[i][j] = int.Parse(splittedLine[j]);
                }
            }
        }
    }

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

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

        // Declare the list containing the picking order
        pickingList = model.List(nbOrders);

        // All orders must be picked
        model.Constraint(model.Count(pickingList) == nbOrders);

        // Create a HexalyOptimizer array for the distance matrix in order to access it using the "at" operator
        HxExpression distancesMatrix = model.Array(distancesData);

        // Lambda expression to compute the distance to the next order
        HxExpression distanceToNextOrderLambda = model.LambdaFunction( 
            i => distancesMatrix[pickingList[i]][pickingList[i + 1]]);

        // The objective is to minimize the total distance required to pick 
        // all the orders and to go back to the initial position
        objective = model.Sum(model.Range(0, nbOrders - 1), distanceToNextOrderLambda) 
            + distancesMatrix[pickingList[nbOrders - 1]][pickingList[0]];

        // Store the index of the initial position in the list.
        // It will be used at the end to write the solution starting from the initial point.
        indexInitialPosition = model.IndexOf(pickingList, 0);

        model.Minimize(objective);

        // End of the model declaration
        model.Close();

        optimizer.GetParam().SetTimeLimit(timeLimit);

        optimizer.Solve();
    }

    public void WriteSolution(string filename) 
    {
        using (StreamWriter output = new StreamWriter(filename)) 
        {
            output.WriteLine(objective.GetIntValue());
            HxCollection solutionCollection = pickingList.GetCollectionValue();
            for (int i = 0; i < nbOrders; ++i) 
            {
                int index = ((int)indexInitialPosition.GetValue() + i) % nbOrders;
                output.Write(solutionCollection[index] + " ");
            }
            output.Close();
        } 
    }


    public static void Main(string[] args) 
    {
        if (args.Length < 1) 
        {
            Console.WriteLine("Usage: OrderPicking inputFile [outputFile] [timeLimit]");
            Environment.Exit(1);
        }
        string inputFile = args[0];
        string outputFile = args.Length > 1 ? args[1] : null;
        int timeLimit = args.Length > 2 ? int.Parse(args[2]) : 10;
        using (OrderPicking model = new OrderPicking()) 
        {
            model.ReadInstance(inputFile);
            model.Solve(timeLimit);
            if (outputFile != null)
                model.WriteSolution(outputFile);
        }
    }

}
Compilation / Execution (Windows)
javac OrderPicking.java -cp %HX_HOME%\bin\hexaly.jar
java -cp %HX_HOME%\bin\hexaly.jar;. OrderPicking instances\Instance_5_3_15_Random_Central_0.txt
Compilation / Execution (Linux)
javac order_picking.java -cp /opt/hexaly_13_0/bin/hexaly.jar
java -cp %HX_HOME%\bin\hexaly.jar;. OrderPicking instances\Instance_5_3_15_Random_Central_0.txt
import java.util.*;
import java.io.*;
import com.hexaly.optimizer.*;

public class OrderPicking {
    private int nbOrders;
    private int[][] distancesData;

    final HexalyOptimizer optimizer;
    private HxExpression pickingList;
    private HxExpression objective; 

    private HxExpression indexInitialPosition;

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

    private void readInstance(String inputFile) throws IOException {
        try (Scanner input = new Scanner(new File(inputFile))) {
            nbOrders = input.nextInt() + 1;
            distancesData = new int[nbOrders][nbOrders];
            for (int i = 0; i < nbOrders; ++i) {
                for (int j = 0; j < nbOrders; ++j) {
                    distancesData[i][j] = input.nextInt();
                }
            }
        }
    }

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

        // Declare the list containing the picking order
        pickingList = model.listVar(nbOrders);

        // All orders must be picked
        model.constraint(model.eq(model.count(pickingList), nbOrders));

        // Create a HexalyOptimizer array for the distance matrix in order to access it using the "at" operator
        HxExpression distancesMatrix = model.array(distancesData);

        // Lambda expression to compute the distance to the next order
        HxExpression distanceToNextOrderLambda = model.lambdaFunction( i -> 
            model.at(distancesMatrix, model.at(pickingList, i), model.at(pickingList, model.sum(i, 1))));

        // The objective is to minimize the total distance required to pick 
        // all the orders and to go back to the initial position
        objective = model.sum(model.sum(model.range(0, nbOrders - 1), distanceToNextOrderLambda),
            model.at(distancesMatrix, model.at(pickingList, nbOrders - 1), model.at(pickingList, 0)));

        // Store the index of the initial position in the list.
        // It will be used at the end to write the solution starting from the initial point.
        indexInitialPosition = model.indexOf(pickingList, 0);

        model.minimize(objective);

        // End of the model declaration
        model.close();

        optimizer.getParam().setTimeLimit(timeLimit);

        optimizer.solve();
    }

    public void writeSolution(String inputFile) throws IOException {
        try (PrintWriter output = new PrintWriter(inputFile)) {
            output.println(objective.getValue());
            HxCollection pickingListCollection = pickingList.getCollectionValue();
            for (int i = 0; i < nbOrders; ++i) {
                int index = ((int)indexInitialPosition.getValue() + i) % nbOrders;
                output.print(pickingListCollection.get(index) + " ");
            }
            output.println();
        }
    }

    public static void main(String[] args) {
        if (args.length < 1) {
            System.out.println("Usage: java OrderPicking inputFile [outputFile] [timeLimit]");
            System.exit(1);
        }

        String inputFile = args[0];
        String outputFile = args.length > 1 ? args[1] : null;
        String strTimeLimit = args.length > 2 ? args[2] : "10";

        try (HexalyOptimizer optimizer = new HexalyOptimizer()) {
            OrderPicking model = new OrderPicking(optimizer);
            model.readInstance(inputFile);
            model.solve(Integer.parseInt(strTimeLimit));
            if (outputFile != null) {
                model.writeSolution(outputFile);
            }
        } catch(Exception ex) {
            System.err.println(ex);
            ex.printStackTrace();
            System.exit(1);
        }
    }
}