Пример #1
0
def reinsert_learner(current: Solution, generator: Generator) -> Solution:
    """
    Computes the best reinsertion moves for each learner, stores these in order,
    and executes them. This improves the solution further by moving learners
    into strictly improving assignments, if possible.
    """
    problem = Problem()

    # Get all instruction activities, grouped by module. We only consider
    # moves out of self-study (self-study could be better as well, but the
    # structure of the repair operators makes it unlikely it is preferred over
    # the current learner assignment).
    activities_by_module = current.activities_by_module()
    del activities_by_module[problem.self_study_module]

    moves = []

    for from_activity in current.activities:
        if not from_activity.can_remove_learner():
            continue

        for learner in from_activity.learners:
            for module_id in problem.most_preferred[learner.id]:
                module = problem.modules[module_id]

                if from_activity.is_self_study() \
                        and not learner.prefers_over_self_study(module):
                    break

                gain = problem.preferences[learner.id, module_id]
                gain -= problem.preferences[learner.id, from_activity.module.id]

                if gain <= 0:
                    break

                for to_activity in activities_by_module[module_id]:
                    if to_activity.can_insert_learner():
                        # Random value only to ensure this orders well -
                        # learners and activities cannot be used to compare
                        # the tuples, and gain is the same for many values.
                        item = (-gain, generator.random(),
                                learner, from_activity, to_activity)
                        heappush(moves, item)

    has_moved = set()  # tracks whether we've already moved a learner.

    while len(moves) != 0:
        *_, learner, from_activity, to_activity = heappop(moves)

        if learner not in has_moved \
                and from_activity.can_remove_learner() \
                and to_activity.can_insert_learner():
            from_activity.remove_learner(learner)
            to_activity.insert_learner(learner)
            has_moved.add(learner)

    return current
Пример #2
0
def greedy_insert(destroyed: Solution, generator: Generator) -> Solution:
    """
    Greedily inserts learners into the best, feasible activities. If no
    activity can be found for a learner, (s)he is inserted into self-study
    instead.
    """
    problem = Problem()

    unused_teachers = set(problem.teachers) - destroyed.used_teachers()

    unused_classrooms = set(problem.classrooms) - destroyed.used_classrooms()
    unused_classrooms = [
        classroom for classroom in unused_classrooms
        if classroom.is_self_study_allowed()
    ]

    # It is typically a good idea to prefers using larger rooms for self-study.
    unused_classrooms.sort(key=attrgetter("capacity"))

    activities = destroyed.activities_by_module()

    while len(destroyed.unassigned) != 0:
        learner = destroyed.unassigned.pop()
        inserted = False

        # Attempts to insert the learner into the most preferred, feasible
        # instruction activity.
        for module_id in problem.most_preferred[learner.id]:
            module = problem.modules[module_id]

            if module not in activities:
                continue

            if not learner.prefers_over_self_study(module):
                break

            # TODO Py3.8: use assignment expression in if-statement.
            inserted = _insert(learner, activities[module])

            if inserted:
                break

            # Could not insert, so the module activities must be exhausted.
            del activities[module]

        # Learner could not be inserted into a regular instruction activity,
        # so now we opt for self-study.
        if not inserted and not _insert(learner,
                                        activities[problem.self_study_module]):
            if len(unused_classrooms) == 0 or len(unused_teachers) == 0:
                # This implies we need to remove one or more instruction
                # activities. Let's do the naive and greedy thing, and switch
                # the instruction activity with lowest objective value into a
                # self-study assignment.
                iterable = [
                    activity for activity in destroyed.activities
                    if activity.is_instruction()
                    if activity.classroom.is_self_study_allowed()
                    # After switching to self-study, the max_batch
                    # constraint is longer applicable - only capacity.
                    if activity.num_learners < activity.classroom.capacity
                ]

                activity = min(iterable, key=methodcaller("objective"))

                activities[problem.self_study_module].append(activity)
                activities[activity.module].remove(activity)

                activity.switch_to_self_study()
                activity.insert_learner(learner)
                continue

            for activity in activities[problem.self_study_module]:
                biggest_classroom = unused_classrooms[-1]

                if activity.classroom.capacity < biggest_classroom.capacity:
                    current = activity.classroom
                    activity.classroom = unused_classrooms.pop()
                    unused_classrooms.insert(0, current)

                    destroyed.switch_classrooms(current, activity.classroom)
                    activity.insert_learner(learner)
                    break

                if activity.can_split():
                    teacher = unused_teachers.pop()
                    classroom = unused_classrooms.pop()

                    new_activity = activity.split_with(classroom, teacher)
                    activity.insert_learner(learner)

                    destroyed.add_activity(new_activity)
                    activities[problem.self_study_module].append(new_activity)
                    break
            else:
                # It could be that there is no self-study activity. In that
                # case we should make one. Should be rare.
                classroom = unused_classrooms.pop()
                teacher = unused_teachers.pop()

                # TODO what if there are insufficient learners left?
                learners = [
                    destroyed.unassigned.pop()
                    for _ in range(problem.min_batch)
                ]

                activity = Activity(learners, classroom, teacher,
                                    problem.self_study_module)

                # Since we popped this learner from the unassigned list before,
                # it is not yet in the new activity.
                activity.insert_learner(learner)

                destroyed.add_activity(activity)
                activities[problem.self_study_module].append(activity)

    return destroyed