0

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?

Geoffrey De Smet
  • 26,223
  • 11
  • 73
  • 120
Hebbs
  • 17
  • 2

1 Answers1

1

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

Christopher Chianelli
  • 1,163
  • 1
  • 8
  • 8
  • Many thanks - it runs and tells me True or False in a log. However, the results don't change. Is this linked to the planning_entity being the Match and the Timeslot being the problem_fact. How would the jointed TimeslotClass's MatchClass get penalised here instead of the initial TimeslotClass which has not got a MatchClass (yet)!? – Hebbs Dec 07 '21 at 17:33
  • I think I know the problem. There are multiple pitches to play a match on at the same time. So I need to know if there are any empty pitches all at the same time, so ifNotExists(MatchClass) will not really work as I need to know if the Timeslot has 8 MatchClasses if I have 8 pitches. It gets more complicated a each pitch can have different timings, so I think I need to change the Timeslot class to include a pitch and generate Timeslots with pitches included (and drop the Pitch from the match and leave it only with the Timeslot). – Hebbs Dec 07 '21 at 20:26
  • I think you have a misunderstanding of how Constraint Streams work. Individual entities/problem facts are not penalized/rewarded. Instead, unique tuples are penalized (the tuples are created by the constraint factory). from_, groupBy and join modify the tuple. These tuples are then filtered by filters and joiners, and the ones that remain are penalized/rewarded. ifExists/ifNotExist do not modify the tuple and act as a filter. For this particular constraint, what being penalized is (timeslot1, timeslot2) where timeslot1 from from_ and timeslot2 from the join (provided it pass all the filters). – Christopher Chianelli Dec 08 '21 at 18:18
  • This particular constraint, as written above, reads "for each timeslot timeslot1 where there does not exist a match that has timeslot1 and there exists a timeslot timeslot2 where there exist a match that has timeslot2 and timeslot1 is before timeslot2, penalize the pair (timeslot1, timeslot2) by 10 soft." – Christopher Chianelli Dec 08 '21 at 18:23