def test_check_stability(): """ Test that StudentAllocation can recognise whether a matching is stable or not. """ students = [Student("A"), Student("B"), Student("C")] projects = [Project("P", 2), Project("Q", 2)] supervisors = [Supervisor("X", 2), Supervisor("Y", 2)] a, b, c = students p, q = projects x, y = supervisors p.set_supervisor(x) q.set_supervisor(y) a.set_prefs([p, q]) b.set_prefs([q]) c.set_prefs([q, p]) x.set_prefs([c, a]) y.set_prefs([a, b, c]) game = StudentAllocation(students, projects, supervisors) matching = game.solve() assert game.check_stability() matching[p] = [c] matching[q] = [a, b] assert not game.check_stability()
def _make_instances( student_prefs, project_supervisors, project_capacities, supervisor_capacities, ): """Create ``Player``, ``Project`` and ``Supervisor`` instances for the names in each dictionary.""" student_dict, project_dict, supervisor_dict = {}, {}, {} for student_name in student_prefs: student = Student(name=student_name) student_dict[student_name] = student for project_name, supervisor_name in project_supervisors.items(): capacity = project_capacities[project_name] project = Project(name=project_name, capacity=capacity) project_dict[project_name] = project for supervisor_name, capacity in supervisor_capacities.items(): supervisor = Supervisor(name=supervisor_name, capacity=capacity) supervisor_dict[supervisor_name] = supervisor for project_name, supervisor_name in project_supervisors.items(): project = project_dict[project_name] supervisor = supervisor_dict[supervisor_name] project.set_supervisor(supervisor) return student_dict, project_dict, supervisor_dict
def test_set_supervisor(name, capacity): """Check that a project can update its supervisor member and that it is added to the supervisor's project list.""" project = Project(name, capacity) supervisor = Supervisor("foo", capacity) project.set_supervisor(supervisor) assert project.supervisor == supervisor assert supervisor.projects == [project]
def test_init(name, capacity): """ Make an instance of Project and check their attributes are correct. """ project = Project(name, capacity) assert project.name == name assert project.capacity == capacity assert project.supervisor is None assert project.prefs is None assert project.pref_names is None assert project.matching == []
def test_inputs_student_prefs(student_names, project_names, supervisor_names, capacities, seed): """ Test that each student's preference list is a subset of the available projects, and check that an Exception is raised if not. """ _, _, _, game = make_game(student_names, project_names, supervisor_names, capacities, seed) assert game._check_student_prefs() student = game.students[0] student.prefs = [Project("foo", None)] with pytest.raises(Exception): game._check_student_prefs()
def test_check_student_matching(student_names, project_names, supervisor_names, capacities, seed): """ Test that StudentAllocation recognises a valid matching requires a student to have a preference of their match, if they have one. """ _, _, _, game = make_game(student_names, project_names, supervisor_names, capacities, seed) game.solve() assert game._check_student_matching() student = game.students[0] student.matching = Project(name="foo", capacity=0) with pytest.raises(Exception): game._check_student_matching()
def test_unmatch(name, capacity, pref_names): """Check that a project can break a matching with a student, and break that matching for their supervisor member, too.""" project = Project(name, capacity) supervisor = Supervisor("foo", capacity) project.supervisor = supervisor students = [Student(student) for student in pref_names] project.matching = students supervisor.matching = students for i, student in enumerate(students[:-1]): project._unmatch(student) assert project.matching == students[i + 1:] assert supervisor.matching == students[i + 1:] project._unmatch(students[-1]) assert project.matching == [] assert supervisor.matching == []
def test_match(name, capacity, pref_names): """Check that a project can match to a student, and match its supervisor to them, too.""" project = Project(name, capacity) supervisor = Supervisor("foo", capacity) project.supervisor = supervisor students = [Student(student) for student in pref_names] project.prefs = students supervisor.prefs = students for i, student in enumerate(students[:-1]): project._match(student) assert project.matching == students[:i + 1] assert supervisor.matching == students[:i + 1] project._match(students[-1]) assert project.matching == students assert supervisor.matching == students
def test_set_prefs(name, capacity, pref_names): """ Test that a Supervisor can set its preferences correctly, and the preferences of its project(s). """ supervisor = Supervisor(name, capacity) projects = [Project(i, capacity) for i in range(3)] students = [] for sname in pref_names: student = Student(sname) student.prefs = projects students.append(student) supervisor.projects = projects supervisor.set_prefs(students) assert supervisor.prefs == students assert supervisor.pref_names == pref_names for project in supervisor.projects: assert project.prefs == students assert project.pref_names == pref_names
def test_check_for_unacceptable_matches_students( student_names, project_names, supervisor_names, capacities, seed, clean ): """Test that StudentAllocation recognises a valid matching requires each student to have a preference of their match, if they have one.""" _, _, _, game = make_game( student_names, project_names, supervisor_names, capacities, seed, clean ) game.solve() student = game.students[0] project = Project(name="foo", capacity=0) student.matching = project with pytest.raises(MatchingError) as e: game.check_validity() error = e.unacceptable_matches[0] assert error.startswith(student.name) assert error.endswith(str(student.prefs)) assert project.name in error
def make_players(student_names, project_names, supervisor_names, capacities): """ Given some names and capacities, make a set of players for SA. """ students = [Student(name) for name in student_names] projects = [ Project(name, cap) for name, cap in zip(project_names, capacities) ] supervisors = [Supervisor(name, capacity=None) for name in supervisor_names] if len(students) > len(projects): students = students[: len(projects)] for project in projects: project.set_supervisor(np.random.choice(supervisors)) supervisors = [ supervisor for supervisor in supervisors if supervisor.projects ] for supervisor in supervisors: capacities = sorted([proj.capacity for proj in supervisor.projects]) min_cap, max_cap = max(capacities), sum(capacities) supervisor.capacity = np.random.randint(min_cap, max_cap + 1) possible_prefs = get_possible_prefs(projects) logged_prefs = {supervisor: [] for supervisor in supervisors} for student in students: prefs = possible_prefs[np.random.choice(range(len(possible_prefs)))] student.set_prefs(prefs) for project in prefs: supervisor = project.supervisor if student not in logged_prefs[supervisor]: logged_prefs[supervisor].append(student) for supervisor, studs in logged_prefs.items(): supervisor.set_prefs(np.random.permutation(studs).tolist()) projects = [p for p in projects if p.prefs] supervisors = [f for f in supervisors if f.prefs] return students, projects, supervisors