コード例 #1
0
    def test_redistribute_to_one_route(self):
        r1 = [0, 1, 4, 6, 0]
        rd1_redistribute = RouteData(r1, route_l(r1, self.D),
                                     route_d(r1, self.d), None)
        r2 = [0, 2, 3, 5, 7, 0]
        rd2_recieving = RouteData(r2, route_l(r2, self.D), route_d(r2, self.d),
                                  None)

        result = do_redistribute_move(rd1_redistribute,
                                      rd2_recieving,
                                      self.D,
                                      strategy=LSOPT.FIRST_ACCEPT)
        self.assertEqual(
            len(result), 3,
            "The redistribute operator should return the redistributed and the new combined routes and the delta"
        )
        self.assertEqual(
            result[1].route, [0, 2, 3, 5, 7, 1, 4, 6, 0],
            "It should be possible to insert all and they sould be appended to the route"
        )

        result = do_redistribute_move(rd1_redistribute,
                                      rd2_recieving,
                                      self.D,
                                      strategy=LSOPT.BEST_ACCEPT)
        self.assertEqual(
            len(result), 3,
            "The redistribute operator should return the redistributed and the new combined routes and the delta"
        )
        self.assertEqual(result[1].route, [0, 1, 2, 3, 4, 5, 6, 7, 0],
                         "It should be possible to insert all")
コード例 #2
0
def delete_heuristic(routes, D, C, d, L):
    """
    If several routes can be deleted, apply the operation that improves the
    solution most (or worsens it it least).
    """

    improvement_found = False
    best_insertion_delta = float("inf")
    best_insertion_routes = None

    for rd1_idx, rd1 in enumerate(routes):
        if rd1.is_empty():
            continue

        # do not allow empty routes
        other_routes = [r for r in routes if (not r.is_empty() and r != rd1)]

        result = do_redistribute_move(rd1,
                                      other_routes,
                                      D,
                                      d,
                                      C,
                                      L,
                                      best_delta=best_insertion_delta,
                                      recombination_level=0)

        delta = result[-1]
        if (delta is not None) and (delta < best_insertion_delta):
            improvement_found = True
            best_insertion_delta = result[-1]
            best_insertion_routes = result[:-1]

    if improvement_found:
        routes[:] = best_insertion_routes
    return improvement_found
コード例 #3
0
def _refine_solution(sol, D, d, C, L, minimize_K):
    # refine until stuck at a local optima
    local_optima_reached = False
    while not local_optima_reached:
        sol = without_empty_routes(sol)
        if not minimize_K:
            sol.append(0)  #make sure there is an empty route to move the pt to

        # improve with relocation and keep 2-optimal
        sol = do_local_search([do_1point_move, do_2opt_move], sol, D, d, C, L,
                              LSOPT.BEST_ACCEPT)

        # try to redistribute the route with smallest demand
        sol = without_empty_routes(sol)
        routes = RouteData.from_solution(sol, D, d)
        min_rd = min(routes, key=lambda rd: rd.demand)
        routes.remove(min_rd)

        if not minimize_K:
            routes.append(RouteData())

        if __debug__:
            log(
                DEBUG, "Applying do_redistribute_move on %s (%.2f)" %
                (str(sol), objf(sol, D)))

        redisribute_result = do_redistribute_move(
            min_rd,
            routes,
            D,
            d,
            C,
            L,
            strategy=LSOPT.FIRST_ACCEPT,
            #Note: Mole and Jameson do not specify exactly
            # how the redistribution is done (how many
            # different combinations are tried).
            # Increase the recombination_level if the
            # for more agressive and time consuming search
            # for redistributing the customers on other
            # routes.
            recombination_level=0)
        redisribute_delta = redisribute_result[-1]

        if (redisribute_delta is not None) and\
           (minimize_K or redisribute_delta<0.0):

            updated_sol = RouteData.to_solution(redisribute_result[:-1])
            if __debug__:
                log(DEBUG - 1,
                    ("Improved from %s (%.2f) to %s (%.2f)" %
                     (sol, objf(sol, D), updated_sol, objf(updated_sol, D))) +
                    "using inter route heuristic do_redistribute_move\n")
            sol = updated_sol
        else:
            local_optima_reached = True
            if __debug__:
                log(DEBUG - 1, "No move with do_redistribute_move\n")
    return sol
