Skip to content
Snippets Groups Projects
Commit 4e61f9a7 authored by Crystal Chang Din's avatar Crystal Chang Din
Browse files

Create a new crossover algorithm

clean up the code
add docstring to the one_coordinate(start_point, side_length) and two_coordinates_are_the_same(coordinates)
fixed the bug: ensure that the number of the generated children is the same as the initial population
change the termination condition of the genetic algorithm: terminate when the average fitness score converge
parent 05b0efaa
No related branches found
No related tags found
1 merge request!16Create a new crossover algorithm
...@@ -3,8 +3,8 @@ ...@@ -3,8 +3,8 @@
import src.genetic_algo as ga import src.genetic_algo as ga
import bike.simu_bike as sb import bike.simu_bike as sb
gen=ga.Genetic_algorithm(population_size=5, gen=ga.Genetic_algorithm(population_size=15,
start_point=(0,5), start_point=(0,5),
side_length=5, side_length=3,
fit_func=sb.creat_bike) fit_func=sb.creat_bike)
gen.run() gen.run()
\ No newline at end of file
...@@ -3,23 +3,46 @@ import math ...@@ -3,23 +3,46 @@ import math
import json import json
import numpy as np import numpy as np
def calculate_distance(ind):
x1, y1 = ind[0]
x2, y2 = ind[1]
return math.sqrt((x2-x1)**2 + (y2-y1)**2)
def fit_func(ind):
return ind[0][0]**2
def two_coordinates_are_the_same(coordinates): def two_coordinates_are_the_same(coordinates):
"""Checking if there are two coordinates in the given list of coordinates that are identical
This function checks if there are two coordinates in the given list of coordinates that are identical.
Parameters
----------
coordinates : list
A list of four coordinates, each is [(x1, y1), (x2, y2)]
returns:
----------
Bool
Returns True if there are two identical ones, othereise, False
"""
for i in range(len(coordinates)-1): for i in range(len(coordinates)-1):
for j in range(i+1, len(coordinates)): for j in range(i+1, len(coordinates)):
if coordinates[i] == coordinates[j]: if coordinates[i] == coordinates[j]:
return True return True
return False return False
def one_coordinate(start_point, side_length): def one_coordinate(start_point, side_length):
"""Generate a coordinate
This function generates a coordinate.
Parameters
----------
start_point : tuple
Two dimentional point representing a staring point for a square box
side_lenght : int
A integer number representing the side lenght of a square pox
returns:
----------
list of tuples
Random coordinate of one point that is inside a square-box that is defined by the input parameters
"""
return [random.uniform(start_point[0], start_point[0]+side_length), random.uniform(start_point[1], start_point[1]+side_length)] return [random.uniform(start_point[0], start_point[0]+side_length), random.uniform(start_point[1], start_point[1]+side_length)]
def generate_random_coordinates(start_point, side_length): def generate_random_coordinates(start_point, side_length):
...@@ -63,7 +86,8 @@ class Individual(): ...@@ -63,7 +86,8 @@ class Individual():
set_coordinates(coordinates) set_coordinates(coordinates)
reset Indivual by given coordinatge reset Indivual by given coordinatge
set_fitness_score(fitness_score) set_fitness_score(fitness_score)
reset fitness score of individual """ reset fitness score of individual
"""
def __init__(self, coordinates): def __init__(self, coordinates):
self.coordinates = coordinates self.coordinates = coordinates
self.fitness_score = 0 self.fitness_score = 0
...@@ -95,9 +119,8 @@ class Population(): ...@@ -95,9 +119,8 @@ class Population():
reset the population list to a given population list reset the population list to a given population list
average_fitness_score(): average_fitness_score():
calculate average fitness score of population calculate average fitness score of population
best_fitness_score(): save_population_to_file(filename):
calculate the maximaum fitness score of population save the population data into a json file
""" """
def __init__(self, size, start_point, side_length, fit_func): def __init__(self, size, start_point, side_length, fit_func):
self.__size = size # this one should be a constant self.__size = size # this one should be a constant
...@@ -107,7 +130,6 @@ class Population(): ...@@ -107,7 +130,6 @@ class Population():
self.population = [Individual(coordinates=generate_random_coordinates(self.__start_point, self.__side_length)) for _ in range(self.__size)] self.population = [Individual(coordinates=generate_random_coordinates(self.__start_point, self.__side_length)) for _ in range(self.__size)]
for i in self.population: for i in self.population:
i.set_fitness_score(self.fit_func(np.array(i.coordinates))) i.set_fitness_score(self.fit_func(np.array(i.coordinates)))
def get_size(self): def get_size(self):
return self.__size return self.__size
def set_population(self, population): def set_population(self, population):
...@@ -117,8 +139,6 @@ class Population(): ...@@ -117,8 +139,6 @@ class Population():
for i in self.population: for i in self.population:
total += i.fitness_score total += i.fitness_score
return total / self.__size return total / self.__size
def best_fitness_score(self):
return max([x.fitness_score for x in self.population])
def save_population_to_file(self,filename): def save_population_to_file(self,filename):
with open(filename,"w") as f: with open(filename,"w") as f:
json.dump([ind.coordinates for ind in self.population],f) json.dump([ind.coordinates for ind in self.population],f)
...@@ -180,7 +200,6 @@ class Genetic_algorithm(): ...@@ -180,7 +200,6 @@ class Genetic_algorithm():
population_list = population.population population_list = population.population
sorted_population = sorted(population_list, key=lambda x: x.fitness_score) sorted_population = sorted(population_list, key=lambda x: x.fitness_score)
sorted_population.reverse() sorted_population.reverse()
#print(f'select top {percentage} from : ', [i.fitness_score for i in sorted_population])
######### shrink the size ######## ######### shrink the size ########
count_top_individuals = self.__population_size*percentage count_top_individuals = self.__population_size*percentage
if count_top_individuals < 1: if count_top_individuals < 1:
...@@ -192,7 +211,6 @@ class Genetic_algorithm(): ...@@ -192,7 +211,6 @@ class Genetic_algorithm():
self.population_size = count_top_individuals self.population_size = count_top_individuals
################################## ##################################
top_individuals = sorted_population[:count_top_individuals] top_individuals = sorted_population[:count_top_individuals]
#print('top_individuals: ', [i.fitness_score for i in top_individuals])
return top_individuals return top_individuals
def crossover(self, top_individuals): def crossover(self, top_individuals):
...@@ -214,23 +232,47 @@ class Genetic_algorithm(): ...@@ -214,23 +232,47 @@ class Genetic_algorithm():
list list
a list of Individual objects a list of Individual objects
''' '''
### randomy select two and two as parents ###
children = [] children = []
for _ in range((self.__population_size//(len(top_individuals)//2))//2): count_children = 0
breaking_point = False
while True:
### randomy select two and two as parents ###
random.shuffle(top_individuals) random.shuffle(top_individuals)
for i in range(0,len(top_individuals),2): for i in range(0,len(top_individuals),2):
mom = top_individuals[i] mom = top_individuals[i]
dad = top_individuals[i+1] dad = top_individuals[i+1]
coordinate1 = [dad.coordinates[0], dad.coordinates[1], mom.coordinates[2], mom.coordinates[3]] #
dad_low_mean = (dad.coordinates[0][1] + dad.coordinates[1][1]) / 2
mom_low_mean = (mom.coordinates[0][1] + mom.coordinates[1][1]) / 2
mom_dad_diff = abs((dad_low_mean + mom_low_mean) / 2)
# version 1
#coordinate1 = [dad.coordinates[0], dad.coordinates[1], mom.coordinates[2], mom.coordinates[3]]
# version 2
coordinate1 = [[mom.coordinates[0][0], mom_dad_diff], [mom.coordinates[1][0], mom_dad_diff], mom.coordinates[2], mom.coordinates[3]]
child1 = Individual(coordinates=coordinate1) child1 = Individual(coordinates=coordinate1)
child1.set_fitness_score(self.fit_func(np.array(coordinate1))) child1.set_fitness_score(self.fit_func(np.array(coordinate1)))
children.append(child1) children.append(child1)
coordinate2 = [mom.coordinates[0], mom.coordinates[1], dad.coordinates[2], dad.coordinates[3]] count_children += 1
if count_children == self.__population_size:
breaking_point = True
break
# version 1
#coordinate2 = [mom.coordinates[0], mom.coordinates[1], dad.coordinates[2], dad.coordinates[3]]
# version 2
coordinate2 = [[dad.coordinates[0][0], mom_dad_diff], [dad.coordinates[1][0], mom_dad_diff], dad.coordinates[2], dad.coordinates[3]]
child2 = Individual(coordinates=coordinate2) child2 = Individual(coordinates=coordinate2)
child2.set_fitness_score(self.fit_func(np.array(coordinate2))) child2.set_fitness_score(self.fit_func(np.array(coordinate2)))
children.append(child2) children.append(child2)
count_children += 1
if count_children == self.__population_size:
breaking_point = True
break
if breaking_point:
break
return children return children
def mutation(self, population,mutation_frequency=0.2): def mutation(self, population,mutation_frequency=0.2):
'''shuffle coordinates of individual in a given population '''shuffle coordinates of individual in a given population
...@@ -250,13 +292,9 @@ random.randint(start_point[1], start_point[1]+side_length)), ...@@ -250,13 +292,9 @@ random.randint(start_point[1], start_point[1]+side_length)),
random.shuffle(population.population) # random.shuffle(population.population) #
children = population.population[:int(self.__population_size*mutation_frequency)] children = population.population[:int(self.__population_size*mutation_frequency)]
for c in children: for c in children:
#random.shuffle(c.coordinates) # Think about a better way for mutation
gene_index=random.choice([0,1,2,3]) gene_index=random.choice([0,1,2,3])
c.coordinates[gene_index] = one_coordinate(self.__start_point, self.__side_length) c.coordinates[gene_index] = one_coordinate(self.__start_point, self.__side_length)
c.set_fitness_score(self.fit_func(np.array(c.coordinates))) c.set_fitness_score(self.fit_func(np.array(c.coordinates)))
#print(f'mutated children : ', [i.fitness_score for i in population.population])
def run(self): def run(self):
...@@ -268,12 +306,12 @@ random.randint(start_point[1], start_point[1]+side_length)), ...@@ -268,12 +306,12 @@ random.randint(start_point[1], start_point[1]+side_length)),
# START # START
# Generate the initial population and Compute fitness # Generate the initial population and Compute fitness
self.p = Population(self.__population_size, self.__start_point, self.__side_length, self.fit_func) self.p = Population(self.__population_size, self.__start_point, self.__side_length, self.fit_func)
average_fitness_score = self.p.average_fitness_score() average_fitness_score = self.p.average_fitness_score()
best_fitness_score = self.p.best_fitness_score()
# REPEAT # REPEAT
last_ten_average = [0]*10
file_counter=0 file_counter=0
while abs(best_fitness_score - average_fitness_score) > 0.5: while abs(average_fitness_score- np.mean(last_ten_average[-10:])) > 0.1:
last_ten_average.append(self.p.average_fitness_score())
self.p.save_population_to_file("data/population_"+str(file_counter)+".json") self.p.save_population_to_file("data/population_"+str(file_counter)+".json")
file_counter+=1 file_counter+=1
# Selection # Selection
...@@ -284,17 +322,10 @@ random.randint(start_point[1], start_point[1]+side_length)), ...@@ -284,17 +322,10 @@ random.randint(start_point[1], start_point[1]+side_length)),
# Mutation and Compute fitness # Mutation and Compute fitness
self.mutation(self.p) self.mutation(self.p)
average_fitness_score = self.p.average_fitness_score() average_fitness_score = self.p.average_fitness_score()
best_fitness_score = self.p.best_fitness_score() last_10_average = sum(last_ten_average[-10:]) / len(last_ten_average[-10:])
print(f'{average_fitness_score=}, {best_fitness_score=}') print(f'{average_fitness_score=}, {last_10_average=}, {len(self.p.population)}')
# UNTIL population has converged # UNTIL population has converged
# STOP # STOP
#return the fittest child among the last siblings
# remaining_sibling = sorted(p.population, key=lambda x: x.fitness_score)
# remaining_sibling.reverse()
# return remaining_sibling[0]
if __name__ == '__main__': if __name__ == '__main__':
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment