Ejemplo n.º 1
0
def importRoomInventory(csv_filename):
    """Import room inventory to a list roomInstance"""
    f = csv.reader(open(csv_filename, 'rU'))
    headers = f.next()

    rooms = []
    for roomInfo in f:
        #Treat the seating style separately
        assert (headers[2] == "Seating Style")
        av_equip = []
        if roomInfo[2]:
            av_equip = [roomInfo[2]]

        #Treat remaining equipment, if non-blank
        paired_info = zip(headers[3:],
                          roomInfo[3:])  #exclude the name and size
        #only add to list if nonblank
        av_equip += [h for h, r in paired_info if r]

        sName = roomInfo[0]
        if sName in rooms:
            raise ses.SESError("Room %s appears twice in inventory." % sName)
        else:
            r = ses.Room(sName, roomInfo[1], av_equip)
            rooms.append(r)

    return rooms
Ejemplo n.º 2
0
def importB2BPairs(b2b_path, courses):
    """Should yield an empty list if nothing at path"""
    #assumes we know the headers and structure of Excel Sheet
    #Changing them BREAKS this code.

    #Try opening hte back-2-back, if not there warn and ignore
    try:
        f = csv.reader(open(b2b_path, 'rU'))
    except IOError:
        pub.sendMessage(
            "warning", "B2B File not found.  No B2B constraints will be used.")
        return []

    #drop the headers
    f.next()
    out = []
    for line in f:
        c1 = tuple(line[:3])
        c2 = tuple(line[3:])

        courses_1 = filter(lambda c: c.isSame(*c1), courses)
        if not courses_1:
            pub.sendMessage(
                "warning",
                "Course %s %s %s from B2B file not found.  Constraint Skipped."
                % c1)
            continue
        elif len(courses_1) > 1:
            raise ses.SESError("Multiple courses match B2B listing %s %s %s" %
                               c1)

        courses_2 = filter(lambda c: c.isSame(*c2), courses)
        if not courses_2:
            pub.sendMessage(
                "warning",
                "Course %s %s %s from B2B file not found.  Constraint Skipped."
                % c2)
            continue
        elif len(courses_2) > 1:
            raise ses.SESError("Multiple courses match B2B listing %s %s %s" %
                               c2)

        out.append((c1, c2))
    return out
Ejemplo n.º 3
0
def convertYesNoToBool(yes_no, default_blank):
    "Convert 'Y'/'N'/'' to true/false/default_blank resp"
    if yes_no.strip().upper() == "Y":
        return True
    elif yes_no.strip().upper() == "N":
        return False
    elif yes_no.strip().upper() == "":
        return default_blank
    else:
        raise ses.SESError("%s must be one of 'Y', 'N', '' " % yes_no)
Ejemplo n.º 4
0
def allowedRoomTimes(course, config_details, roomInventory,
                     forbiddenTimeSlots):
    """Returns a list of allowed (room, Timeslots) for this course."""
    rooms = allowedRooms(course, roomInventory)
    times = allowedTimes(course, config_details, forbiddenTimeSlots)

    if not rooms or not times:
        raise ses.SESError("No viable rooms or times for course %s" %
                           course.__str__())

    return [(r, ts) for r in rooms for ts in times]
Ejemplo n.º 5
0
    def retrieveAssignment(self):
        """Return a course list with the correct assignments"""
        if self.m.status <> grb.GRB.OPTIMAL:
            raise ses.SESError(
                "Optimizer has not been solved yet. Status: %s" %
                self.m.status)

        #leverage the fact that everything is a pointer
        for (c, r, t, v) in self.vars:
            if v.x > 1 - 1e-3:
                c.addAssignment(r, t, testViable=False)

        return self.course_list
Ejemplo n.º 6
0
    def retrieveAssignment(self):
        """Return a course list with the correct assignments"""
        solution = self.m.solution
        if solution.get_status() not in (101, 102):  #fix this
            raise ses.SESError(
                "Optimizer has not been solved yet. Status: %s" %
                solution.get_status())

        for (c, r, t, v) in self.vars:
            if solution.get_values(v) > 1 - 1e-3:
                c.addAssignment(r, t, testViable=False)

        return self.course_list
