def test2_3papers_4reviewers_1custom_load(self, test_util):
        '''
        Reviewer 0 has a custom_load of 1 and cannot be the second reviewer of paper 1
        :param test_util:
        :return:
        '''
        score_matrix = np.array([[10.67801, 3, 0], [0, 10.67801, 0],
                                 [0, 0, 10.67801], [0, 0, 10.67801]])
        num_papers = 3
        num_reviewers = 4
        num_reviews_per_paper = 2
        reviewer_max_papers = 2
        params = Params({
            Params.NUM_PAPERS: num_papers,
            Params.NUM_REVIEWERS: num_reviewers,
            Params.NUM_REVIEWS_NEEDED_PER_PAPER: num_reviews_per_paper,
            Params.REVIEWER_MAX_PAPERS: reviewer_max_papers,
            Params.CUSTOM_LOAD_CONFIG: {
                Params.CUSTOM_LOAD_MAP: {
                    0: 1,
                    1: 2
                }
            },
            Params.SCORES_CONFIG: {
                Params.SCORES_SPEC: {
                    'affinity': {
                        'weight': 1,
                        'default': 0
                    }
                },
                Params.SCORE_TYPE: Params.MATRIX_SCORE,
                Params.SCORE_MATRIX: score_matrix
            }
        })

        test_util.set_test_params(params)
        test_util.build_conference()
        match = Match(test_util.client,
                      test_util.get_conference().get_config_note())
        match.compute_match()
        conference = test_util.get_conference()
        assert conference.get_config_note_status() == Configuration.STATUS_COMPLETE, \
            "Failure: Config status is {} expected {}".format(conference.get_config_note_status(), Configuration.STATUS_COMPLETE)
        assignment_edges = conference.get_assignment_edges()
        assert len(assignment_edges) == num_reviews_per_paper * len(conference.get_paper_notes()), "Number of assignment edges {} is incorrect.  Should be". \
            format(len(assignment_edges), num_reviews_per_paper * len(conference.get_paper_notes()))

        aggregate_score_edges = conference.get_aggregate_score_edges()
        assert len(aggregate_score_edges) == num_reviewers * num_papers
        paper_ids = conference.get_paper_note_ids()
        reviewers = conference.reviewers
        c = AssignmentChecker(conference)
        assert c.is_paper_assigned_to_reviewer(paper_ids[0], reviewers[0])
        assert c.is_paper_assigned_to_reviewer(paper_ids[0], reviewers[1])
        assert c.is_paper_assigned_to_reviewer(paper_ids[1], reviewers[1])
        assert c.is_paper_assigned_to_reviewer(paper_ids[1], reviewers[2])
        assert c.is_paper_assigned_to_reviewer(paper_ids[2], reviewers[2])
        assert c.is_paper_assigned_to_reviewer(paper_ids[2], reviewers[3])
Пример #2
0
    def test3_3papers_4reviewers_1conflict (self, test_util):
        '''
        Paper 0 conflicts with Reviewer 0 so this cannot be in the solution.
        :param test_util:
        :return:
        '''
        score_matrix = np.array([
            [10.67801, 0, 0],
            [0, 10.67801, 0],
            [0, 0, 10.67801],
            [0, 0, 10.67801]
        ])
        num_papers = 3
        num_reviewers = 4
        num_reviews_per_paper = 2
        reviewer_max_papers = 2
        params = Params({Params.NUM_PAPERS: num_papers,
                         Params.NUM_REVIEWERS: num_reviewers,
                         Params.NUM_REVIEWS_NEEDED_PER_PAPER: num_reviews_per_paper,
                         Params.REVIEWER_MAX_PAPERS: reviewer_max_papers,
                         Params.CONFLICTS_CONFIG: {0: [0]},
                         Params.SCORES_CONFIG: {Params.SCORES_SPEC: {'affinity': {'weight': 1, 'default': 0}},
                                                Params.SCORE_TYPE: Params.MATRIX_SCORE,
                                                Params.SCORE_MATRIX: score_matrix
                                                }
                         })

        test_util.set_test_params(params)
        test_util.build_conference()
        match = Match(test_util.client, test_util.get_conference().get_config_note())
        match.compute_match()
        conference = test_util.get_conference()
        assert conference.get_config_note_status() == Configuration.STATUS_COMPLETE, \
            "Failure: Config status is {} expected {}".format(conference.get_config_note_status(), Configuration.STATUS_COMPLETE)
        assignment_edges = conference.get_assignment_edges()
        assert len(assignment_edges) == num_reviews_per_paper * len(conference.get_paper_notes()), "Number of assignment edges {} is incorrect.  Should be". \
            format(len(assignment_edges), num_reviews_per_paper * len(conference.get_paper_notes()))

        aggregate_score_edges = conference.get_aggregate_score_edges()
        assert len(aggregate_score_edges) == num_reviewers * num_papers

        reviewers = conference.reviewers
        papers = conference.get_paper_notes()
        paper_reviewer_data = match.paper_reviewer_data
        self.check_aggregate_score_edges(test_util.client,reviewers,papers,conference,paper_reviewer_data)
        # Validate that the assignment edges are correct
        # reviewer-1 -> paper-1
        assert conference.get_assignment_edge(papers[1].id, reviewers[1]) != None
        # 2 -> 2
        assert conference.get_assignment_edge(papers[2].id, reviewers[2]) != None
        # 3 -> 2
        assert conference.get_assignment_edge(papers[2].id, reviewers[3]) != None

        # !reviewer-0 -> paper-0
        try:
            conference.get_assignment_edge(papers[0].id, reviewers[0])
        except NotFoundError:
            assert True
