Improved initial session scheduling for tight schedules

- Legacy-Id: 17904
This commit is contained in:
Sasha Romijn 2020-06-01 12:26:21 +00:00
parent 2c49e7b2dd
commit fc2693370e

View file

@ -140,7 +140,11 @@ class Schedule(object):
""" """
Check the number of sessions, their required capacity and duration against availability. Check the number of sessions, their required capacity and duration against availability.
If there are too many sessions, the generator exits. If there are too many sessions, the generator exits.
If sessions can't fit, they are trimmed, and a fixed cost is applied. If sessions can't fit, they are trimmed, and a fixed cost is applied.
Note that the trim is only applied on the in-memory object. The purpose
of trimming in advance is to prevent the optimiser from trying to resolve
a constraint that can never be resolved.
""" """
if len(self.sessions) > len(self.timeslots): if len(self.sessions) > len(self.timeslots):
raise CommandError('More sessions ({}) than timeslots ({})' raise CommandError('More sessions ({}) than timeslots ({})'
@ -218,6 +222,9 @@ class Schedule(object):
- Second: shortest duration that still fits - Second: shortest duration that still fits
- Third: smallest room that still fits - Third: smallest room that still fits
If there are multiple options with equal value, a random one is picked. If there are multiple options with equal value, a random one is picked.
For initial scheduling, it is not a hard requirement that the timeslot is long
or large enough, though that will be preferred due to the lower cost.
""" """
if self.verbosity >= 2: if self.verbosity >= 2:
self.stdout.write('== Initial scheduler starting, scheduling {} sessions in {} timeslots ==' self.stdout.write('== Initial scheduler starting, scheduling {} sessions in {} timeslots =='
@ -225,14 +232,9 @@ class Schedule(object):
sessions = sorted(self.sessions, key=lambda s: s.complexity, reverse=True) sessions = sorted(self.sessions, key=lambda s: s.complexity, reverse=True)
for session in sessions: for session in sessions:
possible_slots = [t for t in self.timeslots if possible_slots = [t for t in self.timeslots if t not in self.schedule.keys()]
t not in self.schedule.keys() and session.fits_in_timeslot(t)]
random.shuffle(possible_slots) random.shuffle(possible_slots)
if not len(possible_slots):
# TODO: this needs better handling
raise Exception('No timeslot was left for {} ({})'
.format(session.group, session.session_pk))
def timeslot_preference(t): def timeslot_preference(t):
proposed_schedule = self.schedule.copy() proposed_schedule = self.schedule.copy()
proposed_schedule[t] = session proposed_schedule[t] = session
@ -339,8 +341,6 @@ class Schedule(object):
break break
def _schedule_session(self, session, timeslot): def _schedule_session(self, session, timeslot):
if not session.fits_in_timeslot(timeslot):
raise ValueError
self.schedule[timeslot] = session self.schedule[timeslot] = session
def _cost_for_switch(self, timeslot1, timeslot2): def _cost_for_switch(self, timeslot1, timeslot2):
@ -555,6 +555,14 @@ class Session(object):
violations, cost = [], 0 violations, cost = [], 0
overlapping_sessions = tuple(overlapping_sessions) overlapping_sessions = tuple(overlapping_sessions)
if self.attendees > my_timeslot.capacity:
violations.append('{}: scheduled scheduled in too small room'.format(self.group))
cost += self.business_constraint_costs['session_requires_trim']
if self.requested_duration > my_timeslot.duration:
violations.append('{}: scheduled scheduled in too short timeslot'.format(self.group))
cost += self.business_constraint_costs['session_requires_trim']
if my_timeslot.time_group in self.timeranges_unavailable: if my_timeslot.time_group in self.timeranges_unavailable:
violations.append('{}: scheduled in unavailable timerange {}' violations.append('{}: scheduled in unavailable timerange {}'
.format(self.group, my_timeslot.time_group)) .format(self.group, my_timeslot.time_group))