はじめに
工数の平準化についてGLPKを用いたプログラムを作ってみました。GLPKのインストールや使い方は上の目次リンクからたどってください。
まずは非常に単純化した例です。
最適化の条件1
- 製品ごとに必要な工数が与えられており、日ごとの最大総工数以内になるように生産計画を立てたい。
- 各製品の生産は1日で終了する。
- 製品の製造できる期間は決まっているが、生産する順は任意
プログラム1
GLPKで動くプログラムです。
data;
より後の変更で多くの条件に対応できるようになっています。
# version 0.1
set WorkingDay; # 工場稼働日付を読み込む
set ProductName; # 製品名を読み込む
param manHourByProduct{ProductName}; # 製品を作るために必要な工数
set ManufacturablePeriod dimen 2; # 生産可能日を読み込む
param manHourByDay{WorkingDay} default 100; # 1日の標準的な工数上限
var assign{ManufacturablePeriod} binary; # 生産日を割り当てるための変数
# 各製品の生産は1日だけ
s.t. product1{p in ProductName}: sum{(d,p) in ManufacturablePeriod}assign[d,p]==1;
# 1日の総工数を超えない
s.t. limitWorkerHour{d in WorkingDay}: sum{(d,p) in ManufacturablePeriod}assign[d,p]*manHourByProduct[p]<=manHourByDay[d];
solve;
# 結果を出力
for{d in WorkingDay}{
printf "%s 総工数 %10.2f\n", d, sum{(d,p) in ManufacturablePeriod}assign[d,p]*manHourByProduct[p];
printf " 製品名 工数\n";
printf{(d,p) in ManufacturablePeriod: assign[d,p]==1} " %s %d\n", p, manHourByProduct[p];
}
data;
# 工場稼働日付
set WorkingDay := 1 2 4;
# 製品名と製造に必要な工数
param : ProductName : manHourByProduct:=
"製品1" 100
"製品2" 50
"製品3" 20
"製品4" 80
"製品5" 40
;
# 製品名と生産可能日
set ManufacturablePeriod :=
(*, "製品1") 1 2 # 製品1を1 2 日に限定
(*, "製品2") 2 4
(*, "製品3") 1 2
(*, "製品4") 2 4
(*, "製品5") 1 2 4
;
# 1日の工数上限を特別に変えたい場合
param manHourByDay[2] := 110;
end;
MathProgはかなり癖のある言語ですが、慣れると結構便利なのです。
簡単に補足しておきます。
data;
より上がプログラムの領域で、下がデータを書く領域です。それぞれ書き方が違いますので注意してください。
デバッグをするには、
display ManufacturablePeriod;
のようなdispaly文を追加することで、中身を表示することができます。
(ただし、varで宣言している変数はsolveの後でないと表示されません)
ManufacturablePeriodには製品ごとの生産が可能な日付が読み込まれています。
var assign{ManufacturablePeriod} binary;
バイナリ(0か1)が入る変数を宣言しています。生産を割り当てる場合は1,割り当てない時は0になります。
{}の中は添え字の集合を示しますが、ManufacturablePeriodが二次元ですので、C言語などの二次元配列変数に相当します。C言語などでは3日で製品が5つありますので、要素数は3x5=15個になりますが、ここではManufacturablePeriodで宣言した11個しかありません。添え字(4, "製品1")の要素は存在しません。
s.t. product1{p in ProductName}: sum{(d,p) in ManufacturablePeriod}assign[d,p]==1;
制約条件(s.t.)です。二重のループに相当します。{p in ProductName}が外側のループで、pに"製品1","製品2"...と順に値がpに入ります。
{(d,p) in ManufacturablePeriod}が内側のループで、ここのpは外側のp値で拘束されます。
p=="製品1"の時には、ManufacturablePeriodには(1,"製品1"),(2,"製品1")しかありませんので、内側のループはd=1と2だけ繰り返されassign[d,p]の合計が計算されます。assign[1,"製品1"]+assign[2,"製品1"]の合計が1になるようにassignに値が設定されます。
p=="製品2"....も同様です。
printf{(d,p) in ManufacturablePeriod: assign[d,p]==1}....
{}内の条件が満たされた時だけに印刷されます。: assign[d,p]==1はこの条件が満たされた時、すすなわちassignの要素が1(生産を割り当てる)時だけ印刷されます。
上のプログラムに条件を追加することで、複数の日に渡って生産したり、ロット分割など簡単(?)に機能を追加できますので、徐々に公開していきたいと思います。
質問、要望などがありましたらコメントしてください。
あまり良い例とは言えないように思いますが、以前作った関連したプログラムをリンクしておきます。
かなりのことができるはずですが、だいぶ前に作ったもので、思い出せない部分もあります。今後整理して公開していきたいと思います。
GLPKプログラムA
複数のロットに分割して、生産日を分けることができる。
GLPKプログラムB
結構複雑なことができることをだけは感じられると思います。(汗)