コード例 #4
0
    def test_redistribute_to_two_routes(self):
        r1 = [0, 1, 4, 6, 0]
        rd1_redistribute = RouteData(r1, route_l(r1, self.D),
                                     route_d(r1, self.d), None)
        r2 = [0, 2, 3, 0]
        rd2_recieving = RouteData(r2, route_l(r2, self.D), route_d(r2, self.d),
                                  None)

        r3 = [0, 7, 5, 0]
        rd3_recieving = RouteData(r3, route_l(r3, self.D), route_d(r3, self.d),
                                  None)

        # depending on how the recombinations are made, the results differ
        FI = LSOPT.FIRST_ACCEPT
        BE = LSOPT.BEST_ACCEPT
        tests = [(0, "FIRST", FI, ([0, 2, 3, 1, 4, 0], [0, 7, 5, 6, 0])),
                 (0, "BEST", BE, ([0, 1, 2, 3, 4, 0], [0, 7, 6, 5, 0])),
                 (1, "FIRST", FI, ([0, 2, 3, 4, 1, 0], [0, 7, 5, 6, 0])),
                 (1, "BEST", BE, ([0, 1, 2, 3, 4, 0], [0, 7, 6, 5, 0])),
                 (2, "FIRST", FI, ([0, 2, 3, 1, 4, 0], [0, 7, 5, 6, 0])),
                 (2, "BEST", BE, ([0, 1, 2, 3, 4, 0], [0, 7, 6, 5, 0])),
                 (3, "FIRST", FI, ([0, 2, 3, 4, 1, 0], [0, 7, 5, 6, 0])),
                 (3, "BEST", BE, ([0, 1, 2, 3, 4, 0], [0, 7, 6, 5, 0]))]
        for recombination_level, strategy_name, strategy, target_result in tests:
            result = do_redistribute_move(
                rd1_redistribute, [rd2_recieving, rd3_recieving],
                self.D,
                C=4.0,
                d=self.d,
                strategy=strategy,
                recombination_level=recombination_level)
            #print("QUALITY", strategy_name, "/", recombination_level, "delta", result[-1])
            self.assertEqual(
                len(result), 4,
                "The redistribute operator should return the redistributed and the new combined routes and the delta"
            )
            self.assertEqual(
                result[1].route, target_result[0],
                "It should be possible to insert all and they sould be appended to the route on recombination level %d with strategy %s"
                % (recombination_level, strategy_name))
            self.assertEqual(
                result[2].route, target_result[1],
                "It should be possible to insert all and they sould be appended to the route on recombination level %d with strategy %s"
                % (recombination_level, strategy_name))
コード例 #5
0
    def test_redistribute_does_not_fit(self):
        demands = [0.0, 1, 1, 1, 1, 1, 1, 1]
        r1 = [0, 1, 2, 0]
        rd1_redistribute = RouteData(r1, route_l(r1, self.D),
                                     route_d(r1, demands), None)
        r2 = [0, 3, 4, 5, 6, 7, 0]
        rd2_recieving = RouteData(r2, route_l(r2, self.D),
                                  route_d(r2, demands), None)

        result = do_redistribute_move(rd1_redistribute, [rd2_recieving],
                                      self.D,
                                      C=6.0,
                                      d=self.d,
                                      strategy=LSOPT.BEST_ACCEPT,
                                      recombination_level=1)

        # Fails -> None,None...None is returned (no partial fits)
        self.assertTrue(
            all(rp == None for rp in result),
            "The redistribute operator should return the redistributed and the new combined routes and the delta"
        )
コード例 #6
0
    def test_redistribute_find_best_fit(self):
        demands = [0.0, 1, 1, 2, 3, 1, 3, 1]
        r1 = [0, 1, 4, 6, 0]
        rd1_redistribute = RouteData(r1, route_l(r1, self.D),
                                     route_d(r1, demands), None)
        r2 = [0, 2, 3, 0]
        rd2_recieving = RouteData(r2, route_l(r2, self.D),
                                  route_d(r2, demands), None)

        r3 = [0, 5, 0]
        rd3_recieving = RouteData(r3, route_l(r3, self.D),
                                  route_d(r3, demands), None)

        r4 = [0, 7, 0]
        rd4_recieving = RouteData(r4, route_l(r4, self.D),
                                  route_d(r4, demands), None)

        result = do_redistribute_move(
            rd1_redistribute, [rd2_recieving, rd3_recieving, rd4_recieving],
            self.D,
            C=4.0,
            d=demands,
            strategy=LSOPT.BEST_ACCEPT,
            recombination_level=1)

        self.assertEqual(
            len(result), 5,
            "The redistribute operator should return the redistributed and the new combined routes and the delta"
        )
        self.assertEqual(result[0].route, [0, 0],
                         "It should be possible to insert all customers")
        self.assertEqual(_normalise_route_order(result[1].route),
                         [0, 1, 2, 3, 0],
                         "n1 should be redistributed to first route")
        self.assertEqual(_normalise_route_order(result[2].route), [0, 4, 5, 0],
                         "n4 should be redistributed to first route")
        self.assertEqual(_normalise_route_order(result[3].route), [0, 6, 7, 0],
                         "n6 should be redistributed to first route")
