Order Batching Problem
RoutingProblem
The Order Batching Problem arises in warehouse logistics. In this context, a set of orders, each composed of articles located at specific positions in a rectangular warehouse with parallel aisles, must be grouped into batches. For each batch, a picker collects all items in a single tour through the warehouse, starting and ending at the depot.
A key constraint is the picker’s limited carrying capacity : each batch can only include a certain number of items. The distance traveled to complete a batch is determined using the S-shape routing policy, where the picker moves along the full length of each aisle, alternating direction with each pass (front-to-back, then back-to-front).
The overall goal is to organize the orders into batches in a way that minimizes the total distance traveled by the picker.
Principles learned
- Use the JSON module to read the input files
- Use set decision variables to model the batches
- Use lambda functions to compute the number of items in each batch and the aisles visited by the pickers
Data
The instances of the Order Batching Problem are in JSON format.
Each file contains the following fields:
- “instanceName”: Name of the instance
- “nbOrders”: Total number of customer orders
- “capacity”: Maximum number of articles allowed per batch
- “nbAisles”: Number of physical aisles in the warehouse
- “maxNbBatches”: Upper bound on the number of batches
- “aisleTraversal”: Length of each aisle (distance units)
- “gap”: Distance between the center lines of two adjacent aisles
- “depotOffset”: Distance from the depot to the first aisle
- “orders”: This field contains a list of customer orders. Each order is defined by:
- “nbItems” : Number of articles in the order
- “aislesToVisit”: Indices of the aisles that must be visited to collect the order
We rely on standard JSON libraries to read instance data and write solution outputs for each API model:
- C#: Newtonsoft.Json
- Java: gson 2.8.8
- Python: json
- C++: nlohmann/json.hpp
For the Hexaly Modeler, the built‑in JSON module is used.
Model
The Hexaly model for the Order Batching Problem uses set decision variables, with each set representing the orders assigned to a batch.
First, these sets are constrained to form a partition, meaning each order must be assigned to exactly one batch.
We compute the total number of items in a batch using a variadic sum operator over the set and a lambda function that returns the number of items in each order. Note that the number of terms in this sum varies during the search, along with the size of the set. We can then constrain the total number of items to be lower than the picker’s capacity.
Then, for each batch, we determine which aisles must be visited. An aisle is visited if at least one order in the batch has items stored in that aisle. Based on this, we compute the number of aisles visited and the index of the farthest aisle visited thanks to another lambda function.
Then, the S-shape traversal distance for a batch is computed as follows: the picker starts at the depot, first travels horizontally to reach the farthest visited aisle (distance proportional to gap x maxVisitedAisle), then traverses the full length of every visited aisle (adjusting for parity so the picker always exits on the front side), and finally returns to the depot (see below). In the special case where the batch is empty, its distance is zero.
Overall, the objective is to minimize the total distance traveled for all batches.

