gimuby 0.7.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|