生産現場で役に立つかもしれない最適化手法5【GLPK】(工数の平準化3)

[<--目次へ](https://qiita.com/ki073/items/508b3c20eb03078703dd)

## 最適化の条件
* 製品ごとに製造装置が異なり、製造装置ごとに平準化
* 製品が異なっても製造数量と工数はほぼ比例するため、製造数量の平準化
* バッチ単位で製造し、1日の最低バッチ数と最大バッチ数が設定できる
* 製品ごとに累積製造数を日単位で設定できます（途中に納期がある場合などの優先順）
* 製品ごとに製造の開始日を設定できます
* 特定の日の製品ごとに製造数を設定できます
* 設定した条件の整合性のチェックをほとんどしていないので、使用する時には注意してください
* バッチの数量以下を設定すると、数量がバッチ単位になるように切り上げらます
* 製造数量を日数で割り目標製造数を求め、その数値からの差の絶対値の合計が最小になるように最適化しています
* 製造しない日が多くある時には、製造する日に限定して最適化する方が良い結果が得られます
* 結果が収束しなことがありますので、--tmlim 60 などのオプションを活用してください
目次から辿っていき「結果が得られないときには」の最後の方を参考に
Windowsの方は「インストールと簡単な使い方」も参考に


## プログラム
```GLPK:manufacuring4.mod
# version 0.4
set WorkingDay; # 日付
set ManufacturingMachine dimen 2; # 製品と製造装置の対応を読み込む
set Product:=setof{(p,m) in ManufacturingMachine}p; # 製品名
set Machine:=setof{(p,m) in ManufacturingMachine}m; # 使用装置
param batch_size{Product} integer; # 1バッチの製造数を読み込む
param min_batch{Product} integer;  # 1日の最低バッチ数を読み込む
param max_batch{Product} integer;  # 1日の最高バッチ数を読み込む
param manufacturing_quantity{Product} integer; # 製品ごとの製造数量を読み込む
param max_product_types{Machine} integer, default card(Product); # 装置ごとの1日の最大製品種類数を読み込む
set ManufacturingLimitDay dimen 2; # 納期を設定する製品と日付を読み込む
param manufacturing_limit{ManufacturingLimitDay} integer; # その数量を読み込む
set ManufacturingStart; # 製造開始日が遅れる製品を読み込む
param manufacturing_start{ManufacturingStart} integer; # その日付を読み込む
set StartDay :={i in WorkingDay : card({j in WorkingDay : i>=j})=1}; # 初日
set FinalDay :={i in WorkingDay : card({j in WorkingDay : i<=j})=1}; # 最終日
set FixSchedule dimen 2; # 製造日が決まっている製品名と日付を読み込む
param fix_quantity{FixSchedule} integer; # その数量を読み込む

param min_manufacturing_batch{p in Product, d in WorkingDay} integer := # 累積最低製造バッチ数
    if d in FinalDay then ceil(manufacturing_quantity[p]/batch_size[p]) else (
    if exists{(p,d2) in ManufacturingLimitDay: d2<=d}manufacturing_limit[p, d2] then (max{(p,d2) in ManufacturingLimitDay: d2<=d}manufacturing_limit[p, d2]/batch_size[p]) else 0);
param max_manufacturing_batch{p in Product, d in WorkingDay} integer := # 累積最大製造バッチ数
    if p in ManufacturingStart and manufacturing_start[p]>d then 0 else ceil(manufacturing_quantity[p]/batch_size[p]);
param n_batch{p in Product} := sum{d2 in FinalDay}min_manufacturing_batch[p,d2];  # 製品ごとの総製造バッチ数
param max_batch_in_day{p in Product} := min(n_batch[p], max_batch[p]);  # 製品ごとの1日の最大バッチ数
param quantity_by_machine{m in Machine} := sum{(p,m) in ManufacturingMachine}manufacturing_quantity[p]; # 製造装置ごとの総製造数
param target_manufacturing_quantity{d in WorkingDay, m in Machine} := quantity_by_machine[m]/card(WorkingDay); # 目標製造数

# 製造条件の出力
printf "製品\t製造装置\n";
printf{(p,m) in ManufacturingMachine} "%s\t%s\n", p, m;
printf "\n\t1バッチの製造数\t1日の最低バッチ中";
printf "\t1日の最高バッチ数(総製造数から補正)\n";
printf{p in Product} "%s\t%d\t%d\t%d\n", p, batch_size[p], min_batch[p], max_batch_in_day[p];
printf "\n期間中の総製造数(バッチ数)\n";
printf{p in Product} "%s\t%d\t(%d)\n", p, n_batch[p]*batch_size[p], n_batch[p];
printf "\n装置ごとの総製造数\n";
for{m in Machine}{
    printf "%s\t計\t%d\n", m, quantity_by_machine[m];
    printf{(p,m) in ManufacturingMachine} "\t%s\t%s\n", p, manufacturing_quantity[p];
}
printf "\n装置ごとの目標製造数\n";
printf "日付"; printf{d in WorkingDay} "\t%d", d; printf "\n";
for{m in Machine}{
    printf "%s", m;
    printf{d in WorkingDay} "\t%d", target_manufacturing_quantity[d,m];
    printf "\n";
}

var assgin_batch{p in Product, d in WorkingDay, b in 0..max_batch_in_day[p]} binary <= 
    if (p, d) in FixSchedule then (if ceil(fix_quantity[p,d]/batch_size[p])==b then 1 else 0)
    else (if b>0 and b<min_batch[p] then 0 else 1);
var diffabs{d in WorkingDay, m in Machine} integer >=0; # 目標製造数からの差の絶対値を入れる

s.t. one{p in Product, d in WorkingDay}: sum{b in 0..max_batch_in_day[p]}assgin_batch[p, d, b]==1;
# 累積製造数の下限と上限の範囲になるように
s.t. sum1{p in Product, d in WorkingDay}: sum{d2 in WorkingDay, b in 0..max_batch_in_day[p]: d2<=d}assgin_batch[p, d2, b]*b>=min_manufacturing_batch[p,d];
s.t. sum2{p in Product, d in WorkingDay}: sum{d2 in WorkingDay, b in 0..max_batch_in_day[p]: d2<=d}assgin_batch[p, d2, b]*b<=max_manufacturing_batch[p,d];
# 装置ごとに製品の種類の上限で制限、設定していなければ無視
s.t. maxtypes{d in WorkingDay, m in Machine: max_product_types[m]<card(Product)}: sum{(p,m) in ManufacturingMachine, b in 0..max_batch_in_day[p]: b>0}assgin_batch[p, d, b]<=max_product_types[m];
# 目標製造数量からの差の絶対値を計算
s.t. abs1{d in WorkingDay, m in Machine}: sum{(p,m) in ManufacturingMachine, b in 0..max_batch_in_day[p]: b>0}assgin_batch[p, d, b]*b*batch_size[p]-target_manufacturing_quantity[d,m]<=diffabs[d,m];
s.t. abs2{d in WorkingDay, m in Machine}: sum{(p,m) in ManufacturingMachine, b in 0..max_batch_in_day[p]: b>0}assgin_batch[p, d, b]*b*batch_size[p]-target_manufacturing_quantity[d,m]>=-diffabs[d,m];
# 目標製造数量からの差の絶対値の合計の最小化
minimize min_diffabs: sum{d in WorkingDay, m in Machine}diffabs[d,m];
solve;

# 結果の出力
printf "日付\t"; printf{d in WorkingDay} "\t%d", d; printf "\n";
for{m in Machine}{
    printf "%s\n", m;
    for{p in Product:(p,m) in ManufacturingMachine}{
        printf "\t%s", p;
        printf{d in WorkingDay, b in 0..max_batch_in_day[p]: assgin_batch[p, d, b]==1} "\t%d", b*batch_size[p];
        printf "\n";
    }
    printf "\t小計";
    printf{d in WorkingDay} "\t%d", sum{p in Product, b in 0..max_batch_in_day[p]:(p,m) in ManufacturingMachine and assgin_batch[p, d, b]==1}b*batch_size[p];
    printf "\n";
}

data;
# 製品名と製造装置
set ManufacturingMachine :=
"製品1"  "装置a"
"製品2"  "装置a"
"製品3"  "装置b"
"製品4"  "装置b"
"製品5"  "装置a"
"製品6"  "装置a"
;
# 製品ごとの1バッチの製造数、 1日の最低バッチ数、1日の最高バッチ数(多すぎてもよい,小さいほど速い)
param : batch_size min_batch max_batch :=
"製品1"   750  2  3
"製品2"   800  1  100
"製品3"   1500 1  100
"製品4"   250  1  100
"製品5"   200  1  100
"製品6"   1000 1  100
;
# 日付 (稼働日) 数値が小->大の順になるように
set WorkingDay:=  1 2 3 4 5 6;
# 総製造数
param manufacturing_quantity :=
"製品1"   4500
"製品2"    800
"製品3"  12000
"製品4"      0
"製品5"   1800
"製品6"   5000
;
# 一日で製造する製品の種類の上限(装置別)
param max_product_types:=
"装置a" 2
#"装置b" 2
;
# 納期が期間最終日ではない製品、日付とその日までの累積製造数
param : ManufacturingLimitDay : manufacturing_limit :=
"製品1" 1  1500
"製品1" 3  3000
"製品6" 5  5000
;
# 製造開始日が期間の初日でない製品、製造開始日
param : ManufacturingStart : manufacturing_start :=
# "製品3"  5
;
# 製造日を固定する製品、日付、製造数
param : FixSchedule : fix_quantity :=
"製品3" 2 3000
"製品3" 4 3000
;
end;
```
###動作確認
GLPK 5.0, MacOS 14.4.1
GLPK 5.0, Centos Stream 9