evolvable 0.1.3 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +0 -1
- data/.yardopts +4 -0
- data/CHANGELOG.md +63 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +39 -39
- data/LICENSE +21 -0
- data/README.md +234 -248
- data/README_YARD.md +237 -0
- data/bin/console +20 -43
- data/evolvable.gemspec +2 -3
- 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/error/undefined_method.rb +7 -0
- data/lib/evolvable/evaluation.rb +74 -0
- data/lib/evolvable/evolution.rb +58 -0
- data/lib/evolvable/gene.rb +67 -0
- data/lib/evolvable/gene_combination.rb +69 -0
- data/lib/evolvable/genome.rb +86 -0
- data/lib/evolvable/goal.rb +52 -0
- data/lib/evolvable/maximize_goal.rb +30 -0
- data/lib/evolvable/minimize_goal.rb +29 -0
- data/lib/evolvable/mutation.rb +71 -42
- data/lib/evolvable/point_crossover.rb +71 -0
- data/lib/evolvable/population.rb +202 -83
- data/lib/evolvable/rigid_count_gene.rb +17 -0
- data/lib/evolvable/search_space.rb +181 -0
- data/lib/evolvable/selection.rb +45 -0
- data/lib/evolvable/serializer.rb +21 -0
- data/lib/evolvable/uniform_crossover.rb +42 -0
- data/lib/evolvable/version.rb +1 -1
- data/lib/evolvable.rb +203 -56
- metadata +46 -24
- data/.rubocop.yml +0 -20
- data/lib/evolvable/crossover.rb +0 -35
- data/lib/evolvable/errors/not_implemented.rb +0 -5
- data/lib/evolvable/helper_methods.rb +0 -45
- data/lib/evolvable/hooks.rb +0 -9
data/lib/evolvable/population.rb
CHANGED
@@ -1,119 +1,238 @@
|
|
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
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
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,
|
49
|
+
name: nil,
|
50
|
+
size: 40,
|
51
|
+
evolutions_count: 0,
|
52
|
+
gene_space: nil, # Deprecated
|
53
|
+
search_space: nil,
|
54
|
+
parent_evolvables: [],
|
55
|
+
evaluation: Evaluation.new,
|
56
|
+
evolution: Evolution.new,
|
57
|
+
selection: nil,
|
58
|
+
combination: nil,
|
59
|
+
mutation: nil,
|
60
|
+
evolvables: [])
|
61
|
+
@id = id
|
62
|
+
@evolvable_type = evolvable_type || evolvable_class
|
63
|
+
@name = name
|
16
64
|
@size = size
|
17
|
-
@
|
18
|
-
@
|
19
|
-
@
|
20
|
-
|
21
|
-
@
|
22
|
-
|
65
|
+
@evolutions_count = evolutions_count
|
66
|
+
@search_space = initialize_search_space(search_space || gene_space)
|
67
|
+
@parent_evolvables = parent_evolvables
|
68
|
+
self.evaluation = evaluation
|
69
|
+
@evolution = evolution
|
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)
|
23
74
|
end
|
24
75
|
|
25
|
-
attr_accessor :
|
76
|
+
attr_accessor :id,
|
77
|
+
:evolvable_type,
|
78
|
+
:name,
|
26
79
|
:size,
|
27
|
-
:
|
28
|
-
:
|
29
|
-
:
|
30
|
-
:
|
31
|
-
:
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
:
|
36
|
-
:
|
37
|
-
|
38
|
-
|
39
|
-
:
|
40
|
-
:
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
80
|
+
:evolutions_count,
|
81
|
+
:search_space,
|
82
|
+
:evolution,
|
83
|
+
:parent_evolvables,
|
84
|
+
:evolvables
|
85
|
+
|
86
|
+
def_delegators :evolvable_class,
|
87
|
+
:before_evaluation,
|
88
|
+
:before_evolution,
|
89
|
+
:after_evolution
|
90
|
+
|
91
|
+
def_delegators :evolution,
|
92
|
+
:selection,
|
93
|
+
:selection=,
|
94
|
+
:combination,
|
95
|
+
:combination=,
|
96
|
+
:mutation,
|
97
|
+
:mutation=
|
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
|
+
|
113
|
+
def_delegators :evaluation,
|
114
|
+
:goal,
|
115
|
+
:goal=
|
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
|
+
#
|
147
|
+
def evolve(count: Float::INFINITY, goal_value: nil)
|
148
|
+
goal.value = goal_value if goal_value
|
149
|
+
1.upto(count) do
|
150
|
+
before_evaluation(self)
|
151
|
+
evaluation.call(self)
|
152
|
+
before_evolution(self)
|
153
|
+
break if met_goal?
|
154
|
+
|
155
|
+
evolution.call(self)
|
156
|
+
self.evolutions_count += 1
|
157
|
+
after_evolution(self)
|
56
158
|
end
|
57
159
|
end
|
58
160
|
|
59
|
-
def
|
60
|
-
|
161
|
+
def best_evolvable
|
162
|
+
evaluation.best_evolvable(self)
|
61
163
|
end
|
62
164
|
|
63
|
-
def
|
64
|
-
|
65
|
-
if @fitness_goal
|
66
|
-
@objects.sort_by! { |i| -(i.fitness - @fitness_goal).abs }
|
67
|
-
else
|
68
|
-
@objects.sort_by!(&:fitness)
|
69
|
-
end
|
165
|
+
def met_goal?
|
166
|
+
evaluation.met_goal?(self)
|
70
167
|
end
|
71
168
|
|
72
|
-
def
|
73
|
-
|
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
|
74
177
|
end
|
75
178
|
|
76
|
-
def
|
77
|
-
|
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
|
78
188
|
end
|
79
189
|
|
80
|
-
def
|
81
|
-
|
190
|
+
def reset_evolvables
|
191
|
+
self.evolvables = []
|
192
|
+
new_evolvables(count: size)
|
82
193
|
end
|
83
194
|
|
84
|
-
def
|
85
|
-
|
86
|
-
offspring_genes = @crossover.call(parent_genes, @size)
|
87
|
-
@objects = offspring_genes.map.with_index do |genes, i|
|
88
|
-
evolvable_initialize(genes, self, i)
|
89
|
-
end
|
195
|
+
def new_parent_genome_cycle
|
196
|
+
parent_evolvables.map(&:genome).shuffle!.combination(2).cycle
|
90
197
|
end
|
91
198
|
|
92
|
-
def
|
93
|
-
@
|
199
|
+
def evolvable_class
|
200
|
+
@evolvable_class ||= evolvable_type.is_a?(Class) ? evolvable_type : Object.const_get(evolvable_type)
|
94
201
|
end
|
95
202
|
|
96
|
-
def
|
97
|
-
|
203
|
+
def dump(only: nil, except: nil)
|
204
|
+
Serializer.dump(dump_attrs(only: only, except: except))
|
98
205
|
end
|
99
206
|
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
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
|
107
222
|
end
|
108
223
|
|
109
224
|
private
|
110
225
|
|
111
|
-
def
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
226
|
+
def initialize_search_space(search_space)
|
227
|
+
return SearchSpace.build(search_space, evolvable_class) if search_space
|
228
|
+
|
229
|
+
evolvable_class.new_search_space
|
230
|
+
end
|
231
|
+
|
232
|
+
def generate_evolvables(count)
|
233
|
+
evolvables = combination.new_evolvables(self, count)
|
234
|
+
mutation.mutate_evolvables(evolvables)
|
235
|
+
evolvables
|
117
236
|
end
|
118
237
|
end
|
119
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
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
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
|
+
#
|
16
|
+
class Selection
|
17
|
+
extend Forwardable
|
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
|
+
#
|
29
|
+
def initialize(size: 2)
|
30
|
+
@size = size
|
31
|
+
end
|
32
|
+
|
33
|
+
attr_accessor :size
|
34
|
+
|
35
|
+
def call(population)
|
36
|
+
population.parent_evolvables = select_evolvables(population.evolvables)
|
37
|
+
population.evolvables = []
|
38
|
+
population
|
39
|
+
end
|
40
|
+
|
41
|
+
def select_evolvables(evolvables)
|
42
|
+
evolvables.last(@size)
|
43
|
+
end
|
44
|
+
end
|
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
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Evolvable
|
4
|
+
#
|
5
|
+
# Randomly chooses a gene from one of the parents for each gene position.
|
6
|
+
#
|
7
|
+
class UniformCrossover
|
8
|
+
def call(population)
|
9
|
+
population.evolvables = new_evolvables(population, population.size)
|
10
|
+
population
|
11
|
+
end
|
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
|
+
|
21
|
+
private
|
22
|
+
|
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]
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
data/lib/evolvable/version.rb
CHANGED