def swap2eventPositions(pos1, pos2, preserve_feasibility=True): """ swaps the positions of 2 events in the timetable preserve_feasibility ensures, that only those moves are allowed, which do not violate any hard constraints returns True if feasibility is preserved; returns 2 tuples: returns a backup of the original positions of both events """ event_in_pos1 = data.timetable[pos1] event_in_pos2 = data.timetable[pos2] # print(event_in_pos2) # print(event_in_pos1) # print(pos1,pos2) # check if the events are different if (event_in_pos1 is None and event_in_pos2 is None) or event_in_pos1 == event_in_pos2: return False, (None, None), (None, None) if preserve_feasibility: # checks if the events can be feasibly assigned to the other position # the possible new position is made empty, before it can be checked if the event can be assigned to this timeslot if not event_in_pos1 is None: data.timetable[pos2] = None assignmentPossible = hard.courseFitsIntoTimeslot( event_in_pos1, pos2[1]) data.timetable[pos2] = event_in_pos2 if not assignmentPossible: return False, (None, None), (None, None) if not event_in_pos2 is None: data.timetable[pos1] = None assignmentPossible = hard.courseFitsIntoTimeslot( event_in_pos2, pos1[1]) data.timetable[pos1] = event_in_pos1 if not assignmentPossible: return False, (None, None), (None, None) # swap the positions if not preserve_feasibility: hard.removeCourseAtPosition(pos1) hard.removeCourseAtPosition(pos2) if not event_in_pos1 is None: if hard.courseFitsIntoTimeslot(event_in_pos1, pos2[1]): hard.assignCourseToPosition(event_in_pos1, pos2) else: data.events.append(event_in_pos1) if not event_in_pos2 is None: if hard.courseFitsIntoTimeslot(event_in_pos2, pos1[1]): hard.assignCourseToPosition(event_in_pos2, pos1) else: data.events.append(event_in_pos2) else: data.timetable[pos1] = event_in_pos2 data.timetable[pos2] = event_in_pos1 return True, (pos1, event_in_pos1), (pos2, event_in_pos2)
def swap2timeslots(ts1, ts2, preserve_feasibility=True): """ swaps 2 timeslots in the timetable preserve_feasibility ensures, that only those moves are allowed, which do not violate any unavailability constraints returns 3 variables: returns True if feasibility is preserved; returns a backup of the events in the first timeslot and the second timeslot """ events_in_ts1 = [data.timetable[(i, ts1)] for i in range(data.numberOfRooms)] events_in_ts2 = [data.timetable[(i, ts2)] for i in range(data.numberOfRooms)] if preserve_feasibility: # checks if no unavailability constraints are violated by the swap for ev in events_in_ts1: if not ev is None: if not hard.teacherIsAvailable(ev, ts2): return False, [], [] for ev in events_in_ts2: if not ev is None: if not hard.teacherIsAvailable(ev, ts1): return False, [], [] if not preserve_feasibility: for i, ev in enumerate(events_in_ts1): # data.timetable[(i, ts1)] = None # data.emptyPositions.append(position) hard.removeCourseAtPosition((i, ts1)) for i, ev in enumerate(events_in_ts2): hard.removeCourseAtPosition((i, ts2)) for i, ev in enumerate(events_in_ts1): if not ev is None: if hard.courseFitsIntoTimeslot(ev, ts2): hard.assignCourseToPosition(ev, (i, ts2)) else: data.events.append(ev) for i, ev in enumerate(events_in_ts2): if not ev is None: if hard.courseFitsIntoTimeslot(ev, ts1): hard.assignCourseToPosition(ev, (i, ts1)) else: data.events.append(ev) # for i, ev in enumerate(events_in_ts2): # hard.assignCourseToPosition(ev, (i, ts1)) else: for i, ev in enumerate(events_in_ts1): data.timetable[(i, ts2)] = ev for i, ev in enumerate(events_in_ts2): data.timetable[(i, ts1)] = ev return True, events_in_ts1, events_in_ts2
def swap2eventPositions(pos1, pos2, preserve_feasibility=True): """ swaps the positions of 2 events in the timetable preserve_feasibility ensures, that only those moves are allowed, which do not violate any hard constraints returns True if feasibility is preserved; returns 2 tuples: returns a backup of the original positions of both events """ event_in_pos1 = data.timetable[pos1] event_in_pos2 = data.timetable[pos2] # print(event_in_pos2) # print(event_in_pos1) # print(pos1,pos2) # check if the events are different if (event_in_pos1 is None and event_in_pos2 is None) or event_in_pos1 == event_in_pos2: return False, (None, None), (None, None) if preserve_feasibility: # checks if the events can be feasibly assigned to the other position # the possible new position is made empty, before it can be checked if the event can be assigned to this timeslot if not event_in_pos1 is None: data.timetable[pos2] = None assignmentPossible = hard.courseFitsIntoTimeslot(event_in_pos1, pos2[1]) data.timetable[pos2] = event_in_pos2 if not assignmentPossible: return False, (None, None), (None, None) if not event_in_pos2 is None: data.timetable[pos1] = None assignmentPossible = hard.courseFitsIntoTimeslot(event_in_pos2, pos1[1]) data.timetable[pos1] = event_in_pos1 if not assignmentPossible: return False, (None, None), (None, None) # swap the positions if not preserve_feasibility: hard.removeCourseAtPosition(pos1) hard.removeCourseAtPosition(pos2) if not event_in_pos1 is None: if hard.courseFitsIntoTimeslot(event_in_pos1, pos2[1]): hard.assignCourseToPosition(event_in_pos1, pos2) else: data.events.append(event_in_pos1) if not event_in_pos2 is None: if hard.courseFitsIntoTimeslot(event_in_pos2, pos1[1]): hard.assignCourseToPosition(event_in_pos2, pos1) else: data.events.append(event_in_pos2) else: data.timetable[pos1] = event_in_pos2 data.timetable[pos2] = event_in_pos1 return True, (pos1, event_in_pos1), (pos2, event_in_pos2)
def computeAvailableTimeslots(course, listOfTimeslots=False): """ returns a list and the total number of available timeslots for the course """ if listOfTimeslots: count = 0 ts_list = [] for i in range(data.numberOfTimeslots): if hard.courseFitsIntoTimeslot(course, i): count += 1 ts_list.append(i) return count, ts_list else: count = 0 for i in range(data.numberOfTimeslots): if hard.courseFitsIntoTimeslot(course, i): count += 1 return count
def constructTimetable(): """ constructs a feasible solution or a partially feasible solution; terminates if no solution is found after 10 seconds adapted from Lü, Hao (2010) returns distance to feasibility """ startingTime = time.clock() # it=iter(range(100)) it = 0 while (len(data.events) > 0 or len( data.unplacedEvents) > 0) and it < 1: #time.clock()-startingTime < initialisation.TL_construction: it += 1 # display current cost every 5 seconds # if time.clock() - startingTime > 5: # startingTime += 5 # print(len(data.events)) orderEventsByPriority() num_events = len(data.events) for i in range(num_events): ev = data.events.pop() list_positions = orderPositionsByPriority(ev) # if there are no feasible positions left, move event from unassigned to unplaced if len(list_positions) == 0: data.unplacedEvents.append(ev) else: hard.assignCourseToPosition(ev, list_positions[0]) num_unplaced = len(data.unplacedEvents) random.shuffle(data.unplacedEvents) new_positions = [] for i in range(num_unplaced): pos = random.choice(data.forbiddenPositions) ev = hard.removeCourseAtPosition(pos) data.forbiddenPositions.remove(pos) data.events.append(ev) new_positions.append(pos) for i in range(num_unplaced): unplEv = data.unplacedEvents.pop() assigned = False for pos in new_positions: if hard.courseFitsIntoTimeslot(unplEv, pos[1]): hard.assignCourseToPosition(unplEv, pos) new_positions.remove(pos) assigned = True break if not assigned: data.events.append(unplEv) return len(data.events)
def orderPositionsByPriority(event): """ returns a list of feasible positions """ good_pos = [] feasible_pos = [] for pos in data.emptyPositions: if soft.courseFitsIntoPosition(event, pos): good_pos.append(pos) elif hard.courseFitsIntoTimeslot(event, pos[1]): # compute the penalty for room capacity rcap = soft.roomCapacity(event, pos[0]) feasible_pos.append((rcap, pos)) # sort the feasible positions by their penalty feasible_pos.sort() feasible_pos2 = [pos for (rcap, pos) in feasible_pos] # mix it up a bit more random.shuffle(good_pos) all_pos = good_pos + feasible_pos2 return all_pos
def courseFitsIntoPosition(course, position): room, ts = position return hard.courseFitsIntoTimeslot(course, ts) and roomCapacity(course, room) == 0
def swap2timeslots(ts1, ts2, preserve_feasibility=True): """ swaps 2 timeslots in the timetable preserve_feasibility ensures, that only those moves are allowed, which do not violate any unavailability constraints returns 3 variables: returns True if feasibility is preserved; returns a backup of the events in the first timeslot and the second timeslot """ events_in_ts1 = [ data.timetable[(i, ts1)] for i in range(data.numberOfRooms) ] events_in_ts2 = [ data.timetable[(i, ts2)] for i in range(data.numberOfRooms) ] if preserve_feasibility: # checks if no unavailability constraints are violated by the swap for ev in events_in_ts1: if not ev is None: if not hard.teacherIsAvailable(ev, ts2): return False, [], [] for ev in events_in_ts2: if not ev is None: if not hard.teacherIsAvailable(ev, ts1): return False, [], [] if not preserve_feasibility: for i, ev in enumerate(events_in_ts1): # data.timetable[(i, ts1)] = None # data.emptyPositions.append(position) hard.removeCourseAtPosition((i, ts1)) for i, ev in enumerate(events_in_ts2): hard.removeCourseAtPosition((i, ts2)) for i, ev in enumerate(events_in_ts1): if not ev is None: if hard.courseFitsIntoTimeslot(ev, ts2): hard.assignCourseToPosition(ev, (i, ts2)) else: data.events.append(ev) for i, ev in enumerate(events_in_ts2): if not ev is None: if hard.courseFitsIntoTimeslot(ev, ts1): hard.assignCourseToPosition(ev, (i, ts1)) else: data.events.append(ev) # for i, ev in enumerate(events_in_ts2): # hard.assignCourseToPosition(ev, (i, ts1)) else: for i, ev in enumerate(events_in_ts1): data.timetable[(i, ts2)] = ev for i, ev in enumerate(events_in_ts2): data.timetable[(i, ts1)] = ev return True, events_in_ts1, events_in_ts2
def swapTimeslots(T, tabu=False): """ relevant costs for time slot swapping: Isolated Lectures, Min working days default mode: simulated annealing extra mode: tabu search """ global last_distance, best_feasible_tt, best_distance ts1, ts2 = neighborhood.randomChose2timeslots() if tabu: # if the move is on the tabu list, abort if (ts1, ts2) in T or (ts2, ts1) in T: return False else: T.append((ts1, ts2)) T.append((ts2, ts1)) # init_cost=0 # for course in data.courses: # init_cost+=soft.minWorkingDays(course) # for cu in data.curricula: # init_cost+=soft.isolatedLectures(cu) backupEvents = copy.copy(data.events) # backupUnplaced = copy.copy(data.unplacedEvents) backupEmptyPos = copy.copy(data.emptyPositions) backupTT = copy.deepcopy(data.timetable) successful, backup1, backup2 = neighborhood.swap2timeslots(ts1, ts2, preserve_feasibility=False) if not successful: return False # check if the new assignments are feasible; if not remove the event # backup1 = the events originally in time slot 1, now in time slot 2; # check if their placement in time slot 2 is feasible # if not unassign from timetable # for i, ev in enumerate(backup1): # if ev is not None: # if not hard.courseFitsIntoTimeslot(ev, ts2): # hard.removeCourseAtPosition((i, ts2)) # data.events.append(ev) # # for i, ev in enumerate(backup2): # if ev is not None: # if not hard.courseFitsIntoTimeslot(ev, ts1): # hard.removeCourseAtPosition((i, ts1)) # data.events.append(ev) # try assigning the unplaced courses to empty positions random.shuffle(data.events) events2 = copy.copy(data.events) for ev in data.events: for pos in data.emptyPositions: if hard.courseFitsIntoTimeslot(ev, pos[1]): hard.assignCourseToPosition(ev, pos) events2.remove(ev) break # new_cost=0 # for course in data.courses: # init_cost+=soft.minWorkingDays(course) # for cu in data.curricula: # init_cost+=soft.isolatedLectures(cu) # # # compute the resulting cost change # delta_e=new_cost-init_cost data.events = events2 distance = len(data.events) delta_e = distance - last_distance # print(delta_e) if tabu: if delta_e > 0: # neighborhood.reverseSwapTimeslots(backup1, backup2, ts1, ts2) data.events = backupEvents # data.unplacedEvents = backupUnplaced data.emptyPositions = backupEmptyPos data.timetable = backupTT return False else: # check if the new timetable is worse than the previous one # undo a worse timetable with probability 1 - exp(-delta_e/T) # if the timetable is not accepted, undo the previous neighborhood move # restore the previous state if delta_e > 0 and random.random() > math.exp(-delta_e / T): # neighborhood.reverseSwapTimeslots(backup1, backup2, ts1, ts2) data.events = backupEvents # data.unplacedEvents = backupUnplaced data.emptyPositions = backupEmptyPos data.timetable = backupTT return False # update the last cost value last_distance = distance # check if a new best has been found and save the best timetable if distance < best_distance: best_feasible_tt = copy.deepcopy(data.timetable) best_distance = distance # misc.displayTimetable(data.timetable) return True
def swapPositions(T, tabu=False): """ relevant costs for time slot swapping: Isolated Lectures, Min working days, Room capacity, room stability default mode: simulated annealing extra mode: tabu search """ global last_distance, best_feasible_tt, best_distance pos1, pos2 = neighborhood.randomChose2positions() if tabu: # if the move is on the tabu list, abort if (pos1, pos2) in T or (pos2, pos1) in T: return False else: T.append((pos1, pos2)) T.append((pos2, pos1)) backupEvents = copy.copy(data.events) # backupUnplaced = copy.copy(data.unplacedEvents) backupEmptyPos = copy.copy(data.emptyPositions) backupTT = copy.deepcopy(data.timetable) successful, backup1, backup2 = neighborhood.swap2eventPositions(pos1, pos2, preserve_feasibility=False) if not successful: return False # check if the new assignments are feasible; if not remove the event # backup1[1] is the event that was assigned to pos2 # if not hard.courseFitsIntoTimeslot(backup1[1], pos2[1]): # hard.removeCourseAtPosition(pos2) # data.events.append(backup1[1]) # # if not hard.courseFitsIntoTimeslot(backup2[1], pos1[1]): # hard.removeCourseAtPosition(pos1) # data.events.append(backup2[1]) # try assigning the unplaced courses to empty positions random.shuffle(data.events) events2 = copy.copy(data.events) for ev in data.events: for pos in data.emptyPositions: if hard.courseFitsIntoTimeslot(ev, pos[1]): hard.assignCourseToPosition(ev, pos) events2.remove(ev) break data.events = events2 distance = len(data.events) delta_e = distance - last_distance # print(delta_e) if tabu: if delta_e > 0: # neighborhood.reverseSwapPositions(backup1, backup2) data.events = backupEvents # data.unplacedEvents = backupUnplaced data.emptyPositions = backupEmptyPos data.timetable = backupTT return False else: # check if the new timetable is worse than the previous one # undo a worse timetable with probability 1 - exp(-delta_e/T) # if the timetable is not accepted, undo the previous neighborhood move # restore the previous state if delta_e > 0 and random.random() > math.exp(-delta_e / T): # neighborhood.reverseSwapPositions(backup1, backup2) data.events = backupEvents # data.unplacedEvents = backupUnplaced data.emptyPositions = backupEmptyPos data.timetable = backupTT return False # update the last cost value last_distance = distance # misc.displayTimetable(data.timetable) # check if a new best has been found and save the best timetable if distance < best_distance: best_feasible_tt = copy.deepcopy(data.timetable) best_distance = distance # misc.displayTimetable(data.timetable) return True
def courseFitsIntoPosition(course, position): room, ts = position return hard.courseFitsIntoTimeslot(course, ts) and roomCapacity( course, room) == 0
def swapTimeslots(T, tabu=False): """ relevant costs for time slot swapping: Isolated Lectures, Min working days default mode: simulated annealing extra mode: tabu search """ global last_distance, best_feasible_tt, best_distance ts1, ts2 = neighborhood.randomChose2timeslots() if tabu: # if the move is on the tabu list, abort if (ts1, ts2) in T or (ts2, ts1) in T: return False else: T.append((ts1, ts2)) T.append((ts2, ts1)) # init_cost=0 # for course in data.courses: # init_cost+=soft.minWorkingDays(course) # for cu in data.curricula: # init_cost+=soft.isolatedLectures(cu) backupEvents = copy.copy(data.events) # backupUnplaced = copy.copy(data.unplacedEvents) backupEmptyPos = copy.copy(data.emptyPositions) backupTT = copy.deepcopy(data.timetable) successful, backup1, backup2 = neighborhood.swap2timeslots( ts1, ts2, preserve_feasibility=False) if not successful: return False # check if the new assignments are feasible; if not remove the event # backup1 = the events originally in time slot 1, now in time slot 2; # check if their placement in time slot 2 is feasible # if not unassign from timetable # for i, ev in enumerate(backup1): # if ev is not None: # if not hard.courseFitsIntoTimeslot(ev, ts2): # hard.removeCourseAtPosition((i, ts2)) # data.events.append(ev) # # for i, ev in enumerate(backup2): # if ev is not None: # if not hard.courseFitsIntoTimeslot(ev, ts1): # hard.removeCourseAtPosition((i, ts1)) # data.events.append(ev) # try assigning the unplaced courses to empty positions random.shuffle(data.events) events2 = copy.copy(data.events) for ev in data.events: for pos in data.emptyPositions: if hard.courseFitsIntoTimeslot(ev, pos[1]): hard.assignCourseToPosition(ev, pos) events2.remove(ev) break # new_cost=0 # for course in data.courses: # init_cost+=soft.minWorkingDays(course) # for cu in data.curricula: # init_cost+=soft.isolatedLectures(cu) # # # compute the resulting cost change # delta_e=new_cost-init_cost data.events = events2 distance = len(data.events) delta_e = distance - last_distance # print(delta_e) if tabu: if delta_e > 0: # neighborhood.reverseSwapTimeslots(backup1, backup2, ts1, ts2) data.events = backupEvents # data.unplacedEvents = backupUnplaced data.emptyPositions = backupEmptyPos data.timetable = backupTT return False else: # check if the new timetable is worse than the previous one # undo a worse timetable with probability 1 - exp(-delta_e/T) # if the timetable is not accepted, undo the previous neighborhood move # restore the previous state if delta_e > 0 and random.random() > math.exp(-delta_e / T): # neighborhood.reverseSwapTimeslots(backup1, backup2, ts1, ts2) data.events = backupEvents # data.unplacedEvents = backupUnplaced data.emptyPositions = backupEmptyPos data.timetable = backupTT return False # update the last cost value last_distance = distance # check if a new best has been found and save the best timetable if distance < best_distance: best_feasible_tt = copy.deepcopy(data.timetable) best_distance = distance # misc.displayTimetable(data.timetable) return True
def swapPositions(T, tabu=False): """ relevant costs for time slot swapping: Isolated Lectures, Min working days, Room capacity, room stability default mode: simulated annealing extra mode: tabu search """ global last_distance, best_feasible_tt, best_distance pos1, pos2 = neighborhood.randomChose2positions() if tabu: # if the move is on the tabu list, abort if (pos1, pos2) in T or (pos2, pos1) in T: return False else: T.append((pos1, pos2)) T.append((pos2, pos1)) backupEvents = copy.copy(data.events) # backupUnplaced = copy.copy(data.unplacedEvents) backupEmptyPos = copy.copy(data.emptyPositions) backupTT = copy.deepcopy(data.timetable) successful, backup1, backup2 = neighborhood.swap2eventPositions( pos1, pos2, preserve_feasibility=False) if not successful: return False # check if the new assignments are feasible; if not remove the event # backup1[1] is the event that was assigned to pos2 # if not hard.courseFitsIntoTimeslot(backup1[1], pos2[1]): # hard.removeCourseAtPosition(pos2) # data.events.append(backup1[1]) # # if not hard.courseFitsIntoTimeslot(backup2[1], pos1[1]): # hard.removeCourseAtPosition(pos1) # data.events.append(backup2[1]) # try assigning the unplaced courses to empty positions random.shuffle(data.events) events2 = copy.copy(data.events) for ev in data.events: for pos in data.emptyPositions: if hard.courseFitsIntoTimeslot(ev, pos[1]): hard.assignCourseToPosition(ev, pos) events2.remove(ev) break data.events = events2 distance = len(data.events) delta_e = distance - last_distance # print(delta_e) if tabu: if delta_e > 0: # neighborhood.reverseSwapPositions(backup1, backup2) data.events = backupEvents # data.unplacedEvents = backupUnplaced data.emptyPositions = backupEmptyPos data.timetable = backupTT return False else: # check if the new timetable is worse than the previous one # undo a worse timetable with probability 1 - exp(-delta_e/T) # if the timetable is not accepted, undo the previous neighborhood move # restore the previous state if delta_e > 0 and random.random() > math.exp(-delta_e / T): # neighborhood.reverseSwapPositions(backup1, backup2) data.events = backupEvents # data.unplacedEvents = backupUnplaced data.emptyPositions = backupEmptyPos data.timetable = backupTT return False # update the last cost value last_distance = distance # misc.displayTimetable(data.timetable) # check if a new best has been found and save the best timetable if distance < best_distance: best_feasible_tt = copy.deepcopy(data.timetable) best_distance = distance # misc.displayTimetable(data.timetable) return True