740 lines
28 KiB
Python
740 lines
28 KiB
Python
# FILE: ietf/meeting/placement.py
|
|
#
|
|
# Copyright (c) 2013, The IETF Trust. See ../../../LICENSE.
|
|
#
|
|
# This file contains a model that encapsulates the progress of the automatic placer.
|
|
# Each step of placement is stored as a row in a table, not because this is necessary,
|
|
# but because it helps to debug things.
|
|
#
|
|
# A production run of the placer would do the same work, but simply not save anything.
|
|
#
|
|
|
|
import sys
|
|
|
|
from random import Random
|
|
from datetime import datetime
|
|
|
|
from django.db import models
|
|
#from settings import BADNESS_UNPLACED, BADNESS_TOOSMALL_50, BADNESS_TOOSMALL_100, BADNESS_TOOBIG, BADNESS_MUCHTOOBIG
|
|
#from ietf.meeting.models import Schedule, SchedTimeSessAssignment,TimeSlot,Room
|
|
from ietf.meeting.models import SchedTimeSessAssignment
|
|
from django.template.defaultfilters import slugify, date as date_format, time as time_format
|
|
|
|
def do_prompt():
|
|
print "waiting:"
|
|
sys.stdin.readline()
|
|
|
|
class PlacementException(Exception):
|
|
pass
|
|
|
|
# ScheduleSlot really represents a single column of time.
|
|
# The TimeSlot object would work here, but it associates a room.
|
|
# There is a special Schedule slot (subclass) which corresponds to unscheduled items.
|
|
class ScheduleSlot(object):
|
|
def __init__(self, daytime):
|
|
self.daytime = daytime
|
|
self.badness = None
|
|
self.slotgroups = {}
|
|
|
|
# this is a partial copy of SchedTimeSessAssignment's methods. Prune later.
|
|
#def __unicode__(self):
|
|
# return u"%s [%s<->%s]" % (self.schedule, self.session, self.timeslot)
|
|
#
|
|
#def __str__(self):
|
|
# return self.__unicode__()
|
|
|
|
def add_assignment(self,fs):
|
|
self.slotgroups[fs] = fs
|
|
|
|
def scheduled_session_pk(self, assignments):
|
|
things = []
|
|
slot1 = assignments.slot1
|
|
slot2 = assignments.slot2
|
|
for fs in self.slotgroups.iterkeys():
|
|
session = fs.session
|
|
if slot1 is not None and fs == slot1:
|
|
session = slot2.session
|
|
if slot2 is not None and fs == slot2:
|
|
session = slot1.session
|
|
if session is not None:
|
|
things.append((session.pk,fs))
|
|
return things
|
|
|
|
def recalc_badness1(self, assignments):
|
|
badness = 0
|
|
for fs,fs2 in self.slotgroups.iteritems():
|
|
if fs.session is not None:
|
|
num = fs.session.badness2(self)
|
|
#print "rc,,,,%s,%s,%u,recalc1" % (self.daytime, fs.session.short_name, num)
|
|
badness += num
|
|
self.badness = badness
|
|
|
|
def recalc_badness(self, assignments):
|
|
badness = 0
|
|
session_pk_list = self.scheduled_session_pk(assignments)
|
|
#print "rc,,,%u,slot_recalc" % (len(session_pk_list))
|
|
for pk,fs in session_pk_list:
|
|
#print "rc,,,,%u,%s,list" % (pk,fs.session)
|
|
if fs.session is not None:
|
|
num = fs.session.badness_fast(fs.timeslot, self, session_pk_list)
|
|
#print "rc,,,,%s,%s,%u,recalc0" % (self.daytime, fs.session.short_name, num)
|
|
badness += num
|
|
self.badness = badness
|
|
|
|
def calc_badness(self, assignments):
|
|
if self.badness is None:
|
|
self.recalc_badness(assignments)
|
|
return self.badness
|
|
|
|
#
|
|
# this subclass does everything a ScheduleSlot does, in particular it knows how to
|
|
# maintain and recalculate badness, but it also maintains a list of slots which
|
|
# are unplaced so as to accelerate finding things to place at the beginning of automatic placement.
|
|
#
|
|
# XXX perhaps this should be in the form an iterator?
|
|
#
|
|
class UnplacedScheduleSlot(ScheduleSlot):
|
|
def __init__(self):
|
|
super(UnplacedScheduleSlot, self).__init__(None)
|
|
self.unplaced_slot_numbers = []
|
|
self.unplaced_slots_finishcount = 0
|
|
|
|
def shuffle(self, generator):
|
|
generator.shuffle(self.unplaced_slot_numbers)
|
|
self.unplaced_slots_finishcount = self.count / 10
|
|
|
|
def finished(self):
|
|
if len(self.unplaced_slot_numbers) <= self.unplaced_slots_finishcount:
|
|
return True
|
|
else:
|
|
return False
|
|
|
|
@property
|
|
def count(self):
|
|
return len(self.unplaced_slot_numbers)
|
|
|
|
def add_assignment(self,fs):
|
|
super(UnplacedScheduleSlot, self).add_assignment(fs)
|
|
#print "unplaced add: %s" % (fs.available_slot)
|
|
self.unplaced_slot_numbers.append(fs.available_slot)
|
|
|
|
def get_unplaced_slot_number(self):
|
|
#print "unplaced slots: %s" % (self.unplaced_slot_numbers)
|
|
return self.unplaced_slot_numbers[0]
|
|
|
|
def delete_first(self):
|
|
del self.unplaced_slot_numbers[0]
|
|
|
|
|
|
class FakeSchedTimeSessAssignment(object):
|
|
"""
|
|
This model provides a fake (not-backed by database) N:M relationship between
|
|
Session and TimeSlot, but in this case TimeSlot is always None, because the
|
|
Session is not scheduled.
|
|
"""
|
|
faked = "fake"
|
|
|
|
def __init__(self, schedule):
|
|
self.extendedfrom = None
|
|
self.modified = None
|
|
self.notes = None
|
|
self.badness = None
|
|
self.available_slot = None
|
|
self.origss = None
|
|
self.timeslot = None
|
|
self.session = None
|
|
self.schedule = schedule
|
|
self.pinned = False
|
|
self.scheduleslot = None
|
|
|
|
def fromSchedTimeSessAssignment(self, ss): # or from another FakeSchedTimeSessAssignment
|
|
self.session = ss.session
|
|
self.schedule = ss.schedule
|
|
self.timeslot = ss.timeslot
|
|
self.modified = ss.modified
|
|
self.pinned = ss.pinned
|
|
self.origss = ss
|
|
|
|
def save(self):
|
|
pass
|
|
|
|
# this is a partial copy of SchedTimeSessAssignment's methods. Prune later.
|
|
def __unicode__(self):
|
|
return u"%s [%s<->%s]" % (self.schedule, self.session, self.timeslot)
|
|
|
|
def __str__(self):
|
|
return self.__unicode__()
|
|
|
|
@property
|
|
def room_name(self):
|
|
return "noroom"
|
|
|
|
@property
|
|
def special_agenda_note(self):
|
|
return self.session.agenda_note if self.session else ""
|
|
|
|
@property
|
|
def acronym(self):
|
|
if self.session and self.session.group:
|
|
return self.session.group.acronym
|
|
|
|
@property
|
|
def slot_to_the_right(self):
|
|
return None
|
|
|
|
@property
|
|
def acronym_name(self):
|
|
if not self.session:
|
|
return self.notes
|
|
if hasattr(self, "interim"):
|
|
return self.session.group.name + " (interim)"
|
|
elif self.session.name:
|
|
return self.session.name
|
|
else:
|
|
return self.session.group.name
|
|
|
|
@property
|
|
def session_name(self):
|
|
return self.session.name
|
|
|
|
@property
|
|
def area(self):
|
|
if not self.session or not self.session.group:
|
|
return ""
|
|
if self.session.group.type_id == "irtf":
|
|
return "irtf"
|
|
if self.timeslot.type_id == "plenary":
|
|
return "1plenary"
|
|
if not self.session.group.parent or not self.session.group.parent.type_id in ["area","irtf"]:
|
|
return ""
|
|
return self.session.group.parent.acronym
|
|
|
|
@property
|
|
def break_info(self):
|
|
return None
|
|
|
|
@property
|
|
def area_name(self):
|
|
if self.session and self.session.group and self.session.group.acronym == "edu":
|
|
return "Training"
|
|
elif not self.session or not self.session.group or not self.session.group.parent or not self.session.group.parent.type_id == "area":
|
|
return ""
|
|
return self.session.group.parent.name
|
|
|
|
@property
|
|
def isWG(self):
|
|
if not self.session or not self.session.group:
|
|
return False
|
|
if self.session.group.type_id == "wg" and self.session.group.state_id != "bof":
|
|
return True
|
|
|
|
@property
|
|
def group_type_str(self):
|
|
if not self.session or not self.session.group:
|
|
return ""
|
|
if self.session.group and self.session.group.type_id == "wg":
|
|
if self.session.group.state_id == "bof":
|
|
return "BOF"
|
|
else:
|
|
return "WG"
|
|
|
|
return ""
|
|
|
|
@property
|
|
def slottype(self):
|
|
return ""
|
|
|
|
@property
|
|
def empty_str(self):
|
|
# return JS happy value
|
|
if self.session:
|
|
return "False"
|
|
else:
|
|
return "True"
|
|
|
|
def json_dict(self, selfurl):
|
|
ss = dict()
|
|
ss['assignment_id'] = self.id
|
|
#ss['href'] = self.url(sitefqdn)
|
|
ss['empty'] = self.empty_str
|
|
ss['timeslot_id'] = self.timeslot.id
|
|
if self.session:
|
|
ss['session_id'] = self.session.id
|
|
ss['room'] = slugify(self.timeslot.location)
|
|
ss['roomtype'] = self.timeslot.type.slug
|
|
ss["time"] = date_format(self.timeslot.time, 'Hi')
|
|
ss["date"] = time_format(self.timeslot.time, 'Y-m-d')
|
|
ss["domid"] = self.timeslot.js_identifier
|
|
return ss
|
|
|
|
# this object maintains the current state of the placement tool.
|
|
# the assignments hash says where the sessions would go.
|
|
class CurrentScheduleState:
|
|
def __getitem__(self, key):
|
|
if key in self.tempdict:
|
|
return self.tempdict[key]
|
|
return self.current_assignments[key]
|
|
|
|
def __iter__(self):
|
|
return self.current_assignments.__iter__()
|
|
def iterkeys(self):
|
|
return self.current_assignments.__iter__()
|
|
|
|
def add_to_available_slot(self, fs):
|
|
size = len(self.available_slots)
|
|
if fs.session is not None:
|
|
fs.session.setup_conflicts()
|
|
|
|
time_column = None
|
|
needs_to_be_added = True
|
|
#print "adding fs for slot: %s" % (fs.timeslot)
|
|
if fs.timeslot is not None:
|
|
if fs.timeslot in self.fs_by_timeslot:
|
|
ofs = self.fs_by_timeslot[fs.timeslot]
|
|
#print " duplicate timeslot[%s], updating old one: %s" % (ofs.available_slot, fs.timeslot)
|
|
if ofs.session is None:
|
|
# keep the one with the assignment.
|
|
self.fs_by_timeslot[fs.timeslot] = fs
|
|
# get rid of old item
|
|
fs.available_slot = ofs.available_slot
|
|
self.available_slots[ofs.available_slot] = fs
|
|
needs_to_be_added = False
|
|
else:
|
|
self.fs_by_timeslot[fs.timeslot] = fs
|
|
|
|
# add the slot to the list of vertical slices.
|
|
time_column = self.timeslots[fs.timeslot.time]
|
|
#group_name = "empty"
|
|
#if fs.session is not None:
|
|
# group_name = fs.session.group.acronym
|
|
#print " inserting fs %s / %s to slot: %s" % (fs.timeslot.location.name,
|
|
# group_name,
|
|
# time_column.daytime)
|
|
fs.scheduleslot = time_column
|
|
if fs.session is None:
|
|
self.placed_scheduleslots.append(fs)
|
|
else:
|
|
time_column = self.unplaced_scheduledslots
|
|
fs.scheduleslot = self.unplaced_scheduledslots
|
|
|
|
if needs_to_be_added:
|
|
self.total_slots = size
|
|
self.available_slots.append(fs)
|
|
fs.available_slot = size
|
|
|
|
if time_column is not None:
|
|
# needs available_slot to be filled in
|
|
time_column.add_assignment(fs)
|
|
#print "adding item: %u to unplaced slots (pinned: %s)" % (fs.available_slot, fs.pinned)
|
|
|
|
def __init__(self, schedule, seed=None):
|
|
# initialize available_slots with the places that a session can go based upon the
|
|
# schedtimesessassignment objects of the provided schedule.
|
|
# for each session which is not initially scheduled, also create a schedtimesessassignment
|
|
# that has a session, but no timeslot.
|
|
|
|
self.recordsteps = True
|
|
self.debug_badness = False
|
|
self.lastSaveTime = datetime.now()
|
|
self.lastSaveStep = 0
|
|
self.verbose = False
|
|
|
|
# this maps a *group* to a list of (session,location) pairs, using FakeSchedTimeSessAssignment
|
|
self.current_assignments = {}
|
|
self.tempdict = {} # used when calculating badness.
|
|
|
|
# this contains an entry for each location, and each un-location in the form of
|
|
# (session,location) with the appropriate part None.
|
|
self.fs_by_timeslot = {}
|
|
self.available_slots = []
|
|
self.unplaced_scheduledslots = UnplacedScheduleSlot()
|
|
self.placed_scheduleslots = []
|
|
self.sessions = {}
|
|
self.total_slots = 0
|
|
|
|
self.schedule = schedule
|
|
self.meeting = schedule.meeting
|
|
self.seed = seed
|
|
self.badness = schedule.badness
|
|
self.random_generator=Random()
|
|
self.random_generator.seed(seed)
|
|
self.temperature = 10000000
|
|
self.stepnum = 1
|
|
self.timeslots = {}
|
|
self.slot1 = None
|
|
self.slot2 = None
|
|
|
|
# setup up array of timeslots objects
|
|
for timeslot in schedule.meeting.timeslot_set.filter(type = "session").all():
|
|
if not timeslot.time in self.timeslots:
|
|
self.timeslots[timeslot.time] = ScheduleSlot(timeslot.time)
|
|
fs = FakeSchedTimeSessAssignment(self.schedule)
|
|
fs.timeslot = timeslot
|
|
self.add_to_available_slot(fs)
|
|
self.timeslots[None] = self.unplaced_scheduledslots
|
|
|
|
# make list of things that need placement.
|
|
for sess in self.meeting.sessions_that_can_be_placed().all():
|
|
fs = FakeSchedTimeSessAssignment(self.schedule)
|
|
fs.session = sess
|
|
self.sessions[sess] = fs
|
|
self.current_assignments[sess.group] = []
|
|
|
|
#print "Then had %u" % (self.total_slots)
|
|
# now find slots that are not empty.
|
|
# loop here and the one for useableslots could be merged into one loop
|
|
allschedsessions = self.schedule.qs_assignments_with_sessions.filter(timeslot__type = "session").all()
|
|
for ss in allschedsessions:
|
|
# do not need to check for ss.session is not none, because filter above only returns those ones.
|
|
sess = ss.session
|
|
if not (sess in self.sessions):
|
|
#print "Had to create sess for %s" % (sess)
|
|
self.sessions[sess] = FakeSchedTimeSessAssignment(self.schedule)
|
|
fs = self.sessions[sess]
|
|
#print "Updating %s from %s(%s)" % (fs.session.group.acronym, ss.timeslot.location.name, ss.timeslot.time)
|
|
fs.fromSchedTimeSessAssignment(ss)
|
|
|
|
# if pinned, then do not consider it when selecting, but it needs to be in
|
|
# current_assignments so that conflicts are calculated.
|
|
if not ss.pinned:
|
|
self.add_to_available_slot(fs)
|
|
else:
|
|
del self.sessions[sess]
|
|
self.current_assignments[ss.session.group].append(fs)
|
|
|
|
# XXX can not deal with a session in two slots yet?!
|
|
|
|
# need to remove any sessions that might have gotten through above, but are in non-session
|
|
# places, otherwise these could otherwise appear to be unplaced.
|
|
allspecialsessions = self.schedule.qs_assignments_with_sessions.exclude(timeslot__type = "session").all()
|
|
for ss in allspecialsessions:
|
|
sess = ss.session
|
|
if sess is None:
|
|
continue
|
|
if (sess in self.sessions):
|
|
del self.sessions[sess]
|
|
|
|
# now need to add entries for those sessions which are currently unscheduled (and yet not pinned)
|
|
for sess,fs in self.sessions.iteritems():
|
|
if fs.timeslot is None:
|
|
#print "Considering sess: %s, and loc: %s" % (sess, str(fs.timeslot))
|
|
self.add_to_available_slot(fs)
|
|
|
|
#import pdb; pdb.set_trace()
|
|
# do initial badness calculation for placement that has been done
|
|
for daytime,scheduleslot in self.timeslots.iteritems():
|
|
scheduleslot.recalc_badness(self)
|
|
|
|
def dump_available_slot_state(self):
|
|
for fs in self.available_slots:
|
|
shortname="unplaced"
|
|
sessid = 0
|
|
if fs.session is not None:
|
|
shortname=fs.session.short_name
|
|
sessid = fs.session.id
|
|
pinned = "unplaced"
|
|
ssid=0
|
|
if fs.origss is not None:
|
|
pinned = fs.origss.pinned
|
|
ssid = fs.origss.id
|
|
print "%s: %s[%u] pinned: %s ssid=%u" % (fs.available_slot, shortname, sessid, pinned, ssid)
|
|
|
|
def pick_initial_slot(self):
|
|
if self.unplaced_scheduledslots.finished():
|
|
self.initial_stage = False
|
|
if self.initial_stage:
|
|
item = self.unplaced_scheduledslots.get_unplaced_slot_number()
|
|
slot1 = self.available_slots[item]
|
|
#print "item: %u points to %s" % (item, slot1)
|
|
else:
|
|
slot1 = self.random_generator.choice(self.available_slots)
|
|
return slot1
|
|
|
|
def pick_second_slot(self):
|
|
if self.initial_stage and len(self.placed_scheduleslots)>0:
|
|
self.random_generator.shuffle(self.placed_scheduleslots)
|
|
slot2 = self.placed_scheduleslots[0]
|
|
del self.placed_scheduleslots[0]
|
|
else:
|
|
slot2 = self.random_generator.choice(self.available_slots)
|
|
return slot2
|
|
|
|
def pick_two_slots(self):
|
|
slot1 = self.pick_initial_slot()
|
|
slot2 = self.pick_second_slot()
|
|
tries = 100
|
|
self.repicking = 0
|
|
# 1) no point in picking two slots which are the same.
|
|
# 2) no point in picking two slots which have no session (already empty)
|
|
# 3) no point in picking two slots which are both unscheduled sessions
|
|
# 4) limit outselves to ten tries.
|
|
while (slot1 == slot2 or slot1 is None or slot2 is None or
|
|
(slot1.session is None and slot2.session is None) or
|
|
(slot1.timeslot is None and slot2.timeslot is None)
|
|
) and tries > 0:
|
|
self.repicking += 1
|
|
#print "%u: .. repicking slots, had: %s and %s" % (self.stepnum, slot1, slot2)
|
|
slot1 = self.pick_initial_slot()
|
|
slot2 = self.pick_second_slot()
|
|
tries -= 1
|
|
if tries == 0:
|
|
raise PlacementException("How can it pick the same slot ten times in a row")
|
|
|
|
if slot1.pinned:
|
|
raise PlacementException("Should never attempt to move pinned slot1")
|
|
|
|
if slot2.pinned:
|
|
raise PlacementException("Should never attempt to move pinned slot2")
|
|
|
|
return slot1, slot2
|
|
|
|
# this assigns a session to a particular slot.
|
|
def assign_session(self, session, fslot, doubleup=False):
|
|
import copy
|
|
if session is None:
|
|
# we need to unschedule the session
|
|
session = fslot.session
|
|
self.tempdict[session.group] = []
|
|
return
|
|
|
|
if not session in self.sessions:
|
|
raise PlacementException("Is there a legit case where session is not in sessions here?")
|
|
|
|
oldfs = self.sessions[session]
|
|
# find the group mapping.
|
|
pairs = copy.copy(self.current_assignments[session.group])
|
|
#print "pairs is: %s" % (pairs)
|
|
if oldfs in pairs:
|
|
which = pairs.index(oldfs)
|
|
del pairs[which]
|
|
#print "new pairs is: %s" % (pairs)
|
|
|
|
self.sessions[session] = fslot
|
|
# now fix up the other things.
|
|
pairs.append(fslot)
|
|
self.tempdict[session.group] = pairs
|
|
|
|
def commit_tempdict(self):
|
|
for key,value in self.tempdict.iteritems():
|
|
self.current_assignments[key] = value
|
|
self.tempdict = dict()
|
|
|
|
# calculate badness of the columns which have changed
|
|
def calc_badness(self, slot1, slot2):
|
|
badness = 0
|
|
for daytime,scheduleslot in self.timeslots.iteritems():
|
|
oldbadness = scheduleslot.badness
|
|
if oldbadness is None:
|
|
oldbadness = 0
|
|
recalc=""
|
|
if slot1 is not None and slot1.scheduleslot == scheduleslot:
|
|
recalc="recalc slot1"
|
|
scheduleslot.recalc_badness(self)
|
|
if slot2 is not None and slot2.scheduleslot == scheduleslot:
|
|
recalc="recalc slot2"
|
|
scheduleslot.recalc_badness(self)
|
|
|
|
newbadness = scheduleslot.calc_badness(self)
|
|
if self.debug_badness:
|
|
print " calc: %s %u %u %s" % (scheduleslot.daytime, oldbadness, newbadness, recalc)
|
|
badness += newbadness
|
|
return badness
|
|
|
|
def try_swap(self):
|
|
badness = self.badness
|
|
slot1,slot2 = self.pick_two_slots()
|
|
if self.debug_badness:
|
|
print "start\n slot1: %s.\n slot2: %s.\n badness: %s" % (slot1, slot2,badness)
|
|
self.slot1 = slot1
|
|
self.slot2 = slot2
|
|
#import pdb; pdb.set_trace()
|
|
#self.assign_session(slot2.session, slot1, False)
|
|
#self.assign_session(slot1.session, slot2, False)
|
|
# self can substitute for current_assignments thanks to getitem() above.
|
|
newbadness = self.calc_badness(slot1, slot2)
|
|
if self.debug_badness:
|
|
print "end\n slot1: %s.\n slot2: %s.\n badness: %s" % (slot1, slot2, newbadness)
|
|
return newbadness
|
|
|
|
def log_step(self, accepted_str, change, dice, prob):
|
|
acronym1 = "empty"
|
|
if self.slot1.session is not None:
|
|
acronym1 = self.slot1.session.group.acronym
|
|
place1 = "nowhere"
|
|
if self.slot1.timeslot is not None:
|
|
place1 = str(self.slot1.timeslot.location.name)
|
|
|
|
acronym2= "empty"
|
|
if self.slot2.session is not None:
|
|
acronym2 = self.slot2.session.group.acronym
|
|
place2 = "nowhere"
|
|
if self.slot2.timeslot is not None:
|
|
place2 = str(self.slot2.timeslot.location.name)
|
|
initial = " "
|
|
if self.initial_stage:
|
|
initial = "init"
|
|
|
|
# note in logging: the swap has already occured, but the values were set before
|
|
if self.verbose:
|
|
print "% 5u:%s %s temp=%9u delta=%+9d badness=%10d dice=%.4f <=> prob=%.4f (repicking=%u) %9s:[%8s->%8s], %9s:[%8s->%8s]" % (self.stepnum, initial,
|
|
accepted_str, self.temperature,
|
|
change, self.badness, dice, prob,
|
|
self.repicking, acronym1, place2, place1, acronym2, place1, place2)
|
|
|
|
def do_step(self):
|
|
self.stepnum += 1
|
|
newbadness = self.try_swap()
|
|
if self.badness is None:
|
|
self.commit_tempdict
|
|
self.badness = newbadness
|
|
return True, 0
|
|
|
|
change = newbadness - self.badness
|
|
prob = self.calc_probability(change)
|
|
dice = self.random_generator.random()
|
|
|
|
#self.log_step("consider", change, dice, prob)
|
|
|
|
if dice < prob:
|
|
accepted_str = "accepted"
|
|
accepted = True
|
|
# swap things as planned
|
|
self.commit_tempdict
|
|
|
|
# actually do the swap in the FS
|
|
tmp = self.slot1.session
|
|
self.slot1.session = self.slot2.session
|
|
self.slot2.session = tmp
|
|
self.badness = newbadness
|
|
# save state object
|
|
else:
|
|
accepted_str = "rejected"
|
|
accepted = False
|
|
self.tempdict = dict()
|
|
|
|
self.log_step(accepted_str, change, dice, prob)
|
|
|
|
if accepted and not self.initial_stage:
|
|
self.temperature = self.temperature * 0.9995
|
|
|
|
return accepted, change
|
|
|
|
def calc_probability(self, change):
|
|
import math
|
|
return 1/(1 + math.exp(float(change)/self.temperature))
|
|
|
|
def delete_available_slot(self, number):
|
|
# because the numbers matter, we just None things out, and let repicking
|
|
# work on things.
|
|
#last = len(self.available_slots)-1
|
|
#if number < last:
|
|
# self.available_slots[number] = self.available_slots[last]
|
|
# self.available_slots[last].available_slot = number
|
|
#
|
|
#del self.available_slots[last]
|
|
self.available_slots[number] = None
|
|
|
|
def do_steps(self, limit=None, monitorSchedule=None):
|
|
print "do_steps(%s,%s)" % (limit, monitorSchedule)
|
|
if self.badness is None or self.badness == 0:
|
|
self.badness = self.schedule.calc_badness1(self)
|
|
self.oldbadness = self.badness
|
|
while (limit is None or self.stepnum < limit) and self.temperature > 1000:
|
|
accepted,change = self.do_step()
|
|
#set_prompt_wait(True)
|
|
if not accepted and self.initial_stage:
|
|
# randomize again!
|
|
self.unplaced_scheduledslots.shuffle(self.random_generator)
|
|
|
|
if accepted and self.initial_stage and self.unplaced_scheduledslots.count>0:
|
|
# delete it from available slots, so as not to leave unplaced slots
|
|
self.delete_available_slot(self.slot1.available_slot)
|
|
# remove initial slot from list.
|
|
self.unplaced_scheduledslots.delete_first()
|
|
|
|
if False and accepted and self.recordsteps:
|
|
ass1 = AutomaticScheduleStep()
|
|
ass1.schedule = self.schedule
|
|
if self.slot1.session is not None:
|
|
ass1.session = self.slot1.session
|
|
if self.slot1.origss is not None:
|
|
ass1.moved_to = self.slot1.origss
|
|
ass1.stepnum = self.stepnum
|
|
ass1.save()
|
|
ass2 = AutomaticScheduleStep()
|
|
ass2.schedule = self.schedule
|
|
if self.slot2.session is not None:
|
|
ass2.session = self.slot2.session
|
|
if self.slot2.origss is not None:
|
|
ass2.moved_to = self.slot2.origss
|
|
ass2.stepnum = self.stepnum
|
|
ass2.save()
|
|
#print "%u: accepted: %s change %d temp: %d" % (self.stepnum, accepted, change, self.temperature)
|
|
if (self.stepnum % 1000) == 0 and monitorSchedule is not None:
|
|
self.saveToSchedule(monitorSchedule)
|
|
print "Finished after %u steps, badness = %u->%u" % (self.stepnum, self.oldbadness, self.badness)
|
|
|
|
def saveToSchedule(self, targetSchedule):
|
|
when = datetime.now()
|
|
since = 0
|
|
rate = 0
|
|
if targetSchedule is None:
|
|
targetSchedule = self.schedule
|
|
else:
|
|
# XXX more stuff to do here, setup mapping, copy pinned items
|
|
pass
|
|
|
|
if self.lastSaveTime is not None:
|
|
since = when - self.lastSaveTime
|
|
if since.microseconds > 0:
|
|
rate = 1000 * float(self.stepnum - self.lastSaveStep) / (1000*since.seconds + since.microseconds / 1000)
|
|
print "%u: saved to schedule: %s %s elapsed=%s rate=%.2f" % (self.stepnum, targetSchedule.name, when, since, rate)
|
|
self.lastSaveTime = datetime.now()
|
|
self.lastSaveStep = self.stepnum
|
|
|
|
# first, remove all assignments in the schedule.
|
|
for ss in targetSchedule.assignments.all():
|
|
if ss.pinned:
|
|
continue
|
|
ss.delete()
|
|
|
|
# then, add new items for new placements.
|
|
for fs in self.available_slots:
|
|
if fs is None:
|
|
continue
|
|
ss = SchedTimeSessAssignment(timeslot = fs.timeslot,
|
|
schedule = targetSchedule,
|
|
session = fs.session)
|
|
ss.save()
|
|
|
|
def do_placement(self, limit=None, targetSchedule=None):
|
|
self.badness = self.schedule.calc_badness1(self)
|
|
if limit is None:
|
|
limitstr = "unlimited "
|
|
else:
|
|
limitstr = "%u" % (limit)
|
|
print "Initial stage (limit=%s) starting with: %u items to place" % (limitstr, self.unplaced_scheduledslots.count)
|
|
|
|
# permute the unplaced sessions
|
|
self.unplaced_scheduledslots.shuffle(self.random_generator)
|
|
|
|
self.initial_stage = True
|
|
monitorSchedule = targetSchedule
|
|
if monitorSchedule is None:
|
|
monitorSchedule = self.schedule
|
|
self.do_steps(limit, monitorSchedule)
|
|
self.saveToSchedule(targetSchedule)
|
|
|
|
#
|
|
# this does not clearly have value at this point.
|
|
# Not worth a migration/table yet.
|
|
#
|
|
if False:
|
|
class AutomaticScheduleStep(models.Model):
|
|
schedule = models.ForeignKey('Schedule', null=False, blank=False, help_text=u"Who made this agenda.")
|
|
session = models.ForeignKey('Session', null=True, default=None, help_text=u"Scheduled session involved.")
|
|
moved_from = models.ForeignKey('SchedTimeSessAssignment', related_name="+", null=True, default=None, help_text=u"Where session was.")
|
|
moved_to = models.ForeignKey('SchedTimeSessAssignment', related_name="+", null=True, default=None, help_text=u"Where session went.")
|
|
stepnum = models.IntegerField(default=0, blank=True, null=True)
|
|
|