First draft of the genetic algorithm.

This commit is contained in:
Cory Balaton 2024-09-29 10:45:08 +02:00
parent 117446ffb0
commit b475d970b4
Signed by: coryab
GPG Key ID: F7562F0EC4E4A61B

110
genetic_algorithm.py Normal file
View File

@ -0,0 +1,110 @@
import random
from typing import Tuple
import numpy as np
import numpy.typing as npt
from common import plot_plan, read_data
class GeneticTSP:
def __init__(self, population: int, crossover_prob: float, mutation_prob: float, data):
self.generation: int = 0
self.population: int = population
self.data: npt.NDArray = data
self.genes: int = len(data)
self.crossover_prob: float = crossover_prob
self.mutation_prob: float = mutation_prob
self.generate_first_generation()
def generate_first_generation(self):
self.candidates: npt.NDArray = np.array([np.random.permutation(np.arange(self.genes)) for _ in range(self.population)])
def get_distance(self, candidate):
return sum([self.data[candidate[i - 1], candidate[i]] for i in range(self.genes)])
def fitness(self):
distances = np.array([ self.get_distance(candidate) for candidate in self.candidates ])
max_distance = max(distances)
fitness = max_distance - distances
fitness_sum = sum(fitness)
self.fitness_probs: npt.NDArray = fitness / fitness_sum
def crossover(self, parent1: npt.NDArray, parent2: npt.NDArray) -> Tuple[npt.NDArray, npt.NDArray]:
if self.crossover_prob < np.random.random():
return (parent1, parent2)
cut: int = np.random.randint(0, self.genes)
offspring1 = parent1[:cut]
offspring2 = parent2[:cut]
offspring1 = np.concatenate((offspring1, np.array([gene for gene in parent2 if gene not in offspring1])))
offspring2 = np.concatenate((offspring2, np.array([gene for gene in parent1 if gene not in offspring2])))
return (offspring1, offspring2)
def mutate(self, offspring):
if self.mutation_prob < np.random.random():
return
pos1: int = np.random.randint(0, self.genes)
pos2: int = np.random.randint(0, self.genes)
offspring[pos1], offspring[pos2] = offspring[pos2], offspring[pos1]
def select_individual(self):
choice = np.random.choice(self.population, 1, p=self.fitness_probs)[0]
return self.candidates[choice]
def generate_next_generation(self):
new_generation = []
self.fitness()
for _ in range(0,self.population,2):
offspring1, offspring2 = self.crossover(self.select_individual(), self.select_individual())
self.mutate(offspring1)
self.mutate(offspring2)
new_generation.append(offspring1)
new_generation.append(offspring2)
self.candidates = np.array(new_generation)
def run(self, generations = 10):
for _ in range(generations):
self.generate_next_generation()
def get_candidates(self):
return self.candidates
if __name__ == "__main__":
cities, data = read_data("./european_cities.csv")
np.random.seed(1987)
gen = GeneticTSP(500, .8, .4, data[:10,:10])
original_cands = gen.get_candidates()
res = [ (gen.get_distance(cand), cand) for cand in original_cands ]
res.sort(key=lambda i: i[0])
print(f"Original population")
print(f"Distance: {res[0][0]}")
plot_plan([ cities[i] for i in res[-1][1] ])
gen.run(500)
arr = gen.get_candidates()
res = [ (gen.get_distance(cand), cand) for cand in arr ]
res.sort(key=lambda i: i[0])
print(f"Improved population")
print(f"Distance: {res[0][0]}")
plot_plan([ cities[i] for i in res[0][1] ])