Ejemplo n.º 7
0
def addAssignments(courses, roomInventory, csv_filename):
    """Add assignments to courselist  
    """
    f = csv.reader(open(csv_filename, 'rU'))
    headers = f.next()
    all_viable = []

    for courseInfo in f:
        d = dict(zip(headers, courseInfo))
        #make sure course exists
        find_course = lambda c: c.isSame(d["Course"], d["Section"], d[
            "classtype"])
        course = filter(find_course, courses)
        if not course:
            raise ses.SESError("Course %s-%s-%s not in list" %
                               (d["Course"], d["Section"], d["classtype"]))
        else:
            assert (len(course) == 1)
            course = course[0]

        #we only add details for courses that are properly assigned
        if not (d["Room"] and d["StartTime"] and d["EndTime"] and d["Days"]):
            pub.sendMessage(
                "warning", "Course %s-%s-%s not properly assigned\n" %
                (d["Course"], d["Section"], d["classtype"]))
            continue

        #create the room
        #this is an annoyance because of roomnames
        find_room = lambda r: str(r) == d["Room"].strip().upper()
        room = filter(find_room, roomInventory)
        if room:
            assert len(room == 0)
            room = room[0]
        else:
            #fail silently
            pub.sendMessage("warning",
                            "Room %s not in inventory\n" % d["Room"])
            room = ses.Room(d["Room"], ses.Room.MAX_SIZE)

        #create the timeslot
        sdays = d["Days"].split(", ")
        sdays = " ".join(sdays)
        ts = ses.TimeSlot(d["Half"], sdays, d["StartTime"], d["EndTime"])

        course.addAssignment(room, ts, False)

    return courses
Ejemplo n.º 8
0
    def addAssignments(self, path):
        """Add assignments to the courses"""
        try:
            readData.addAssignments(self.optimizer.getCourses(),
                                    self.optimizer.getRoomInventory(), path)

            pub.sendMessage("update_weights")

            #throw an error if not all asignments present.
            for c in self.optimizer.getCourses():
                if c.assignedRoom is None or c.assignedTime is None:
                    #raise an error
                    raise ses.SESError(
                        "Course %s does not have an assignment." % c)
        except ses.SESError as e:
            print e
            pub.sendMessage("status_bar.error", str(e))

        pub.sendMessage("assignments_calced")
Ejemplo n.º 9
0
    def updateObjFcnAndSolve(self, score_weights, pref_weight, e_cap_weight,
                             congestion_weight, dept_fairness, b2b_weight):
        """choiceweights should be in order [1st choice, 2nd choice, etc]"""
        pref_weight = max(pref_weight, self.config.EPS_SAFETY_OVERRIDE)

        #normalize the score weights
        max_weight = float(max(score_weights))
        if max_weight == 0:
            max_weight = 1.
        score_weights = [w / max_weight for w in score_weights]

        #check to see if fairness constraints are already there
        if self.FairnessConstraints:
            [self.m.remove(const) for const in self.FairnessConstraints]

        self.FairnessConstraints = []
        self.addDeptFairnessConstraints(score_weights)

        #Normalize weights to make order unity
        e_cap_weight /= float(-len(self.course_list))
        pref_weight /= float(len(self.course_list))

        obj = grb.LinExpr()
        for c, r, t, var in self.vars:
            obj += helpers.e_cap(c, r) * e_cap_weight * var

        #add the preference weights
        for c, r, t, var in self.vars:
            for ix in range(len(c.roomPrefs)):
                if c.roomPrefs[ix] == r:
                    obj += score_weights[ix] * pref_weight * var
            for ix in range(len(c.timePrefs)):
                if c.timePrefs[ix] == t:
                    obj += score_weights[ix] * pref_weight * var

            #add large penalty if day string doesn't match prefferred
            if not c.isPreferredDays(t):
                obj -= self.config.SOFT_CNST_PENALTY * var

        #add the maxCong, minDeptFairness
        obj += -congestion_weight * self.maxCongVar
        obj += dept_fairness * self.minDept

        #add the bonuses for b2b teaching
        obj += grb.quicksum(b2b_weight * v for v in self.b2b_vars)

        self.m.setObjective(obj, grb.GRB.MAXIMIZE)
        self.m.optimize()

        if self.m.status == grb.GRB.status.INF_OR_UNBD:
            self.m.params.presolve = 0
            self.m.optimize()

        if self.m.status == grb.GRB.status.INFEASIBLE:
            self.m.computeIIS()
            self.m.write("infeasible_conflict.ilp")
            raise ses.SESError(
                "Optimization Infeasible.  Check file infeasible_conflict.ilp")
        elif self.m.status <> grb.GRB.status.OPTIMAL:
            self.m.write("ses.lp")
            raise SESError("Optimizer did not solve. Check ses.lp Status: %d" %
                           self.m.status)
