Truck Loading Problem
PackingProblem
In the Truck Loading Problem, a given set of items must be loaded into trucks. Thus, each item must be assigned to exactly one truck.
Each truck has two levels: a floor level and an upper level. Each level contains the same number of item positions, and each truck has the same weight capacity. Therefore, the total weight of the items assigned to a truck must not exceed this capacity.
In addition to their weight, items may have stacking restrictions:
- Type 1 items must be placed on the floor and cannot have another item on top of them. They are called “Alone” items.
- Type 2 items must be placed on the floor, but may have another item on top of them. They are called “Floor” items.
- Type 3 items have no stacking restriction. They are called “No-restriction” items.
- Type 4 items cannot have another item on top of them, but may be placed either on the floor or on the upper level. They are called “Delicate” items.
Finally, the upper level of a truck can be used only when its floor level is full. The objective is to minimize the number of used trucks.
Principles learned
- Add set decision variables to model the contents of the bins
- Define lambda functions to compute the total weight of a truck and enforce the stacking restrictions
- Use the solution found by Hexaly Optimizer in a post-processing function
Data
The provided instances for the Truck Loading Problem are adapted from the Falkenauer instances from the BPPLIB. The format of the data files is as follows:
- The first line contains a single integer: the number of items,
- The second line contains two integers: the weight capacity of each truck, and the number of item positions of each level of a truck,
- Then, each of the following lines describes one item using two integers:
- First, its weight,
- Then, an integer between 1 and 4 (inclusive) specifying its type:
- 1 for “Alone” items,
- 2 for “Floor” items,
- 3 for “No-restriction” items,
- or 4 for “Delicate” items.
Model
The Hexaly model for the Truck Loading Problem uses set decision variables. For each truck, we define a set variable that represents the items assigned to it. We constrain the set variables to form a partition, ensuring that each item belongs to exactly one truck.
We compute the total weight of a truck using a variadic sum operator on the set and a lambda function returning the weight associated with any item index. 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 weight not to exceed the truck capacity.
“Alone” and “Floor” items must be on the floor of their truck. Thus, the model ensures that they do not exceed the number of positions per level in any truck. We then add a similar constraint for “Alone” and “Delicate” items, which cannot have another item on top of them.
“Alone” items must be placed on the floor and block the corresponding above positions, since they cannot have another item above them. Therefore, we consider that they use two position units instead of one. The model then ensures that the number of blocked positions in each truck does not exceed the total number of positions.
We compute the total number of trucks used thanks to the count operator, which returns the number of elements in a set.
The model only gives the set of items to load in each truck. The exact placement of items within the trucks is computed separately by a post-processing function. It loads the “Alone” and “Floor” items first to ensure they are placed on the floor, then the “No-restriction” items, and finally the “Delicate” items, which are guaranteed to be on top.
- Execution
-
hexaly truck_loading.hxm inFileName=instances/t60_00.txt [hxTimeLimit=] [solFileName=]
// Copyright (c) Hexaly. Permission is hereby granted to use, copy,
// and modify this code for applications developed with Hexaly.
use io;
use math;
/* Read instance data */
function input() {
local usage = "Usage: hexaly truck_loading.hxm "
+ "inFileName=inputFile [solFileName=outputFile] [hxTimeLimit=timeLimit]";
if (inFileName == nil) throw usage;
local inFile = io.openRead(inFileName);
nbItems = inFile.readInt();
truckWeightCapacity = inFile.readInt();
levelSpotsCount = inFile.readInt();
for [i in 0...nbItems] {
itemWeights[i] = inFile.readInt();
categoryItems[i] = inFile.readInt();
// Type 1 items block both a floor position and the upper position above it
volumes[i] = (categoryItems[i] == 1 ? 2 : 1);
}
// Bounds on the number of used trucks
nbMinTrucks = ceil(sum[i in 0...nbItems](itemWeights[i]) / truckWeightCapacity);
nbMaxTrucks = nbItems;
}
/* Declare the optimization model */
function model() {
// Set decisions: trucks[k] represents the set of items assigned to truck k
trucks[k in 0...nbMaxTrucks] <- set(nbItems);
// Each item must be assigned to exactly one truck
constraint partition[k in 0...nbMaxTrucks](trucks[k]);
for [k in 0...nbMaxTrucks] {
// Weight constraint for each truck
truckWeights[k] <- sum(trucks[k], i => itemWeights[i]);
constraint truckWeights[k] <= truckWeightCapacity;
// Volume constraint for each truck
constraint sum(trucks[k], i => volumes[i]) <= 2 * levelSpotsCount;
// Type 1 and type 2 items must be placed on the floor level
constraint sum(trucks[k], i => or((categoryItems[i] == 1), (categoryItems[i] == 2))) <= levelSpotsCount;
// Type 1 and type 4 items cannot have another item on top of them
constraint sum(trucks[k], i => or((categoryItems[i] == 1), (categoryItems[i] == 4))) <= levelSpotsCount;
// Truck k is used if at least one item is in it
isUsed[k] <- (count(trucks[k]) > 0);
}
// Count the used trucks
totalUsedTrucks <- sum[k in 0...nbMaxTrucks](isUsed[k]);
// Minimize the number of used trucks
minimize totalUsedTrucks;
}
/* Parametrize the solver */
function param() {
if (hxTimeLimit == nil) hxTimeLimit = 10;
// Stop the search if the lower threshold is reached
hxObjectiveThreshold = nbMinTrucks;
}
/*
Auxiliary function used in the later "insideTruck" function.
Inserts an item into the next free slot in the truck.
*/
function placeFirstAvailPos(positions, item) {
if (positions[0].count() < levelSpotsCount) positions[0].add(item);
else positions[1].add(item);
}
/*
The optimizer assigns items to trucks but does not position them inside each truck.
This function computes a feasible two-level placement for the items in a truck.
*/
function insideTruck(truckSet) {
// Split the items assigned to the truck by category
setA = {}; // Type 1: floor level and nothing above. Called "Alone" items
setF = {}; // Type 2: floor level. Called "Floor" items
setN = {}; // Type 3: no restriction. Called "No-restriction" items
setD = {}; // Type 4: nothing above. Called "Delicate" items
for[i in truckSet] {
if (categoryItems[i] == 1) setA.add(i);
else if (categoryItems[i] == 2) setF.add(i);
else if (categoryItems[i] == 3) setN.add(i);
else setD.add(i);
}
// Initialize the two-level position map: positions[0] represents the floor level, and positions[1] the upper level
positions[0..1] = {};
// Assign positions to items, from the most constraining category to the least constraining one: A -> F -> N -> D
// Place all "Alone" items on the floor, and leave the corresponding upper positions empty (represented by -1)
for [i in setA] {
positions[0].add(i);
positions[1].add(-1);
}
// Place all "Floor" items on the floor
for [i in setF] positions[0].add(i);
// Place all "No-restriction" items at the first available position
for [i in setN] placeFirstAvailPos(positions, i);
// Place all "Delicate" items at the first available position
for [i in setD] placeFirstAvailPos(positions, i);
return positions;
}
/* Auxiliary function used to display the solution */
function padLeft(x, width) {
local s;
if (x == -1) s = " ";
else s = "" + x;
while (s.length() < width) s = " " + s;
return s;
}
/* Write the solution in a file */
function output() {
if (solFileName == nil) return;
local solFile = io.openWrite(solFileName);
solFile.println("Number of used trucks: " + totalUsedTrucks.value + "\n");
printWidth = ceil(math.log10(nbItems - 1)) + 2;
for [k in 0...nbMaxTrucks] {
if (!isUsed[k].value) continue;
truck = insideTruck(trucks[k].value);
solFile.println("Truck weight: " + truckWeights[k].value);
for [_ in 0...(levelSpotsCount * printWidth)] solFile.print("-");
solFile.println();
for [i in truck[1]] solFile.print(padLeft(i, printWidth));
solFile.println();
for [i in truck[0]] solFile.print(padLeft(i, printWidth));
solFile.println();
for [_ in 0...(levelSpotsCount * printWidth)] solFile.print("-");
solFile.println("\n");
}
}
- Execution (Windows)
-
set PYTHONPATH=%HX_HOME%\bin\pythonpython truck_loading.py instances\t60_00.txt
- Execution (Linux)
-
export PYTHONPATH=/opt/hexaly_15_0/bin/pythonpython truck_loading.py instances/t60_00.txt
# Copyright (c) Hexaly. Permission is hereby granted to use, copy,
# and modify this code for applications developed with Hexaly.
import hexaly.optimizer
import sys
def read_integers(filename):
with open(filename) as f:
return [int(elem) for elem in f.read().split()]
#
# Auxiliary function used in the later "insideTruck" function.
# Inserts an item into the next free slot in the truck.
#
def place_first_available_pos(positions, item, level_spots_count):
if len(positions[0]) < level_spots_count:
positions[0].append(item)
else:
positions[1].append(item)
#
# The optimizer assigns items to trucks but does not position them inside each truck.
# This function computes a feasible two-level placement for the items in a truck.
#
def inside_truck(truck_set, category_items, level_spots_count):
# Split the items assigned to the truck by category
set_a = [] # Type 1: floor level and nothing above. Called "Alone" items
set_f = [] # Type 2: floor level. Called "Floor" items
set_n = [] # Type 3: no restriction. Called "No-restriction" items
set_d = [] # Type 4: nothing above. Called "Delicate" items
for i in truck_set:
category = category_items[i]
if category == 1:
set_a.append(i)
elif category == 2:
set_f.append(i)
elif category == 3:
set_n.append(i)
else:
set_d.append(i)
# Initialize the two-level position list: positions[0] represents the floor level, and positions[1] the upper level
positions = [[], []]
#
# Assign positions to items, from the most constraining category to the least constraining one: A -> F -> N -> D
#
# Place all "Alone" items on the floor, and leave the corresponding upper positions empty (represented by -1)
for i in set_a:
positions[0].append(i)
positions[1].append(-1)
# Place all "Floor" items on the floor
for i in set_f:
positions[0].append(i)
# Place all "No-restriction" items at the first available position
for i in set_n:
place_first_available_pos(positions, i, level_spots_count)
# Place all "Delicate" items at the first available position
for i in set_d:
place_first_available_pos(positions, i, level_spots_count)
return positions
# Auxiliary function used to display the solution
def pad_left(x, width):
return (" " if x == -1 else str(x)).rjust(width)
def solve(instance_file, sol_file, time_limit=10):
#
# Read instance data
#
file_it = iter(read_integers(instance_file))
nb_items = int(next(file_it))
truck_weight_capacity = int(next(file_it))
level_spots_count = int(next(file_it))
item_weights = []
category_items = []
volumes_data = []
for _ in range(nb_items):
weight = int(next(file_it))
category = int(next(file_it))
item_weights.append(weight)
category_items.append(category)
# "Alone" items block both a floor position and the upper position above it
volumes_data.append(2 if category == 1 else 1)
# Bounds on the number of used trucks
nb_min_trucks = (sum(item_weights) + truck_weight_capacity - 1) // truck_weight_capacity
nb_max_trucks = nb_items
#
# Declare the optimization model
#
with hexaly.optimizer.HexalyOptimizer() as optimizer:
model = optimizer.model
# Set decisions: trucks[k] represents the items assigned to truck k
trucks = [model.set(nb_items) for _ in range(nb_max_trucks)]
# Each item must be assigned to exactly one truck
model.constraint(model.partition(trucks))
#
# Create arrays and functions to retrieve the item's data
#
weights = model.array(item_weights)
categories = model.array(category_items)
volumes = model.array(volumes_data)
weight_lambda = model.lambda_function(lambda i: weights[i])
volume_lambda = model.lambda_function(lambda i: volumes[i])
floor_lambda = model.lambda_function(
lambda i: model.or_(categories[i] == 1, categories[i] == 2)
)
delicate_lambda = model.lambda_function(
lambda i: model.or_(categories[i] == 1, categories[i] == 4)
)
truck_weights = []
is_used = []
for k in range(nb_max_trucks):
truck_weight = model.sum(trucks[k], weight_lambda)
truck_weights.append(truck_weight)
# Weight constraint for each truck
model.constraint(truck_weight <= truck_weight_capacity)
# Volume constraint for each truck
model.constraint(model.sum(trucks[k], volume_lambda) <= 2 * level_spots_count)
# Type 1 and type 2 items must be placed on the floor level
model.constraint(model.sum(trucks[k], floor_lambda) <= level_spots_count)
# Type 1 and type 4 items cannot have another item on top of them
model.constraint(model.sum(trucks[k], delicate_lambda) <= level_spots_count)
# Truck k is used if at least one item is in it
is_used.append(model.count(trucks[k]) > 0)
# Count the used trucks
total_used_trucks = model.sum(is_used)
# Minimize the number of used trucks
model.minimize(total_used_trucks)
model.close()
# Parametrize the optimizer
optimizer.param.time_limit = time_limit
# Stop the search if the lower threshold is reached
optimizer.param.set_objective_threshold(0, nb_min_trucks)
optimizer.solve()
#
# Write the solution in a file
#
if sol_file is not None:
print_width = len(str(nb_items - 1)) + 2
with open(sol_file, "w") as f:
f.write(f"Number of used trucks: {total_used_trucks.value}\n\n")
for k in range(nb_max_trucks):
if not is_used[k].value:
continue
truck = inside_truck(trucks[k].value, category_items, level_spots_count)
line = "-" * (level_spots_count * print_width)
f.write(f"Truck weight: {truck_weights[k].value}\n")
f.write(line + "\n")
for item in truck[1]:
f.write(pad_left(item, print_width))
f.write("\n")
for item in truck[0]:
f.write(pad_left(item, print_width))
f.write("\n")
f.write(line + "\n\n")
def main():
if len(sys.argv) < 2:
print("Usage: python truck_loading.py inputFile [outputFile] [timeLimit]")
sys.exit(1)
instance_file = sys.argv[1]
if (len(sys.argv) >= 4):
time_limit = int(sys.argv[3])
else:
time_limit = 10
if (len(sys.argv) >= 3):
solve(instance_file, sys.argv[2], time_limit)
else:
solve(instance_file, None, time_limit)
if __name__ == "__main__":
main()
- Compilation / Execution (Windows)
-
cl /EHsc truck_loading.cpp -I%HX_HOME%\include /link %HX_HOME%\bin\hexaly150.libtruck_loading instances\t60_00.txt
- Compilation / Execution (Linux)
-
g++ truck_loading.cpp -I/opt/hexaly_15_0/include -lhexaly150 -lpthread -o truck_loading./truck_loading instances/t60_00.txt
// Copyright (c) Hexaly. Permission is hereby granted to use, copy,
// and modify this code for applications developed with Hexaly.
#include "optimizer/hexalyoptimizer.h"
#include <cmath>
#include <cstdlib>
#include <fstream>
#include <iostream>
#include <string>
#include <vector>
using namespace hexaly;
using namespace std;
class TruckLoading {
private:
// Number of items
int nbItems;
// Weight capacity of each truck
int truckWeightCapacity;
// Number of available spots per level for each truck
int levelSpotsCount;
// Weight of each item
vector<hxint> itemWeightsData;
// Type (ie. stacking restrictions) of each item
vector<hxint> categoryItemsData;
// Volume occupied by each item
vector<hxint> volumesData;
// Bounds on the number of used trucks
int nbMinTrucks;
int nbMaxTrucks;
// Hexaly Optimizer
HexalyOptimizer optimizer;
// Decision variables and expressions
vector<HxExpression> trucks;
vector<HxExpression> truckWeights;
vector<HxExpression> isUsed;
HxExpression totalUsedTrucks;
struct TruckPlacement {
vector<int> floor;
vector<int> upper;
};
public:
/* Read instance data */
void readInstance(const string& fileName) {
ifstream infile;
infile.exceptions(ifstream::failbit | ifstream::badbit);
infile.open(fileName.c_str());
infile >> nbItems;
infile >> truckWeightCapacity;
infile >> levelSpotsCount;
itemWeightsData.resize(nbItems);
categoryItemsData.resize(nbItems);
volumesData.resize(nbItems);
hxint totalWeight = 0;
for (int i = 0; i < nbItems; ++i) {
infile >> itemWeightsData[i];
infile >> categoryItemsData[i];
// Type 1 items block both a floor position and the upper position above it
volumesData[i] = (categoryItemsData[i] == 1 ? 2 : 1);
totalWeight += itemWeightsData[i];
}
// Bounds on the number of used trucks
nbMinTrucks = (totalWeight + truckWeightCapacity - 1) / truckWeightCapacity;
nbMaxTrucks = nbItems;
}
void solve(int timeLimit) {
// Declare the optimization model
HxModel model = optimizer.getModel();
trucks.resize(nbMaxTrucks);
truckWeights.resize(nbMaxTrucks);
isUsed.resize(nbMaxTrucks);
// Set decisions: trucks[k] represents the set of items assigned to truck k
for (int k = 0; k < nbMaxTrucks; ++k) {
trucks[k] = model.setVar(nbItems);
}
// Each item must be assigned to exactly one truck
model.constraint(model.partition(trucks.begin(), trucks.end()));
// Create arrays and functions to retrieve the item's data
HxExpression itemWeights = model.array(itemWeightsData.begin(), itemWeightsData.end());
HxExpression categoryItems = model.array(categoryItemsData.begin(), categoryItemsData.end());
HxExpression volumes = model.array(volumesData.begin(), volumesData.end());
HxExpression weightLambda = model.lambdaFunction([&](HxExpression i) {
return itemWeights[i];
});
HxExpression volumeLambda = model.lambdaFunction([&](HxExpression i) {
return volumes[i];
});
HxExpression mustBeOnFloorLambda = model.lambdaFunction([&](HxExpression i) {
return model.or_(model.eq(categoryItems[i], 1), model.eq(categoryItems[i], 2));
});
HxExpression cannotHaveAboveLambda = model.lambdaFunction([&](HxExpression i) {
return model.or_(model.eq(categoryItems[i], 1), model.eq(categoryItems[i], 4));
});
for (int k = 0; k < nbMaxTrucks; ++k) {
// Weight constraint for each truck
truckWeights[k] = model.sum(trucks[k], weightLambda);
model.constraint(truckWeights[k] <= truckWeightCapacity);
// Volume constraint for each truck
model.constraint(model.sum(trucks[k], volumeLambda) <= 2 * levelSpotsCount);
// Type 1 and type 2 items must be placed on the floor level
model.constraint(model.sum(trucks[k], mustBeOnFloorLambda) <= levelSpotsCount);
// Type 1 and type 4 items cannot have another item on top of them
model.constraint(model.sum(trucks[k], cannotHaveAboveLambda) <= levelSpotsCount);
// Truck k is used if at least one item is in it
isUsed[k] = model.count(trucks[k]) > 0;
}
// Count the used trucks
totalUsedTrucks = model.sum(isUsed.begin(), isUsed.end());
// Minimize the number of used trucks
model.minimize(totalUsedTrucks);
model.close();
// Parametrize the optimizer
optimizer.getParam().setTimeLimit(timeLimit);
// Stop the search if the lower threshold is reached
optimizer.getParam().setObjectiveThreshold(0, static_cast<hxint>(nbMinTrucks));
optimizer.solve();
}
private:
/*
Auxiliary function used in the later "insideTruck" function.
Inserts an item into the next free slot in the truck.
*/
void placeFirstAvailPos(TruckPlacement& positions, int item) const {
if (static_cast<int>(positions.floor.size()) < levelSpotsCount) {
positions.floor.push_back(item);
} else {
positions.upper.push_back(item);
}
}
/*
The optimizer assigns items to trucks but does not position them inside each truck.
This function computes a feasible two-level placement for the items in a truck.
*/
TruckPlacement insideTruck(const HxCollection& truckSet) const {
// Split the items assigned to the truck by category.
vector<int> setA; // Type 1: floor level and nothing above. Called "Alone" items
vector<int> setF; // Type 2: floor level. Called "Floor" items
vector<int> setN; // Type 3: no restriction. Called "No-restriction" items
vector<int> setD; // Type 4: nothing above. Called "Delicate" items
for (int p = 0; p < truckSet.count(); ++p) {
int i = static_cast<int>(truckSet[p]);
if (categoryItemsData[i] == 1) {
setA.push_back(i);
} else if (categoryItemsData[i] == 2) {
setF.push_back(i);
} else if (categoryItemsData[i] == 3) {
setN.push_back(i);
} else {
setD.push_back(i);
}
}
TruckPlacement positions;
// Assign positions to items, from the most constraining category to the least constraining one: A -> F -> N -> D
// Place all "Alone" items on the floor, and leave the corresponding upper positions empty, represented by -1
for (int i : setA) {
positions.floor.push_back(i);
positions.upper.push_back(-1);
}
// Place all "Floor" items on the floor
for (int i : setF) {
positions.floor.push_back(i);
}
// Place all "No-restriction" items at the first available position
for (int i : setN) {
placeFirstAvailPos(positions, i);
}
// Place all "Delicate" items at the first available position
for (int i : setD) {
placeFirstAvailPos(positions, i);
}
return positions;
}
/* Auxiliary function used to display the solution */
string padLeft(int x, int width) const {
string s = (x == -1 ? " " : to_string(x));
while (static_cast<int>(s.length()) < width) {
s = " " + s;
}
return s;
}
public:
/* Write the solution in a file */
void writeSolution(const string& fileName) {
ofstream outfile;
outfile.exceptions(ofstream::failbit | ofstream::badbit);
outfile.open(fileName.c_str());
outfile << "Number of used trucks: " << totalUsedTrucks.getValue() << "\n" << endl;
int printWidth = 2;
if (nbItems > 1) {
printWidth = static_cast<int>(ceil(log10(static_cast<double>(nbItems - 1)))) + 2;
}
for (int k = 0; k < nbMaxTrucks; ++k) {
if (!isUsed[k].getValue()) continue;
HxCollection truckCollection = trucks[k].getCollectionValue();
TruckPlacement truck = insideTruck(truckCollection);
outfile << "Truck weight: " << truckWeights[k].getValue() << endl;
for (int i = 0; i < levelSpotsCount * printWidth; ++i) outfile << "-";
outfile << endl;
for (int item : truck.upper) outfile << padLeft(item, printWidth);
outfile << endl;
for (int item : truck.floor) outfile << padLeft(item, printWidth);
outfile << endl;
for (int i = 0; i < levelSpotsCount * printWidth; ++i) outfile << "-";
outfile << "\n" << endl;
}
}
};
int main(int argc, char** argv) {
if (argc < 2) {
cerr << "Usage: truck_loading inputFile [outputFile] [timeLimit]" << endl;
return 1;
}
const char* instanceFile = argv[1];
const char* solFile = (argc > 2 ? argv[2] : NULL);
const int timeLimit = (argc > 3 ? atoi(argv[3]) : 5);
try {
TruckLoading model;
model.readInstance(instanceFile);
model.solve(timeLimit);
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 TruckLoading.cs /reference:Hexaly.NET.dllTruckLoading instances\t60_00.txt
// 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 System.Linq;
using Hexaly.Optimizer;
public class TruckLoading : IDisposable
{
// Number of items
int nbItems;
// Weight capacity of each truck
int truckWeightCapacity;
// Number of available spots per level for each truck
int levelSpotsCount;
// Weight of each item
long[] itemWeightsData;
// Type (ie. stacking restrictions) of each item
long[] categoryItemsData;
// Volume occupied by each item
long[] volumesData;
// Bounds on the number of used trucks
int nbMinTrucks;
int nbMaxTrucks;
// Hexaly Optimizer
HexalyOptimizer optimizer;
// Decision variables and expressions
HxExpression[] trucks;
HxExpression[] truckWeights;
HxExpression[] isUsed;
HxExpression totalUsedTrucks;
struct TruckPlacement
{
public List<int> floor;
public List<int> upper;
public TruckPlacement(bool init)
{
floor = new List<int>();
upper = new List<int>();
}
}
public TruckLoading()
{
optimizer = new HexalyOptimizer();
}
/* Read instance data */
void ReadInstance(string fileName)
{
using (StreamReader input = new StreamReader(fileName))
{
nbItems = int.Parse(input.ReadLine());
string[] secondLine = input.ReadLine()
.Split((char[])null, StringSplitOptions.RemoveEmptyEntries);
truckWeightCapacity = int.Parse(secondLine[0]);
levelSpotsCount = int.Parse(secondLine[1]);
itemWeightsData = new long[nbItems];
categoryItemsData = new long[nbItems];
volumesData = new long[nbItems];
long totalWeight = 0;
for (int i = 0; i < nbItems; ++i)
{
string[] line = input.ReadLine()
.Split((char[])null, StringSplitOptions.RemoveEmptyEntries);
itemWeightsData[i] = long.Parse(line[0]);
categoryItemsData[i] = long.Parse(line[1]);
// Type 1 items block both a floor position and the upper position above it
volumesData[i] = (categoryItemsData[i] == 1 ? 2 : 1);
totalWeight += itemWeightsData[i];
}
// Bounds on the number of used trucks
nbMinTrucks = (int)Math.Ceiling((double)totalWeight / truckWeightCapacity);
nbMaxTrucks = nbItems;
}
}
public void Dispose()
{
if (optimizer != null)
optimizer.Dispose();
}
void Solve(int limit)
{
// Declare the optimization model
HxModel model = optimizer.GetModel();
trucks = new HxExpression[nbMaxTrucks];
truckWeights = new HxExpression[nbMaxTrucks];
isUsed = new HxExpression[nbMaxTrucks];
// Set decisions: trucks[k] represents the set of items assigned to truck k
for (int k = 0; k < nbMaxTrucks; ++k)
{
trucks[k] = model.Set(nbItems);
}
// Each item must be assigned to exactly one truck
model.Constraint(model.Partition(trucks));
/* Create arrays and functions to retrieve the item's data */
HxExpression itemWeights = model.Array(itemWeightsData);
HxExpression categoryItems = model.Array(categoryItemsData);
HxExpression volumes = model.Array(volumesData);
HxExpression weightLambda = model.LambdaFunction(i => itemWeights[i]);
HxExpression volumeLambda = model.LambdaFunction(i => volumes[i]);
HxExpression mustBeOnFloorLambda = model.LambdaFunction(i =>
model.Or(model.Eq(categoryItems[i], 1), model.Eq(categoryItems[i], 2))
);
HxExpression cannotHaveAboveLambda = model.LambdaFunction(i =>
model.Or(model.Eq(categoryItems[i], 1), model.Eq(categoryItems[i], 4))
);
for (int k = 0; k < nbMaxTrucks; ++k)
{
// Weight constraint for each truck
truckWeights[k] = model.Sum(trucks[k], weightLambda);
model.Constraint(truckWeights[k] <= truckWeightCapacity);
// Volume constraint for each truck
model.Constraint(model.Sum(trucks[k], volumeLambda) <= 2 * levelSpotsCount);
// Type 1 and type 2 items must be placed on the floor level
model.Constraint(model.Sum(trucks[k], mustBeOnFloorLambda) <= levelSpotsCount);
// Type 1 and type 4 items cannot have another item on top of them
model.Constraint(model.Sum(trucks[k], cannotHaveAboveLambda) <= levelSpotsCount);
// Truck k is used if at least one item is in it
isUsed[k] = model.Count(trucks[k]) > 0;
}
// Count the used trucks
totalUsedTrucks = model.Sum(isUsed);
// Minimize the number of used trucks
model.Minimize(totalUsedTrucks);
model.Close();
// Parametrize the optimizer
optimizer.GetParam().SetTimeLimit(limit);
// Stop the search if the lower threshold is reached
optimizer.GetParam().SetObjectiveThreshold(0, nbMinTrucks);
optimizer.Solve();
}
/*
Auxiliary function used in the later "insideTruck" function.
Inserts an item into the next free slot in the truck.
*/
void PlaceFirstAvailPos(ref TruckPlacement positions, int item)
{
if (positions.floor.Count < levelSpotsCount)
{
positions.floor.Add(item);
}
else
{
positions.upper.Add(item);
}
}
/*
The optimizer assigns items to trucks but does not position them inside each truck.
This function computes a feasible two-level placement for the items in a truck.
*/
TruckPlacement InsideTruck(HxCollection truckSet)
{
List<int> setA = new List<int>(); // Type 1: floor level and nothing above. Called "Alone" items
List<int> setF = new List<int>(); // Type 2: floor level. Called "Floor" items
List<int> setN = new List<int>(); // Type 3: no restriction. Called "No-restriction" items
List<int> setD = new List<int>(); // Type 4: nothing above. Called "Delicate" items
// Split the items assigned to the truck by category
for (int p = 0; p < truckSet.Count(); ++p)
{
int i = (int)truckSet[p];
if (categoryItemsData[i] == 1)
{
setA.Add(i);
}
else if (categoryItemsData[i] == 2)
{
setF.Add(i);
}
else if (categoryItemsData[i] == 3)
{
setN.Add(i);
}
else
{
setD.Add(i);
}
}
TruckPlacement positions = new TruckPlacement(true);
// Assign positions to items, from the most constraining category to the least constraining one: A -> F -> N -> D
// Place all "Alone" items on the floor, and leave the corresponding upper positions empty, represented by -1.
foreach (int i in setA)
{
positions.floor.Add(i);
positions.upper.Add(-1);
}
// Place all "Floor" items on the floor
foreach (int i in setF)
{
positions.floor.Add(i);
}
// Place all "No-restriction" items at the first available position
foreach (int i in setN)
{
PlaceFirstAvailPos(ref positions, i);
}
// Place all "Delicate" items at the first available position
foreach (int i in setD)
{
PlaceFirstAvailPos(ref positions, i);
}
return positions;
}
/* Auxiliary function used to display the solution */
string PadLeft(int x, int width)
{
string s = (x == -1 ? " " : x.ToString());
while (s.Length < width)
{
s = " " + s;
}
return s;
}
/* Write the solution in a file */
void WriteSolution(string fileName)
{
using (StreamWriter output = new StreamWriter(fileName))
{
output.WriteLine("Number of used trucks: " + totalUsedTrucks.GetValue());
output.WriteLine();
int printWidth = 2;
if (nbItems > 1)
{
printWidth = (int)Math.Ceiling(Math.Log10((double)(nbItems - 1))) + 2;
}
for (int k = 0; k < nbMaxTrucks; ++k)
{
if (isUsed[k].GetValue() == 0) continue;
HxCollection truckCollection = trucks[k].GetCollectionValue();
TruckPlacement truck = InsideTruck(truckCollection);
output.WriteLine("Truck weight: " + truckWeights[k].GetValue());
for (int i = 0; i < levelSpotsCount * printWidth; ++i) output.Write("-");
output.WriteLine();
foreach (int item in truck.upper) output.Write(PadLeft(item, printWidth));
output.WriteLine();
foreach (int item in truck.floor) output.Write(PadLeft(item, printWidth));
output.WriteLine();
for (int i = 0; i < levelSpotsCount * printWidth; ++i) output.Write("-");
output.WriteLine();
output.WriteLine();
}
}
}
public static void Main(string[] args)
{
if (args.Length < 1)
{
Console.Error.WriteLine("Usage: TruckLoading 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] : "10";
using (TruckLoading model = new TruckLoading())
{
model.ReadInstance(instanceFile);
model.Solve(int.Parse(strTimeLimit));
if (outputFile != null)
model.WriteSolution(outputFile);
}
}
}
- Compilation / Execution (Windows)
-
javac TruckLoading.java -cp %HX_HOME%\bin\hexaly.jarjava -cp %HX_HOME%\bin\hexaly.jar;. TruckLoading instances\t60_00.txt
- Compilation / Execution (Linux)
-
javac TruckLoading.java -cp /opt/hexaly_15_0/bin/hexaly.jarjava -cp /opt/hexaly_15_0/bin/hexaly.jar:. TruckLoading instances/t60_00.txt
// 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.*;
public class TruckLoading {
// Number of items
private int nbItems;
// Weight capacity of each truck
private int truckWeightCapacity;
// Number of available spots per level for each truck
private int levelSpotsCount;
// Weight of each item
private long[] itemWeights;
// Type (ie. stacking restrictions) of each item
private long[] categoryItems;
// Volume occupied by each item
private long[] volumes;
// Bounds on the number of used trucks
private int nbMinTrucks;
private int nbMaxTrucks;
// Hexaly Optimizer
private final HexalyOptimizer optimizer;
// Decision variables and expressions
private HxExpression[] trucks;
private HxExpression[] truckWeights;
private HxExpression[] isUsed;
private HxExpression totalUsedTrucks;
private String inFileName;
private TruckLoading(HexalyOptimizer optimizer) {
this.optimizer = optimizer;
}
/* Read instance data */
private void readInstance(String fileName) throws IOException {
inFileName = fileName;
try (Scanner input = new Scanner(new File(fileName))) {
input.useLocale(Locale.ROOT);
nbItems = input.nextInt();
truckWeightCapacity = input.nextInt();
levelSpotsCount = input.nextInt();
itemWeights = new long[nbItems];
categoryItems = new long[nbItems];
volumes = new long[nbItems];
long totalWeight = 0;
for (int i = 0; i < nbItems; ++i) {
itemWeights[i] = input.nextInt();
categoryItems[i] = input.nextInt();
totalWeight += itemWeights[i];
// Type 1 items block both a floor position and the upper position above it
volumes[i] = categoryItems[i] == 1 ? 2 : 1;
}
// Bounds on the number of used trucks
nbMinTrucks = (int) Math.ceil((double) totalWeight / truckWeightCapacity);
nbMaxTrucks = nbItems;
}
}
private void solve(int limit) {
// Declare the optimization model
HxModel model = optimizer.getModel();
trucks = new HxExpression[nbMaxTrucks];
truckWeights = new HxExpression[nbMaxTrucks];
isUsed = new HxExpression[nbMaxTrucks];
// Set decisions: trucks[k] represents the set of items assigned to truck k
for (int k = 0; k < nbMaxTrucks; ++k) {
trucks[k] = model.setVar(nbItems);
}
// Each item must be assigned to exactly one truck
model.constraint(model.partition(trucks));
/* Create arrays and functions to retrieve the item's data */
HxExpression weightsArray = model.array(itemWeights);
HxExpression volumesArray = model.array(volumes);
HxExpression categoriesArray = model.array(categoryItems);
HxExpression weightLambda = model.lambdaFunction(i -> model.at(weightsArray, i));
HxExpression volumeLambda = model.lambdaFunction(i -> model.at(volumesArray, i));
HxExpression floorLevelLambda = model.lambdaFunction(i -> model.or(
model.eq(model.at(categoriesArray, i), 1),
model.eq(model.at(categoriesArray, i), 2)));
HxExpression delicatenessLambda = model.lambdaFunction(i -> model.or(
model.eq(model.at(categoriesArray, i), 1),
model.eq(model.at(categoriesArray, i), 4)));
for (int k = 0; k < nbMaxTrucks; ++k) {
// Weight constraint for each truck
truckWeights[k] = model.sum(trucks[k], weightLambda);
model.constraint(model.leq(truckWeights[k], truckWeightCapacity));
// Volume constraint for each truck
model.constraint(model.leq(model.sum(trucks[k], volumeLambda), 2 * levelSpotsCount));
// Type 1 and type 2 items must be placed on the floor level
model.constraint(model.leq(model.sum(trucks[k], floorLevelLambda), levelSpotsCount));
// Type 1 and type 4 items cannot have another item on top of them
model.constraint(model.leq(model.sum(trucks[k], delicatenessLambda), levelSpotsCount));
// Truck k is used if at least one item is in it
isUsed[k] = model.gt(model.count(trucks[k]), 0);
}
// Count the used trucks
totalUsedTrucks = model.sum(isUsed);
// Minimize the number of used trucks
model.minimize(totalUsedTrucks);
model.close();
// Parametrize the optimizer
optimizer.getParam().setTimeLimit(limit);
// Stop the search if the lower threshold is reached
optimizer.getParam().setObjectiveThreshold(0, nbMinTrucks);
optimizer.solve();
}
/*
Auxiliary function used in the later "insideTruck" function.
Inserts an item into the next free slot in the truck.
*/
private void placeFirstAvailPos(List<Integer>[] positions, int item) {
if (positions[0].size() < levelSpotsCount) positions[0].add(item);
else positions[1].add(item);
}
/*
The optimizer assigns items to trucks but does not position them inside each truck.
This function computes a feasible two-level placement for the items in a truck.
*/
@SuppressWarnings("unchecked")
private List<Integer>[] insideTruck(HxCollection truckSet) {
// Split the items assigned to the truck by category
List<Integer> setA = new ArrayList<>(); // Type 1: floor level and nothing above. Called "Alone" items
List<Integer> setF = new ArrayList<>(); // Type 2: floor level. Called "Floor" items
List<Integer> setN = new ArrayList<>(); // Type 3: no restriction. Called "No-restriction" items
List<Integer> setD = new ArrayList<>(); // Type 4: nothing above. Called "Delicate" items
for (int pos = 0; pos < truckSet.count(); ++pos) {
int i = (int) truckSet.get(pos);
if (categoryItems[i] == 1) {
setA.add(i);
} else if (categoryItems[i] == 2) {
setF.add(i);
} else if (categoryItems[i] == 3) {
setN.add(i);
} else {
setD.add(i);
}
}
// Initialize the two-level position map: positions[0] represents the floor level, and positions[1] the upper level
List<Integer>[] positions = new ArrayList[2];
positions[0] = new ArrayList<>();
positions[1] = new ArrayList<>();
// Assign positions to items, from the most constraining category to the least constraining one: A -> F -> N -> D
// Place all "Alone" items on the floor, and leave the corresponding upper positions empty (represented by -1)
for (int i : setA) {
positions[0].add(i);
positions[1].add(-1);
}
// Place all "Floor" items on the floor
for (int i : setF) positions[0].add(i);
// Place all "No-restriction" items at the first available position
for (int i : setN) placeFirstAvailPos(positions, i);
// Place all "Delicate" items at the first available position
for (int i : setD) placeFirstAvailPos(positions, i);
return positions;
}
/* Auxiliary function used to display the solution */
private String padLeft(int x, int width) {
String s;
if (x == -1) s = " ";
else s = "" + x;
while (s.length() < width) s = " " + s;
return s;
}
/* Write the solution in a file */
private void writeSolution(String fileName) throws IOException {
try (PrintWriter output = new PrintWriter(fileName)) {
output.println("Number of used trucks: " + totalUsedTrucks.getValue() + "\n");
int printWidth = (int) Math.ceil(Math.log10(nbItems - 1)) + 2;
for (int k = 0; k < nbMaxTrucks; ++k) {
if (isUsed[k].getValue() == 0) continue;
List<Integer>[] truck = insideTruck(trucks[k].getCollectionValue());
output.println("Truck weight: " + truckWeights[k].getValue());
for (int i = 0; i < levelSpotsCount * printWidth; ++i) output.print("-");
output.println();
for (int i : truck[1]) output.print(padLeft(i, printWidth));
output.println();
for (int i : truck[0]) output.print(padLeft(i, printWidth));
output.println();
for (int i = 0; i < levelSpotsCount * printWidth; ++i) output.print("-");
output.println("\n");
}
}
}
public static void main(String[] args) {
if (args.length < 1) {
System.err.println("Usage: java TruckLoading 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] : "10";
try (HexalyOptimizer optimizer = new HexalyOptimizer()) {
TruckLoading model = new TruckLoading(optimizer);
model.readInstance(instanceFile);
model.solve(Integer.parseInt(strTimeLimit));
if (outputFile != null)
model.writeSolution(outputFile);
} catch (Exception ex) {
System.err.println(ex);
ex.printStackTrace();
System.exit(1);
}
}
}