def _test_ls_vs_vrhp_w_random_sol(self,
                                      vrph_heur,
                                      ls_heur,
                                      times=10,
                                      routes=1):
        for i in range(times):
            initial_sol = list(range(1, len(self.D)))
            shuffle(initial_sol)
            initial_sol = [0] + initial_sol + [0]
            routes_to_create = routes - 1
            while routes_to_create > 0:
                possible_0_positions = len(initial_sol) - initial_sol.count(
                    0) - 2
                insert_0_counter = randint(0, possible_0_positions - 1)
                for j in range(0, len(initial_sol) - 1):
                    if initial_sol[j] != 0 and initial_sol[j + 1] != 0:
                        # valid position to insert

                        if insert_0_counter == 0:
                            initial_sol.insert(j + 1, 0)
                            break
                        else:
                            insert_0_counter -= 1
                routes_to_create -= 1
            #initial_sol = [0, 7, 3, 0, 4, 1, 5, 2, 6, 0]
            #print("initial_solution", initial_sol)
            #route = [max(0,int(n)-1) for n in "0-2-5-7-4-6-8-3-0".split("-")]

            with NamedTemporaryFile(delete=False, suffix='.vrp') as tmpfile:
                tsplib_file_path = tmpfile.name
            write_TSPLIB_file(tsplib_file_path,
                              self.D,
                              float_to_int_precision=1000)

            with NamedTemporaryFile(delete=False, suffix='.opt') as tmpfile:
                opt_file_path = tmpfile.name
            write_OPT_file(opt_file_path, self.D, initial_sol)

            vrph_sol = _do_vrph_ls(tsplib_file_path, opt_file_path,
                                   [vrph_heur])

            ls_sol = do_local_search([ls_heur], initial_sol, self.D, self.d,
                                     self.C, LSOPT.BEST_ACCEPT)

            # make sure the routes are in right order

            vrph_sol = normalize_solution(vrph_sol)
            ls_sol = normalize_solution(ls_sol)

            print(
                "LS on", initial_sol, "vrph_sol = %s (%.2f)" %
                (str(vrph_sol), objf(vrph_sol, self.D)),
                "ls_sol = %s (%.2f)" % (str(ls_sol), objf(ls_sol, self.D)))
            self.assertEqual(vrph_sol, ls_sol)

            if not DEBUG_VRPH_CALL:
                os.remove(tsplib_file_path)
                os.remove(opt_file_path)
def _compare_improved_from_solution(testcase, sol, D,d,C,L,
                                           ls_ops, naive_ops,
                                           operator_strategy=LSOPT.BEST_ACCEPT,
                                           extra_msg=""):
        """ Improves the solution `sol` for the problem `D`,`d`,`C`,`L` using
        the local_search module operators `ls_ops` and naive implementations
        `naive_ops`. 
        """
        
        if __debug__:
            print("THE PROBLEM:")
            print("D,d,C,L =","np.%s"%repr(D),",",d,",",C,",",L)
        
        rls_ops = list(reversed(ls_ops))
        # note: if given multiple operators, the best single move out of among
        #  all moves is chosen at each step due to ITEROPT.BEST_ACCEPT.
        ls_sol_fwdop = do_local_search(ls_ops, sol, D, d, C, L=L,
                                 operator_strategy=operator_strategy,
                                 iteration_strategy=operator_strategy)
        ls_sol_rwdop = do_local_search(rls_ops, sol, D, d, C, L=L,
                                 operator_strategy=operator_strategy,
                                 iteration_strategy=operator_strategy)
        bf_sol = do_naive_local_search(naive_ops, sol, D, d, C, L=L,
                                       operator_strategy=operator_strategy)
        
        ls_sol_fwdop = normalize_solution(ls_sol_fwdop)
        ls_sol_rwdop = normalize_solution(ls_sol_rwdop)
        bf_sol = normalize_solution(bf_sol)
        
        if __debug__:
            print("\nFINAL SOLUTIONS:")
            print("+".join( op.__name__ for op in ls_ops),"alt 1 :",
                  ls_sol_fwdop, "(%.2f)"%objf(ls_sol_fwdop, D))
            print("+".join( op.__name__ for op in ls_ops),"alt 2 :",
                  ls_sol_rwdop, "(%.2f)"%objf(ls_sol_rwdop, D))
            print("+".join( op.__name__ for op in naive_ops),":", bf_sol,
                  "(%.2f)"%objf(bf_sol, D))
        
        testcase.assertTrue(all(check_solution_feasibility(ls_sol_fwdop, D, d, C, L)))
        testcase.assertTrue(all(check_solution_feasibility(ls_sol_rwdop, D, d, C, L)))
        testcase.assertTrue(all(check_solution_feasibility(bf_sol, D, d, C, L)))
        testcase.assertTrue(ls_sol_fwdop==bf_sol or ls_sol_rwdop==bf_sol, extra_msg)   
