111 lines
3.4 KiB
Python
111 lines
3.4 KiB
Python
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] ])
|