0

This clarification makes a lot of sense to me, but if I try and apply the reasoning to the code below (which is based on the employee scheduling example available in optapy) I would have expected set_timeslot_list to be called when set_task is called but it does not look like it is.

The optimisation runs OK and finds a suitable set of tasks to assign to the list of time slots that I have available, but each task.timeslot_list remains empty, and looks like the set_timeslot_list method is never called.

I believe I am missing something..Can you please help me understand what is wrong with how I modified the example in the code below or with I am interpreting how shadow vars work? I can provide longer snippets, or the @planning_solution class if this is not sufficient.

@planning_entity(pinning_filter=timeslot_pinning_filter)
class Timeslot:
    def __init__(self, start: datetime.datetime = None, end: datetime.datetime = None,
             location: str = None, required_skill: str = None, task: object = None):
        self.start = start
        self.end = end
        self.location = location
        self.required_skill = required_skill
        self.task = task

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

        # The type of the planning variable is Task, but we cannot use it because task refers to Timeslot below.
        @planning_variable(object, value_range_provider_refs=['task_range'], nullable=False)
        def get_task(self):
            return self.task

        def set_task(self, task):
            self.task = task
    

@planning_entity
class Task:
    def __init__(self, name: str = None, duration: int = None, skill_set: list = None):
        self.name = name
        self.duration = duration
        self.skill_set = skill_set
        self.timeslot_list = [] #The shadow property, which is a list, can never be None. If no genuine variable references that shadow entity, then it is an empty list

    @inverse_relation_shadow_variable(Timeslot, source_variable_name = "task")
    def get_timeslot_list(self):
        return self.timeslot_list

    def set_timeslot_list(self, ts):
        self.timeslot_list = ts
Progman
  • 16,827
  • 6
  • 33
  • 48
Andrea
  • 5
  • 1

1 Answers1

0

Inverse Relation Shadow variables work differently than other variables: in particular, they directly modify the list returned by get_timeslot_list, so set_timeslot_list is never called. Your code look correct, which leaves me to believe you are checking the original planning entities and not the solution planning entities. In OptaPy (and OptaPlanner), the working solution/planning solution is cloned whenever we find a new best solution. As a result, the original problem (and the original planning entities) are never touched. So if your code look similar to this:

solver = optapy.solver_factory_create(...).buildSolver()
timeslot_list = [...]
task_1 = Task(...)
task_2 = Task(...)
task_list = [task_1, task_2]
problem = EmployeeSchedulingProblem(timeslot_list, task_list, ...)
solution = solver.solve(problem)
# this is incorrect; it prints the timeslot_list of the original problem
print(task_1.timeslot_list)

It should be changed to this instead:

solver = optapy.solver_factory_create(...).buildSolver()
timeslot_list = [...]
task_1 = Task(...)
task_2 = Task(...)
task_list = [task_1, task_2]
problem = EmployeeSchedulingProblem(timeslot_list, task_list, ...)
solution = solver.solve(problem)
# this is correct; it prints the timeslot_list of the solution
print(solution.task_list[0].timeslot_list)
Christopher Chianelli
  • 1,163
  • 1
  • 8
  • 8
  • Hi Christopher, thanks a lot for confirming that my code is correct. I am reading the `timeslot_list` from `solution` which then makes my results confusing. In the solution that I read `task_list` is correctly allocated, but `timeslot_list` remains empty. Would you have any suggestion to help me debug what is happening? – Andrea Jun 01 '22 at 11:58
  • That is confusing; I know it works since there is a test for correct behaviour ( https://github.com/optapy/optapy/blob/main/optapy-core/tests/test_inverse_relation.py ). I would need to see your `@planning_solution` class and see how you are calling the solver so I can try to reproduce locally. – Christopher Chianelli Jun 01 '22 at 15:34
  • Yep, it was the `@planning_solution` class - I did not mark correctly that `Task` (which has the inverse relation shadow var) is a `@optapy.planning_entity_collection_property(Task)`. Once I followed your link to the test I quickly figured it out. Thank you very much Christopher, you saved my day and your help has been highly appreciated. – Andrea Jun 01 '22 at 19:18