-
Notifications
You must be signed in to change notification settings - Fork 0
/
genetic.py
143 lines (121 loc) · 5.57 KB
/
genetic.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
import time
class AlgorithmFunctionsConfig(object):
def __init__(self, parent_selectors, replace_selectors, crossover_function, mutation_instance, stop_instance_list):
"""Returns a Genetic Algorithm Functions instance
Parameters
----------
parent_selectors : CombinedSelector
Combined selector to use when selecting K parents
replace_selectors : CombinedSelector
Combined selector to use when selecting N players for next gen
crossover_function : function
Function used to perform crossover between 2 parents, generating 2 childs
mutation_instance : Mutation
Instance used to perform mutations on a player
stop_instance_list : List of Stopper
List of instances used to determine when to stop iteration
"""
self.parent_selectors = parent_selectors
self.replace_selectors = replace_selectors
self.crossover_function = crossover_function
self.mutation_instance = mutation_instance
self.stop_instance_list = stop_instance_list
class GeneticAlgorithm(object):
def __init__(self, base_gen_collection, son_count, function_config, fill_all):
"""Returns a Genetic Algorithm instance
Parameters
----------
base_gen_collection : list
Generation 0 of players. N = len(base_gen_collection)
son_count : int
Number of childs to generate in each iteration. K = son_count
function_config : AlgorithmFunctionsConfig
Object with desired functions for selectors, crossover, mutation and stopping
fill_all : boolean
If true, use fill_all. If false, use fill_parent
"""
self.player_collection = base_gen_collection
self.N = len(base_gen_collection)
self.K = son_count
self.function_config = function_config
self.fill_all = fill_all
self.start_time = time.time()
self.generation_count = 0
self.previous_generation = {}
self.update_algo_stats()
self.current_stopper = None
def update_algo_stats(self):
generation_changes = 0
current_generation = {}
sum_fitness = 0
worst_fit = best_fit = self.player_collection[0]
for player in self.player_collection:
# Update best fit & worst fit
if player > best_fit: best_fit = player
if player < worst_fit: worst_fit = player
# Accumulate fitness
sum_fitness += player.fitness()
# ------------------------------------------
# Control changes with previous generation
if player in self.previous_generation and self.previous_generation[player] > 0:
self.previous_generation[player] -= 1
else:
generation_changes += 1
# Save current generation
if player not in current_generation:
current_generation[player] = 0
current_generation[player] += 1
# Update fitness values
self.best_fit = best_fit
self.worst_fit = worst_fit
self.avg_fitness = sum_fitness / self.N
# Update changes
self.generation_changes = generation_changes
self.previous_generation = current_generation
# Update diversity
self.diversity = len(current_generation) / self.N
def is_algorithm_over(self):
return any(stopper.is_algorithm_over(self) for stopper in self.function_config.stop_instance_list)
def iterate(self):
# Select K parents from player_collection
parent_collection = self.function_config.parent_selectors.get_count(
self.K, self.player_collection, self.generation_count)
# Cross parents, generating K childs
child_collection = []
for i in range(0, self.K - 1, 2):
child_collection.extend(self.function_config.crossover_function(
parent_collection[i], parent_collection[i + 1]))
# If last parent remained unmatched, add it to child collection
if len(child_collection) != self.K:
child_collection.append(parent_collection[self.K - 1])
# Mutate each child
mutated_child_collection = []
for child in child_collection:
mutated_child_collection.append(self.function_config.mutation_instance.mutate(child))
# Choose N from N (actual) + K (childs) to save new generation
if self.fill_all:
self.player_collection = self.do_fill_all(mutated_child_collection)
else:
self.player_collection = self.do_fill_parent(mutated_child_collection)
# Update general values
self.generation_count += 1
self.update_algo_stats()
return self.player_collection
def do_fill_all(self, child_collection):
# Select N from N (actual) + K (childs)
child_collection.extend(self.player_collection)
return self.function_config.replace_selectors.get_count(
self.N, child_collection, self.generation_count
)
def do_fill_parent(self, child_collection):
if self.K > self.N:
# Select N from K (childs)
return self.function_config.replace_selectors.get_count(
self.N, child_collection, self.generation_count
)
# K <= N
# K childs + Select N-K from N (actual)
child_collection.extend(self.function_config.replace_selectors.get_count(
self.N - self.K, self.player_collection, self.generation_count
))
return child_collection