Example #3
0
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)
Example #4
0
def read_and_solve_a_problem(problem_instance_path,
                             with_algorithm_function,
                             minimize_K,
                             best_of_n=1,
                             verbosity=-1,
                             single=False,
                             measure_time=False):
    """ Solve a problem instance with the path in problem_instance_path
    with the agorithm in <with_algorithm_function>.
    
    The <with_algorithm_function> has a signature of:
    init_f(points, D_c, d, C, L, st, wtt, verbosity, single, minimize_K)
    
    Options <verbosity>, <single> and <measure_time> may be used to adjust what
    is printed and if a restricted single iteration search (different meaning 
    for different algorithms) is made."""

    pfn = problem_instance_path
    N, points, dd_points, d, D, C, ewt = cvrp_io.read_TSPLIB_CVRP(pfn)
    required_K, L, st = cvrp_io.read_TSBLIB_additional_constraints(pfn)

    # model service time with the distance matrix
    D_c = cvrp_ops.D2D_c(D, st) if st else D

    if points is None:
        if dd_points is not None:
            points = dd_points
        else:
            points, ewt = cvrp_ops.generate_missing_coordinates(D)

    tightness = None
    if C and required_K:
        tightness = (sum(d) / (C * required_K))
    if verbosity >= 0:
        print_problem_information(points, D, d, C, L, st, tightness, verbosity)

    best_sol = None
    best_f = float('inf')
    best_K = len(D)
    interrupted = False
    for repeat_n in range(best_of_n):

        sol, sol_f, sol_K = None, float('inf'), float('inf')
        start = time()
        try:
            sol = with_algorithm_function(points, D_c, d, C, L, st, ewt,
                                          single, minimize_K)
        except KeyboardInterrupt as e:
            print("WARNING: Solving was interrupted, returning " +
                  "intermediate solution",
                  file=sys.stderr)
            interrupted = True
            # if interrupted on initial sol gen, return the best of those
            if len(e.args) > 0 and type(e.args[0]) is list:
                sol = e.args[0]
        elapsed = time() - start

        if sol:
            sol = cvrp_ops.normalize_solution(sol)
            sol_f = objf(sol, D_c)
            sol_K = sol.count(0) - 1
            if is_better_sol(best_f, best_K, sol_f, sol_K, minimize_K):
                best_sol = sol
                best_f = sol_f
                best_K = sol_K
            if best_of_n > 1 and verbosity >= 1:
                print("SOLUTION QUALITY %d of %d: %.2f" %
                      (repeat_n + 1, best_of_n, objf(best_sol, D_c)))
            if measure_time or verbosity >= 1:
                print("SOLVED IN: %.2f s" % elapsed)

        if interrupted:
            break

    if verbosity >= 0 and best_sol:
        n_best_sol = cvrp_ops.normalize_solution(best_sol)
        print_solution_statistics(n_best_sol,
                                  D,
                                  D_c,
                                  d,
                                  C,
                                  L,
                                  st,
                                  verbosity=verbosity)

    if interrupted:
        raise KeyboardInterrupt()

    return best_sol, objf(best_sol, D), objf(best_sol, D_c)
