Search code examples
pythonoptapy

Constraint for curriculum to be met with Optapy - School timetabling optimization


I am using Optapy library in python and I want to add constraint that the curriculum needs to be met, since I get for now many unassigned lessons even though most of the timeslots and rooms are empty and the teacher resources are mostly unused. I tried to add the following constraint:

def curriculum_needs_to_be_met(constraint_factory):
return constraint_factory \
    .forEach(LessonClass) \
    .filter(lambda lesson: lesson.timeslot is None) \
    .penalize("Curriculum needs to be met", HardSoftScore.ONE_HARD)

But still I get many unassigned lessons. Any idea how can I define the curriculum constraint?


Solution

  • All variables should be assigned without a constraint. The only reason why they would be unassigned is either:

    1. You are checking the input problem (where all the lessons are unassigned) instead of the solution returned from solver.solve (where all the lessons should be assigned, unless the solver terminated before it found a feasible solution (in which case, the solver should be given more time)).

    2. You passed nullable=True to @planning_variable (i.e. the domain looks like this):

       @planning_entity
       class Lesson:
           def __init__(self, id, subject, teacher, student_group, timeslot=None, room=None):
               self.id = id
               self.subject = subject
               self.teacher = teacher
               self.student_group = student_group
               self.timeslot = timeslot
               self.room = room
      
           @planning_id
           def get_id(self):
               return self.id
      
           @planning_variable(Timeslot, ["timeslotRange"], nullable=True)
           def get_timeslot(self):
               return self.timeslot
      
           def set_timeslot(self, new_timeslot):
               self.timeslot = new_timeslot
      
           # ...
      

      which you do if you want to allow the variable to take a None value. However, this is probably not the case, since you don't want the variable to take a None value.

    Thus, I would double check you are using the solution instead of the problem. If you are using the solution and still see unassigned values, then I would update its TerminationConfiguration to give it more time. See the OptaPlanner docs for example termination configurations: https://www.optaplanner.org/docs/optaplanner/latest/optimization-algorithms/optimization-algorithms.html#termination ; You can create a new termination configuration in OptaPy like this:

    import optapy.config
    termination = optapy.config.solver.termination.TerminationConfig()
    termination.setBestScoreFeasible(True) # keep solving until the solution feasible
    solver_config = optapy.config.solver.SolverConfig() \
        .withTerminationConfig(termination) \
    # rest of SolverConfig setup