University of Calgary
Rob Kremer
Exam Scheduling

CPSC 433: Artifical Intelligence
Fall 2014
Department of Computer Science
Computer
Science


Problem Description

The assignment is based on the university's registrar's office's on-going problem of scheduling final exams.

Each course (eg: CPSC433) has one or more lectures (eg: CPSC433 L01), and each lecture has an exam, which is a specific length in hours. There are pre-allocated rooms and time slots (day, hour, and length in hours) that may be used for writing these exams. While several exams may take place in a single room at the same time, rooms have a limit on the number of students that may write at the same time. The task is to assign each and every exam to a room/time slot (called a session).

To help you with the problem, I have formally codified the rules in the above paragraph has hard constraints in the next section. But there are also a number of other consideration that the registrar's office would like to recognize. For example:

These are codified in the section on soft constraints and, while they may be violated by your solution, each is associated with a specific penalty.

In the formalization that follows we use the the following types and predicates:

COURSE A course name (eg: CPSC433)
LECTURE A section name (eg: L01)
SESSION A scheduled exam time slot name (eg: W1400)
ROOM A room name (eg: ICT101)
DAY A day name (eg: Wednesday)
TIME A time name (eg: 14:00)
LECTURE(COURSE, LECTURE) A scheduled lecture (eg: CPSC433 L01)
ASSIGN(COURSE, LECTURE, SESSION) LECTURE of COURSE is assigned to SESSION
ROOMASSIGN(SESSION, ROOM) SESSION is assigned to ROOM
ENROLLED(STUDENT, COURSE, LECTURE) STUDENT is enrolled in COURSE in LECTURE
AT(SESSION, DAY, TIME, int)

SESSION is on DAY at TIME for int hours

EXAMLENGTH(COURSE, LECTURE, int) The length of the exam for COURSE, LECTURE is int hours.
CAPACITY(ROOM, int) ROOM can hold int students

Hard Constraints

There are a few mandatory constraints that may not be violated. Any exam schedule that violates these is not considered a solution. These are:

//H1: every lecture is assigned an exam session (completeness)
FORALL c:COURSE, lec:LECTURE | LECTURE(c,lec) . EXISTS ses:SESSION . ASSIGN(c,lec,ses)

//H2: no lecture is assigned more than one exam session
FORALL c:COURSE, lec:LECTURE, ses1,ses2:SESSION | LECTURE(c,lec) .
  (ASSIGN(lec,ses1) /\ ASSIGN(lec,ses2)) => ses1=ses2

//H3: the number of students writing an exam in a particular exam session may not exceed the capacity of the room
FORALL ses:SESSION, r:ROOM | ROOMASSIGN(ses,r) .
  EXITS cap:int | CAPACITY(r,cap) .
    #{s:STUDENT | FORALL c:COURSE, lec: LECTURE | ASSIGN(c,lec,ses) . ENROLLED(s,c,lec)} <= cap

//H4: every lecture's required time must be less than the session length
FORALL ses:SESSION, c:COURSE, lec:LECTURE | ASSIGN(c,lec,ses) .
  EXISTS slen, llen:int | AT(ses,?,?,slen) /\ EXAMLENGTH(c,lec,llen) . llen <= slen

Soft Constraints

There are also several "soft" constraints that may be violated (and since some of these constraints actually conflict with each other, sometimes must be violated). These all have a penalty associated with them (reflecting their importance). Your job is to come up with a solution that maximizes the utility function that naturally arises from these soft constraints and their penalty weightings. I have, of course, written my own version of this utility function that (I believe) precisely conforms to the these constraints, and I will test your program's solutions against my utility function. You will have to write your own utility function to test your program.

// S1: penalty=100/incident. No student writes more than one exam in a timeslot (no direct conflict)
FORALL s:STUDENT, c1,c2:COURSE, lec1,lec2:LECTURE | (lec1 /= lect2 \/ c1 /= c2) /\ ENROLLED(s,c1,lec1) /\ ENROLLED(s,c2,lec2) .
  FORALL ses1,ses2:SESSION | ASSIGN(c1,lec1,ses1) /\ ASSIGN(c2,lect2,ses2) .
    EXISTS d1,d2:DAY, t1,t2,i:int | AT(ses1,d1,t1,?) /\ AT(ses2,d2,t2,?) /\ d1=d2 /\ t1<=t2 /\ EXAMLENGTH(lec1,i) .
      t1+i < t2

