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