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.
Files changed (60) hide show
  1. data/Gemfile +1 -0
  2. data/LICENSE.md +25 -0
  3. data/README.md +0 -0
  4. data/lib/gimuby.rb +10 -0
  5. data/lib/gimuby/config.rb +39 -0
  6. data/lib/gimuby/dependencies.rb +37 -0
  7. data/lib/gimuby/event/event.rb +29 -0
  8. data/lib/gimuby/event/event_manager.rb +34 -0
  9. data/lib/gimuby/factory.rb +275 -0
  10. data/lib/gimuby/genetic/archipelago/archipelago.rb +305 -0
  11. data/lib/gimuby/genetic/archipelago/connect_strategy/barabasi_albert_connect_strategy.rb +77 -0
  12. data/lib/gimuby/genetic/archipelago/connect_strategy/circle_connect_strategy.rb +11 -0
  13. data/lib/gimuby/genetic/archipelago/connect_strategy/connect_strategy.rb +34 -0
  14. data/lib/gimuby/genetic/archipelago/connect_strategy/constant_degree_connect_strategy.rb +22 -0
  15. data/lib/gimuby/genetic/archipelago/connect_strategy/fully_connected_connect_strategy.rb +11 -0
  16. data/lib/gimuby/genetic/archipelago/connect_strategy/random_connect_strategy.rb +29 -0
  17. data/lib/gimuby/genetic/archipelago/connect_strategy/watts_strogatz_connect_strategy.rb +63 -0
  18. data/lib/gimuby/genetic/archipelago/measure/clustering_coefficient_measure.rb +92 -0
  19. data/lib/gimuby/genetic/archipelago/measure/connected_measure.rb +64 -0
  20. data/lib/gimuby/genetic/archipelago/measure/diameter_measure.rb +38 -0
  21. data/lib/gimuby/genetic/archipelago/measure/measure.rb +7 -0
  22. data/lib/gimuby/genetic/archipelago/measure/shortest_paths_measure.rb +46 -0
  23. data/lib/gimuby/genetic/population/pick_strategy/bests_pick_strategy.rb +17 -0
  24. data/lib/gimuby/genetic/population/pick_strategy/pick_strategy.rb +21 -0
  25. data/lib/gimuby/genetic/population/pick_strategy/random_wheel_pick_strategy.rb +40 -0
  26. data/lib/gimuby/genetic/population/pick_strategy/tournament_pick_strategy.rb +26 -0
  27. data/lib/gimuby/genetic/population/population.rb +97 -0
  28. data/lib/gimuby/genetic/population/replace_strategy/replace_strategy.rb +9 -0
  29. data/lib/gimuby/genetic/population/replace_strategy/replace_worst_replace_strategy.rb +52 -0
  30. data/lib/gimuby/genetic/population/replace_strategy/uniform_replace_strategy.rb +48 -0
  31. data/lib/gimuby/genetic/solution/check_strategy/check_strategy.rb +8 -0
  32. data/lib/gimuby/genetic/solution/check_strategy/permutation_check_strategy.rb +37 -0
  33. data/lib/gimuby/genetic/solution/check_strategy/solution_space_check_strategy.rb +74 -0
  34. data/lib/gimuby/genetic/solution/function_based_solution.rb +64 -0
  35. data/lib/gimuby/genetic/solution/mutation_strategy/mutation_strategy.rb +22 -0
  36. data/lib/gimuby/genetic/solution/mutation_strategy/permutation_mutation_strategy.rb +17 -0
  37. data/lib/gimuby/genetic/solution/mutation_strategy/solution_space_mutation_strategy.rb +69 -0
  38. data/lib/gimuby/genetic/solution/new_generation_strategy/average_new_generation_strategy.rb +41 -0
  39. data/lib/gimuby/genetic/solution/new_generation_strategy/combined_new_generation_strategy.rb +27 -0
  40. data/lib/gimuby/genetic/solution/new_generation_strategy/cross_over_new_generation_strategy.rb +40 -0
  41. data/lib/gimuby/genetic/solution/new_generation_strategy/new_generation_strategy.rb +9 -0
  42. data/lib/gimuby/genetic/solution/new_generation_strategy/parent_range_new_generation_strategy.rb +42 -0
  43. data/lib/gimuby/genetic/solution/solution.rb +86 -0
  44. data/lib/gimuby/problem/foxholes/foxholes.rb +76 -0
  45. data/lib/gimuby/problem/foxholes/foxholes_solution.rb +29 -0
  46. data/lib/gimuby/problem/lennard_jones/lennard_jones.rb +38 -0
  47. data/lib/gimuby/problem/lennard_jones/lennard_jones_solution.rb +62 -0
  48. data/lib/gimuby/problem/rastrigin/rastrigin.rb +26 -0
  49. data/lib/gimuby/problem/rastrigin/rastrigin_solution.rb +35 -0
  50. data/lib/gimuby/problem/rosenbrock/rosenbrock.rb +14 -0
  51. data/lib/gimuby/problem/rosenbrock/rosenbrock_solution.rb +39 -0
  52. data/lib/gimuby/problem/schaffer/schaffer.rb +18 -0
  53. data/lib/gimuby/problem/schaffer/schaffer_solution.rb +29 -0
  54. data/lib/gimuby/problem/sphere/sphere.rb +9 -0
  55. data/lib/gimuby/problem/sphere/sphere_solution.rb +27 -0
  56. data/lib/gimuby/problem/step/step.rb +9 -0
  57. data/lib/gimuby/problem/step/step_solution.rb +29 -0
  58. data/lib/gimuby/problem/tsp/tsp.rb +76 -0
  59. data/lib/gimuby/problem/tsp/tsp_solution.rb +46 -0
  60. 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,11 @@
1
+ require 'gimuby/genetic/archipelago/connect_strategy/connect_strategy'
2
+
3
+
4
+ class CircleConnectStrategy < ConnectStrategy
5
+
6
+ def connect(archipelago)
7
+ nodes = get_nodes(archipelago)
8
+ archipelago.connect_path(nodes, TRUE)
9
+ end
10
+
11
+ 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