PCB Manufacturing Scheduling

このサンプルでは、Printed Circuit Board(PCB) の生産スケジュール問題を取り扱います。

問題設定

ここでは PCB には挿入実装と表面実装の部品が存在し、それぞれ別工程で実装することを考えます。生産工程は下記のように考えます。

  • 表面実装工程: 表面実装のはんだ付け(マウンタ)

  • 挿入実装工程: 挿入実装のはんだ付け(卓上型はんだ付けロボット)

  • 検査工程: 部品の検査

一つの PCB の生産は表面実装工程、挿入実装工程、検査工程の 3 つの工程で完成されます。表面実装工程では一つのマウンタで実装され、挿入実装工程は PCB ごとに決められた数の卓上はんだ付けロボットで実装され、最後に検査工程で PCB ごとに決められた検査手順を行います。

また、挿入実装工程と検査工程はそれぞれの工程を開始したら中断することができないとします。

表面実装工程

表面実装工程では、表面実装のはんだ付けを行うマウンタが 3 つ(マウンタ A, マウンタ B、マウンタ C)あるとします。 それぞれのマウンタは、PCB を一つずつ実装することができます。

挿入実装工程

挿入実装工程では、卓上型はんだ付けロボットが 6 つ(卓ロボ A, 卓ロボ B, 卓ロボ C, 卓ロボ D, 卓ロボ E, 卓ロボ F)があるとします。各 PCB は決められた複数の卓ロボで決められた順序で実装されますが、一度実装を始めた場合は、挿入実装工程を完了する必要があります。

検査工程

検査工程では、PCB ごとに決められた検査手順を行います。検査工程は、PCB ごとに決められた手順を行う検査手順 1,2,3,4 があり、検査工程は一度開始したら中断することができません。また検査工程ではリソースとして、作業員 1 人と PCB ごとに決められた検査治具が必要です。また同じ作業員が一貫して検査工程を行うほうが効率がよいとして、作業員の入れ替えにはコストがかかるとします。

from amplify_sched import *
import itertools
import pandas as pd
import random

num_operator = 3
num_pcb = 15
jig_list1 = ["治具{}".format(i) for i in range(1, 4)]
jig_list2 = ["治具{}".format(i) for i in range(4, 7)]
jig_list3 = ["治具{}".format(i) for i in range(7, 10)]
jig_list4 = ["治具{}".format(i) for i in range(10, 15)]
jig_capacity = 300
mounters = ["マウンタA", "マウンタB", "マウンタC"]
takurobos = ["卓ロボA", "卓ロボB", "卓ロボC", "卓ロボD", "卓ロボE", "卓ロボF"]
operators = ["従業員A", "従業員B", "従業員C"]
checks = ["検査手順1", "検査手順2", "検査手順3", "検査手順4"]
operator_change_time = 100
d = {
    "PCB": [],
    "マウンタA": [],
    "マウンタB": [],
    "マウンタC": [],
    "卓ロボA": [],
    "卓ロボB": [],
    "卓ロボC": [],
    "卓ロボD": [],
    "卓ロボE": [],
    "卓ロボF": [],
    "検査手順1": [],
    "検査手順2": [],
    "検査手順3": [],
    "検査手順4": [],
    "検査治具1": [],
    "検査治具2": [],
    "検査治具3": [],
    "検査治具4": [],
}
cols = pd.MultiIndex.from_tuples(
    [
        ("表面実装工程", "マウンタA"),
        ("表面実装工程", "マウンタB"),
        ("表面実装工程", "マウンタC"),
        ("挿入実装工程", "卓ロボA"),
        ("挿入実装工程", "卓ロボB"),
        ("挿入実装工程", "卓ロボC"),
        ("挿入実装工程", "卓ロボD"),
        ("挿入実装工程", "卓ロボE"),
        ("挿入実装工程", "卓ロボF"),
        ("検査手順1", "従業員A"),
        ("検査手順1", "従業員B"),
        ("検査手順1", "従業員C"),
        ("検査手順2", "従業員A"),
        ("検査手順2", "従業員B"),
        ("検査手順2", "従業員C"),
        ("検査手順3", "従業員A"),
        ("検査手順3", "従業員B"),
        ("検査手順3", "従業員C"),
        ("検査手順4", "従業員A"),
        ("検査手順4", "従業員B"),
        ("検査手順4", "従業員C"),
        ("治具", "検査手順1"),
        ("治具", "検査手順2"),
        ("治具", "検査手順3"),
        ("治具", "検査手順4"),
    ]
)
index = pd.Index(["PCB{}".format(i + 1) for i in range(num_pcb)], name="PCB")
data = []
for i in range(num_pcb):
    d = []
    d.append(random.randint(10, 30))  # マウンタA
    d.append(random.randint(10, 30))  # マウンタB
    d.append(random.randint(10, 30))  # マウンタC
    d.append(random.randint(0, 30))  # 卓ロボA
    d.append(random.randint(0, 30))  # 卓ロボB
    d.append(random.randint(0, 30))  # 卓ロボC
    d.append(random.randint(0, 30))  # 卓ロボD
    d.append(random.randint(0, 30))  # 卓ロボE
    d.append(random.randint(0, 30))  # 卓ロボF
    d.append(random.randint(10, 30))  # 検査手順1: 従業員A
    d.append(random.randint(10, 30))  # 検査手順1: 従業員B
    d.append(random.randint(10, 30))  # 検査手順1: 従業員C
    d.append(random.randint(10, 30))  # 検査手順2: 従業員A
    d.append(random.randint(10, 30))  # 検査手順2: 従業員B
    d.append(random.randint(10, 30))  # 検査手順2: 従業員C
    d.append(random.randint(10, 30))  # 検査手順3: 従業員A
    d.append(random.randint(10, 30))  # 検査手順3: 従業員B
    d.append(random.randint(10, 30))  # 検査手順3: 従業員C
    d.append(random.randint(10, 30))  # 検査手順4: 従業員A
    d.append(random.randint(10, 30))  # 検査手順4: 従業員B
    d.append(random.randint(10, 30))  # 検査手順4: 従業員C
    d.append(random.choices(jig_list1, k=2))  # 治具: 検査手順1
    d.append(random.choices(jig_list2, k=2))  # 治具: 検査手順2
    d.append(random.choices(jig_list3, k=2))  # 治具: 検査手順3
    d.append(random.choices(jig_list4, k=3))  # 治具: 検査手順4
    data.append(d)

