Search code examples
pythonoptapy

School timetabling with Optapy - allow to choose teacher from a list for a lesson


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?


Solution

  • 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)