排产排程、原料采购、仓储存放等是制造业降本增效的关键问题。如何合理安排采购量和生产计划,让利润更高,也可以运用数学规划的方法来建模合求解。
优化问题
某香皂制造厂要对未来半年内的香皂生产和原料采买制定计划。香皂是由不同的油脂制作而成。每种油脂可以在当月立刻采买使用,也可以在期货市场购买(也即,以约定好的价格预定未来某个月的油脂)并在下一个月运达。而如果储备的油脂当月用不完,则需要付出储存成本。此外,每个月每种类型的油脂有使用上限,而生产出的香皂无法储存,只能在当月卖掉。该制造厂要在这诸多约束下制定香皂生产和原料采买制定计划,以最大化其利润。
数学建模与MAPL转化
集合
- 月份集合 M M M= {1, 2, 3, 4, 5, 6}
- 制作香皂的油脂集合 O O O
- 油脂分为两种,我们定义植物油脂集合为 O 1 O_1 O1,动物油脂集合为 O 2 O_2 O2
- 油脂的处理方式集合 N N N= {“Buy”, “Use”, “Store”}
参数
- 每吨油脂 j ∈ O j \in O j∈O在不同月份 m ∈ M m \in M m∈M的采购成本 c j m c_{jm} cjm
- 油脂 j ∈ O j \in O j∈O的硬度 h j h_j hj
- 植物油脂每月的使用量上限 b 1 b_1 b1(单位: 吨)
- 动物油脂每月的使用量上限 b 2 b_2 b2(单位: 吨)
- 每卖出一吨香皂,盈利 r r r
- 香皂的硬度不得低于 ℓ \ell ℓ,也不得高于 u u u
- 在1月初,每种油脂 j ∈ O j \in O j∈O的储存量为 s s s吨。要求在6月末,每种油脂也需要剩余 s s s吨
- 每种油脂 j ∈ O j \in O j∈O每月的储存代价是 d d d
变量
-
x
j
m
n
x_{jmn}
xjmn表示在工厂在第
m
∈
M
m\in M
m∈M个月,对油脂
j
∈
O
j\in O
j∈O的购买(n=“Buy”), 使用(n=“Use”)和储存(n=“Store”)计划(单位:吨)。
x[O * M * N] >= 0
-
y
m
y_m
ym表示工厂在第
m
∈
M
m\in M
m∈M个月,对香皂的生产计划(单位:吨)
y[M] >= 0
目标
max
∑
m
∈
M
(
r
y
m
−
∑
j
∈
O
c
j
m
x
j
m
n
2
−
d
∑
j
∈
O
x
j
m
n
3
)
\max \quad \sum_{m\in M}\Big(r y_m - \sum_{j \in O} c_{jm}x_{jmn_2} - d\sum_{j\in O}x_{jmn_3}\Big)
max∑m∈M(rym−∑j∈Ocjmxjmn2−d∑j∈Oxjmn3)其中,
n
2
n_2
n2= “Use”,
n
3
=
n_3 =
n3=“Store”
最大化工厂的利润,需计算工厂每月的盈利
r
y
m
r y_m
rym、采购成本
∑
j
∈
O
c
j
m
x
j
m
n
2
\sum_{j \in O} c_{jm}x_{jmn_2}
∑j∈Ocjmxjmn2、储存成本
d
∑
j
∈
O
x
j
m
n
3
d\sum_{j\in O}x_{jmn_3}
d∑j∈Oxjmn3
r * y[m]
即销售单价与每月产成品的数量「盈利」sum {<j> in O} cost[j, m] * x[j, m, "Buy"]
即油脂每月的采购单价与油脂每月的采购计划「采购成本」d * sum{<j> in O} x[j, m, "Store"]
油脂储存所付出的代价与每月油脂的储存计划「储存成本」
约束
- 考虑因素:为了例题更好的计算,假设制作香皂时,油脂转化过程中没有浪费
- 公式:
∑
j
∈
O
x
j
m
n
2
=
y
m
,
∀
m
∈
M
\sum_{j\in O} x_{jmn_2} = y_m, \forall\, m \in M
∑j∈Oxjmn2=ym,∀m∈M
x[j, m, "Use"]
油脂每个月的使用情况与y[m]
生产计划相同
- 考虑因素:生产的产品有一定的质量要求,例如螺帽,有形状的限制或者大小限制,在此案例我们考虑的是香皂的硬度,
- 公式:
ℓ
y
m
≤
∑
j
∈
O
h
j
x
j
m
n
2
≤
u
y
m
,
∀
m
∈
M
\ell y_m \le \sum_{j\in O} h_j x_{jmn_2} \le u y_m, \forall\, m \in M
ℓym≤∑j∈Ohjxjmn2≤uym,∀m∈M
hardness[j] * x[j, m, "Use"]
油脂的硬度与每月油脂的使用计划「生产场景」y[m] * l``y[m] * u
香皂硬度的下限与上限
- 考虑因素:仓库有一定的空间,为了对仓库有效率的利用,通过规范油脂的使用数量,对每月储存量进行把控。
- 公式:
∑
j
∈
O
1
x
j
m
n
2
≤
b
1
,
∀
m
∈
M
\sum_{j\in O_1} x_{jmn_2} \le b_1, \forall\, m \in M
∑j∈O1xjmn2≤b1,∀m∈M,
∑
j
∈
O
2
x
j
m
n
2
≤
b
2
,
∀
m
∈
M
\sum_{j\in O_2} x_{jmn_2} \le b_2, \forall\, m \in M
∑j∈O2xjmn2≤b2,∀m∈M
x[j, m, "Use"]
每月油脂的使用计划,b1
、b2
植物与动物油脂每月的使用上限
- 考虑因素:在生活实际上,每月油脂的采购、使用、储存存在等量关系,以及仓库可能有本次计划之前的油脂剩余情况,所以我们分为一月和其他月份两种情况
- 上个月的储存量与本月购买量的总和要等于本月的使用量与该月储存量的总和
- 一月公式:(
s
+
x
j
m
n
1
=
x
j
m
n
2
+
x
j
m
n
3
,
∀
j
∈
O
,
m
=
0.
s + x_{j m n_1} = x_{j m n_2 } + x_{j m n_3 },\quad \forall~j\in O, \quad m = 0.
s+xjmn1=xjmn2+xjmn3,∀ j∈O,m=0.)
s + x[j,1,"Buy"]
油脂的初始储存数量与油脂在一月的购买计划x[j,1,"Use"] + x[j,1,"Store"]
油脂在一月的使用计划与储存计划
- 其他月份公式:(
x
j
(
m
−
1
)
n
3
+
x
j
m
n
1
=
x
j
m
n
2
+
x
j
m
n
3
,
∀
j
∈
O
,
∀
m
∈
M
x_{j(m-1)n_3} + x_{j m n_1} = x_{j m n_2 } + x_{j m n_3 },\quad \forall~j\in O, \quad \forall~m\in M
xj(m−1)n3+xjmn1=xjmn2+xjmn3,∀ j∈O,∀ m∈M)
x[j,m-1,"Store"] + x[j,m,"Buy"]
2-6月份油脂的储存计划与每月的采购计划x[j,m,"Use"] + x[j,m,"Store"]
优质每月的使用计划与储存计划
- 考虑因素:本次生产计划完毕后,补充仓库,防止仓库空间空余
- 公式:
x
j
m
n
3
=
s
,
∀
j
∈
O
x_{jmn_3} = s, \forall\, j\in O
xjmn3=s,∀j∈O,其中
m
=
0
m=0
m=0
x[j,6,"Store"]
六月每种油脂的储存计划
建模求解问题
此案例使用的关键命令如下:
set 声明集合
parma 声明参数
var 声明变量
maxmaximize 声明目标,此为最大化
subto 声明约束
option solver mindopt 指定求解的求解器(默认为mindopt)
solve 求解
display 打印求解变量值
forall 快速声明约束的方法,主要用来循环定义的集合
with 加布尔表达式,表示我们要遍历集合中所有使得布尔表达式为真的元素
案例排产排程03提供了完整的源代码:
clear model;#清除model,多次run的时候使用
option modelname model/manufacture_03_soap2;中间文件生成地址
#---------建模-----------------
# manufacture_03_soap2.mapl
set O1 := { "VEG1", "VEG2" };
set O2 := {"OIL1", "OIL2", "OIL3"};
set O := O1 + O2;
set M := {1, 2, 3, 4, 5, 6};
set N := {"Buy", "Use", "Store"};
param cost[O * M] :=
| 1, 2, 3, 4, 5, 6 |
|"VEG1"| 110, 130, 110, 120, 100, 90 |
|"VEG2"| 120, 130, 140, 110, 120, 100 |
|"OIL1"| 130, 110, 130, 120, 150, 140 |
|"OIL2"| 110, 90, 100, 120, 110, 80 |
|"OIL3"| 115, 115, 95, 125, 105, 135 |;
param hardness[O] := <"VEG1"> 8.0, <"VEG2"> 6.0,
<"OIL1"> 2.0, <"OIL2"> 4.0, <"OIL3"> 5.0;
param r := 150;
param b1 := 200;
param b2 := 250;
param l := 3;
param u := 6;
param s := 500;
param d := 5;
var x[O * M * N] >= 0;
var y[M] >= 0;
maximize Reward: sum {<m> in M}(
r * y[m]
- sum {<j> in O} cost[j, m] * x[j, m, "Buy"]
- d * sum{<j> in O} x[j, m, "Store"]
);
subto Weight:
forall { <m> in M }
sum {<j> in O} x[j, m, "Use"] == y[m];
subto Hardness1:
forall { <m> in M }
sum {<j> in O} hardness[j] * x[j, m, "Use"] >= y[m] * l;
subto Hardness2:
forall {<m> in M }
sum {<j> in O} hardness[j] * x[j, m, "Use"] <= y[m] * u;
subto VEGBound:
forall {<m> in M }
sum {<j> in O1} x[j, m, "Use"] <= b1;
subto OILBound:
forall { <m> in M }
sum {<j> in O2} x[j, m, "Use"] <= b2;
subto Link_1:
forall {<j, 1> in O * M }
s + x[j,1,"Buy"] == x[j,1,"Use"] + x[j,1,"Store"];
subto Link_2to6:
forall {<j, m> in O * M with m > 1 }
x[j,m-1,"Store"] + x[j,m,"Buy"] == x[j,m,"Use"] + x[j,m,"Store"];
subto Store_June:
forall { <j> in O }
x[j,6,"Store"] == s;
#------------------------------
print "-----------------用MindOpt求解---------------";
option solver mindopt;
solve;
#display;
#将决策目标的输出格式化
print "-----------------结果---------------";
print "最大利润 = ", sum {<m> in M}(
r * y[m]
- sum {<j> in O} cost[j, m] * x[j, m, "Buy"]
- d * sum{<j> in O} x[j, m, "Store"]
);
求解结果
-----------------用MindOpt求解---------------
Running mindoptampl
wantsol=1
MindOpt Version 0.25.1 (Build date: 20230816)
Copyright (c) 2020-2023 Alibaba Cloud.
Start license validation (current time : 24-AUG-2023 19:46:33).
License validation terminated. Time : 0.014s
Model summary.
- Num. variables : 96
- Num. constraints : 60
- Num. nonzeros : 253
- Bound range : [2.0e+02,5.0e+02]
- Objective range : [5.0e+00,1.5e+02]
- Matrix range : [1.0e+00,8.0e+00]
Presolver started.
Presolver terminated. Time : 0.000s
Simplex method started.
Model fingerprint: =Y2dgZWZ3ZWY35mY
Iteration Objective Dual Inf. Primal Inf. Time
0 1.23462e+06 0.0000e+00 1.0186e+03 0.01s
50 1.08250e+05 0.0000e+00 0.0000e+00 0.01s
Postsolver started.
Simplex method terminated. Time : 0.002s
OPTIMAL; objective 108250.00
50 simplex iterations
Completed.
-----------------结果---------------
最大利润 = 108250
验证约束
约束条件的验证是优化问题求解过程中的重要环节,是验证求解结果是否准确的重要指标。因此我们进行一次验证,例如验证「六月末每种油脂的储存数量需要等于一月初的初始储存数量」
forall {<j> in O} print
'六月末(',j,')的储存数量= ',
x[j,6,"Store"];
运行上述代码结果:
六月末(VEG1)的储存数量= 500
六月末(VEG2)的储存数量= 500
六月末(OIL1)的储存数量= 500
六月末(OIL2)的储存数量= 500
六月末(OIL3)的储存数量= 500
结果解析
display指令运行时,会打印出很多求解的结果,x@name 和 y@name 是决策变量的取值,后面的dual solution是对偶解的值。示意如下:
Primal Solution:
x@<VEG1,1,Buy> = 0.00000000
x@<VEG1,1,Use> = 100.000000
x@<VEG1,1,Store> = 400.000000
x@<VEG1,2,Buy> = 0.00000000
x@<VEG1,2,Use> = 200.000000
x@<VEG1,2,Store> = 200.000000
x@<VEG1,3,Buy> = 0.00000000
x@<VEG1,3,Use> = 0.00000000
x@<VEG1,3,Store> = 200.000000
x@<VEG1,4,Buy> = 0.00000000
x@<VEG1,4,Use> = 0.00000000
x@<VEG1,4,Store> = 200.000000
x@<VEG1,5,Buy> = 0.00000000
x@<VEG1,5,Use> = 200.000000
x@<VEG1,5,Store> = 0.00000000
x@<VEG1,6,Buy> = 700.000000
x@<VEG1,6,Use> = 200.000000
x@<VEG1,6,Store> = 500.000000
x@<VEG2,1,Buy> = 0.00000000
x@<VEG2,1,Use> = 100.000000
x@<VEG2,1,Store> = 400.000000
x@<VEG2,2,Buy> = 0.00000000
x@<VEG2,2,Use> = 0.00000000
x@<VEG2,2,Store> = 400.000000
x@<VEG2,3,Buy> = 0.00000000
x@<VEG2,3,Use> = 200.000000
x@<VEG2,3,Store> = 200.000000
x@<VEG2,4,Buy> = 0.00000000
x@<VEG2,4,Use> = 200.000000
x@<VEG2,4,Store> = 0.00000000
x@<VEG2,5,Buy> = 0.00000000
x@<VEG2,5,Use> = 0.00000000
x@<VEG2,5,Store> = 0.00000000
x@<VEG2,6,Buy> = 500.000000
x@<VEG2,6,Use> = 0.00000000
x@<VEG2,6,Store> = 500.000000
x@<OIL1,1,Buy> = 0.00000000
x@<OIL1,1,Use> = 0.00000000
x@<OIL1,1,Store> = 500.000000
x@<OIL1,2,Buy> = 0.00000000
x@<OIL1,2,Use> = 0.00000000
x@<OIL1,2,Store> = 500.000000
x@<OIL1,3,Buy> = 0.00000000
x@<OIL1,3,Use> = 0.00000000
x@<OIL1,3,Store> = 500.000000
x@<OIL1,4,Buy> = 0.00000000
x@<OIL1,4,Use> = 0.00000000
x@<OIL1,4,Store> = 500.000000
x@<OIL1,5,Buy> = 0.00000000
x@<OIL1,5,Use> = 0.00000000
x@<OIL1,5,Store> = 500.000000
x@<OIL1,6,Buy> = 0.00000000
x@<OIL1,6,Use> = 0.00000000
x@<OIL1,6,Store> = 500.000000
x@<OIL2,1,Buy> = 0.00000000
x@<OIL2,1,Use> = 0.00000000
x@<OIL2,1,Store> = 500.000000
x@<OIL2,2,Buy> = 150.000000
x@<OIL2,2,Use> = 250.000000
x@<OIL2,2,Store> = 400.000000
x@<OIL2,3,Buy> = 0.00000000
x@<OIL2,3,Use> = 0.00000000
x@<OIL2,3,Store> = 400.000000
x@<OIL2,4,Buy> = 0.00000000
x@<OIL2,4,Use> = 250.000000
x@<OIL2,4,Store> = 150.000000
x@<OIL2,5,Buy> = 0.00000000
x@<OIL2,5,Use> = 150.000000
x@<OIL2,5,Store> = 0.00000000
x@<OIL2,6,Buy> = 750.000000
x@<OIL2,6,Use> = 250.000000
x@<OIL2,6,Store> = 500.000000
x@<OIL3,1,Buy> = 0.00000000
x@<OIL3,1,Use> = 250.000000
x@<OIL3,1,Store> = 250.000000
x@<OIL3,2,Buy> = 0.00000000
x@<OIL3,2,Use> = 0.00000000
x@<OIL3,2,Store> = 250.000000
x@<OIL3,3,Buy> = 0.00000000
x@<OIL3,3,Use> = 250.000000
x@<OIL3,3,Store> = 0.00000000
x@<OIL3,4,Buy> = 0.00000000
x@<OIL3,4,Use> = 0.00000000
x@<OIL3,4,Store> = 0.00000000
x@<OIL3,5,Buy> = 600.000000
x@<OIL3,5,Use> = 100.000000
x@<OIL3,5,Store> = 500.000000
x@<OIL3,6,Buy> = 0.00000000
x@<OIL3,6,Use> = 0.00000000
x@<OIL3,6,Store> = 500.000000
y@1 = 450.000000
y@2 = 450.000000
y@3 = 450.000000
y@4 = 450.000000
y@5 = 450.000000
y@6 = 450.000000
-----------------结果---------------
最大利润 = 108250
同时,在最近建模的文件所在目录或option modelname指定的位置,会生成对应的文件 .nl
和 .sol
。其中 .nl
文件是建模的问题模型文件,可被多数求解器识别,.sol
文件中存储了求解结果solution。
从打印的结果,我们可以得到最大利润为108250元。