Example #5
0
def cli(init_name, init_desc, init_f):
    ## Simple command line interface
    single = False  # ask to run only single iteration of the algorithm
    measure_time = False
    verbosity = DEFAULT_DEBUG_VERBOSITY
    minimize_K = False
    output_logfilepath = None
    best_of_n = 1
    interrupted = False

    for i in range(0, len(sys.argv) - 1):
        if sys.argv[i] == "-v" and sys.argv[i + 1].isdigit():
            verbosity = int(sys.argv[i + 1])
        if sys.argv[i] == "-n" and sys.argv[i + 1].isdigit():
            best_of_n = int(sys.argv[i + 1])
        if sys.argv[i] == "-1":
            single = True
        if sys.argv[i] == "-t":
            measure_time = True
        if sys.argv[i] == "-l":
            output_logfilepath = sys.argv[i + 1]
        if sys.argv[i] == "-b":
            otarget = sys.argv[i + 1].lower()
            if otarget == "cost" or otarget == "c":
                minimize_K = False
            elif otarget == "vehicles" or otarget == "k":
                minimize_K = True
            else:
                print("WARNING: Ignoring unknown optimization target %s" %
                      otarget)

    if verbosity >= 0:
        set_logger_level(verbosity, logfile=output_logfilepath)

    if sys.argv[-1].isdigit():
        N = int(sys.argv[-1])
        problem_name = "random " + str(N) + " point problem"
        N, points, _, d, D, C, _ = cvrp_io.generate_CVRP(N, 100, 20, 5)
        d = [int(de) for de in d]
        D_c = D
        L, st = None, None
        wtt = "EXACT_2D"

        best_sol = None
        best_f = float('inf')
        best_K = len(D)
        for i in range(best_of_n):
            sol, sol_f, sol_K = None, float('inf'), float('inf')
            try:
                sol = init_f(points, D_c, d, C, L, st, wtt, single, minimize_K)
            except KeyboardInterrupt as e:
                print("WARNING: Solving was interrupted, returning " +
                      "intermediate solution",
                      file=sys.stderr)
                interrupted = True
                # if interrupted on initial sol gen, return the best of those
                if len(e.args) > 0 and type(e.args[0]) is list:
                    sol = e.args[0]
            if sol:
                sol = cvrp_ops.normalize_solution(sol)
                sol_f = objf(sol, D_c)
                sol_K = sol.count(0) - 1

                if is_better_sol(best_f, best_K, sol_f, sol_K, minimize_K):
                    best_sol = sol
                    best_f = sol_f
                    best_K = sol_K

            if interrupted:
                break

        print_solution_statistics(best_sol,
                                  D,
                                  D_c,
                                  d,
                                  C,
                                  L,
                                  st,
                                  verbosity=verbosity)

    problem_file_list = get_a_problem_file_list([sys.argv[-1]])
    if not problem_file_list or "-h" in sys.argv or "--help" in sys.argv:
        print ("Please give a TSPLIB file to solve with "+\
          init_name+\
          " OR give N (integer) to generate a random problem of N customers."+\
          " OR give a path to a folder with .vrp files."+\
          "\n\nOptions (before the file name):\n"+\
          "  -v <int> to set the verbosity level (default %d)\n"%DEFAULT_DEBUG_VERBOSITY+\
          "  -n <int> run the algorithm this many times and return only the best solution\n"+\
          "  -1 to run only one iteration (if applicable)\n"+\
          "  -t to print elapsed wall time\n"+\
          "  -l <file_path> to store the debug output to a file\n"+\
          "  -b <'cost'|'vehicles'> or <c|K> sets the primary optimization oBjective (default is cost)",
          file=sys.stderr)
    elif problem_file_list:
        for problem_path in problem_file_list:
            problem_name = path.basename(problem_path)
            print("Solve", problem_name, "with", init_name)
            read_and_solve_a_problem(problem_path, init_f, minimize_K,
                                     best_of_n, verbosity, single,
                                     measure_time)
