-1

Here is my problem to solve using optapy.

@problem_fact
class Room:
    id: int
    name: str

    def __init__(self, id, name):
        self.id = id
        self.name = name

    @planning_id
    def get_id(self):
        return self.id

    def __str__(self):
        return f"Room(id={self.id}, name={self.name})"


@problem_fact
class Timeslot:
    id: int
    day_of_week: str
    start_time: datetime.time
    end_time: datetime.time

    def __init__(self, id, day_of_week, start_time, end_time):
        self.id = id
        self.day_of_week = day_of_week
        self.start_time = start_time
        self.end_time = end_time
    
    
                       
    @planning_id
    def get_id(self):
        return self.id
    
    def set_value(self, id):
        self.id = id
        
    def __str__(self):
        return (
                f"Timeslot("
                f"id={self.id}, "
                f"day_of_week={self.day_of_week}, "
                f"start_time={self.start_time}, "
                f"end_time={self.end_time})"
        )
@problem_fact
class Base:
    pass

@planning_entity
class Slot(Base):
    def __init__(self, timeslot=None):
        self.timeslot = timeslot
        # self.timeslot1 = timeslot1
        
    @optapy.planning_variable(Base, value_range_provider_refs=['timeslotRangeLSEntity', 'timeslotRangeLBEntity'],
                              graph_type=PlanningVariableGraphType.CHAINED)
    def get_timeslot(self):
        return self.timeslot

    def set_timeslot(self, new_timeslot):
        self.timeslot = new_timeslot
    
   
@planning_entity
class Lesson(Slot):
    id: int
    subject: str
    teacher: str
    student_group: str
    timeslot: Timeslot
    room: Room

    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(Room, ["roomRange"])
    def get_room(self):
        return self.room

    def set_room(self, new_room):
        self.room = new_room
        
    def __str__(self):
        return (
            f"Lesson("
            f"id={self.id}, "
            f"timeslot={self.timeslot}, "
            f"room={self.room}, "
            f"teacher={self.teacher}, "
            f"subject={self.subject}, "
            f"student_group={self.student_group}"
            f")"
        )

@planning_entity
class Lab(Slot):
    id: int
    subject: str
    teacher: str
    student_group: str
    timeslot: Timeslot
    room: Room

    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(Room, ["roomRange"])
    def get_room(self):
        return self.room

    def set_room(self, new_room):
        self.room = new_room

    def __str__(self):
        return (
            f"Lab("
            f"id={self.id}, "
            f"timeslot={self.timeslot}, "
            f"room={self.room}, "
            f"teacher={self.teacher}, "
            f"subject={self.subject}, "
            f"student_group={self.student_group}"
            f")"
        )
def format_list(a_list):
    return ',\n'.join(map(str, a_list))


@planning_solution
class TimeTable:
    timeslot_list: list[Timeslot]
    # timeslot_list1: list[Timeslot]
    room_list: list[Room]
    lesson_list: list[Lesson]
    lab_list: list[Lab]
    score: HardSoftScore

    def __init__(self, timeslot_list, room_list, lesson_list,lab_list, score=None):
        self.timeslot_list = timeslot_list
        # self.timeslot_list1 = timeslot_list1
        self.room_list = room_list
        self.lesson_list = lesson_list
        self.lab_list = lab_list
        self.score = score
    

    @problem_fact_collection_property(Room)
    @value_range_provider("roomRange")
    def get_room_list(self):
        return self.room_list

    @planning_entity_collection_property(Slot)
    @value_range_provider("timeslotRangeLSEntity")
    def get_lesson_list(self):
        return self.lesson_list

    @planning_entity_collection_property(Slot)
    @value_range_provider("timeslotRangeLBEntity")
    def get_lab_list(self):
        return self.lab_list

    @planning_score(HardSoftScore)
    def get_score(self):
        return self.score

    def set_score(self, score):
        self.score = score

    def __str__(self):
        return (
            f"TimeTable("
            f"timeslot_list={format_list(self.timeslot_list)},\n"
            # f"timeslot_list1={format_list(self.timeslot_list1)},\n"
            f"room_list={format_list(self.room_list)},\n"
            f"lesson_list={format_list(self.lesson_list)},\n"
            f"lab_list={format_list(self.lab_list)},\n"
            f"score={str(self.score.toString()) if self.score is not None else 'None'}"
            f")"
        )


