module Schedule; from Databases import CourseDB, Course, CourseTitle, Section, ChosenTimeArrangements, TimeArrangement, InstructorDB, Instructor, Name, ID, Handicapped, InstructorCoursePreferenceDB, InstructorCoursePreference, InstructorTimePreferenceDB, InstructorTimePreference, AvailBlock, Days, Day, ClassroomDB, Classroom, Room, BuildingNumber, RoomNumber, HandicapCapable, LabCapable, ScheduleDB, ScheduleRecord, CallNum, Rating, Time, TimeBlock, Hour, Minute, AmOrPm, AM, PM; function generateSchedule is in: cdb:CourseDB, idb:InstructorDB, icrsdb:InstructorCoursePreferenceDB, itimedb:InstructorTimePreferenceDB, cldb:ClassroomDB, sdb:ScheduleDB; out: sdb':ScheduleDB; precondition: (* Nothing is currently scheduled *) #sdb = 0; postcondition: (* The best schedule is created *) exists (sdb' in allSchedules(cdb,idb,icrsdb,cldb)) ( forall (sdb in allSchedules(cdb,idb,icrsdb,cldb)) ( averageRating(sdb',sdb',cdb,idb,cldb,icrsdb,itimedb) >= averageRating(sdb,sdb,cdb,idb,cldb,icrsdb,itimedb) ) ); end generateSchedule; function allSchedules is in: cdb:CourseDB, idb:InstructorDB, icrsdb:InstructorCoursePreferenceDB, cldb:ClassroomDB; out: sdbl':ScheduleDB*; precondition: (* None *); postcondition: forall (sdb in sdbl') ( (* all courses have a schedule record *) forall (c in cdb.cl) ( exists (sr1 in sdb) ( (sr1.title = c.title) and (sr1.sec = c.sec) ) ) and forall (sr in sdb) ( (* The course for the scheduleRecord is in the * courseDB and the scheduleRecord type is a type * which is deemed valid by the time arrangements * defined for the course *) exists (crs in cdb) ( (sr.sec = crs.sec) and (sr.title = crs.title) and (* a time arrangement exists for the course which has lecture * and lab hours which match the hours assigned to the schedule * record *) exists (ta in crs.tas) ( ( (* the course has a lecture portion *) (sr.is_lec = true) and (* the time arrangement has lecture hours specified *) (ta.lec_hrs > 0) and (* the number of lecture minutes for the schedule record * matches that of the time arrangement *) (timeLength(sr.lec_start, sr.lec_stop) = ta.lec_hrs * 60) and (* lecture start time is between 7 AM and 10 PM *) (isValidStartTime(sr.lec_start)) and (* lecture stop time is between 8 AM and 11 PM *) (isValidStopTime(sr.lec_stop)) and ( (* a lecture room is specified and is in the database *) (sr.lec_rm != nil) and exists (lec_rm in cldb) ( (lec_rm.rm.bn = sr.lec_rm.bn) and (lec_rm.rm.rn = sr.lec_rm.rn) ) or (* no lecture room is specified, TBA Condition *) (sr.lec_rm = nil) ) or (* the course has no lecture portion *) (sr.is_lec = false) and (ta.lec_hrs = 0) and (sr.lec_start = nil) and (sr.lec_stop = nil) and (sr.lec_rm = nil) ) and ( (* the course has a lab portion *) (sr.is_lab = true) and (* the time arrangement has lab hours specified *) (ta.lab_hrs > 0) and (* the of lab minutes for the schedule record matches * that of the time arrangement *) (timeLength(sr.lab_start, sr.lab_stop) = ta.lab_hrs * 60) and (* lab start time is between 7 AM and 10 PM *) (isValidStartTime(sr.lab_start)) and (* lab stop time is between 8 AM and 11 PM *) (isValidStopTime(sr.lab_stop)) and ( (* a lab room is specified and is in the database *) (sr.lab_rm != nil) and exists (lab_rm in cldb) ( (lab_rm.rm.bn = sr.lab_rm.bn) and (lab_rm.rm.rn = sr.lab_rm.rn) and (lab_rm.is_lab = true) ) or (* no lab room is specified, TBA Condition *) (sr.lab_rm = nil) ) or (* the course has no lab portion *) (sr.is_lab = false) and (ta.lab_hrs = 0) and (sr.lab_start = nil) and (sr.lab_stop = nil) and (sr.lab_rm = nil) ) and (* if lab and lecture are supposed to be held at adjacent times * then the time difference between the end of lecture and the start * of lab should be 10 minutes *) if ( (sr.is_lec = true) and (sr.is_lab = true) and (ta.lab_and_lec_linked = true) ) then ( timeLength(sr.lec_stop,sr.lab_start) = 10 ) ) ) and (* The call number is unique *) forall (sr2 in sdb) ( if (sr.title != sr2.title and sr.sec != sr2.sec) then (sr.call_num != sr2.call_num) ) and (* The assigned instructor is in the InstructorDB *) exists (instr in idb) ( (instr.id = sr.instr_id)) and (* The assigned instructor is capable of teaching the course *) exists (crspref in icrsdb) ( (crspref.instr_id = instr.id) and (crspref.title = sr.title) and (crspref.rating > 0) ) ) ); end allSchedules; function averageRating is description: (* Recursively determines the average rating for all * scheduleRecords in a ScheduleDB. sdb_total is the entire * scheduleDB which is used for evaluating criteria D ratings, * and sdb_selection is used for recursion *); in: sdb_total:ScheduleDB, sdb_selection:ScheduleDB, cdb:CourseDB, idb:InstructorDB, cldb:ClassroomDB, icrsdb:InstructorCoursePreferenceDB, itimedb:InstructorTimePreferenceDB; out: rating':Rating; pre: (* None *); post: (* *) if ( #sdb_selection = 0) then ( rating' = 0) else ( rating' = (recordRating(sdb_total,sdb_selection[1],cdb,idb,cldb, icrsdb,itimedb) + averageRating(sdb_total,sdb_selection[2:#sdb_selection],cdb,idb,cldb, icrsdb,itimedb) * (#sdb_selection - 1))/#sdb_selection ); end averageRating; function recordRating is description: (* Determines the rating for a specific scheduleRecord * based on Criteria A through G *); in: sdb:ScheduleDB, sr:ScheduleRecord, cdb:CourseDB, idb:InstructorDB, cldb:ClassroomDB, icrsdb:InstructorCoursePreferenceDB, itimedb:InstructorTimePreferenceDB; out: rating':Rating; pre: exists (sr1 in sdb) ( sr = sr1 ); post: (* An instructor is assigned * The course is a lab and a lecture * A lecture room is assigned * A lab room is assigned * Max Rating: 100 *) if ( (sr.instr_name != nil) and (sr.is_lec) and (sr.lec_rm != nil) and (sr.is_lab) and (sr.lab_rm != nil) ) then ( exists (c in cdb) ( c.title = sr.title ) and exists (i in idb) ( i.id = sr.instr_id ) and exists (lab_rm in cldb) ( lab_rm.rm = sr.lab_rm ) and exists (lec_rm in cldb) ( lec_rm.rm = sr.lec_rm ) and exists (icrspref in icrsdb) ( (icrspref.instr_id = sr.instr_id) and (icrspref.title = sr.title) ) and exists (itimepref in itimedb) ( itimepref.instr_id = sr.instr_id ) and rating' = (criteriaA(c,i,icrspref) + criteriaB(sr.lec_start,sr.lec_stop, sr.days,itimepref) + criteriaB(sr.lab_start,sr.lab_stop, sr.days,itimepref) + criteriaC(c,sr.days,icrspref) + criteriaD(sr,sdb) + criteriaE(lec_rm,i) + criteriaE(lab_rm,i) + criteriaF(lec_rm,icrspref) + criteriaG(lec_rm,c,sr.sec) + criteriaG(lab_rm,c,sr.sec) )/(10 * 5)*100 ) (* An instructor is assigned * The course is a lab and a lecture * A lecture room is assigned * A lab room is not assigned * Max Rating: 75 *) else if ( (sr.instr_name != nil) and (sr.is_lec) and (sr.lec_rm != nil) and (sr.is_lab) and (sr.lab_rm = nil) ) then ( exists (c in cdb) ( c.title = sr.title ) and exists (i in idb) ( i.id = sr.instr_id ) and exists (lec_rm in cldb) ( lec_rm.rm = sr.lec_rm ) and exists (icrspref in icrsdb) ( (icrspref.instr_id = sr.instr_id) and (icrspref.title = sr.title) ) and exists (itimepref in itimedb) ( itimepref.instr_id = sr.instr_id ) and rating' = (criteriaA(c,i,icrspref) + criteriaB(sr.lec_start,sr.lec_stop, sr.days,itimepref) + criteriaB(sr.lab_start,sr.lab_stop, sr.days,itimepref) + criteriaC(c,sr.days,icrspref) + criteriaD(sr,sdb) + criteriaE(lec_rm,i) + criteriaF(lec_rm,icrspref) + criteriaG(lec_rm,c,sr.sec) )/(8 * 5)*75 ) (* An instructor is assigned * The course is a lab and a lecture * A lecture room is not assigned * A lab room is assigned * Max Rating: 75*) else if ( (sr.instr_name != nil) and (sr.is_lec) and (sr.lec_rm = nil) and (sr.is_lab) and (sr.lab_rm != nil) ) then ( exists (c in cdb) ( c.title = sr.title ) and exists (i in idb) ( i.id = sr.instr_id ) and exists (lab_rm in cldb) ( lab_rm.rm = sr.lab_rm ) and exists (icrspref in icrsdb) ( (icrspref.instr_id = sr.instr_id) and (icrspref.title = sr.title) ) and exists (itimepref in itimedb) ( itimepref.instr_id = sr.instr_id ) and rating' = (criteriaA(c,i,icrspref) + criteriaB(sr.lec_start,sr.lec_stop, sr.days,itimepref) + criteriaB(sr.lab_start,sr.lab_stop, sr.days,itimepref) + criteriaC(c,sr.days,icrspref) + criteriaD(sr,sdb) + criteriaE(lab_rm,i) + criteriaF(lab_rm,icrspref) + criteriaG(lab_rm,c,sr.sec) )/(8 * 5)*75 ) (* An instructor is assigned * The course is a lab and a lecture * A lecture room is not assigned * A lab room is not assigned * Max Rating: 66*) else if ( (sr.instr_name != nil) and (sr.is_lec) and (sr.lec_rm = nil) and (sr.is_lab) and (sr.lab_rm = nil) ) then ( exists (c in cdb) ( c.title = sr.title ) and exists (i in idb) ( i.id = sr.instr_id ) and exists (icrspref in icrsdb) ( (icrspref.instr_id = sr.instr_id) and (icrspref.title = sr.title) ) and exists (itimepref in itimedb) ( itimepref.instr_id = sr.instr_id ) and rating' = (criteriaA(c,i,icrspref) + criteriaB(sr.lec_start,sr.lec_stop, sr.days,itimepref) + criteriaB(sr.lab_start,sr.lab_stop, sr.days,itimepref) + criteriaC(c,sr.days,icrspref) )/(4 * 5)*66 ) (* An instructor is assigned * The course is a lecture only * A lecture room is assigned * Max Rating: 100*) else if ( (sr.instr_name != nil) and (sr.is_lec) and (sr.lec_rm != nil) and (sr.is_lab = false) and (sr.lab_rm = nil) ) then ( exists (c in cdb) ( c.title = sr.title ) and exists (i in idb) ( i.id = sr.instr_id ) and exists (lec_rm in cldb) ( lec_rm.rm = sr.lec_rm ) and exists (icrspref in icrsdb) ( (icrspref.instr_id = sr.instr_id) and (icrspref.title = sr.title) ) and exists (itimepref in itimedb) ( itimepref.instr_id = sr.instr_id ) and rating' = (criteriaA(c,i,icrspref) + criteriaB(sr.lec_start,sr.lec_stop, sr.days,itimepref) + criteriaC(c,sr.days,icrspref) + criteriaD(sr,sdb) + criteriaE(lec_rm,i) + criteriaF(lec_rm,icrspref) + criteriaG(lec_rm,c,sr.sec) )/(7 * 5)*100 ) (* An instructor is assigned * The course is a lab only * A lab room is assigned * Max Rating: 100*) else if ( (sr.instr_name != nil) and (sr.is_lec = false) and (sr.lec_rm = nil) and (sr.is_lab) and (sr.lab_rm != nil) ) then ( exists (c in cdb) ( c.title = sr.title ) and exists (i in idb) ( i.id = sr.instr_id ) and exists (lab_rm in cldb) ( lab_rm.rm = sr.lab_rm ) and exists (icrspref in icrsdb) ( (icrspref.instr_id = sr.instr_id) and (icrspref.title = sr.title) ) and exists (itimepref in itimedb) ( itimepref.instr_id = sr.instr_id ) and rating' = (criteriaA(c,i,icrspref) + criteriaB(sr.lab_start,sr.lab_stop, sr.days,itimepref) + criteriaC(c,sr.days,icrspref) + criteriaD(sr,sdb) + criteriaE(lab_rm,i) + --Room resources are not evaluated for labs criteriaG(lab_rm,c,sr.sec) )/(6 * 5)*100 ) (* An instructor is not assigned * The course is a lab and a lecture * A lecture room is assigned * A lab room is assigned * Max Rating: 66 *) else if ( (sr.instr_name = nil) and (sr.is_lec) and (sr.lec_rm != nil) and (sr.is_lab) and (sr.lab_rm != nil) ) then ( exists (c in cdb) ( c.title = sr.title ) and exists (lab_rm in cldb) ( lab_rm.rm = sr.lab_rm ) and exists (lec_rm in cldb) ( lec_rm.rm = sr.lec_rm ) and rating' = (criteriaG(lec_rm,c,sr.sec) + criteriaG(lab_rm,c,sr.sec) )/(2 * 5)*66 ) (* An instructor is not assigned, * The course is a lecture only * A lecture room is assigned, * Max Rating: 66) else if ( (sr.instr_name = nil) and (sr.is_lec) and (sr.lec_rm != nil) and (sr.is_lab = false) and (sr.lab_rm = nil) ) then ( exists (c in cdb) ( c.title = sr.title ) and exists (lec_rm in cldb) ( lec_rm.rm = sr.lec_rm ) and rating' = (criteriaG(lec_rm,c,sr.sec))/5*66 ) (* An instructor is not assigned, * The course is a lab only * A lab room is assigned * Max Rating: 66 *) else if ( (sr.instr_name = nil) and (sr.is_lec = false) and (sr.lec_rm = nil) and (sr.is_lab) and (sr.lab_rm != nil) ) then ( exists (c in cdb) ( c.title = sr.title ) and exists (lab_rm in cldb) ( lab_rm.rm = sr.lab_rm ) and rating' = (criteriaG(lab_rm,c,sr.sec))/5*66 ) (* An instructor is not assigned, * and no classrooms are assigned * Max Rating: 50 *) else if ( (sr.instr_name = nil) and ( (sr.is_lec) and (sr.is_lab) or (sr.is_lec) and (sr.is_lab = false) or (sr.is_lec = false) and (sr.is_lab) ) and (sr.lec_rm = nil) and (sr.lab_rm = nil) ) then ( rating' = 50 ); end recordRating; function criteriaA is description: (* Determines the rating for how well the course fits with the * instructor assigned to it based on the instructor's teaching * abilities. *); in: c:Course, i:Instructor, icrspref:InstructorCoursePreference; out: rating':Rating; pre: (c != nil) and (c.title != nil) and (icrspref.title = c.title) and (i != nil) and (i.id != nil) and (i.id = icrspref.instr_id); post: rating' = icrspref.rating; end criteriaA; function criteriaB is description: (* Determines the rating for how well the time slot fits with * the instructor's preferred times to work. "days" specifies * the days the which the course is scheduled for and should * therefore be checked*); in: start:Time, stop:Time, days:Days, itimepref:InstructorTimePreference; out: rating':Rating; pre: forall (day in days) ( exists (mblock in itimepref.availblocks) ( mblock.day = day ) ); post: rating' = CriteriaBWeekTotal(start,stop,days,itimepref)/#days; end criteriaB; function CriteriaBWeekTotal is description: (* Determines the rating for Criteria B, but does it on a daily * basis rather than a weekly one *); in: start:Time, stop:Time, days:Days, itimepref:InstructorTimePreference; out: totalRating':Rating; pre: exists (avail in itimepref.availblocks) ( avail.day = days[1] ); post: if (#days = 0) then ( totalRating' = 0) else ( exists (avail in itimepref.availblocks) ( avail.day = days[1] ) and if ( findRemainder(start,stop,avail.open_times) = 0) then ( totalRating' = 5 + CriteriaBWeekTotal(start,stop,days[2:#days],itimepref) ) else if ( findRemainder(start,stop,avail.open_times) <= 30) then ( totalRating' = 4 + CriteriaBWeekTotal(start,stop,days[2:#days],itimepref) ) else if ( findRemainder(start,stop,avail.open_times) <= 60) then ( totalRating' = 3 + CriteriaBWeekTotal(start,stop,days[2:#days],itimepref) ) else if ( findRemainder(start,stop,avail.open_times) <= 120) then ( totalRating' = 2 + CriteriaBWeekTotal(start,stop,days[2:#days],itimepref) ) else if ( findRemainder(start,stop,avail.open_times) <= 150) then ( totalRating' = 1 + CriteriaBWeekTotal(start,stop,days[2:#days],itimepref) ) else if ( findRemainder(start,stop,avail.open_times) > 150) then ( totalRating' = 0 + CriteriaBWeekTotal(start,stop,days[2:#days],itimepref) ) ); end CriteriaBPerDay; function findRemainder is description: (* Recursively whittles a block of time down to find the length of * time (in minutes) which is not overlapped by time blocks in * a list of time blocks *); in: start:Time, stop:Time, times:TimeBlock*; out: leftover:integer; pre: (* None *); post: (* The block of time defined by start and stop will be the whittled block * The block of time in the list will be the whittling block *) if ( #times = 0 ) then ( leftover = timeLength(start,stop) ) else ( (* The whittling block does not overlap the whittled block *) if ( (isBefore(times[1].stop,start)) or (times[1].stop = start) or (isBefore(stop,times[1].start)) or (stop = times[1].start) ) then ( leftover = findRemainder(start, stop, times[2:#times]); ) (* The whittling block overlaps some of the beginning of * the whittled block *) else if ( (isBefore(times[1].start,start)) and (isBefore(start,times[1].stop)) and (isBefore(times[1].stop,stop)) ) then ( leftover = findRemainder(times[1].stop,stop,times[2:#times]) ) (* The whittling block overlaps some of the end of the whittled block *) else if ( (isBefore(start,times[1].start)) and (isBefore(times[1].start,stop)) and (isBefore(stop,times[1].stop)) ) then ( leftover = findRemainder(start,times[1].start,times[2:#times]) ) (* The whittling block splits the whittled block *) else if ( (isBefore(start,times[1].start)) and (isBefore(times[1].stop,stop)) ) then ( leftover = findRemainder(start,times[1].start,times[2:#times]) + findRemainder(times[1].stop,stop,times[2:#times]) ) (* The whittling block engulfs the whittled block *) else if ( (isBefore(times[1].start,start)) and (isBefore(stop,times[1].stop)) ) then ( leftover = 0 ) ); end findRemainder; function criteriaC is description: (* Determines the rating for if the days scheduled for the * course fits well with the instructor's preferences for * that course. *); in: c:Course, days:Days, icrspref:InstructorCoursePreference; out: rating':Rating; pre: c.title = icrspref.title; post: (* *); end criteriaC; function criteriaD is description: (* Determines the rating for how close the scheduled classroom is * to any classrooms for courses which the selected instructor is * scheduled to teach in adjacent timeslots. This distance is * derived by using the Pythagorean theorem based on the location * of the classroom being scheduled and the locations specified * for any courses scheduled adjacent to the time block which have * the same instructor. *); in: sr:ScheduleRecord, sdb:ScheduleDB; out: rating':Rating; pre: (* *); post: (* *); end criteriaD; function criteriaE is description: (* Determines the rating for how well a classroom's handicap * capabilities accomodate an instructor. *); in: cl:Classroom, i:Instructor; out: rating':Rating; pre: (* None *); post: if ( (cl.is_handicap = true and i.is_handicap = true) or (cl.is_handicap = false and i.is_handicap = false) or (cl.is_handicap = true and i.is_handicap = false) ) then ( rating' = 5) else if ( cl.is_handicap = false and i.is_handicap = true ) then ( rating' = 0); end criteriaE; function criteriaF is description: (* Determines the rating for how well the classroom meets the * room resource (such as VCR and smart room) criteria desired * by the instructor. Should be used for lectures only. *); in: cl:Classroom, icrspref:InstructorCoursePreference; out: rating':Rating; pre: (* None *); post: (* *); end criteriaF; function criteriaG is description: (* Determines the rating for how well the course fits the * classroom it is assigned to based on size *); in: cl:Classroom, c:Course, sec:Section; out: rating':Rating; pre: (* None *); post: (* *); end criteriaG; function isValidStartTime is description: (* Tells whether or not a Time is a valid start time for a course *); in: time:Time; out: valid:boolean; pre: (time != nil) and (time.hr != nil) and (time.min != nil) and (time.aorp != nil); post: ( (time.aorp?am) and (time.hr >= 7) and (time.hr <= 11) or (time.aorp?pm) and ( (time.hr >= 1) and (time.hr <= 10) or (time.hr = 12) ) and ( (time.min = 0) or (time.min = 10) or (time.min = 20) or (time.min = 30) or (time.min = 40) or (time.min = 50) ) ); end isValidStartTime; function isValidStopTime is description: (* Tells whether or not a Time is a valid start time for a course *); in: time:Time; out: valid:boolean; pre: (time != nil) and (time.hr != nil) and (time.min != nil) and (time.aorp != nil); post: ( (time.aorp?am) and (time.hr >= 8) and (time.hr <= 11) or (time.aorp?pm) and ( (time.hr >= 1) and (time.hr <= 11) or (time.hr = 12) ) and ( (time.min = 0) or (time.min = 10) or (time.min = 20) or (time.min = 30) or (time.min = 40) or (time.min = 50) ) ); end isValidStopTime; function isBefore is description: (* Takes two time objects and returns true if the first object * is before the second *); in: first:Time, second:Time; out: is_before:boolean; pre: (* None *); post: if ( ( (first.aorp?am) and (second.aorp?am) or (first.aorp?pm) and (second.aorp?pm) ) and ( (first.hr != 12) and (second.hr != 12) and ( (first.hr < second.hr) or (first.hr = second.hr) and (first.min < second.min) ) or (first.hr = 12) and (second.hr != 12) and (0 < second.hr) ) or (first.aorp?am) and (second.aorp?pm) ) then ( is_before = true ) else ( is_before = false ); end isBefore; function timeLength is description: (* Takes two Time objects and returns the number of minutes between them *); in: start:Time, stop:Time; out: length:integer; pre: (* Nothing is left empty *) (start != nil) and (start.hr != nil) and (start.hr >= 1) and (start.hr <= 12) and (start.min != nil) and (start.min >= 0) and (start.hr <= 59) and (start.aorp != nil) and (stop != nil) and (stop.hr != nil) and (stop.hr >= 1) and (stop.hr <= 12) and (stop.min != nil) and (stop.min >= 0) and (stop.hr <= 59) and (stop.aorp != nil) and (* The start time is before the end time *) isBefore(start, stop); post: (* length is equal to the time difference in minutes *) ( (start.aorp = stop.aorp) ) and ( (start.hr = 12) and (stop.hr != 12) and (length = (stop.hr * 60 + stop.min) - (start.min)) or (start.hr != 12) and (stop.hr != 12) and (length = (stop.hr * 60 + stop.min) - (start.hr * 60 + start.min)) ) or (start.aorp?am) and (stop.aorp?pm) and (length = (stop.hr * 60 + 12 * 60 + stop.min) - (start.hr * 60 + start.min)); end timeLength; end Schedule;