remove std:: from std::min|max in comments; performance improvements on the SAT, bug fixes

This commit is contained in:
Laurent Perron
2018-01-10 13:21:06 +01:00
parent d0dabc6f4d
commit 88ef3d0302
28 changed files with 1500 additions and 490 deletions

View File

@@ -0,0 +1,123 @@
from ortools.sat.python import cp_model
def SolveRosteringWithTravel():
model = cp_model.CpModel()
# [duration, start, end, location]
jobs = [[3, 0, 6, 1],
[5, 0, 6, 0],
[1, 3, 7, 1],
[1, 3, 5, 0],
[3, 0, 3, 0],
[3, 0, 8, 0]]
max_length = 20
num_machines = 3
all_machines = range(num_machines)
horizon = 20
travel_time = 1
num_jobs = len(jobs)
all_jobs = range(num_jobs)
intervals = []
optional_intervals = []
performed = []
starts = []
ends = []
travels = []
for m in all_machines:
optional_intervals.append([])
for i in all_jobs:
# Create main interval.
start = model.NewIntVar(jobs[i][1], horizon, 'start_%i' % i)
duration = jobs[i][0]
end = model.NewIntVar(0, jobs[i][2], 'end_%i' % i)
interval = model.NewIntervalVar(start, duration, end, 'interval_%i' % i)
starts.append(start)
intervals.append(interval)
ends.append(end)
job_performed = []
job_travels = []
for m in all_machines:
performed_on_m = model.NewBoolVar('perform_%i_on_m%i' % (i, m))
job_performed.append(performed_on_m)
# Create an optional copy of interval to be executed on a machine
location0 = model.NewOptionalIntVar(
jobs[i][3], jobs[i][3], performed_on_m, 'location_%i_on_m%i' % (i, m))
start0 = model.NewOptionalIntVar(jobs[i][1], horizon, performed_on_m,
'start_%i_on_m%i' % (i, m))
end0 = model.NewOptionalIntVar(0, jobs[i][2], performed_on_m,
'end_%i_on_m%i' % (i, m))
interval0 = model.NewOptionalIntervalVar(
start0, duration, end0, performed_on_m, 'interval_%i_on_m%i' % (i, m))
optional_intervals[m].append(interval0)
# We only propagate the constraint if the tasks is performed on the machine.
model.Add(start0 == start).OnlyEnforceIf(performed_on_m)
# Adding travel constraint
travel = model.NewBoolVar('is_travel_%i_on_m%i' % (i, m))
startT = model.NewOptionalIntVar(0, horizon, travel,
'start_%i_on_m%i' % (i, m))
endT = model.NewOptionalIntVar(0, horizon, travel, 'end_%i_on_m%i' % (i,
m))
intervalT = model.NewOptionalIntervalVar(
startT, travel_time, endT, travel, 'travel_interval_%i_on_m%i' % (i,
m))
optional_intervals[m].append(intervalT)
job_travels.append(travel)
model.Add(end0 == startT).OnlyEnforceIf(travel)
performed.append(job_performed)
travels.append(job_travels)
model.Add(sum(job_performed) == 1)
for m in all_machines:
if m == 1:
for i in all_jobs:
if i == 2:
for c in all_jobs:
if (i != c) and (jobs[i][3] != jobs[c][3]):
is_job_earlier = model.NewBoolVar('is_j%i_earlier_j%i' % (i, c))
model.Add(starts[i] < starts[c]).OnlyEnforceIf(is_job_earlier)
model.Add(starts[i] >= starts[c]).OnlyEnforceIf(is_job_earlier.Not())
# Max Length constraint (modeled as a cumulative)
# model.AddCumulative(intervals, demands, max_length)
# Choose which machine to perform the jobs on.
for m in all_machines:
model.AddNoOverlap(optional_intervals[m])
# Objective variable.
total_cost = model.NewIntVar(0, 1000, 'cost')
model.Add(total_cost == sum(
performed[j][m] * (10 * (m + 1)) for j in all_jobs for m in all_machines))
model.Minimize(total_cost)
# Solve model.
solver = cp_model.CpSolver()
result = solver.Solve(model)
print()
print(result)
print('Statistics')
print(' - conflicts : %i' % solver.NumConflicts())
print(' - branches : %i' % solver.NumBranches())
print(' - wall time : %f ms' % solver.WallTime())
def main():
SolveRosteringWithTravel()
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,176 @@
from ortools.sat.python import cp_model
class SchoolSchedulingProblem(object):
def __init__(self, subjects, teachers, curriculum, specialties, working_days,
periods, levels, sections, teacher_work_hours):
self.subjects = subjects
self.teachers = teachers
self.curriculum = curriculum
self.specialties = specialties
self.working_days = working_days
self.periods = periods
self.levels = levels
self.sections = sections
self.teacher_work_hours = teacher_work_hours
class SchoolSchedulingSatSolver(object):
def __init__(self, problem):
# Problem
self.problem = problem
# Utilities
self.timeslots = [
'{0:10} {1:6}'.format(x, y)
for x in problem.working_days
for y in problem.periods
]
self.num_days = len(problem.working_days)
self.num_periods = len(problem.periods)
self.num_slots = len(self.timeslots)
self.num_teachers = len(problem.teachers)
self.num_subjects = len(problem.subjects)
self.num_levels = len(problem.levels)
self.num_sections = len(problem.sections)
self.courses = [
x * self.num_levels + y
for x in problem.levels
for y in problem.sections
]
self.num_courses = self.num_levels * self.num_sections
all_courses = range(self.num_courses)
all_teachers = range(self.num_teachers)
all_slots = range(self.num_slots)
all_sections = range(self.num_sections)
all_subjects = range(self.num_subjects)
all_levels = range(self.num_levels)
self.model = cp_model.CpModel()
self.assignment = {}
for c in all_courses:
for s in all_subjects:
for t in all_teachers:
for slot in all_slots:
if t in self.problem.specialties[s]:
name = 'C:{%i} S:{%i} T:{%i} Slot:{%i}' % (c, s, t, slot)
self.assignment[c, s, t, slot] = self.model.NewBoolVar(name)
else:
name = 'NO DISP C:{%i} S:{%i} T:{%i} Slot:{%i}' % (c, s, t, slot)
self.assignment[c, s, t, slot] = self.model.NewIntVar(0, 0, name)
# Constraints
# Each course must have the quantity of classes specified in the curriculum
for level in all_levels:
for section in all_sections:
course = level * self.num_sections + section
for subject in all_subjects:
required_slots = self.problem.curriculum[self.problem.levels[
level], self.problem.subjects[subject]]
self.model.Add(
sum(self.assignment[course, subject, teacher, slot]
for slot in all_slots
for teacher in all_teachers) == required_slots)
# Teacher can do at most one class at a time
for teacher in all_teachers:
for slot in all_slots:
self.model.Add(
sum([
self.assignment[c, s, teacher, slot]
for c in all_courses
for s in all_subjects
]) <= 1)
# Maximum work hours for each teacher
for teacher in all_teachers:
self.model.Add(
sum([
self.assignment[c, s, teacher, slot]
for c in all_courses for s in all_subjects for slot in all_slots
]) <= self.problem.teacher_work_hours[teacher])
# Teacher makes all the classes of a subject's course
teacher_courses = {}
for level in all_levels:
for section in all_sections:
course = level * self.num_sections + section
for subject in all_subjects:
for t in all_teachers:
name = 'C:{%i} S:{%i} T:{%i}' % (course, subject, teacher)
teacher_courses[course, subject, t] = self.model.NewBoolVar(name)
temp_array = [
self.assignment[course, subject, t, slot] for slot in all_slots
]
self.model.AddMaxEquality(teacher_courses[course, subject, t],
temp_array)
self.model.Add(
sum(teacher_courses[course, subject, t]
for t in all_teachers) == 1)
# Solution collector
self.collector = None
def solve(self):
print('Solving')
solver = cp_model.CpSolver()
solution_printer = SchoolSchedulingSatSolutionPrinter()
status = solver.SearchForAllSolutions(self.model, solution_printer)
print()
print('Branches', solver.NumBranches())
print('Conflicts', solver.NumConflicts())
print('WallTime', solver.WallTime())
def print_status(self):
pass
class SchoolSchedulingSatSolutionPrinter(cp_model.CpSolverSolutionCallback):
def NewSolution(self):
print('Found Solution!')
def main():
# DATA
subjects = ['English', 'Math', 'History']
levels = ['1-', '2-', '3-']
sections = ['A']
teachers = ['Mario', 'Elvis', 'Donald', 'Ian']
teachers_work_hours = [18, 12, 12, 18]
working_days = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday']
periods = ['08:00-09:30', '09:45-11:15', '11:30-13:00']
curriculum = {
('1-', 'English'): 3,
('1-', 'Math'): 3,
('1-', 'History'): 2,
('2-', 'English'): 4,
('2-', 'Math'): 2,
('2-', 'History'): 2,
('3-', 'English'): 2,
('3-', 'Math'): 4,
('3-', 'History'): 2
}
# Subject -> List of teachers who can teach it
specialties_idx_inverse = [
[1, 3], # English -> Elvis & Ian
[0, 3], # Math -> Mario & Ian
[2, 3] # History -> Donald & Ian
]
problem = SchoolSchedulingProblem(
subjects, teachers, curriculum, specialties_idx_inverse, working_days,
periods, levels, sections, teachers_work_hours)
solver = SchoolSchedulingSatSolver(problem)
solver.solve()
solver.print_status()
if __name__ == '__main__':
main()