무엇을 정하나요
로트의 공정마다 쓸 수 있는 설비와 시작 시각을 정하고, 배치 설비라면 함께 돌릴 배치도 정합니다.
최소 실행 예제
from optimaru.semiconductor_scheduling import solve
from optimaru.semiconductor_scheduling.models import (
LotModel, MachineModel, MachineOptionModel, OperationModel, SemiconductorScheduleModel,
)
def step(id, family, *machines):
return OperationModel(id=id, family=family,
eligible_machines=[MachineOptionModel(machine_id=m, duration=30) for m in machines])
problem = SemiconductorScheduleModel(
machines=[
MachineModel(id="LITHO-1", group="LITHO"),
MachineModel(id="LITHO-2", group="LITHO"),
MachineModel(id="ETCH-1", group="ETCH"),
],
lots=[
LotModel(id="L1", product="A", due_time=240, operations=[
step("L1-1", "M1", "LITHO-1", "LITHO-2"), step("L1-2", "E1", "ETCH-1"),
step("L1-3", "M2", "LITHO-1", "LITHO-2"), # 재진입: 리소그래피로 돌아옵니다
]),
LotModel(id="L2", product="B", due_time=200, priority=2, operations=[
step("L2-1", "M1", "LITHO-1", "LITHO-2"), step("L2-2", "E1", "ETCH-1"),
]),
],
)
print(solve(problem, time_limit_seconds=2.0, random_seed=42)) # Score를 돌려주고 problem.assignments를 채웁니다
print([(a.lot_id, a.operation_id, a.machine_id, a.start, a.end) for a in problem.assignments])
print(problem.makespan, problem.total_weighted_tardiness)
같은 유스케이스를 그림과 실행 결과가 담긴 Jupyter 노트북으로도 볼 수 있습니다. examples/semiconductor-scheduling.ipynb ↗
MachineModel
id 필수 입력 | 설비 id입니다. |
group 필수 입력 | 설비 그룹입니다. LITHO, ETCH 같은 값입니다. |
batch_capacity | 한 번에 돌릴 수 있는 로트 수입니다. 1이면 단일 처리 설비입니다. |
minimum_batch_size | 배치 하나에 넣고 싶은 최소 로트 수입니다. |
unavailable | 예방정비와 그 밖의 비가동 구간입니다. start, end로 줍니다. |
OperationModel
id 필수 입력 | 공정 id입니다. 로트 안에서 겹치면 안 됩니다. |
family 필수 입력 | 레시피 계열입니다. 배치와 셋업 판단에 씁니다. |
eligible_machines 필수 입력 | 쓸 수 있는 설비 목록입니다. 각각 machine_id, duration, preference_cost를 줍니다. |
required_resources | 처리하는 동안 점유하는 작업자, 레티클, 치구의 id입니다. |
min_queue_time | 이 공정 뒤 다음 단계까지 기다려야 하는 최소 시간입니다. |
max_queue_time | 이 공정 뒤 다음 단계가 시작되기까지 허용하는 최대 시간입니다. |
LotModel
id 필수 입력 | 로트 id입니다. |
product 필수 입력 | 제품 이름입니다. |
release_time | 가장 이른 투입 시각입니다. |
due_time 필수 입력 | 납기입니다. |
priority | 로트 납기 지연에 곱하는 값입니다. |
max_cycle_time | 투입부터 완료까지 허용하는 최대 시간입니다. |
operations 필수 입력 | 순서대로 나열한 공정 경로입니다. 같은 설비 그룹으로 다시 돌아와도 되며, 이것이 재진입 공정입니다. |
AuxiliaryResourceModel
id 필수 입력 | 자원 id입니다. |
capacity | 동시에 점유할 수 있는 공정 수입니다. |
unavailable | 쓸 수 없는 구간입니다. |
SetupTimeModel
machine_group 필수 입력 | 셋업이 적용되는 설비 그룹입니다. |
from_family 필수 입력 | 이전 레시피 계열입니다. |
to_family 필수 입력 | 다음 레시피 계열입니다. |
duration 필수 입력 | 셋업 시간입니다. |
TransportTimeModel
from_group 필수 입력 | 로트가 떠나는 설비 그룹입니다. |
to_group 필수 입력 | 로트가 도착할 설비 그룹입니다. |
duration 필수 입력 | 최소 운반 시간입니다. |
SemiconductorScheduleModel
machines 필수 입력 | 설비 목록입니다. |
lots 필수 입력 | 로트 목록입니다. |
auxiliary_resources | 작업자, 레티클, 치구 같은 보조 자원입니다. |
setup_times | 순서에 따라 달라지는 레시피 셋업 시간입니다. |
transport_times | 설비 그룹 사이의 운반 시간입니다. |
pinned_assignments | 재계획할 때 고정할 공정입니다. |
풀이 파라미터
풀이를 호출할 때 넘기는 값입니다. 시간 제한, 시드, 병렬 수가 여기에 있습니다.
탐색 옵션 전체 (26)
SolverConfig에 값을 넣어 config=로 넘기세요.
돌아오는 결과
solve는 problem을 그 자리에서 채우고 엔진 Score를 돌려줍니다. 값은 .hard와 .soft로 읽습니다. problem.to_core()로 만든 엔진 객체도 받습니다.
AssignmentModel
lot_id 필수 입력 | 로트입니다. |
operation_id 필수 입력 | 공정입니다. |
machine_id 필수 입력 | 선택된 설비입니다. |
start 필수 입력 | 시작 시각입니다. |
end 필수 입력 | 종료 시각입니다. |
batch_id | 배치 설비에서 묶인 배치입니다. |
SemiconductorScheduleModel
assignments | 공정마다 하나씩 있는 AssignmentModel 목록입니다. |
makespan | 마지막 공정의 완료 시각입니다. |
total_weighted_tardiness | 로트 지연에 우선순위를 곱해 더한 값입니다. |
total_cycle_time | 투입부터 완료까지 걸린 시간의 합계입니다. |
score_hard | 필수 점수입니다. 0이면 가능한 해입니다. |
score_soft | 선호 점수입니다. |
제약
optimaru.semiconductor_scheduling.constraints에서 클래스를 가져와 MachineEligibilityCost()처럼 호출해서 넘깁니다. 넘긴 제약만 점수에 들어가고, 아무것도 넘기지 않으면 기본 제약을 씁니다. 목록에 같은 제약을 여러 번 넣을 수 있으며, 넣은 것마다 따로 점수에 들어갑니다. 제약마다 다음 파라미터를 받습니다.
위의 최소 실행 예제에 이어서 실행합니다.
from optimaru.semiconductor_scheduling.constraints import (
LotRoutePrecedenceCost, MachineCapacityCost, MachineEligibilityCost, WeightedTardinessCost,
)
custom = problem.model_copy(deep=True)
print(solve(custom, time_limit_seconds=2.0, random_seed=42, constraints=[
MachineEligibilityCost(), LotRoutePrecedenceCost(), MachineCapacityCost(),
WeightedTardinessCost(weight=5),
]))
제약 사용법 →
기존 해에서 시작하기
최신 버전이 필요합니다. pip install -U optimaru로 올려 주세요.
with_initial_solution(problem, solution)은 solution에서 탐색을 시작하는 problem의 복사본을 돌려줍니다. solution은 풀린 모델이나 그 JSON 딕셔너리입니다.
위의 최소 실행 예제에 이어서 실행합니다.
from optimaru.semiconductor_scheduling import with_initial_solution
warm = with_initial_solution(problem, problem.model_dump(mode="json"))
print(solve(warm, time_limit_seconds=2.0, random_seed=42))
print(warm.makespan, warm.total_weighted_tardiness)
- 배정은 로트 id와 공정 id로 맞춥니다.
solve의 결과라면 가능한 해든 아니든 받습니다. - 완전한 해는 넣은 그대로 채점하고, 탐색이 확실히 더 나은 일정을 찾지 못하면 그대로 돌려줍니다. 그래서 점수가 시작 해보다 나빠지지 않습니다. 다만 탐색이 시작 해 근처에 머물거나 해를 개선한다는 보장은 없습니다.
- 일부만 있는 해는 채점할 수 없어서 디코딩합니다. 설비와 상대적인 순서는 유지하지만 시작 시각은 다시 계산합니다.
time_limit_seconds=0을 주면 바로 멈추므로 완전한 해는 넣은 그대로 돌아옵니다. step_count_limit = 0은 제한이 없다는 뜻이므로, 바로 멈추려면 시간 제한을 쓰세요.- 모르는 id, 중복, 쓸 수 없는 설비, 맞지 않는 처리 시간은
ValueError를 냅니다.
예외와 제한
- 시각은 단위가 정해지지 않은 정수입니다. 한 가지 단위로 통일해서 쓰세요. 2^62를 넘는 입력은
ValueError를, 64비트 범위를 넘는 비용은 OverflowError를 냅니다. - 같은 설비, 같은 계열로 묶이는 배치의 공정은 처리 시간이 같아야 합니다.
직접 해 보기