Exemplo n.º 1
0
def in_route_two_opt(route: Route) -> Route:
    """
    Performs the best in-route two-opt swap, based on routing costs.

    Intra 2-opt in Hornstra et al. (2020).
    """
    problem = Problem()

    tour = np.array([DEPOT] + route.customers.to_list())

    feasible_moves = Heap()
    feasible_moves.push(route.cost(), route)

    for first in range(1, len(route)):
        for second in range(first + 1, len(route)):
            if _gain(tour, first, second) >= 0:
                continue  # this is not a better move.

            # This is a better route than the one we have currently. Of course
            # that does not mean we can find a good handling configuration as
            # well, so we attempt to create a route for this 2-opt move and skip
            # if it is infeasible.
            tour[first:second] = tour[second - 1:first - 1:-1]
            new_route = Route([], [Stacks(problem.num_stacks)])

            if new_route.attempt_append_tail(tour[1:]):
                feasible_moves.push(new_route.cost(), new_route)

    _, best_route = feasible_moves.pop()
    return best_route
def pickup_push_to_front(route: Route) -> Route:
    """
    Pushes pickup items to the front, at various legs of the route. This is
    somewhat preferred, as these items cannot get in the way if they are
    positioned at the front.
    """
    if np.isclose(route.handling_cost(), 0.):
        return route

    problem = Problem()

    for idx_customer, customer in enumerate(route, 1):
        pickup = problem.pickups[customer]
        idx_stack = route.plan[idx_customer].find_stack(pickup).index

        # This skips the last offset, as that would not be interesting anyway
        # (that is the leg towards the depot, where we have only pickups and
        # handling can no longer be improved).
        for plan_offset in range(idx_customer, len(route)):
            new_route = deepcopy(route)

            for stacks in new_route.plan[plan_offset:]:
                stack = stacks[idx_stack]

                idx_current = stack.item_index(pickup)
                stack.remove(pickup)

                stack.push(idx_current + 1, pickup)

            new_route.invalidate_handling_cache()

            if new_route.handling_cost() < route.handling_cost():
                return new_route

    return route
Exemplo n.º 3
0
def item_reinsert(route: Route) -> Route:
    """
    Reinserts customer demands and pickups item in the optimal stack and
    position. Stops once an improving move has been found.
    """
    if np.isclose(route.handling_cost(), 0.):
        return route

    problem = Problem()

    for idx, customer in enumerate(route, 1):
        delivery = problem.demands[customer]
        pickup = problem.pickups[customer]

        new_route = deepcopy(route)

        for stacks in new_route.plan[:idx]:
            stacks.find_stack(delivery).remove(delivery)

        for stacks in new_route.plan[idx:]:
            stacks.find_stack(pickup).remove(pickup)

        next_route = _insert_item(new_route, delivery)
        next_route = _insert_item(next_route, pickup)

        if next_route.handling_cost() < route.handling_cost():
            return next_route

    return route
Exemplo n.º 4
0
def _gain(tour: np.ndarray, first: int, second: int) -> float:
    # Proposed changes.
    gain = Route.distance([tour[first - 1], tour[second - 1]])
    gain += Route.distance([tour[first], tour[second]])

    # Current situation.
    gain -= Route.distance([tour[first - 1], tour[first]])
    gain -= Route.distance([tour[second - 1], tour[second]])

    return gain
Exemplo n.º 5
0
def _customer_routing_cost(route: Route, customer: int, idx: int) -> float:
    customers = route.customers
    problem = Problem()

    assert 0 <= idx < len(customers)
    assert customer in route

    # There is just one customer, which, once removed, would result in a cost
    # of zero. Hence the cost for this single customer is just the route cost.
    if len(customers) == 1:
        return route.routing_cost()

    if idx == 0:
        cost = problem.short_distances[DEPOT, customer, customers[1]]
        cost -= problem.distances[DEPOT + 1, customers[1] + 1]
        return cost

    if idx == len(route) - 1:
        cost = problem.short_distances[customers[-2], customer, DEPOT]
        cost -= problem.distances[customers[-2] + 1, DEPOT + 1]
        return cost

    cost = problem.short_distances[customers[idx - 1], customer,
                                   customers[idx + 1]]
    cost -= problem.distances[customers[idx - 1] + 1, customers[idx + 1] + 1]
    return cost
Exemplo n.º 6
0
def _gain(costs: np.ndarray, route1: Route, idx1: int, route2: Route,
          idx2: int) -> float:
    prev1 = DEPOT if idx1 == 0 else route1.customers[idx1 - 1]
    next1 = DEPOT if idx1 == len(route1) - 1 else route1.customers[idx1 + 1]

    prev2 = DEPOT if idx1 == 0 else route2.customers[idx2 - 1]
    next2 = DEPOT if idx2 == len(route2) - 1 else route2.customers[idx2 + 1]

    # Proposed changes.
    gain = Route.distance([prev1, route2.customers[idx2], next1])
    gain += Route.distance([prev2, route1.customers[idx1], next2])

    # Current situation.
    gain -= costs[route1.customers[idx1]]
    gain -= costs[route2.customers[idx2]]

    return gain
def create_single_customer_route(customer: int) -> Route:
    """
    Creates a single customer route for the passed-in customer. This route
    visits the DEPOT, then the customer, and returns to the DEPOT. O(1).
    """
    problem = Problem()

    # After depot, and after customer: two configurations in total.
    stacks = [Stacks(problem.num_stacks) for _ in range(2)]

    # We place the deliveries and pickups in the shortest stack - this
    # does not really matter much, as each stack is empty at this point
    # anyway.
    stacks[0].shortest_stack().push_rear(problem.demands[customer])
    stacks[1].shortest_stack().push_rear(problem.pickups[customer])

    return Route([customer], stacks)
Exemplo n.º 8
0
def _gain(costs: np.ndarray, route: Route, idx: int, customer: int) -> float:
    pred = DEPOT if idx == 0 else route.customers[idx - 1]
    succ = DEPOT if idx == len(route) else route.customers[idx]

    return Route.distance([pred, customer, succ]) - costs[customer]