Пример #3
0
    def test_aggregate_score_with_missing_score (self, test_util):
        '''
        Is the same as test4 above but it deletes all the edges created from the 0's in the score_matrix which sets up the test case.
        This will test the matcher's ability to use a default when no score is present in the configuration.
        :param test_util:
        :return:
        '''
        score_matrix = np.array([
            [10.67801, 0, 0],
            [0, 10.67801, 0],
            [0, 0, 10.67801],
            [0, 0, 10.67801]
        ])
        num_papers = 3
        num_reviewers = 4
        num_reviews_per_paper = 2
        reviewer_max_papers = 2
        params = Params({Params.NUM_PAPERS: num_papers,
                         Params.NUM_REVIEWERS: num_reviewers,
                         Params.NUM_REVIEWS_NEEDED_PER_PAPER: num_reviews_per_paper,
                         Params.REVIEWER_MAX_PAPERS: reviewer_max_papers,
                         Params.SCORES_CONFIG: {Params.SCORES_SPEC: {'affinity': {'weight': 1, 'default': 0}},
                                                Params.SCORE_TYPE: Params.MATRIX_SCORE,
                                                Params.OMIT_ZERO_SCORE_EDGES: True,
                                                Params.SCORE_MATRIX: score_matrix
                                                }
                         })

        test_util.set_test_params(params)
        test_util.build_conference()
        match = Match(test_util.client, test_util.get_conference().get_config_note())
        match.compute_match()
        conference = test_util.get_conference()
        assert conference.get_config_note_status() == Configuration.STATUS_COMPLETE, \
            "Failure: Config status is {} expected {}".format(conference.get_config_note_status(), Configuration.STATUS_COMPLETE)
        assignment_edges = conference.get_assignment_edges()
        assert len(assignment_edges) == num_reviews_per_paper * len(conference.get_paper_notes()), "Number of assignment edges {} is incorrect.  Should be". \
            format(len(assignment_edges), num_reviews_per_paper * len(conference.get_paper_notes()))

        aggregate_score_edges = conference.get_aggregate_score_edges()
        assert len(aggregate_score_edges) == num_reviewers * num_papers

        reviewers = conference.reviewers
        papers = conference.get_paper_notes()
        paper_reviewer_data = match.paper_reviewer_data
        self.check_aggregate_score_edges(test_util.client,reviewers,papers,conference,paper_reviewer_data)
        # Validate that the assignment edges are correct
        # reviewer-1 -> paper-1
        assert conference.get_assignment_edge(papers[1].id, reviewers[1]) != None
        # 2 -> 2
        assert conference.get_assignment_edge(papers[2].id, reviewers[2]) != None
        # 3 -> 2
        assert conference.get_assignment_edge(papers[2].id, reviewers[3]) != None
        # !reviewer-0 -> paper-0
        assert conference.get_assignment_edge(papers[0].id, reviewers[0]) != None
    def test3_5papers_5reviewers_custom_loads(self, test_util):
        '''
        Sanity check to make sure the correct solution is found
        :param test_util:
        :return:
        '''
        score_matrix = np.array([[10.67801, 0, 0, 0,
                                  0], [0, 0, 10.67801, 0, 0],
                                 [0, 0, 0, 0,
                                  10.67801], [0, 0, 0, 10.67801, 0],
                                 [0, 10.67801, 0, 0, 0]])
        num_papers = 5
        num_reviewers = 5
        num_reviews_per_paper = 1
        reviewer_max_papers = 2
        params = Params({
            Params.NUM_PAPERS: num_papers,
            Params.NUM_REVIEWERS: num_reviewers,
            Params.NUM_REVIEWS_NEEDED_PER_PAPER: num_reviews_per_paper,
            Params.REVIEWER_MAX_PAPERS: reviewer_max_papers,
            Params.SCORES_CONFIG: {
                Params.SCORES_SPEC: {
                    'affinity': {
                        'weight': 1,
                        'default': 0
                    }
                },
                Params.SCORE_TYPE: Params.MATRIX_SCORE,
                Params.SCORE_MATRIX: score_matrix
            }
        })

        test_util.set_test_params(params)
        test_util.build_conference()
        match = Match(test_util.client,
                      test_util.get_conference().get_config_note())
        match.compute_match()
        conference = test_util.get_conference()
        assert conference.get_config_note_status() == Configuration.STATUS_COMPLETE, \
            "Failure: Config status is {} expected {}".format(conference.get_config_note_status(), Configuration.STATUS_COMPLETE)
        assignment_edges = conference.get_assignment_edges()
        assert len(assignment_edges) == num_reviews_per_paper * len(conference.get_paper_notes()), "Number of assignment edges {} is incorrect.  Should be". \
            format(len(assignment_edges), num_reviews_per_paper * len(conference.get_paper_notes()))
        paper_ids = conference.get_paper_note_ids()
        reviewers = conference.reviewers
        c = AssignmentChecker(conference)
        assert c.is_paper_assigned_to_reviewer(paper_ids[0], reviewers[0])
        assert c.is_paper_assigned_to_reviewer(paper_ids[1], reviewers[4])
        assert c.is_paper_assigned_to_reviewer(paper_ids[2], reviewers[1])
        assert c.is_paper_assigned_to_reviewer(paper_ids[3], reviewers[3])
        assert c.is_paper_assigned_to_reviewer(paper_ids[4], reviewers[2])
