evolvable 1.0.0 → 1.2.0
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.
- checksums.yaml +4 -4
- data/.yardopts +4 -0
- data/CHANGELOG.md +56 -1
- data/Gemfile +3 -0
- data/Gemfile.lock +38 -21
- data/LICENSE +21 -0
- data/README.md +234 -161
- data/README_YARD.md +237 -0
- data/bin/console +18 -5
- data/evolvable.gemspec +2 -2
- data/examples/ascii_art.rb +62 -0
- data/examples/ascii_gene.rb +9 -0
- data/examples/hello_world.rb +91 -0
- data/examples/images/diagram.png +0 -0
- data/examples/stickman.rb +77 -0
- data/exe/hello +16 -0
- data/lib/evolvable/count_gene.rb +42 -0
- data/lib/evolvable/equalize_goal.rb +29 -0
- data/lib/evolvable/evaluation.rb +29 -6
- data/lib/evolvable/evolution.rb +40 -8
- data/lib/evolvable/gene.rb +54 -2
- data/lib/evolvable/gene_combination.rb +73 -0
- data/lib/evolvable/genome.rb +86 -0
- data/lib/evolvable/goal.rb +36 -3
- data/lib/evolvable/maximize_goal.rb +30 -0
- data/lib/evolvable/minimize_goal.rb +29 -0
- data/lib/evolvable/mutation.rb +66 -15
- data/lib/evolvable/point_crossover.rb +33 -19
- data/lib/evolvable/population.rb +171 -31
- data/lib/evolvable/rigid_count_gene.rb +17 -0
- data/lib/evolvable/search_space.rb +181 -0
- data/lib/evolvable/selection.rb +28 -1
- data/lib/evolvable/serializer.rb +21 -0
- data/lib/evolvable/uniform_crossover.rb +28 -8
- data/lib/evolvable/version.rb +1 -1
- data/lib/evolvable.rb +197 -29
- metadata +38 -27
- data/.rubocop.yml +0 -20
- data/examples/evolvable_string/char_gene.rb +0 -9
- data/examples/evolvable_string.rb +0 -32
- data/lib/evolvable/gene_crossover.rb +0 -28
- data/lib/evolvable/gene_space.rb +0 -37
- data/lib/evolvable/goal/equalize.rb +0 -19
- data/lib/evolvable/goal/maximize.rb +0 -19
- data/lib/evolvable/goal/minimize.rb +0 -19
data/lib/evolvable/population.rb
CHANGED
@@ -1,38 +1,87 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Evolvable
|
4
|
+
#
|
5
|
+
# @readme
|
6
|
+
# Population objects are responsible for generating and evolving instances.
|
7
|
+
# They orchestrate all the other Evolvable objects to do so.
|
8
|
+
#
|
9
|
+
# Populations can be initialized and re-initialized with a number of useful
|
10
|
+
# parameters.
|
11
|
+
#
|
12
|
+
# @example
|
13
|
+
# # TODO: initialize a population with all supported parameters
|
4
14
|
class Population
|
5
15
|
extend Forwardable
|
6
16
|
|
7
|
-
def
|
8
|
-
|
17
|
+
def self.load(data)
|
18
|
+
dump_attrs = Serializer.load(data)
|
19
|
+
new(**dump_attrs)
|
20
|
+
end
|
21
|
+
|
22
|
+
# Initializes an Evolvable::Population.
|
23
|
+
# Keyword arguments:
|
24
|
+
# #### evolvable_class
|
25
|
+
# Required. Implicitly specified when using EvolvableClass.new_population.
|
26
|
+
# #### id, name
|
27
|
+
# Both default to `nil`. Not used by Evolvable, but convenient when working
|
28
|
+
# with multiple populations.
|
29
|
+
# #### size
|
30
|
+
# Defaults to `40`. Specifies the number of instances in the population.
|
31
|
+
# #### evolutions_count
|
32
|
+
# Defaults to `0`. Useful when re-initializing a saved population with instances.
|
33
|
+
# #### search_space
|
34
|
+
# Defaults to `evolvable_class.new_search_space` which uses the
|
35
|
+
# [EvolvableClass.search_space](#evolvableclasssearch_space) method
|
36
|
+
# #### evolution
|
37
|
+
# Defaults to `Evolvable::Evolution.new`. See [evolution](#evolution-1)
|
38
|
+
# #### evaluation
|
39
|
+
# Defaults to `Evolvable::Evaluation.new`, with a goal of maximizing
|
40
|
+
# towards Float::INFINITY. See [evaluation](#evaluation-1)
|
41
|
+
# #### instances
|
42
|
+
# Defaults to initializing a `size` number of `evolvable_class`
|
43
|
+
# instances using the `search_space` object. Any given instances
|
44
|
+
# are assigned, but if given less than `size`, more will be initialized.
|
45
|
+
#
|
46
|
+
def initialize(evolvable_type: nil,
|
47
|
+
evolvable_class: nil, # Deprecated
|
48
|
+
id: nil,
|
9
49
|
name: nil,
|
10
50
|
size: 40,
|
11
51
|
evolutions_count: 0,
|
12
|
-
gene_space: nil,
|
13
|
-
|
52
|
+
gene_space: nil, # Deprecated
|
53
|
+
search_space: nil,
|
54
|
+
parent_evolvables: [],
|
14
55
|
evaluation: Evaluation.new,
|
15
|
-
|
56
|
+
evolution: Evolution.new,
|
57
|
+
selection: nil,
|
58
|
+
combination: nil,
|
59
|
+
mutation: nil,
|
60
|
+
evolvables: [])
|
16
61
|
@id = id
|
17
|
-
@
|
62
|
+
@evolvable_type = evolvable_type || evolvable_class
|
18
63
|
@name = name
|
19
64
|
@size = size
|
20
65
|
@evolutions_count = evolutions_count
|
21
|
-
@
|
66
|
+
@search_space = initialize_search_space(search_space || gene_space)
|
67
|
+
@parent_evolvables = parent_evolvables
|
68
|
+
self.evaluation = evaluation
|
22
69
|
@evolution = evolution
|
23
|
-
|
24
|
-
|
70
|
+
self.selection = selection if selection
|
71
|
+
self.combination = combination if combination
|
72
|
+
self.mutation = mutation if mutation
|
73
|
+
@evolvables = new_evolvables(count: @size - evolvables.count, evolvables: evolvables)
|
25
74
|
end
|
26
75
|
|
27
76
|
attr_accessor :id,
|
28
|
-
:
|
77
|
+
:evolvable_type,
|
29
78
|
:name,
|
30
79
|
:size,
|
31
80
|
:evolutions_count,
|
32
|
-
:
|
81
|
+
:search_space,
|
33
82
|
:evolution,
|
34
|
-
:
|
35
|
-
:
|
83
|
+
:parent_evolvables,
|
84
|
+
:evolvables
|
36
85
|
|
37
86
|
def_delegators :evolvable_class,
|
38
87
|
:before_evaluation,
|
@@ -42,18 +91,62 @@ module Evolvable
|
|
42
91
|
def_delegators :evolution,
|
43
92
|
:selection,
|
44
93
|
:selection=,
|
45
|
-
:
|
46
|
-
:
|
94
|
+
:combination,
|
95
|
+
:combination=,
|
47
96
|
:mutation,
|
48
97
|
:mutation=
|
49
98
|
|
99
|
+
def_delegator :selection, :size, :selection_size
|
100
|
+
def_delegator :selection, :size=, :selection_size=
|
101
|
+
|
102
|
+
def_delegator :mutation, :rate, :mutation_rate
|
103
|
+
def_delegator :mutation, :rate=, :mutation_rate=
|
104
|
+
def_delegator :mutation, :probability, :mutation_probability
|
105
|
+
def_delegator :mutation, :probability=, :mutation_probability=
|
106
|
+
|
107
|
+
attr_reader :evaluation
|
108
|
+
|
109
|
+
def evaluation=(val)
|
110
|
+
@evaluation = Evolvable.new_object(@evaluation, val, Evaluation)
|
111
|
+
end
|
112
|
+
|
50
113
|
def_delegators :evaluation,
|
51
114
|
:goal,
|
52
115
|
:goal=
|
53
116
|
|
117
|
+
#
|
118
|
+
# Keyword arguments:
|
119
|
+
#
|
120
|
+
# #### count
|
121
|
+
# The number of evolutions to run. Expects a positive integer
|
122
|
+
# and Defaults to Float::INFINITY and will therefore run indefinitely
|
123
|
+
# unless a `goal_value` is specified.
|
124
|
+
# #### goal_value
|
125
|
+
# Assigns the goal object's value. Will continue running until any
|
126
|
+
# instance's value reaches it. See [evaluation](#evaluation-1)
|
127
|
+
#
|
128
|
+
# ### Evolvable::Population#best_instance
|
129
|
+
# Returns an instance with the value that is nearest to the goal value.
|
130
|
+
#
|
131
|
+
# ### Evolvable::Population#met_goal?
|
132
|
+
# Returns true if any instance's value matches the goal value, otherwise false.
|
133
|
+
#
|
134
|
+
# ### Evolvable::Population#new_instance
|
135
|
+
# Initializes an instance for the population. Note that this method does not
|
136
|
+
# add the new instance to its array of instances.
|
137
|
+
#
|
138
|
+
# Keyword arguments:
|
139
|
+
#
|
140
|
+
# #### genes
|
141
|
+
# An array of initialized gene objects. Defaults to `[]`
|
142
|
+
# #### population_index
|
143
|
+
# Defaults to `nil` and expects an integer.
|
144
|
+
#
|
145
|
+
# See (EvolvableClass#population_index)[#evolvableclasspopulation_index-population_index]
|
146
|
+
#
|
54
147
|
def evolve(count: Float::INFINITY, goal_value: nil)
|
55
148
|
goal.value = goal_value if goal_value
|
56
|
-
(
|
149
|
+
1.upto(count) do
|
57
150
|
before_evaluation(self)
|
58
151
|
evaluation.call(self)
|
59
152
|
before_evolution(self)
|
@@ -65,34 +158,81 @@ module Evolvable
|
|
65
158
|
end
|
66
159
|
end
|
67
160
|
|
68
|
-
def
|
69
|
-
evaluation.
|
161
|
+
def best_evolvable
|
162
|
+
evaluation.best_evolvable(self)
|
70
163
|
end
|
71
164
|
|
72
165
|
def met_goal?
|
73
166
|
evaluation.met_goal?(self)
|
74
167
|
end
|
75
168
|
|
76
|
-
def
|
77
|
-
|
78
|
-
|
79
|
-
|
169
|
+
def new_evolvable(genome: nil)
|
170
|
+
return generate_evolvables(1).first unless genome || parent_evolvables.empty?
|
171
|
+
|
172
|
+
evolvable = evolvable_class.new_evolvable(population: self,
|
173
|
+
genome: genome || search_space.new_genome,
|
174
|
+
generation_index: @evolvables.count)
|
175
|
+
@evolvables << evolvable
|
176
|
+
evolvable
|
177
|
+
end
|
178
|
+
|
179
|
+
def new_evolvables(count:, evolvables: nil)
|
180
|
+
evolvables ||= @evolvables || []
|
181
|
+
@evolvables = evolvables
|
182
|
+
|
183
|
+
if parent_evolvables.empty?
|
184
|
+
Array.new(count) { new_evolvable(genome: search_space.new_genome) }
|
185
|
+
else
|
186
|
+
@evolvables = generate_evolvables(count)
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
def reset_evolvables
|
191
|
+
self.evolvables = []
|
192
|
+
new_evolvables(count: size)
|
193
|
+
end
|
194
|
+
|
195
|
+
def new_parent_genome_cycle
|
196
|
+
parent_evolvables.map(&:genome).shuffle!.combination(2).cycle
|
197
|
+
end
|
198
|
+
|
199
|
+
def evolvable_class
|
200
|
+
@evolvable_class ||= evolvable_type.is_a?(Class) ? evolvable_type : Object.const_get(evolvable_type)
|
201
|
+
end
|
202
|
+
|
203
|
+
def dump(only: nil, except: nil)
|
204
|
+
Serializer.dump(dump_attrs(only: only, except: except))
|
205
|
+
end
|
206
|
+
|
207
|
+
DUMP_METHODS = %i[evolvable_type
|
208
|
+
id
|
209
|
+
name
|
210
|
+
size
|
211
|
+
evolutions_count
|
212
|
+
search_space
|
213
|
+
evolution
|
214
|
+
evaluation].freeze
|
215
|
+
|
216
|
+
def dump_attrs(only: nil, except: nil)
|
217
|
+
attrs = {}
|
218
|
+
dump_methods = only || DUMP_METHODS
|
219
|
+
dump_methods -= except if except
|
220
|
+
dump_methods.each { |m| attrs[m] = send(m) }
|
221
|
+
attrs
|
80
222
|
end
|
81
223
|
|
82
224
|
private
|
83
225
|
|
84
|
-
def
|
85
|
-
return
|
226
|
+
def initialize_search_space(search_space)
|
227
|
+
return SearchSpace.build(search_space, evolvable_class) if search_space
|
86
228
|
|
87
|
-
evolvable_class.
|
229
|
+
evolvable_class.new_search_space
|
88
230
|
end
|
89
231
|
|
90
|
-
def
|
91
|
-
|
92
|
-
(
|
93
|
-
|
94
|
-
@instances << new_instance(genes: genes, population_index: n)
|
95
|
-
end
|
232
|
+
def generate_evolvables(count)
|
233
|
+
evolvables = combination.new_evolvables(self, count)
|
234
|
+
mutation.mutate_evolvables(evolvables)
|
235
|
+
evolvables
|
96
236
|
end
|
97
237
|
end
|
98
238
|
end
|
@@ -0,0 +1,181 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Evolvable
|
4
|
+
#
|
5
|
+
# @readme
|
6
|
+
# The search space encapsulates the range of possible genes
|
7
|
+
# for a particular evolvable. You can think of it as the boundaries of
|
8
|
+
# genetic variation. It is configured via the
|
9
|
+
# [.search_space](#evolvableclasssearch_space) method that you define
|
10
|
+
# on your evolvable class. It's used by populations to initialize
|
11
|
+
# new evolvables.
|
12
|
+
#
|
13
|
+
# Evolvable provides flexibility in how you define your search space.
|
14
|
+
# The below example implementations for `.search_space` produce the
|
15
|
+
# exact same search space for the
|
16
|
+
# [Hello World](https://github.com/mattruzicka/evolvable#hello-world)
|
17
|
+
# demo program. The different styles arguably vary in suitability for
|
18
|
+
# different contexts, perhaps depending on how programs are loaded and
|
19
|
+
# the number of different gene types.
|
20
|
+
#
|
21
|
+
# @example
|
22
|
+
# # All 9 of these example definitions are equivalent
|
23
|
+
#
|
24
|
+
# # Hash syntax
|
25
|
+
# { chars: { type: 'CharGene', max_count: 100 } }
|
26
|
+
# { chars: { type: 'CharGene', min_count: 1, max_count: 100 } }
|
27
|
+
# { chars: { type: 'CharGene', count: 1..100 } }
|
28
|
+
#
|
29
|
+
# # Array of arrays syntax
|
30
|
+
# [[:chars, 'CharGene', 1..100]]
|
31
|
+
# [['chars', 'CharGene', 1..100]]
|
32
|
+
# [['CharGene', 1..100]]
|
33
|
+
#
|
34
|
+
# # A single array works when there's only one type of gene
|
35
|
+
# ['CharGene', 1..100]
|
36
|
+
# [:chars, 'CharGene', 1..100]
|
37
|
+
# ['chars', 'CharGene', 1..100]
|
38
|
+
#
|
39
|
+
#
|
40
|
+
|
41
|
+
class SearchSpace
|
42
|
+
class << self
|
43
|
+
def build(config, evolvable_class = nil)
|
44
|
+
if config.is_a?(SearchSpace)
|
45
|
+
config.evolvable_class = evolvable_class if evolvable_class
|
46
|
+
config
|
47
|
+
else
|
48
|
+
new(config: config, evolvable_class: evolvable_class)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def initialize(config: {}, evolvable_class: nil)
|
54
|
+
@evolvable_class = evolvable_class
|
55
|
+
@config = normalize_config(config)
|
56
|
+
end
|
57
|
+
|
58
|
+
attr_accessor :evolvable_class, :config
|
59
|
+
|
60
|
+
def new_genome
|
61
|
+
Genome.new(config: new_genome_config)
|
62
|
+
end
|
63
|
+
|
64
|
+
def merge_search_space(val)
|
65
|
+
val = val.config if val.is_a?(self.class)
|
66
|
+
@config.merge normalize_config(val)
|
67
|
+
end
|
68
|
+
|
69
|
+
def merge_search_space!(val)
|
70
|
+
val = val.config if val.is_a?(self.class)
|
71
|
+
@config.merge! normalize_config(val)
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
|
76
|
+
def normalize_config(config)
|
77
|
+
case config
|
78
|
+
when Hash
|
79
|
+
normalize_hash_config(config)
|
80
|
+
when Array
|
81
|
+
if config.first.is_a?(Array)
|
82
|
+
build_config_from_2d_array(config)
|
83
|
+
else
|
84
|
+
merge_config_with_array({}, config)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def normalize_hash_config(config)
|
90
|
+
config.each do |gene_key, gene_config|
|
91
|
+
next unless gene_config[:type]
|
92
|
+
|
93
|
+
gene_class = lookup_gene_class(gene_config[:type])
|
94
|
+
gene_class.key = gene_key
|
95
|
+
gene_config[:class] = gene_class
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def build_config_from_2d_array(array_config)
|
100
|
+
config = {}
|
101
|
+
array_config.each { |array| merge_config_with_array(config, array) }
|
102
|
+
config
|
103
|
+
end
|
104
|
+
|
105
|
+
def merge_config_with_array(config, gene_array)
|
106
|
+
gene_key, gene_class, count = extract_array_configs(gene_array)
|
107
|
+
gene_class.key = gene_key
|
108
|
+
config[gene_key] = { class: gene_class, count: count }
|
109
|
+
config
|
110
|
+
end
|
111
|
+
|
112
|
+
def extract_array_configs(gene_array)
|
113
|
+
first_item = gene_array.first
|
114
|
+
return extract_array_with_key_configs(gene_array) if first_item.is_a?(Symbol)
|
115
|
+
|
116
|
+
gene_class = lookup_gene_class(first_item)
|
117
|
+
_type, count = gene_array
|
118
|
+
[gene_class, gene_class, count]
|
119
|
+
rescue NameError
|
120
|
+
extract_array_with_key_configs(gene_array)
|
121
|
+
end
|
122
|
+
|
123
|
+
def extract_array_with_key_configs(gene_array)
|
124
|
+
gene_key, type, count = gene_array
|
125
|
+
gene_class = lookup_gene_class(type)
|
126
|
+
[gene_key, gene_class, count]
|
127
|
+
end
|
128
|
+
|
129
|
+
def lookup_gene_class(class_name)
|
130
|
+
return class_name if class_name.is_a?(Class)
|
131
|
+
|
132
|
+
(@evolvable_class || Object).const_get(class_name)
|
133
|
+
end
|
134
|
+
|
135
|
+
def new_genome_config
|
136
|
+
genome_config = {}
|
137
|
+
config.each do |gene_key, gene_config|
|
138
|
+
genome_config[gene_key] = new_gene_config(gene_config)
|
139
|
+
end
|
140
|
+
genome_config
|
141
|
+
end
|
142
|
+
|
143
|
+
def new_gene_config(gene_config)
|
144
|
+
count_gene = init_count_gene(gene_config)
|
145
|
+
gene_class = gene_config[:class]
|
146
|
+
genes = Array.new(count_gene.count) { gene_class.new }
|
147
|
+
{ count_gene: count_gene, genes: genes }
|
148
|
+
end
|
149
|
+
|
150
|
+
def init_count_gene(gene_config)
|
151
|
+
min = gene_config[:min_count]
|
152
|
+
max = gene_config[:max_count]
|
153
|
+
return init_min_max_count_gene(min, max) if min || max
|
154
|
+
|
155
|
+
count = gene_config[:count] || 1
|
156
|
+
case count
|
157
|
+
when Numeric
|
158
|
+
RigidCountGene.new(count)
|
159
|
+
when Range
|
160
|
+
CountGene.new(range: count)
|
161
|
+
when String
|
162
|
+
Kernel.const_get(gene_config[:count]).new
|
163
|
+
when Class
|
164
|
+
gene_config[:count].new
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
def init_min_max_count_gene(min, max)
|
169
|
+
min ||= 1
|
170
|
+
max ||= min * 10
|
171
|
+
CountGene.new(range: min..max)
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
#
|
176
|
+
# @deprecated
|
177
|
+
# Will be removed in 2.0
|
178
|
+
# use {SearchSpace} instead
|
179
|
+
#
|
180
|
+
class GeneSpace < SearchSpace; end
|
181
|
+
end
|
data/lib/evolvable/selection.rb
CHANGED
@@ -1,9 +1,31 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Evolvable
|
4
|
+
#
|
5
|
+
# @readme
|
6
|
+
# The selection object assumes that a population's evolvables have already
|
7
|
+
# been sorted by the evaluation object. It selects "parent" evolvables to
|
8
|
+
# undergo combination and thereby produce the next generation of evolvables.
|
9
|
+
#
|
10
|
+
# Only two evolvables are selected as parents for each generation by default.
|
11
|
+
# The selection size is configurable.
|
12
|
+
#
|
13
|
+
# @example
|
14
|
+
# # TODO: Show how to add/change population's selection object
|
15
|
+
#
|
4
16
|
class Selection
|
5
17
|
extend Forwardable
|
6
18
|
|
19
|
+
#
|
20
|
+
# Initializes a new selection object.
|
21
|
+
#
|
22
|
+
# Keyword arguments:
|
23
|
+
#
|
24
|
+
# #### size
|
25
|
+
# The number of instances to select from each generation from which to
|
26
|
+
# perform crossover and generate or "breed" the next generation. The
|
27
|
+
# number of parents The default is 2.
|
28
|
+
#
|
7
29
|
def initialize(size: 2)
|
8
30
|
@size = size
|
9
31
|
end
|
@@ -11,8 +33,13 @@ module Evolvable
|
|
11
33
|
attr_accessor :size
|
12
34
|
|
13
35
|
def call(population)
|
14
|
-
population.
|
36
|
+
population.parent_evolvables = select_evolvables(population.evolvables)
|
37
|
+
population.evolvables = []
|
15
38
|
population
|
16
39
|
end
|
40
|
+
|
41
|
+
def select_evolvables(evolvables)
|
42
|
+
evolvables.last(@size)
|
43
|
+
end
|
17
44
|
end
|
18
45
|
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Evolvable
|
4
|
+
class Serializer
|
5
|
+
class << self
|
6
|
+
def dump(data)
|
7
|
+
klass.dump(data)
|
8
|
+
end
|
9
|
+
|
10
|
+
def load(data)
|
11
|
+
klass.load(data)
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def klass
|
17
|
+
@klass ||= Marshal
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -1,21 +1,41 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Evolvable
|
4
|
+
#
|
5
|
+
# Randomly chooses a gene from one of the parents for each gene position.
|
6
|
+
#
|
4
7
|
class UniformCrossover
|
5
8
|
def call(population)
|
6
|
-
population.
|
9
|
+
population.evolvables = new_evolvables(population, population.size)
|
7
10
|
population
|
8
11
|
end
|
9
12
|
|
13
|
+
def new_evolvables(population, count)
|
14
|
+
parent_genome_cycle = population.new_parent_genome_cycle
|
15
|
+
Array.new(count) do
|
16
|
+
genome = build_genome(parent_genome_cycle.next)
|
17
|
+
population.new_evolvable(genome: genome)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
10
21
|
private
|
11
22
|
|
12
|
-
def
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
genes =
|
18
|
-
|
23
|
+
def build_genome(genome_pair)
|
24
|
+
new_config = {}
|
25
|
+
genome_1, genome_2 = genome_pair.shuffle!
|
26
|
+
genome_1.each do |gene_key, gene_config_1|
|
27
|
+
count_gene = gene_config_1[:count_gene]
|
28
|
+
genes = crossover_genes(count_gene.count, gene_config_1, genome_2.config[gene_key])
|
29
|
+
new_config[gene_key] = { count_gene: count_gene, genes: genes }
|
30
|
+
end
|
31
|
+
Genome.new(config: new_config)
|
32
|
+
end
|
33
|
+
|
34
|
+
def crossover_genes(count, gene_config_1, gene_config_2)
|
35
|
+
gene_arrays = [gene_config_1[:genes], gene_config_2[:genes]]
|
36
|
+
Array.new(count) do |index|
|
37
|
+
genes = gene_arrays.sample
|
38
|
+
genes[index] || gene_arrays.detect { |a| a != genes }[index]
|
19
39
|
end
|
20
40
|
end
|
21
41
|
end
|
data/lib/evolvable/version.rb
CHANGED