무엇을 정하나요
모든 항공편의 승무원 자리마다 탈 승무원을 정합니다.
최소 실행 예제
from datetime import datetime, timedelta
from optimaru import flight_crew_scheduling as fc
from optimaru.flight_crew_scheduling.models import (
AirportModel, EmployeeModel, FlightAssignmentModel, FlightCrewScheduleModel, FlightModel,
)
out = datetime(2026, 1, 5, 8)
flights = [
FlightModel(flight_number="OUT", departure_airport="ICN", departure_utc_date_time=out,
arrival_airport="NRT", arrival_utc_date_time=out + timedelta(hours=2)),
FlightModel(flight_number="BACK", departure_airport="NRT",
departure_utc_date_time=out + timedelta(hours=5),
arrival_airport="ICN", arrival_utc_date_time=out + timedelta(hours=7)),
]
problem = FlightCrewScheduleModel(
airports=[AirportModel(code="ICN"), AirportModel(code="NRT")],
employees=[EmployeeModel(id="P1", home_airport="ICN", skills=["Pilot"])],
flights=flights,
flight_assignments=[
FlightAssignmentModel(id=f"A{i}", flight=f.flight_number, required_skill="Pilot")
for i, f in enumerate(flights)
],
)
result = fc.solve(problem, time_limit_seconds=2.0, random_seed=42)
print(result.score_hard, result.score_soft)
print({a.id: a.employee for a in result.flight_assignments})
같은 유스케이스를 그림과 실행 결과가 담긴 Jupyter 노트북으로도 볼 수 있습니다. examples/flight-crew-scheduling.ipynb ↗
AirportModel
code 필수 입력 | 공항 코드입니다. 항공편과 승무원이 이 값을 참조합니다. |
EmployeeModel
id 필수 입력 | 승무원 id입니다. |
home_airport 필수 입력 | 소속 공항 코드입니다. |
skills | 보유한 자격 목록입니다. |
unavailable_days | 근무할 수 없는 날짜 목록입니다. |
FlightModel
flight_number 필수 입력 | 항공편 id입니다. |
departure_airport 필수 입력 | 출발 공항 코드입니다. |
departure_utc_date_time 필수 입력 | 출발 시각(UTC)입니다. 시간대가 없는 datetime은 UTC로 읽습니다. |
arrival_airport 필수 입력 | 도착 공항 코드입니다. |
arrival_utc_date_time 필수 입력 | 도착 시각(UTC)입니다. |
FlightAssignmentModel
id 필수 입력 | 자리 id입니다. |
flight 필수 입력 | 해당 항공편의 flight_number입니다. |
required_skill | 이 자리에 필요한 자격입니다. |
pinned | 이 자리의 employee를 고정합니다. 점수에는 들어가지만 바뀌지 않습니다. |
FlightCrewScheduleModel
airports 필수 입력 | 공항 목록입니다. |
employees 필수 입력 | 승무원 목록입니다. |
flights 필수 입력 | 항공편 목록입니다. |
flight_assignments 필수 입력 | 승무원 자리마다 하나씩 있는 목록입니다. |
풀이 파라미터
풀이를 호출할 때 넘기는 값입니다. 시간 제한, 시드, 병렬 수가 여기에 있습니다.
탐색 옵션 전체 (20)
SolverConfig에 값을 넣어 config=로 넘기세요.
돌아오는 결과
solve는 모델이나 같은 모양의 딕셔너리를 받아 풀린 모델을 돌려줍니다.
FlightAssignmentModel
employee | 이 자리에 배정된 승무원 id입니다. None이면 미배정입니다. |
FlightCrewScheduleModel
score_hard | 필수 점수입니다. 0이면 가능한 해입니다. |
score_soft | 선호 점수입니다. |
제약
optimaru.flight_crew_scheduling.constraints에서 클래스를 가져와 RequiredSkillCost()처럼 호출해서 넘깁니다. 넘긴 제약만 점수에 들어가고, 아무것도 넘기지 않으면 기본 제약을 씁니다. 목록에 같은 제약을 여러 번 넣을 수 있으며, 넣은 것마다 따로 점수에 들어갑니다. 제약마다 다음 파라미터를 받습니다.
위의 최소 실행 예제에 이어서 실행합니다.
from optimaru.flight_crew_scheduling.constraints import (
EmployeeUnavailabilityCost, FlightConflictCost, RequiredSkillCost, get_constraints,
)
picked = get_constraints([
RequiredSkillCost(), FlightConflictCost(), EmployeeUnavailabilityCost(level="soft"),
])
custom = fc.solve(problem, time_limit_seconds=2.0, random_seed=42, constraints=picked)
print(custom.score_hard, custom.score_soft)
제약 사용법 →
기존 해에서 시작하기
최신 버전이 필요합니다. pip install -U optimaru로 올려 주세요.
with_initial_solution(problem, solution)은 solution에서 탐색을 시작하는 problem의 복사본을 돌려줍니다. solution은 solve가 돌려준 모델이나 그 JSON 딕셔너리입니다.
위의 최소 실행 예제에 이어서 실행합니다.
seeded = fc.with_initial_solution(problem, result.model_dump(mode="json"))
again = fc.solve(seeded, time_limit_seconds=2.0, random_seed=43)
print(again.score_hard, again.score_soft)
- 자리는 id로 맞춥니다.
- 완전한 해를 넣으면 구성 단계를 건너뛰고 그 해에서 바로 탐색을 시작합니다. 일부만 넣으면 넣은 부분은 그대로 두고, 빠진 자리만 새로 구성합니다.
time_limit_seconds=0을 주면 바로 멈추므로 완전한 해는 넣은 그대로 돌아옵니다. step_count_limit = 0은 제한이 없다는 뜻이므로, 바로 멈추려면 시간 제한을 쓰세요.- 고정된 자리는 해의 내용과 상관없이
problem에 있는 승무원을 유지합니다. - 모르는 id가 있으면
ValueError가 납니다.
예외와 제한
- 휴식 시간이나 근무 시간 규칙은 없습니다. 필수 규칙은 자격, 일정 중복, 연결편, 근무 불가 날짜입니다.
직접 해 보기