组合优化(Combinatorial Optimization,CO)数学优化研究的一个分支。主要关注的是从有限的对象集合中寻找最优解的问题。这个词的由来主要是由“组合”和“优化”两部分构成。“组合”指的是从有限的对象集合中选择一部分的过程,而“优化”则指的是在满足一定条件下,使得某个目标函数达到最大或最小。即:组合优化问题的解集合有限个的,要做的事情是在有限的集合里面找到目标最优的集合。
组合优化问题在许多实际情况中都有出现,包括经济管理、工业制造、交通运输等领域。比如:
组合优化问题特征:
解组合优化的方式有:
本文将以一个简化的装箱问题为例,来讲解采用数学规划的方法来解决装箱这个组合优化问题。
装箱问题(Bin Packing Problem)是组合优化领域中的一个经典问题,主要涉及如何将一系列对象高效地装入有限数量的容器(或“箱”)中,同时满足特定的约束条件。这个问题的目标是最小化所需使用的箱子数量或者最大化箱子的装载效率,以减少空间或资源的浪费。
在数学领域,装箱问题通常被建模为整数规划问题。它的变体众多,包括一维装箱问题、二维装箱问题、多维装箱问题等,以及它们各自的约束条件,如容器容量、物品形状、摆放方向限制等。

装箱问题广泛应用于各种实际场景,几个典型的应用示例包括:
假设我们有一批货物要运输走,这批货物包装盒的比较整齐,侧面面尺寸一样,仅厚度不同、重量不同。运输货车里可以放两列,如上图示意。
货车的总承重是10吨。为了行驶平稳,希望左右摆的货物质量差异不超过500公斤。货车可装货物的总长度(厚度)是5米。
要装的货物的信息如此表格文件所示: data/items-1010.csv
部分数据如下:
| 序号 | 包装和厚(cm) | 重量(kg) |
|---|---|---|
| 1 | 101 | 505 |
| 2 | 61 | 305 |
| 3 | 126 | 630 |
| …… | …… | …… |
| 100 | 85 | 765 |
| …… | …… | …… |
| 200 | 60 | 600 |
| …… | …… | …… |
| 1010 | 80 | 640 |
当数据维度太高的时候,采用数学规划方式计算会很慢。
我们可以拆解问题,比如分块去算某100个货物的装载方案,切割前100条数据为 data/items-100.csv 。更多分块数据方案用户可以进data文件夹自己修改。
下面我们采用数学规划的方法来建模这个问题。
首先:定义几何,描述数据,方便后面引用
然后,设置变量来表达要解决的问题。
由此,我们可以用来表达装货的约束。
此外,我们需要设置目标,用的车辆数最少。
我们可以设置新的变量,增加约束来表述和上面的装载方案 x i , b , s x_{i,b,s} xi,b,s的关系。
汇总得到数学公式如下:
min ∑ b ∈ B y b s.t. ∑ i ∈ I x i , b , l e f t ⋅ W i ≤ W l e f t m a x , ∀ b ∈ B ∑ i ∈ I x i , b , r i g h t ⋅ W i ≤ W r i g h t m a x , ∀ b ∈ B 0 ≤ ( ∑ i ∈ I x i , b , l e f t ⋅ W i ) − ( ∑ i ∈ I x i , b , r i g h t ⋅ W i ) ≤ W l e g t r i g h t , ∀ b ∈ B ∑ b ∈ B x i , b , s ⋅ L i ≤ L m a x , ∀ b ∈ B , s ∈ S ∑ i ∈ I , s ∈ S x i , b , s ≥ y b , ∀ b ∈ B y b ≥ x i , b , s , ∀ i ∈ I , b ∈ B , s ∈ S x i , b , s ∈ { 0 , 1 } y b ∈ { 0 , 1 } \begin{array}{rll} \min & \sum_{b \in B} y_{b} & \\ \text{s.t.} & \sum_{i \in I} x_{i,b,left} \cdot W_{i} \leq W_{leftmax} , & \forall b \in B \\ & \sum_{i \in I} x_{i,b,right} \cdot W_{i} \leq W_{rightmax} , & \forall b \in B \\ & 0 \leq (\sum_{i \in I} x_{i,b,left} \cdot W_{i}) - (\sum_{i \in I} x_{i,b,right} \cdot W_{i}) \leq W_{legt_right} , & \forall b \in B \\ & \sum_{b \in B} x_{i,b,s} \cdot L_{i} \leq L_{max}, & \forall b \in B, s \in S \\ & \sum_{ i \in I, s \in S} x_{i,b,s} \geq y_{b}, & \forall b \in B \\ & y_{b} \geq x_{i,b,s}, & \forall i \in I, b \in B, s\in S \\ & x_{i,b,s} \in\{0,1\} & \\ & y_{b} \in \{0,1\} & \\ \end{array} mins.t.∑b∈Byb∑i∈Ixi,b,left⋅Wi≤Wleftmax,∑i∈Ixi,b,right⋅Wi≤Wrightmax,0≤(∑i∈Ixi,b,left⋅Wi)−(∑i∈Ixi,b,right⋅Wi)≤Wlegtright,∑b∈Bxi,b,s⋅Li≤Lmax,∑i∈I,s∈Sxi,b,s≥yb,yb≥xi,b,s,xi,b,s∈{0,1}yb∈{0,1}∀b∈B∀b∈B∀b∈B∀b∈B,s∈S∀b∈B∀i∈I,b∈B,s∈S
下面的内容包含了code源码,如果您当前在浏览项目内容页,请复制项目后,点击右上角的NoteBook按钮,进入NoteBook环境中查看和运行代码。
In [1]:
clear model;
option modelname binpacking_01; #定义存储文件名
# ----------建模--------Start----
# binpacking_01.mapl
## 导入数据-------
print "导入数据-------";
param fileDir = "data/";
set Items = {read fileDir+"items.csv" as "<1n>" skip 1}; #读取货物序号
print "总共有{}个货物待装载"% card(Items);
param ItemSize[Items] = read fileDir+"items.csv" as "<1n> 2n" skip 1; #读取货物的长度(厚度),单位厘米cm
param ItemWeight[Items] = read fileDir+"items.csv" as "<1n> 3n" skip 1; #读取货物的重量,单位千克kg
param BinLength = 500; #货车的长度,单位厘米cm
param BinLoadCapacity = 10*1000; #货车的载重,单位kg
param BinLeftRightBalanceDiff = 500; #或者左右两边的重量差异千克
# 因为左右无定数,假设货物都是重的放左边,轻的右边,则左边载重不超过 5250,右边不超过4750.
param BinLoadCapacity_Left = BinLoadCapacity * 0.5 + 500 * 0.5;
param BinLoadCapacity_right = BinLoadCapacity * 0.5 - 500 * 0.5;
# 假设总共有x辆货车
param Bin_Num = 20;
set Bins = {1..Bin_Num};
print "先假设有{}辆货车"%Bin_Num;
## 左右两边
set Sides = {"left","right"};
## 数学建模-------
print "数学建模--------------";
# 变量----
var x_Item2Bin[Items*Bins*Sides] >= 0 binary; #货物放在哪个车的哪一边
var x_BinUsed[Bins] >= 0 binary;; #总共被使用的货车数
# 目标----
# 目标函数:最小化使用货车数量
minimize TotalBins: sum { b in Bins} x_BinUsed[b];
# 货车的总承重是10吨。为了行驶平稳,希望左右摆的货物质量差异不超过500公斤。货车可装货物的总长度(厚度)是5米。 要装的货物的信息如下表格所示:
# 约束----
# 每个货物只能放一个位置,且至少是一个位置
subto constraint_0:
forall {i in Items}
sum {b in Bins, s in Sides} x_Item2Bin[i,b,s] == 1;
# 每辆货车总载重上限是10吨 , 左右两边差异有限制500kg
# 假设左边的重,变换上限和差异,定义左边比右边重
subto constraint_1:
forall {b in Bins}
sum {i in Items} x_Item2Bin[i,b,"left"] * ItemWeight[i] <= BinLoadCapacity_Left;
subto constraint_1_2:
forall {b in Bins}
sum {i in Items} x_Item2Bin[i,b,"right"] * ItemWeight[i] <= BinLoadCapacity_right;
subto constraint_1_3:
forall {b in Bins}
0 <= (sum {i in Items} x_Item2Bin[i,b,"left"]* ItemWeight[i]
- sum {i in Items} x_Item2Bin[i,b,"right"] * ItemWeight[i]) <= BinLeftRightBalanceDiff;
# 每辆货车的每一边的装货长度不超过5米
subto constraint_2:
forall {b in Bins}
forall { s in Sides}
sum { i in Items} x_Item2Bin[i,b,s] * ItemSize[i] <= BinLength;
# 车被用了
subto constraint_3:
forall {b in Bins}
sum {i in Items, s in Sides} x_Item2Bin[i,b,s] >= x_BinUsed[b];
subto constraint_4:
forall {b in Bins}
forall { s in Sides}
forall { i in Items}
x_BinUsed[b] >= x_Item2Bin[i,b,s];
#求解
option solver mindopt; # (可选)指定求解用的求解器,默认是MindOpt
#option mindopt_options 'max_time=600'; #设置求解器参数
option mindopt_options 'print=0'; #设置求解器输出级别,减少过程打印
solve; # 求解
# 结果打印和检查结果
print "----------------结果打印和存文件--------------";
#display;
print "最小需要货车数 = ",sum { b in Bins} x_BinUsed[b];
print "------每辆车----";
print "|车ID|左边货物数|右边货物数|……|左边厚度|右边厚度|……|左边重量|右边重量|左右重量差异|";
print "|--|--|--|--|--|--|--|--|--|--|";
forall { in Bins with x_BinUsed[b] >= 0.5}
print "|{}|{}|{}|……|{}|{}|……|{}|{}|{}|"
%b,
sum{i in Items} x_Item2Bin[i,b,"left"],
sum{i in Items} x_Item2Bin[i,b,"right"],
sum{i in Items} x_Item2Bin[i,b,"left"] * ItemSize[i],
sum{i in Items} x_Item2Bin[i,b,"right"]* ItemSize[i],
sum{i in Items} x_Item2Bin[i,b,"left"] * ItemWeight[i],
sum{i in Items} x_Item2Bin[i,b,"right"]* ItemWeight[i],
sum{i in Items} x_Item2Bin[i,b,"left"] * ItemWeight[i] - sum{i in Items} x_Item2Bin[i,b,"right"]* ItemWeight[i] ;
#打印太长,注释。可以 cmd+/ 来批量注释和解除注释,或者加“and i <=5"来只打印前几行
print "------每个货物----";
print "|货物ID|包装厚度(cm)|重量(kg)|放置车辆|边|";
print "|--|--|--|--|--|";
forall { in Items*Bins*Sides with x_Item2Bin[i,b,s] >= 0.5 and i <=5}
print "|{}|{}|{}|{}|{}|"
%i,ItemSize[i],ItemWeight[i],b,s;
print "|……|……|……|……|……|";
# 存文件
print "车ID,左边货物数,右边货物数,……,左边厚度,右边厚度,……,左边重量,右边重量,左右重量差异" : "车辆装载情况.csv";
close "车辆装载情况.csv";
forall { in Bins with x_BinUsed[b] >= 0.5}
print "{},{},{},……,{},{},……,{},{},{}"
%b,
sum{i in Items} x_Item2Bin[i,b,"left"],
sum{i in Items} x_Item2Bin[i,b,"right"],
sum{i in Items} x_Item2Bin[i,b,"left"] * ItemSize[i],
sum{i in Items} x_Item2Bin[i,b,"right"]* ItemSize[i],
sum{i in Items} x_Item2Bin[i,b,"left"] * ItemWeight[i],
sum{i in Items} x_Item2Bin[i,b,"right"]* ItemWeight[i],
sum{i in Items} x_Item2Bin[i,b,"left"] * ItemWeight[i] - sum{i in Items} x_Item2Bin[i,b,"right"]* ItemWeight[i]
>> "车辆装载情况.csv";
close "车辆装载情况.csv";
print "货物ID,包装厚度(cm),重量(kg),放置车辆,放置边" : "货物装载方案.csv";
close "货物装载方案.csv";
forall { in Items*Bins*Sides with x_Item2Bin[i,b,s] >= 0.5}
print "{},{},{},{},{}"
%i,ItemSize[i],ItemWeight[i],b,s
>> "货物装载方案.csv";
close "货物装载方案.csv";
print "结果文件存储在: [车辆装载情况.csv](车辆装载情况.csv) 和 [货物装载方案.csv](货物装载方案.csv)";
运行上述代码,结果如下:
导入数据-------
总共有100个货物待装载
先假设有20辆货车
数学建模--------------
Running mindoptampl
wantsol=1
print=0
MindOpt Version 1.1.0 (Build date: 20240123)
Copyright (c) 2020-2024 Alibaba Cloud.
Start license validation (current time : 01-MAR-2024 11:22:15).
License validation terminated. Time : 0.011s
OPTIMAL; objective 9.00
Completed.
----------------结果打印和存文件--------------
最小需要货车数 = 9
------每辆车----
|车ID|左边货物数|右边货物数|……|左边厚度|右边厚度|……|左边重量|右边重量|左右重量差异|
|--|--|--|--|--|--|--|--|--|--|
|1|5|5|……|500|462|……|4363|4237|126|
|3|7|10|……|488|411|……|3393|3046|347|
|6|6|5|……|499|474|……|4526|4053|473|
|8|5|5|……|482|499|……|3574|3112|462|
|9|6|4|……|491|488|……|4454|4412|42|
|10|6|5|……|461|488|……|4118|3812|306|
|11|6|6|……|487|440|……|3931|3816|115|
|13|4|5|……|492|469|……|4460|4135|325|
|14|5|5|……|414|492|……|3948|3544|404|
------每个货物----
|货物ID|包装厚度(cm)|重量(kg)|放置车辆|边|
|--|--|--|--|--|
|1|101|505|14|left|
|2|61|305|3|right|
|3|126|630|8|right|
|4|32|256|3|left|
|5|39|429|13|right|
|……|……|……|……|……|
结果文件存储在: [车辆装载情况.csv](车辆装载情况.csv) 和 [货物装载方案.csv](货物装载方案.csv)
上面的打印的结果部分为:
总共有100个货物待装载, 先假设有20辆货车
最小需要货车数 = 9
------每辆车----
| 车ID | 左边货物数 | 右边货物数 | …… | 左边厚度 | 右边厚度 | …… | 左边重量 | 右边重量 | 左右重量差异 |
|---|---|---|---|---|---|---|---|---|---|
| 1 | 5 | 5 | …… | 500 | 462 | …… | 4363 | 4237 | 126 |
| 3 | 7 | 10 | …… | 488 | 411 | …… | 3393 | 3046 | 347 |
| 6 | 6 | 5 | …… | 499 | 474 | …… | 4526 | 4053 | 473 |
| 8 | 5 | 5 | …… | 482 | 499 | …… | 3574 | 3112 | 462 |
| 9 | 6 | 4 | …… | 491 | 488 | …… | 4454 | 4412 | 42 |
| 10 | 6 | 5 | …… | 461 | 488 | …… | 4118 | 3812 | 306 |
| 11 | 6 | 6 | …… | 487 | 440 | …… | 3931 | 3816 | 115 |
| 13 | 4 | 5 | …… | 492 | 469 | …… | 4460 | 4135 | 325 |
| 14 | 5 | 5 | …… | 414 | 492 | …… | 3948 | 3544 | 404 |
------每个货物----
| 货物ID | 包装厚度(cm) | 重量(kg) | 放置车辆 | 边 |
|---|---|---|---|---|
| 1 | 101 | 505 | 14 | left |
| 2 | 61 | 305 | 3 | right |
| 3 | 126 | 630 | 8 | right |
| 4 | 32 | 256 | 3 | left |
| 5 | 39 | 429 | 13 | right |
| …… | …… | …… | …… | …… |
另外,上面的结果还存在了文件 车辆装载情况.csv 和 货物装载方案.csv,可在下文的案例地址中查看。
读者可尝试复制本项目,增加难度进行调试。比如:
data/items-100-2.csv,观察不同数据的结果区别。data/items-200.csv 的200个货一起算装载方式。