Ejemplo n.º 10
0
def allowedTimes(course, config_details, forbiddenTimeSlot=None):
    """Return a list of allowed TimeSlots for this course.
        Args:
        course - course instance
        forbiddenTimeSlot - No TimeSlot meets OVERLAP with this 1 time slot

        If asked ot respect time, always return that time.
        Otherwise, will add preferred times even if deemed inviable (with warning)
    """
    if course.respectTime:
        return [course.timePrefs[0]]

    #Figure out how many times it meets
    ts = course.timePrefs[0]
    no_meetings_wk = ts.meetingsPerWk()
    days = []
    if no_meetings_wk == 3:
        days = ("M W F", )
    elif no_meetings_wk == 2:
        days = ("M W", "T Th")
    elif no_meetings_wk == 1:
        #if its a recitation requesting last half week, honor it
        if course.isRec() and ts.days[0] in ("W", "Th", "F"):
            days = ("W", "Th", "F")
        else:
            days = ses.TimeSlot.daysOfWeek
    else:  #no_meetings_wk >= 4:
        raise ses.SESError("Course %s meets more than 3X/wk" % course)

    #figure out for how long it meets
    session_length = ts.sessionLength()
    half_hour = dt.timedelta(minutes=30)
    if session_length < half_hour:
        raise ValueError("Course %s meets for less than 30 min per session" %
                         course)

    starts = []
    if session_length == dt.timedelta(hours=1, minutes=30):
        #Sloan blocks only
        starts = [str2time(s) for s, e in config_details.SLOAN_BLOCKS]
    elif session_length == dt.timedelta(hours=1):
        #Sloan Blocks and half hour offsets
        starts = [str2time(s) for s, e in config_details.SLOAN_BLOCKS]
        starts += [start + half_hour for start in starts]
    else:
        #If it specifically requested a time before 4pm, let it have it
        #otherwise, must be after 4pm
        #This rule should be revisited by the SES team
        if ts.startTime < str2time(config_details.FIRST_SEMINAR):
            earliest_time = str2time(config_details.SLOAN_BLOCKS[0][0])
        else:
            earliest_time = str2time(config_details.FIRST_SEMINAR)

        latest_time = str2time(config_details.LAST_CLASS) - session_length
        while earliest_time <= latest_time:
            starts.append(earliest_time)
            earliest_time += half_hour

    #put everything together
    ts_from_start = lambda s, d: ses.TimeSlot(ts.half, d, s, s + session_length
                                              )
    viable_ts = [
        ts_from_start(s, d) for (s, d) in itertools.product(starts, days)
    ]

    #exclude the foribidden time slots.
    if forbiddenTimeSlot is not None:
        assert isinstance(forbiddenTimeSlot, ses.TimeSlot)
        viable_ts = filter(lambda t: not t.overlap(forbiddenTimeSlot),
                           viable_ts)

    #add the preferences back just incase they aren't in there
    #potentially violates forbidden times
    #ideally throw a warning here instead of adding back directly
    for ts in course.timePrefs:
        if ts not in viable_ts:
            pub.sendMessage(
                "warning",
                "Time Pref %s was added, but not deemed viable for %s" %
                (ts, course))
            viable_ts.append(ts)

    return viable_ts
