Skip to content

●  사용방법 · 스케줄링

반도체 팹 스케줄링

재진입 공정의 로트를 대체 가능한 설비에 배정합니다. 레시피 셋업, 배치 처리, 큐타임 제한, 예방정비, 납기를 함께 고려합니다.

무엇을 정하나요

로트의 공정마다 쓸 수 있는 설비와 시작 시각을 정하고, 배치 설비라면 함께 돌릴 배치도 정합니다.

최소 실행 예제

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 재계획할 때 고정할 공정입니다.

풀이 파라미터

풀이를 호출할 때 넘기는 값입니다. 시간 제한, 시드, 병렬 수가 여기에 있습니다.

파라미터 기본값 의미
time_limit_seconds None 탐색 시간(초)이며, 0부터 1e9까지 줄 수 있습니다. 지정하지 않으면 설정의 spent_limit_seconds를 쓰며, 기본값은 30입니다.
random_seed None 탐색에 쓰는 난수 시드입니다. 지정하지 않으면 설정값을 쓰며, 기본값은 0입니다.
parallel_solver_count None 독립적으로 동시에 돌리는 탐색 수입니다. 탐색마다 시드가 다르고 시간은 똑같이 쓰며, 가장 좋은 결과를 고릅니다. 지정하지 않으면 설정값을 쓰며, 기본값은 1입니다.
stop_at_first_feasible False 필수 점수가 0이 되는 즉시 멈춥니다.
full_assert False 증분 점수를 전체 재계산과 매번 비교합니다. 느리므로 디버깅할 때만 씁니다.
on_new_best None 새 최고 해를 찾을 때마다 부르는 콜백입니다. 콜백이 예외를 내면 풀이를 멈추고 그 예외를 그대로 냅니다.
config None 기본으로 쓸 SolverConfig입니다. 여기서 준 키워드 인자는 이번 풀이에만 이 값을 덮어쓰며, 넘긴 객체 자체는 바뀌지 않습니다.
constraints None 점수에 넣을 제약과 그 설정입니다. 지정하지 않으면 기본 제약을 씁니다.
탐색 옵션 전체 (26)

SolverConfig에 값을 넣어 config=로 넘기세요.

파라미터 기본값 의미
acceptor_type SIMULATED_ANNEALING 이동을 받아들일지 판단하는 규칙입니다.
best_score_limit — 최고 점수가 이 값에 닿으면 일찍 멈춥니다. 기본값으로는 작동하지 않습니다.
change_move_weight 1.0 변경 이동의 상대적 비중입니다. 0이면 쓰지 않습니다.
custom_move_probability 0.45 한 걸음에서 유스케이스 전용 이동을 고를 수 있는 확률의 상한입니다. 1이면 제한이 없습니다.
full_assert False 증분 점수를 전체 재계산과 매번 비교합니다. 느리므로 디버깅할 때만 씁니다.
late_acceptance_size 0 늦은 수락(late acceptance) 기록의 길이입니다. 0이면 문제 크기에 맞춰 정합니다.
machine_move_probability 0.55 변경 이동 가운데 공정의 설비를 바꾸는 비율입니다. 나머지는 공정의 우선순위를 바꿉니다.
on_new_best None 새 최고 해를 찾을 때마다 부르는 콜백입니다. 콜백이 예외를 내면 풀이를 멈추고 그 예외를 그대로 냅니다.
parallel_solver_count 1 독립적으로 동시에 돌리는 탐색 수입니다. 탐색마다 시드가 다르고 시간은 똑같이 쓰며, 가장 좋은 결과를 고릅니다.
priority_swap_moves True 두 공정의 우선순위를 맞바꾸는 이동을 씁니다. 설비는 그대로 둡니다.
random_seed 0 탐색에 쓰는 난수 시드입니다.
ruin_recreate False 탐색이 정체되면 적응형 파괴·재구성을 실행합니다.
ruin_recreate_max_ruin_count 60 한 번에 제거하는 항목 수의 절대 상한입니다.
ruin_recreate_max_ruin_fraction 0.3 한 번에 제거하는 해의 최대 비율입니다.
ruin_recreate_min_ruin_fraction 0.1 한 번에 제거하는 해의 최소 비율입니다.
ruin_recreate_operator_decay 0.8 연산자 가중치가 적응하는 속도입니다.
ruin_recreate_polish_steps 5000 실행 후 다듬는 언덕 오르기 이동 수입니다. step_count_limit에는 세지 않습니다.
ruin_recreate_rewards [33.0, 9.0, 3.0, 0.0] 새 최고, 개선, 수락, 거절 결과에 주는 연산자 보상입니다.
ruin_recreate_unimproved_step_trigger 5000 파괴·재구성을 실행하기까지 새 최고 해 없이 지나는 걸음 수입니다.
simulated_annealing_starting_temperature (0, 0.0) SIMULATED_ANNEALING 수락 방식의 시작 온도를 (hard, soft)로 줍니다. (0, 0.0)이면 시작 해를 보고 자동으로 정합니다. 필수는 0, 선호는 시작 해 선호 점수의 절댓값에 simulated_annealing_temperature_fraction을 곱한 값이며, 1보다 작아지지 않습니다. 온도는 시간 제한에 맞춰 0까지 내려가고, step_count_limit이 더 많이 진행됐다면 걸음 수에 맞춰 내려갑니다.
simulated_annealing_temperature_fraction 0.01 시작 온도를 자동으로 정할 때 시작 해 선호 점수의 절댓값에 곱하는 비율입니다. 0보다 커야 합니다.
spent_limit_seconds 30.0 탐색 시간(초)이며, 0부터 1e9까지 줄 수 있습니다.
step_count_limit 0 이 걸음 수에 도달하면 멈춥니다. 0이면 제한이 없습니다.
swap_move_weight 0.0 교환 이동의 상대적 비중입니다. 0이면 쓰지 않습니다.
unimproved_spent_limit_seconds 0.0 새 최고 해 없이 이 시간(초)이 지나면 멈춥니다. 0이면 제한이 없습니다.
unimproved_step_count_limit 0 새 최고 해 없이 이 걸음 수가 지나면 멈춥니다. 0이면 제한이 없습니다.

