def test_accepts_better(): for _ in range(1, 100): simulated_annealing = SimulatedAnnealing(2, 1, 1) assert_( simulated_annealing.accept(rnd.RandomState(), One(), Zero(), Zero()))
def test_accepts_equal(): simulated_annealing = SimulatedAnnealing(2, 1, 1) for _ in range(100): # This results in an acceptance probability of exp{0}, that is, one. # Thus, the candidate state should always be accepted. assert_( simulated_annealing.accept(rnd.RandomState(), One(), One(), One()))
def test_raises_start_smaller_than_end(): """ The initial temperature at the start should be bigger (or equal) to the end temperature. """ with assert_raises(ValueError): SimulatedAnnealing(0.5, 1, 1) SimulatedAnnealing(1, 1, 1) # should not raise for equality
def test_raises_explosive_step(): """ For exponential updating, the step parameter must not be bigger than one, as that would result in an explosive threshold. """ with assert_raises(ValueError): SimulatedAnnealing(2, 1, 2, "exponential") SimulatedAnnealing(2, 1, 1, "exponential") # boundary should be fine
def test_exponential_random_solutions(): """ Checks if the exponential ``accept`` method correctly decides in two known cases for a fixed seed. This is the exponential equivalent to the linear random solutions test above. """ simulated_annealing = SimulatedAnnealing(2, 1, 0.5, "exponential") state = rnd.RandomState(0) assert_(simulated_annealing.accept(state, Zero(), Zero(), One())) assert_(not simulated_annealing.accept(state, Zero(), Zero(), One()))
def test_raises_negative_parameters(): """ Simulated annealing does not work with negative parameters, so those should not be accepted. """ with assert_raises(ValueError): # start temperature cannot be SimulatedAnnealing(-1, 1, 1) # negative with assert_raises(ValueError): # nor can the end temperature SimulatedAnnealing(1, -1, 1) with assert_raises(ValueError): # nor the updating step SimulatedAnnealing(1, 1, -1)
def test_linear_random_solutions(): """ Checks if the linear ``accept`` method correctly decides in two known cases for a fixed seed. """ simulated_annealing = SimulatedAnnealing(2, 1, 1) state = rnd.RandomState(0) # Using the above seed, the first two random numbers are 0.55 and .72, # respectively. The acceptance probability is 0.61 first, so the first # should be accepted (0.61 > 0.55). Thereafter, it drops to 0.37, so the # second should not (0.37 < 0.72). assert_(simulated_annealing.accept(state, Zero(), Zero(), One())) assert_(not simulated_annealing.accept(state, Zero(), Zero(), One()))
def test_temperature_boundary(): """ The boundary case for the end temperature parameter is at zero, which must *not* be accepted, as it would result in a division-by-zero. """ with assert_raises(ValueError): SimulatedAnnealing(1, 0, 1)
def get_criterion(init_objective: float) -> SimulatedAnnealing: """ Returns an SA object with initial temperature such that there is a 50% chance of selecting a solution up to 5% worse than the initial solution. The step parameter is then chosen such that the temperature reaches 1 in the set number of iterations. """ start_temp = MAX_WORSE * init_objective / np.log(ACCEPTANCE_PROBABILITY) step = (1 / start_temp) ** (1 / ITERATIONS) return SimulatedAnnealing(start_temp, 1, step, method="exponential")
def test_fixed_seed_outcomes(): """ Tests if fixing a seed results in deterministic outcomes even when using a 'random' acceptance criterion (here SA). """ outcomes = [0.01171, 0.00011, 0.01025] for seed, desired in enumerate(outcomes): # idx is seed alns = get_alns_instance( [lambda state, rnd: ValueState(rnd.random_sample())], [lambda state, rnd: None], seed) simulated_annealing = SimulatedAnnealing(1, .25, 1 / 100) result = alns.iterate(One(), [1, 1, 1, 1], .5, simulated_annealing, 100) assert_almost_equal(result.best_state.objective(), desired, decimal=5)
def test_accepts_generator_and_random_state(): """ Tests if SimulatedAnnealing works with both Generator and RandomState randomness classes. See also https://numpy.org/doc/1.18/reference/random/index.html#quick-start """ class Old: # old RandomState interface mock def random_sample(self): # pylint: disable=no-self-use return 0.5 simulated_annealing = SimulatedAnnealing(2, 1, 1) assert_(simulated_annealing.accept(Old(), One(), One(), Zero())) class New: # new Generator interface mock def random(self): # pylint: disable=no-self-use return 0.5 simulated_annealing = SimulatedAnnealing(2, 1, 1) assert_(simulated_annealing.accept(New(), One(), One(), Zero()))
def test_end_temperature(): """ Tests if the end_temperature parameter is correctly set. """ for end in range(1, 100): assert_equal(SimulatedAnnealing(100, end, 1).end_temperature, end)
def test_start_temperature(): """ Tests if the start_temperature parameter is correctly set. """ for start in range(1, 100): assert_equal(SimulatedAnnealing(start, 1, 1).start_temperature, start)
import os from alns.criteria import RecordToRecordTravel, SimulatedAnnealing DEPOT = -1 TEAM_NUMBER = 3 if "TRAVIS" in os.environ: NEARNESS = 3 DEGREE_OF_DESTRUCTION = 0.2 WEIGHTS = [25, 5, 1, 1] DECAY = 0.6 ITERATIONS = 1000 CRITERION = RecordToRecordTravel(200, 0, step=200 / ITERATIONS) else: # Use these to play around with - the above is for Travis runs, and should # not be changed too much. NEARNESS = 3 DEGREE_OF_DESTRUCTION = 0.1 WEIGHTS = [25, 10, 1, 0.8] DECAY = 0.9 ITERATIONS = 25000 CRITERION = SimulatedAnnealing(2000, 1, 0.999696, method="exponential")
print("Executing hard-coded instance:", instance_to_run) irp.load_file(instance_path) alns = ALNS(random_state) alns.add_destroy_operator(remove_rand_pos) alns.add_destroy_operator(swap_rand_pos) alns.add_destroy_operator(remove_rand_sps) alns.add_destroy_operator(remove_worst_sps) alns.add_repair_operator(greedy_repair) initial_solution = greedy_repair(Solution(irp), random_state) print(instance_path) print("Initial solution:", int(initial_solution.get_cost())) #criterion = HillClimbing() criterion = SimulatedAnnealing(100, 5, 5, method='linear') start_time = time.time() result = alns.iterate(initial_solution, [3, 2, 1, 0.5], 0.8, criterion, iterations=iterations, collect_stats=True) end_time = time.time() solution = result.best_state print("Best solution:", int(solution.objective())) print("Time taken (s):", int(end_time - start_time)) print("Iterations:", iterations) _, ax = plt.subplots(figsize=(12, 6)) result.plot_objectives(ax=ax, lw=2) figure = plt.figure("method_counts", figsize=(14, 6)) figure.subplots_adjust(bottom=0.15, hspace=.5)
def solve_cvrp_with_alns(seed=SEED, size=SIZE, capacity=CAPACITY, number_of_depots=NUMBER_OF_DEPOTS, iterations=ITERATIONS, collect_statistics=COLLECT_STATISTICS, draw=False, **kwargs): weights = WEIGHTS operator_decay = OPERATOR_DECAY start_temperature_control = settings.START_TEMPERATURE_CONTROL cooling_rate = settings.COOLING_RATE end_temperature = settings.END_TEMPERATURE verbose = False if 'weights' in kwargs: weights = kwargs['weights'] if 'operator_decay' in kwargs: operator_decay = kwargs['operator_decay'] if 'start_temperature_control' in kwargs: start_temperature_control = kwargs['start_temperature_control'] if 'cooling_rate' in kwargs: cooling_rate = kwargs['cooling_rate'] if 'end_temperature' in kwargs: end_temperature = kwargs['end_temperature'] if 'verbose' in kwargs: verbose = kwargs['verbose'] cvrp_instance = generate_cvrp_instance(size, capacity, number_of_depots, seed) if verbose: print("Created CVRP graph") # Create an empty state initial_state = CvrpState(cvrp_instance, collect_alns_statistics=collect_statistics, size=size, number_of_depots=number_of_depots, capacity=capacity) if verbose: print("Created CVRP state.\nGenerating initial solution... ", end='', flush=True) initial_solution = generate_initial_solution(initial_state) if verbose: print("done !") initial_distance = initial_solution.objective() if draw: initial_solution.draw() number_of_node_features = len( initial_solution.dgl_graph.ndata['n_feat'][0]) number_of_edge_features = len( initial_solution.dgl_graph.edata['e_feat'][0]) # Initialize ALNS random_state = rnd.RandomState(seed) alns = ALNS(random_state) # ml_removal_heuristic = define_ml_removal_heuristic(number_of_node_features, number_of_edge_features, # NETWORK_PARAMS_FILE, NETWORK_GCN) # ml_removal_heuristic = define_ml_removal_heuristic(number_of_node_features, number_of_edge_features, # 'GatedGCN_ep71.pkl', NETWORK_GATEDGCN) alns.add_destroy_operator(removal_heuristic) alns.add_repair_operator(greedy_insertion) initial_temperature = compute_initial_temperature( initial_distance, start_temperature_control) criterion = SimulatedAnnealing(initial_temperature, end_temperature, cooling_rate) # Solve the cvrp using ALNS print("Starting ALNS, with {} iterations".format(iterations), flush=True) time_start = time.time() result = alns.iterate(initial_solution, weights, operator_decay, criterion, iterations=iterations, collect_stats=collect_statistics) time_end = time.time() print("ALNS finished in {:.1f} seconds".format(time_end - time_start), flush=True) solution = result.best_state print("Initial objective : {:.4f}".format(initial_distance)) print("Solution objective : {:.4f}".format(solution.objective())) if draw: solution.draw() # Create the statistics if necessary solution_data = {} if solution.collect_alns_statistics: solution_data = { 'Size': solution.size, 'Number_of_depots': solution.number_of_depots, 'Capacity': solution.capacity, 'Seed': seed, 'Parameters': { 'decay': operator_decay, 'degree_destruction': DEGREE_OF_DESTRUCTION, 'determinism': DETERMINISM } } solution_statistics = [{ 'destroyed_nodes': solution.statistics['destroyed_nodes'][i], 'objective_difference': result.statistics.objectives[i + 1] - result.statistics.objectives[i], 'list_of_edges': solution.statistics['list_of_edges'][i] } for i in range(iterations)] solution_data['Statistics'] = solution_statistics if 'display_proportion_objective_difference' in kwargs and kwargs[ 'display_proportion_objective_difference']: number_of_elements_train_set = {0: 0, 1: 0, 2: 0} for stat in solution_statistics: if stat['objective_difference'] > EPSILON: stat_class = 0 elif abs(stat['objective_difference']) < EPSILON: stat_class = 1 else: stat_class = 2 number_of_elements_train_set[stat_class] += 1 print("{:^20}{:^7.2%}{:^7.2%}{:^7.2%}\n\n".format( 'Proportions', round(number_of_elements_train_set[0] / iterations, 4), round(number_of_elements_train_set[1] / iterations, 4), round(number_of_elements_train_set[2] / iterations, 4), )) if 'pickle_single_stat' in kwargs and kwargs['pickle_single_stat']: if 'file_path' in kwargs: pickle_alns_solution_stats(solution_data, file_path=kwargs['file_path']) else: pickle_alns_solution_stats(solution_data) return solution_data
def test_does_not_raise(): """ This set of parameters should work correctly. """ SimulatedAnnealing(10, 5, 2)
def test_step(): """ Tests if the step parameter is correctly set. """ for step in range(100): assert_equal(SimulatedAnnealing(1, 1, step).step, step)
def Large_Neighborhood_Search(Y): class TopKState(State): """ Solution class for the top k worker task assignment problem, Series (vector) of tasks as index and workers as values the assignment quality matrix """ def __init__(self, solution, AssQuality_matrix): self.solution = solution # vector of tasks as indcies and workers as values self.tasks = solution.index self.workers = [ self.solution[task] for task in self.solution.index ] self.L_dash = [] # to keep the removed workers list self.AssQuality_matrix = AssQuality_matrix # the assignment values matrix A def copy(self): """ Helper method to ensure each solution state is immutable. """ return TopKState(self.solution.copy(), self.AssQuality_matrix.copy()) def objective(self): """ The objective function is simply the sum of all Assignment qualities """ value = 0.0 for task in self.solution.index: if not pd.isna(self.solution[task]): value += self.AssQuality_matrix.loc[task, self.solution[task]] return -value def find_L(self): worker_lose = pd.Series() lose = pd.Series() for worker in self.workers: worker_lose[worker] = 0.0 for task in self.tasks: if (self.solution[task]) == worker: worker_lose[worker] += self.AssQuality_matrix.loc[ task, worker] # total objective value (mins) the quality of the worker at hand lose.loc[worker] = self.objective() - (self.objective() - worker_lose[worker]) lose = lose.sort_values(ascending=True) return list(lose.index) """----------------------------------------------------------------------------""" """ Destroy method """ """----------------------------------------------------------------------------""" degree_of_destruction = random.random() # Random float x, 0.0 <= x < 1.0 def workers_to_remove(state): # How many worker to be removed based on the degree of destruction return int(len(state.workers) * degree_of_destruction) def worst_removal(current, random_state): """ Worst removal iteratively removes the 'worst' workers, that is, those workers that have the lowest quality. """ destroyed = current.copy() worst_workers = destroyed.find_L() # L h = workers_to_remove(current) # h the number of workers to be removed p = 5 # the parameter p set to 5 according to ref ---- while (h > 0): for task in destroyed.tasks: z = random.random() # random number in [0,1) E = int((z**p) * len(worst_workers)) if destroyed.solution.loc[task] == worst_workers[ E]: # try to find the worst worker destroyed.solution.loc[ task] = np.nan # set the task with the worst worker to NAN destroyed.workers.remove( worst_workers[E] ) # remove the worst worker from the solution destroyed.L_dash.append(worst_workers[E]) h = h - 1 return destroyed """----------------------------------------------------------------------------""" """ Repair method """ """----------------------------------------------------------------------------""" def greedy_repair(current, random_state): """ Greedily repairs a solution, """ # each worker has a capacity capacity = pd.Series() for worker in current.L_dash: capacity[worker] = 10 current.L_dash = set( current.L_dash) # L' the list of removed workers from Y' U_dash = [] # U' the list of unassigned task in Y' for task in current.solution.index: if pd.isna(current.solution.loc[task]): U_dash.append(task) # the objective value of the destroyed solution objective_value_of_destroyed = current.objective() # find Delta fw, Delta_f = pd.DataFrame(index=[task for task in U_dash]) for task in U_dash: for worker in current.L_dash: Delta_f.loc[ task, worker] = (objective_value_of_destroyed + current.AssQuality_matrix.loc[task, worker] ) - objective_value_of_destroyed for task in U_dash: if (capacity[Delta_f.loc[task, :].idxmax()]) > 0: current.solution.loc[task] = Delta_f.loc[task, :].idxmax( ) # Get the BEST worker for the task at hand capacity[Delta_f.loc[ task, :].idxmax()] -= 1 # reduce the capacity by one if (capacity[Delta_f.loc[task, :].idxmax()]) == 0: Delta_f.loc[:, Delta_f.loc[task, :].idxmax( )] = 0.0 # Burn the Best worker (Best worker will not be chosen next time) return current AssQuality_matrix = pd.read_csv( "C:/Users/Arwa/Desktop/datasets/MOO/A_matrix.csv", index_col=0) AssQuality_matrix = AssQuality_matrix.fillna(0.0) random_state = rnd.RandomState( SEED ) #generating random numbers drawn from a variety of probability distributions state = TopKState(Y, AssQuality_matrix) initial_solution = greedy_repair(state, random_state) print("########################initial###########################", type(initial_solution)) print("Initial solution objective is {0}.".format( initial_solution.objective())) """----------------------------------------------------------------------------""" """ Heuristic solution """ """----------------------------------------------------------------------------""" alns = ALNS(random_state) alns.add_destroy_operator(worst_removal) alns.add_repair_operator(greedy_repair) criterion = SimulatedAnnealing( 1, 0.1, 0.6) #'start_temperature', 'end_temperature', and 'step' result = alns.iterate(initial_solution, [3, 2, 1, 0.5], 0.8, criterion, iterations=100, collect_stats=True) H_solution = result.best_state objective = H_solution.objective() print( "########################the best heuristic solution########################\n", H_solution.solution, "with objective value\n", objective) return H_solution.solution, objective