Spaces:
Runtime error
Runtime error
| import numpy as np | |
| import pandas as pd | |
| import random | |
| import copy | |
| # Be carefull, in Python lists are passed by reference; a list of list is a list of references to lists | |
| # TSP hyperparameters | |
| grid_size = 10 | |
| # Set seed | |
| def set_seed(seed): | |
| np.random.seed(seed) | |
| random.seed(seed) | |
| # Places to visit (return a list of locations of the TSP) | |
| def places(grid_size,n_places): | |
| points = list(zip(np.random.randint(0,grid_size,n_places),np.random.randint(0,grid_size,n_places))) | |
| while (0,0) in points: | |
| points = list(zip(np.random.randint(0,grid_size,n_places),np.random.randint(0,grid_size,n_places))) | |
| return points | |
| # Add the origin and destination to the sequence-list | |
| def add_extremes(sequence): | |
| return [(0,0)] + sequence + [(0,0)] | |
| # Fitness function (works for one sequence-list) | |
| def distance(sequence): | |
| path=add_extremes(sequence) | |
| return sum(np.sqrt((path[i][0] - path[i+1][0])**2 + (path[i][1] - path[i+1][1])**2) | |
| for i in range(len(path)-1)) | |
| def fitness(solution): | |
| return 1 / distance(solution) # shorter distence higher fitness | |
| # Intial Population (return a list of lists!) | |
| def initial_population(places,population_size): | |
| return [random.sample(places, len(places)) for _ in range(population_size)] | |
| # Selection (random tournament) | |
| def random_tournament(population, tournament_size): | |
| selected_for_next_round = [] | |
| for _ in range(len(population)): | |
| tournament = random.sample(population, tournament_size) | |
| winner = max(tournament, key=fitness) | |
| selected_for_next_round.append(winner.copy()) # Be careful, a copy of a list to get a new solution | |
| return selected_for_next_round | |
| # Crossover (ox crossover) returns two offspring solutions from two parents | |
| # Ox ccopy | |
| def ox_copy(parent1,parent2,start,end): | |
| size = min(len(parent1), len(parent2)) | |
| child=parent1.copy() # Lists are always passed by reference in Phyton! | |
| tmp=parent2.copy() | |
| tmp.reverse() | |
| for place in child[start:end]: | |
| tmp.remove(place) | |
| for j in range(end,size): | |
| child[j] = tmp.pop() | |
| for j in range(0,start): | |
| child[j] = tmp.pop() | |
| return child | |
| # Generate offsprings | |
| def ox_crossover(parent1, parent2): | |
| size = min(len(parent1), len(parent2)) | |
| start, end = sorted(random.sample(range(size), 2)) | |
| child1=ox_copy(parent1,parent2,start,end) | |
| child2=ox_copy(parent2,parent1,start,end) | |
| return child1, child2 | |
| # Mutation | |
| def mutate(solution, mutation_rate): | |
| size=len(solution) | |
| if random.random() < mutation_rate: | |
| # swap two random positions | |
| i,j = random.sample(range(size), 2) | |
| tmp=solution[i] | |
| solution[i]=solution[j] | |
| solution[j]=tmp | |
| return solution | |
| # Genetic algortihm | |
| def genetic_algorithm(targets, population_size, generations, tournament_size, mutation_rate, elitism,crossover): | |
| # Initialize the population | |
| population = initial_population(targets,population_size) | |
| best_solution=max(population, key=fitness) | |
| fitness_evolution=[fitness(best_solution)] | |
| # Track simulating generations | |
| df_simulation = pd.DataFrame({'c%i'%i:[add_extremes(population[i])] for i in range(population_size)}) | |
| e = max(1, int(elitism*population_size)) | |
| e = e if (e % 2 == 0) else e + 1 # size of elitism: even number | |
| nc = int(crossover*population_size) | |
| nc = nc if (nc % 2 == 0) else nc + 1 # size of crossover: even number | |
| for t in range(generations): | |
| # Sort population by fitness in descending order | |
| population = sorted(population, key=fitness, reverse=True) | |
| # Create next generation list | |
| # (1) elite (no mutation) | |
| next_generation = copy.deepcopy(population[:e]) # Keep elite individuals (best solutions) | |
| # Selection | |
| selection = random_tournament(population, tournament_size) | |
| parents=random.sample(selection, len(selection)) # shuffle the selected parents | |
| # (2) direct descendants with mutation | |
| for i in range(population_size - nc - e): | |
| parent = parents.pop() | |
| next_generation.append(mutate(parent,mutation_rate)) | |
| # (3) descendants with croosover and mutation | |
| for i in range(int(nc/2)): | |
| parent1 = parents.pop() | |
| parent2 = parents.pop() | |
| child1, child2 = ox_crossover(parent1, parent2) | |
| next_generation.append(mutate(child1,mutation_rate)) | |
| next_generation.append(mutate(child2,mutation_rate)) | |
| # Update population | |
| population = copy.deepcopy(sorted(next_generation, key=fitness, reverse=True)) | |
| fitness_evolution.append(fitness(max(population, key=fitness))) | |
| # Track simulation data | |
| df_simulation=pd.concat([df_simulation, | |
| pd.DataFrame({'c%i'%i:[add_extremes(population[i])] for i in range(population_size)})],ignore_index=True) | |
| best_solution=max(population, key=fitness) | |
| return add_extremes(best_solution), fitness_evolution, df_simulation |