Ejemplo n.º 11
0
def importCourses(csv_filename, roomInventory):
    ''' Imports to list of courses.  
    roomInventory is dict of rooms
    '''
    #assumes we know the headers and structure of Excel Sheet
    #Changing them BREAKS this code.
    f = csv.reader(open(csv_filename, 'rU'))

    #correct the misnamed headers
    headers = f.next()
    old_names = ("Course", "Section", "Title", "Dept", "Half",
                 "Anticipated Enrollment")
    new_names = ("courseNumber", "section", "title", "dept", "half",
                 "enrollment")

    headers_dict = dict(zip(old_names, new_names))
    for indx, h in enumerate(headers):
        if h in old_names:
            headers[indx] = headers_dict[h]

    courses = []
    for vals in f:
        d = dict(zip(headers, vals))

        #Arguments must become objects
        d["enrollment"] = int(d["enrollment"])

        if d["AV Requirements"]:
            d["av_requirements"] = d["AV Requirements"].split(", ")
        del d["AV Requirements"]

        #delete if its blank
        if not d["classtype"]:
            del d["classtype"]

        #pop off the first one
        #VG check for a " "
        instructors = d["Instructors"].split(", ")
        instructors = [ses.Instructor(i) for i in instructors]

        d["instructor"] = instructors[0]
        del d["Instructors"]

        respectRoom = convertYesNoToBool(d["Respect Room"], False)
        del d["Respect Room"]

        if d["Respect Time"]:
            d["respectTime"] = convertYesNoToBool(d["Respect Time"], False)
        del d["Respect Time"]

        timePrefs = _createTimeSlots(d)
        d["firstTimePref"] = timePrefs[0]
        del d["half"]

        roomPrefs, in_inv = _createRooms(d, roomInventory, respectRoom)

        c = ses.Course(**d)
        c.addExtraInstructors(instructors[1:])
        c.addTimePrefs(timePrefs[1:])
        c.addRoomPrefs(roomPrefs, respectRoom)

        if c in courses:
            raise ses.SESError("Attempted to add Course %s twice" % c)

        courses.append(c)

    #Every recitation/breakout has a partner lecture
    for c in courses:
        if c.isRec() or c.isBreakout():
            partner_courses = filter(
                lambda x: x.isSame(c.number, c.section, "LEC"), courses)
            #exit early for efficiency
            if len(partner_courses) == 1:
                continue
            elif not partner_courses:
                raise ses.SESError("Course %s has no partner lecture" % c)
            else:
                raise ses.SESError(
                    "Course %s has %d possible partner lectures" %
                    (c, len(partner_courses)))

    return courses
Ejemplo n.º 12
0
    def updateObjFcnAndSolve(self, score_weights, pref_weight, e_cap_weight,
                             congestion_weight, dept_fairness, b2b_weight):
        """choiceweights should be in order [1st choice, 2nd choice, etc]"""
        pref_weight = max(pref_weight, self.config.EPS_SAFETY_OVERRIDE)

        #normalize the score weights
        max_weight = float(max(score_weights))
        if max_weight == 0:
            max_weight = 1.
        score_weights = [w / max_weight for w in score_weights]

        #check to see if fairness constraints are already there
        if self.hasDeptFairness:
            const_names = [
                "DeptFairness_%s" % dept for dept in self.getDepts()
            ]
            self.m.linear_constraints.delete(const_names)

        self.addDeptFairnessConstraints(score_weights)

        #normalize weights to make comparable
        e_cap_weight /= float(-len(self.course_list))
        pref_weight /= float(len(self.course_list))

        #VG better performance if we don't normalize here...
        #b2b_weight /= float(len(self.b2b_vars) + 1 ) #add 1 for safety

        obj_coefs = []
        for c, r, t, var in self.vars:
            #ecap weight
            coef_ecap = helpers.e_cap(c, r) * e_cap_weight

            #preference business
            coef_pref = 0
            for ix in range(len(c.roomPrefs)):
                if c.roomPrefs[ix] == r:
                    coef_pref += score_weights[ix]
            for ix in range(len(c.timePrefs)):
                if c.timePrefs[ix] == t:
                    coef_pref += score_weights[ix]

            #add large penalty if day string doesn't match prefferred
            coef_pref_day = 0.
            if not c.isPreferredDays(t):
                coef_pref_day = -self.config.SOFT_CNST_PENALTY

            obj_coefs.append(coef_pref * pref_weight + coef_ecap +
                             coef_pref_day)

        self.m.objective.set_sense(self.m.objective.sense.maximize)

        #add the maxCong
        vars_only = [v for c, r, t, v in self.vars]
        vars_only += [self.maxCongVar]
        obj_coefs += [-congestion_weight]

        #add minDeptFairness
        vars_only += [self.minDept]
        obj_coefs += [dept_fairness]

        #add the bonuses for b2b teaching
        vars_only += self.b2b_vars
        obj_coefs += [b2b_weight] * len(self.b2b_vars)

        self.m.objective.set_linear(zip(vars_only, obj_coefs))
        self.m.solve()

        solution = self.m.solution

        #Debug
        #print solution.get_status(), solution.status[solution.get_status()],

        if solution.get_status() not in (101, 102):  #fix this
            sol_status = solution.get_status()

            #probably infeasible
            self.m.conflict.refine(self.m.conflict.all_constraints())
            self.m.conflict.write("infeasible_conflict")

            self.m.write("ses.lp")
            print "VG Sol_status", sol_status
            print "VG solution string", solution.status[sol_status]
            raise ses.SESError(
                "Optimizer did not solve. Check ses.lp Status and infeasible_conflict: %s %s"
                % (solution.status[sol_status], sol_status))