무엇을 정하나요
발표마다 시간대와 발표장을 정합니다.
최소 실행 예제
from optimaru import conference_scheduling as cs
from optimaru.conference_scheduling.models import (
ConferenceScheduleModel, RoomModel, SpeakerModel, TalkModel, TimeslotModel,
)
problem = ConferenceScheduleModel(
name="demo",
talk_types=["Talk"],
timeslots=[
TimeslotModel(id="S1", start_minute=540, end_minute=585, talk_types=[0]),
TimeslotModel(id="S2", start_minute=630, end_minute=675, talk_types=[0]),
],
rooms=[
RoomModel(id="R1", capacity=200, talk_types=[0]),
RoomModel(id="R2", capacity=50, talk_types=[0]),
],
speakers=[SpeakerModel(id="Ana"), SpeakerModel(id="Ben", unavailable_timeslots=[0])],
talks=[
TalkModel(code="T1", speakers=[0], theme_track_tags=["AI"], favorite_count=120),
TalkModel(code="T2", speakers=[1], theme_track_tags=["AI"], favorite_count=40),
TalkModel(code="T3", speakers=[0], theme_track_tags=["Ops"], favorite_count=10),
],
)
core = problem.to_core()
cs.solve(core, time_limit_seconds=2.0, random_seed=42) # 그 자리에서 풉니다
solved = ConferenceScheduleModel.from_core(core)
print(solved.score_hard, solved.score_soft)
for talk in solved.talks:
slot, room = divmod(talk.slot_room, len(solved.rooms))
print(talk.code, solved.timeslots[slot].id, solved.rooms[room].id) 같은 유스케이스를 그림과 실행 결과가 담긴 Jupyter 노트북으로도 볼 수 있습니다. examples/conference-scheduling.ipynb ↗
입력 파라미터
TimeslotModel
| 필드 | 의미 |
|---|
id 필수 입력 start_ minute 필수 입력 end_ minute 필수 입력 talk_ types 필수 입력 talk_types 내 위치입니다.tags RoomModel
| 필드 | 의미 |
|---|
id 필수 입력 capacity talk_ types talk_types 내 위치입니다.unavailable_ timeslots timeslots 내 위치입니다.tags SpeakerModel
| 필드 | 의미 |
|---|
id 필수 입력 unavailable_ timeslots timeslots 내 위치입니다.required_ timeslot_ tagsprohibited_ timeslot_ tagsrequired_ room_ tagsprohibited_ room_ tags preferred_ timeslot_ tagsundesired_ timeslot_ tagspreferred_ room_ tagsundesired_ room_ tags TalkModel
| 필드 | 의미 |
|---|
code 필수 입력 talk_ type talk_types 내 위치입니다.speakers speakers 내 위치입니다.theme_ track_ tags sector_ tags audience_ types audience_ level content_ tags language en입니다.required_ timeslot_ tagsprohibited_ timeslot_ tagsrequired_ room_ tagsprohibited_ room_ tags preferred_ timeslot_ tagsundesired_ timeslot_ tagspreferred_ room_ tagsundesired_ room_ tags mutually_ exclusive_ talks_ tags prerequisite_ talks talks 내 위치입니다.favorite_ count crowd_ control_ risk pinned slot_room을 고정합니다.ConferenceScheduleModel
| 필드 | 의미 |
|---|
name 필수 입력 talk_ types 필수 입력 timeslots 필수 입력 rooms 필수 입력 speakers talks minimum_ consecutive_ talks_ pause_ in_ minutes 풀이 파라미터
풀이를 호출할 때 넘기는 값입니다. 시간 제한, 시드, 병렬 수가 여기에 있습니다.
| 파라미터 | 기본값 | 의미 |
|---|---|---|
time_ | None | 탐색 시간(초)이며, 0부터 1e9까지 줄 수 있습니다. 지정하지 않으면 설정의 spent_limit_seconds를 쓰며, 기본값은 30입니다. |
random_ | None | 탐색에 쓰는 난수 시드입니다. 지정하지 않으면 설정값을 쓰며, 기본값은 0입니다. |
parallel_ | None | 독립적으로 동시에 돌리는 탐색 수입니다. 탐색마다 시드가 다르고 시간은 똑같이 쓰며, 가장 좋은 결과를 고릅니다. 지정하지 않으면 설정값을 쓰며, 기본값은 1입니다. |
stop_ | False | 필수 점수가 0이 되는 즉시 멈춥니다. |
on_ | None | 새 최고 해를 찾을 때마다 부르는 콜백입니다. 콜백이 예외를 내면 풀이를 멈추고 그 예외를 그대로 냅니다. |
config | None | 기본으로 쓸 SolverConfig입니다. 여기서 준 키워드 인자는 이번 풀이에만 이 값을 덮어쓰며, 넘긴 객체 자체는 바뀌지 않습니다. |
constraints | None | 점수에 넣을 제약과 그 설정입니다. 지정하지 않으면 기본 제약을 씁니다. |
탐색 옵션 전체 (26)
SolverConfig에 값을 넣어 config=로 넘기세요.
| 파라미터 | 기본값 | 의미 |
|---|---|---|
best_ | — | 최고 점수가 이 값에 닿으면 일찍 멈춥니다. 기본값으로는 작동하지 않습니다. |
change_ | 0.75 | 변경 이동의 상대적 비중입니다. 0이면 쓰지 않습니다. |
compatible_ | 0.9 | 변경 이동이 발표 유형을 받는 칸만 고를 확률입니다. 0이면 쓰지 않습니다. |
descent_ | 0.0 | 최선 개선 하강의 비중입니다. 0이면 끕니다. |
full_ | False | 증분 점수를 전체 재계산과 매번 비교합니다. 느리므로 디버깅할 때만 씁니다. |
hot_ | 0.0 | 필수 위반이 있는 발표를 옮길 확률입니다. 0이면 쓰지 않습니다. |
late_ | 0 | 늦은 수락(late acceptance) 기록의 길이입니다. 0이면 문제 크기에 맞춰 정합니다. |
on_ | None | 새 최고 해를 찾을 때마다 부르는 콜백입니다. 콜백이 예외를 내면 풀이를 멈추고 그 예외를 그대로 냅니다. |
parallel_ | 1 | 독립적으로 동시에 돌리는 탐색 수입니다. 탐색마다 시드가 다르고 시간은 똑같이 쓰며, 가장 좋은 결과를 고릅니다. |
random_ | 0 | 탐색에 쓰는 난수 시드입니다. |
restart_ | 8 | 시간 제한을 나눠 쓰는 연속 로컬 서치 횟수입니다. 1이면 한 번만 돕니다. |
ruin_ | True | 탐색이 정체되면 적응형 파괴·재구성을 실행합니다. |
ruin_ | 12 | 그 작은 파괴로 제거할 최대 항목 수입니다. |
ruin_ | -1.0 | 한 걸음을 작은 파괴·복구로 쓰고 일반 이동처럼 판단할 확률입니다. 음수는 지원하는 유스케이스에서 자동 선택이고, 0이면 끕니다. |
ruin_ | 60 | 한 번에 제거하는 항목 수의 절대 상한입니다. |
ruin_ | 0.3 | 한 번에 제거하는 해의 최대 비율입니다. |
ruin_ | 0.1 | 한 번에 제거하는 해의 최소 비율입니다. |
ruin_ | 0.8 | 연산자 가중치가 적응하는 속도입니다. |
ruin_ | 5000 | 실행 후 다듬는 언덕 오르기 이동 수입니다. step_count_limit에는 세지 않습니다. |
ruin_ | [33.0, 9.0, 3.0, 0.0] | 새 최고, 개선, 수락, 거절 결과에 주는 연산자 보상입니다. |
ruin_ | 5000 | 파괴·재구성을 실행하기까지 새 최고 해 없이 지나는 걸음 수입니다. |
spent_ | 30.0 | 탐색 시간(초)이며, 0부터 1e9까지 줄 수 있습니다. |
step_ | 0 | 이 걸음 수에 도달하면 멈춥니다. 0이면 제한이 없습니다. |
swap_ | 0.25 | 교환 이동의 상대적 비중입니다. 0이면 쓰지 않습니다. |
unimproved_ | 0.0 | 새 최고 해 없이 이 시간(초)이 지나면 멈춥니다. 0이면 제한이 없습니다. |
unimproved_ | 0 | 새 최고 해 없이 이 걸음 수가 지나면 멈춥니다. 0이면 제한이 없습니다. |
돌아오는 결과
solve는 엔진 객체를 받아 그 자리에서 풀고 돌려줍니다. 결과는 from_core로 변환해 읽습니다.
TalkModel
| 필드 | 의미 |
|---|
slot_ room timeslot_index * room_count + room_index로 만든 합성 값입니다. -1이면 미배정입니다.ConferenceScheduleModel
| 필드 | 의미 |
|---|
score_ hard score_ soft 제약
optimaru.conference_scheduling.constraints에서 클래스를 가져와 TalkTypeIncompatibilityCost()처럼 호출해서 넘깁니다. 넘긴 제약만 점수에 들어가고, 아무것도 넘기지 않으면 기본 제약을 씁니다. 목록에 같은 제약을 여러 번 넣을 수 있으며, 넣은 것마다 따로 점수에 들어갑니다. 제약마다 다음 파라미터를 받습니다.
| 파라미터 | 기본값 | 의미 |
|---|---|---|
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이 위반 없음이라는 뜻을 지키기 위해서입니다. 키워드로만 줄 수 있습니다. |
audience_type_diversity_cost, audience_level_diversity_cost, language_diversity_cost는 비용이 음수인 보상입니다. 가중치를 올리면 점수가 올라가고, level="hard"를 주면 ValueError가 납니다. 나머지는 모두 벌점입니다.
위의 최소 실행 예제에 이어서 실행합니다.
from optimaru.conference_scheduling.constraints import (
ContentConflictCost, RoomConflictCost, SpeakerConflictCost, get_constraints,
)
picked = get_constraints([RoomConflictCost(), SpeakerConflictCost(), ContentConflictCost(weight=500)])
custom = problem.to_core()
cs.solve(custom, time_limit_seconds=2.0, random_seed=42, constraints=picked)
print(custom.score) | 이름 | 수준 | 기본 가중치 | 하는 일 |
|---|---|---|---|
talk_ | 필수 | 100000 | 발표 유형을 받지 않는 시간대나 발표장에 놓이면 벌점을 줍니다. |
room_ | 필수 | 100000 | 닫혀 있는 시간대의 발표장에 놓이면 벌점을 줍니다. |
room_ | 필수 | 1000 | 시간이 겹치는 두 발표가 같은 발표장을 쓰면 겹친 시간(분)만큼 벌점을 줍니다. |
speaker_ | 필수 | 100 | 발표자가 올 수 없는 시간대에 놓이면 벌점을 줍니다. |
speaker_ | 필수 | 10 | 시간이 겹치는 두 발표가 발표자를 공유하면 발표자별로 겹친 시간(분)만큼 벌점을 줍니다. |
talk_ | 필수 | 10 | 선행 발표보다 먼저 열리거나 겹치면 벌점을 줍니다. |
talk_ | 필수 | 1 | 서로 배타적인 태그를 공유하는 두 발표가 겹치면 벌점을 줍니다. |
consecutive_ | 필수 | 1 | 한 발표자의 연속 발표 사이에 최소 간격이 없으면 벌점을 줍니다. |
crowd_ | 필수 | 1 | 사람이 몰리는 발표가 같은 시간대에 그런 발표 둘 이상과 겹치면 벌점을 줍니다. |
speaker_ | 필수 | 1 | 발표자가 요구한 태그가 시간대에 없으면 벌점을 줍니다. |
speaker_ | 필수 | 1 | 발표자가 금지한 태그가 시간대에 있으면 벌점을 줍니다. |
talk_ | 필수 | 1 | 발표가 요구한 태그가 시간대에 없으면 벌점을 줍니다. |
talk_ | 필수 | 1 | 발표가 금지한 태그가 시간대에 있으면 벌점을 줍니다. |
speaker_ | 필수 | 1 | 발표자가 요구한 태그가 발표장에 없으면 벌점을 줍니다. |
speaker_ | 필수 | 1 | 발표자가 금지한 태그가 발표장에 있으면 벌점을 줍니다. |
talk_ | 필수 | 1 | 발표가 요구한 태그가 발표장에 없으면 벌점을 줍니다. |
talk_ | 필수 | 1 | 발표가 금지한 태그가 발표장에 있으면 벌점을 줍니다. |
theme_ | 선호 | 10 | 주제 트랙이 같은 두 발표가 겹치면 벌점을 줍니다. |
theme_ | 선호 | 10 | 같은 날 같은 트랙의 두 발표가 다른 발표장에서 열리면 벌점을 줍니다. |
sector_ | 선호 | 10 | 대상 분야가 같은 두 발표가 겹치면 벌점을 줍니다. |
audience_ | 선호 | 1 | 주제 트랙과 청중 유형이 모두 같은 두 발표가 겹치면 벌점을 줍니다. |
content_ | 선호 | 10 | 내용이 같은 입문 발표가 심화 발표보다 늦거나 겹치면 벌점을 줍니다. |
content_ | 선호 | 100 | 내용이 같은 두 발표가 겹치면 벌점을 줍니다. 선호 규칙 중 가장 무겁습니다. |
different_ | 선호 | 10 | 내용이나 트랙이 같은 두 발표가 다른 날에 열리면 벌점을 줍니다. |
popular_ | 선호 | 10 | 덜 인기 있는 발표가 더 인기 있는 발표보다 큰 발표장을 쓰면 벌점을 줍니다. |
speaker_ | 선호 | 20 | 발표자가 원하는 태그가 시간대에 없으면 벌점을 줍니다. |
speaker_ | 선호 | 20 | 발표자가 피하고 싶은 태그가 시간대에 있으면 벌점을 줍니다. |
talk_ | 선호 | 20 | 발표가 원하는 태그가 시간대에 없으면 벌점을 줍니다. |
talk_ | 선호 | 20 | 발표가 피하고 싶은 태그가 시간대에 있으면 벌점을 줍니다. |
speaker_ | 선호 | 20 | 발표자가 원하는 태그가 발표장에 없으면 벌점을 줍니다. |
speaker_ | 선호 | 20 | 발표자가 피하고 싶은 태그가 발표장에 있으면 벌점을 줍니다. |
talk_ | 선호 | 20 | 발표가 원하는 태그가 발표장에 없으면 벌점을 줍니다. |
talk_ | 선호 | 20 | 발표가 피하고 싶은 태그가 발표장에 있으면 벌점을 줍니다. |
speaker_ | 선호 | 20 | 발표자의 발표가 이틀을 넘겨 흩어지면 벌점을 줍니다. |
audience_ | 선호 보상 | 1 | 한 시간대에 여러 청중 유형을 다루면 보상을 줍니다. |
audience_ | 선호 보상 | 1 | 한 시간대에 여러 청중 수준이 섞이면 보상을 줍니다. |
language_ | 선호 보상 | 10 | 한 시간대에 여러 언어의 발표가 열리면 보상을 줍니다. |
기존 해에서 시작하기
최신 버전이 필요합니다. pip install -U optimaru로 올려 주세요.
with_initial_solution(problem, solution)은 solution에서 탐색을 시작하는 problem의 복사본을 돌려줍니다. solution은 풀린 ConferenceScheduleModel이나 그 JSON 딕셔너리입니다. solve는 엔진 객체를 받으므로 to_core()로 변환해 넘깁니다.
위의 최소 실행 예제에 이어서 실행합니다.
restart = cs.with_initial_solution(problem, solved).to_core()
cs.solve(restart, time_limit_seconds=2.0, random_seed=43)
print(restart.score) - 발표는
code로 맞춥니다. - 완전한 해를 넣으면 구성 단계를 건너뛰고 그 해에서 바로 탐색을 시작합니다. 일부만 넣으면 넣은 부분은 그대로 두고, 빠진 발표만 새로 구성합니다.
time_limit_seconds=0을 주면 바로 멈추므로 완전한 해는 넣은 그대로 돌아옵니다.step_count_limit = 0은 제한이 없다는 뜻이므로, 바로 멈추려면 시간 제한을 쓰세요.- 고정된 발표는 해의 내용과 상관없이
problem에 있는 값을 유지합니다. slot_room은 시간대와 발표장 목록의 위치로 만든 값입니다. 그래서 해의 목록이 다르거나 순서만 달라도ValueError가 납니다. 모르는 코드와 범위를 벗어난 값도 마찬가지입니다.
예외와 제한
- 탐색은 시간대×발표장 격자 하나에서 이루어집니다. 발표를 맞는 칸에 두는 일은
talk_type_incompatibility_cost가 맡으므로, 이 규칙을 없앨 생각이 아니면 켜 두세요. - 발표자, 선행 발표, 발표 유형, 불가능한 시간대는 id가 아니라 목록 안의 위치로 적습니다.