def main():
    """Main entry point"""
    args = basic_parser().parse_args()
    if VERBOSE:
        print(args.instances)
    for instance in args.instances:
        graph = None
        with open(instance, 'r') as instance_file:
            graph = Graph(instance_file)
            graph.name = os.path.splitext(os.path.basename(instance))[0]
        if VERBOSE:
            print('-' * 100)
            print('File: {name}.txt'.format(name=graph.name))
        start = time.time()
        S = iterated_local_search(graph, args.max_iter, args.time_limit,
                                  args.exclude_ls)
        elapsed = time.time() - start
        if VERBOSE:
            if S is None:
                print('! NO SOLUTION FOUND: NO SATISFYING INITIAL !')
            else:
                print('O* = {o}'.format(o=IlsObjective()(graph, S, None)))
                print('All served?', S.all_served(graph.customer_number))
                print('Everything satisfied?',
                      satisfies_all_constraints(graph, S))
                print('----- PERFORMANCE -----')
                print('ILS took {some} seconds'.format(some=elapsed))
                # visualize(S)
            print('-' * 100)
        if S is not None and not args.no_sol:
            filedir = os.path.dirname(os.path.abspath(__file__))
            generate_sol(graph, S, cwd=filedir, prefix='_ils_')
    return 0
def iterated_local_search(graph, max_iter, time_limit, excludes):
    """Iterated local search algorithm"""
    # O - objective function
    # S - current solution
    # best_S <=> S*
    # MD - method specific supplementary data
    best_S = None
    try:
        O = IlsObjective()
        MD = {
            'ignore_feasibility': False,
            'history': set(),  # history of perturbation: swapped customers
        }
        S = search.construct_initial_solution(graph, O, MD)
        if not satisfies_all_constraints(graph, S):
            raise ValueError("couldn't find satisfying initial solution")
        best_S = S

        if VERBOSE:
            print('O = {o}'.format(o=O(graph, S, None)))

        objective_unchanged = 0

        start = time.time()
        for i in range(max_iter):
            # check timeout
            elapsed = time.time() - start  # in seconds
            if elapsed > time_limit:  # > 45 minutes
                print('- Timeout reached -')
                raise TimeoutError('algorithm timeout reached')

            # main logic
            S = _perturbation(graph, O, S, MD)
            S = search.local_search(graph, O, S, None, excludes)

            if VERBOSE and i % max_iter / 10 == 0:
                print("O* so far:", O(graph, best_S, None))

            if S == best_S:
                # solution didn't change after perturbation + local search
                break
            if objective_unchanged > max_iter * 0.1:
                # if 10% of iterations in a row there's no improvement, stop
                break
            if O(graph, S, None) >= O(graph, best_S, None):
                objective_unchanged += 1
                continue
            objective_unchanged = 0
            best_S = S

    except TimeoutError:
        pass  # supress timeout errors, expecting only from algo timeout
    finally:
        if best_S is None:
            return None
        # final LS just in case
        return search.local_search(graph, O, best_S, None, excludes)
def guided_local_search(graph, penalty_factor, max_iter, time_limit, excludes):
    """Guided local search algorithm"""
    # O - objective function
    # S - current solution
    # best_S <=> S*
    # MD - method specific supplementary data
    best_S = None
    try:
        O = GlsObjective()
        MD = {
            'p': PenaltyMap(graph.raw_data),  # penalties
            'lambda': penalty_factor,
            'f': [],  # feature set,
            'ignore_feasibility': False
        }
        S = search.construct_initial_solution(graph, O, MD)
        if not satisfies_all_constraints(graph, S):
            raise ValueError("couldn't find satisfying initial solution")
        best_S = S

        if VERBOSE:
            print('O = {o}'.format(o=O(graph, S, None)))

        start = time.time()
        for i in range(max_iter):
            # check timeout
            elapsed = time.time() - start  # in seconds
            if elapsed > time_limit:  # elapsed > 60 minutes
                print('- Timeout reached -')
                raise TimeoutError('algorithm timeout reached')

            # main logic
            MD['f'] = _choose_current_features(graph, S, MD)
            most_utilized = _most_utilized_feature(graph, MD)
            MD['p'][most_utilized] += 1
            S = search.local_search(graph, O, S, MD, excludes)

            if VERBOSE and i % max_iter / 10 == 0:
                print("O* so far:", O(graph, best_S, None))

            if O(graph, S, None) >= O(graph, best_S, None):
                # due to deterministic behavior of the local search, once objective
                # function stops decreasing, best solution found
                break
            best_S = S

    except TimeoutError:
        pass  # supress timeout errors, expecting only from algo timeout
    finally:
        if best_S is None:
            return None
        # final LS with no penalties to get true local min
        return search.local_search(graph, O, best_S, None, excludes)
def _perturbation(graph, O, S, md):
    """Perform perturbation between routes on solution"""
    # sort by highest objective
    # as O -> min, maximal values are bad
    # we need to try to reduce max values / compensate
    # this way LS can be guided towards a better solution
    routes = _sort_solution_by_objective(graph, O, S)
    four_opt_performed = False
    while not four_opt_performed and routes:
        ri_a = routes.pop(0)
        route_a = S[ri_a]
        for i in range(len(routes)):
            if four_opt_performed:
                break
            ri_b = routes[i]
            route_b = S[ri_b]
            for ci_a in range(len(route_a) - 1):
                if four_opt_performed:
                    break
                # skip depots
                if route_a[ci_a] == graph.depot:
                    continue
                if route_a[ci_a + 1] == graph.depot:
                    break
                for ci_b in range(len(route_b) - 1):
                    # skip depots
                    if route_b[ci_b] == graph.depot:
                        continue
                    if route_b[ci_b + 1] == graph.depot:
                        break
                    if _make_history_tuple(ci_a, ci_a + 1, ci_b,
                                           ci_b + 1) in md['history']:
                        # skip already swapped customers
                        continue
                    # reverse swap two customers from each route
                    new_route_a, new_route_b = swap_nodes(
                        route_a, route_b, ci_a, ci_b + 1)
                    new_route_a, new_route_b = swap_nodes(
                        new_route_a, new_route_b, ci_a + 1, ci_b)
                    new_S = S.changed(new_route_a, ri_a)
                    new_S = new_S.changed(new_route_b, ri_b)
                    satisfies = satisfies_all_constraints(graph, new_S)
                    if not satisfies:
                        continue
                    S = new_S
                    four_opt_performed = True
                    # add current tuple of 4 customers to history
                    md['history'].add(
                        _make_history_tuple(ci_a, ci_a + 1, ci_b, ci_b + 1))
                    break
    return S