Ejemplo n.º 1
0
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)
Ejemplo n.º 2
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
Ejemplo n.º 3
0
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)
Ejemplo n.º 4
0
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
Ejemplo n.º 5
0
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)
Ejemplo n.º 6
0
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
Ejemplo n.º 7
0
def courseFitsIntoPosition(course, position):
    room, ts = position
    return hard.courseFitsIntoTimeslot(course, ts) and roomCapacity(course, room) == 0
Ejemplo n.º 8
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
Ejemplo n.º 9
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
Ejemplo n.º 10
0
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
Ejemplo n.º 11
0
def courseFitsIntoPosition(course, position):
    room, ts = position
    return hard.courseFitsIntoTimeslot(course, ts) and roomCapacity(
        course, room) == 0
Ejemplo n.º 12
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
Ejemplo n.º 13
0
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