def test_solver4(self): ''' Tests 6 papers, 6 reviewers. Reviewers review min: 2, max: 3 papers. Each paper needs 2 reviews. All scores set to 1 so that any match that does not violate constraints is optimal Purpose: Honors minimums == 2 for all reviewers ''' cost_matrix = np.array([[1, 1, 1, 1, 1, 1], [1, 1, 1, 1, 1, 1], [1, 1, 1, 1, 1, 1], [1, 1, 1, 1, 1, 1], [1, 1, 1, 1, 1, 1], [1, 1, 1, 1, 1, 1]]) constraint_matrix = np.array([[0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0]]) graph_builder = GraphBuilder.get_builder('SimpleGraphBuilder') solver = AssignmentGraph([2, 2, 2, 2, 2, 2], [3, 3, 3, 3, 3, 3], [2, 2, 2, 2, 2, 2], cost_matrix, constraint_matrix, graph_builder) res = solver.solve() assert res.shape == (6, 6) # make sure every reviewer is reviewing 2 papers nrows, ncols = res.shape for rix in range(nrows): reviewer_count_reviews = 0 for pix in range(ncols): if res[rix, pix] != 0: reviewer_count_reviews += 1 assert reviewer_count_reviews == 2 self.check_solution(solver, solver.min_cost_flow.OptimalCost())
def test_solver_respects_constraints(self): ''' Tests 5 papers, 4 reviewers. Reviewers review min: 1, max: 3 papers. Each paper needs 2 reviews. Constrained such that: Reviewer 0: available for all papers 1: cannot review papers 2,3 2: cannot review papers 2,3 3: cannot review papers 0, 1 All scores set to 1 so that any match that does not violate constraints is optimal Purpose: Honors constraints in its solution ''' cost_matrix = np.array([[1, 1, 1, 1, 1], [1, 1, 1, 1, 1], [1, 1, 1, 1, 1], [1, 1, 1, 1, 1]]) constraint_matrix = np.array([[0, 0, 0, 0, 0], [0, 0, -1, -1, 0], [0, 0, -1, -1, 0], [-1, -1, 0, 0, 0]]) graph_builder = GraphBuilder.get_builder('SimpleGraphBuilder') solver = AssignmentGraph([1, 1, 1, 1], [3, 3, 3, 3], [2, 2, 2, 2, 2], cost_matrix, constraint_matrix, graph_builder) res = solver.solve() assert res.shape == (4, 5) # make sure result does not violate constraints (i.e. no flow at i,j if there is a -1 constraint at i,j nrows, ncols = res.shape for i in range(nrows): for j in range(ncols): assert not ( constraint_matrix[i, j] == -1 and res[i, j] > 0 ), "Solution violates constraint at [{},{}]".format(i, j) self.print_header() self.check_solution(solver, solver.min_cost_flow.OptimalCost())
def test_solver_find_lowest_cost_and_respect_constraints(self): ''' Tests 5 papers, 4 reviewers. Reviewers review min: 1, max: 3 papers. Each paper needs 2 reviews. Constrained such that: Reviewer 0: available for all papers 1: cannot review papers 0,3 2: cannot review papers 3,4 3: cannot review papers 1,2 Scores set such that a lowest-cost solution can be found along all reviewer-paper arcs with cost = -10 and no others. Purpose: Finds the lowest cost solution in combination with honoring constraints (i.e. ignores lower-cost paths that are constrained to be ommitted) ''' cost_matrix = np.array([[-10, 1, 1, -10, -10], [-100, -10, -10, -100, 1], [1, -10, -10, -100, -100], [-10, -100, -100, -10, -10]]) constraint_matrix = np.array([[0, 0, 0, 0, 0], [-1, 0, 0, -1, 0], [0, 0, 0, -1, -1], [0, -1, -1, 0, 0]]) graph_builder = GraphBuilder.get_builder('SimpleGraphBuilder') solver = AssignmentGraph([1, 1, 1, 1], [3, 3, 3, 3], [2, 2, 2, 2, 2], cost_matrix, constraint_matrix, graph_builder) res = solver.solve() assert res.shape == (4, 5) # make sure result does not violate constraints (i.e. no flow at i,j if there is a -1 constraint at i,j # make sure the score at i,j = -10 if there is flow there. nrows, ncols = res.shape for i in range(nrows): for j in range(ncols): assert not ( constraint_matrix[i, j] == -1 and res[i, j] > 0 ), "Solution violates constraint at [{},{}]".format(i, j) assert not ( res[i, j] > 0 and cost_matrix[i, j] > -10 ), "Solution contains an arc that is not part of an lowest-cost solution" self.print_header() self.check_solution(solver, solver.min_cost_flow.OptimalCost())
def test_solver6(self): ''' Tests 3 papers, 4 reviewers. Reviewers review min: 2, max: 3 papers. Each paper needs 3 reviews. Reviewer 4 has very high cost. Other reviewers have 0 cost. Purpose: Make sure all reviewers get at least their minimum ''' num_papers = 3 num_reviewers = 4 min_papers_per_reviewer = 2 max_papers_per_reviewer = 3 paper_revs_reqd = 3 cost_matrix = np.array([[0, 0, 0], [0, 0, 0], [0, 0, 0], [2000, 2000, 2000]]) constraint_matrix = np.array([[0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0]]) graph_builder = GraphBuilder.get_builder('SimpleGraphBuilder') rev_mins = [min_papers_per_reviewer] * num_reviewers rev_maxs = [max_papers_per_reviewer] * num_reviewers papers_reqd = [paper_revs_reqd] * num_papers solver = AssignmentGraph(rev_mins, rev_maxs, papers_reqd, cost_matrix, constraint_matrix, graph_builder) res = solver.solve() assert res.shape == (4, 3) # make sure every reviewer has at least 1 paper nrows, ncols = res.shape for rix in range(nrows): reviewer_count_reviews = 0 for pix in range(ncols): if res[rix, pix] != 0: reviewer_count_reviews += 1 assert reviewer_count_reviews >= 1 # TestSolver.silent = False self.check_solution(solver, solver.min_cost_flow.OptimalCost())
def test_solver_respects_minimums_icml(self): ''' Based on ICML workshop which produces a match that has users either getting 4 or 6 papers with one user getting just 2. There are 44 papers, 27 reviewers. 3 users per paper and between 4 and 6 papers per user. All reviewers are 0 cost. Purpose: Make sure all reviewers get between their min and max TODO Why do all reviewers get either the min or max? None of them get a number in between! ''' num_papers = 44 num_reviewers = 27 min_papers_per_reviewer = 4 max_papers_per_reviewer = 6 paper_revs_reqd = 3 cost_matrix = np.zeros((num_reviewers, num_papers)) constraint_matrix = np.zeros((num_reviewers, num_papers)) graph_builder = GraphBuilder.get_builder('SimpleGraphBuilder') rev_mins = [min_papers_per_reviewer] * num_reviewers rev_maxs = [max_papers_per_reviewer] * num_reviewers papers_reqd = [paper_revs_reqd] * num_papers solver = AssignmentGraph(rev_mins, rev_maxs, papers_reqd, cost_matrix, constraint_matrix, graph_builder) res = solver.solve() assert res.shape == (27, 44) # make sure every reviewer has >= minimum number of papers # make sure every reviewer has <= maximum number of papers nrows, ncols = res.shape total = 0 num4 = 0 num6 = 0 for rix in range(nrows): reviewer_count_reviews = 0 for pix in range(ncols): if res[rix, pix] != 0: reviewer_count_reviews += 1 total += reviewer_count_reviews if reviewer_count_reviews == 4: num4 += 1 else: num6 += 1 print("Reviewer: {} has {} papers".format(rix, reviewer_count_reviews)) assert reviewer_count_reviews >= min_papers_per_reviewer assert reviewer_count_reviews <= max_papers_per_reviewer assert total == num_papers * paper_revs_reqd assert total == num4 * 4 + num6 * 6 print("Total reviews", total, "num 4", num4, "num 6", num6)
def test_solver_finds_lowest_cost_soln(self): ''' 4 reviewers 3 papers. Papers 0,1 need 1 review; Paper 2 needs 2 reviews. Reviewers can do max of 2 reviews Setup so that lowest cost solution should be Reviewer 0 reviews paper 0 1 reviews paper 1 2 reviews paper 2 3 reviews paper 2 Purpose: Finds the lowest cost solution ''' cost_matrix = np.array([[0, 1, 1], [1, 0, 1], [1, 1, 0], [2, 2, 0]]) constraint_matrix = np.zeros(np.shape(cost_matrix)) graph_builder = GraphBuilder.get_builder('SimpleGraphBuilder') solver = AssignmentGraph([1, 1, 1, 1], [2, 2, 2, 2], [1, 1, 2], cost_matrix, constraint_matrix, graph_builder) res = solver.solve() assert res.shape == (4, 3) self.print_header() expected_cost = 0 self.check_solution(solver, expected_cost)
def test_solver_respects_two_minimum(self): ''' Tests 3 papers, 4 reviewers. Reviewers review min: 2, max: 3 papers. Each paper needs 3 reviews. Reviewer 4 has very high cost. Other reviewers have 0 cost. Purpose: Make sure all reviewers (including reviewer 4) get at least their minimum ''' num_papers = 3 num_reviewers = 4 min_papers_per_reviewer = 2 max_papers_per_reviewer = 3 paper_revs_reqd = 3 cost_matrix = np.array([[0, 0, 0], [0, 0, 0], [0, 0, 0], [2000, 2000, 2000]]) constraint_matrix = np.array([[0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0]]) graph_builder = GraphBuilder.get_builder('SimpleGraphBuilder') rev_mins = [min_papers_per_reviewer] * num_reviewers rev_maxs = [max_papers_per_reviewer] * num_reviewers papers_reqd = [paper_revs_reqd] * num_papers solver = AssignmentGraph(rev_mins, rev_maxs, papers_reqd, cost_matrix, constraint_matrix, graph_builder) res = solver.solve() assert res.shape == (4, 3) # make sure every reviewer has at least 1 paper nrows, ncols = res.shape for rix in range(nrows): reviewer_count_reviews = 0 for pix in range(ncols): if res[rix, pix] != 0: reviewer_count_reviews += 1 assert reviewer_count_reviews >= 1 # TestSolver.silent = False print("-----") print("Reviewer min: {}, max: {}".format(min_papers_per_reviewer, max_papers_per_reviewer)) print("cost matrix") print(cost_matrix) print("solution matrix") print(res)
def test_decode(self, test_util): ''' Test that the decode returns a correct assignment dictionary. Must manually call the encoder.encode and solver. We send a score matrix that forces it to choose the expected solution reviewer-0->paper-0, 1->1, 2->2, 3->2 ''' score_matrix = np.array([[10, 0, 0], [0, 10, 0], [0, 0, 10], [0, 0, 10]]) num_papers = 3 num_reviewers = 4 num_reviews_needed_per_paper = 2 reviewer_max_papers = 2 reviewer_min_papers = 1 paper_demands = [1, 1, 2] params = Params({ Params.NUM_PAPERS: num_papers, Params.NUM_REVIEWERS: num_reviewers, Params.NUM_REVIEWS_NEEDED_PER_PAPER: num_reviews_needed_per_paper, Params.REVIEWER_MAX_PAPERS: reviewer_max_papers, Params.REVIEWER_MIN_PAPERS: reviewer_min_papers, Params.SCORES_CONFIG: { Params.SCORES_SPEC: { 'affinity': { 'weight': 1, 'default': 0 } }, Params.SCORE_TYPE: Params.MATRIX_SCORE, Params.SCORE_MATRIX: score_matrix } }) ''' Test that the decoder produces the expected assignment. Its necessary to configure the inputs to get a predictable solution. ''' or_client = test_util.client conf = ConferenceConfigWithEdges(or_client, test_util.next_conference_count(), params) papers = conf.get_paper_notes() reviewers = conf.reviewers config = conf.get_config_note() scores_spec = config.content[Configuration.SCORES_SPECIFICATION] edge_invitations = PaperReviewerEdgeInvitationIds( conf.score_invitation_ids, conf.conf_ids.CONFLICTS_INV_ID, conf.conf_ids.CUSTOM_LOAD_INV_ID) prd = PaperReviewerData(conf.paper_notes, conf.reviewers, edge_invitations, scores_spec, edge_fetcher=EdgeFetcher(or_client)) enc = Encoder(prd) cost_matrix = enc.cost_matrix constraint_matrix = np.zeros(np.shape(cost_matrix)) graph_builder = GraphBuilder.get_builder('SimpleGraphBuilder') solver = AssignmentGraph([reviewer_min_papers] * num_reviewers, [reviewer_max_papers] * num_reviewers, paper_demands, cost_matrix, constraint_matrix, graph_builder) solution = solver.solve() assignments_by_forum = enc.decode(solution) assert assignments_by_forum[papers[0].id][0].user == reviewers[0] assert assignments_by_forum[papers[1].id][0].user == reviewers[1] assert assignments_by_forum[papers[2].id][0].user == reviewers[2] assert assignments_by_forum[papers[2].id][1].user == reviewers[3]