Exam scheduling infeasible solutions?
AnsweredHi all,
I am trying to solve an examination timetabling problem (ITC 2007 track 1 instances, using only a subset of the original constraints)
I am trying to run below code but for some reason not all exams get assigned a room and a period, even though I have a constraint taking care of that every exam needs to be scheduled for exactly 1 room and 1 period. Could anyone perhaps help me out with this?
# Create the model within the Gurobi environment
model = gp.Model(env=env)
model.setParam('PoolSearchMode', 2) # Find multiple solutions
model.setParam('PoolSolutions', 40) # Maximum number of solutions to store in the pool
# Decision Variables
num_exams = timetable_instance.meta['Exams']
num_periods = timetable_instance.meta['Periods']
num_rooms = timetable_instance.meta['Rooms']
imm = timetable_instance.imm
durations = timetable_instance.durations
# Calculate the maximum number of invigilators
num_max_invigilators = sum(math.ceil(len(timetable_instance.imm[exam]) / 30) for exam in range(num_exams))
x = gp.tuplelist([(exam, room, period) for exam in range(num_exams) for room in range(num_rooms) for period in range(num_periods)])
x = model.addVars(x, vtype=GRB.BINARY, name="x")
model.update()
# Constraints
# Constraint: Each exam is assigned to exactly one room and one period
for exam in range(num_exams):
model.addConstr(gp.quicksum(x[exam, room, period] for room in range(num_rooms) for period in range(num_periods)) == 1,
f'exam_assignment[{exam}]')
model.update()
# Constraint: Room capacities are never to be exceeded
for room in range(num_rooms):
for period in range(num_periods):
# Create a list to hold expressions for each exam in the current room and period
exam_expressions = []
for exam in range(num_exams):
expr = gp.LinExpr()
expr.addTerms(len(timetable_instance.imm[exam]), x[exam, room, period])
exam_expressions.append(expr)
# Sum up the expressions for all exams in the current room and period
total_students_expr = gp.LinExpr()
for expr in exam_expressions:
total_students_expr.add(expr)
# Add a constraint that the total students should not exceed the room's capacity
model.addConstr(total_students_expr <= timetable_instance.rooms[room]['capacity'],
f'room_capacity[{room},{period}]')
model.update()
#Constraint: Each student can only be assigned to one exam in a period
student_exams = {}
for i in range(len(timetable_instance.imm)):
for student_number in timetable_instance.imm[i]:
if student_number not in student_exams:
student_exams[student_number] = []
student_exams[student_number].append(i)
for student, ex in student_exams.items():
num_exams_this_student = len(ex)
if num_exams_this_student>1:
for i in range(num_exams_this_student - 1):
exam1 = ex[i]
for j in range(i + 1, num_exams_this_student):
exam2 = ex[j]
for period in range(num_periods):
model.addConstr(gp.quicksum(x[exam1, room, period] + \
x[exam2, room, period] for room in range(num_rooms))\
<= 1, f'student_{student}_period_{period}_constraint')
model.update()
# Constraint: Period lengths respected
for exam in range(num_exams):
for room in range(num_rooms):
for period in range(num_periods):
if durations[exam] > timetable_instance.periods[period]['duration']:
model.addConstr(x[exam, room, period] == 0, f'duration_constraint[{exam},{room},{period}]')
model.update()
model.setParam('FeasibilityTol', 1e-9)
model.params.TimeLimit = 1800 # setting a time limit of 300 seconds
solutions_dfs = []
# Optimize the model
model.optimize()
if model.Status == GRB.INFEASIBLE:
model.computeIIS()
# Print out the IIS constraints and variables
print('\nThe following constraints and variables are in the IIS:')
for c in model.getConstrs():
if c.IISConstr:
print(f'\t{c.constrname}: {model.getRow(c)} {c.Sense} {c.RHS}')
for v in model.getVars():
if v.IISLB:
print(f'\t{v.varname} ≥ {v.LB}')
if v.IISUB:
print(f'\t{v.varname} ≤ {v.UB}')
else:
num_solutions = model.SolCount
print("Number of solutions found:", num_solutions)
print('runtime is',model.Runtime)
# Getting % usage of virtual_memory ( 3rd field)
print('RAM memory % used:', psutil.virtual_memory()[2])
solution_pool_size = model.getAttr('SolCount')
print(f"Solution pool contains {solution_pool_size} solutions")
# Access every solution from the Gurobi solution pool
solution_dataframes = []
count_0s=0
for solution_index in range(solution_pool_size):
model.params.SolutionNumber = solution_index
# Create a DataFrame to store variable values for this solution
solution_vars_df = pd.DataFrame(columns=['Exam', 'Room', 'Period','Invigilators'])
# Retrieve and store variable values for this solution
for exam in range(num_exams):
for room in range(num_rooms):
for period in range(num_periods):
if exam==0:
if x[exam, room, period].Xn == 1:
count_0s+=1
if x[exam, room, period].Xn == 1:
solution_vars_df = solution_vars_df.append({
'Exam': exam,
'Room': room,
'Period': period,
}, ignore_index=True)
# Store the DataFrame for this solution
solution_dataframes.append(solution_vars_df)
-
Official comment
This post is more than two years old. Some information may not be up to date. For current information, please check the Gurobi Documentation or Knowledge Base. If you need more help, please create a new post in the community forum, or try Gurobot, our chatbot interface offering instant, expert-level support. -
Hi Suzan,
As the first step to debugging the behaviour you are observing, please write your model in the LP file format using model.write("mymodel.lp") and check if the exam_assignment constraints are constructed as per your expectation in the model. The lp file can be viewed with any text editor.
Best regards,
Simran0
Post is closed for comments.
Comments
2 comments