df = pd.DataFrame(data, columns=cols, index=index)
df["表面実装工程"]
マウンタA マウンタB マウンタC
PCB
PCB1 24 25 30
PCB2 22 29 12
PCB3 10 19 19
PCB4 23 24 15
PCB5 24 30 27
PCB6 28 18 18
PCB7 30 24 26
PCB8 15 28 11
PCB9 15 22 16
PCB10 20 10 25
PCB11 24 22 27
PCB12 27 27 20
PCB13 20 25 23
PCB14 24 13 24
PCB15 25 17 20
df["挿入実装工程"]
卓ロボA 卓ロボB 卓ロボC 卓ロボD 卓ロボE 卓ロボF
PCB
PCB1 0 4 5 27 13 18
PCB2 7 9 26 4 14 20
PCB3 8 7 7 20 28 20
PCB4 11 4 12 10 4 28
PCB5 7 29 20 9 13 6
PCB6 14 30 2 30 3 13
PCB7 18 25 19 3 9 30
PCB8 26 20 10 27 24 14
PCB9 29 13 25 14 18 20
PCB10 23 25 15 23 7 5
PCB11 3 22 6 29 30 0
PCB12 5 28 13 24 23 9
PCB13 26 4 25 17 24 3
PCB14 17 15 4 11 27 20
PCB15 24 11 21 1 19 3
df["検査手順1"]
従業員A 従業員B 従業員C
PCB
PCB1 28 11 26
PCB2 19 27 17
PCB3 27 29 24
PCB4 29 17 10
PCB5 14 16 24
PCB6 11 16 27
PCB7 27 26 23
PCB8 10 14 30
PCB9 12 28 14
PCB10 20 17 16
PCB11 23 19 10
PCB12 24 18 28
PCB13 30 12 20
PCB14 30 18 27
PCB15 26 19 30
df["検査手順2"]
従業員A 従業員B 従業員C
PCB
PCB1 16 28 25
PCB2 20 20 28
PCB3 14 21 11
PCB4 24 21 20
PCB5 19 17 14
PCB6 30 30 25
PCB7 27 12 17
PCB8 14 27 25
PCB9 26 11 22
PCB10 13 16 24
PCB11 10 16 25
PCB12 28 11 25
PCB13 26 29 22
PCB14 10 29 29
PCB15 28 26 21
df["検査手順3"]
従業員A 従業員B 従業員C
PCB
PCB1 26 27 25
PCB2 29 23 24
PCB3 17 25 20
PCB4 28 16 21
PCB5 16 12 26
PCB6 22 18 21
PCB7 29 14 18
PCB8 15 24 24
PCB9 20 20 30
PCB10 22 27 12
PCB11 25 30 18
PCB12 30 26 19
PCB13 16 29 15
PCB14 13 19 29
PCB15 14 28 21
df["検査手順4"]
従業員A 従業員B 従業員C
PCB
PCB1 15 21 26
PCB2 12 16 23
PCB3 19 22 25
PCB4 21 26 19
PCB5 17 20 18
PCB6 29 24 17
PCB7 12 10 14
PCB8 15 28 20
PCB9 28 10 17
PCB10 24 23 10
PCB11 25 22 15
PCB12 13 14 21
PCB13 16 11 17
PCB14 24 20 24
PCB15 20 23 11
df["治具"]
検査手順1 検査手順2 検査手順3 検査手順4
PCB
PCB1 [治具1, 治具1] [治具5, 治具6] [治具7, 治具8] [治具14, 治具10, 治具13]
PCB2 [治具1, 治具2] [治具5, 治具5] [治具9, 治具8] [治具13, 治具14, 治具10]
PCB3 [治具2, 治具2] [治具6, 治具5] [治具9, 治具8] [治具14, 治具14, 治具10]
PCB4 [治具1, 治具1] [治具4, 治具6] [治具8, 治具8] [治具14, 治具10, 治具12]
PCB5 [治具2, 治具3] [治具4, 治具4] [治具9, 治具8] [治具11, 治具13, 治具14]
PCB6 [治具1, 治具1] [治具5, 治具4] [治具9, 治具8] [治具10, 治具14, 治具12]
PCB7 [治具3, 治具1] [治具4, 治具4] [治具7, 治具8] [治具12, 治具12, 治具14]
PCB8 [治具2, 治具1] [治具4, 治具4] [治具8, 治具8] [治具11, 治具12, 治具12]
PCB9 [治具2, 治具2] [治具4, 治具6] [治具9, 治具7] [治具11, 治具13, 治具14]
PCB10 [治具2, 治具1] [治具6, 治具6] [治具7, 治具8] [治具13, 治具13, 治具12]
PCB11 [治具2, 治具3] [治具5, 治具5] [治具7, 治具9] [治具12, 治具12, 治具12]
PCB12 [治具2, 治具1] [治具6, 治具4] [治具8, 治具9] [治具14, 治具10, 治具14]
PCB13 [治具1, 治具1] [治具5, 治具6] [治具7, 治具7] [治具14, 治具13, 治具10]
PCB14 [治具1, 治具1] [治具5, 治具6] [治具8, 治具7] [治具11, 治具14, 治具11]
PCB15 [治具2, 治具3] [治具4, 治具5] [治具9, 治具8] [治具11, 治具12, 治具12]