Пример #5
0
    def test_aggregate_with_missing_scores (self, test_util):
        '''
        Three scores are used but no user will provide them.
        This will test the matcher's ability to use a default when no score is present in the configuration.
        :param test_util:
        :return:
        '''
        num_papers = 3
        num_reviewers = 4
        num_reviews_per_paper = 2
        reviewer_max_papers = 2
        params = Params({Params.NUM_PAPERS: num_papers,
                         Params.NUM_REVIEWERS: num_reviewers,
                         Params.NUM_REVIEWS_NEEDED_PER_PAPER: num_reviews_per_paper,
                         Params.REVIEWER_MAX_PAPERS: reviewer_max_papers,
                         Params.SCORES_CONFIG: {
                                                Params.SCORES_SPEC: {
                                                    'affinity': {'weight': 1, 'default': 0},
                                                    'recommendation': {'weight': 1, 'default': 0},
                                                    'bid': {'weight': 1, 'default': 0.3, 'translate_fn': self.bid_translate_fn}
                                                },
                                                Params.SCORE_TYPE: Params.FIXED_SCORE,
                                                Params.OMIT_ZERO_SCORE_EDGES: True,
                                                Params.FIXED_SCORE_VALUE: {'affinity': 0, 'recommendation': 0, 'bid': 'low'}
                                                }
                         })

        test_util.set_test_params(params)
        test_util.build_conference()
        match = Match(test_util.client, test_util.get_conference().get_config_note())
        match.compute_match()
        conference = test_util.get_conference()
        assert conference.get_config_note_status() == Configuration.STATUS_COMPLETE, \
            "Failure: Config status is {} expected {}".format(conference.get_config_note_status(), Configuration.STATUS_COMPLETE)
        assignment_edges = conference.get_assignment_edges()
        assert len(assignment_edges) == num_reviews_per_paper * len(conference.get_paper_notes()), "Number of assignment edges {} is incorrect.  Should be". \
            format(len(assignment_edges), num_reviews_per_paper * len(conference.get_paper_notes()))

        aggregate_score_edges = conference.get_aggregate_score_edges()
        assert len(aggregate_score_edges) == num_reviewers * num_papers

        reviewers = conference.reviewers
        papers = conference.get_paper_notes()
        paper_reviewer_data = match.paper_reviewer_data
        self.check_aggregate_score_edges(test_util.client,reviewers,papers,conference,paper_reviewer_data)