def generate_problem():
    timeslot_list = [
        Timeslot(1, "MONDAY", time(hour=8, minute=30), time(hour=9, minute=30)),
        Timeslot(2, "MONDAY", time(hour=9, minute=30), time(hour=10, minute=30)),
        Timeslot(3, "MONDAY", time(hour=10, minute=30), time(hour=11, minute=30)),
        Timeslot(4, "MONDAY", time(hour=13, minute=30), time(hour=14, minute=30)),
        Timeslot(5, "MONDAY", time(hour=14, minute=30), time(hour=15, minute=30)),
        Timeslot(6, "TUESDAY", time(hour=10, minute=30), time(hour=11, minute=30)),
        Timeslot(7, "TUESDAY", time(hour=13, minute=30), time(hour=14, minute=30)),
        Timeslot(8, "TUESDAY", time(hour=14, minute=30), time(hour=15, minute=30)),
        Timeslot(9, "TUESDAY", time(hour=15, minute=30), time(hour=16, minute=30)),
        Timeslot(10, "TUESDAY", time(hour=16, minute=30), time(hour=17, minute=30)),
        Timeslot(11, "MONDAY", time(hour=15, minute=30), time(hour=17, minute=30)),
        Timeslot(12, "TUESDAY", time(hour=8, minute=30), time(hour=10, minute=30))
    ]
    # timeslot_list1 = [
        
    # ]
    room_list = [
        Room(1, "Room A"),
        Room(2, "Room B"),
        Room(3, "Room C"),
        Room(4, "Room D")
    ]
    
    lesson_list = [
        Lesson(1, "Math", "A. Turing", "9th grade"),
        Lesson(2, "Math", "A. Turing", "9th grade"),
        Lesson(3, "Physics", "M. Curie", "9th grade"),
        Lesson(4, "Chemistry", "M. Curie", "9th grade"),
        Lesson(5, "Biology", "C. Darwin", "9th grade"),
        Lesson(6, "History", "I. Jones", "9th grade"),
        Lesson(7, "English", "I. Jones", "9th grade"),
        Lesson(8, "English", "I. Jones", "9th grade"),
        Lesson(9, "Spanish", "P. Cruz", "9th grade"),
        Lesson(10, "Spanish", "P. Cruz", "9th grade"),
        Lesson(11, "Math", "A. Turing", "10th grade"),
        Lesson(12, "Math", "A. Turing", "10th grade"),
        Lesson(13, "Math", "A. Turing", "10th grade"),
        Lesson(14, "Physics", "M. Curie", "10th grade"),
        Lesson(15, "Chemistry", "M. Curie", "10th grade"),
        Lesson(16, "French", "M. Curie", "10th grade"),
        Lesson(17, "Geography", "C. Darwin", "10th grade"),
        Lesson(18, "History", "I. Jones", "10th grade"),
        Lesson(19, "English", "P. Cruz", "10th grade"),
        Lesson(20, "Spanish", "P. Cruz", "10th grade")
        
    ]
    
    lab_list = [
        Lab(21, "LAB", "Selva", "9th grade"),
        Lab(22, "LAB", "Selva", "10th grade")
    ]
    
    lesson = lesson_list[0]
    lesson.set_timeslot(timeslot_list[0])
    lesson.set_room(room_list[0])
    
    lab = lab_list[0]
    lab.set_timeslot(timeslot_list[10])
    lab.set_room(room_list[3])

    return TimeTable(timeslot_list, room_list, lesson_list,lab_list)

Here I used Base class(@problem_fact) and extended the abstract class as Slot(@planning_entity). Here I declared the timeslot as a common for two @planning_entity(one is Lab another one is Lesson). I am passing 2 different timeslot range for those two @planning_entity.

When i used to solve this problem i am getting below error!

