排產排程問題也可以用數學規劃的方式進行建模,調用MindOpt最佳化求解器進行求解。
行業背景
排產排程、原料採購、倉儲存放等是製造業降本增效的關鍵問題。如何合理安排採購量和生產計劃,讓成本降低,或者利潤更高。這個最佳化問題也可以運用數學規劃的方法來建模和求解。
例如:某香皂製造廠要對未來半年內的香皂生產和原料採買制定計劃。原料的部分需要提前預留,但是儲存也有成本,需要盡量合理安排,使得原料夠用,又能滿足使用需要。
業務調研、資料量化、數學建模
在使用最佳化技術的時候,需要更詳細的調研業務的需求,整理相關的商務邏輯和資料,並量化表示它。然後採用數學規劃的方法進行數學建模。
此部分細節較多,可在案例排產排程03中查閱細節,此處我們僅列出數學公式如下,參數部分請點擊案例連結查看:

集合O 是油脂,包含O1和O2, M是月份。
以上公式對應的約束有:
假設從油脂到香皂的轉化過程中,油脂沒有任何浪費,生產過程中重量守恒
假設從油脂到香皂的製造過程中,硬度的轉化滿足線性關係
任意油脂上個月的儲存量與本月購買量的總和要等於本月的使用量與該月儲存量的總和,需要考慮一月與其他月份,其中
是1月份時,各種油脂的初始儲備量植物油脂與動物油脂購買量的限制
六月末每種油脂的儲存數量需要與儲儲存備量一致
原始碼
MindOpt支援多種程式設計語言或者建模語言來調用。此處僅列出一種供參考:
MindOpt APL建模語言調用
MindOpt APL建模語言的原始碼(可在排產排程03線上試運行):
##====MindOpt APL 建模語言版本代碼====
clear model;#清除model,多次run的時候使用
option modelname 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; # 指定求解用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"]
);結果和結果用法
不同代碼日誌列印不一樣。部分結果日誌列印如下所示:
...
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]
...
Simplex method terminated. Time : 0.002s
...
OPTIMAL; objective 108250.00
...
-----------------結果---------------
最大利潤 = 108250目標的最優解是:108250。更多解的細節,如決策變數的取值,可以通過print命令列印來顯示,也可以從程式運行寫的.sol檔案裡面擷取,還可以調用display擷取所有變數的。
如運行如下指令,驗證“六月末每種油脂的儲存數量需要等於一月初的初始儲存數量”這個約束:
forall {<j> in O} print '六月末(',j,')的儲存數量= ', x[j,6,"Store"];輸出結果是500,與1月初需求相同:
六月末(VEG1)的儲存數量= 500
六月末(VEG2)的儲存數量= 500
六月末(OIL1)的儲存數量= 500
六月末(OIL2)的儲存數量= 500
六月末(OIL3)的儲存數量= 500運行如下代碼,將輸出列印為csv格式,作為排產排程問題的解決方案:
print "{}, {}, {}, {}" % "油脂", "月份", "處理方案","數量" : "每月油脂處理方式.csv";
close "每月油脂處理方式.csv";
forall {<j,m,n> in O*M*N}
print "{}, {}, {}, {}" % j, m, n, x[j,m,n] >> "每月油脂的處理方式.csv";
close "每月油脂處理方式.csv";
print "{}, {}" % "月份", "生產計劃" : "Results_.csv";
close "Results_.csv";
forall {<m> in M}
print "{}, {}" % m,y[m] >> "Results_.csv";
close "Results_.csv";部分結果如下:


即,一月採購油脂VEG1的計劃為0,使用計劃為100噸,儲存計劃為400噸、二月的採購計劃為0,使用和儲存計劃各為200噸。