- Execution
-
hexaly order_batching_problem.hxm inFileName=instances/15s-50-40-0.json [hxTimeLimit=] [solFileName=]
// Copyright (c) Hexaly. Permission is hereby granted to use, copy,
// and modify this code for applications developed with Hexaly.
use io;
use json;
// Read input
function input() {
local usage = "Usage: hexaly order_batching_problem.hxm "
+ "inFileName=inputFile [solFileName=outputFile] [hxTimeLimit=timeLimit]";
if (inFileName == nil) throw usage;
local data = json.parse(inFileName);
nbOrders = data["nbOrders"]; // Number of orders
capacity = data["capacity"]; // Capacity of the picker
nbAisles = data["nbAisles"]; // Number of aisles
maxNbBatches = data["maxNbBatches"]; // Upper bound on the number of batches
aisleTraversal = data["aisleTraversal"]; // Length of each aisle
depotOffset = data["depotOffset"]; // Distance from the depot to the first aisle
gap = data["gap"]; // Distance between the center lines and two adjacent aisles
// Aisles visited by each order
local orders = data["orders"];
for [i in 0...nbOrders] {
nbItems[i] = orders[i]["nbItems"]; // Number of items in each order
for [a in 0...nbAisles] {
visitsAisle[a][i] = 0;
}
local aisles = orders[i]["aislesToVisit"];
for [j in 0...aisles.count()] {
visitsAisle[aisles[j]][i] = 1;
}
}
}
// Declare the optimization model
function model() {
// Set decision: batch[k] contains orders assigned to batch k
batch[k in 0...maxNbBatches] <- set(nbOrders);
// Partition constraint: each order is assigned to exactly one batch
constraint partition[k in 0...maxNbBatches](batch[k]);
for [k in 0...maxNbBatches] {
// Capacity constraint: total number of items of orders assigned to batch k is limited
local batchItems <- sum(batch[k], i => nbItems[i]);
constraint batchItems <= capacity;
// Expressions for objective function
// Batch size
batchCount[k] <- count(batch[k]);
// Check which aisles are visited by current batch k
for [a in 0...nbAisles] {
visited[k][a] <- sum(batch[k], i => visitsAisle[a][i]) >= 1;
}
// Number of visited aisles
nbVisited[k] <- sum[a in 0...nbAisles](visited[k][a]);
// Farthest visited aisle
maxVisited[k] <- max[a in 0...nbAisles](visited[k][a] * a);
// S-shape traversal distance (full traversal of every visited aisle)
distance[k] <- (batchCount[k] > 0) * (
2 * depotOffset
+ 2 * gap * maxVisited[k]
+ (nbVisited[k] + nbVisited[k] % 2) * aisleTraversal
);
}
// Minimize total distance
totalDistance <- sum[k in 0...maxNbBatches](distance[k]);
minimize totalDistance;
}
// Parameterize the solver
function param() {
if (hxTimeLimit == nil) hxTimeLimit = 20;
}
// Write the solution in a JSON file
// - Objective function
// - Content of each batch
// | Computed orders
// | Travelled Distance
// | Visited Aisles
function output() {
if (hxSolution.status == "OPTIMAL" || hxSolution.status == "FEASIBLE") {
if (solFileName == nil) return;
local solFile = io.openWrite(solFileName);
solFile.println("{");
solFile.println(" \"objective\": " + totalDistance.value + ",");
solFile.println(" \"batches\": [");
local first = true;
for [k in 0...maxNbBatches] {
local c = batchCount[k].value;
if (c > 0) {
if (!first) solFile.println(",");
first = false;
local aisleStr = "";
local firstAisle = true;
for [a in 0...nbAisles] {
if (visited[k][a].value == 1) {
if (!firstAisle) aisleStr += ",";
firstAisle = false;
aisleStr += a;
}
}
solFile.print(" {\"orders\": [");
for [j in 0...c] {
if (j > 0) solFile.print(", ");
solFile.print(batch[k].value[j]);
}
solFile.print("], \"distance\": " + distance[k].value);
solFile.print(", \"visitedAisles\": [" + aisleStr + "]}");
}
}
solFile.println("");
solFile.println(" ]");
solFile.println("}");
solFile.close();
println("Solution written in file ", solFileName);
} else {
println("No feasible solution have been found within the allowed time.");
}
}
- Execution (Windows)
-
set PYTHONPATH=%HX_HOME%\bin\pythonpython order_batching_problem.py instances\15s-50-40-0.json
- Execution (Linux)
-
export PYTHONPATH=/opt/hexaly_15_0/bin/pythonpython order_batching_problem.py instances/15s-50-40-0.json
# Copyright (c) Hexaly. Permission is hereby granted to use, copy,
# and modify this code for applications developed with Hexaly.
import hexaly.optimizer
import json
import sys
def read_instance(input_file):
with open(input_file) as f:
data = json.load(f)
nb_orders = data["nbOrders"] # Number of orders
capacity = data["capacity"] # Capacity of the picker
nb_aisles = data["nbAisles"] # Number of aisles
max_nb_batches = data["maxNbBatches"] # Upper bound on the number of batches
aisle_traversal = data["aisleTraversal"] # Length of each aisle
depot_offset = data["depotOffset"] # Distance from the depot to the first aisle
gap = data["gap"] # Distance between the center lines and two adjacent aisles
nb_items = [0] * nb_orders # Number of items in each order
visits_aisle = [[0] * nb_orders for _ in range(nb_aisles)] # Aisles visited by each order
orders = data["orders"]
for i in range(nb_orders):
nb_items[i] = orders[i]["nbItems"]
for a in orders[i]["aislesToVisit"]:
visits_aisle[a][i] = 1
return nb_orders, capacity, nb_aisles, max_nb_batches, aisle_traversal, gap, \
depot_offset, nb_items, visits_aisle
# Create and solve the model
def main(input_file, output_file, time_limit):
# Read instance data
nb_orders, capacity, nb_aisles, max_nb_batches, aisle_traversal, gap, \
depot_offset, nb_items, visits_aisle = read_instance(input_file)
with hexaly.optimizer.HexalyOptimizer() as optimizer:
# Declare the optimization model
model = optimizer.model
# Set decision: batch[k] contains orders assigned to batch k
batch = [model.set(nb_orders) for _ in range(max_nb_batches)]
# Partition constraint: each order is assigned to exactly one batch
model.constraint(model.partition(batch))
# Initialize batch properties
batch_count = [None] * max_nb_batches
distance = [None] * max_nb_batches
visited = [[None] * nb_aisles for _ in range(max_nb_batches)]
# Create arrays for aisle visits
visits_aisle_array = [model.array(visits_aisle[a]) for a in range(nb_aisles)]
# Create lambda function for nbItems (reused across batches)
nb_items_array = model.array(nb_items)
nb_items_lambda = model.lambda_function(lambda i: nb_items_array[i])
for k in range(max_nb_batches):
# Capacity constraint: total number of items of orders assigned to batch k is limited
batch_items = model.sum(batch[k], nb_items_lambda)
model.constraint(batch_items <= capacity)
# Expressions for objective function
# Batch size
batch_count[k] = model.count(batch[k])
# Check which aisles are visited by current batch k
for a in range(nb_aisles):
arr = visits_aisle_array[a]
visited[k][a] = model.sum(batch[k], model.lambda_function(lambda i: arr[i])) >= 1
# Number of visited aisles
nb_visited = model.sum(visited[k])
# Farthest visited aisle
max_visited = model.max(visited[k][a] * a for a in range(nb_aisles))
# S-shape traversal distance (full traversal of every visited aisle)
distance[k] = (batch_count[k] > 0) * (
2 * depot_offset
+ 2 * gap * max_visited
+ (nb_visited + nb_visited % 2) * aisle_traversal
)
# Minimize total distance
total_distance = model.sum(distance)
model.minimize(total_distance)
model.close()
# Parameterize the solver
optimizer.param.time_limit = time_limit
optimizer.solve()
# Write the solution in a JSON file
# - Objective function
# - Content of each batch
# | Computed orders
# | Travelled Distance
# | Visited Aisles
feasible_list = ["HxSolutionStatus.FEASIBLE", "HxSolutionStatus.OPTIMAL"]
if (str(optimizer.solution.status) in feasible_list):
if output_file is not None:
sol = {"objective": int(total_distance.value), "batches": []}
for k in range(max_nb_batches):
c = batch_count[k].value
if c > 0:
sol["batches"].append({
"orders": list(batch[k].value),
"distance": int(distance[k].value),
"visitedAisles": [
a for a in range(nb_aisles)
if visited[k][a].value == 1
],
})
with open(output_file, "w") as f:
json.dump(sol, f, indent = 2)
f.write("\n")
print("Solution written in file ", output_file)
else:
print("No feasible solution have been found within the allowed time.")
if __name__ == "__main__":
if len(sys.argv) < 2:
print("Usage: python order_batching_problem.py inputFile [outputFile] [timeLimit]")
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 20
main(input_file, output_file, time_limit)
- Compilation / Execution (Windows)
-
cl /EHsc order_batching_problem.cpp -I%HX_HOME%\include /link %HX_HOME%\bin\hexaly150.liborder_batching_problem instances\15s-50-40-0.json
- Compilation / Execution (Linux)
-
g++ order_batching_problem.cpp -I/opt/hexaly_15_0/include -lhexaly150 -lpthread -o order_batching_problem./order_batching_problem instances/15s-50-40-0.json
// Copyright (c) Hexaly. Permission is hereby granted to use, copy,
// and modify this code for applications developed with Hexaly.
#include "optimizer/hexalyoptimizer.h"
#include "json.hpp"
#include <fstream>
#include <iostream>
#include <vector>
using namespace hexaly;
using namespace std;
using json = nlohmann::json;
class OrderBatchingProblem {
public:
// Instance data
int nbOrders; // Number of orders
int capacity; // Capacity of the picker
int nbAisles; // Number of aisles
int maxNbBatches; // Upper bound on the number of batches
double aisleTraversal; // Length of each aisle
double gap; // Distance between the center lines and two adjacent aisles
double depotOffset; // Distance from the depot to the first aisle
vector<int> nbItems; // Number of items in each order
vector<vector<int>> visitsAisle; // Aisles visited by each order
// Hexaly Optimizer
HexalyOptimizer optimizer;
// Decision variables
vector<HxExpression> batch;
// Size of each batch
vector<HxExpression> batchCount;
// Aisles visited by each batch
vector<vector<HxExpression>> visited;
// Distance travelled by each batch
vector<HxExpression> distance;
// Objective function : total travelled distance
HxExpression totalDistance;
// Read instance data
void readInstance(const string& fileName) {
std::ifstream infile(fileName);
if (!infile.is_open()) {
throw std::runtime_error("Cannot open file: " + fileName);
}
json data = json::parse(infile);
infile.close();
nbOrders = data["nbOrders"];
capacity = data["capacity"];
nbAisles = data["nbAisles"];
maxNbBatches = data["maxNbBatches"];
aisleTraversal = data["aisleTraversal"];
gap = data["gap"];
depotOffset = data["depotOffset"];
nbItems.resize(nbOrders);
visitsAisle.resize(nbAisles, vector<int>(nbOrders, 0));
for (int i = 0; i < nbOrders; ++i) {
nbItems[i] = data["orders"][i]["nbItems"];
for (int a : data["orders"][i]["aislesToVisit"]) {
visitsAisle[a][i] = 1;
}
}
}
// Create and solve the model
void solve(int timeLimit) {
// Declare the optimization model
HxModel model = optimizer.getModel();
// Set decision: batch[k] contains orders assigned to batch k
batch.resize(maxNbBatches);
for (int k = 0; k < maxNbBatches; ++k) {
batch[k] = model.setVar(nbOrders);
}
// Partition constraint: each order is assigned to exactly one batch
model.constraint(model.partition(batch.begin(), batch.end()));
// Initialize batch properties
batchCount.resize(maxNbBatches);
distance.resize(maxNbBatches);
visited.resize(maxNbBatches);
// Create arrays for aisle visits
vector<HxExpression> visitsAisleArray(nbAisles);
for (int a = 0; a < nbAisles; ++a) {
visitsAisleArray[a] = model.array(visitsAisle[a].begin(), visitsAisle[a].end());
}
// Create lambda function for nbItems (reused across batches)
HxExpression nbItemsArray = model.array(nbItems.begin(), nbItems.end());
HxExpression nbItemsLambda = model.createLambdaFunction(
[&](HxExpression i) { return model.at(nbItemsArray, i); });
for (int k = 0; k < maxNbBatches; ++k) {
// Capacity constraint: total number of items of orders assigned to batch k is limited
HxExpression batchItems = model.sum(batch[k], nbItemsLambda);
model.constraint(batchItems <= capacity);
// Expressions for objective function
// Batch size
batchCount[k] = model.count(batch[k]);
// Check which aisles are visited by current batch k
visited[k].resize(nbAisles);
for (int a = 0; a < nbAisles; ++a) {
HxExpression visitedLambda = model.createLambdaFunction(
[&](HxExpression i) { return model.at(visitsAisleArray[a], i); });
visited[k][a] = model.sum(batch[k], visitedLambda) >= 1;
}
// Number of visited aisles
HxExpression nbVisited = model.sum();
for (int a = 0; a < nbAisles; ++a) {
nbVisited.addOperand(visited[k][a]);
}
// Farthest visited aisle
HxExpression maxVisited = model.max();
for (int a = 0; a < nbAisles; ++a) {
maxVisited.addOperand(visited[k][a] * a);
}
// S-shape traversal distance (full traversal of every visited aisle)
distance[k] = (batchCount[k] > 0) * (
2 * depotOffset
+ 2 * gap * maxVisited
+ (nbVisited + nbVisited % 2) * aisleTraversal);
}
// Minimize total distance
totalDistance = model.sum();
for (int k = 0; k < maxNbBatches; ++k) {
totalDistance.addOperand(distance[k]);
}
model.minimize(totalDistance);
model.close();
// Parameterize the solver
optimizer.getParam().setTimeLimit(timeLimit);
optimizer.solve();
}
// Write the solution in a JSON file
// - Objective function
// - Content of each batch
// | Computed orders
// | Travelled Distance
// | Visited Aisles
void writeSolution(const string& fileName) {
if (optimizer.getSolution().getStatus() == 2
|| optimizer.getSolution().getStatus() == 3) {
json sol;
sol["objective"] = (long long) totalDistance.getDoubleValue();
sol["batches"] = json::array();
for (int k = 0; k < maxNbBatches; ++k) {
int c = (int) batchCount[k].getValue();
if (c > 0) {
json b;
HxCollection batchValue = batch[k].getCollectionValue();
b["orders"] = json::array();
for (int j = 0; j < c; ++j)
b["orders"].push_back(batchValue[j]);
b["distance"] = (long long) distance[k].getDoubleValue();
b["visitedAisles"] = json::array();
for (int a = 0; a < nbAisles; ++a)
if (visited[k][a].getValue() == 1)
b["visitedAisles"].push_back(a);
sol["batches"].push_back(b);
}
}
ofstream outfile(fileName);
outfile << sol.dump(2) << endl;
printf("Solution written in file %s\n\n", fileName.c_str());
} else {
printf("No feasible solution have been found within the allowed time.\n");
}
}
};
int main(int argc, char** argv) {
if (argc < 2) {
cerr << "Usage: order_batching_problem 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";
try {
OrderBatchingProblem model;
model.readInstance(instanceFile);
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 %HX_HOME%\bin\Hexaly.NET.dll .csc OrderBatchingProblem.cs /reference:Hexaly.NET.dllOrderBatchingProblem instances\15s-50-40-0.json
// Copyright (c) Hexaly. Permission is hereby granted to use, copy,
// and modify this code for applications developed with Hexaly.
using System;
using System.Collections.Generic;
using System.IO;
using Hexaly.Optimizer;
using Newtonsoft.Json.Linq;
public class OrderBatchingProblem : IDisposable
{
// Instance data
int nbOrders; // Number of orders
int capacity; // Capacity of the picker
int nbAisles; // Number of aisles
int maxNbBatches; // Upper bound on the number of batches
double aisleTraversal; // Length of each aisle
double gap; // Distance between the center lines and two adjacent aisles
double depotOffset; // Distance from the depot to the first aisle
int[] nbItems; // Number of items in each order
int[][] visitsAisle; // Aisles visited by each order
// Hexaly Optimizer
HexalyOptimizer optimizer;
HxExpression[] batch; // Decision variables
HxExpression[] batchCount; // Size of each batch
HxExpression[][] visited; // Aisles visited by each batch
HxExpression[] distance; // Distance travelled by each batch
// Objective function : total travelled distance
HxExpression totalDistance;
public OrderBatchingProblem()
{
optimizer = new HexalyOptimizer();
}
public void Dispose()
{
if (optimizer != null) optimizer.Dispose();
}
// Read instance data
void ReadInstance(string fileName)
{
JObject data = JObject.Parse(File.ReadAllText(fileName));
nbOrders = data["nbOrders"].Value<int>();
capacity = data["capacity"].Value<int>();
nbAisles = data["nbAisles"].Value<int>();
maxNbBatches = data["maxNbBatches"].Value<int>();
aisleTraversal = data["aisleTraversal"].Value<double>();
gap = data["gap"].Value<double>();
depotOffset = data["depotOffset"].Value<double>();
nbItems = new int[nbOrders];
visitsAisle = new int[nbAisles][];
for (int a = 0; a < nbAisles; ++a)
{
visitsAisle[a] = new int[nbOrders];
}
JArray orders = (JArray)data["orders"];
for (int i = 0; i < nbOrders; ++i)
{
nbItems[i] = orders[i]["nbItems"].Value<int>();
JArray aisles = (JArray)orders[i]["aislesToVisit"];
foreach (var a in aisles)
{
visitsAisle[a.Value<int>()][i] = 1;
}
}
}
// Create and solve the model
void Solve(int limit)
{
// Declare the optimization model
HxModel model = optimizer.GetModel();
// Set decision: batch[k] contains orders assigned to batch k
batch = new HxExpression[maxNbBatches];
for (int k = 0; k < maxNbBatches; ++k)
{
batch[k] = model.Set(nbOrders);
}
// Partition constraint: each order is assigned to exactly one batch
model.Constraint(model.Partition(batch));
// Initialize batch properties
batchCount = new HxExpression[maxNbBatches];
distance = new HxExpression[maxNbBatches];
visited = new HxExpression[maxNbBatches][];
// Create arrays for aisle visits
HxExpression[] visitsAisleArray = new HxExpression[nbAisles];
for (int a = 0; a < nbAisles; ++a)
{
long[] col = new long[nbOrders];
for (int i = 0; i < nbOrders; ++i)
{
col[i] = visitsAisle[a][i];
}
visitsAisleArray[a] = model.Array(col);
}
// Create lambda function for nbItems (reused across batches)
long[] nbItemsLong = new long[nbOrders];
for (int i = 0; i < nbOrders; ++i)
{
nbItemsLong[i] = nbItems[i];
}
HxExpression nbItemsArray = model.Array(nbItemsLong);
HxExpression nbItemsLambda = model.LambdaFunction(i => nbItemsArray[i]);
for (int k = 0; k < maxNbBatches; ++k)
{
// Capacity constraint: total number of items of orders assigned to batch k is limited
HxExpression batchItems = model.Sum(batch[k], nbItemsLambda);
model.Constraint(batchItems <= capacity);
// Expressions for objective function
// Batch size
batchCount[k] = model.Count(batch[k]);
// Check which aisles are visited by current batch k
visited[k] = new HxExpression[nbAisles];
for (int a = 0; a < nbAisles; ++a)
{
int localA = a;
HxExpression visitedLambda = model.LambdaFunction(i => visitsAisleArray[localA][i]);
visited[k][a] = model.Sum(batch[k], visitedLambda) >= 1;
}
// Number of visited aisles
HxExpression nbVisited = model.Sum();
for (int a = 0; a < nbAisles; ++a)
{
nbVisited.AddOperand(visited[k][a]);
}
// Farthest visited aisle
HxExpression maxVisited = model.Max();
for (int a = 0; a < nbAisles; ++a)
{
maxVisited.AddOperand(visited[k][a] * a);
}
// S-shape traversal distance (full traversal of every visited aisle)
distance[k] = (batchCount[k] > 0) * (
2 * depotOffset
+ 2 * gap * maxVisited
+ (nbVisited + nbVisited % 2) * aisleTraversal);
}
// Minimize total distance
totalDistance = model.Sum();
for (int k = 0; k < maxNbBatches; ++k)
{
totalDistance.AddOperand(distance[k]);
}
model.Minimize(totalDistance);
model.Close();
// Parametrize the solver
optimizer.GetParam().SetTimeLimit(limit);
optimizer.Solve();
}
// Write the solution in a JSON file
// - Objective function
// - Content of each batch
// | Computed orders
// | Travelled Distance
// | Visited Aisles
void WriteSolution(string fileName)
{
if (optimizer.GetSolution().GetStatus() == HxSolutionStatus.Feasible
|| optimizer.GetSolution().GetStatus() == HxSolutionStatus.Optimal)
{
var sol = new JObject();
sol["objective"] = (long)totalDistance.GetDoubleValue();
var batchesArr = new JArray();
for (int k = 0; k < maxNbBatches; ++k)
{
int c = (int)batchCount[k].GetValue();
if (c > 0)
{
var b = new JObject();
var orders = new JArray();
HxCollection batchValue = batch[k].GetCollectionValue();
for (int j = 0; j < c; ++j)
{
orders.Add(batchValue[j]);
}
b["orders"] = orders;
b["distance"] = (long)distance[k].GetDoubleValue();
var visitedAisles = new JArray();
for (int a = 0; a < nbAisles; ++a)
{
if (visited[k][a].GetValue() == 1)
{
visitedAisles.Add(a);
}
}
b["visitedAisles"] = visitedAisles;
batchesArr.Add(b);
}
}
sol["batches"] = batchesArr;
File.WriteAllText(fileName, sol.ToString(Newtonsoft.Json.Formatting.Indented) + "\n");
Console.WriteLine("Solution written in file " + fileName);
}
else
{
Console.WriteLine("No feasible solution have been found within the allowed time.");
}
}
public static void Main(string[] args)
{
if (args.Length < 1)
{
Console.Error.WriteLine("Usage: OrderBatchingProblem 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] : "20";
using (OrderBatchingProblem model = new OrderBatchingProblem())
{
model.ReadInstance(instanceFile);
model.Solve(int.Parse(strTimeLimit));
if (outputFile != null)
{
model.WriteSolution(outputFile);
}
}
}
}
- Compilation / Execution (Windows)
-
javac OrderBatchingProblem.java -cp %HX_HOME%\bin\hexaly.jarjava -cp %HX_HOME%\bin\hexaly.jar;. OrderBatchingProblem instances\15s-50-40-0.json
- Compilation / Execution (Linux)
-
javac OrderBatchingProblem.java -cp /opt/hexaly_15_0/bin/hexaly.jarjava -cp /opt/hexaly_15_0/bin/hexaly.jar:. OrderBatchingProblem instances/15s-50-40-0.json
// Copyright (c) Hexaly. Permission is hereby granted to use, copy,
// and modify this code for applications developed with Hexaly.
import java.util.*;
import java.io.*;
import com.hexaly.optimizer.*;
import com.google.gson.*;
import com.google.gson.stream.*;
public class OrderBatchingProblem {
// Instance data
private int nbOrders; // Number of orders
private int capacity; // Capacity of the picker
private int nbAisles; // Number of aisles
private int maxNbBatches; // Upper bound on the number of batches
private double aisleTraversal; // Length of each aisle
private double gap; // Distance between the center lines and two adjacent aisles
private double depotOffset; // Distance from the depot to the first aisle
private int[] nbItems; // Number of items in each order
private int[][] visitsAisle; // Aisles visited by each order
// Hexaly Optimizer
private final HexalyOptimizer optimizer;
// Decision variables
private HxExpression[] batch;
// Size of each batch
private HxExpression[] batchCount;
// Aisles visited by each batch
private HxExpression[][] visited;
// Distance travelled by each batch
private HxExpression[] distance;
// Objective function : total travelled distance
private HxExpression totalDistance;
private OrderBatchingProblem(HexalyOptimizer optimizer) {
this.optimizer = optimizer;
}
// Read instance data
private void readInstance(String fileName) throws IOException {
JsonReader reader = new JsonReader(
new InputStreamReader(new FileInputStream(fileName)));
JsonObject data = JsonParser.parseReader(reader).getAsJsonObject();
nbOrders = data.get("nbOrders").getAsInt();
capacity = data.get("capacity").getAsInt();
nbAisles = data.get("nbAisles").getAsInt();
maxNbBatches = data.get("maxNbBatches").getAsInt();
aisleTraversal = data.get("aisleTraversal").getAsDouble();
gap = data.get("gap").getAsDouble();
depotOffset = data.get("depotOffset").getAsDouble();
nbItems = new int[nbOrders];
visitsAisle = new int[nbAisles][nbOrders];
JsonArray orders = (JsonArray) data.get("orders");
for (int i = 0; i < nbOrders; ++i) {
JsonObject order = (JsonObject) orders.get(i);
nbItems[i] = order.get("nbItems").getAsInt();
JsonArray aisles = (JsonArray) order.get("aislesToVisit");
for (int j = 0; j < aisles.size(); ++j) {
int a = aisles.get(j).getAsInt();
visitsAisle[a][i] = 1;
}
}
}
// Create and solve the model
private void solve(int limit) {
// Declare the optimization model
HxModel model = optimizer.getModel();
// Set decision: batch[k] contains orders assigned to batch k
batch = new HxExpression[maxNbBatches];
for (int k = 0; k < maxNbBatches; ++k) {
batch[k] = model.setVar(nbOrders);
}
// Partition constraint: each order is assigned to exactly one batch
model.constraint(model.partition(batch));
// Initialize batch properties
batchCount = new HxExpression[maxNbBatches];
distance = new HxExpression[maxNbBatches];
visited = new HxExpression[maxNbBatches][];
// Create arrays for aisle visits
HxExpression[] visitsAisleArray = new HxExpression[nbAisles];
for (int a = 0; a < nbAisles; ++a) {
long[] col = new long[nbOrders];
for (int i = 0; i < nbOrders; ++i) {
col[i] = visitsAisle[a][i];
}
visitsAisleArray[a] = model.array(col);
}
// Create lambda function for nbItems (reused across batches)
long[] nbItemsLong = new long[nbOrders];
for (int i = 0; i < nbOrders; ++i) {
nbItemsLong[i] = nbItems[i];
}
HxExpression nbItemsArray = model.array(nbItemsLong);
HxExpression nbItemsLambda = model.lambdaFunction(
i -> model.at(nbItemsArray, i));
for (int k = 0; k < maxNbBatches; ++k) {
// Capacity constraint: total number of items of orders assigned to batch k is limited
HxExpression batchItems = model.sum(batch[k], nbItemsLambda);
model.constraint(model.leq(batchItems, capacity));
// Expressions for objective function
// Batch size
batchCount[k] = model.count(batch[k]);
// Check which aisles are visited by current batch k
visited[k] = new HxExpression[nbAisles];
for (int a = 0; a < nbAisles; ++a) {
final int localA = a;
HxExpression visitedLambda = model.lambdaFunction(
i -> model.at(visitsAisleArray[localA], i));
visited[k][a] = model.geq(model.sum(batch[k], visitedLambda), 1);
}
// Number of visited aisles
HxExpression nbVisited = model.sum();
for (int a = 0; a < nbAisles; ++a) {
nbVisited.addOperand(visited[k][a]);
}
// Farthest visited aisle
HxExpression maxVisited = model.max();
for (int a = 0; a < nbAisles; ++a) {
maxVisited.addOperand(model.prod(visited[k][a], a));
}
// S-shape traversal distance (full traversal of every visited aisle)
distance[k] = model.prod(
model.gt(batchCount[k], 0),
model.sum(
model.sum(2 * depotOffset, model.prod(2 * gap, maxVisited)),
model.prod(
model.sum(nbVisited, model.mod(nbVisited, 2)),
aisleTraversal)));
}
// Minimize total distance
totalDistance = model.sum();
for (int k = 0; k < maxNbBatches; ++k) {
totalDistance.addOperand(distance[k]);
}
model.minimize(totalDistance);
model.close();
// Parameterize the solver
optimizer.getParam().setTimeLimit(limit);
optimizer.solve();
}
// Write the solution in a JSON file
// - Objective function
// - Content of each batch
// | Computed orders
// | Travelled Distance
// | Visited Aisles
private void writeSolution(String fileName) throws IOException {
JsonObject sol = new JsonObject();
sol.addProperty("objective", (long) totalDistance.getDoubleValue());
JsonArray batchesArr = new JsonArray();
for (int k = 0; k < maxNbBatches; ++k) {
int c = (int) batchCount[k].getValue();
if (c > 0) {
JsonObject b = new JsonObject();
JsonArray orders = new JsonArray();
HxCollection batchValue = batch[k].getCollectionValue();
for (int j = 0; j < c; ++j) {
orders.add(batchValue.get(j));
}
b.add("orders", orders);
b.addProperty("distance", (long) distance[k].getDoubleValue());
JsonArray visitedAisles = new JsonArray();
for (int a = 0; a < nbAisles; ++a) {
if (visited[k][a].getValue() == 1) {
visitedAisles.add(a);
}
}
b.add("visitedAisles", visitedAisles);
batchesArr.add(b);
}
}
sol.add("batches", batchesArr);
try (PrintWriter output = new PrintWriter(fileName)) {
Gson gson = new GsonBuilder().setPrettyPrinting().create();
output.println(gson.toJson(sol));
}
}
public static void main(String[] args) {
if (args.length < 1) {
System.err.println("Usage: OrderBatchingProblem 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 (HexalyOptimizer optimizer = new HexalyOptimizer()) {
OrderBatchingProblem model = new OrderBatchingProblem(optimizer);
model.readInstance(instanceFile);
model.solve(Integer.parseInt(strTimeLimit));
if (optimizer.getSolution().getStatus() == HxSolutionStatus.Feasible
|| optimizer.getSolution().getStatus() == HxSolutionStatus.Optimal) {
if (outputFile != null) {
model.writeSolution(outputFile);
}
} else {
System.err.println("No feasible solution have been found within the allowed time.");
}
} catch (Exception ex) {
System.err.println(ex);
ex.printStackTrace();
System.exit(1);
}
}
}