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
- Add a list decision variable to model the picking order
- Define a lambda function to compute the traveled distance
- Get the value of a list variable
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\pythonpython order_picking.py instances\Instance_5_3_15_Random_Central_0.txt
- Execution (Linux)
-
export PYTHONPATH=/opt/hexaly_13_0/bin/pythonpython 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.liborder_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.dllOrderPicking 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.jarjava -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.jarjava -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);
}
}
}