in src/genetic_algorithm.py [0:0]
def find_best_path():
current_generation = create_random_initial_population()
generation_number = 1
best_distance_all_time = 99999999
best_candidate_all_time = None
best_solution_generation_number = 0
per_generation_best_scores = []
# the multiprocessing code doesn't work on Windows
use_multiprocessing = "win" not in sys.platform or sys.platform == "darwin"
pool = multiprocessing.Pool()
job_start_time = time.time()
while True:
generation_start_time = time.time()
uniq_scores = set()
if use_multiprocessing:
# this function calls calc_score_for_candidate for each member of current_generation,
# then combines the results into the scores list:
scores = pool.map(calc_score_for_candidate, current_generation)
for index, candidate in enumerate(current_generation):
candidate.fitness_score = scores[index]
uniq_scores.add(candidate.fitness_score)
else:
for candidate in current_generation:
# check_candidate_validity(candidate)
candidate.fitness_score = calc_score_for_candidate(candidate)
uniq_scores.add(candidate.fitness_score)
num_uniq_fitness_scores = len(uniq_scores)
# find the best one this generation
best_candidate_this_generation = min(current_generation, key=lambda c: c.fitness_score)
per_generation_best_scores.append(best_distance_all_time)
# did this generation give us a new all-time best?
if best_candidate_this_generation.fitness_score < best_distance_all_time:
# make a copy, since the best candidate of this generation may be used
# in later generations (and therefore possibly modified)
best_candidate_all_time = copy.deepcopy(best_candidate_this_generation)
best_distance_all_time = best_candidate_this_generation.fitness_score
best_solution_generation_number = generation_number
else:
# have we gone many generations without improvement? If so, we should exit
if (generation_number - best_solution_generation_number) >= MAX_STAGNANT_GENERATIONS:
break
# alternatively, if we've hit the maximum number of generations, exit
if generation_number > MAX_GENERATIONS:
break
# now create the next generation, starting with elites
num_elites = int(ELITISM_RATE * POPULATION_SIZE)
current_generation.sort(key=lambda c: c.fitness_score)
next_generation = [current_generation[i] for i in range(num_elites)]
# then populate the rest of the next generation
num_to_add = POPULATION_SIZE - num_elites
for _ in range(num_to_add):
parent1, parent2 = select_parents(current_generation)
child1, child2 = crossover_parents_to_create_children(parent1, parent2)
mutate_candidate_maybe(child1)
next_generation.append(child1)
mutate_candidate_maybe(child2)
next_generation.append(child2)
# print per-generation stats
gen_num_str = '{:>4}'.format(generation_number)
low_score_str = '{:>6}'.format(str(best_distance_all_time))
duration = '{:4.1f}'.format(time.time() - generation_start_time)
uniq_str = '{:>4}'.format(num_uniq_fitness_scores)
print(f'Gen {gen_num_str} best: {low_score_str} uniq: {uniq_str} dur: {duration}s ')
# now that the next generation is ready, replace the current generation with it
current_generation = next_generation
generation_number += 1
# we drop out of the loop once we go stagnant, or hit a maximum number of generations
job_total_time = time.time() - job_start_time
total_minutes = '{:6.1f}'.format(job_total_time / 60.0)
print(f'Job complete. Total duration: {total_minutes} min over {generation_number - 1} generations')
return best_candidate_all_time, per_generation_best_scores