Amplify Sched を用いた定式化

挿入実装工程と検査工程はそれぞれの工程を開始したら中断することができないので、Amplify Sched における no wait 制約を用いて定式化します。

表面実装工程、挿入実装工程、検査工程をそれぞれ別の Job として、Job 間の順序制約を課します。

model = Model()
for j in df.index:
    model.jobs.add(j + "表面実装工程")
    model.jobs.add(j + "挿入実装工程")
    model.jobs[j + "挿入実装工程"].no_wait = True
    model.jobs[j + "挿入実装工程"].add_dependent_jobs(model.jobs[j + "表面実装工程"])
    model.jobs.add(j + "検査工程")
    model.jobs[j + "検査工程"].no_wait = True
    model.jobs[j + "検査工程"].add_dependent_jobs(model.jobs[j + "挿入実装工程"])
for m in mounters + takurobos + operators:
    model.machines.add(m)
for k in jig_list1 + jig_list2 + jig_list3 + jig_list4:
    model.resources.add(k)
    model.resources[k].capacity = jig_capacity


for j in df.index:
    # 表面実装工程
    model.jobs[j + "表面実装工程"].append(Task())
    for m in mounters:
        model.jobs[j + "表面実装工程"][0].processing_times[m] = int(df.loc[j, "表面実装工程"][m])
    # 挿入実装工程
    for i, m in enumerate(takurobos):
        model.jobs[j + "挿入実装工程"].append(Task())
        model.jobs[j + "挿入実装工程"][i].processing_times[m] = int(df.loc[j, "挿入実装工程"][m])
    # 検査工程
    for i, p in enumerate(checks[0:2]):
        model.jobs[j + "検査工程"].append(Task())
        for m in operators:
            model.jobs[j + "検査工程"][i].processing_times[m] = int(df.loc[j, p][m])
        for r in df.loc[j, "治具"][p]:
            model.jobs[j + "検査工程"][i].add_required_resource(r)
        for m1, m2 in itertools.combinations(operators, 2):
            model.jobs[j + "検査工程"][i].add_transportation_time(operator_change_time, m1, m2)
            model.jobs[j + "検査工程"][i].add_transportation_time(operator_change_time, m2, m1)
token = "xxxxxxxxxxxxxxxxxxxxxx"
gantt = model.solve(token=token, timeout=10)
gantt.timeline(machine_view=True)