コード例 #7
0
ファイル: petalvrp.py プロジェクト: mschmidt87/VeRyPy
def _regain_feasibility(to_move, r1_delta, r2_delta, rd1_aftermove, ri1, ri2,
                        discard_at_most, d_excess, l_excess, route_datas, D, d,
                        C, L):
    """ From Foster & Ryan (1976) "The result of such a (1 point) move may
    be to cause the receiving route to exceed the mileage or capacity 
    restriction and so it must be permitted to discard deliveries to regain
    feasibility. ... these ... are in turn relocated in the current solution
    schedule without causing further infeasibility and that the total effect 
    of all the relocations must be a reduction in the mileage."
    
    Thus, after the first of the stored 1 point moves causes the solution
    to become infeasible, this function tries to regain the feasibility by
    redistributing some of the customers from the recieving route to other 
    routes so that the moves still produce an improved solution. However,
    to keep the entire operation improving, the redistribution should not
    increase the cost more than delta_slack."""

    ## REPLACE MOVE
    # there is no room, have to replace one *OR MORE* nodes
    # We need to check removing all combinations of customers to make room and
    #  store those that still would remove the problem.

    route2, r2_l, r2_d, _ = route_datas[ri2]
    assert route2[0] == 0 and route2[
        -1] == 0, "All routes must start and end to the depot"
    r2_min_d = min(d[n] for n in route2[1:-1]) if d else 0

    # First route is changed and reserve an empty route to receive the nodes.
    new_empty_rd = RouteData()
    routes_with_slack = [rd for ri, rd in enumerate(route_datas)
                        if (ri!=ri1 and ri!=ri2 and (not C or C-rd.demand+C_EPS>r2_min_d))]\
                        +[rd1_aftermove,new_empty_rd]

    # A depth first search (no recursion) for removing JUST enough customers
    if d:
        stack = [(i + 1, [n], d[n]) for i, n in enumerate(route2[1:-1])]
    else:
        stack = [(i + 1, [n], None) for i, n in enumerate(route2[1:-1])]

    improving_rds = []
    while stack:
        last_n_i, to_remove_ns, to_remove_d = stack.pop()

        # We have to recalculate the delta_r2 as removing nodes almost surely
        #  will change the best position to insert to.
        new_route2 = [n for n in route2 if n not in to_remove_ns]
        new_r2_delta = float('inf')
        new_r2_l = 0.0
        best_j = None
        for j in xrange(1, len(new_route2)):
            insert_after = new_route2[j - 1]
            insert_before = new_route2[j]
            new_r2_l += D[insert_after, insert_before]

            delta = +D[insert_after,to_move]\
                    +D[to_move,insert_before]\
                    -D[insert_after,insert_before]

            if delta < new_r2_delta:
                new_r2_delta = delta
                best_j = j
        to_remove_l = (r2_l + r2_delta) - (new_r2_l + new_r2_delta)
        new_route2 = new_route2[:best_j] + [to_move] + new_route2[best_j:]

        if (not d_excess or to_remove_d+C_EPS >= d_excess) and\
           (not l_excess or to_remove_l+S_EPS >= l_excess):

            # Redistributing ALWAYS increases the cost at least a little,
            #  it makes no sense to even try sometimes.
            # If all of the improvements goes to inserting to_move customer,
            #  there is none left to try to redistribute.
            delta_slack = -(r1_delta + new_r2_delta)
            if delta_slack + S_EPS < 0:
                continue

            # After removing customers in to_remove_ns route2 becomes feasible,
            #  now we have to check if redistributing the removed back to the
            #  solution is possible.

            to_redistribute_r = [0] + to_remove_ns + [0]
            to_redisribute_rd = RouteData(to_redistribute_r,
                                          objf(to_redistribute_r, D),
                                          totald(to_redistribute_r, d))

            result = do_redistribute_move(to_redisribute_rd,
                                          routes_with_slack,
                                          D,
                                          d,
                                          C,
                                          L,
                                          strategy=LSOPT.BEST_ACCEPT,
                                          recombination_level=0,
                                          best_delta=delta_slack)
            redistribute_delta = result[-1]
            if redistribute_delta is not None:

                new_r2_d = r2_d - to_remove_d + d[to_move] if d else 0
                new_rd2 = RouteData(new_route2, new_r2_l + new_r2_delta,
                                    new_r2_d)
                improving_rds.append(new_rd2)
                improving_rds += result[1:-1]  # includes modified rd1

            #TODO: if one would like to explore all discard combinations,
            # one would do branching also here or at least if the redisribute
            # fails.

        elif not discard_at_most or len(to_remove_ns) < discard_at_most:
            # branch out
            for candidate_j, candidate_n in enumerate(route2[last_n_i + 1:-1]):
                stack.append(
                    (last_n_i + 1 + candidate_j, to_remove_ns + [candidate_n],
                     to_remove_d + d[candidate_n] if d else None))

    return improving_rds