- HOME
- psim言語講座(第17回)チュートリアル編(8) AGV による搬送
2025年1月21日 10:06
前回は、AGV による搬送があるような生産ラインの例を実装しました。
今回は、もう少し AGV の動作を工夫していこうと思います。
前回のモデルの復習(工程)
まず、前回の工程、AGVの定義を復習します。
工程0
初期に生産する製品が全て置かれている。
工程1、2
各工程には以下の要素があります:
加工前の製品置き場
加工後の製品置き場
加工前の製品置き場に製品が置かれると、自動で加工が開始され、加工後の製品置き場に移されます。
加工機械と置き場は同時に1つの製品のみを処理可能。
工程3
生産済みの製品置き場。
AGV
AGVは3台あり、工程0, 1, 2, 3を順番に巡回します。工程3の次は工程0に戻ります。
AGVは1台につき1つの製品を搬送できます。
搬送ルール
工程0では在庫があれば製品を取得。
工程1や工程2では加工前または加工後の製品を取得・配置。
工程3では製品を最終的に配置。
今回のモデル
前回の設計、実装した AGV の運用方式は、固定巡回方式と呼ばれます。ルールが単純で、動作が予測可能で、トラブルが少ない方式であると言えます。しかし、無駄な移動が発生し、生産効率を最大化できず、状況変化に対応できず、ボトルネックも発生しやすい方式でもあります。
実際のAGVのスケジューリング方法には様々な方法が存在します。効率を重視する方式や、強化学習を利用した方式なども提案されています。
今回は前回のモデルをルールベースのスケジューリングに置き換えていこうと思います。具体的には、納期最小化を目指します。
各製品が納期(due_date)を持ち、AGVがどの工程でどの製品を優先的に扱うかを判断するルールを導入します。
実装コード
initialize() t0 = Store(False, monitor = True) s1 = Store(1, monitor = True) f1 = Facility(1, monitor = True) t1 = Store(1, monitor = True) s2 = Store(1, monitor = True) f2 = Facility(1, monitor = True) t2 = Store(1, monitor = True) s3 = Store(False, monitor = True) getlock = Facility(1, monitor = True) T0_LOC = 0 T1_LOC = 1 T2_LOC = 2 T3_LOC = 3 travel_matrix = [ [ 0, 10, 20, 30], [10, 0, 10, 20], [20, 10, 0, 10], [30, 20, 10, 0]] # 最終工程や保管場所として利用 class Product: def __init__(self, name, instructions, due_date): self.name = name self.instructions = instructions self.due_date = due_date def init(): for name, instructions, due_date in [ ("製品1", [1, 2, 3], 80), ("製品2", [1, 3], 100), ("製品3", [2, 3], 75), ("製品4", [2, 1, 3], 50), ("製品5", [1, 3], 55), ("製品6", [2, 3], 130), ]: prod = Product(name, instructions, due_date) yield t0.put1(prod, name = "put1") def process1(): yield pause(10) while True: result = yield s1.get1(name = "get") item = result["get"] print(f"時間 {now()} 工程1 作業開始 {item.name}") lock = (yield f1.request(name = "lock"))["lock"] yield pause(10) lock.release() print(f"時間 {now()} 工程1 作業終了 {item.name}") yield t1.put1(item, name = "put") def process2(): yield pause(10) while True: result = yield s2.get1(name = "get") item = result["get"] print(f"時間 {now()} 工程2 作業開始 {item.name}") lock = (yield f2.request(name = "lock"))["lock"] yield pause(10) lock.release() print(f"時間 {now()} 工程2 作業終了 {item.name}") yield t2.put1(item, name = "put") def init_agv(): for i in range(3): #for i in range(1): yield subactivate(agv)(f"AGV{i}") yield alwaysFalse() def agv(agvname): prod = None # 各AGVの初期位置を設定 current_loc = T0_LOC while True: # 1. 製品を保持していない場合、納期最短の製品を取得する if prod is None: next_loc = None # 納期が最も近い製品を取得予約 lock = (yield getlock.request(name = "lock"))["lock"] candidates = [] candidates.extend(t0.buffer) candidates.extend(t1.buffer) candidates.extend(t2.buffer) if candidates: print(f"候補製品: {[(p.name, p.due_date) for p in candidates]}") prod = min(candidates, key=lambda p: p.due_date) #print((prod.name, prod.due_date)) if prod in t0.buffer: prod = (yield t0.get1(lambda v: v is prod, name="get"))["get"] next_loc = T0_LOC elif prod in t1.buffer: prod = (yield t1.get1(lambda v: v is prod, name="get"))["get"] next_loc = T1_LOC elif prod in t2.buffer: prod = (yield t2.get1(lambda v: v is prod, name="get"))["get"] next_loc = T2_LOC lock.release() if prod is not None: # 該当するバッファに移動 if next_loc is not None: move_time = travel_matrix[current_loc][next_loc] print(f"時間 {now()} {agvname} 移動 空 工程{current_loc} -> 工程{next_loc} 移動時間 {move_time}") yield pause(move_time) current_loc = next_loc print(f"時間 {now()} {agvname} 取得 {prod.name} 工程{current_loc} 納期 {prod.due_date}") # 2. 保持している製品があれば、次の工程に従って配置を試みる if prod is not None: next_loc = prod.instructions[0] if prod.instructions else None if next_loc is not None: prod.instructions.pop(0) move_time = travel_matrix[current_loc][next_loc] print(f"時間 {now()} {agvname} 移動 {prod.name} 工程{current_loc} -> 工程{next_loc} 移動時間 {move_time}") yield pause(move_time) current_loc = next_loc print(f"時間 {now()} {agvname} 配置待ち {prod.name} 工程{current_loc} 納期 {prod.due_date}") s = [None, s1, s2, s3][next_loc] yield s.put1(prod, name="put") print(f"時間 {now()} {agvname} 配置完了 {prod.name} 工程{current_loc} 納期 {prod.due_date}") prod = None yield pause(1) # 次のサイクル activate(init)() activate(process1)() activate(process2)() activate(init_agv)() start(until = 120)
工程とバッファ:
t0 ~ t3 は各工程の製品置き場を表します。
s1, s2 は加工前の製品置き場、t1, t2 は加工後の製品置き場。
f1, f2 は加工機械。
AGV の設定
- AGV の初期位置 (T0_LOC など) や移動時間を定義した travel_matrix を設定。
製品クラス(Product)
製品名、加工指示 (instructions)、納期 (due_date) を保持するクラスです。
各製品の加工指示は工程番号のリストとして管理されます。
プロセスの定義
init は、各製品を初期状態で t0 に配置します。
process1, process2 は、工程1および工程2の加工処理をシミュレートします。製品を取得し、加工機械を使用して一定時間処理した後、加工後のバッファに配置します。
init_agv と agv は、3台の AGV を初期化し、それぞれの搬送ルールに従って動作します。納期が最も近い製品を優先的に取得し、次の加工工程または最終配置場所に搬送します。製品を保持していない場合、すべての工程のバッファを確認し、納期が最も近い製品を選択し、製品を保持している場合、加工指示に従い次の工程に搬送します。
実行結果
候補製品: [('製品1', 80), ('製品2', 100), ('製品3', 75)] 時間 0.0 AGV0 移動 空 工程0 -> 工程0 移動時間 0 候補製品: [('製品1', 80), ('製品2', 100), ('製品4', 50)] 時間 0.0 AGV0 取得 製品3 工程0 納期 75 時間 0.0 AGV0 移動 製品3 工程0 -> 工程2 移動時間 20 時間 0.0 AGV1 移動 空 工程0 -> 工程0 移動時間 0 候補製品: [('製品1', 80), ('製品2', 100), ('製品5', 55)] 時間 0.0 AGV1 取得 製品4 工程0 納期 50 時間 0.0 AGV1 移動 製品4 工程0 -> 工程2 移動時間 20 時間 0.0 AGV2 移動 空 工程0 -> 工程0 移動時間 0 時間 0.0 AGV2 取得 製品5 工程0 納期 55 時間 0.0 AGV2 移動 製品5 工程0 -> 工程1 移動時間 10 時間 10.0 AGV2 配置待ち 製品5 工程1 納期 55 時間 10.0 AGV2 配置完了 製品5 工程1 納期 55 時間 10.0 工程1 作業開始 製品5 候補製品: [('製品1', 80), ('製品2', 100), ('製品6', 130)] 時間 11.0 AGV2 移動 空 工程1 -> 工程0 移動時間 10 時間 20.0 AGV0 配置待ち 製品3 工程2 納期 75 時間 20.0 AGV1 配置待ち 製品4 工程2 納期 50 時間 20.0 工程1 作業終了 製品5 時間 20.0 AGV0 配置完了 製品3 工程2 納期 75 時間 20.0 工程2 作業開始 製品3 時間 20.0 AGV1 配置完了 製品4 工程2 納期 50 時間 21.0 AGV2 取得 製品1 工程0 納期 80 時間 21.0 AGV2 移動 製品1 工程0 -> 工程1 移動時間 10 ...
納期ベースのスケジューリングにより、効率的な搬送が実現されています。
並列動作が正確にシミュレートされ、リアルタイムの生産ラインに近い動作が確認できます。
しかしながら、このアルゴリズムでは、特定の工程でボトルネックが発生する場合がある。また、残り工程を加味したスケジューリングになっていない、移動時間を加味したスケジューリングになっていないなどの課題が残ります。
まとめ
今回は、納期を基準としたルールベースのスケジューリングにより、効率的な搬送を実現しました。各 AGV が独立して動作しながらも、リソースの競合を避けつつ、生産ライン全体の効率を高めています。
一方で、まだまだ改善の余地は残ります。次回は、更に改善していこうと思います。

http://www.msi.co.jp NTTデータ数理システムができること