돌아오는 결과

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()처럼 호출해서 넘깁니다. 넘긴 제약만 점수에 들어가고, 아무것도 넘기지 않으면 기본 제약을 씁니다. 목록에 같은 제약을 여러 번 넣을 수 있으며, 넣은 것마다 따로 점수에 들어갑니다. 제약마다 다음 파라미터를 받습니다.

파라미터 기본값 의미
weight None 제약 비용에 곱하는 값이며, 적용되는 수준의 단위로 셉니다. 지정하지 않으면 아래 기본 가중치를 씁니다. 기본 가중치가 0인 제약은 가중치를 줘야 작동합니다.
level None "hard"면 필수, "soft"면 선호로 바꿉니다. Level.HARD, Level.SOFT도 됩니다. 지정하지 않으면 아래 수준을 그대로 씁니다. 이 유스케이스에는 중간 수준이 없어서 "medium"을 주면 ValueError가 납니다. 보상에 "hard"를 주거나 수준이 고정된 제약의 수준을 바꾸려 해도 ValueError가 납니다.
exponent 1.0 부호를 유지한 채 비용에 거듭제곱을 적용하며, 0보다 커야 합니다. offset을 반영한 뒤에 적용합니다. 2.0이면 제곱이라 한곳에 몰린 위반의 비용이 크게 늘어납니다. 차량이나 날짜 같은 단위마다 적용하고, 합계 하나로 계산하는 제약에는 그 합계에 적용합니다.
offset 0.0 거듭제곱 전에 비용 x에서 빼는 값입니다. exponent처럼 단위마다, 합계로 계산하는 제약은 그 합계에 적용하며, 비용과 같은 척도(초, 분, 개수 등)로 셉니다. 부호와 상관없이 유한한 수면 되고, 키워드로만 줄 수 있습니다. 이 값 아래에서 어떻게 계산할지는 clip이 정합니다.
clip True True면 offset을 문턱값으로 씁니다. 넘은 부분만 계산하므로(max(0, x - offset) ** exponent) 그 이하의 비용은 아무것도 더하지 않습니다. False면 부호를 유지한 채 (x - offset) ** exponent를 그대로 계산하므로, 오프셋보다 작은 단위는 비용이 0이어도 음수, 곧 보상으로 들어갑니다. 하드 점수에 들어가는 수준에서는 clip=False에 0이 아닌 오프셋을 주면 ValueError가 납니다. 하드 0이 위반 없음이라는 뜻을 지키기 위해서입니다. 키워드로만 줄 수 있습니다.

위의 최소 실행 예제에 이어서 실행합니다.

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),
]))
이름 수준 기본 가중치 하는 일
machine_eligibility_cost 필수 1 배정이 빠졌거나 그 공정에 없는 설비를 쓰면 벌점을 줍니다.
lot_route_precedence_cost 필수 1 투입 시각이나 공정 순서를 어기면 벌점을 줍니다. 재진입 공정도 포함합니다.
machine_capacity_cost 필수 1 단일 처리 설비에서 처리 시간이 겹치면 벌점을 줍니다.
batch_capacity_cost 필수 1 배치 설비의 동시 처리 한도를 넘은 만큼 벌점을 줍니다.
batch_family_conflict_cost 필수 1 배치 설비에서 서로 맞지 않는 레시피 계열이 겹치면 벌점을 줍니다.
missing_setup_time_cost 필수 1 연속된 처리 사이에 레시피 셋업 시간이 모자라면 벌점을 줍니다.
machine_unavailability_cost 필수 1 비가동 구간과 겹치는 처리에 벌점을 줍니다.
max_queue_time_cost 필수 1 대기 시간이 max_queue_time을 넘으면 벌점을 줍니다.
min_queue_time_cost 필수 1 연속 단계 사이의 최소 대기 시간이 모자라면 벌점을 줍니다.
missing_transport_time_cost 필수 1 설비 그룹 사이의 운반 시간이 모자라면 벌점을 줍니다.
auxiliary_resource_capacity_cost 필수 1 보조 자원의 동시 사용량이 한도를 넘으면 벌점을 줍니다.
auxiliary_resource_unavailability_cost 필수 1 보조 자원을 쓸 수 없는 구간에 사용하면 벌점을 줍니다.
pinned_assignments_cost 필수 1 고정한 공정이 바뀌면 벌점을 줍니다.
max_cycle_time_cost 필수 1 로트 사이클 타임이 max_cycle_time을 넘으면 벌점을 줍니다.
machine_preference_cost 선호 1 선택한 설비의 preference_cost를 더합니다.
min_batch_size_cost 선호 1 배치가 minimum_batch_size에 모자란 로트 수에 벌점을 줍니다.
machine_load_imbalance_cost 선호 1 설비 그룹 안에서 대체 설비 사이의 부하 차이를 줄입니다.
weighted_tardiness_cost 선호 10 로트 납기 지연에 우선순위를 곱한 값을 줄입니다.
cycle_time_cost 선호 1 로트 투입부터 완료까지 걸린 시간의 합을 줄입니다.
makespan_cost 선호 1 마지막 공정의 완료 시각을 줄입니다.

제약 사용법 →

기존 해에서 시작하기

최신 버전이 필요합니다. 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를 냅니다.
  • 같은 설비, 같은 계열로 묶이는 배치의 공정은 처리 시간이 같아야 합니다.