무엇을 정하나요
회의마다 시작 시간 칸과 회의실을 정합니다. 회의는 시작 칸부터 duration_in_grains개의 연속된 칸을 차지합니다.
최소 실행 예제
from optimaru import meeting_scheduling as ms
from optimaru.meeting_scheduling.models import (
MeetingAssignmentModel, MeetingModel, MeetingScheduleModel, PersonModel, RoomModel, TimeGrainModel,
)
problem = MeetingScheduleModel(
people=[PersonModel(id=f"P{i}") for i in range(4)],
time_grains=[ # 15분 단위 칸, 하루 09:00~11:00
TimeGrainModel(id=f"G{i}", grain_index=i, day_of_year=200, starting_minute_of_day=540 + 15 * i)
for i in range(8)
],
rooms=[RoomModel(id="R1", capacity=4), RoomModel(id="R2", capacity=2)],
meetings=[
MeetingModel(id="M1", topic="Roadmap", duration_in_grains=2,
required_attendees=[0, 1], preferred_attendees=[2]),
MeetingModel(id="M2", topic="Budget", duration_in_grains=3, required_attendees=[1, 3]),
],
meeting_assignments=[
MeetingAssignmentModel(id="A1", meeting=0),
MeetingAssignmentModel(id="A2", meeting=1),
],
)
result = ms.solve(problem, time_limit_seconds=2.0, random_seed=42)
print(result.score_hard, result.score_soft)
for a in result.meeting_assignments:
grain, room = divmod(a.grain_room, len(result.rooms))
print(result.meetings[a.meeting].topic, result.time_grains[grain].starting_minute_of_day, result.rooms[room].id)
같은 유스케이스를 그림과 실행 결과가 담긴 Jupyter 노트북으로도 볼 수 있습니다. examples/meeting-scheduling.ipynb ↗
PersonModel
id 필수 입력 | 사람 id입니다. 사람은 people 목록의 위치로 참조합니다. |
TimeGrainModel
id 필수 입력 | 시간 칸 id입니다. |
grain_index 필수 입력 | 기간과 겹침을 계산할 때 쓰는 번호입니다. |
day_of_year 필수 입력 | 이 칸이 속한 날짜입니다. |
starting_minute_of_day 필수 입력 | 자정부터 센 시작 시각(분)입니다. |
RoomModel
id 필수 입력 | 회의실 id입니다. |
capacity | 좌석 수입니다. |
MeetingModel
id 필수 입력 | 회의 id입니다. |
topic | 회의 주제입니다. |
duration_in_grains | 15분 단위 칸으로 센 길이입니다. |
required_attendees | 반드시 참석할 사람의 people 내 위치입니다. |
preferred_attendees | 참석하면 좋은 사람의 people 내 위치입니다. |
MeetingAssignmentModel
id 필수 입력 | 배정 id입니다. |
meeting 필수 입력 | 회의의 meetings 내 위치입니다. 회의와 배정은 1:1입니다. |
pinned | 이 배정의 값을 고정합니다. |
MeetingScheduleModel
people 필수 입력 | 사람 목록입니다. |
time_grains 필수 입력 | 달력입니다. |
rooms 필수 입력 | 회의실 목록입니다. |
meetings 필수 입력 | 회의 목록입니다. |
meeting_assignments | 회의마다 하나씩 있는 목록입니다. |
풀이 파라미터
풀이를 호출할 때 넘기는 값입니다. 시간 제한, 시드, 병렬 수가 여기에 있습니다.
탐색 옵션 전체 (23)
SolverConfig에 값을 넣어 config=로 넘기세요.
돌아오는 결과
solve는 모델을 받아 풀린 모델을 돌려줍니다.
MeetingAssignmentModel
grain_room | time_grain_pos * room_count + room_index로 만든 합성 값입니다. None이면 미배정입니다. |
MeetingScheduleModel
score_hard | 필수 점수입니다. 0이면 가능한 해입니다. |
score_soft | 중간과 선호를 합친 값입니다. -(중간 벌점 * 1e6 + 선호 벌점)입니다. |
제약
optimaru.meeting_scheduling.constraints에서 클래스를 가져와 OvertimeCost()처럼 호출해서 넘깁니다. 넘긴 제약만 점수에 들어가고, 아무것도 넘기지 않으면 기본 제약을 씁니다. 목록에 같은 제약을 여러 번 넣을 수 있으며, 넣은 것마다 따로 점수에 들어갑니다. 제약마다 다음 파라미터를 받습니다.
중간 제약의 기본 가중치는 중간 수준 단위로 1입니다. 탐색은 중간 값에 1e6을 곱해 선호 값과 합친 점수로 해를 비교하므로, 선호 합계가 1e6보다 작은 동안에는 중간 수준이 선호보다 우선합니다. level로 어느 제약이든 세 수준 가운데 하나로 옮길 수 있습니다. level="medium"이면 중간으로 옮기고, "hard"나 "soft"면 중간 제약을 필수나 선호로 옮깁니다. 가중치는 적용되는 수준의 단위로 셉니다.
위의 최소 실행 예제에 이어서 실행합니다.
from optimaru.meeting_scheduling.constraints import (
ConsecutiveMeetingsCost, RequiredAttendanceConflictCost, RoomCapacityCost, RoomConflictCost,
get_constraints,
)
picked = get_constraints([
RoomConflictCost(), RequiredAttendanceConflictCost(), RoomCapacityCost(),
ConsecutiveMeetingsCost(weight=500),
])
custom = ms.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 = ms.with_initial_solution(problem, result.model_dump(mode="json"))
again = ms.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에 있는 값을 유지합니다. grain_room은 time_grains와 rooms의 위치로 만든 값입니다. 그래서 해의 목록이 다르거나 순서만 달라도 ValueError가 납니다. 모르는 id와 범위를 벗어난 값도 마찬가지입니다.
예외와 제한
- 시간 칸 하나는 15분입니다.
- 회의와 배정은 1:1이어야 하며, 아니면
ValueError가 납니다. score_soft는 중간 벌점에 1e6을 곱해 선호 벌점과 합칩니다. 선호 벌점이 1e6보다 작은 동안에는 중간 벌점이 항상 우선합니다.
직접 해 보기