Search code examples
pythonoptaplanneroptapy

OptaPy isEqual isNotEqual in python


I'm trying to schedule sports matches into timeslots and would like to not have empty timeslots during the day (so finish as early as possible). I think isEqual and isNotEqual should help, but stuck in the syntax of the Python version. I think I'm close (relevant code below)

in domain.py

@problem_fact
class Timeslot:
    def __init__(self, id, match_date, date_str, start_time, end_time):
        self.id = id
        self.match_date = match_date
        self.date_str = date_str # java does not have date or datetime equivalent so str also needed
        self.start_time = start_time
        self.end_time = end_time

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

    def __str__(self):
        return (
                f"Timeslot("
                f"id={self.id}, "
                f"match_date={self.match_date}, "
                f"date_str={self.date_str}, "
                f"start_time={self.start_time}, "
                f"end_time={self.end_time})"

in constraints.py

def fill_pitches_from_start(constraint_factory):
    # A pitch should not be empty if possible
    return constraint_factory \
        .from_(TimeslotClass).ifNotExists(MatchClass, Joiners.equal(Timeslot.get_id(), Match.get_timeslot() ) )  \
        .join(TimeslotClass).ifExists(MatchClass, Joiners.equal(Timeslot.get_id(), Match.get_timeslot())) \
        .filter(lambda slot1, slot2: datetime.combine(slot1.date_str, slot1.timeslot.start_time) < datetime.combine(slot2.date_str, slot2.start_time) ) \
        .penalize("Pitch Empty with Later pitches populated", HardSoftScore.ofSoft(10))

This generates and expected error: TypeError: get_id() missing 1 required positional argument: 'self'

But I can't work out the correct syntax - perhaps using lambda?


Solution

  • You are close; this should work:

    def fill_pitches_from_start(constraint_factory):
    # A pitch should not be empty if possible
    return constraint_factory \
        .forEach(TimeslotClass).ifNotExists(MatchClass, Joiners.equal(lambda timeslot: timeslot.get_id(), lambda match: match.get_timeslot() ) )  \
        .join(TimeslotClass).ifExists(MatchClass, Joiners.equal(lambda timeslot1, timeslot2: timeslot2.get_id(), lambda match: match.get_timeslot())) \
        .filter(lambda slot1, slot2: datetime.combine(slot1.date_str, slot1.timeslot.start_time) < datetime.combine(slot2.date_str, slot2.start_time) ) \
        .penalize("Pitch Empty with Later pitches populated", HardSoftScore.ofSoft(10))
    

    It can probably be improved using Joiners.lessThan, but that would require an update to optapy first so Joiners can work with any Python type. (Will update this answer when optapy is updated to support said feature).