diff --git a/algorithm/src/mip_matching/match_meetings.py b/algorithm/src/mip_matching/match_meetings.py index e8fce2e7..ab5b8372 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 combinations + + class MeetingMatch(TypedDict): """Type definition of a meeting match object""" solver_status: mip.OptimizationStatus @@ -41,19 +44,17 @@ 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_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 combinations(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 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"""