Example #6
0
def main(overridden_args=None):
    ## 1. parse arguments

    parser = ArgumentParser(
        description=
        "Solve some .vrp problems with the algorithms built into VeRyPy.")
    parser.add_argument('-l',
                        dest='list_algorithms',
                        help="List the available heuristics and quit",
                        action="store_true")
    parser.add_argument(
        '-v',
        dest='verbosity',
        help=
        "Set the verbosity level (to completely disable debug output, run this script with 'python -O')",
        type=int,
        default=-1)
    parser.add_argument(
        '-a',
        dest='active_algorithms',
        help=
        "Algorithm to apply (argument can be set multiple times to enable multiple algorithms, or one can use 'all' or 'classical')",
        action='append')
    parser.add_argument(
        '-b',
        dest='objective',
        choices=['c', 'cost', 'K', 'vehicles'],
        help="Primary optimization oBjective (default is cost)",
        default="cost")
    parser.add_argument(
        '-m',
        dest='minimal_output',
        help=
        "Overrides the output options and prints only one line CSV report per solved instance",
        action="store_true")
    parser.add_argument(
        '-t',
        dest='print_elapsed_time',
        help="Print elapsed wall time for each solution attempt",
        action="store_true")
    parser.add_argument(
        '-c',
        dest='show_solution_cost',
        help="Display solution cost instead of solution length",
        action="store_true")
    parser.add_argument('-D',
                        dest='dist_weight_format',
                        choices=['ROUND', 'EXACT', 'TRUNCATE'],
                        help="Force distance matrix rounding")
    parser.add_argument(
        '-1',
        dest='use_single_iteration',
        help="Force the algorithms to use only single iteration.",
        action="store_true")
    parser.add_argument(
        '--iinfo',
        dest='print_instance_info',
        help="Print the instance info in the collected results",
        action="store_true")
    parser.add_argument(
        '--routes',
        dest='print_route_stat',
        help="Print per route statistics of the final solution",
        action="store_true")
    parser.add_argument('--vrph',
                        dest='print_vrph_sol',
                        help="Print the final solution in the VRPH format",
                        action="store_true")
    parser.add_argument(
        '--forbid',
        dest='forbid_algorithms',
        help=
        "Forbid applying algorithms (argument can set multiple times to forbid multiple algorithms)",
        action='append')
    parser.add_argument(
        '--simulate',
        dest='simulate',
        help=
        "Do not really invoke algorithms, can be used e.g. to test scripts",
        action="store_true")
    #TODO: consider adding more LS opts e.g. 2optstart, 3optstart
    parser.add_argument(
        '--post-optimize',
        dest='local_search_operators',
        choices=['2opt', '3opt'],
        help=
        "Do post-optimization with local search operator(s) (can set multiple)",
        action='append')
    parser.add_argument(
        "problem_file",
        help=
        "a path of a .vrp problem file, a directory containing .vrp files, or a text file of paths to .vrp files",
        action='append')

    if overridden_args:
        app_args = parser.parse_args(overridden_args)
    elif "-l" in sys.argv:
        print("Select at least one algorithm (with -a) from the list:",
              file=sys.stderr)
        print(_build_algorithm_help())
        sys.exit(1)
    elif len(sys.argv) == 1:
        print("Give at least one .vrp file and use -h to get help.",
              file=sys.stderr)
        sys.exit(1)
    else:
        app_args = parser.parse_args()

    # some further argument validation
    if not app_args.active_algorithms or app_args.list_algorithms:
        print("Select at least one algorithm (with -a) from the list:",
              file=sys.stderr)
        print(_build_algorithm_help())
        exit()
    if len(app_args.problem_file) == 0:
        print("Provide at least one .vrp file to solve", file=sys.stderr)
        exit()

    # get .vrp file list
    files_to_solve = shared_cli.get_a_problem_file_list(app_args.problem_file)

    # get algorithms
    algos = get_algorithms(app_args.active_algorithms)
    if app_args.forbid_algorithms:
        forbidden_algos = [
            algo_name_aliases[algo_name]
            for algo_name in app_args.forbid_algorithms
            if (algo_name in app_args.forbid_algorithms)
        ]
        algos = [a for a in algos if (a[0] not in forbidden_algos)]

    # get primary objective
    minimize_K = False
    if app_args.objective == 'K' or app_args.objective == 'vehicles':
        minimize_K = True

    run_single_iteration = False
    if app_args.use_single_iteration:
        run_single_iteration = True

    # get post-optimization local search move operators
    ls_ops = []
    ls_algo_names = []
    if app_args.local_search_operators:
        ls_algo_names = app_args.local_search_operators
        for ls_op_name in ls_algo_names:
            if ls_op_name == "2opt":
                ls_ops.append(do_2opt_move)
            if ls_op_name == "3opt":
                ls_ops.append(do_3opt_move)

    # verbosity
    if app_args.verbosity >= 0:
        shared_cli.set_logger_level(app_args.verbosity)

    # minimal header
    if app_args.minimal_output:
        print("algo;problem;is_feasible;f;K;t")

    ## 2. solve
    results = defaultdict(lambda: defaultdict(float))
    instance_data = dict()

    interrupted = False
    for pfn in files_to_solve:
        bn = path.basename(pfn).replace(".vrp",
                                        "").replace(".tsp",
                                                    "").replace(".pickle", "")

        try:
            N, points, dd_points, d, D, C, ewt, K, L, st = pickle.load(
                open(pfn, "rb"))
        except:
            N, points, dd_points, d, D, C, ewt = cvrp_io.read_TSPLIB_CVRP(pfn)
            K, L, st = cvrp_io.read_TSBLIB_additional_constraints(pfn)

        # We do not have point coodrinates, but we have D!
        if points is None:
            if dd_points is not None:
                points = dd_points
            else:
                points, ewt = cvrp_ops.generate_missing_coordinates(D)

        if app_args.dist_weight_format == "TRUNCATE":
            D = np.floor(D)
            ewt = "FLOOR_2D"
        if app_args.dist_weight_format == "ROUND":
            D = np.int(D)
            ewt = "EUC_2D"

        # Bake service time to D (if needed)
        D_c = cvrp_ops.D2D_c(D, st) if st else D

        for algo_abbreviation, algo_name, _, algo_f in algos:
            if not app_args.minimal_output:
                print("Solving %s with %s" % (bn, algo_name))
            start_t = time()
            sol = None
            try:
                if not app_args.simulate:
                    sol = algo_f(points, D_c, d, C, L, st, ewt,
                                 run_single_iteration, minimize_K)
            except (KeyboardInterrupt, Exception) as e:
                if type(e) is KeyboardInterrupt:
                    interrupted = True
                    # if interrupted on initial sol gen, return the best of those
                    if len(e.args) > 0 and type(e.args[0]) is list:
                        sol = e.args[0]
                    if not app_args.minimal_output:
                        print("WARNING: Interrupted solving %s with %s" %
                              (bn, algo_abbreviation),
                              file=sys.stderr)
                else:
                    if not app_args.minimal_output:
                        print("ERROR: Failed to solve %s with %s because %s" %
                              (bn, algo_abbreviation, str(e)),
                              file=sys.stderr)
                    sol = None

            if sol:
                sol = cvrp_ops.normalize_solution(sol)
                if app_args.show_solution_cost:
                    sol_q = cvrp_ops.calculate_objective(sol, D_c)
                else:
                    sol_q = cvrp_ops.calculate_objective(sol, D)
                sol_K = sol.count(0) - 1

                if app_args.local_search_operators:
                    if not app_args.minimal_output:
                        print("Postoptimize with %s ..." %
                              ", ".join(app_args.local_search_operators),
                              end="")
                    sol = do_local_search(ls_ops, sol, D, d, C, L)
                    sol = cvrp_ops.normalize_solution(sol)

                    if app_args.show_solution_cost:
                        ls_sol_q = cvrp_ops.calculate_objective(sol, D_c)
                    else:
                        ls_sol_q = cvrp_ops.calculate_objective(sol, D)
                    if ls_sol_q < sol_q:
                        if not app_args.minimal_output:
                            print(" improved by %.2f%%." %
                                  (1 - ls_sol_q / sol_q))
                        sol_q = ls_sol_q
                        sol_K = sol.count(0) - 1
                    else:
                        if not app_args.minimal_output:
                            print(" did not find improving moves.")
            else:
                sol_q = float('inf')

            elapsed_t = time() - start_t
            if app_args.minimal_output:
                print("%s;%s" % (algo_abbreviation, bn), end="")
                timecap_symbol = "*" if interrupted else ""
                if sol:
                    feasible = all(
                        cvrp_ops.check_solution_feasibility(
                            sol, D_c, d, C, L, st))
                    print(";%s;%.2f;%d;%.2f%s" % (str(feasible), sol_q, sol_K,
                                                  elapsed_t, timecap_symbol))
                else:
                    print(";False;inf;inf;%.2f%s" %
                          (elapsed_t, timecap_symbol))

            elif sol:
                # Minimal output is not enabled, print like crazy :)

                if app_args.print_elapsed_time:
                    print("Algorithm produced a solution in %.2f s\n" %
                          (elapsed_t))
                else:
                    #just a newline
                    print()

                tightness = None
                if C and sol_K:
                    tightness = (sum(d) / (C * sol_K))
                if not bn in instance_data or sol_K < instance_data[bn][1]:
                    #"N K C tightness L st"
                    instance_data[bn] = (N, sol_K, C, "%.3f" % tightness, L,
                                         st)

                shared_cli.print_problem_information(
                    points,
                    D_c,
                    d,
                    C,
                    L,
                    st,
                    tightness,
                    verbosity=app_args.verbosity)

                solution_print_verbosity = 3 if app_args.print_route_stat else 1
                shared_cli.print_solution_statistics(sol, D, D_c, d, C, L, st,
                                                     solution_print_verbosity)

                if app_args.print_vrph_sol:
                    print("SOLUTION IN VRPH FORMAT:")
                    print(" ".join(
                        str(n) for n in cvrp_io.as_VRPH_solution(sol)))
                print("\n")

            short_algo_name = algo_name
            results[bn][short_algo_name] = sol_q

            if interrupted:
                break  # algo loop
        if interrupted:
            break  # problem file loop

    ## Print collected results
    sys.stdout.flush()
    sys.stderr.flush()
    if not app_args.minimal_output and (len(results) > 1 or len(algos) > 1):
        print("\n")
        print_title = True
        ls_label = "+".join(ls_algo_names)
        for problem, algo_results in sorted(results.items()):
            algo_names = [ "%s+%s"%(algo_name,ls_label) if ls_algo_names else (algo_name)\
                           for algo_name in sorted(algo_results.keys())]

            if print_title:
                instance_fields = "instance\t"
                if PRINT_INSTANCE_DATA:
                    #"N K C tightness L st"
                    instance_fields += "N\tK*\tC\ttightness\tL\tst\t"
                print(instance_fields + "\t".join(algo_names))
                print_title = False
            print(problem, end="")
            if PRINT_INSTANCE_DATA:
                print("\t", end="")
                print("\t".join(str(e) for e in instance_data[problem]),
                      end="")
            for _, result in sorted(algo_results.items()):
                print("\t", result, end="")
            print()
    sys.stdout.flush()
    sys.stderr.flush()