|
|
|
|
@@ -67,6 +67,14 @@
|
|
|
|
|
"!pip install ortools"
|
|
|
|
|
]
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"cell_type": "markdown",
|
|
|
|
|
"id": "description",
|
|
|
|
|
"metadata": {},
|
|
|
|
|
"source": [
|
|
|
|
|
"'''Solve a School Scheduling Problem'''"
|
|
|
|
|
]
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"cell_type": "code",
|
|
|
|
|
"execution_count": null,
|
|
|
|
|
@@ -77,173 +85,344 @@
|
|
|
|
|
"from ortools.sat.python import cp_model\n",
|
|
|
|
|
"\n",
|
|
|
|
|
"\n",
|
|
|
|
|
"class SchoolSchedulingProblem(object):\n",
|
|
|
|
|
"class SchoolSchedulingProblem():\n",
|
|
|
|
|
" '''Data of the problem.'''\n",
|
|
|
|
|
"\n",
|
|
|
|
|
" def __init__(self, subjects, teachers, curriculum, specialties, working_days,\n",
|
|
|
|
|
" periods, levels, sections, teacher_work_hours):\n",
|
|
|
|
|
" self.subjects = subjects\n",
|
|
|
|
|
" self.teachers = teachers\n",
|
|
|
|
|
" self.curriculum = curriculum\n",
|
|
|
|
|
" self.specialties = specialties\n",
|
|
|
|
|
" self.working_days = working_days\n",
|
|
|
|
|
" self.periods = periods\n",
|
|
|
|
|
" self.levels = levels\n",
|
|
|
|
|
" self.sections = sections\n",
|
|
|
|
|
" self.teacher_work_hours = teacher_work_hours\n",
|
|
|
|
|
" def __init__(self, levels, sections, subjects, curriculum, teachers,\n",
|
|
|
|
|
" specialties, time_slots):\n",
|
|
|
|
|
" self._levels = levels\n",
|
|
|
|
|
" self._sections = sections\n",
|
|
|
|
|
" self._subjects = subjects\n",
|
|
|
|
|
" self._curriculum = curriculum\n",
|
|
|
|
|
" assert len(self._curriculum) == len(self._levels) * len(\n",
|
|
|
|
|
" self._subjects), 'Some curriculum are missing'\n",
|
|
|
|
|
" for (lvl, sub) in self._curriculum.keys():\n",
|
|
|
|
|
" assert lvl in self._levels, f'{lvl} not in LEVELS'\n",
|
|
|
|
|
" assert sub in self._subjects, f'{sub} not in SUBJECTS'\n",
|
|
|
|
|
"\n",
|
|
|
|
|
" self._teachers = teachers\n",
|
|
|
|
|
" self._specialties = specialties\n",
|
|
|
|
|
" assert len(self._specialties) == len(\n",
|
|
|
|
|
" self._subjects), 'Missing some rows'\n",
|
|
|
|
|
" for s, ts in self._specialties.items():\n",
|
|
|
|
|
" assert s in self._subjects, f'{s} is not in SUBJECTS'\n",
|
|
|
|
|
" for t in ts:\n",
|
|
|
|
|
" assert t in self._teachers, f'{t} is not in TEACHERS'\n",
|
|
|
|
|
"\n",
|
|
|
|
|
" self._time_slots = time_slots\n",
|
|
|
|
|
"\n",
|
|
|
|
|
" @property\n",
|
|
|
|
|
" def levels(self):\n",
|
|
|
|
|
" return self._levels\n",
|
|
|
|
|
"\n",
|
|
|
|
|
" @property\n",
|
|
|
|
|
" def sections(self):\n",
|
|
|
|
|
" return self._sections\n",
|
|
|
|
|
"\n",
|
|
|
|
|
" @property\n",
|
|
|
|
|
" def subjects(self):\n",
|
|
|
|
|
" return self._subjects\n",
|
|
|
|
|
"\n",
|
|
|
|
|
" @property\n",
|
|
|
|
|
" def curriculum(self):\n",
|
|
|
|
|
" return self._curriculum\n",
|
|
|
|
|
"\n",
|
|
|
|
|
" @property\n",
|
|
|
|
|
" def teachers(self):\n",
|
|
|
|
|
" return self._teachers\n",
|
|
|
|
|
"\n",
|
|
|
|
|
" def teacher_name(self, teacher_idx):\n",
|
|
|
|
|
" assert 0 <= teacher_idx < len(self._teachers)\n",
|
|
|
|
|
" return list(self._teachers.keys())[teacher_idx]\n",
|
|
|
|
|
"\n",
|
|
|
|
|
" def teacher_max_hours(self, teacher_idx):\n",
|
|
|
|
|
" assert 0 <= teacher_idx < len(self._teachers)\n",
|
|
|
|
|
" return list(self._teachers.values())[teacher_idx]\n",
|
|
|
|
|
"\n",
|
|
|
|
|
" @property\n",
|
|
|
|
|
" def specialties(self):\n",
|
|
|
|
|
" return self._specialties\n",
|
|
|
|
|
"\n",
|
|
|
|
|
" def specialtie_teachers(self, subject):\n",
|
|
|
|
|
" assert subject in self._subjects, f'{subject} not in SUBJECTS'\n",
|
|
|
|
|
" return self._specialties[subject]\n",
|
|
|
|
|
"\n",
|
|
|
|
|
" @property\n",
|
|
|
|
|
" def time_slots(self):\n",
|
|
|
|
|
" return self._time_slots\n",
|
|
|
|
|
"\n",
|
|
|
|
|
" def slot_duration(self, slot_idx):\n",
|
|
|
|
|
" assert 0 <= slot_idx < len(self._time_slots)\n",
|
|
|
|
|
" return list(self._time_slots.values())[slot_idx]\n",
|
|
|
|
|
"\n",
|
|
|
|
|
"\n",
|
|
|
|
|
"class SchoolSchedulingSatSolver(object):\n",
|
|
|
|
|
"class SchoolSchedulingSatSolver():\n",
|
|
|
|
|
" '''Solver instance.'''\n",
|
|
|
|
|
"\n",
|
|
|
|
|
" def __init__(self, problem):\n",
|
|
|
|
|
" # Problem\n",
|
|
|
|
|
" self.problem = problem\n",
|
|
|
|
|
" def __init__(self, problem: SchoolSchedulingProblem):\n",
|
|
|
|
|
" # Problem\n",
|
|
|
|
|
" self._problem = problem\n",
|
|
|
|
|
"\n",
|
|
|
|
|
" # Utilities\n",
|
|
|
|
|
" self.timeslots = [\n",
|
|
|
|
|
" '{0:10} {1:6}'.format(x, y)\n",
|
|
|
|
|
" for x in problem.working_days\n",
|
|
|
|
|
" for y in problem.periods\n",
|
|
|
|
|
" ]\n",
|
|
|
|
|
" self.num_days = len(problem.working_days)\n",
|
|
|
|
|
" self.num_periods = len(problem.periods)\n",
|
|
|
|
|
" self.num_slots = len(self.timeslots)\n",
|
|
|
|
|
" self.num_teachers = len(problem.teachers)\n",
|
|
|
|
|
" self.num_subjects = len(problem.subjects)\n",
|
|
|
|
|
" self.num_levels = len(problem.levels)\n",
|
|
|
|
|
" self.num_sections = len(problem.sections)\n",
|
|
|
|
|
" self.courses = [\n",
|
|
|
|
|
" x * self.num_levels + y\n",
|
|
|
|
|
" for x in problem.levels\n",
|
|
|
|
|
" for y in problem.sections\n",
|
|
|
|
|
" ]\n",
|
|
|
|
|
" self.num_courses = self.num_levels * self.num_sections\n",
|
|
|
|
|
" # Utilities\n",
|
|
|
|
|
" num_levels = len(self._problem.levels)\n",
|
|
|
|
|
" self._all_levels = range(num_levels)\n",
|
|
|
|
|
" num_sections = len(self._problem.sections)\n",
|
|
|
|
|
" self._all_sections = range(num_sections)\n",
|
|
|
|
|
" num_subjects = len(self._problem.subjects)\n",
|
|
|
|
|
" self._all_subjects = range(num_subjects)\n",
|
|
|
|
|
" num_teachers = len(self._problem.teachers)\n",
|
|
|
|
|
" self._all_teachers = range(num_teachers)\n",
|
|
|
|
|
" num_slots = len(self._problem.time_slots)\n",
|
|
|
|
|
" self._all_slots = range(num_slots)\n",
|
|
|
|
|
"\n",
|
|
|
|
|
" all_courses = range(self.num_courses)\n",
|
|
|
|
|
" all_teachers = range(self.num_teachers)\n",
|
|
|
|
|
" all_slots = range(self.num_slots)\n",
|
|
|
|
|
" all_sections = range(self.num_sections)\n",
|
|
|
|
|
" all_subjects = range(self.num_subjects)\n",
|
|
|
|
|
" all_levels = range(self.num_levels)\n",
|
|
|
|
|
" # Create Model\n",
|
|
|
|
|
" self._model = cp_model.CpModel()\n",
|
|
|
|
|
"\n",
|
|
|
|
|
" self.model = cp_model.CpModel()\n",
|
|
|
|
|
" # Create Variables\n",
|
|
|
|
|
" self._assignment = {}\n",
|
|
|
|
|
" for lvl_idx, level in enumerate(self._problem.levels):\n",
|
|
|
|
|
" for sec_idx, section in enumerate(self._problem.sections):\n",
|
|
|
|
|
" for sub_idx, subject in enumerate(self._problem.subjects):\n",
|
|
|
|
|
" for tch_idx, teacher in enumerate(self._problem.teachers):\n",
|
|
|
|
|
" for slt_idx, slot in enumerate(self._problem.time_slots):\n",
|
|
|
|
|
" key = (lvl_idx, sec_idx, sub_idx, tch_idx, slt_idx)\n",
|
|
|
|
|
" name = f'{level}-{section} S:{subject} T:{teacher} Slot:{slot}'\n",
|
|
|
|
|
" #print(name)\n",
|
|
|
|
|
" if teacher in self._problem.specialtie_teachers(subject):\n",
|
|
|
|
|
" self._assignment[key] = self._model.NewBoolVar(name)\n",
|
|
|
|
|
" else:\n",
|
|
|
|
|
" name = 'NO DISP ' + name\n",
|
|
|
|
|
" self._assignment[key] = self._model.NewIntVar(0, 0, name)\n",
|
|
|
|
|
"\n",
|
|
|
|
|
" self.assignment = {}\n",
|
|
|
|
|
" for c in all_courses:\n",
|
|
|
|
|
" for s in all_subjects:\n",
|
|
|
|
|
" for t in all_teachers:\n",
|
|
|
|
|
" for slot in all_slots:\n",
|
|
|
|
|
" if t in self.problem.specialties[s]:\n",
|
|
|
|
|
" name = 'C:{%i} S:{%i} T:{%i} Slot:{%i}' % (c, s, t, slot)\n",
|
|
|
|
|
" self.assignment[c, s, t, slot] = self.model.NewBoolVar(name)\n",
|
|
|
|
|
" else:\n",
|
|
|
|
|
" name = 'NO DISP C:{%i} S:{%i} T:{%i} Slot:{%i}' % (c, s, t, slot)\n",
|
|
|
|
|
" self.assignment[c, s, t, slot] = self.model.NewIntVar(0, 0, name)\n",
|
|
|
|
|
" # Constraints\n",
|
|
|
|
|
" # Each Level-Section must have the quantity of classes per Subject specified in the Curriculum\n",
|
|
|
|
|
" for lvl_idx, level in enumerate(self._problem.levels):\n",
|
|
|
|
|
" for sec_idx in self._all_sections:\n",
|
|
|
|
|
" for sub_idx, subject in enumerate(self._problem.subjects):\n",
|
|
|
|
|
" required_duration = self._problem.curriculum[level, subject]\n",
|
|
|
|
|
" #print(f'L:{level} S:{subject} duration:{required_duration}h')\n",
|
|
|
|
|
" self._model.Add(\n",
|
|
|
|
|
" sum(self._assignment[lvl_idx, sec_idx, sub_idx, tch_idx, slt_idx] *\n",
|
|
|
|
|
" int(self._problem.slot_duration(slt_idx) * 10)\n",
|
|
|
|
|
" for tch_idx in self._all_teachers\n",
|
|
|
|
|
" for slt_idx in self._all_slots) == int(required_duration * 10))\n",
|
|
|
|
|
"\n",
|
|
|
|
|
" # Constraints\n",
|
|
|
|
|
" # Each Level-Section can do at most one class at a time\n",
|
|
|
|
|
" for lvl_idx in self._all_levels:\n",
|
|
|
|
|
" for sec_idx in self._all_sections:\n",
|
|
|
|
|
" for slt_idx in self._all_slots:\n",
|
|
|
|
|
" self._model.Add(\n",
|
|
|
|
|
" sum([\n",
|
|
|
|
|
" self._assignment[lvl_idx, sec_idx, sub_idx, tch_idx, slt_idx]\n",
|
|
|
|
|
" for sub_idx in self._all_subjects\n",
|
|
|
|
|
" for tch_idx in self._all_teachers\n",
|
|
|
|
|
" ]) <= 1)\n",
|
|
|
|
|
"\n",
|
|
|
|
|
" # Each course must have the quantity of classes specified in the curriculum\n",
|
|
|
|
|
" for level in all_levels:\n",
|
|
|
|
|
" for section in all_sections:\n",
|
|
|
|
|
" course = level * self.num_sections + section\n",
|
|
|
|
|
" for subject in all_subjects:\n",
|
|
|
|
|
" required_slots = self.problem.curriculum[\n",
|
|
|
|
|
" self.problem.levels[level], self.problem.subjects[subject]]\n",
|
|
|
|
|
" self.model.Add(\n",
|
|
|
|
|
" sum(self.assignment[course, subject, teacher, slot]\n",
|
|
|
|
|
" for slot in all_slots\n",
|
|
|
|
|
" for teacher in all_teachers) == required_slots)\n",
|
|
|
|
|
" # Teacher can do at most one class at a time\n",
|
|
|
|
|
" for tch_idx in self._all_teachers:\n",
|
|
|
|
|
" for slt_idx in self._all_slots:\n",
|
|
|
|
|
" self._model.Add(\n",
|
|
|
|
|
" sum([\n",
|
|
|
|
|
" self._assignment[lvl_idx, sec_idx, sub_idx, tch_idx, slt_idx]\n",
|
|
|
|
|
" for lvl_idx in self._all_levels\n",
|
|
|
|
|
" for sec_idx in self._all_sections\n",
|
|
|
|
|
" for sub_idx in self._all_subjects\n",
|
|
|
|
|
" ]) <= 1)\n",
|
|
|
|
|
"\n",
|
|
|
|
|
" # Teacher can do at most one class at a time\n",
|
|
|
|
|
" for teacher in all_teachers:\n",
|
|
|
|
|
" for slot in all_slots:\n",
|
|
|
|
|
" self.model.Add(\n",
|
|
|
|
|
" sum([\n",
|
|
|
|
|
" self.assignment[c, s, teacher, slot]\n",
|
|
|
|
|
" for c in all_courses\n",
|
|
|
|
|
" for s in all_subjects\n",
|
|
|
|
|
" ]) <= 1)\n",
|
|
|
|
|
" # Maximum work hours for each teacher\n",
|
|
|
|
|
" for tch_idx in self._all_teachers:\n",
|
|
|
|
|
" self._model.Add(\n",
|
|
|
|
|
" sum([\n",
|
|
|
|
|
" self._assignment[lvl_idx, sec_idx, sub_idx, tch_idx, slt_idx] *\n",
|
|
|
|
|
" int(self._problem.slot_duration(slt_idx) * 10)\n",
|
|
|
|
|
" for lvl_idx in self._all_levels\n",
|
|
|
|
|
" for sec_idx in self._all_sections\n",
|
|
|
|
|
" for sub_idx in self._all_subjects\n",
|
|
|
|
|
" for slt_idx in self._all_slots\n",
|
|
|
|
|
" ]) <= int(self._problem.teacher_max_hours(tch_idx) * 10))\n",
|
|
|
|
|
"\n",
|
|
|
|
|
" # Maximum work hours for each teacher\n",
|
|
|
|
|
" for teacher in all_teachers:\n",
|
|
|
|
|
" self.model.Add(\n",
|
|
|
|
|
" sum([\n",
|
|
|
|
|
" self.assignment[c, s, teacher, slot] for c in all_courses\n",
|
|
|
|
|
" for s in all_subjects for slot in all_slots\n",
|
|
|
|
|
" ]) <= self.problem.teacher_work_hours[teacher])\n",
|
|
|
|
|
" # Teacher makes all the classes of a subject's course\n",
|
|
|
|
|
" teacher_courses = {}\n",
|
|
|
|
|
" for lvl_idx, level in enumerate(self._problem.levels):\n",
|
|
|
|
|
" for sec_idx, section in enumerate(self._problem.sections):\n",
|
|
|
|
|
" for sub_idx, subject in enumerate(self._problem.subjects):\n",
|
|
|
|
|
" for tch_idx, teacher in enumerate(self._problem.teachers):\n",
|
|
|
|
|
" name = f'{level}-{section} S:{subject} T:{teacher}'\n",
|
|
|
|
|
" teacher_courses[lvl_idx, sec_idx, sub_idx, tch_idx] = self._model.NewBoolVar(name)\n",
|
|
|
|
|
" temp_array = [\n",
|
|
|
|
|
" self._assignment[lvl_idx, sec_idx, sub_idx, tch_idx, slt_idx]\n",
|
|
|
|
|
" for slt_idx in self._all_slots\n",
|
|
|
|
|
" ]\n",
|
|
|
|
|
" self._model.AddMaxEquality(\n",
|
|
|
|
|
" teacher_courses[lvl_idx, sec_idx, sub_idx, tch_idx], temp_array)\n",
|
|
|
|
|
" self._model.Add(\n",
|
|
|
|
|
" sum([teacher_courses[lvl_idx, sec_idx, sub_idx, tch_idx]\n",
|
|
|
|
|
" for tch_idx in self._all_teachers\n",
|
|
|
|
|
" ]) == 1)\n",
|
|
|
|
|
"\n",
|
|
|
|
|
" # Teacher makes all the classes of a subject's course\n",
|
|
|
|
|
" teacher_courses = {}\n",
|
|
|
|
|
" for level in all_levels:\n",
|
|
|
|
|
" for section in all_sections:\n",
|
|
|
|
|
" course = level * self.num_sections + section\n",
|
|
|
|
|
" for subject in all_subjects:\n",
|
|
|
|
|
" for t in all_teachers:\n",
|
|
|
|
|
" name = 'C:{%i} S:{%i} T:{%i}' % (course, subject, teacher)\n",
|
|
|
|
|
" teacher_courses[course, subject, t] = self.model.NewBoolVar(name)\n",
|
|
|
|
|
" temp_array = [\n",
|
|
|
|
|
" self.assignment[course, subject, t, slot] for slot in all_slots\n",
|
|
|
|
|
" ]\n",
|
|
|
|
|
" self.model.AddMaxEquality(teacher_courses[course, subject, t],\n",
|
|
|
|
|
" temp_array)\n",
|
|
|
|
|
" self.model.Add(\n",
|
|
|
|
|
" sum(teacher_courses[course, subject, t]\n",
|
|
|
|
|
" for t in all_teachers) == 1)\n",
|
|
|
|
|
"\n",
|
|
|
|
|
" def solve(self):\n",
|
|
|
|
|
" print('Solving')\n",
|
|
|
|
|
" solver = cp_model.CpSolver()\n",
|
|
|
|
|
" solution_printer = SchoolSchedulingSatSolutionPrinter()\n",
|
|
|
|
|
" status = solver.Solve(self.model, solution_printer)\n",
|
|
|
|
|
" print()\n",
|
|
|
|
|
" print('status', status)\n",
|
|
|
|
|
" print('Branches', solver.NumBranches())\n",
|
|
|
|
|
" print('Conflicts', solver.NumConflicts())\n",
|
|
|
|
|
" print('WallTime', solver.WallTime())\n",
|
|
|
|
|
" def print_teacher_schedule(self, tch_idx):\n",
|
|
|
|
|
" teacher_name = self._problem.teacher_name(tch_idx)\n",
|
|
|
|
|
" print(f'Teacher: {teacher_name}')\n",
|
|
|
|
|
" total_working_hours = 0\n",
|
|
|
|
|
" for slt_idx, slot in enumerate(self._problem.time_slots):\n",
|
|
|
|
|
" for lvl_idx, level in enumerate(self._problem.levels):\n",
|
|
|
|
|
" for sec_idx, section in enumerate(self._problem.sections):\n",
|
|
|
|
|
" for sub_idx, subject in enumerate(self._problem.subjects):\n",
|
|
|
|
|
" key = (lvl_idx, sec_idx, sub_idx, tch_idx, slt_idx)\n",
|
|
|
|
|
" if self._solver.BooleanValue(self._assignment[key]):\n",
|
|
|
|
|
" total_working_hours += self._problem.slot_duration(slt_idx)\n",
|
|
|
|
|
" print(f'{slot}: C:{level}-{section} S:{subject}')\n",
|
|
|
|
|
" print(f'Total working hours: {total_working_hours}h')\n",
|
|
|
|
|
"\n",
|
|
|
|
|
"\n",
|
|
|
|
|
" def print_class_schedule(self, lvl_idx, sec_idx):\n",
|
|
|
|
|
" level = self._problem.levels[lvl_idx]\n",
|
|
|
|
|
" section = self._problem.sections[sec_idx]\n",
|
|
|
|
|
" print(f'Class: {level}-{section}')\n",
|
|
|
|
|
" total_working_hours = {}\n",
|
|
|
|
|
" for sub in self._problem.subjects:\n",
|
|
|
|
|
" total_working_hours[sub] = 0\n",
|
|
|
|
|
" for slt_idx, slot in enumerate(self._problem.time_slots):\n",
|
|
|
|
|
" for tch_idx, teacher in enumerate(self._problem.teachers):\n",
|
|
|
|
|
" for sub_idx, subject in enumerate(self._problem.subjects):\n",
|
|
|
|
|
" key = (lvl_idx, sec_idx, sub_idx, tch_idx, slt_idx)\n",
|
|
|
|
|
" if self._solver.BooleanValue(self._assignment[key]):\n",
|
|
|
|
|
" total_working_hours[subject] += self._problem.slot_duration(slt_idx)\n",
|
|
|
|
|
" print(f'{slot}: S:{subject} T:{teacher}')\n",
|
|
|
|
|
" for (subject, hours) in total_working_hours.items():\n",
|
|
|
|
|
" print(f'Total working hours for {subject}: {hours}h')\n",
|
|
|
|
|
"\n",
|
|
|
|
|
"\n",
|
|
|
|
|
" def print_school_schedule(self):\n",
|
|
|
|
|
" print('School:')\n",
|
|
|
|
|
" for slt_idx, slot in enumerate(self._problem.time_slots):\n",
|
|
|
|
|
" tmp = f'{slot}:'\n",
|
|
|
|
|
" for lvl_idx, level in enumerate(self._problem.levels):\n",
|
|
|
|
|
" for sec_idx, section in enumerate(self._problem.sections):\n",
|
|
|
|
|
" for sub_idx, subject in enumerate(self._problem.subjects):\n",
|
|
|
|
|
" for tch_idx, teacher in enumerate(self._problem.teachers):\n",
|
|
|
|
|
" key = (lvl_idx, sec_idx, sub_idx, tch_idx, slt_idx)\n",
|
|
|
|
|
" if self._solver.BooleanValue(self._assignment[key]):\n",
|
|
|
|
|
" tmp += f' {level}-{section}:({subject},{teacher})'\n",
|
|
|
|
|
" print(tmp)\n",
|
|
|
|
|
"\n",
|
|
|
|
|
"\n",
|
|
|
|
|
" def solve(self):\n",
|
|
|
|
|
" print('Solving')\n",
|
|
|
|
|
" # Create Solver\n",
|
|
|
|
|
" self._solver = cp_model.CpSolver()\n",
|
|
|
|
|
"\n",
|
|
|
|
|
" solution_printer = SchoolSchedulingSatSolutionPrinter()\n",
|
|
|
|
|
" status = self._solver.Solve(self._model, solution_printer)\n",
|
|
|
|
|
" print('Status: ', self._solver.StatusName(status))\n",
|
|
|
|
|
"\n",
|
|
|
|
|
" if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE:\n",
|
|
|
|
|
" print('\\n# Teachers')\n",
|
|
|
|
|
" for teacher_idx in self._all_teachers:\n",
|
|
|
|
|
" self.print_teacher_schedule(teacher_idx)\n",
|
|
|
|
|
"\n",
|
|
|
|
|
" print('\\n# Classes')\n",
|
|
|
|
|
" for level_idx in self._all_levels:\n",
|
|
|
|
|
" for section_idx in self._all_sections:\n",
|
|
|
|
|
" self.print_class_schedule(level_idx, section_idx)\n",
|
|
|
|
|
"\n",
|
|
|
|
|
" print('\\n# School')\n",
|
|
|
|
|
" self.print_school_schedule()\n",
|
|
|
|
|
"\n",
|
|
|
|
|
" print('Branches: ', self._solver.NumBranches())\n",
|
|
|
|
|
" print('Conflicts: ', self._solver.NumConflicts())\n",
|
|
|
|
|
" print('WallTime: ', self._solver.WallTime())\n",
|
|
|
|
|
"\n",
|
|
|
|
|
"\n",
|
|
|
|
|
"class SchoolSchedulingSatSolutionPrinter(cp_model.CpSolverSolutionCallback):\n",
|
|
|
|
|
"\n",
|
|
|
|
|
" def __init__(self):\n",
|
|
|
|
|
" cp_model.CpSolverSolutionCallback.__init__(self)\n",
|
|
|
|
|
" self.__solution_count = 0\n",
|
|
|
|
|
" def __init__(self):\n",
|
|
|
|
|
" cp_model.CpSolverSolutionCallback.__init__(self)\n",
|
|
|
|
|
" self.__solution_count = 0\n",
|
|
|
|
|
"\n",
|
|
|
|
|
" def OnSolutionCallback(self):\n",
|
|
|
|
|
" print('Found Solution!')\n",
|
|
|
|
|
" def OnSolutionCallback(self):\n",
|
|
|
|
|
" print(\n",
|
|
|
|
|
" f'Solution #{self.__solution_count}, objective: {self.ObjectiveValue()}'\n",
|
|
|
|
|
" )\n",
|
|
|
|
|
" self.__solution_count += 1\n",
|
|
|
|
|
"\n",
|
|
|
|
|
"\n",
|
|
|
|
|
"def main():\n",
|
|
|
|
|
" # DATA\n",
|
|
|
|
|
" subjects = ['English', 'Math', 'History']\n",
|
|
|
|
|
" levels = ['1-', '2-', '3-']\n",
|
|
|
|
|
" sections = ['A']\n",
|
|
|
|
|
" teachers = ['Mario', 'Elvis', 'Donald', 'Ian']\n",
|
|
|
|
|
" teachers_work_hours = [18, 12, 12, 18]\n",
|
|
|
|
|
" working_days = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday']\n",
|
|
|
|
|
" periods = ['08:00-09:30', '09:45-11:15', '11:30-13:00']\n",
|
|
|
|
|
" curriculum = {\n",
|
|
|
|
|
" ('1-', 'English'): 3,\n",
|
|
|
|
|
" ('1-', 'Math'): 3,\n",
|
|
|
|
|
" ('1-', 'History'): 2,\n",
|
|
|
|
|
" ('2-', 'English'): 4,\n",
|
|
|
|
|
" ('2-', 'Math'): 2,\n",
|
|
|
|
|
" ('2-', 'History'): 2,\n",
|
|
|
|
|
" ('3-', 'English'): 2,\n",
|
|
|
|
|
" ('3-', 'Math'): 4,\n",
|
|
|
|
|
" ('3-', 'History'): 2\n",
|
|
|
|
|
" }\n",
|
|
|
|
|
" # DATA\n",
|
|
|
|
|
" ## Classes\n",
|
|
|
|
|
" LEVELS = [\n",
|
|
|
|
|
" '1',\n",
|
|
|
|
|
" '2',\n",
|
|
|
|
|
" '3',\n",
|
|
|
|
|
" ]\n",
|
|
|
|
|
" SECTIONS = [\n",
|
|
|
|
|
" 'A',\n",
|
|
|
|
|
" 'B',\n",
|
|
|
|
|
" ]\n",
|
|
|
|
|
" SUBJECTS = [\n",
|
|
|
|
|
" 'English',\n",
|
|
|
|
|
" 'Math',\n",
|
|
|
|
|
" #'Science',\n",
|
|
|
|
|
" 'History',\n",
|
|
|
|
|
" ]\n",
|
|
|
|
|
" CURRICULUM = {\n",
|
|
|
|
|
" ('1', 'English'): 3,\n",
|
|
|
|
|
" ('1', 'Math'): 3,\n",
|
|
|
|
|
" ('1', 'History'): 2,\n",
|
|
|
|
|
" ('2', 'English'): 4,\n",
|
|
|
|
|
" ('2', 'Math'): 2,\n",
|
|
|
|
|
" ('2', 'History'): 2,\n",
|
|
|
|
|
" ('3', 'English'): 2,\n",
|
|
|
|
|
" ('3', 'Math'): 4,\n",
|
|
|
|
|
" ('3', 'History'): 2,\n",
|
|
|
|
|
" }\n",
|
|
|
|
|
"\n",
|
|
|
|
|
" # Subject -> List of teachers who can teach it\n",
|
|
|
|
|
" specialties_idx_inverse = [\n",
|
|
|
|
|
" [1, 3], # English -> Elvis & Ian\n",
|
|
|
|
|
" [0, 3], # Math -> Mario & Ian\n",
|
|
|
|
|
" [2, 3] # History -> Donald & Ian\n",
|
|
|
|
|
" ]\n",
|
|
|
|
|
" ## Teachers\n",
|
|
|
|
|
" TEACHERS = { # name, max_work_hours\n",
|
|
|
|
|
" 'Mario': 14,\n",
|
|
|
|
|
" 'Elvis': 12,\n",
|
|
|
|
|
" 'Harry': 12,\n",
|
|
|
|
|
" 'Ian': 14,\n",
|
|
|
|
|
" }\n",
|
|
|
|
|
" # Subject -> List of teachers who can teach it\n",
|
|
|
|
|
" SPECIALTIES = {\n",
|
|
|
|
|
" 'English': ['Elvis', 'Ian'],\n",
|
|
|
|
|
" 'Math': ['Mario', 'Ian'],\n",
|
|
|
|
|
" 'History': ['Harry', 'Ian'],\n",
|
|
|
|
|
" }\n",
|
|
|
|
|
"\n",
|
|
|
|
|
" problem = SchoolSchedulingProblem(\n",
|
|
|
|
|
" subjects, teachers, curriculum, specialties_idx_inverse, working_days,\n",
|
|
|
|
|
" periods, levels, sections, teachers_work_hours)\n",
|
|
|
|
|
" solver = SchoolSchedulingSatSolver(problem)\n",
|
|
|
|
|
" solver.solve()\n",
|
|
|
|
|
" ## Schedule\n",
|
|
|
|
|
" TIME_SLOTS = {\n",
|
|
|
|
|
" 'Monday:08:00-09:30': 1.5,\n",
|
|
|
|
|
" 'Monday:09:45-11:15': 1.5,\n",
|
|
|
|
|
" 'Monday:11:30-12:30': 1,\n",
|
|
|
|
|
" 'Monday:13:30-15:30': 2,\n",
|
|
|
|
|
" 'Monday:15:45-17:15': 1.5,\n",
|
|
|
|
|
" 'Tuesday:08:00-09:30': 1.5,\n",
|
|
|
|
|
" 'Tuesday:09:45-11:15': 1.5,\n",
|
|
|
|
|
" 'Tuesday:11:30-12:30': 1,\n",
|
|
|
|
|
" 'Tuesday:13:30-15:30': 2,\n",
|
|
|
|
|
" 'Tuesday:15:45-17:15': 1.5,\n",
|
|
|
|
|
" 'Wednesday:08:00-09:30': 1.5,\n",
|
|
|
|
|
" 'Wednesday:09:45-11:15': 1.5,\n",
|
|
|
|
|
" 'Wednesday:11:30-12:30': 1,\n",
|
|
|
|
|
" 'Thursday:08:00-09:30': 1.5,\n",
|
|
|
|
|
" 'Thursday:09:45-11:15': 1.5,\n",
|
|
|
|
|
" 'Thursday:11:30-12:30': 1,\n",
|
|
|
|
|
" 'Thursday:13:30-15:30': 2,\n",
|
|
|
|
|
" 'Thursday:15:45-17:15': 1.5,\n",
|
|
|
|
|
" 'Friday:08:00-09:30': 1.5,\n",
|
|
|
|
|
" 'Friday:09:45-11:15': 1.5,\n",
|
|
|
|
|
" 'Friday:11:30-12:30': 1,\n",
|
|
|
|
|
" 'Friday:13:30-15:30': 2,\n",
|
|
|
|
|
" 'Friday:15:45-17:15': 1.5,\n",
|
|
|
|
|
" }\n",
|
|
|
|
|
"\n",
|
|
|
|
|
" problem = SchoolSchedulingProblem(LEVELS, SECTIONS, SUBJECTS, CURRICULUM,\n",
|
|
|
|
|
" TEACHERS, SPECIALTIES, TIME_SLOTS)\n",
|
|
|
|
|
" solver = SchoolSchedulingSatSolver(problem)\n",
|
|
|
|
|
" solver.solve()\n",
|
|
|
|
|
"\n",
|
|
|
|
|
"\n",
|
|
|
|
|
"main()\n",
|
|
|
|
|
|