Пример #6
0
    def test1_10papers_7reviewers (self, test_util):
        '''
        Tests 10 papers each requiring 2 reviews.  7 users each capable of giving 3 reviews.
        Validates that the output aggregate score edges are correct with respect to the paper-reviewer scores input
        '''
        num_papers = 10
        num_reviewers = 7
        num_reviews_per_paper = 2
        params = Params({Params.NUM_PAPERS: num_papers,
                         Params.NUM_REVIEWERS: num_reviewers,
                         Params.NUM_REVIEWS_NEEDED_PER_PAPER: num_reviews_per_paper,
                         Params.REVIEWER_MAX_PAPERS: 3,
                         Params.SCORES_CONFIG: { Params.SCORE_TYPE: Params.INCREMENTAL_SCORE,
                                                 Params.SCORE_INCREMENT: 0.01,
                                                 Params.SCORES_SPEC: {'affinity': {'weight': 1, 'default': 0}}}
                         })
        test_util.set_test_params(params)
        test_util.build_conference()
        match = Match(test_util.client, test_util.get_conference().get_config_note())
        match.compute_match()
        conference = test_util.get_conference()
        assert conference.get_config_note_status() == Configuration.STATUS_COMPLETE, \
            "Failure: Config status is {} expected {}".format(conference.get_config_note_status(), Configuration.STATUS_COMPLETE)
        assignment_edges = conference.get_assignment_edges()
        assert len(assignment_edges) == num_reviews_per_paper * len(conference.get_paper_notes()), "Number of assignment edges {} is incorrect.  Should be". \
            format(len(assignment_edges), num_reviews_per_paper * len(conference.get_paper_notes()))

        aggregate_score_edges = conference.get_aggregate_score_edges()
        assert len(aggregate_score_edges) == num_reviewers * num_papers
        # Verify for every paper P and reviewer R that there is an aggregate score edge with a weight set to
        # the matcher's cost_func applied to the score edges for P and R * the weights.
        # Its not safe to just compare the edges to the cost_matrix because that's what they were built from.  Going back to the
        # score edges will be closer to the source of the data that forms the cost.
        reviewers = conference.reviewers
        papers = conference.get_paper_notes()
        # enc = Encoder(config=test_util.get_conference().get_config_note().content)
        paper_reviewer_data = match.paper_reviewer_data
        self.check_aggregate_score_edges(test_util.client, reviewers, papers, conference, paper_reviewer_data)
    def test4_5papers_5reviewers_custom_loads(self, test_util):
        '''
        Reviewer 0 can only do 1 paper, but reviewer 1 can do 3, so result should make use of these
        :param test_util:
        :return:
        '''
        score_matrix = np.array([[10.67801, 0, 0, 0,
                                  0], [0, 0, 10.67801, 0, 0],
                                 [0, 0, 0, 0,
                                  10.67801], [0, 0, 0, 10.67801, 0],
                                 [0, 10.67801, 0, 0, 0]])
        num_papers = 5
        num_reviewers = 5
        num_reviews_per_paper = 2
        reviewer_max_papers = 2
        params = Params({
            Params.NUM_PAPERS: num_papers,
            Params.NUM_REVIEWERS: num_reviewers,
            Params.NUM_REVIEWS_NEEDED_PER_PAPER: num_reviews_per_paper,
            Params.REVIEWER_MAX_PAPERS: reviewer_max_papers,
            Params.CUSTOM_LOAD_CONFIG: {
                Params.CUSTOM_LOAD_MAP: {
                    0: 1,
                    1: 3
                }
            },
            Params.SCORES_CONFIG: {
                Params.SCORES_SPEC: {
                    'affinity': {
                        'weight': 1,
                        'default': 0
                    }
                },
                Params.SCORE_TYPE: Params.MATRIX_SCORE,
                Params.SCORE_MATRIX: score_matrix
            }
        })

        test_util.set_test_params(params)
        test_util.build_conference()
        match = Match(test_util.client,
                      test_util.get_conference().get_config_note())
        match.compute_match()
        conference = test_util.get_conference()
        assert conference.get_config_note_status() == Configuration.STATUS_COMPLETE, \
            "Failure: Config status is {} expected {}".format(conference.get_config_note_status(), Configuration.STATUS_COMPLETE)
        assignment_edges = conference.get_assignment_edges()
        assert len(assignment_edges) == num_reviews_per_paper * len(conference.get_paper_notes()), "Number of assignment edges {} is incorrect.  Should be". \
            format(len(assignment_edges), num_reviews_per_paper * len(conference.get_paper_notes()))

        aggregate_score_edges = conference.get_aggregate_score_edges()
        assert len(aggregate_score_edges) == num_reviewers * num_papers

        reviewers = conference.reviewers

        assert len(conference.get_assignment_edges_by_reviewer(
            reviewers[0])) == 1
        assert len(conference.get_assignment_edges_by_reviewer(
            reviewers[1])) == 3
        assert len(conference.get_assignment_edges_by_reviewer(
            reviewers[2])) == 2
        assert len(conference.get_assignment_edges_by_reviewer(
            reviewers[3])) == 2
        assert len(conference.get_assignment_edges_by_reviewer(
            reviewers[4])) == 2
