diff --git a/src/genetic_algorithm.py b/src/genetic_algorithm.py index 9fab9b3..d2152de 100644 --- a/src/genetic_algorithm.py +++ b/src/genetic_algorithm.py @@ -51,7 +51,7 @@ def evaluate_individual(individual, data): def select_distinct_genes(matching_genes, parents, m): first_parent = parents[0].query("point not in @matching_genes") second_parent = parents[1].query("point not in @matching_genes") - cutoff = randint(len(first_parent.point.values)) + cutoff = randint(m - len(matching_genes)) first_parent_genes = first_parent.point.values[cutoff:] second_parent_genes = second_parent.point.values[:cutoff] return first_parent_genes, second_parent_genes @@ -65,18 +65,28 @@ def select_random_genes(matching_genes, parents, m): return genes +def select_random_parent(parents): + random_index = randint(len(parents)) + random_parent = parents[random_index] + if random_parent.point.empty: + opposite_index = 1 - random_index + random_parent = parents[opposite_index] + return random_parent + + def repair_offspring(offspring, parents, m): while len(offspring) != m: if len(offspring) > m: best_index = offspring["distance"].idxmax() offspring.drop(index=best_index, inplace=True) elif len(offspring) < m: - random_parent = parents[randint(len(parents))] + # NOTE Refactor into its own function while True: + random_parent = select_random_parent(parents) best_index = random_parent["distance"].idxmax() best_point = random_parent["point"].loc[best_index] random_parent.drop(index=best_index, inplace=True) - if not any(offspring["point"].isin([best_point])): + if best_point not in offspring.point.values: break offspring = offspring.append( {"point": best_point, "distance": 0, "fitness": 0}, ignore_index=True @@ -119,7 +129,7 @@ def position_crossover(parents, m): def crossover(mode, parents, m): - split_parents = [parents[i : i + 2] for i in range(0, len(parents), 2)] + split_parents = zip(*[iter(parents)] * 2) if mode == "uniform": crossover_func = partial(uniform_crossover, m=m) else: @@ -140,25 +150,36 @@ def select_new_gene(individual, n): return new_gene -def mutate(population, n, probability=0.001): - expected_mutations = len(population) * n * probability +def mutate(offspring, data, probability=0.001): + expected_mutations = len(offspring) * n * probability individuals = [] genes = [] for _ in range(ceil(expected_mutations)): - individuals.append(randint(n)) + individuals.append(randint(len(offspring))) current_individual = individuals[-1] - genes.append(population[current_individual].sample().index) + genes.append(offspring[current_individual].sample().index) for ind, gen in zip(individuals, genes): - individual = population[ind] + individual = offspring[ind] individual["point"].iloc[gen] = select_new_gene(individual, n) - individual["distance"].iloc[gen] = 0 - return population + individual["distance"].iloc[gen] = compute_distance( + element=individual["point"].iloc[gen].values[0], + individual=individual, + data=data, + ) + return offspring -def tournament_selection(m, population): - individuals = [population[randint(m)] for _ in range(2)] - best_index = population.index(max(population, key=lambda x: all(x.fitness))) - return individuals[best_index] +def get_individual_index(population, element): + for index in range(len(population)): + if population[index].fitness.values[0] == element.fitness.values[0]: + return index + + +def tournament_selection(population): + individuals = [population[randint(len(population))] for _ in range(2)] + best_element = max(individuals, key=lambda x: x.fitness.values[0]) + population_index = get_individual_index(population, best_element) + return best_element, population_index def generational_replacement(previous_population, current_population): @@ -209,20 +230,41 @@ def evaluate_population(population, data, cores=4): return evaluated_population -def select_new_population(population, n, m, mode): +def select_parents(population, n, mode): + select_population = population + parents = [] if mode == "generational": - parents = [tournament_selection(m, population) for _ in range(n)] + for _ in range(n): + element, index = tournament_selection(population=select_population) + parents.append(element) + select_population.pop(index) else: - parents = [tournament_selection(m, population) for _ in range(2)] + for _ in range(2): + element, index = tournament_selection(population=select_population) + parents.append(element) + select_population.pop(index) return parents -def genetic_algorithm(n, m, data, mode, max_iterations=100000): +def genetic_algorithm(n, m, data, select_mode, crossover_mode, max_iterations=100000): population = [generate_individual(n, m, data) for _ in range(n)] population = evaluate_population(population, data) for _ in range(max_iterations): - parents = select_new_population(population, n, m, mode) + parents = select_parents(population, n, select_mode) + offspring = crossover(crossover_mode, parents, m) + offspring = mutate(offspring, data) + population = replace_population(population, offspring, select_mode) + population = evaluate_population(population, data) + best_solution, _ = get_best_elements(population) + return best_solution n, m, data = parse_file("data/GKD-c_11_n500_m50.txt") -genetic_algorithm(n=10, m=5, data=data, mode="generational", max_iterations=1) +genetic_algorithm( + n=10, + m=4, + data=data, + select_mode="generational", + crossover_mode="uniform", + max_iterations=1, +)