def position_swap(self, tabu_list): position_1, position_2 = neighborhood.get_random_positions( self.timetable) # check if the moves are already in the tabu list # if not, add them to the list if (position_1, position_2) in tabu_list or (position_2, position_1) in tabu_list: return False tabu_list.append((position_1, position_2)) tabu_list.append((position_2, position_1)) # make a back up, so a rollback is possible timetable_back_up = copy.deepcopy(self.timetable) events_back_up = copy.deepcopy(self.events) success, self.timetable, self.events = neighborhood.swap_positions( self.timetable, self.events, position_1, position_2, feasibility=False) if not success: self.timetable = timetable_back_up self.events = events_back_up return False # shuffle events, and try to place them in a random order random.shuffle(self.events) events_to_remove = [] for event in self.events: for position in self.timetable.empty_positions: room_fi_number = position[0] time_slot = position[1] room = gi.class_rooms_dict[room_fi_number] if hc.course_event_fits_into_time_slot( event, time_slot + self.timetable.offset * 40) and hc.room_capacity_constraint(event, room): self.timetable.assign_course_to_position(event, position) events_to_remove.append(event) break # removed assigned events for event in events_to_remove: self.events.remove(event) distance = len(self.events) delta_e = distance - self.last_distance if delta_e > 0: self.timetable = timetable_back_up self.events = events_back_up return False # Success! self.last_distance = distance if self.last_distance < self.best_distance: self.best_feasible_tt = copy.deepcopy(self.timetable) self.best_distance = distance return True
def occupied_unplaced_time_slot_swap(self, tabu_list): time_slot = neighborhood.get_random_time_slot() if time_slot in tabu_list: return False tabu_list.append(time_slot) timetable_back_up = copy.deepcopy(self.timetable) events_back_up = copy.deepcopy(self.events) success, self.timetable, self.events = neighborhood.swap_occupied_for_unplaced_in_time_slot( self.timetable, self.events, time_slot) if not success: self.timetable = timetable_back_up self.events = events_back_up return False # shuffle events, and try to place them in a random order random.shuffle(self.events) events_to_remove = [] for event in self.events: for position in self.timetable.empty_positions: room_fi_number = position[0] time_slot = position[1] room = gi.class_rooms_dict[room_fi_number] if hc.course_event_fits_into_time_slot( event, time_slot + self.timetable.offset * 40) and hc.room_capacity_constraint(event, room): self.timetable.assign_course_to_position(event, position) events_to_remove.append(event) break # removed assigned events for event in events_to_remove: self.events.remove(event) distance = len(self.events) delta_e = distance - self.last_distance if delta_e > 0: self.timetable = timetable_back_up self.events = events_back_up return False # Success! self.last_distance = distance if self.last_distance < self.best_distance: self.best_feasible_tt = copy.deepcopy(self.timetable) self.best_distance = distance return True
def swap_positions(timetable, events, position_1, position_2, feasibility=True): """ :param timetable: instance of Timetable :param events: list of courseEvents :param position_1: (fi_number, time_slot) :param position_2: (fi_number, time_slot) :param feasibility: boolean that indicates if feasibility should be preserved :return: """ event_1 = timetable.timetable[position_1] fi_number_1 = position_1[0] room_1 = gi.class_rooms_dict[fi_number_1] time_slot_1 = position_1[1] event_2 = timetable.timetable[position_2] fi_number_2 = position_2[0] room_2 = gi.class_rooms_dict[fi_number_2] time_slot_2 = position_2[1] # Check if both events are not None or that they are not equal if (event_1 is None and event_2 is None) or event_1 == event_2: return False, timetable, events # if feasibility should be preserved, the positions only get swapped if the swap is possible for both if feasibility: # Check if no hard constraints get violated if event_1 is not None: timetable.remove_course_from_position(position_2) swap_possible = hc.course_event_fits_into_time_slot(event_1, time_slot_2+timetable.offset*40) and hc.room_capacity_constraint(event_1, room_2) timetable.assign_course_to_position(event_2, position_2) if not swap_possible: return False, timetable, events if event_2 is not None: timetable.remove_course_from_position(position_1) swap_possible = hc.course_event_fits_into_time_slot(event_2, time_slot_1+timetable.offset*40) and hc.room_capacity_constraint(event_2, room_1) timetable.assign_course_to_position(event_1, position_1) if not swap_possible: return False, timetable, events # swapping is possible, so the positions of the two events will get swapped timetable.remove_course_from_position(position_1) timetable.remove_course_from_position(position_2) timetable.assign_course_to_position(event_1, position_2) timetable.assign_course_to_position(event_2, position_1) return True, timetable, events # if feasibility is false, the swap will occur if no hard constraints are broken for at least one event # the other event will get swapped if possible, or get appended the the unplaced_list if not timetable.remove_course_from_position(position_2) timetable.remove_course_from_position(position_1) if event_1 is not None: swap_possible = hc.course_event_fits_into_time_slot(event_1, time_slot_2+timetable.offset*40) and hc.room_capacity_constraint(event_1, room_2) if swap_possible: timetable.assign_course_to_position(event_1, position_2) else: events.append(event_1) if event_2 is not None: swap_possible = hc.course_event_fits_into_time_slot(event_2, time_slot_1+timetable.offset*40) and hc.room_capacity_constraint(event_2, room_1) if swap_possible: timetable.assign_course_to_position(event_2, position_1) else: events.append(event_2) return True, timetable, events
def swap_occupied_for_unplaced_in_time_slot(timetable, events, time_slot): """ This function will look at all occupied places in the timetable for the given time_slot and swap an entry with an event in the events list. :param timetable: instance of Timetable :param events: list of events :param time_slot: the time slot that will be checked :return: timetable, events """ for occupied_position in timetable.occupied_positions: if occupied_position[1] != time_slot: continue event_back_up = timetable.remove_course_from_position(occupied_position) timetable.assign_course_to_position(event_back_up, occupied_position) room = gi.class_rooms_dict[occupied_position[0]] time_slot = occupied_position[1] found_replacement = False for event in events: if hc.course_event_fits_into_time_slot(event, time_slot+timetable.offset*40) and hc.room_capacity_constraint(event, room): timetable.assign_course_to_position(event, occupied_position) found_replacement = True break if not found_replacement: timetable.assign_course_to_position(event_back_up, occupied_position) continue if found_replacement: events.append(event_back_up) return True, timetable, events return False, timetable, events
def split_event(self, tabu_list): # sort events by largest student amount self.events.sort(key=lambda ev: ev.student_amount, reverse=True) # get the course with the most amount of students event = self.events.pop(0) # check if this event is not in the tabu list max_events = len(self.events) index = 0 while event in tabu_list and index < max_events: self.events.append(event) event = self.events.pop(0) index += 1 # get all available positions, not taking in account the room capacity biggest_capacity = 0 for empty_position in self.timetable.empty_positions: fi_number = empty_position[0] time_slot = empty_position[1] if hc.course_event_fits_into_time_slot( event, time_slot + self.timetable.offset * 40): size = gi.class_rooms_dict[fi_number].capacity if size > biggest_capacity: biggest_capacity = size if biggest_capacity == 0: tabu_list.append(event) self.events.append(event) return # split the event course_code = event.course_code lecturers = event.lecturers student_amount_1 = biggest_capacity student_amount_2 = event.student_amount - student_amount_1 curricula = event.curricula event_number = event.event_number course = gi.courses_dict[course_code] course.course_hours += 1 # because the event is split into two, an extra course hour should be created event_1 = data.CourseEvent(course_code=course_code, lecturers=lecturers, student_amount=student_amount_1, curricula=curricula, event_number=event_number) event_2 = data.CourseEvent(course_code=course_code, lecturers=lecturers, student_amount=student_amount_2, curricula=curricula, event_number=event_number) # add the new events to the tabu list tabu_list.append(event_1) tabu_list.append(event_2) self.events.insert(0, event_1) self.events.insert(1, event_2) # check if it is possible to place extra events events_to_remove = [] for event in self.events: for position in self.timetable.empty_positions: room_fi_number = position[0] time_slot = position[1] room = gi.class_rooms_dict[room_fi_number] if hc.course_event_fits_into_time_slot( event, time_slot + self.timetable.offset * 40) and hc.room_capacity_constraint(event, room): self.timetable.assign_course_to_position(event, position) events_to_remove.append(event) break # remove the events that got assigned for event in events_to_remove: self.events.remove(event) distance = len(self.events) self.last_distance = distance if self.last_distance <= self.best_distance: self.best_feasible_tt = copy.deepcopy(self.timetable) self.best_distance = distance return