generated from guenp/python-project-template
-
Notifications
You must be signed in to change notification settings - Fork 1
/
assign_reviews.py
160 lines (123 loc) · 6.24 KB
/
assign_reviews.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
# %%
####################
## ASSIGN REVIEWS ##
####################
# Imports
import json
from pathlib import Path
import numpy as np
from scipy.optimize import Bounds, LinearConstraint, milp
DEBUG = True
def create_objective_fun(df_reviewers, df_submissions, tutorial_coeff):
reviewers = df_reviewers.to_dict("records")
submissions = df_submissions.to_dict("records")
n_reviewers = len(reviewers)
n_submissions = len(submissions)
# Maximize the total number of reviews
objective_fun = -np.ones((n_reviewers, n_submissions))
# Make tutorials more expensive to review
for n, reviewer in enumerate(reviewers):
objective_fun[n][df_submissions.track == "TUT"] *= tutorial_coeff
objective_fun = objective_fun.flatten()
return objective_fun
def create_lb_ub(reviewers, submissions, assign_tutorials_to_anyone):
n_reviewers = len(reviewers)
n_submissions = len(submissions)
# both zero if reviewer cannot review submission, both one if reviewer is assigned to submission
lb = np.zeros((n_reviewers, n_submissions)) # lower bound
ub = np.zeros((n_reviewers, n_submissions)) # upper bound
# Establish lower and upper bounds
# reviewer cannot be assigned out of domain
# reviewer must be re-assigned previous assignments
for i, reviewer in enumerate(reviewers):
for j, submission in enumerate(submissions):
# each variable is assignment of a submission j to a reviewer i
# everyone can be assigned a tutorial because we're short on tutorial reviewers
in_domain = submission["track"] in reviewer["tracks"] or (
assign_tutorials_to_anyone and submission["track"] == "TUT"
)
no_conflict = submission["submission_id"] not in reviewer["conflicts_submission_ids"]
ub[i, j] = in_domain and no_conflict
already_assigned = submission["submission_id"] in reviewer["assigned_submission_ids"]
lb[i, j] = already_assigned
return lb, ub
def create_constraints(reviewers, submissions, min_reviews, max_reviews, min_reviewers, max_reviewers):
n_reviewers = len(reviewers)
n_submissions = len(submissions)
submission_constraints = np.zeros(
(n_submissions, n_reviewers, n_submissions)
) # constraints on reviews per submission
reviewer_constraints = np.zeros(
(n_reviewers, n_reviewers, n_submissions)
) # constraints on submissions per reviewer. 1 in all the places where reviewer could be assigned
# Constraints
# each reviewer assigned < max reviews
for i, reviewer in enumerate(reviewers):
reviewer_constraints[i, i, :] = 1
reviewer_constraints = reviewer_constraints.reshape(n_reviewers, -1)
# each paper assigned to 4 reviewers
for j, _ in enumerate(submissions):
submission_constraints[j, :, j] = 1
submission_constraints = submission_constraints.reshape(n_submissions, -1)
submission_per_reviewer_constraint = LinearConstraint(reviewer_constraints, min_reviews, max_reviews)
reviews_per_paper_constraint = LinearConstraint(submission_constraints, min_reviewers, max_reviewers)
constraints = [submission_per_reviewer_constraint, reviews_per_paper_constraint]
return constraints
def solve_milp(
df_reviewers,
df_submissions,
min_reviews,
max_reviews,
min_reviewers,
max_reviewers,
tutorial_coeff,
assign_tutorials_to_anyone,
):
reviewers = df_reviewers.to_dict("records")
submissions = df_submissions.to_dict("records")
objective_fun = create_objective_fun(df_reviewers, df_submissions, tutorial_coeff)
lb, ub = create_lb_ub(reviewers, submissions, assign_tutorials_to_anyone)
bounds = Bounds(lb.ravel(), ub.ravel())
constraints = create_constraints(reviewers, submissions, min_reviews, max_reviews, min_reviewers, max_reviewers)
# Run MILP
res = milp(objective_fun, integrality=True, bounds=bounds, constraints=constraints)
print(res)
n_reviewers = len(reviewers)
n_submissions = len(submissions)
# %%
if res.success:
x = np.round(res.x).astype(bool)
solution = x.reshape(n_reviewers, n_submissions)
return solution
# %%
############################
## FORMAT AND OUTPUT DATA ##
############################
def format_and_output_result(df_reviewers, df_submissions, solution, post_fix="", output_dir=Path.cwd() / "output"):
reviewers = df_reviewers.to_dict("records")
submissions = df_submissions.to_dict("records")
for reviewer, assignments in zip(reviewers, solution):
# reviewer["assigned_submission_ids"] = reviewer["assigned_submission_ids"] + \
# df_submissions.submission_id[assignments].values.tolist()
reviewer["assigned_submission_ids"] = df_submissions.submission_id[assignments].values.tolist()
if DEBUG:
# Check how many tutorials everyone got
reviewer["is_tutorial"] = [t == "TUT" for t in df_submissions.track[assignments]]
reviewer["num_tutorials"] = sum(reviewer["is_tutorial"])
reviewer["num_submissions"] = len(reviewer["assigned_submission_ids"])
reviewer["tutorial_reviewer"] = "TUT" in reviewer["tracks"]
# Check that each reviewer actually was assigned a submission in their domain
reviewer["track_in_domain"] = [t in reviewer["tracks"] for t in df_submissions.track[assignments]]
if DEBUG:
result = {reviewer["reviewer_id"]: sorted(reviewer["is_tutorial"]) for reviewer in reviewers}
with open(output_dir / f"review-assignments-debug{post_fix}.json", "w") as fp:
fp.write(json.dumps(result, indent=4))
result = {reviewer["reviewer_id"]: reviewer["assigned_submission_ids"] for reviewer in reviewers}
with open(output_dir / f"review-assignments{post_fix}.json", "w") as fp:
fp.write(json.dumps(result, indent=4))
for submission, assignments in zip(submissions, solution.T):
submission["assigned_reviewer_ids"] = df_reviewers.reviewer_id[assignments].values.tolist()
result = {submission["submission_id"]: submission["assigned_reviewer_ids"] for submission in submissions}
with open(output_dir / f"submission-assignments{post_fix}.json", "w") as fp:
fp.write(json.dumps(result, indent=4))
return reviewers, submissions