コード例 #1
0
def do_naive_2point_move(solution,
                         D,
                         d,
                         C,
                         L,
                         strategy=LSOPT.BEST_ACCEPT,
                         best_delta=None):
    """ This is an educational version of the inter route node exchange move
    (two point move). A customer is swapped with another on the same or
    different route if it improves the solution.
    """

    sol_f = objf(solution, D)
    best_sol = None
    best_found = False
    if not best_delta:
        best_delta = 0

    for i in range(len(solution)):
        n1 = solution[i]
        if n1 == 0:
            continue

        for j in range(1, len(solution) - 1):
            n2 = solution[j]
            if n2 == 0:
                continue

            ansatz_sol = list(solution)
            ansatz_sol[i], ansatz_sol[j] = ansatz_sol[j], ansatz_sol[i]

            #            print("\nmove", i, j)
            #            print("orig",solution )
            #            print("check",ansatz_sol )
            #            print("quality_delta", objf(ansatz_sol, D)-objf(solution, D))
            #            print("feasibility",fast_constraint_check(ansatz_sol,D,d,C,L) )

            if fast_constraint_check(ansatz_sol, D, d, C, L):
                ansatz_sol_f = objf(ansatz_sol, D)
                delta = ansatz_sol_f - sol_f

                if delta + S_EPS < best_delta:
                    #print("set as the best")

                    best_delta = delta
                    best_sol = ansatz_sol

                    if strategy == LSOPT.FIRST_ACCEPT:
                        best_found = True
                        break
        if best_found:
            break

    if best_sol:
        return best_sol, best_delta
    else:
        return None, None
コード例 #2
0
def do_naive_1point_move(solution,
                         D,
                         d,
                         C,
                         L,
                         strategy=LSOPT.BEST_ACCEPT,
                         best_delta=None):
    """ This is an educational version of the one point/relocate move.
    A customer is moved to the different position of the same or different
    route if it improves the solution.
    """

    sol_f = objf(solution, D)
    best_sol = None
    best_found = False
    if not best_delta:
        best_delta = 0

    for n in solution:
        if n == 0:
            continue

        for j in range(1, len(solution) - 1):
            ansatz_sol = list(solution)
            ansatz_sol.remove(n)
            ansatz_sol.insert(j, n)

            if fast_constraint_check(ansatz_sol, D, d, C, L):
                ansatz_sol_f = objf(ansatz_sol, D)
                delta = ansatz_sol_f - sol_f

                if delta + S_EPS < best_delta:
                    best_delta = delta
                    best_sol = ansatz_sol

                    if strategy == LSOPT.FIRST_ACCEPT:
                        best_found = True
                        break
        if best_found:
            break

    if best_sol:
        return best_sol, best_delta
    else:
        return None, None
コード例 #3
0
def do_naive_2optstar_move(solution,
                           D,
                           d,
                           C,
                           L,
                           strategy=LSOPT.BEST_ACCEPT,
                           best_delta=None):
    """ A naive and inefficient inter-route version of the do_naive_2opt_move.
    Used to verify the operation of inter_route_operations.do_2optstar_move.
    """
    # free d for other uses
    demands = d

    sol_f = objf(solution, D)
    best_sol = None
    best_found = False
    if not best_delta:
        best_delta = 0

    # all possible ways of reconnecting the sequences
    for a in range(0, len(solution) - 3):
        b = a + 1
        left_mid_depot = b if solution[b] == 0 else None
        right_mid_depot = left_mid_depot
        for c in range(b, len(solution) - 1):
            d = c + 1
            if solution[c] == 0:
                if left_mid_depot is None:
                    left_mid_depot = c
                right_mid_depot = c

            for alternative in [1, 2]:
                if alternative == 1:
                    #print("\nalt1",a,b,c,d, "depot", mid_depot)
                    # a->c b->d
                    #       ______________
                    #      /              \
                    # 0->-a   b-<-0->-0-<-c   d->-0
                    #         \_______________/
                    #
                    if left_mid_depot is None:
                        ansatz_sol = solution[:b]+\
                                     solution[c:a:-1]+\
                                     solution[d:]
                    else:
                        ansatz_sol = solution[:b]+\
                                solution[c:right_mid_depot:-1]+\
                                solution[left_mid_depot:right_mid_depot+1]+\
                                solution[left_mid_depot-1:a:-1]+\
                                solution[d:]
                if alternative == 2:
                    # This is not possible if there is no depot on the string
                    if left_mid_depot is None:
                        continue
                    #print("\nalt2",a,b,c,d, "depot", mid_depot)
                    # a->d c->b
                    #       __________________
                    #      /                  \
                    # 0->-a   b-<-0->-0-<-c   d->-0
                    #         \___________/
                    #

                    next_depot_from_d = d + solution[d:].index(0)
                    ansatz_sol = solution[:b]+\
                                 solution[d:next_depot_from_d]+\
                                 solution[left_mid_depot:right_mid_depot+1]+\
                                 solution[left_mid_depot-1:a:-1]+\
                                 solution[c:right_mid_depot:-1]+\
                                 solution[next_depot_from_d:]

                ansatz_sol_f = objf(ansatz_sol, D)
                delta = ansatz_sol_f - sol_f

                if delta + S_EPS < best_delta:
                    if fast_constraint_check(ansatz_sol, D, demands, C, L):
                        best_delta = delta
                        best_sol = ansatz_sol

                        if strategy == LSOPT.FIRST_ACCEPT:
                            best_found = True
                            break  # alternative loop
            if best_found:
                break  # second edge loop
        if best_found:
            break  # first edge loop

    if best_sol:
        # remove 0,0´s
        best_sol = [x[0] for x in groupby(best_sol)]

        return best_sol, best_delta
    else:
        return None, None