Пример #8
0
def match():
    app.logger.debug("POST /match")
    res = {}
    matcher = None
    try:
        token = request.headers.get('Authorization')
        if not token:
            raise NoTokenException('No Authorization token in headers')
        # N.B. If the token is invalid, it succeeds using a guest
        client = get_client(token=token)
        params = request.json
        config_note_id = params['configNoteId']
        # TODO move status setting out of Match and into this ConfigStatus class
        # config_status = ConfigStatus(client,config_note_id)
        app.logger.debug("Request to assign reviewers for configId: " +
                         config_note_id)
        # If the client was constructed with a bad token, the failure happens here
        config_note = client.get_note(config_note_id)
        # If the configuration is already running a matching task, do not allow another until the
        # running task is complete
        if config_note.content[
                Configuration.STATUS] == Configuration.STATUS_RUNNING:
            raise AlreadyRunningException(
                'There is already a running matching task for config ' +
                config_note_id)
        elif config_note.content[
                Configuration.STATUS] == Configuration.STATUS_COMPLETE:
            raise AlreadyCompleteException(
                'Cannot run matcher on a completed configuration ' +
                config_note_id)

        matcher = Match(client, config_note, app.logger)
        # runs the match task in a separate thread
        matcher.run()
    except openreview.OpenReviewException as e:
        app.logger.error('OpenReview-py error:', exc_info=True)
        # this exception type has args which is a tuple containing a list containing a dict where the type key indicates what went wrong
        err_type = e.args[0][0]['type']
        status = 500
        if err_type.lower() == 'not found':
            status = 404
        elif err_type.lower() == 'forbidden':
            status = 403
        else:
            err_type = str(e)
        res['error'] = err_type
        if matcher:
            matcher.set_status(Configuration.STATUS_ERROR, "Error: " + str(e))
        return jsonify(res), status
    except (NoTokenException, BadTokenException, AlreadyRunningException) as e:
        app.logger.error('OpenReview-matcher error:', exc_info=True)
        res['error'] = str(e)
        if matcher:
            matcher.set_status(Configuration.STATUS_ERROR, "Error: " + str(e))
        return jsonify(res), 400

    except Exception as e:
        app.logger.error('OpenReview-matcher error:', exc_info=True)
        res['error'] = str(e)
        # TODO Shouldn't need to rely on existence of a matcher in order to set config status- Use ConfigStatus object instead.
        if matcher:
            matcher.set_status(Configuration.STATUS_ERROR, "Error: " + str(e))
        return jsonify(res), 500
    else:
        app.logger.debug("POST returns " + str(res))
        return jsonify(res)