//S2: penalty=20/incident. No instructor invigulates in more than one room at the same time (no direct conflict)
FORALL s:INSTRUCTOR, c1,c2:COURSE, lec1,lec2:LECTURE | (lec1 /= lect2 \/ c1 /= c2) /\ INSTRUCTS(s,c1,lec1) /\ INSTRUCTS(s,c2,lec2) .
  FORALL ses1,ses2:SESSION | ASSIGN(c1,lec1,ses1) /\ ASSIGN(c2,lect2,ses2) .
    EXISTS d1,d2:DAY, t1,t2,i:int | AT(ses1,d1,t1,?) /\ AT(ses2,d2,t2,?) /\ d1=d2 /\ t1<=t2 /\ EXAMLENGTH(lec1,i) .
      (t1+i < t2) => ses1=ses2

//S3: penalty=50/incident. Every lecture for the same course should have the same exam timeslot
FORALL c:COURSE .
  EXISTS1 day:Day, time:int .
    FORALL lec:LECTURE, ses:SESSION | LECTURE(c,lec) /\ ASSIGN(c,lec,ses) .
      AT(ses,day,time,?)

//S4: penalty=50/incident. No student writes for longer than 5 hours in a single day
FORALL s:STUDENT .
  FORALL d:DAY .
    (SUM i:int | (FORALL c:COURSE, lec:LECTURE, ses:SESSION | ENROLLED(s,c,lec) /\ ASSIGN(c,lec,ses) .       dayAssign(ses,d)) . EXAMLENGTH(lec,i)) <= 5

//S5: penalty=50/incident. No student should write exams with no break between them
FORALL s:STUDENT, c1,c2:COURSE, lec1,lec2:LECTURE | (lec1 /= lect2 \/ c1 /= c2) /\ ENROLLED(s,c1,lec1) /\ ENROLLED(s,c2,lec2) .
  FORALL ses1,ses2:SESSION | ASSIGN(c1,lec1,ses1) /\ ASSIGN(c2,lect2,ses2) .
    EXISTS d1,d2:DAY, t1,t2,i:int | AT(ses1,d1,t1,?) /\ AT(ses2,d2,t2,?) /\ d1=d2 /\ t1<=t2 /\ EXAMLENGTH(lec1,i) .
      t1+i /= t2

//S6: penalty=20/session. All the exams taking place in a particular session should have the same length
FORALL ses:SESSIONS .
  EXISTS c1,c2: COURSE, lec1,lect1:LECTURE | ASSIGN(c1,lec1,ses) /\ ASSIGN(c2,lec2,ses) .
    EXISTS i:int . EXAMLENGTH(lec1, i) /\ EXAMLENGTH(lect2,i)

//S7: penalty=5/session. Every exam in a session should take up the full time of the session
FORALL ses:SESSIONS .
  EXISTS c: COURSE, lec:LECTURE | ASSIGN(c,lec,ses) .
    EXISTS i:int . EXAMLENGTH(lec, i) /\ AT(ses,?,?,i)


Input/Output Formats

The input and output file formats are the same, namely, a set of ASCII-format predicates, one per line. The BNF for this format is:

file ::= { predicate \n }*    
predicate ::= predicateName ( params )    
params ::= value {, value }*    


  // -- PARAMETER LIST -- -- ERROR HANDLING--
predicateName ::= student // student-name 2nd occurance: do nothing

::= instructor // instructor-name 2nd occurance: do nothing

::= course // course-name 2nd occurance: do nothing

::= day // course-name 2nd occurance: do nothing

::= lecture // course-name, lecture-name Course doesn't exist: create course;
2nd occurance: do nothing

::= lecture // course-name, lecture-name, instructor-name, length-int Course/instructor doesn't exist: create them;
2nd occurance: update

::= session // session-name 2nd occurance: do nothing

::= session // session-name, room-name, day-name, time-int, length-int

session/room/day doesn't exit: create them;
2nd occurence: update


::= room // room-name 2nd occurance: do nothing

::= capacity // room-name. int

Room doesn't exist: create it;
2nd occurance: update


::= instructs // instructor-name, course-name, lecture-name instructor doesn't exist: create instructor;
course/lecture doesn't exist: create them;
2nd occurence: do nothing (but note that an instuctor can teach more than one course/lecture)

::= examLength // course-name, lecture-name, int course/lecture doesn't exist: create them;
2nd occurence: update

::= at // session-name, day-name, time-int, length-int

session/day doesn't exit: create them;
2nd occurence: update


::= enrolled // student-name, course-name, lecture-name

student/course/lecture doesn't exist: create them;
2nd occurence: do nothing (but note that a student can take more than one course/lecture)


::= enrolled // student-name, vector-of-course-names-and-lecture-names

student/course/lecture doesn't exist: create them;
2nd occurence: do nothing (but note that a student can take more than one course/lecture)


::= roomAssign // session-name, room-name

session/room doesn't exist: create them;
Already assigned: update


::= dayAssign // session-name, day-name

