マシニングセンタにおける生産計画¶
Fixstars Amplify Scheduling Engine (Amplify SE) を用いた生産計画への取り組み例として、マシニングセンタ (machining center) へのジョブ割り当てを紹介します。
イントロダクション¶
マシニングセンタは材料の加工を行う機械です。自動工具交換装置(Automatic Tool Changer)を搭載しており、予め取り付けておいた工具を、材料加工の最中に自動で切り替えることができます。
ここでは、各マシニングセンタに1人ずつ作業員が配置され、材料と工具の着脱を行う状況を想定します。
一つの材料を加工するために、以下のような手順を踏みます。
Step 1. 不要な工具を取り外し、必要な工具を取り付ける
Step 2. 加工する材料を取り付ける
Step 3. マシニングセンタが材料を加工する
Step 4. 加工された材料を取り外す
工場には複数のマシニングセンタがあり、機体によって材料の加工時間が異なります。 また、材料によって必要な工具が異なります。 各マシニングセンタに適切な順番で適切な材料を加工させることによって、効率的に作業を行うことを目指します。
問題設定¶
本サンプルプログラムでは、例としてマシニングセンタの数を 3 台、材料の数を 15 個の場合を考えます。目的関数をメイクスパンとして、Amplify SE を用いて マシニングセンタに材料を割り当てるスケジューリング問題を解きます。以下で用いられている用語や Amplify SE については、『Amplify SE とは』をご覧ください。
以下、材料を材料 1, …, 材料 15、マシニングセンタをマシン 1, マシン 2, マシン 3 と呼ぶことにします。
材料の加工時間¶
加工時間を、材料の取付から取り外しまで(Step 2 から 4)の時間と定義します。
材料 $i$ ($i=1,\cdots,15$) の材料をマシン $j$ ($j=1,\cdots,3$) で加工する場合の加工時間は以下の表のように与えられています。
時間は整数値である必要があります。ここでは、1 分を 1 とします。
マシン 1 | マシン 2 | マシン 3 | |
---|---|---|---|
材料 1 | 10 | 20 | 30 |
材料 2 | 20 | 30 | 10 |
材料 3 | 30 | 40 | 20 |
材料 4 | 40 | 10 | 30 |
材料 5 | 10 | 30 | 40 |
材料 6 | 20 | 40 | 10 |
材料 7 | 30 | 10 | 20 |
材料 8 | 40 | 20 | 30 |
材料 9 | 10 | 20 | 40 |
材料 10 | 20 | 30 | 10 |
材料 11 | 30 | 40 | 20 |
材料 12 | 40 | 10 | 30 |
材料 13 | 10 | 30 | 40 |
材料 14 | 20 | 40 | 10 |
材料 15 | 30 | 10 | 20 |
使用工具¶
各材料ごとに使用工具が決まっています。
今回は使用工具の数を 20 個 とし、工具 1,…,工具 20 と表すことにします。
各材料の加工に必要な工具は、下記の表のように与えられています。
工具 1 | 工具 2 | 工具 3 | 工具 4 | 工具 5 | 工具 6 | 工具 7 | 工具 8 | 工具 9 | 工具 10 | 工具 11 | 工具 12 | 工具 13 | 工具 14 | 工具 15 | 工具 16 | 工具 17 | 工具 18 | 工具 19 | 工具 20 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
材料 1 | o | o | o | o | o | |||||||||||||||
材料 2 | o | o | o | o | o | |||||||||||||||
材料 3 | o | o | o | o | o | |||||||||||||||
材料 4 | o | o | o | o | o | |||||||||||||||
材料 5 | o | o | o | o | ||||||||||||||||
材料 6 | o | o | o | o | ||||||||||||||||
材料 7 | o | o | o | o | ||||||||||||||||
材料 8 | o | o | o | o | ||||||||||||||||
材料 9 | o | o | o | o | ||||||||||||||||
材料 10 | o | o | o | o | ||||||||||||||||
材料 11 | o | o | o | o | o | |||||||||||||||
材料 12 | o | o | o | o | o | |||||||||||||||
材料 13 | o | o | o | o | o | |||||||||||||||
材料 14 | o | o | o | o | o | |||||||||||||||
材料 15 | o | o | o | o | o |
材料の切り替え時間¶
各マシニングセンタにおいて、材料の切り替えにかかる時間として工具の着脱にかかる時間のみを考慮します(材料の着脱は無視できる時間で行えるものとする)。
今回は簡単のため、全ての工具について $\text{取り外し時間} = \text{取り付け時間} = 10$ とします。
すると、材料の切り替え時間は以下の式で計算できます。
$$ \begin{align} \text{材料の切り替え時間} = \text{工具の取り外し数} \times \text{取り外し時間} + \text{工具の取り付け数} \times \text{取り付け時間} \end{align} $$
例 1: 材料 1 から材料 2 に切り替えるための時間は、工具 1, 工具 2, 工具 3, 工具 4, 工具 5 を取り外し、工具 6, 工具 7, 工具 8, 工具 9, 工具 10 を取り付けるため、$5 \times 10 + 5 \times 10 = 100$となります。
例 2: 材料 1 から材料 5 に切り替えるための時間は、工具 3, 工具 4, 工具 5 を取り外し、工具 11, 工具 16 を取り付けるため、$3 \times 10 + 2 \times 10 = 50$となります。
工具の同時使用可能数¶
工具の数には限りがあるため、同時に使用できる数に制限があります。 各工具 工具 1, $\cdots$, 工具 20 の同時使用可能数は次の表のように与えられます。
工具 | 工具 1 | 工具 2 | 工具 3 | 工具 4 | 工具 5 | 工具 6 | 工具 7 | 工具 8 | 工具 9 | 工具 10 | 工具 11 | 工具 12 | 工具 13 | 工具 14 | 工具 15 | 工具 16 | 工具 17 | 工具 18 | 工具 19 | 工具 20 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
同時使用可能数 | 2 | 2 | 3 | 3 | 3 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 3 | 2 | 2 | 2 |
Amplify SE による材料割り当て¶
それでは、Amplify SE を用いて各マシンへの最適な材料割り当てを求めましょう。
Amplify SE の用語との対応¶
まず初めに、今回の問題における要素(マシニングセンタ、材料、工具)が Amplify SE の言葉でどのように表されるか考えましょう。
マシニングセンタが材料を加工する、という状況なので、これらはそれぞれ マシン と ジョブ に対応します。 工具は加工において必要な対象で、Amplify SE の言葉では リソース に対応します。 (これらの用語についての説明はチュートリアルページをご参照ください。)
要素 | Amplify SE の言葉 |
---|---|
マシニングセンタ | マシン |
材料 | ジョブ |
工具 | リソース |
ライブラリのインポート¶
今回の処理に必要なライブラリをインポートします。
# ! pip install amplify_sched # Google Colab 場合、こちらのコメントアウトを外し、amplify_sched をインストールしてください。
from amplify_sched import *
import itertools
import pandas as pd
# 注 Pandas は Python のデータ解析ライブラリで、データ構造やデータ処理を行うツールを提供しています。
pip install amplify-sched
によって Amplify SE をインストールする際、Pandas も同時にインストールされているため、Amplify SE
が使えれば自動的に Pandas も使える状態になっています。
今回、Pandas の高度な機能は使用していないため、Pandas を使わなくても同じ処理をするコードが書けますが、Pandas を使うことでより分かりやすく扱いやすいコードを書くことができます。
前処理¶
まず、材料と工具に関する上記の設定を Pandas の DataFrame として保存します。
machine_list = ["マシン1", "マシン2", "マシン3"] # マシニングセンタのリスト
remove_work_time = 10 # 工具の取り外し時間
set_work_time = 10 # 工具の取り付け時間
# (1) 材料の情報をまとめたDataFrameを構築する
df_material = pd.DataFrame()
# 材料の名前
df_material["材料"] = ["材料{}".format(i) for i in range(1, 16)]
df_material.set_index("材料", inplace=True)
# 各マシンによる加工時間
df_material["加工時間(マシン1)"] = [
10,
20,
30,
40,
10,
20,
30,
40,
10,
20,
30,
40,
10,
20,
30,
] # 材料1, 材料2, ...の加工にかかる時間
df_material["加工時間(マシン2)"] = [20, 30, 40, 10, 30, 40, 10, 20, 20, 30, 40, 10, 30, 40, 10]
df_material["加工時間(マシン3)"] = [30, 10, 20, 30, 40, 10, 20, 30, 40, 10, 20, 30, 40, 10, 20]
# 材料1,...,15に使用する工具
df_material["使用工具"] = [
["工具1", "工具2", "工具3", "工具4", "工具5"], # 材料1
["工具6", "工具7", "工具8", "工具9", "工具10"], # 材料2
["工具11", "工具12", "工具13", "工具14", "工具15"], # 材料3
["工具16", "工具17", "工具18", "工具19", "工具20"], # 材料4
["工具1", "工具2", "工具11", "工具16"], # 材料5
["工具3", "工具4", "工具12", "工具17"], # 材料6
["工具5", "工具6", "工具13", "工具18"], # 材料7
["工具7", "工具8", "工具14", "工具19"], # 材料8
["工具9", "工具10", "工具15", "工具20"], # 材料9
["工具1", "工具6", "工具11", "工具16"], # 材料10
["工具1", "工具2", "工具3", "工具4", "工具5"], # 材料11
["工具6", "工具7", "工具8", "工具9", "工具10"], # 材料12
["工具11", "工具12", "工具13", "工具14", "工具15"], # 材料13
["工具16", "工具17", "工具18", "工具19", "工具20"], # 材料14
["工具1", "工具2", "工具3", "工具4", "工具5"], # 材料15
]
# (2) 工具の情報をまとめたDataFrameを構築する
df_tool = pd.DataFrame()
# 工具の名前
df_tool["工具"] = ["工具{}".format(i) for i in range(1, 21)]
df_tool.set_index("工具", inplace=True)
# 同時使用可能数
df_tool["同時使用可能数"] = [
2,
2,
3,
3,
3,
2,
2,
2,
2,
2,
2,
2,
2,
2,
2,
2,
3,
2,
2,
2,
] # 工具1, 2, ..., 20
df_material
モデル構築¶
次に、上で作った DataFrame を用いて、Amplify SE の Model()
オブジェクトを構築します。
model = Model()
# modelにマシンを追加
for m in machine_list:
model.machines.add(m)
# modelにリソースを追加
for k in df_tool.index:
model.resources.add(k)
model.resources[k].capacity = int(df_tool.loc[k, "同時使用可能数"])
for j in df_material.index:
# modelにジョブを追加
model.jobs.add(j)
model.jobs[j].append(Task())
# マシンの加工時間を設定
for m in machine_list:
model.jobs[j][0].processing_times[m] = int(df_material.loc[j, f"加工時間({m})"])
# 工具の同時使用可能数を設定
for k in df_material.loc[j, "使用工具"]:
model.jobs[j][0].required_resources.append(k)
材料の切り替え時間を考慮するために、setup_times
を使用します。
for j1, j2 in itertools.combinations(df_material.index, 2):
# 材料の切り替え時間
remove_num = len(
set(df_material.loc[j1, "使用工具"]) - set(df_material.loc[j2, "使用工具"])
)
add_num = len(set(df_material.loc[j2, "使用工具"]) - set(df_material.loc[j1, "使用工具"]))
time_j1_to_j2 = remove_num * remove_work_time + add_num * set_work_time
time_j2_to_j1 = add_num * remove_work_time + remove_num * set_work_time
# 切り替え時間を setup_times に追加
for m in machine_list:
model.machines[m].setup_times.append((time_j1_to_j2, j1, j2))
model.machines[m].setup_times.append((time_j2_to_j1, j2, j1))
トークンを設定し、model.solve
を呼ぶと Amplify SE が実行され、解が返ってきます。
# Amplify SEの実行
token = "" # ローカル環境等で使用する場合は、Amplify SE のアクセストークンを入力してください。
result = model.solve(token=token, timeout=10) # 必要に応じて timeout を変更してください。
result
には解の情報が格納されています。
result.status
を見ることで、得られた解が最適解か、実行可能解か、あるいは制約を満たす解が見つからなかったかが分かります。FEASIBLE
な解でもある程度の最適化が施されていることは期待されますが、timeout
を増やすことでより最適解に近い解を得ることが可能です。
status |
意味 |
---|---|
OPTIMAL | 最適解 |
FEASIBLE | 実行可能解(制約は満たすがメイクスパンは最小とは限らない) |
INFEASIBLE | 実行可能解が見つからなかった |
print(result.status)
fig = result.timeline(machine_view=True)
fig.show()
出力結果を解釈するために、以下の条件が満たされているか確認してみましょう。
- 材料 1, 11, 15 には同一の工具が使用されています。これらを連続で加工すれば、工具の着脱をしなくて済むので効率がよいはずです。実際にそうなっているでしょうか?
model.solve
を実行するたびに異なる結果が返されます。複数回実行したとき、メイクスパンは同じ値になっているでしょうか?
result.timeline
において、引数を設定しない場合は材料ごとのガントチャートが表示されます。
fig = result.timeline()
fig.show()
この図からは、各時刻においてどの材料が加工されているかが分かります。
ある時刻に注目した時、同時に使用されている工具の数が上限を上回っていないことを確認してみましょう。
まとめ¶
今回は、マシニングセンタへの最適なジョブ割り当てを Amplify SE によって求めました。
各材料の加工時間だけでなく、装置の工具を取り替える時間も setup_times
によって反映することができました。
今回説明したコードを 1 つにまとめると、次のようになります。
from amplify_sched import *
import itertools
import pandas as pd
def schedule_machining_center():
machine_list = ["マシン1", "マシン2", "マシン3"] # マシニングセンタのリスト
remove_work_time = 10 # 工具の取り外し時間
set_work_time = 10 # 工具の取り付け時間
# (1) 材料の情報をまとめたDataFrameを構築する
df_material = pd.DataFrame()
# 材料の名前
df_material["材料"] = ["材料{}".format(i) for i in range(1, 16)]
df_material.set_index("材料", inplace=True)
# 各マシンによる加工時間
df_material["加工時間(マシン1)"] = [
10,
20,
30,
40,
10,
20,
30,
40,
10,
20,
30,
40,
10,
20,
30,
] # 材料1, 材料2, ...の加工にかかる時間
df_material["加工時間(マシン2)"] = [
20,
30,
40,
10,
30,
40,
10,
20,
20,
30,
40,
10,
30,
40,
10,
]
df_material["加工時間(マシン3)"] = [
30,
10,
20,
30,
40,
10,
20,
30,
40,
10,
20,
30,
40,
10,
20,
]
# 材料1,...,15に使用する工具
df_material["使用工具"] = [
["工具1", "工具2", "工具3", "工具4", "工具5"], # 材料1
["工具6", "工具7", "工具8", "工具9", "工具10"], # 材料2
["工具11", "工具12", "工具13", "工具14", "工具15"], # 材料3
["工具16", "工具17", "工具18", "工具19", "工具20"], # 材料4
["工具1", "工具2", "工具11", "工具16"], # 材料5
["工具3", "工具4", "工具12", "工具17"], # 材料6
["工具5", "工具6", "工具13", "工具18"], # 材料7
["工具7", "工具8", "工具14", "工具19"], # 材料8
["工具9", "工具10", "工具15", "工具20"], # 材料9
["工具1", "工具6", "工具11", "工具16"], # 材料10
["工具1", "工具2", "工具3", "工具4", "工具5"], # 材料11
["工具6", "工具7", "工具8", "工具9", "工具10"], # 材料12
["工具11", "工具12", "工具13", "工具14", "工具15"], # 材料13
["工具16", "工具17", "工具18", "工具19", "工具20"], # 材料14
["工具1", "工具2", "工具3", "工具4", "工具5"], # 材料15
]
# (2) 工具の情報をまとめたDataFrameを構築する
df_tool = pd.DataFrame()
# 工具の名前
df_tool["工具"] = ["工具{}".format(i) for i in range(1, 21)]
df_tool.set_index("工具", inplace=True)
# 同時使用可能数
df_tool["同時使用可能数"] = [
2,
2,
3,
3,
3,
2,
2,
2,
2,
2,
2,
2,
2,
2,
2,
2,
3,
2,
2,
2,
] # 工具1, 2, ..., 20
model = Model()
# modelにマシンを追加
for m in machine_list:
model.machines.add(m)
# modelにリソースを追加
for k in df_tool.index:
model.resources.add(k)
model.resources[k].capacity = int(df_tool.loc[k, "同時使用可能数"])
for j in df_material.index:
# modelにジョブを追加
model.jobs.add(j)
model.jobs[j].append(Task())
# マシンの加工時間を設定
for m in machine_list:
model.jobs[j][0].processing_times[m] = int(df_material.loc[j, f"加工時間({m})"])
# 工具の同時使用可能数を設定
for k in df_material.loc[j, "使用工具"]:
model.jobs[j][0].required_resources.append(k)
for j1, j2 in itertools.combinations(df_material.index, 2):
# 材料の切り替え時間
remove_num = len(
set(df_material.loc[j1, "使用工具"]) - set(df_material.loc[j2, "使用工具"])
)
add_num = len(
set(df_material.loc[j2, "使用工具"]) - set(df_material.loc[j1, "使用工具"])
)
time_j1_to_j2 = remove_num * remove_work_time + add_num * set_work_time
time_j2_to_j1 = add_num * remove_work_time + remove_num * set_work_time
# 切り替え時間を setup_times に追加
for m in machine_list:
model.machines[m].setup_times.append((time_j1_to_j2, j1, j2))
model.machines[m].setup_times.append((time_j2_to_j1, j2, j1))
# Amplify SEの実行
token = "" # ローカル環境等で使用する場合は、Amplify SE のアクセストークンを入力してください。
result = model.solve(token=token, timeout=10) # tokenにはご自身のトークンを入力してください
fig = result.timeline(machine_view=True)
fig.show()