gimuby 0.7.2

Sign up to get free protection for your applications and to get access to all the features.
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