コード例 #4
0
ファイル: lr3opt.py プロジェクト: yorak/VeRyPy
def lr3opt_init(D,
                d,
                C,
                L,
                initial_lambda1_C=None,
                initial_lambda1_L=None,
                initialization_algorithm=_init_with_tsp,
                postoptimize_with_3optstar=True,
                max_concecutive_lamba_incs=None):
    """ An implementation of the Stewart & Golden [1]_ 3-opt* heuristic
    with Lagrangean relaxation.
    
    The algorithm starts from a solution that can be either feasible or
    infeasible and uses local search to move towards better and feasible 
    solutions. More specifically, it works by replacing the constraint checks 
    of the 3-opt* with a penalty that depends on how much the constraint was 
    violated. The 3-opt* that operates on the entire solution, that is, checks 
    for both intra and inter route moves on one pass, was used. The penalties
    are iteratively doubled as the local search progresses and it is assumed
    that this eventually forces the solutions to feasible region.

    .. [1] Stewart, W. R. and Golden, B. L. (1984). A lagrangean relaxation 
           heuristic for vehicle routing. European Journal of Operational
           Research, 15(1):84–88.
    
    Parameters
    ----------
    D : numpy.ndarray
        is the full 2D distance matrix.
    d : list
        is a list of demands. d[0] should be 0.0 as it is the depot.
    C : float
        is the capacity constraint limit for the identical vehicles.
    L : float
        is the optional constraint for the maximum route length/duration/cost.
    
    initial_lambda1_C : float
        is the initial Langrange multiplier value for the capacity constraint C.
        If left empty (None) the formula ``l1_C=average(d)/(20*max(D))`` is used.
        The alternative value suggested by Stewart & Golden (1984) was 0.05.
    initial_lambda1_L : float
        is the initial Langrange multiplier value for the maximum route cost/
        duration/length constraint. If left empty (None) the formula
        ``l1_L=average(distance to nearest neighbor)/(10*max(D))`` is used.
    initialization_algorithm (function): is a function that retuns a TSP or VRP 
        solution and its objective function value. The default is to use LKH TSP 
        solution, but the function _init_with_random can be used to replicate the
        results of Stewart & Golden (1984) where a random solution is used.
    
    Returns
    -------
    list
        The solution as a list of node indices to visit.
    
    .. todo:: due to how the algorithm works, introducing minimize_K would require
    balancing between penalizing constraint violations and penalizing new 
    routes with an additional multipiler. This was not implemented.
    """

    sol = None
    try:
        ## STEP 1: Generate an initial solution
        sol, initial_f = initialization_algorithm(D, d, C, L)

        max_D = None
        lambdas = [initial_lambda1_C, initial_lambda1_L]
        if C and lambdas[0] == None:
            max_D = _get_max(D, sol)
            lambdas[0] = np.average(d) / (20 * max_D)
        if L and lambdas[1] == None:
            # Stewart & Golden (1984) did not propose an extension for the maximum
            #  route duration/length/cost constraint, but here we have something
            #  similar to L than they used for C constraint relaxation.
            max_D = _get_max(D, sol) if (max_D is None) else max_D
            closest_neighbor_D = D.copy()
            np.fill_diagonal(closest_neighbor_D, max_D)
            lambdas[1] = np.average(
                closest_neighbor_D.min(axis=0)) / (10 * max_D)

        if __debug__:
            log(
                DEBUG,
                "Start from initial solution %s (%.2f), and with l1=%.2f, l2=%.2f"
                % (sol, calculate_objective(sol, D),
                   (0 if lambdas[0] is None else lambdas[0]),
                   (0 if lambdas[1] is None else lambdas[1])))

        checker_function = partial(_check_lr3opt_move, lambdas=lambdas)

        # STEP 2: Solve the relaxed problem using 3-opt*
        c_lambda_incs = 0
        while True:
            # Make sure there is an empty route (for giving the 3-opt* procedure
            #  the option of adding vehicles)
            while not (sol[-1] == 0 and sol[-2] == 0):
                sol += [0]

            if __debug__:
                log(
                    DEBUG - 2, "Finding a LR3OPT move for %s (%.2f)" %
                    (sol, calculate_objective(sol, D)))
            new_sol, delta = do_3optstar_move(sol,
                                              D,
                                              d,
                                              C,
                                              L,
                                              strategy=LSOPT.FIRST_ACCEPT,
                                              move_checker=checker_function)

            # local optima reached, tighten the relaxation
            # TODO: it should not happen that the sol==new_sol. However it happens and as a quickfix check for it.
            if delta is None or sol == new_sol:
                # return the first feasible solution (note: does not check for covering)
                if fast_constraint_check(sol, D, d, C, L):
                    if __debug__:
                        log(
                            DEBUG, "Reached feasible solution %s (%.2f)" %
                            (sol, calculate_objective(sol, D)))
                    while postoptimize_with_3optstar:
                        opt_sol, delta = do_3optstar_move(
                            sol, D, d, C, L, strategy=LSOPT.FIRST_ACCEPT)
                        if delta is None:
                            return normalize_solution(
                                sol)  # remove any [0,0]'s
                        else:
                            sol = opt_sol
                            #print("REMOVEME improved with post-optimization 3-opt*")
                            log(
                                DEBUG,
                                "Found improving 3-opt* move leading to %s (%.2f)"
                                % (sol, calculate_objective(sol, D)))

                    return normalize_solution(sol)  # remove any [0,0]'s
                else:
                    # STEP 3: Update lambdas
                    lambda_at_inf = False
                    if lambdas[0] is not None:
                        lambdas[0] = lambdas[0] * 2
                        lambda_at_inf = lambdas[0] == float('inf')
                    if lambdas[1] is not None:
                        lambdas[1] = lambdas[1] * 2
                        lambda_at_inf = lambda_at_inf or lambdas[0] == float(
                            'inf')
                    if __debug__:
                        log(
                            DEBUG - 1,
                            "No improving moves left, increasing lambda to l1=%.2f, l2=%.2f"
                            % ((0 if lambdas[0] is None else lambdas[0]),
                               (0 if lambdas[1] is None else lambdas[1])))
                    #print("No improving moves left, increasing lambda to l1=%.2f, l2=%.2f"%
                    #        ((0 if lambdas[0] is None else lambdas[0]),
                    #         (0 if lambdas[1] is None else lambdas[1])))

                    #TODO: if penalty >> cost, break (stuck on a infeasible region)
                    # how much bigger can be determined by finding such a
                    # pathological problem instance?

                    # safeguard for getting stuck
                    c_lambda_incs += 1
                    #print("REMOVEME: c_lambda_incs", c_lambda_incs)
                    if lambda_at_inf or (
                            max_concecutive_lamba_incs is not None
                            and c_lambda_incs > max_concecutive_lamba_incs):
                        return _force_feasible(sol, D, d, C, L)

            else:
                if __debug__:
                    log(
                        DEBUG,
                        "Found improving LR3OPT move leading to %s (%.2f)" %
                        (new_sol, calculate_objective(new_sol, D)))
                    log(
                        DEBUG - 2, "However, routes %s remain infeasible." % [
                            r for r in sol2routes(new_sol)
                            if not fast_constraint_check(r, D, d, C, L)
                        ])

                sol = new_sol
                c_lambda_incs = 0

    except KeyboardInterrupt:  # or SIGINT
        # Pass on the current solution forced feasbile by splitting routes
        #  according to the constraints.
        raise KeyboardInterrupt(_force_feasible(sol, D, d, C, L))

    return without_empty_routes(sol)