Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Legg til clustering #287

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 31 additions & 5 deletions algorithm/src/mip_matching/match_meetings.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,22 @@
from mip_matching.Applicant import Applicant
import mip

from datetime import timedelta
from datetime import timedelta, time
from itertools import combinations

from mip_matching.utils import subtract_time


# Hvor stort buffer man ønsker å ha mellom intervjuene
APPLICANT_BUFFER_LENGTH = timedelta(minutes=15)

# Et mål på hvor viktig det er at intervjuer er i nærheten av hverandre
CLUSTERING_WEIGHT = 0.001

# Når på dagen man helst vil ha intervjuene rundt
CLUSTERING_TIME_BASELINE = time(12, 00)
MAX_SCALE_CLUSTERING_TIME = timedelta(seconds=43200) # TODO: Rename variable


class MeetingMatch(TypedDict):
"""Type definition of a meeting match object"""
Expand All @@ -25,7 +34,7 @@ def match_meetings(applicants: set[Applicant], committees: set[Committee]) -> Me
"""Matches meetings and returns a MeetingMatch-object"""
model = mip.Model(sense=mip.MAXIMIZE)

m = {}
m: dict[tuple[Applicant, Committee, TimeInterval], mip.Var] = {}

# Lager alle maksimeringsvariabler
for applicant in applicants:
Expand Down Expand Up @@ -60,10 +69,27 @@ def match_meetings(applicants: set[Applicant], committees: set[Committee]) -> Me
for interview_a, interview_b in combinations(potential_interviews, r=2):
if interview_a[1].intersects(interview_b[1]) or interview_a[1].is_within_distance(interview_b[1], APPLICANT_BUFFER_LENGTH):
model += m[(applicant, *interview_a)] + \
m[(applicant, *interview_b)] <= 1
m[(applicant, *interview_b)] <= 1 # type: ignore

# Setter mål til å være maksimering av antall møter
model.objective = mip.maximize(mip.xsum(m.values()))
# Legger til sekundærmål om at man ønsker å sentrere intervjuer rundt CLUSTERING_TIME_BASELINE
clustering_objectives = []

for name, variable in m.items():
applicant, committee, interval = name
if interval.start.time() < CLUSTERING_TIME_BASELINE:
relative_distance_from_baseline = subtract_time(CLUSTERING_TIME_BASELINE,
interval.end.time()) / MAX_SCALE_CLUSTERING_TIME
else:
relative_distance_from_baseline = subtract_time(interval.start.time(),
CLUSTERING_TIME_BASELINE) / MAX_SCALE_CLUSTERING_TIME

clustering_objectives.append(
CLUSTERING_WEIGHT * relative_distance_from_baseline * variable) # type: ignore

# Setter mål til å være maksimering av antall møter
# med sekundærmål om å samle intervjuene rundt CLUSTERING_TIME_BASELINE
model.objective = mip.maximize(
mip.xsum(m.values()) + mip.xsum(clustering_objectives))

# Kjør optimeringen
solver_status = model.optimize()
Expand Down
41 changes: 41 additions & 0 deletions algorithm/src/mip_matching/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
from mip_matching.Applicant import Applicant
from mip_matching.Committee import Committee
from mip_matching.TimeInterval import TimeInterval

from datetime import time, date, datetime, timedelta


def group_by_committee(meetings: list[tuple[Applicant, Committee, TimeInterval]]) -> dict[Committee, list[tuple[Applicant, Committee, TimeInterval]]]:
result = {}

for applicant, committee, interval in meetings:
if committee not in result:
result[committee] = []

result[committee].append((applicant, committee, interval))

return result


def measure_clustering(meetings: list[tuple[Applicant, Committee, TimeInterval]]) -> int:
grouped_meetings = group_by_committee(meetings)

holes = 0

for _, committee_meetings in grouped_meetings.items():
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hva vil _, si?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Det er bare konvensjon for navnet til throwaway-variabler som ikke skal bruker til noe. Men her refererer det vel til en komité, da.

committee_meetings.sort(key=lambda meeting: meeting[2].end)

previous_interval: TimeInterval = committee_meetings[0][2]
for _, _, interval in committee_meetings[1:]:
if not previous_interval.is_within_distance(interval, timedelta(minutes=1)):
holes += 1
previous_interval = interval

return holes


def subtract_time(minuend: time, subtrahend: time) -> timedelta:
minuend_date = datetime.combine(date.min, minuend)
subtrahend_date = datetime.combine(date.min, subtrahend)

return minuend_date - subtrahend_date