java.lang.IllegalStateException: Error occurred when wrapping object (Lab(id=21, timeslot=Timeslot(id=11, day_of_week=MONDAY, start_time=15:30:00, end_time=17:30:00), room=Room(id=4, name=Room D), teacher=Selva, subject=LAB, student_group=9th grade)).

Kindly help me to solve this! Thanks

VijiS
  • 1
  • 2

2 Answers2

1

Your model is incorrect: you have a chained @planning_variable in Slot without an anchor value range ('timeslotRangeLSEntity' and 'timeslotRangeLBEntity' correspond to Lesson and Lab respectively, which are both planning entities that extend Slot, which cannot be used as an anchor). I am guessing you want TimeSlot to be your anchor. However, currently, it does not extend Base, so you need to modify TimeSlot to extend Base.

However, you are also hitting an OptaPlanner bug: having multiple planning entity classes in a chained model will throw a class cast exception (https://issues.redhat.com/browse/PLANNER-2798). Until the bug is fixed, you need to workaround it. What this mean in your scenario is that Lab and Lesson need to be merged into one class, and in order to distinguish them, you need to use a field on the class. This modified domain should work:

import datetime
from datetime import time

import optapy
import optapy.config
from optapy.score import HardSoftScore
from optapy.types import PlanningVariableGraphType

@optapy.problem_fact
class Room:
    id: int
    name: str

    def __init__(self, id, name):
        self.id = id
        self.name = name

    @optapy.planning_id
    def get_id(self):
        return self.id

    def __str__(self):
        return f"Room(id={self.id}, name={self.name})"


@optapy.problem_fact
class Base:
    pass


@optapy.problem_fact
class Timeslot(Base):
    id: int
    day_of_week: str
    start_time: datetime.time
    end_time: datetime.time

    def __init__(self, id, day_of_week, start_time, end_time):
        self.id = id
        self.day_of_week = day_of_week
        self.start_time = start_time
        self.end_time = end_time
    
                       
    @optapy.planning_id
    def get_id(self):
        return self.id
    
    def set_value(self, id):
        self.id = id
        
    def __str__(self):
        return (
                f"Timeslot("
                f"id={self.id}, "
                f"day_of_week={self.day_of_week}, "
                f"start_time={self.start_time}, "
                f"end_time={self.end_time})"
        )

    
   
@optapy.planning_entity
class LessonOrLab(Base):
    id: int
    kind: str
    subject: str
    teacher: str
    student_group: str
    timeslot: Base
    room: Room

    def __init__(self, id, kind, 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
    
    @optapy.planning_id
    def get_id(self):
        return self.id
    
    @optapy.planning_variable(Base, value_range_provider_refs=['timeslotRange', 'lessonOrLabRange'],
                              graph_type=PlanningVariableGraphType.CHAINED)
    def get_timeslot(self):
        return self.timeslot

    def set_timeslot(self, new_timeslot):
        self.timeslot = new_timeslot

    @optapy.planning_variable(Room, ["roomRange"])
    def get_room(self):
        return self.room

    def set_room(self, new_room):
        self.room = new_room
        
    def __str__(self):
        return (
            f"LessonOrLab("
            f"id={self.id}, "
            f"kind={self.kind}, "
            f"timeslot={self.timeslot}, "
            f"room={self.room}, "
            f"teacher={self.teacher}, "
            f"subject={self.subject}, "
            f"student_group={self.student_group}"
            f")"
        )

def format_list(a_list):
    return ',\n'.join(map(str, a_list))


@optapy.planning_solution
class TimeTable:
    timeslot_list: list[Timeslot]
    room_list: list[Room]
    lesson_or_lab_list: list[LessonOrLab]
    score: HardSoftScore

    def __init__(self, timeslot_list, room_list, lesson_or_lab_list, score=None):
        self.timeslot_list = timeslot_list
        self.room_list = room_list
        self.lesson_or_lab_list = lesson_or_lab_list
        self.score = score
    

    @optapy.problem_fact_collection_property(Room)
    @optapy.value_range_provider("roomRange")
    def get_room_list(self):
        return self.room_list

    @optapy.problem_fact_collection_property(Timeslot)
    @optapy.value_range_provider("timeslotRange")
    def get_timeslot_list(self):
        return self.timeslot_list

    @optapy.planning_entity_collection_property(LessonOrLab)
    @optapy.value_range_provider("lessonOrLabRange")
    def get_lesson_list(self):
        return self.lesson_or_lab_list

    @optapy.planning_score(HardSoftScore)
    def get_score(self):
        return self.score

    def set_score(self, score):
        self.score = score

    def __str__(self):
        return (
            f"TimeTable("
            f"timeslot_list={format_list(self.timeslot_list)},\n"
            f"room_list={format_list(self.room_list)},\n"
            f"lesson_or_lab_list={format_list(self.lesson_or_lab_list)},\n"
            f"score={str(self.score.toString()) if self.score is not None else 'None'}"
            f")"
        )


def generate_problem():
    timeslot_list = [
        Timeslot(1, "MONDAY", time(hour=8, minute=30), time(hour=9, minute=30)),
        Timeslot(2, "MONDAY", time(hour=9, minute=30), time(hour=10, minute=30)),
        Timeslot(3, "MONDAY", time(hour=10, minute=30), time(hour=11, minute=30)),
        Timeslot(4, "MONDAY", time(hour=13, minute=30), time(hour=14, minute=30)),
        Timeslot(5, "MONDAY", time(hour=14, minute=30), time(hour=15, minute=30)),
        Timeslot(6, "TUESDAY", time(hour=10, minute=30), time(hour=11, minute=30)),
        Timeslot(7, "TUESDAY", time(hour=13, minute=30), time(hour=14, minute=30)),
        Timeslot(8, "TUESDAY", time(hour=14, minute=30), time(hour=15, minute=30)),
        Timeslot(9, "TUESDAY", time(hour=15, minute=30), time(hour=16, minute=30)),
        Timeslot(10, "TUESDAY", time(hour=16, minute=30), time(hour=17, minute=30)),
        Timeslot(11, "MONDAY", time(hour=15, minute=30), time(hour=17, minute=30)),
        Timeslot(12, "TUESDAY", time(hour=8, minute=30), time(hour=10, minute=30))
    ]
    # timeslot_list1 = [
        
    # ]
    room_list = [
        Room(1, "Room A"),
        Room(2, "Room B"),
        Room(3, "Room C"),
        Room(4, "Room D")
    ]
    
    lesson_or_lab_list = [
        LessonOrLab(1, "Lesson", "Math", "A. Turing", "9th grade"),
        LessonOrLab(2, "Lesson", "Math", "A. Turing", "9th grade"),
        LessonOrLab(3, "Lesson", "Physics", "M. Curie", "9th grade"),
        LessonOrLab(4, "Lesson", "Chemistry", "M. Curie", "9th grade"),
        LessonOrLab(5, "Lesson", "Biology", "C. Darwin", "9th grade"),
        LessonOrLab(6, "Lesson", "History", "I. Jones", "9th grade"),
        LessonOrLab(7, "Lesson", "English", "I. Jones", "9th grade"),
        LessonOrLab(8, "Lesson", "English", "I. Jones", "9th grade"),
        LessonOrLab(9, "Lesson", "Spanish", "P. Cruz", "9th grade"),
        LessonOrLab(10, "Lesson", "Spanish", "P. Cruz", "9th grade"),
        LessonOrLab(11, "Lesson", "Math", "A. Turing", "10th grade"),
        LessonOrLab(12, "Lesson", "Math", "A. Turing", "10th grade"),
        LessonOrLab(13, "Lesson", "Math", "A. Turing", "10th grade"),
        LessonOrLab(14, "Lesson", "Physics", "M. Curie", "10th grade"),
        LessonOrLab(15, "Lesson", "Chemistry", "M. Curie", "10th grade"),
        LessonOrLab(16, "Lesson", "French", "M. Curie", "10th grade"),
        LessonOrLab(17, "Lesson", "Geography", "C. Darwin", "10th grade"),
        LessonOrLab(18, "Lesson", "History", "I. Jones", "10th grade"),
        LessonOrLab(19, "Lesson", "English", "P. Cruz", "10th grade"),
        LessonOrLab(20, "Lesson", "Spanish", "P. Cruz", "10th grade"),
        LessonOrLab(21, "Lab", "LAB", "Selva", "9th grade"),
        LessonOrLab(22, "Lab", "LAB", "Selva", "10th grade")
    ]
    
    lesson = lesson_or_lab_list[0]
    lesson.set_timeslot(timeslot_list[0])
    lesson.set_room(room_list[0])
    
    lab = lesson_or_lab_list[-2]
    lab.set_timeslot(timeslot_list[10])
    lab.set_room(room_list[3])

    return TimeTable(timeslot_list, room_list, lesson_or_lab_list)
Christopher Chianelli
  • 1,163
  • 1
  • 8
  • 8
  • Thanks for your reply! @Christopher. But when i am running the above code, it is getting error like the following. AttributeError: 'proxy.LessonOrLab' object has no attribute 'day_of_week'. – VijiS Oct 21 '22 at 14:09
  • That exception does not come from the above code; that is from your constraints (which I don't see in the current question). Add a day_of_week field to 'LessonOrLab' and set it in its __init__. My guess is you are using `timeslot` somewhere in your constraints, which can be either `Timeslot` (which has `day_of_week`) OR `LessonOrLab` (which does not currently have `day_of_week`). – Christopher Chianelli Oct 21 '22 at 15:34
0

I executed your Domain (Christopher) with the following constraints:

    def room_conflict(constraint_factory: ConstraintFactory):
        # A room can accommodate at most one lesson at the same time.
        return constraint_factory \
            .for_each(LessonOrLab) \
            .join(LessonOrLab,
                  # ... in the same timeslot ...
                  Joiners.equal(lambda lesson: lesson.timeslot),
                  # ... in the same room ...
                  Joiners.equal(lambda lesson: lesson.room),
                  # form unique pairs
                  Joiners.less_than(lambda lesson: lesson.id)
                  ) \
            .penalize("Room conflict", HardSoftScore.ONE_HARD)
    
    
    def teacher_conflict(constraint_factory: ConstraintFactory):
        # A teacher can teach at most one lesson at the same time.
        return constraint_factory \
            .for_each(LessonOrLab) \
            .join(LessonOrLab,
                  Joiners.equal(lambda lesson: lesson.timeslot),
                  Joiners.equal(lambda lesson: lesson.teacher),
                  Joiners.less_than(lambda lesson: lesson.id)
                  ) \
            .penalize("Teacher conflict", HardSoftScore.ONE_HARD)
    
    
    def student_group_conflict(constraint_factory: ConstraintFactory):
        # A student can attend at most one lesson at the same time.
        return constraint_factory \
            .for_each(LessonOrLab) \
            .join(LessonOrLab,
                  Joiners.equal(lambda lesson: lesson.timeslot),
                  Joiners.equal(lambda lesson: lesson.student_group),
                  Joiners.less_than(lambda lesson: lesson.id)
                  ) \
            .penalize("Student group conflict", HardSoftScore.ONE_HARD)

However, the solution just assign two LessonOrLab:

|------------|------------|------------|------------|------------|
|            | Room A     | Room B     | Room C     | Room D     | 
|------------|------------|------------|------------|------------|
| MON 08:30: | LAB        |            |            |            | 
|            | Selva      |            |            |            | 
|            | 10th grade |            |            |            | 
|------------|------------|------------|------------|------------|
| MON 09:30: |            |            |            |            | 
|            |            |            |            |            | 
|            |            |            |            |            | 
|------------|------------|------------|------------|------------|
| MON 10:30: |            |            |            |            | 
|            |            |            |            |            | 
|            |            |            |            |            | 
|------------|------------|------------|------------|------------|
| MON 13:30: |            |            |            |            | 
|            |            |            |            |            | 
|            |            |            |            |            | 
|------------|------------|------------|------------|------------|
| MON 14:30: |            |            |            |            | 
|            |            |            |            |            | 
|            |            |            |            |            | 
|------------|------------|------------|------------|------------|
| TUE 10:30: |            |            |            |            | 
|            |            |            |            |            | 
|            |            |            |            |            | 
|------------|------------|------------|------------|------------|
| TUE 13:30: |            |            |            |            | 
|            |            |            |            |            | 
|            |            |            |            |            | 
|------------|------------|------------|------------|------------|
| TUE 14:30: |            |            |            |            | 
|            |            |            |            |            | 
|            |            |            |            |            | 
|------------|------------|------------|------------|------------|
| TUE 15:30: |            |            |            |            | 
|            |            |            |            |            | 
|            |            |            |            |            | 
|------------|------------|------------|------------|------------|
| TUE 16:30: |            |            |            |            | 
|            |            |            |            |            | 
|            |            |            |            |            | 
|------------|------------|------------|------------|------------|
| MON 15:30: |            |            |            | LAB        | 
|            |            |            |            | P. Cruz    | 
|            |            |            |            | 10th grade | 
|------------|------------|------------|------------|------------|
| TUE 08:30: |            |            |            |            | 
|            |            |            |            |            | 
|            |            |            |            |            | 
|------------|------------|------------|------------|------------|
  • Did you solve the problem (i.e use `solver = solver_factory_create(solver_config).buildSolver()`, then do `solution = solver.solve(problem)`? Do note OptaPlanner/OptaPy does not modify the original problem; it will return a new solution. The reason I am asking is the two lessons/labs the are assigned are the ones that are initially assigned in `generate_problem()`, leaving me to believe either solving did not occur, or you are printing the original (unchanged) problem instead of the found solution; OptaPlanner/OptaPy will assign all entities since no variables are nullable in the above domain. – Christopher Chianelli Oct 23 '22 at 03:26
  • Yes, this was the code for that: ``` python solver_config = SolverConfig().withEntityClasses(LessonOrLab) \ .withSolutionClass(TimeTable) \ .withConstraintProviderClass(define_constraints) \ .withTerminationSpentLimit(Duration.ofSeconds(30)) solver = solver_factory_create(solver_config).buildSolver() solution = solver.solve(generate_problem()) ``` – Alejandro Gómez Montoya Oct 24 '22 at 14:52
  • After debugging the solution result, I found that the timeslot for the others LessonOrLab instances are nested LessonOrLab prev instances: LessonOrLab(id=25, kind=Lab, timeslot=LessonOrLab(id=26, kind=Lab, timeslot=LessonOrLab(id=27, kind=Lab, timeslot=Timeslot(id=1, day_of_week=MONDAY, start_time=08:30:00, end_time=09:30:00), room=Room(id=1, name=Room A), teacher=Selva, subject=LAB 2, student_group=10th grade), room=Room(id=1, name=Room A), teacher=P. Cruz, subject=LAB, student_group=10th grade), room=Room(id=1, name=Room A), teacher=P. Cruz, subject=LAB, student_group=10th grade) – Alejandro Gómez Montoya Oct 24 '22 at 14:57
  • But this ignore the timeslot available sets. What is the way to manage this in the constraints? – Alejandro Gómez Montoya Oct 24 '22 at 15:01
  • 1
    Correct; that expected given the above model: the above model form a chained list of LessonOrLab with the head of the list being a Timeslot. If a constraint does not incentivize having multiple chains, all the LessonOrLab can be placed in a single linked list pointing to a single timeslot. I do find the above domain odd (having both timeslots and a chained model). The issue here is `Joiner.equal(lesson.timeslot)` will never be true (since in chained models, an entity/anchor can be the "previous" for at most 1 entity). – Christopher Chianelli Oct 24 '22 at 19:41
  • If you want to keep the current domain, and want to match on the timeslot at the head of the list, you could use an anchor shadow variable (https://www.optapy.org/docs/latest/shadow-variable/shadow-variable.html#anchorShadowVariable). Although I would probably recommend a different domain depending on your requirements. – Christopher Chianelli Oct 24 '22 at 19:43