I am trying to use Optapy with the school timetabling example as a base. For any given of subject I have a list of teachers that potentially can teach this subject and each teacher can potentially teach 1 to 3 subjects. I want to optimize the teacher allocation to the lessons, such that, each student group can only have one teacher for any given subject and each student group supposed to get few lessons for any subject (i.e. 4 times math lesson for the student group A given by teacher B)
For that I tried to the add this problem fact:
@problem_fact
class Teacher:
def __init__(self, id, name, subject, subject2, subject3):
self.id = id
self.name = name
self.subject = subject
self.subject2 = subject2
self.subject3 = subject3
@planning_id
def get_id(self):
return self.id
def __str__(self):
return (
f"Teacher("
f"id={self.id}, "
f"name={self.name}, "
f"subject={self.subject}, "
f"subject2={self.subject2}, "
f"subject3={self.subject3})"
)
and I added planning variable to the planning entity:
@planning_variable(Teacher, ["teacherRange"])
def get_teacher(self):
return self.teacher
def set_teacher(self, new_teacher):
self.teacher = new_teacher
But I don’t know how to continue make the algorithm to chose a teacher from a list and how to add the constraints of one teacher for one student group for any subject. Any help?
In order to make the algorithm to chose a teacher from a list, the list must be returned from a method on the @planning_solution
class annotated with the appropriate @value_range_provider
:
@planning_solution
class TimeTable:
def __init__(self, teacher_list, ..., score=None):
self.teacher_list = teacher_list
# ...
self.score = score
@problem_fact_collection_property(Teacher)
@value_range_provider("teacherRange")
def get_teacher_list(self):
return self.teacher_list
# rest of the class
For the constraint "one teacher for one student group for any subject.", I would rephrase it "consistent teacher for subject on student group" (i.e. if a lesson has the same student group and subject, it must also have the same teacher). Which would be written like this:
def consistent_teacher_for_subject_and_student_group(constraint_factory: ConstraintFactory):
return constraint_factory \
.forEach(LessonClass) \
.join(LessonClass,
[
# ... the same student group ...
Joiners.equal(lambda lesson: lesson.student_group),
# ... the same subject ...
Joiners.equal(lambda lesson: lesson.subject),
# form unique pairs
Joiners.lessThan(lambda lesson: lesson.id)
]) \
.filter(lambda lesson1, lesson2: lesson1.teacher != lesson2.teacher) \
.penalize("Different teacher for the same student group and subject", HardSoftScore.ONE_HARD)
For the constraint "teacher must be able to teach subject", you can use a filter:
def teacher_must_be_able_to_teach_subject(constraint_factory: ConstraintFactory):
return constraint_factory \
.forEach(LessonClass) \
.filter(lambda lesson: lesson.subject not in {lesson.teacher.subject, lesson.teacher.subject2, lesson.teacher.subject3}) \
.penalize("Teacher cannot teach subject", HardSoftScore.ONE_HARD)