-
Notifications
You must be signed in to change notification settings - Fork 0
/
Class_assignment_limited_capacity.py
173 lines (129 loc) · 5.9 KB
/
Class_assignment_limited_capacity.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
161
162
163
164
165
166
167
168
169
170
171
172
173
# -*- coding: utf-8 -*-
"""
Created on Wed Jan 1 20:39:21 2020
@author: sattwik
"""
'''
QUESTION:
We consider the problem of assigning students to courses with limited capacity. We are given
n students, and each student i ∈ {1, . . . , n} wants to attend ai courses. Moreover, for each
course j ∈ {1, . . . , m} we know the maximum number of students bj that may attend it.
Finally, every student i also gave a preference ci,j for each course j, where smaller ci,j -values
mean higher preferences. We now want to minimize the sum of all preferences (of studentcourse assignments), satisfying the constraints that every student is assigned to their requested
number of courses and that the maximum course capacity is not exceeded.
(a) Model this problem as an integer program, and briefly explain the meaning of your variables
and constraints.
(b) Since there might not exist a feasible assignment, the model shall now be modified to
guarantee the existence of solution. To achieve this, not all students have to be assigned
their requested number of courses. Instead, for each student a penalty cost of M shall be
added to the objective for each course fewer than the requested number of courses.
Extend your model from part (a) accordingly. You do not have to restate the model, but
specify the required modifications instead. Explain all changes you make to your model.
(c) Additionally, overbooking of courses shall be possible. For every course j we know a
number dj ∈ Z+ of additional slots as well as penalty costs fj ∈ R+. The penalty costs fj
have to be paid once as soon as at least one of the additional slots of course j is occupied.
Extend your model from part (a) accordingly. You do not have to restate the model, but
specify the required modifications instead. Explain all changes you make to your model.
INSTANCE OF PROBLEM USED FOR THE CODE:
6 students and 3 courses.
each student has to take 2 courses and the class size for each course is 4
each students give a preference of 2 courses
our program allocates courses to students satisfying student preference and batch sizes
'''
import gurobipy as gp
from gurobipy import *
model = Model("student_to_courses")
students, stud_cap = gp.multidict({'stud1':2, 'stud2':2, 'stud3':2, 'stud4':2, 'stud5':2, 'stud6':2})
courses, course_cap = gp.multidict({'crs1':4, 'crs2':4, 'crs3':4})
courses, add_course_cap = gp.multidict({'crs1':2, 'crs2':2, 'crs3':2}) #for part 'c'
courses, add_course_cost = gp.multidict({'crs1':10, 'crs2':10, 'crs3':10}) #for part 'c'
#the dictionary with keys "arcs" and value "preference" defined below is of the form -> student, courses : preferences
#This definition of arcs is for part 'a' of the Solution
#This definition can also be used for part 'c' in case we don't want to include the case of additing penalty costs to unpreferred courses.
'''
arcs, pref = gp.multidict({
('stud1', 'crs1'): 1,
('stud1', 'crs2'): 2,
('stud2', 'crs1'): 1,
('stud2', 'crs2'): 2,
('stud3', 'crs2'): 1,
('stud3', 'crs3'): 2,
('stud4', 'crs2'): 1,
('stud4', 'crs3'): 2,
('stud5', 'crs3'): 1,
('stud5', 'crs1'): 2,
('stud6', 'crs3'): 1,
('stud6', 'crs1'): 2,})
'''
#Below definition of arcs is for part 'b' of the solution
#This arc definition can also be used for part 'c'
'''
This is implemented by adding a preference = a very large value
for all subjects that the student has not preferred
M is assigned a large value (100 in this case)
'''
M=100
arcs, pref = gp.multidict({
('stud1', 'crs1'): 1,
('stud1', 'crs2'): 2,
('stud1', 'crs3'): M,
('stud2', 'crs1'): 1,
('stud2', 'crs2'): 2,
('stud2', 'crs3'): M,
('stud3', 'crs2'): 1,
('stud3', 'crs1'): 2,
('stud3', 'crs3'): M,
('stud4', 'crs3'): 1,
('stud4', 'crs1'): 2,
('stud4', 'crs2'): M,
('stud5', 'crs3'): 1,
('stud5', 'crs1'): 2,
('stud5', 'crs2'): M,
('stud6', 'crs2'): 1,
('stud6', 'crs3'): 2,
('stud6', 'crs1'): M,})
#the above tuples dictionary is of the form -> student, courses : preferences
'''
VARIABLE 1:
variable x[i] tells whether arc[i] is assigned or not
'''
x = model.addVars(arcs, obj=pref, vtype='b', name="assign")
'''
VARIABLE 2:
variable y[i] counts the number of additonal slots of course[i] that have been used
'''
y = model.addVars(courses, lb = 0, ub= add_course_cap, obj=add_course_cost, vtype='i', name="add_counter")
'''
CONSTRAINT 1: STUDENT_CAP
number of arc containing stud1 <=stud_cap('stud1')
'''
model.addConstrs(
(x.sum(i, '*') == stud_cap[i] for i in students), "student_cap")
'''
CONSTRAINT 2: COURSE_CAP (THIS WAS USED FOR PART A AND B)
number of arc containing crs1 <=course_cap('crs1')
'''
'''
model.addConstrs(
(x.sum('*', i) <= course_cap[i] for i in courses), "course_cap")
'''
#FOR PART C FOLLOWING COURSE CAPACITY CONSTRAINT HAS TO BE USED (THIS CAN BE USED EVEN WHEN ARCS ARE DEFINED USING M PENALTY COSTS)
'''
CONSTRAINT 3: ADD_COURSE_CAPACITY
number of arcs containing 'crs1'<=course_cap('crs1') + y['crs1]
'''
model.addConstrs(
(x.sum('*', i) <= (course_cap[i] + y[i]) for i in courses), "add_course_cap")
#Run the optimization
model.optimize()
#Print the Solution:
if model.status == GRB.OPTIMAL:
print("\nRESULT:")
for j in courses:
print(f"\nCourse {j} has following students:")
for i in students:
if (i,j) in arcs:
if x[i,j].x==1:
print(f" {i}")
else:
print("\n\nRESULT:\nNo solution could be obtained for given preferences")