线性规划用于排产排程问题——03
1. 问题描述和数学规划模型
比上一篇问题02中,我们只考虑了一次性的采购和生产计划,实际中的排产排程问题要更加复杂和精细。例如,我们要考虑未来三个月内采购和排产排程计划。其中,原材料每个月的采买价格均有不同,并且原材料购买后的存储也需要成本开销。在本节中,我们将考虑这样一个相对复杂的排产排程的决策问题。
问题描述
某香皂制造厂要对未来半年内的香皂生产和原料采买制定计划。香皂是由不同的油脂制作而成。每种油脂可以在当月立刻采买使用,也可以在期货市场购买(也即,以约定好的价格预定未来某个月的油脂)并在下一个月运达。而如果储备的油脂当月用不完,则需要付出储存成本。此外,每个月每种类型的油脂有使用上限,而生产出的香皂无法储存,只能在当月卖掉。该制造厂要在这诸多约束下制定香皂生产和原料采买制定计划,以最大化其利润。
数学规划模型
集合
- 月份集合= {1, 2, 3, 4, 5, 6}
- 制作香皂的油脂集合
- 油脂分为两种,我们定义植物油脂集合为,动物油脂集合为
- 油脂的处理方式集合= {"Buy", "Use", "Store"}
参数
- 每吨油脂在不同月份的采购成本
- 油脂的硬度
- 植物油脂每月的使用量上限(单位: 吨)
- 动物油脂每月的使用量上限(单位: 吨)
- 每卖出一吨香皂,盈利
- 香皂的硬度不得低于,也不得高于
- 在1月初,每种油脂的储存量为吨。要求在6月末,每种油脂也需要剩余吨
- 每种油脂每月的储存代价是
变量
- 表示在工厂在第个月,对油脂的购买(n="Buy"), 使用(n="Use")和储存(n="Store")计划(单位:吨)。
x[O * M * N] >= 0
- 表示工厂在第个月,对香皂的生产计划(单位:吨)
y[M] >= 0
目标
其中,= "Use", ="Store"
最大化工厂的利润,需计算工厂每月的盈利、采购成本、储存成本
r * y[m]
即销售单价与每月产成品的数量「盈利」sum { in O} cost[j, m] * x[j, m, "Buy"]
即油脂每月的采购单价与油脂每月的采购计划「采购成本」d * sum{ in O} x[j, m, "Store"]
油脂储存所付出的代价与每月油脂的储存计划「储存成本」
约束
- 考虑因素:为了例题更好的计算,假设制作香皂时,油脂转化过程中没有浪费
- 公式:
x[j, m, "Use"]
油脂每个月的使用情况与y[m]
生产计划相同
- 考虑因素:生产的产品有一定的质量要求,例如螺帽,有形状的限制或者大小限制,在此案例我们考虑的是香皂的硬度,
- 公式:
hardness[j] * x[j, m, "Use"]
油脂的硬度与每月油脂的使用计划「生产场景」y[m] * l
y[m] * u
香皂硬度的下限与上限
- 考虑因素:仓库有一定的空间,为了对仓库有效率的利用,通过规范油脂的使用数量,对每月储存量进行把控。
- 公式:
x[j, m, "Use"]
每月油脂的使用计划,b1
、b2
植物与动物油脂每月的使用上限
- 考虑因素:在生活实际上,每月油脂的采购、使用、储存存在等量关系,以及仓库可能有本次计划之前的油脂剩余情况,所以我们分为一月和其他月份两种情况
- 上个月的储存量与本月购买量的总和要等于本月的使用量与该月储存量的总和
- 一月公式:
s + x[j,1,"Buy"]
油脂的初始储存数量与油脂在一月的购买计划x[j,1,"Use"] + x[j,1,"Store"]
油脂在一月的使用计划与储存计划
- 其他月份公式:
x[j,m-1,"Store"] + x[j,m,"Buy"]
2-6月份油脂的储存计划与每月的采购计划x[j,m,"Use"] + x[j,m,"Store"]
优质每月的使用计划与储存计划
- 考虑因素:本次生产计划完毕后,补充仓库,防止仓库空间空余
- 公式:,其中
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元。