From 56a93c4eb80a0ccfaf3bb380b471100d8c87821f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20Galdal?= Date: Wed, 14 Aug 2024 19:09:24 +0000 Subject: [PATCH 1/4] test: check if no interviews overlap for same applicant --- algorithm/tests/mip_test.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/algorithm/tests/mip_test.py b/algorithm/tests/mip_test.py index a8b3a9f8..ec173ea2 100644 --- a/algorithm/tests/mip_test.py +++ b/algorithm/tests/mip_test.py @@ -1,6 +1,5 @@ from __future__ import annotations from datetime import datetime, timedelta, date, time -# import ..algorithm.mip_matching.core.Applicant.py as applicant from mip_matching.TimeInterval import TimeInterval from mip_matching.Committee import Committee @@ -12,6 +11,7 @@ import unittest import random +from itertools import combinations def print_matchings(committees: list[Committee], @@ -59,6 +59,20 @@ def check_constraints(self, matchings: list[tuple[Applicant, Committee, TimeInte self.assertGreaterEqual(committee.get_capacity(interval), load, f"Constraint \"Number of interviews per slot per committee cannot exceed capacity\" failed for Committee {committee} and interval {interval}") + # Overlapping interviews per applicant + interviews_per_applicant: dict[Applicant, + set[tuple[Committee, TimeInterval]]] = {} + for applicant, committee, interval in matchings: + if applicant not in interviews_per_applicant: + interviews_per_applicant[applicant] = set() + + interviews_per_applicant[applicant].add((committee, interval)) + + for applicant, interviews in interviews_per_applicant.items(): + for interview_a, interview_b in combinations(interviews, r=2): + self.assertFalse(interview_a[1].intersects(interview_b[1]), f"Constraint \"Applicant cannot have time-overlapping interviews\" failed for { + applicant}'s interviews with {interview_a[0]} ({interview_a[1]}) and {interview_b[0]} ({interview_b[1]})") + def test_fixed_small(self): """Small, fixed test with all capacities set to one""" From 18bf0b3a32f4e2084bff29ed83f2ed130cad24a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20Galdal?= Date: Wed, 14 Aug 2024 19:49:45 +0000 Subject: [PATCH 2/4] feat: don't allow overlapping interviews for same applicant --- algorithm/src/mip_matching/match_meetings.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/algorithm/src/mip_matching/match_meetings.py b/algorithm/src/mip_matching/match_meetings.py index e8fce2e7..fe2c0551 100644 --- a/algorithm/src/mip_matching/match_meetings.py +++ b/algorithm/src/mip_matching/match_meetings.py @@ -5,6 +5,9 @@ from mip_matching.Applicant import Applicant import mip +from itertools import permutations + + class MeetingMatch(TypedDict): """Type definition of a meeting match object""" solver_status: mip.OptimizationStatus @@ -43,17 +46,15 @@ def match_meetings(applicants: set[Applicant], committees: set[Committee]) -> Me # Legger inn begrensninger for at en person kun kan ha ett intervju på hvert tidspunkt for applicant in applicants: - potential_intervals = set() + potential_interviews: set[tuple[Committee, TimeInterval]] = set() for applicant_candidate, committee, interval in m: if applicant == applicant_candidate: - potential_intervals.add(interval) - - for interval in potential_intervals: + potential_interviews.add((committee, interval)) - model += mip.xsum(m[(applicant, committee, interval)] - for committee in applicant.get_committees() - # type: ignore - if (applicant, committee, interval) in m) <= 1 + for interview_a, interview_b in permutations(potential_interviews, r=2): + if interview_a[1].intersects(interview_b[1]): + model += m[(applicant, *interview_a)] + \ + m[(applicant, *interview_b)] <= 1 # Setter mål til å være maksimering av antall møter model.objective = mip.maximize(mip.xsum(m.values())) @@ -80,4 +81,4 @@ def match_meetings(applicants: set[Applicant], committees: set[Committee]) -> Me "matchings": matchings, } - return match_object \ No newline at end of file + return match_object From c0de4f9873cb1fac6a5e7c5d69e5da948198e2d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20Galdal?= Date: Wed, 14 Aug 2024 19:50:51 +0000 Subject: [PATCH 3/4] docs: comments --- algorithm/src/mip_matching/match_meetings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/algorithm/src/mip_matching/match_meetings.py b/algorithm/src/mip_matching/match_meetings.py index fe2c0551..af8fc3b7 100644 --- a/algorithm/src/mip_matching/match_meetings.py +++ b/algorithm/src/mip_matching/match_meetings.py @@ -44,7 +44,7 @@ def match_meetings(applicants: set[Applicant], committees: set[Committee]) -> Me # type: ignore for interval in applicant.get_fitting_committee_slots(committee)) <= 1 - # Legger inn begrensninger for at en person kun kan ha ett intervju på hvert tidspunkt + # Legger inn begrensninger for at en søker ikke kan ha overlappende intervjutider for applicant in applicants: potential_interviews: set[tuple[Committee, TimeInterval]] = set() for applicant_candidate, committee, interval in m: From 8f8d5682986695c6338c326f602b3587ef7dc745 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20Galdal?= Date: Wed, 14 Aug 2024 19:53:58 +0000 Subject: [PATCH 4/4] refactor: small optimization --- algorithm/src/mip_matching/match_meetings.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/algorithm/src/mip_matching/match_meetings.py b/algorithm/src/mip_matching/match_meetings.py index af8fc3b7..ab5b8372 100644 --- a/algorithm/src/mip_matching/match_meetings.py +++ b/algorithm/src/mip_matching/match_meetings.py @@ -5,7 +5,7 @@ from mip_matching.Applicant import Applicant import mip -from itertools import permutations +from itertools import combinations class MeetingMatch(TypedDict): @@ -51,7 +51,7 @@ def match_meetings(applicants: set[Applicant], committees: set[Committee]) -> Me if applicant == applicant_candidate: potential_interviews.add((committee, interval)) - for interview_a, interview_b in permutations(potential_interviews, r=2): + for interview_a, interview_b in combinations(potential_interviews, r=2): if interview_a[1].intersects(interview_b[1]): model += m[(applicant, *interview_a)] + \ m[(applicant, *interview_b)] <= 1