session/day doesn't exist: create them;
Already assigned: update


::= time // session-name, int

session doesn't exist: create it;
Already assigned: update


::= length // session-name, int

session doesn't exist: create it;
Already assigned: update


::= assign // course-name, lecture-name, session-name

session/course/lecture doesn't exist: create them;
Already assigned: update

 
     
value ::=

student-name
| instructor-name
|
session-name
|
course-name
|
lecture-name
| room-name
| int
| day-name
| length-int
| time-int

   
name ::= " {char}+ "    

::= letter {letter-or-digit }*    
set ::= { {name {, name}*} }    
vector ::= [ {name {, name}*} ]    

 


Parser

You will be pleased to learn that I have saved you the effort of writing a parser for the file format. See the JavaDoc documentation. You can download the following JAVA classes:

(or download them all in a jar file which includes the .class files, .java files, and manifest as well).

It is highly recommended that you use this parser for your assignment, as it will save you a lot of trouble, it works, and might avoid some possible problems with your assessment and demos. These classes make use of the JAVA reflect package, so that you need only define methods a_predicateName() (to assert a predicate as true) and/or e_predicateName() (to evaluate the truth value of a predicate). Your methods must also, of course, support the appropriate parameter list. You may define additional predicates if you wish, but the assessment will only require you to interpret the predicates listed here. Be warned that the only predicate type expected in your output file is the asssign(course-name, lab-name, session-name) predicate (and perhaps comments).


Sample Input/Output

You can use the following sample texts as a test suite for your program. You will be tested on other scenarios.

There is a long case (and a solution), and a trivial case:

// Lectures ********************************
lecture(CPSC433,L01,Kremer,3)
lecture(CPSC433,L02,Kremer,2)
lecture(CPSC599.68,L01,Kremer,3)

// Students ****************************************
enrolled(Alice,[CPSC433,L02,CPSC599.68,L01])
enrolled(Bob,[CPSC433,L01,CPSC599.68,L01])
enrolled(Carol,[CPSC433,L01])

// Rooms **************************
capacity(JackSimpson,2)
capacity(RedGym ,2)
capacity(GoldGym ,3)

// Sessions ****************
session(M1-08-G,GoldGym ,M1,8,3)
session(M1-11-G,GoldGym ,M1,11,2)
session(M1-15-G,GoldGym ,M1,15,2)
session(M1-18-G,GoldGym ,M1,18,3)
session(M1-09-R,RedGym ,M1,9,3)
session(M1-08-J,JackSimpson,M1,8,3)

// Fixed Assignments
assign(CPSC433,L01,M1-08-G)


When I run my program on the above program, I get the solution shown below. My utility function rates its utility at -100. Note that in this problem, the solution is constrained by there being an assign() predicate in the input data, which the solution has to respect, even though a better solution might be found if that exam were assigned differently. You might also observe that my program can be run in interactive mode (as well as the "batch" mode required by this assignment), and I'm using the PredicateReader class to parse my interactive input and execute appropriate methods -- a big help with debugging!

> !show(solution)
// --solution#22 : examSchedule.solve.DepthFirstTreeSearch------------------------------------
assign(CPSC433, L01, M1-08-G) // fixed
assign(CPSC433, L02, M1-11-G)
assign(CPSC599.68, L01, M1-18-G)
// Attributes: complete, solved, utility=-100, 3/3 lectures assigned.
// for 2 courses, 3 lectures, 1 instructors, 3 students, 3 rooms, 6 sessions, 1 fixed assignments.
// searched 66 nodes, including 32 solved leaves, 0 unsolved leaves, 0 dead-end internal nodes.
// --END solution#22 : examSchedule.solve.DepthFirstTreeSearch--------------------------------

> !report
solution#22 running doCalcGoodness()...
doCalcGoodness() added -50 to goodness. Violated constraint 3, Lecture CPSC433 L02 has a different timeslot from the other lectures in the same course.
doCalcGoodness() added -50 to goodness. Violated constraint 4, Student Bob has 6 exam hours (> limit 5) on day M1.
Const # Pen Total
----- --- --- -----
3 : 1 * -50 = -50
4 : 1 * -50 = -50
-----
-100
solution#22 running doCheckSolved()...
// Attributes: complete, solved, utility=-100, 3/3 lectures assigned.
// for 2 courses, 3 lectures, 1 instructors, 3 students, 3 rooms, 6 sessions, 1 fixed assignments.
// searched 66 nodes, including 32 solved leaves, 0 unsolved leaves, 0 dead-end internal nodes.
TOTAL PENALTY: -100

 


UofC
CPSC 433: Artificial Intelligence
Department of Computer Science

Last updated 2014-09-02
Rob Kremer