gimuby 0.7.2
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +1 -0
- data/LICENSE.md +25 -0
- data/README.md +0 -0
- data/lib/gimuby.rb +10 -0
- data/lib/gimuby/config.rb +39 -0
- data/lib/gimuby/dependencies.rb +37 -0
- data/lib/gimuby/event/event.rb +29 -0
- data/lib/gimuby/event/event_manager.rb +34 -0
- data/lib/gimuby/factory.rb +275 -0
- data/lib/gimuby/genetic/archipelago/archipelago.rb +305 -0
- data/lib/gimuby/genetic/archipelago/connect_strategy/barabasi_albert_connect_strategy.rb +77 -0
- data/lib/gimuby/genetic/archipelago/connect_strategy/circle_connect_strategy.rb +11 -0
- data/lib/gimuby/genetic/archipelago/connect_strategy/connect_strategy.rb +34 -0
- data/lib/gimuby/genetic/archipelago/connect_strategy/constant_degree_connect_strategy.rb +22 -0
- data/lib/gimuby/genetic/archipelago/connect_strategy/fully_connected_connect_strategy.rb +11 -0
- data/lib/gimuby/genetic/archipelago/connect_strategy/random_connect_strategy.rb +29 -0
- data/lib/gimuby/genetic/archipelago/connect_strategy/watts_strogatz_connect_strategy.rb +63 -0
- data/lib/gimuby/genetic/archipelago/measure/clustering_coefficient_measure.rb +92 -0
- data/lib/gimuby/genetic/archipelago/measure/connected_measure.rb +64 -0
- data/lib/gimuby/genetic/archipelago/measure/diameter_measure.rb +38 -0
- data/lib/gimuby/genetic/archipelago/measure/measure.rb +7 -0
- data/lib/gimuby/genetic/archipelago/measure/shortest_paths_measure.rb +46 -0
- data/lib/gimuby/genetic/population/pick_strategy/bests_pick_strategy.rb +17 -0
- data/lib/gimuby/genetic/population/pick_strategy/pick_strategy.rb +21 -0
- data/lib/gimuby/genetic/population/pick_strategy/random_wheel_pick_strategy.rb +40 -0
- data/lib/gimuby/genetic/population/pick_strategy/tournament_pick_strategy.rb +26 -0
- data/lib/gimuby/genetic/population/population.rb +97 -0
- data/lib/gimuby/genetic/population/replace_strategy/replace_strategy.rb +9 -0
- data/lib/gimuby/genetic/population/replace_strategy/replace_worst_replace_strategy.rb +52 -0
- data/lib/gimuby/genetic/population/replace_strategy/uniform_replace_strategy.rb +48 -0
- data/lib/gimuby/genetic/solution/check_strategy/check_strategy.rb +8 -0
- data/lib/gimuby/genetic/solution/check_strategy/permutation_check_strategy.rb +37 -0
- data/lib/gimuby/genetic/solution/check_strategy/solution_space_check_strategy.rb +74 -0
- data/lib/gimuby/genetic/solution/function_based_solution.rb +64 -0
- data/lib/gimuby/genetic/solution/mutation_strategy/mutation_strategy.rb +22 -0
- data/lib/gimuby/genetic/solution/mutation_strategy/permutation_mutation_strategy.rb +17 -0
- data/lib/gimuby/genetic/solution/mutation_strategy/solution_space_mutation_strategy.rb +69 -0
- data/lib/gimuby/genetic/solution/new_generation_strategy/average_new_generation_strategy.rb +41 -0
- data/lib/gimuby/genetic/solution/new_generation_strategy/combined_new_generation_strategy.rb +27 -0
- data/lib/gimuby/genetic/solution/new_generation_strategy/cross_over_new_generation_strategy.rb +40 -0
- data/lib/gimuby/genetic/solution/new_generation_strategy/new_generation_strategy.rb +9 -0
- data/lib/gimuby/genetic/solution/new_generation_strategy/parent_range_new_generation_strategy.rb +42 -0
- data/lib/gimuby/genetic/solution/solution.rb +86 -0
- data/lib/gimuby/problem/foxholes/foxholes.rb +76 -0
- data/lib/gimuby/problem/foxholes/foxholes_solution.rb +29 -0
- data/lib/gimuby/problem/lennard_jones/lennard_jones.rb +38 -0
- data/lib/gimuby/problem/lennard_jones/lennard_jones_solution.rb +62 -0
- data/lib/gimuby/problem/rastrigin/rastrigin.rb +26 -0
- data/lib/gimuby/problem/rastrigin/rastrigin_solution.rb +35 -0
- data/lib/gimuby/problem/rosenbrock/rosenbrock.rb +14 -0
- data/lib/gimuby/problem/rosenbrock/rosenbrock_solution.rb +39 -0
- data/lib/gimuby/problem/schaffer/schaffer.rb +18 -0
- data/lib/gimuby/problem/schaffer/schaffer_solution.rb +29 -0
- data/lib/gimuby/problem/sphere/sphere.rb +9 -0
- data/lib/gimuby/problem/sphere/sphere_solution.rb +27 -0
- data/lib/gimuby/problem/step/step.rb +9 -0
- data/lib/gimuby/problem/step/step_solution.rb +29 -0
- data/lib/gimuby/problem/tsp/tsp.rb +76 -0
- data/lib/gimuby/problem/tsp/tsp_solution.rb +46 -0
- metadata +128 -0
@@ -0,0 +1,305 @@
|
|
1
|
+
require 'gimuby/dependencies'
|
2
|
+
require 'gimuby/genetic/archipelago/connect_strategy/random_connect_strategy'
|
3
|
+
require 'gimuby/genetic/archipelago/measure/clustering_coefficient_measure'
|
4
|
+
require 'gimuby/genetic/archipelago/measure/diameter_measure'
|
5
|
+
require 'gimuby/genetic/archipelago/measure/connected_measure'
|
6
|
+
|
7
|
+
class Archipelago
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@populations = [] # each population is an island
|
11
|
+
@connections = {} # an hash of array containing indexes pointing population,
|
12
|
+
# (sparse adjacency matrix)
|
13
|
+
@connect_strategy = RandomConnectStrategy.new
|
14
|
+
@migration_rate = (1.0/100.0)
|
15
|
+
@migration_type = :random # use also :synchronized or :fixed_time
|
16
|
+
@migration_symmetric = FALSE
|
17
|
+
|
18
|
+
@generation_step_count = 0 #internal usage
|
19
|
+
@populations_to_migrate_indexes = []
|
20
|
+
|
21
|
+
reset_topological_metrics
|
22
|
+
|
23
|
+
trigger(:on_archipelago_init)
|
24
|
+
end
|
25
|
+
|
26
|
+
attr_accessor :populations
|
27
|
+
attr_accessor :connections
|
28
|
+
attr_accessor :connect_strategy
|
29
|
+
attr_accessor :migration_rate
|
30
|
+
attr_accessor :migration_type
|
31
|
+
attr_accessor :migration_symmetric
|
32
|
+
|
33
|
+
# Trigger a generation step on each subpopulation
|
34
|
+
def generation_step
|
35
|
+
reset_populations_migration_state
|
36
|
+
@generation_step_count += 1
|
37
|
+
archipelago_level_migration = FALSE
|
38
|
+
if @migration_type == :synchronized
|
39
|
+
archipelago_level_migration = rand() < @migration_rate
|
40
|
+
end
|
41
|
+
if @migration_type == :fixed_time
|
42
|
+
archipelago_level_migration = should_fixed_time_migrate
|
43
|
+
end
|
44
|
+
@populations.each do |population|
|
45
|
+
population.generation_step
|
46
|
+
migrate(population, archipelago_level_migration)
|
47
|
+
end
|
48
|
+
trigger(:on_archipelago_generation_step)
|
49
|
+
end
|
50
|
+
|
51
|
+
def add_population population
|
52
|
+
@populations.push population
|
53
|
+
reset_topological_metrics
|
54
|
+
end
|
55
|
+
|
56
|
+
# Connect the different populations of the archipelago
|
57
|
+
def connect_all
|
58
|
+
@connect_strategy.connect(self)
|
59
|
+
end
|
60
|
+
|
61
|
+
def get_population_size
|
62
|
+
size = 0
|
63
|
+
@populations.each do |population|
|
64
|
+
size += population.get_population_size
|
65
|
+
end
|
66
|
+
size
|
67
|
+
end
|
68
|
+
|
69
|
+
def get_size
|
70
|
+
@populations.size
|
71
|
+
end
|
72
|
+
|
73
|
+
def get_connected_classes_count
|
74
|
+
ConnectedMeasure.new.compute(self)
|
75
|
+
end
|
76
|
+
|
77
|
+
def get_diameter
|
78
|
+
DiameterMeasure.new.compute(self)
|
79
|
+
end
|
80
|
+
|
81
|
+
def get_clustering_coefficient
|
82
|
+
unless @clustering_coefficient.nil?
|
83
|
+
return @clustering_coefficient
|
84
|
+
end
|
85
|
+
@clustering_coefficient = ClusteringCoefficientMeasure.new.compute(self)
|
86
|
+
end
|
87
|
+
|
88
|
+
# @internal
|
89
|
+
def get_nodes
|
90
|
+
nodes = *(0..@populations.length - 1)
|
91
|
+
nodes
|
92
|
+
end
|
93
|
+
|
94
|
+
# @internal
|
95
|
+
def get_neighbors(node)
|
96
|
+
unless @connections.has_key?(node)
|
97
|
+
return []
|
98
|
+
end
|
99
|
+
@connections[node].clone
|
100
|
+
end
|
101
|
+
|
102
|
+
# Connect two population from their index in the list
|
103
|
+
# @internal
|
104
|
+
def connect(population_index1, population_index2)
|
105
|
+
if population_index1 == population_index2
|
106
|
+
return
|
107
|
+
end
|
108
|
+
add_edge(population_index1, population_index2)
|
109
|
+
add_edge(population_index2, population_index1)
|
110
|
+
reset_topological_metrics
|
111
|
+
end
|
112
|
+
|
113
|
+
# Create a connection from a population to another
|
114
|
+
def add_edge(population_index1, population_index2)
|
115
|
+
if population_index1 == population_index2
|
116
|
+
return
|
117
|
+
end
|
118
|
+
unless @connections.has_key? population_index1
|
119
|
+
@connections[population_index1] = []
|
120
|
+
end
|
121
|
+
unless @connections[population_index1].include? population_index2
|
122
|
+
@connections[population_index1].push(population_index2)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
# Remove a connection
|
127
|
+
def remove_edge(population_index1, population_index2)
|
128
|
+
if @connections.has_key? population_index1
|
129
|
+
@connections[population_index1].delete(population_index2)
|
130
|
+
end
|
131
|
+
if @connections.has_key? population_index2
|
132
|
+
@connections[population_index2].delete(population_index1)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
def has_edge(population_index1, population_index2)
|
137
|
+
unless @connections.has_key? population_index1
|
138
|
+
return FALSE
|
139
|
+
end
|
140
|
+
@connections[population_index1].include? population_index2
|
141
|
+
end
|
142
|
+
|
143
|
+
def get_edges
|
144
|
+
edges = []
|
145
|
+
@connections.each do |node, facing_nodes|
|
146
|
+
facing_nodes.each do |facing_node|
|
147
|
+
edges.push([node, facing_node])
|
148
|
+
end
|
149
|
+
end
|
150
|
+
edges
|
151
|
+
end
|
152
|
+
|
153
|
+
def get_edges_count
|
154
|
+
edges_count = 0
|
155
|
+
@connections.values.each do |target_connections|
|
156
|
+
target_connections_length = target_connections.length
|
157
|
+
edges_count += target_connections_length
|
158
|
+
end
|
159
|
+
edges_count
|
160
|
+
end
|
161
|
+
|
162
|
+
def get_average_degree
|
163
|
+
nodes_count = @populations.length.to_f
|
164
|
+
edges_count = get_edges_count.to_f
|
165
|
+
edges_count / nodes_count
|
166
|
+
end
|
167
|
+
|
168
|
+
def get_average_fitness
|
169
|
+
sum = 0
|
170
|
+
@populations.each do |population|
|
171
|
+
sum += population.get_average_fitness
|
172
|
+
end
|
173
|
+
# Beware of that division by 0
|
174
|
+
sum / @populations.length
|
175
|
+
end
|
176
|
+
|
177
|
+
def get_best_fitness
|
178
|
+
best_solution = get_best_solution
|
179
|
+
best_solution.get_fitness
|
180
|
+
end
|
181
|
+
|
182
|
+
def get_best_solution
|
183
|
+
best_population = @populations.min_by do |population|
|
184
|
+
population.get_best_fitness
|
185
|
+
end
|
186
|
+
best_population.get_best_solution
|
187
|
+
end
|
188
|
+
|
189
|
+
# @internal
|
190
|
+
def connect_path(path, closed_path = FALSE)
|
191
|
+
previous = nil
|
192
|
+
previous = path[-1] if closed_path
|
193
|
+
path.each do |current|
|
194
|
+
connect(previous, current) unless previous.nil?
|
195
|
+
previous = current
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
# @internal
|
200
|
+
def get_degree(node)
|
201
|
+
unless @connections.has_key?(node)
|
202
|
+
return 0
|
203
|
+
end
|
204
|
+
@connections[node].length
|
205
|
+
end
|
206
|
+
|
207
|
+
protected
|
208
|
+
|
209
|
+
def migrate(population, forced = FALSE)
|
210
|
+
if (not forced) && (@migration_type != :random)
|
211
|
+
return FALSE
|
212
|
+
end
|
213
|
+
unless forced
|
214
|
+
r = rand()
|
215
|
+
unless (r <= @migration_rate)
|
216
|
+
return FALSE
|
217
|
+
end
|
218
|
+
end
|
219
|
+
if is_population_migrated(population)
|
220
|
+
# the population has already been migrated, we avoid to do it twice
|
221
|
+
return FALSE
|
222
|
+
end
|
223
|
+
population_index = @populations.index(population)
|
224
|
+
unless @connections.has_key?(population_index)
|
225
|
+
return FALSE
|
226
|
+
end
|
227
|
+
connected_populations_indexes = @connections[population_index]
|
228
|
+
if connected_populations_indexes.length == 0
|
229
|
+
return FALSE
|
230
|
+
end
|
231
|
+
random_index = rand(connected_populations_indexes.length + 1) - 1 # +1 -1 to avoid strange suspected behavior on rand(1)
|
232
|
+
target_population_index = connected_populations_indexes[random_index]
|
233
|
+
if target_population_index.nil? # Why handling that case ? No idea,
|
234
|
+
# a bug occurred randomly
|
235
|
+
return FALSE
|
236
|
+
end
|
237
|
+
target_population = @populations[target_population_index]
|
238
|
+
migrate_from_to(population, target_population)
|
239
|
+
TRUE
|
240
|
+
end
|
241
|
+
|
242
|
+
def migrate_from_to(population, target_population)
|
243
|
+
selected_solutions_1 = population.pick
|
244
|
+
to_migrate_count = selected_solutions_1.length
|
245
|
+
selected_solutions_2 = nil
|
246
|
+
if @migration_symmetric
|
247
|
+
selected_solutions_2 = target_population.pick
|
248
|
+
to_migrate_count += selected_solutions_2.length
|
249
|
+
end
|
250
|
+
trigger(:on_archipelago_migration_begin,
|
251
|
+
{:migrated_solutions_count => to_migrate_count})
|
252
|
+
target_population.replace(selected_solutions_1)
|
253
|
+
mark_population_as_migrated(population)
|
254
|
+
unless selected_solutions_2.nil?
|
255
|
+
population.replace(selected_solutions_2)
|
256
|
+
mark_population_as_migrated(target_population)
|
257
|
+
end
|
258
|
+
trigger(:on_archipelago_migration_end,
|
259
|
+
{:migrated_solutions_count => to_migrate_count})
|
260
|
+
end
|
261
|
+
|
262
|
+
def reset_topological_metrics
|
263
|
+
@clustering_coefficient = nil
|
264
|
+
end
|
265
|
+
|
266
|
+
def reset_populations_migration_state
|
267
|
+
@populations_to_migrate = get_nodes
|
268
|
+
end
|
269
|
+
|
270
|
+
def mark_population_as_migrated(population)
|
271
|
+
population_index = @populations.index(population)
|
272
|
+
@populations_to_migrate_indexes.delete(population_index)
|
273
|
+
end
|
274
|
+
|
275
|
+
# @return [Boolean]
|
276
|
+
def is_population_migrated(population)
|
277
|
+
population_index = @populations.index(population)
|
278
|
+
# If population is migrated it's not anymore in the list
|
279
|
+
@populations_to_migrate.index(population_index).nil?
|
280
|
+
end
|
281
|
+
|
282
|
+
def should_fixed_time_migrate
|
283
|
+
migration_period = get_migration_period
|
284
|
+
if migration_period.nil?
|
285
|
+
return FALSE
|
286
|
+
end
|
287
|
+
(@generation_step_count % get_migration_period).round == 0
|
288
|
+
end
|
289
|
+
|
290
|
+
def get_migration_period
|
291
|
+
if @migration_rate == 0
|
292
|
+
return FALSE
|
293
|
+
end
|
294
|
+
1.0 / @migration_rate
|
295
|
+
end
|
296
|
+
|
297
|
+
def trigger(event_type, event_data = {})
|
298
|
+
event_data[:archipelago] = self
|
299
|
+
get_event_manager.trigger_event(event_type, event_data)
|
300
|
+
end
|
301
|
+
|
302
|
+
def get_event_manager
|
303
|
+
$dependencies.event_manager
|
304
|
+
end
|
305
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
require 'gimuby/genetic/archipelago/connect_strategy/connect_strategy'
|
2
|
+
require 'gimuby/config'
|
3
|
+
|
4
|
+
class BarabasiAlbertConnectStrategy < ConnectStrategy
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
@average_degree = 4.0
|
8
|
+
@score_factor = 1.0
|
9
|
+
end
|
10
|
+
|
11
|
+
attr_accessor :average_degree
|
12
|
+
attr_accessor :score_factor
|
13
|
+
|
14
|
+
def connect(archipelago)
|
15
|
+
nodes = get_nodes(archipelago)
|
16
|
+
nodes.shuffle!
|
17
|
+
|
18
|
+
edges_to_build_per_node = (@average_degree.to_f / 2.0).round
|
19
|
+
|
20
|
+
handled_nodes = nodes.slice!(0, edges_to_build_per_node)
|
21
|
+
|
22
|
+
# We make a fully connected component of those ones
|
23
|
+
built = make_fully_connected(archipelago, handled_nodes)
|
24
|
+
|
25
|
+
nodes.each do |node|
|
26
|
+
built += connect_node(archipelago, handled_nodes, node)
|
27
|
+
handled_nodes.push(node)
|
28
|
+
end
|
29
|
+
|
30
|
+
built
|
31
|
+
end
|
32
|
+
|
33
|
+
protected
|
34
|
+
|
35
|
+
def connect_node(archipelago, handled_nodes, node)
|
36
|
+
edges_to_build_per_node = (@average_degree.to_f / 2.0).round
|
37
|
+
picked_nodes = pick_random_nodes(archipelago, handled_nodes, edges_to_build_per_node)
|
38
|
+
picked_nodes.each do |picked_node|
|
39
|
+
archipelago.connect(node, picked_node)
|
40
|
+
end
|
41
|
+
picked_nodes.length * 2
|
42
|
+
end
|
43
|
+
|
44
|
+
def pick_random_nodes(archipelago, handled_nodes, number_to_pick)
|
45
|
+
scores = get_nodes_scores(archipelago, handled_nodes)
|
46
|
+
scores_sum = scores.reduce(:+)
|
47
|
+
picked = []
|
48
|
+
begin
|
49
|
+
min_score = rand() * scores_sum.to_f
|
50
|
+
scores.each_index do |index|
|
51
|
+
score = scores[index]
|
52
|
+
if min_score < score
|
53
|
+
#we pick this one
|
54
|
+
node = handled_nodes[index]
|
55
|
+
unless picked.include?(node)
|
56
|
+
picked.push(node)
|
57
|
+
break
|
58
|
+
end
|
59
|
+
else
|
60
|
+
min_score -= score
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end while picked.length < number_to_pick
|
64
|
+
picked
|
65
|
+
end
|
66
|
+
|
67
|
+
def get_nodes_scores(archipelago, nodes)
|
68
|
+
nodes.map do |node|
|
69
|
+
get_node_score(archipelago, node) * @score_factor
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def get_node_score(archipelago, node)
|
74
|
+
archipelago.get_degree(node) + 1
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
|
2
|
+
class ConnectStrategy
|
3
|
+
|
4
|
+
# @param archipelago {Archipelago} The archipelago to connect
|
5
|
+
# @api
|
6
|
+
def connect(archipelago)
|
7
|
+
raise NotImplementedError
|
8
|
+
end
|
9
|
+
|
10
|
+
protected
|
11
|
+
|
12
|
+
def make_fully_connected(archipelago, nodes)
|
13
|
+
nodes.each do |node1|
|
14
|
+
nodes.each do |node2|
|
15
|
+
if node1 < node2
|
16
|
+
archipelago.connect(node1, node2)
|
17
|
+
archipelago.connect(node2, node1)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
nodes.length * (nodes.length - 1) * 2
|
22
|
+
end
|
23
|
+
|
24
|
+
def get_nodes(archipelago)
|
25
|
+
populations_count = archipelago.populations.length
|
26
|
+
return *(0..populations_count - 1)
|
27
|
+
end
|
28
|
+
|
29
|
+
def check_connections_to_make(connections_to_make, nodes)
|
30
|
+
if nodes.length * (nodes.length - 1) < connections_to_make
|
31
|
+
raise 'Not enough nodes'
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'gimuby/genetic/archipelago/connect_strategy/connect_strategy'
|
2
|
+
|
3
|
+
class ConstantDegreeConnectStrategy < ConnectStrategy
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
@average_degree = 4.0
|
7
|
+
end
|
8
|
+
|
9
|
+
attr_accessor :average_degree
|
10
|
+
|
11
|
+
def connect(archipelago)
|
12
|
+
nodes = get_nodes(archipelago)
|
13
|
+
|
14
|
+
# This won't create a graph where all nodes have same degree
|
15
|
+
# since we may try to connect the same node twice
|
16
|
+
number_times = (@average_degree.to_f / 2.0).round
|
17
|
+
number_times.times do |_|
|
18
|
+
archipelago.connect_path(nodes.shuffle!, TRUE)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|