evolvable 1.2.0 → 2.0.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 +2 -0
- data/CHANGELOG.md +24 -0
- data/Gemfile +2 -2
- data/Gemfile.lock +44 -25
- data/README.md +498 -190
- data/README_YARD.md +85 -166
- data/bin/console +10 -19
- data/docs/Evolvable/ClassMethods.html +1233 -0
- data/docs/Evolvable/Community/ClassMethods.html +708 -0
- data/docs/Evolvable/Community.html +1342 -0
- data/docs/Evolvable/CountGene.html +886 -0
- data/docs/Evolvable/EqualizeGoal.html +347 -0
- data/docs/Evolvable/Error.html +134 -0
- data/docs/Evolvable/Evaluation.html +773 -0
- data/docs/Evolvable/Evolution.html +616 -0
- data/docs/Evolvable/Gene/ClassMethods.html +413 -0
- data/docs/Evolvable/Gene.html +522 -0
- data/docs/Evolvable/GeneCluster/ClassMethods.html +431 -0
- data/docs/Evolvable/GeneCluster.html +280 -0
- data/docs/Evolvable/GeneCombination.html +515 -0
- data/docs/Evolvable/GeneSpace.html +619 -0
- data/docs/Evolvable/Genome.html +1070 -0
- data/docs/Evolvable/Goal.html +500 -0
- data/docs/Evolvable/MaximizeGoal.html +348 -0
- data/docs/Evolvable/MinimizeGoal.html +348 -0
- data/docs/Evolvable/Mutation.html +729 -0
- data/docs/Evolvable/PointCrossover.html +444 -0
- data/docs/Evolvable/Population.html +2826 -0
- data/docs/Evolvable/RigidCountGene.html +501 -0
- data/docs/Evolvable/Selection.html +594 -0
- data/docs/Evolvable/Serializer.html +293 -0
- data/docs/Evolvable/UniformCrossover.html +286 -0
- data/docs/Evolvable.html +1619 -0
- data/docs/_index.html +341 -0
- data/docs/class_list.html +54 -0
- data/docs/css/common.css +1 -0
- data/docs/css/full_list.css +58 -0
- data/docs/css/style.css +503 -0
- data/docs/file.README.html +750 -0
- data/docs/file_list.html +59 -0
- data/docs/frames.html +22 -0
- data/docs/index.html +750 -0
- data/docs/js/app.js +344 -0
- data/docs/js/full_list.js +242 -0
- data/docs/js/jquery.js +4 -0
- data/docs/method_list.html +1302 -0
- data/docs/top-level-namespace.html +110 -0
- data/evolvable.gemspec +6 -6
- data/examples/ascii_art.rb +5 -9
- data/examples/stickman.rb +25 -33
- data/{examples/hello_world.rb → exe/hello_evolvable_world} +46 -30
- data/lib/evolvable/community.rb +190 -0
- data/lib/evolvable/count_gene.rb +65 -0
- data/lib/evolvable/equalize_goal.rb +2 -9
- data/lib/evolvable/evaluation.rb +113 -14
- data/lib/evolvable/evolution.rb +38 -15
- data/lib/evolvable/gene.rb +124 -25
- data/lib/evolvable/gene_cluster.rb +106 -0
- data/lib/evolvable/gene_combination.rb +57 -16
- data/lib/evolvable/gene_space.rb +111 -0
- data/lib/evolvable/genome.rb +32 -4
- data/lib/evolvable/goal.rb +19 -24
- data/lib/evolvable/maximize_goal.rb +2 -9
- data/lib/evolvable/minimize_goal.rb +3 -9
- data/lib/evolvable/mutation.rb +87 -41
- data/lib/evolvable/point_crossover.rb +24 -4
- data/lib/evolvable/population.rb +258 -84
- data/lib/evolvable/rigid_count_gene.rb +36 -0
- data/lib/evolvable/selection.rb +68 -14
- data/lib/evolvable/serializer.rb +46 -0
- data/lib/evolvable/uniform_crossover.rb +30 -6
- data/lib/evolvable/version.rb +2 -1
- data/lib/evolvable.rb +268 -107
- metadata +57 -36
- data/examples/images/diagram.png +0 -0
- data/exe/hello +0 -16
- data/lib/evolvable/error/undefined_method.rb +0 -7
- data/lib/evolvable/search_space.rb +0 -181
data/lib/evolvable/serializer.rb
CHANGED
@@ -1,6 +1,52 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Evolvable
|
4
|
+
#
|
5
|
+
# @readme
|
6
|
+
# Evolvable supports saving and restoring the state of both populations
|
7
|
+
# and individual evolvable instances through a built-in `Serializer`.
|
8
|
+
# By default, it uses Ruby's `Marshal` class for fast, portable binary serialization.
|
9
|
+
#
|
10
|
+
# Serialization is useful for:
|
11
|
+
# - Saving progress during long-running evolution
|
12
|
+
# - Storing champion solutions for later reuse
|
13
|
+
# - Transferring evolved populations between systems
|
14
|
+
# - Creating checkpoints you can revert to
|
15
|
+
#
|
16
|
+
# Both `Population` and individual evolvables expose `dump` and `load` methods
|
17
|
+
# that use the `Serializer` internally.
|
18
|
+
#
|
19
|
+
# Save a population to a file:
|
20
|
+
#
|
21
|
+
# ```ruby
|
22
|
+
# population = YourEvolvable.new_population
|
23
|
+
# population.evolve(count: 100)
|
24
|
+
# File.write("population.marshal", population.dump)
|
25
|
+
# ```
|
26
|
+
#
|
27
|
+
# Restore and continue evolution:
|
28
|
+
#
|
29
|
+
# ```ruby
|
30
|
+
# data = File.read("population.marshal")
|
31
|
+
# restored = Evolvable::Population.load(data)
|
32
|
+
# restored.evolve(count: 100)
|
33
|
+
# ```
|
34
|
+
#
|
35
|
+
# Save an individual evolvable's genome:
|
36
|
+
#
|
37
|
+
# ```ruby
|
38
|
+
# best = restored.best_evolvable
|
39
|
+
# File.write("champion.marshal", best.dump_genome)
|
40
|
+
# ```
|
41
|
+
#
|
42
|
+
# Restore genome into a new evolvable:
|
43
|
+
#
|
44
|
+
# ```ruby
|
45
|
+
# raw = File.read("champion.marshal")
|
46
|
+
# champion = YourEvolvable.new_evolvable
|
47
|
+
# champion.load_genome(raw)
|
48
|
+
# ```
|
49
|
+
#
|
4
50
|
class Serializer
|
5
51
|
class << self
|
6
52
|
def dump(data)
|
@@ -2,7 +2,31 @@
|
|
2
2
|
|
3
3
|
module Evolvable
|
4
4
|
#
|
5
|
-
#
|
5
|
+
# @readme
|
6
|
+
# Chooses genes independently at each position, selecting randomly from either
|
7
|
+
# parent with equal probability. No segments are preserved—each gene is treated
|
8
|
+
# in isolation.
|
9
|
+
#
|
10
|
+
# Best for:
|
11
|
+
# - Problems where gene order doesn't matter
|
12
|
+
# - High genetic diversity and exploration
|
13
|
+
# - Complex interdependencies across traits
|
14
|
+
#
|
15
|
+
# Uniform crossover is especially effective when good traits are scattered across the genome.
|
16
|
+
#
|
17
|
+
# Set your population to use this strategy during initialization with:
|
18
|
+
#
|
19
|
+
# ```ruby
|
20
|
+
# population = MyEvolvable.new_population(
|
21
|
+
# combination: Evolvable::UniformCrossover.new
|
22
|
+
# )
|
23
|
+
# ```
|
24
|
+
#
|
25
|
+
# Or update an existing population:
|
26
|
+
#
|
27
|
+
# ```ruby
|
28
|
+
# population.combination = Evolvable::UniformCrossover.new
|
29
|
+
# ```
|
6
30
|
#
|
7
31
|
class UniformCrossover
|
8
32
|
def call(population)
|
@@ -22,10 +46,11 @@ module Evolvable
|
|
22
46
|
|
23
47
|
def build_genome(genome_pair)
|
24
48
|
new_config = {}
|
25
|
-
genome_1, genome_2 = genome_pair
|
49
|
+
genome_1, genome_2 = genome_pair
|
26
50
|
genome_1.each do |gene_key, gene_config_1|
|
27
|
-
|
28
|
-
|
51
|
+
gene_config_2 = genome_2.config[gene_key]
|
52
|
+
count_gene = [gene_config_1, gene_config_2].sample[:count_gene]
|
53
|
+
genes = crossover_genes(count_gene.count, gene_config_1, gene_config_2)
|
29
54
|
new_config[gene_key] = { count_gene: count_gene, genes: genes }
|
30
55
|
end
|
31
56
|
Genome.new(config: new_config)
|
@@ -34,8 +59,7 @@ module Evolvable
|
|
34
59
|
def crossover_genes(count, gene_config_1, gene_config_2)
|
35
60
|
gene_arrays = [gene_config_1[:genes], gene_config_2[:genes]]
|
36
61
|
Array.new(count) do |index|
|
37
|
-
genes
|
38
|
-
genes[index] || gene_arrays.detect { |a| a != genes }[index]
|
62
|
+
gene_arrays.shuffle!.detect { |genes| genes[index] }
|
39
63
|
end
|
40
64
|
end
|
41
65
|
end
|
data/lib/evolvable/version.rb
CHANGED
data/lib/evolvable.rb
CHANGED
@@ -2,9 +2,9 @@
|
|
2
2
|
|
3
3
|
require 'forwardable'
|
4
4
|
require 'evolvable/version'
|
5
|
-
require 'evolvable/error/undefined_method'
|
6
5
|
require 'evolvable/gene'
|
7
|
-
require 'evolvable/
|
6
|
+
require 'evolvable/gene_cluster'
|
7
|
+
require 'evolvable/gene_space'
|
8
8
|
require 'evolvable/genome'
|
9
9
|
require 'evolvable/goal'
|
10
10
|
require 'evolvable/equalize_goal'
|
@@ -21,28 +21,87 @@ require 'evolvable/population'
|
|
21
21
|
require 'evolvable/count_gene'
|
22
22
|
require 'evolvable/rigid_count_gene'
|
23
23
|
require 'evolvable/serializer'
|
24
|
+
require 'evolvable/community'
|
24
25
|
|
25
26
|
#
|
26
27
|
# @readme
|
27
|
-
#
|
28
|
-
#
|
29
|
-
#
|
30
|
-
#
|
28
|
+
# **Quick start**:
|
29
|
+
# 1. Include `Evolvable` in your Ruby class
|
30
|
+
# 2. Define genes with the macro-style `gene` method
|
31
|
+
# 3. Have the `#fitness` method return a numeric value
|
32
|
+
# 4. Initialize a population and evolve it
|
31
33
|
#
|
32
|
-
#
|
34
|
+
# Example population of "shirts" with various colors, buttons, and collars.
|
33
35
|
#
|
34
|
-
#
|
35
|
-
#
|
36
|
-
#
|
37
|
-
#
|
36
|
+
# ```ruby
|
37
|
+
# # Step 1
|
38
|
+
# class Shirt
|
39
|
+
# include Evolvable
|
40
|
+
#
|
41
|
+
# # Step 2
|
42
|
+
# gene :color, type: ColorGene # count: 1 default
|
43
|
+
# gene :buttons, type: ButtonGene, count: 0..10 # Builds an array of genes that can vary in size
|
44
|
+
# gene :collar, type: CollarGene, count: 0..1 # Collar optional
|
45
|
+
#
|
46
|
+
# # Step 3
|
47
|
+
# attr_accessor :fitness
|
48
|
+
# end
|
49
|
+
#
|
50
|
+
# # Step 4
|
51
|
+
# population = Shirt.new_population(size: 10)
|
52
|
+
# population.evolvables.each { |shirt| shirt.fitness = style_rating }
|
53
|
+
# ```
|
54
|
+
#
|
55
|
+
# You are free to tailor the genes to your needs and find a style that suits you.
|
56
|
+
#
|
57
|
+
# The `ColorGene` could be as simple as this:
|
58
|
+
#
|
59
|
+
# ```ruby
|
60
|
+
# class ColorGene
|
61
|
+
# include Evolvable::Gene
|
62
|
+
#
|
63
|
+
# def to_s
|
64
|
+
# @to_s ||= %w[red green blue].sample
|
65
|
+
# end
|
66
|
+
# end
|
67
|
+
# ```
|
68
|
+
#
|
69
|
+
# Shirts aren't your style?
|
70
|
+
#
|
71
|
+
# Here's a [Hello World](https://github.com/mattruzicka/evolvable/blob/main/exe/hello_evolvable_world)
|
72
|
+
# command line demo.
|
38
73
|
#
|
39
74
|
module Evolvable
|
40
75
|
extend Forwardable
|
41
76
|
|
77
|
+
#
|
78
|
+
# Error class for Evolvable-specific exceptions.
|
79
|
+
#
|
80
|
+
class Error < StandardError; end
|
81
|
+
|
82
|
+
#
|
83
|
+
# Called when the Evolvable module is included in a class.
|
84
|
+
# Sets up the necessary instance variables and extends the class with ClassMethods.
|
85
|
+
#
|
86
|
+
# @param base [Class] The class that is including the Evolvable module
|
87
|
+
# @return [void]
|
88
|
+
#
|
42
89
|
def self.included(base)
|
90
|
+
base.instance_variable_set(:@gene_config, {})
|
91
|
+
base.instance_variable_set(:@cluster_config, {})
|
43
92
|
base.extend(ClassMethods)
|
44
93
|
end
|
45
94
|
|
95
|
+
#
|
96
|
+
# Helper method for creating or updating objects with hash parameters.
|
97
|
+
# This is used internally for creating evaluation, selection, mutation, etc. objects
|
98
|
+
# from configuration hashes.
|
99
|
+
#
|
100
|
+
# @param old_val [Object, nil] The existing object to update, if any
|
101
|
+
# @param new_val [Object, Hash] The new object or configuration hash
|
102
|
+
# @param default_class [Class] The default class to instantiate if new_val is a Hash
|
103
|
+
# @return [Object] The new or updated object
|
104
|
+
#
|
46
105
|
def self.new_object(old_val, new_val, default_class)
|
47
106
|
new_val.is_a?(Hash) ? (old_val&.class || default_class).new(**new_val) : new_val
|
48
107
|
end
|
@@ -50,9 +109,8 @@ module Evolvable
|
|
50
109
|
module ClassMethods
|
51
110
|
#
|
52
111
|
# @readme
|
53
|
-
# Initializes a population
|
54
|
-
#
|
55
|
-
# [Population#initialize](https://rubydoc.info/github/mattruzicka/evolvable/Evolvable/Population#initialize).
|
112
|
+
# Initializes a population with defaults that can be changed using the same named parameters as
|
113
|
+
# [Population#initialize](https://mattruzicka.github.io/evolvable/Evolvable/Population#initialize).
|
56
114
|
#
|
57
115
|
def new_population(keyword_args = {})
|
58
116
|
keyword_args[:evolvable_type] = self
|
@@ -69,102 +127,148 @@ module Evolvable
|
|
69
127
|
# initialized you can override either of the following two "initialize_instance"
|
70
128
|
# methods.
|
71
129
|
#
|
72
|
-
def new_evolvable(population: nil,
|
73
|
-
genome: Genome.new,
|
74
|
-
generation_index: nil)
|
130
|
+
def new_evolvable(population: nil, genome: nil, generation_index: nil)
|
75
131
|
evolvable = initialize_evolvable
|
76
|
-
evolvable.population
|
77
|
-
evolvable.
|
78
|
-
evolvable.generation_index = generation_index
|
79
|
-
evolvable.after_initialize
|
132
|
+
evolvable.make_evolvable(population: population, genome: genome, generation_index: generation_index)
|
133
|
+
evolvable.after_initialize_evolvable
|
80
134
|
evolvable
|
81
135
|
end
|
82
136
|
|
137
|
+
#
|
138
|
+
# Override this method to customize how your evolvable instances are initialized.
|
139
|
+
# By default, simply calls new with no arguments.
|
140
|
+
#
|
141
|
+
# @return [Evolvable] A new evolvable instance
|
142
|
+
#
|
83
143
|
def initialize_evolvable
|
84
144
|
new
|
85
145
|
end
|
86
146
|
|
87
|
-
def new_search_space
|
88
|
-
space_config = search_space.empty? ? gene_space : search_space
|
89
|
-
search_space = SearchSpace.build(space_config, self)
|
90
|
-
search_spaces.each { |space| search_space.merge_search_space!(space) }
|
91
|
-
search_space
|
92
|
-
end
|
93
|
-
|
94
|
-
#
|
95
|
-
# @abstract
|
96
147
|
#
|
97
|
-
#
|
98
|
-
#
|
99
|
-
#
|
100
|
-
# of being used and searched by evolvable objects.
|
148
|
+
# @readme
|
149
|
+
# The `.gene` macro defines traits that can mutate and evolve over time.
|
150
|
+
# Syntactically similar to ActiveRecord-style macros, it sets up the genetic structure of your model.
|
101
151
|
#
|
102
|
-
#
|
103
|
-
#
|
104
|
-
#
|
152
|
+
# Key features:
|
153
|
+
# - Fixed or variable gene counts
|
154
|
+
# - Automatic accessor methods
|
155
|
+
# - Optional clustering for related genes
|
105
156
|
#
|
106
|
-
#
|
157
|
+
# @example Basic gene definition
|
158
|
+
# class Melody
|
159
|
+
# include Evolvable
|
107
160
|
#
|
108
|
-
#
|
109
|
-
#
|
161
|
+
# gene :notes, type: NoteGene, count: 4..16 # Can have 4-16 notes
|
162
|
+
# gene :instrument, type: InstrumentGene, count: 1
|
110
163
|
#
|
111
|
-
#
|
112
|
-
#
|
113
|
-
#
|
114
|
-
# notes: { type: NoteGene, count: 16 } }
|
115
|
-
# end
|
116
|
-
# @example Array of arrays config
|
117
|
-
# # With explicit gene names
|
118
|
-
# def search_space
|
119
|
-
# [[:instrument, InstrumentGene, 1..4],
|
120
|
-
# [:notes, NoteGene, 16]]
|
164
|
+
# def play
|
165
|
+
# instrument.play(notes)
|
166
|
+
# end
|
121
167
|
# end
|
122
168
|
#
|
123
|
-
#
|
124
|
-
#
|
125
|
-
#
|
126
|
-
#
|
127
|
-
# @example Array config
|
128
|
-
# # Available when when just one type of gene
|
129
|
-
# def search_space
|
130
|
-
# [NoteGene, 1..100]
|
131
|
-
# end
|
169
|
+
# @param name [Symbol] The name of the gene
|
170
|
+
# @param type [String, Class] The gene type or class name
|
171
|
+
# @param count [Integer, Range] Number or range of gene instances
|
172
|
+
# @param cluster [Symbol, nil] Optional cluster name for grouping related genes
|
132
173
|
#
|
133
|
-
|
134
|
-
|
135
|
-
|
174
|
+
def gene(name, type:, count: 1, cluster: nil)
|
175
|
+
raise Error, "Gene name '#{name}' is already defined" if @gene_config.key?(name)
|
176
|
+
|
177
|
+
@gene_config[name] = { type: type, count: count }
|
178
|
+
|
179
|
+
if (count.is_a?(Range) ? count.last : count) > 1
|
180
|
+
define_method(name) { find_genes(name) }
|
181
|
+
else
|
182
|
+
define_method(name) { find_gene(name) }
|
183
|
+
end
|
184
|
+
|
185
|
+
if cluster
|
186
|
+
raise Error, "Cluster name '#{cluster}' conflicts with an existing gene name" if @gene_config.key?(cluster)
|
187
|
+
|
188
|
+
if @cluster_config[cluster]
|
189
|
+
@cluster_config[cluster] << name
|
190
|
+
else
|
191
|
+
@cluster_config[cluster] = [name]
|
192
|
+
define_method(cluster) { find_gene_cluster(cluster) }
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
#
|
198
|
+
# @readme
|
199
|
+
# The `.cluster` macro applies a pre-defined group of related genes.
|
200
|
+
#
|
201
|
+
# Clusters promote code organization through:
|
202
|
+
# - Modularity: Define related genes once, reuse them
|
203
|
+
# - Organization: Group genes by function
|
204
|
+
# - Maintenance: Update in one place
|
205
|
+
# - Accessibility: Access via a single accessor
|
206
|
+
#
|
207
|
+
# @example UI Component with Styling Cluster
|
208
|
+
# # Define a gene cluster for UI styling properties
|
209
|
+
# class ColorSchemeCluster
|
210
|
+
# include Evolvable::GeneCluster
|
211
|
+
#
|
212
|
+
# gene :background_color, type: 'ColorGene', count: 1
|
213
|
+
# gene :text_color, type: 'ColorGene', count: 1
|
214
|
+
# gene :accent_color, type: 'ColorGene', count: 0..1
|
136
215
|
# end
|
137
216
|
#
|
138
|
-
#
|
217
|
+
# # Use the cluster in an evolvable UI component
|
218
|
+
# class Button
|
219
|
+
# include Evolvable
|
139
220
|
#
|
140
|
-
#
|
221
|
+
# cluster :colors, type: ColorSchemeCluster
|
222
|
+
# gene :padding, type: PaddingGene, count: 1
|
141
223
|
#
|
142
|
-
def
|
143
|
-
|
224
|
+
# def render
|
225
|
+
# puts "Button with #{colors.count} colors"
|
226
|
+
# puts "Background: #{colors.background_color.hex_code}"
|
227
|
+
# end
|
228
|
+
# end
|
229
|
+
#
|
230
|
+
# @param cluster_name [Symbol] The name for accessing the cluster
|
231
|
+
# @param type [Class, String] The class that defines the cluster
|
232
|
+
# @param opts [Hash] Optional arguments passed to the cluster initializer
|
233
|
+
#
|
234
|
+
def cluster(cluster_name, type:, **opts)
|
235
|
+
recipe = type.is_a?(String) ? Object.const_get(type) : type
|
236
|
+
unless recipe.respond_to?(:apply_cluster)
|
237
|
+
raise ArgumentError, "#{recipe} cannot apply a gene cluster"
|
238
|
+
end
|
239
|
+
|
240
|
+
recipe.apply_cluster(self, cluster_name, **opts)
|
241
|
+
|
242
|
+
define_method(cluster_name) { find_gene_cluster(cluster_name) }
|
144
243
|
end
|
145
244
|
|
245
|
+
attr_reader :gene_config, :cluster_config
|
246
|
+
|
146
247
|
#
|
147
|
-
#
|
148
|
-
#
|
149
|
-
# @return [Array]
|
248
|
+
# Creates a new gene space for this evolvable class.
|
249
|
+
# Used internally when initializing populations.
|
150
250
|
#
|
151
|
-
# @
|
251
|
+
# @return [Evolvable::GeneSpace] A new gene space
|
152
252
|
#
|
153
|
-
def
|
154
|
-
|
253
|
+
def new_gene_space
|
254
|
+
GeneSpace.build(@gene_config, self)
|
155
255
|
end
|
156
256
|
|
157
|
-
#
|
158
|
-
#
|
159
|
-
#
|
160
|
-
|
161
|
-
|
257
|
+
#
|
258
|
+
# Ensures that subclasses inherit the gene and cluster configuration.
|
259
|
+
#
|
260
|
+
# @param subclass [Class] The subclass that is inheriting from this class
|
261
|
+
# @return [void]
|
262
|
+
#
|
263
|
+
def inherited(subclass)
|
264
|
+
super
|
265
|
+
subclass.instance_variable_set(:@gene_config, @gene_config.dup)
|
266
|
+
subclass.instance_variable_set(:@cluster_config, @cluster_config.dup)
|
162
267
|
end
|
163
268
|
|
164
|
-
|
165
269
|
#
|
166
270
|
# @readme
|
167
|
-
#
|
271
|
+
# Called before evaluation.
|
168
272
|
#
|
169
273
|
def before_evaluation(population); end
|
170
274
|
|
@@ -193,49 +297,106 @@ module Evolvable
|
|
193
297
|
def after_evolution(population); end
|
194
298
|
end
|
195
299
|
|
196
|
-
|
197
|
-
|
300
|
+
def after_initialize_evolvable; end
|
301
|
+
|
302
|
+
attr_accessor :fitness
|
303
|
+
|
304
|
+
def find_gene(...)
|
305
|
+
@genome&.find_gene(...)
|
306
|
+
end
|
307
|
+
|
308
|
+
def find_genes(...)
|
309
|
+
@genome&.find_genes(...) || []
|
310
|
+
end
|
311
|
+
|
312
|
+
attr_reader :population,
|
313
|
+
:genome,
|
314
|
+
:generation_index
|
198
315
|
|
316
|
+
def genes
|
317
|
+
@genome&.genes || []
|
318
|
+
end
|
319
|
+
|
320
|
+
#
|
321
|
+
# Makes this object evolvable by setting up its population, genome, and generation index.
|
322
|
+
# This is called internally by the class method `new_evolvable`.
|
199
323
|
#
|
200
|
-
#
|
201
|
-
#
|
324
|
+
# @param population [Evolvable::Population, nil] The population this evolvable belongs to
|
325
|
+
# @param genome [Evolvable::Genome, nil] The genome to initialize with
|
326
|
+
# @param generation_index [Integer, nil] The generation index
|
327
|
+
# @return [self] This evolvable object
|
202
328
|
#
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
329
|
+
def make_evolvable(population: nil, genome: nil, generation_index: nil)
|
330
|
+
self.population = population
|
331
|
+
@genome = genome
|
332
|
+
self.generation_index = generation_index
|
333
|
+
self
|
334
|
+
end
|
335
|
+
|
336
|
+
def population=(val)
|
337
|
+
@population = val
|
338
|
+
genes.each { |gene| gene.evolvable = self if gene.respond_to?(:evolvable=) }
|
339
|
+
end
|
208
340
|
|
341
|
+
def generation_index=(val)
|
342
|
+
@generation_index = val
|
343
|
+
end
|
344
|
+
|
345
|
+
#
|
346
|
+
# Finds an array of genes that belong to the specified cluster.
|
347
|
+
# This is used internally when accessing gene clusters.
|
209
348
|
#
|
210
|
-
# @
|
211
|
-
#
|
212
|
-
# Use {#generation_index} instead.
|
349
|
+
# @param cluster [Symbol] The cluster name
|
350
|
+
# @return [Array<Evolvable::Gene>] The genes belonging to the cluster
|
213
351
|
#
|
214
|
-
def
|
215
|
-
|
352
|
+
def find_gene_cluster(cluster)
|
353
|
+
find_genes(*self.class.cluster_config[cluster])
|
216
354
|
end
|
217
355
|
|
218
356
|
#
|
219
|
-
#
|
220
|
-
#
|
221
|
-
#
|
222
|
-
#
|
223
|
-
#
|
224
|
-
# @see Genome#find_genes_count
|
225
|
-
# @!method genes
|
226
|
-
# @see Genome#genes
|
357
|
+
# Dumps the genome to a serialized format.
|
358
|
+
# Useful for saving the state of an evolvable.
|
359
|
+
#
|
360
|
+
# @param serializer [Evolvable::Serializer] The serializer to use
|
361
|
+
# @return [String] The serialized genome
|
227
362
|
#
|
228
|
-
def_delegators :genome,
|
229
|
-
:find_gene,
|
230
|
-
:find_genes,
|
231
|
-
:find_genes_count,
|
232
|
-
:genes
|
233
|
-
|
234
363
|
def dump_genome(serializer: Serializer)
|
235
364
|
@genome.dump(serializer: serializer)
|
236
365
|
end
|
237
366
|
|
367
|
+
#
|
368
|
+
# Loads a genome from serialized data.
|
369
|
+
# Useful for restoring the state of an evolvable.
|
370
|
+
#
|
371
|
+
# @param data [String] The serialized genome data
|
372
|
+
# @param serializer [Evolvable::Serializer] The serializer to use
|
373
|
+
# @return [Evolvable::Genome] The loaded genome
|
374
|
+
#
|
238
375
|
def load_genome(data, serializer: Serializer)
|
239
376
|
@genome = Genome.load(data, serializer: serializer)
|
240
377
|
end
|
378
|
+
|
379
|
+
#
|
380
|
+
# Loads a genome from serialized data and merges it with the current genome.
|
381
|
+
# Useful for combining genomes from different sources.
|
382
|
+
#
|
383
|
+
# @param data [String] The serialized genome data
|
384
|
+
# @param serializer [Evolvable::Serializer] The serializer to use
|
385
|
+
# @return [Evolvable::Genome] The merged genome
|
386
|
+
#
|
387
|
+
def load_and_merge_genome!(data, serializer: Serializer)
|
388
|
+
genome = Genome.load(data, serializer: serializer)
|
389
|
+
merge_genome!(genome)
|
390
|
+
end
|
391
|
+
|
392
|
+
#
|
393
|
+
# Merges another genome into this evolvable's genome.
|
394
|
+
# Useful for combining genetic traits from different evolvables.
|
395
|
+
#
|
396
|
+
# @param other_genome [Evolvable::Genome] The genome to merge
|
397
|
+
# @return [Evolvable::Genome] The merged genome
|
398
|
+
#
|
399
|
+
def merge_genome!(other_genome)
|
400
|
+
@genome.merge!(other_genome)
|
401
|
+
end
|
241
402
|
end
|