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
@@ -0,0 +1,111 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Evolvable
|
4
|
+
#
|
5
|
+
# The gene space defines the structure of an evolvable's genome.
|
6
|
+
# It acts as a blueprint that describes what kinds of genes each
|
7
|
+
# evolvable instance should have—and how many.
|
8
|
+
#
|
9
|
+
# At runtime, Evolvable::GeneSpace is responsible for interpreting this blueprint,
|
10
|
+
# constructing genomes, and managing gene configurations. You typically won’t need to
|
11
|
+
# interact with GeneSpace directly.
|
12
|
+
#
|
13
|
+
# @see Evolvable::Gene
|
14
|
+
# @see Evolvable::GeneCluster
|
15
|
+
# @see Evolvable::GeneCombination
|
16
|
+
#
|
17
|
+
class GeneSpace
|
18
|
+
class << self
|
19
|
+
def build(config, evolvable_class = nil)
|
20
|
+
if config.is_a?(GeneSpace)
|
21
|
+
config.evolvable_class = evolvable_class if evolvable_class
|
22
|
+
config
|
23
|
+
else
|
24
|
+
new(config: config, evolvable_class: evolvable_class)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def initialize(config: {}, evolvable_class: nil)
|
30
|
+
@evolvable_class = evolvable_class
|
31
|
+
@config = normalize_config(config)
|
32
|
+
end
|
33
|
+
|
34
|
+
attr_accessor :evolvable_class, :config
|
35
|
+
|
36
|
+
def new_genome
|
37
|
+
Genome.new(config: new_genome_config)
|
38
|
+
end
|
39
|
+
|
40
|
+
def merge_gene_space(val)
|
41
|
+
val = val.config if val.is_a?(self.class)
|
42
|
+
@config.merge normalize_config(val)
|
43
|
+
end
|
44
|
+
|
45
|
+
def merge_gene_space!(val)
|
46
|
+
val = val.config if val.is_a?(self.class)
|
47
|
+
@config.merge! normalize_config(val)
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def normalize_config(config)
|
53
|
+
normalize_hash_config(config)
|
54
|
+
end
|
55
|
+
|
56
|
+
def normalize_hash_config(config)
|
57
|
+
config.each do |gene_key, gene_config|
|
58
|
+
next unless gene_config[:type]
|
59
|
+
|
60
|
+
gene_class = lookup_gene_class(gene_config[:type])
|
61
|
+
gene_class.key = gene_key
|
62
|
+
gene_config[:class] = gene_class
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def lookup_gene_class(class_name)
|
67
|
+
return class_name if class_name.is_a?(Class)
|
68
|
+
|
69
|
+
(@evolvable_class || Object).const_get(class_name)
|
70
|
+
end
|
71
|
+
|
72
|
+
def new_genome_config
|
73
|
+
genome_config = {}
|
74
|
+
config.each do |gene_key, gene_config|
|
75
|
+
genome_config[gene_key] = new_gene_config(gene_config)
|
76
|
+
end
|
77
|
+
genome_config
|
78
|
+
end
|
79
|
+
|
80
|
+
def new_gene_config(gene_config)
|
81
|
+
count_gene = init_count_gene(gene_config)
|
82
|
+
gene_class = gene_config[:class]
|
83
|
+
genes = Array.new(count_gene.count) { gene_class.new }
|
84
|
+
{ count_gene: count_gene, genes: genes }
|
85
|
+
end
|
86
|
+
|
87
|
+
def init_count_gene(gene_config)
|
88
|
+
min = gene_config[:min_count]
|
89
|
+
max = gene_config[:max_count]
|
90
|
+
return init_min_max_count_gene(min, max) if min || max
|
91
|
+
|
92
|
+
count = gene_config[:count] || 1
|
93
|
+
case count
|
94
|
+
when Numeric
|
95
|
+
RigidCountGene.new(count)
|
96
|
+
when Range
|
97
|
+
CountGene.new(range: count)
|
98
|
+
when String
|
99
|
+
Object.const_get(gene_config[:count]).new
|
100
|
+
when Class
|
101
|
+
gene_config[:count].new
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def init_min_max_count_gene(min, max)
|
106
|
+
min ||= 1
|
107
|
+
max ||= min * 10
|
108
|
+
CountGene.new(range: min..max)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
data/lib/evolvable/genome.rb
CHANGED
@@ -2,8 +2,22 @@
|
|
2
2
|
|
3
3
|
module Evolvable
|
4
4
|
#
|
5
|
-
#
|
6
|
-
#
|
5
|
+
# The Genome class represents the fully instantiated genetic blueprint of an evolvable instance.
|
6
|
+
# It stores all gene data in a structured, accessible form and provides methods to inspect,
|
7
|
+
# manipulate, and serialize that genetic information.
|
8
|
+
#
|
9
|
+
# A genome consists of:
|
10
|
+
# - A hash of gene configurations organized by key
|
11
|
+
# - Count genes that determine how many instances of each gene type are present
|
12
|
+
# - The actual gene instances used by the evolvable
|
13
|
+
#
|
14
|
+
# The genome acts as the bridge between the gene space (definition) and the
|
15
|
+
# evolvable instance (implementation), enabling flexible gene access and
|
16
|
+
# supporting dynamic mutation or crossover behavior.
|
17
|
+
#
|
18
|
+
# @see Evolvable::GeneSpace
|
19
|
+
# @see Evolvable::GeneCombination
|
20
|
+
# @see Evolvable::Gene
|
7
21
|
#
|
8
22
|
class Genome
|
9
23
|
extend Forwardable
|
@@ -18,8 +32,15 @@ module Evolvable
|
|
18
32
|
|
19
33
|
attr_reader :config
|
20
34
|
|
35
|
+
alias to_h config
|
36
|
+
|
21
37
|
#
|
22
|
-
# Returns the first gene with the given key. In the Melody example above, the instrument
|
38
|
+
# Returns the first gene with the given key. In the Melody example above, the instrument
|
39
|
+
# gene has the key `:instrument` so we might write something like:
|
40
|
+
#
|
41
|
+
# ```ruby
|
42
|
+
# instrument_gene = melody.find_gene(instrument)
|
43
|
+
# ```
|
23
44
|
#
|
24
45
|
# @param [<Type>] key <description>
|
25
46
|
#
|
@@ -30,7 +51,10 @@ module Evolvable
|
|
30
51
|
end
|
31
52
|
|
32
53
|
#
|
33
|
-
# Returns an array of genes that have the given key. Gene keys are defined
|
54
|
+
# Returns an array of genes that have the given key. Gene keys are defined using the
|
55
|
+
# [EvolvableClass.gene](https://mattruzicka.github.io/evolvable/Evolvable/ClassMethods#gene-instance_method)
|
56
|
+
# macro method. In the Melody example above, the key for the note genes would be `:notes`.
|
57
|
+
# The following would return an array of them: `note_genes = melody.find_genes(:notes)`
|
34
58
|
#
|
35
59
|
# @param [<Type>] *keys <description>
|
36
60
|
#
|
@@ -75,6 +99,10 @@ module Evolvable
|
|
75
99
|
@config.flat_map { |_gene_key, gene_config| gene_config[:genes] }
|
76
100
|
end
|
77
101
|
|
102
|
+
def merge!(other_genome)
|
103
|
+
@config.merge!(other_genome.config)
|
104
|
+
end
|
105
|
+
|
78
106
|
def inspect
|
79
107
|
self.class.name
|
80
108
|
end
|
data/lib/evolvable/goal.rb
CHANGED
@@ -1,36 +1,31 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Evolvable
|
4
|
-
|
5
|
-
#
|
6
|
-
# The goal for a population can be specified via assignment - `population.goal = Evolvable::Goal::Equalize.new` - or by passing an evaluation object when [initializing a population](#evolvablepopulationnew).
|
7
4
|
#
|
8
|
-
#
|
5
|
+
# @see Evolvable::Evaluation
|
9
6
|
#
|
10
|
-
#
|
7
|
+
# @readme
|
8
|
+
# **Custom Goals**
|
11
9
|
#
|
12
|
-
#
|
13
|
-
#
|
14
|
-
#
|
10
|
+
# You can create custom goals by subclassing `Evolvable::Goal` and implementing:
|
11
|
+
# - `evaluate(evolvable)`: Return a value that for sorting evolvables
|
12
|
+
# - `met?(evolvable)`: Returns true when the goal value is reached
|
15
13
|
#
|
16
|
-
#
|
14
|
+
# @example Example goal implementation that prioritizes evolvables with fitness values within a specific range
|
15
|
+
# class YourRangeGoal < Evolvable::Goal
|
16
|
+
# def value
|
17
|
+
# @value ||= 0..100
|
18
|
+
# end
|
17
19
|
#
|
18
|
-
#
|
19
|
-
#
|
20
|
-
# Evolvable::Evaluation.new(maximize: 50) # Sets goal value to 50
|
21
|
-
# Evolvable::Evaluation.new(:minimize) # Uses default goal value of -Float::INFINITY
|
22
|
-
# Evolvable::Evaluation.new(minimize: 100) # Sets goal value to 100
|
23
|
-
# Evolvable::Evaluation.new(:equalize) # Uses default goal value of 0
|
24
|
-
# Evolvable::Evaluation.new(equalize: 1000) # Sets goal value to 1000
|
20
|
+
# def evaluate(evolvable)
|
21
|
+
# return 1 if value.include?(evolvable.fitness)
|
25
22
|
#
|
26
|
-
#
|
27
|
-
#
|
28
|
-
# def evaluate(instance)
|
29
|
-
# # Required by Evolvable::Evaluation in order to sort instances in preparation for selection.
|
23
|
+
# min, max = value.minmax
|
24
|
+
# -[(min - evolvable.fitness).abs, (max - evolvable.fitness).abs].min
|
30
25
|
# end
|
31
26
|
#
|
32
|
-
# def met?(
|
33
|
-
#
|
27
|
+
# def met?(evolvable)
|
28
|
+
# value.include?(evolvable.fitness)
|
34
29
|
# end
|
35
30
|
# end
|
36
31
|
#
|
@@ -42,11 +37,11 @@ module Evolvable
|
|
42
37
|
attr_accessor :value
|
43
38
|
|
44
39
|
def evaluate(_evolvable)
|
45
|
-
raise
|
40
|
+
raise Error, "Undefined method: #{self.class.name}##{__method__}"
|
46
41
|
end
|
47
42
|
|
48
43
|
def met?(_evolvable)
|
49
|
-
raise
|
44
|
+
raise Error, "Undefined method: #{self.class.name}##{__method__}"
|
50
45
|
end
|
51
46
|
end
|
52
47
|
end
|
@@ -13,18 +13,11 @@ module Evolvable
|
|
13
13
|
end
|
14
14
|
|
15
15
|
def evaluate(evolvable)
|
16
|
-
evolvable.
|
16
|
+
evolvable.fitness
|
17
17
|
end
|
18
18
|
|
19
19
|
def met?(evolvable)
|
20
|
-
evolvable.
|
20
|
+
evolvable.fitness >= value
|
21
21
|
end
|
22
22
|
end
|
23
|
-
|
24
|
-
#
|
25
|
-
# @deprecated
|
26
|
-
# Will be removed in 2.0.
|
27
|
-
# Use {MaximizeGoal} instead
|
28
|
-
#
|
29
|
-
class Goal::Maximize < MaximizeGoal; end
|
30
23
|
end
|
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Evolvable
|
4
|
+
#
|
4
5
|
# Prioritizes instances with lesser values.
|
5
6
|
#
|
6
7
|
# The default goal value is `-Float::INFINITY`, but it can be reassigned
|
@@ -12,18 +13,11 @@ module Evolvable
|
|
12
13
|
end
|
13
14
|
|
14
15
|
def evaluate(evolvable)
|
15
|
-
-evolvable.
|
16
|
+
-evolvable.fitness
|
16
17
|
end
|
17
18
|
|
18
19
|
def met?(evolvable)
|
19
|
-
evolvable.
|
20
|
+
evolvable.fitness <= value
|
20
21
|
end
|
21
22
|
end
|
22
|
-
|
23
|
-
#
|
24
|
-
# @deprecated
|
25
|
-
# Will be removed in 2.0.
|
26
|
-
# Use {MinimizeGoal} instead
|
27
|
-
#
|
28
|
-
class Goal::Minimize < MinimizeGoal; end
|
29
23
|
end
|
data/lib/evolvable/mutation.rb
CHANGED
@@ -3,70 +3,101 @@
|
|
3
3
|
module Evolvable
|
4
4
|
#
|
5
5
|
# @readme
|
6
|
-
# Mutation
|
7
|
-
#
|
8
|
-
#
|
9
|
-
# from the genetic search space.
|
6
|
+
# Mutation introduces genetic variation by randomly replacing genes with new
|
7
|
+
# ones. This helps the population explore new areas of the solution space
|
8
|
+
# and prevents premature convergence on suboptimal solutions.
|
10
9
|
#
|
11
|
-
# Mutation
|
12
|
-
#
|
10
|
+
# Mutation is controlled by two key parameters:
|
11
|
+
# - **probability**: Likelihood that an individual will undergo mutation (range: 0.0–1.0)
|
12
|
+
# - **rate**: Fraction of genes to mutate within those individuals (range: 0.0–1.0)
|
13
13
|
#
|
14
|
-
#
|
15
|
-
#
|
14
|
+
# A typical strategy is to start with higher mutation to encourage exploration:
|
15
|
+
#
|
16
|
+
# ```ruby
|
17
|
+
# population = MyEvolvable.new_population(
|
18
|
+
# mutation: { probability: 0.4, rate: 0.2 }
|
19
|
+
# )
|
20
|
+
# ```
|
21
|
+
#
|
22
|
+
# Then later reduce the mutation rate to focus on refinement and convergence:
|
23
|
+
#
|
24
|
+
# ```ruby
|
25
|
+
# population.mutation_probability = 0.1
|
26
|
+
# population.mutation_rate = 0.05
|
27
|
+
# ```
|
16
28
|
#
|
17
29
|
class Mutation
|
18
30
|
extend Forwardable
|
19
31
|
|
32
|
+
#
|
33
|
+
# The default probability of mutation (3%).
|
34
|
+
# This is used when no probability is specified.
|
35
|
+
#
|
20
36
|
DEFAULT_PROBABILITY = 0.03
|
21
37
|
|
22
38
|
#
|
23
39
|
# Initializes a new mutation object.
|
24
40
|
#
|
25
|
-
#
|
26
|
-
#
|
27
|
-
#
|
28
|
-
#
|
29
|
-
#
|
30
|
-
#
|
31
|
-
#
|
32
|
-
#
|
33
|
-
#
|
34
|
-
#
|
35
|
-
#
|
36
|
-
#
|
37
|
-
#
|
38
|
-
#
|
39
|
-
#
|
40
|
-
#
|
41
|
-
#
|
42
|
-
#
|
43
|
-
# `probability` is 0, no mutation will ever happen. When the `probability`
|
44
|
-
# is not 0 but the rate is 0, then any instance that undergoes mutation
|
45
|
-
# will only receive one mutant gene. If the rate is not 0, then if an
|
46
|
-
# instance has been chosen to undergo mutation, each of its genes will
|
47
|
-
# mutate with a probability as defined by the `rate`.
|
48
|
-
#
|
49
|
-
# @example Example Initializations:
|
50
|
-
# Evolvable::Mutation.new # Approximately #{DEFAULT_PROBABILITY} of instances will receive one mutant gene
|
51
|
-
# Evolvable::Mutation.new(probability: 0.5) # Approximately 50% of instances will receive one mutant gene
|
52
|
-
# Evolvable::Mutation.new(rate: 0.03) # Approximately 3% of all genes in the population will mutate.
|
53
|
-
# Evolvable::Mutation.new(probability: 0.3, rate: 0.03) # Approximately 30% of instances will have approximately 3% of their genes mutated.
|
54
|
-
#
|
55
|
-
# Custom mutation objects must implement the `#call` method which accepts the population as the first object.
|
41
|
+
# @example Basic initialization patterns
|
42
|
+
# # Default: 3% of instances get one mutated gene
|
43
|
+
# Evolvable::Mutation.new
|
44
|
+
#
|
45
|
+
# # 50% of instances get one mutated gene
|
46
|
+
# Evolvable::Mutation.new(probability: 0.5)
|
47
|
+
#
|
48
|
+
# # All instances are considered, with 3% of genes mutating in each
|
49
|
+
# Evolvable::Mutation.new(rate: 0.03)
|
50
|
+
#
|
51
|
+
# # 30% of instances have 3% of their genes mutated
|
52
|
+
# Evolvable::Mutation.new(probability: 0.3, rate: 0.03)
|
53
|
+
#
|
54
|
+
# When rate is specified but probability isn't, probability defaults to 1.0.
|
55
|
+
# When rate is 0 or not specified, only one gene is mutated per affected instance.
|
56
|
+
#
|
57
|
+
# @param probability [Float, nil] Chance of an instance being mutated (0.0-1.0)
|
58
|
+
# @param rate [Float, nil] Chance of each gene mutating when an instance is selected (0.0-1.0)
|
56
59
|
#
|
57
60
|
def initialize(probability: nil, rate: nil)
|
58
61
|
@probability = probability || (rate ? 1 : DEFAULT_PROBABILITY)
|
59
62
|
@rate = rate
|
60
63
|
end
|
61
64
|
|
62
|
-
|
63
|
-
|
65
|
+
#
|
66
|
+
# The probability that an evolvable instance will undergo mutation.
|
67
|
+
# Value between 0.0 (never) and 1.0 (always).
|
68
|
+
#
|
69
|
+
# @return [Float] The mutation probability
|
70
|
+
#
|
71
|
+
attr_accessor :probability
|
64
72
|
|
73
|
+
#
|
74
|
+
# The rate at which genes mutate within an instance.
|
75
|
+
# Value between 0.0 (no genes mutate) and 1.0 (all genes likely to mutate).
|
76
|
+
# When nil, exactly one random gene is mutated per instance.
|
77
|
+
#
|
78
|
+
# @return [Float, nil] The mutation rate
|
79
|
+
#
|
80
|
+
attr_accessor :rate
|
81
|
+
|
82
|
+
#
|
83
|
+
# Applies mutations to the population's evolvables based on the
|
84
|
+
# configured probability and rate.
|
85
|
+
#
|
86
|
+
# @param population [Evolvable::Population] The population to mutate
|
87
|
+
# @return [Evolvable::Population] The mutated population
|
88
|
+
#
|
65
89
|
def call(population)
|
66
90
|
mutate_evolvables(population.evolvables) unless probability.zero?
|
67
91
|
population
|
68
92
|
end
|
69
93
|
|
94
|
+
#
|
95
|
+
# Mutates a collection of evolvable instances based on the mutation
|
96
|
+
# probability and rate.
|
97
|
+
#
|
98
|
+
# @param evolvables [Array<Evolvable>] The collection of evolvable instances to potentially mutate
|
99
|
+
# @return [Array<Evolvable>] The potentially mutated evolvables
|
100
|
+
#
|
70
101
|
def mutate_evolvables(evolvables)
|
71
102
|
evolvables.each do |evolvable|
|
72
103
|
next unless rand <= probability
|
@@ -77,6 +108,14 @@ module Evolvable
|
|
77
108
|
|
78
109
|
private
|
79
110
|
|
111
|
+
#
|
112
|
+
# Mutates genes in the given collection based on the mutation rate.
|
113
|
+
# If a rate is set, each gene has a chance to mutate according to the rate.
|
114
|
+
# If no rate is set, a single random gene is mutated.
|
115
|
+
#
|
116
|
+
# @param genes [Array<Evolvable::Gene>] The collection of genes to potentially mutate
|
117
|
+
# @return [void]
|
118
|
+
#
|
80
119
|
def mutate_genes(genes)
|
81
120
|
genes_count = genes.count
|
82
121
|
return if genes_count.zero?
|
@@ -86,6 +125,13 @@ module Evolvable
|
|
86
125
|
genes_count.times { |index| mutate_gene_by_index(genes, index) if rand <= rate }
|
87
126
|
end
|
88
127
|
|
128
|
+
#
|
129
|
+
# Replaces a gene at the specified index with a new instance of the same gene class.
|
130
|
+
#
|
131
|
+
# @param genes [Array<Evolvable::Gene>] The collection of genes
|
132
|
+
# @param gene_index [Integer] The index of the gene to mutate
|
133
|
+
# @return [void]
|
134
|
+
#
|
89
135
|
def mutate_gene_by_index(genes, gene_index)
|
90
136
|
genes[gene_index] = genes[gene_index].class.new
|
91
137
|
end
|
@@ -2,10 +2,30 @@
|
|
2
2
|
|
3
3
|
module Evolvable
|
4
4
|
#
|
5
|
-
#
|
6
|
-
#
|
7
|
-
#
|
8
|
-
#
|
5
|
+
# @readme
|
6
|
+
# A classic genetic algorithm strategy that performs single or multi-point crossover
|
7
|
+
# by selecting random positions in the genome and swapping gene segments between parents.
|
8
|
+
#
|
9
|
+
# - **Single-point crossover (default):** Swaps all genes after a randomly chosen position.
|
10
|
+
# - **Multi-point crossover:** Alternates segments between multiple randomly chosen points.
|
11
|
+
#
|
12
|
+
# Best for:
|
13
|
+
# - Preserving beneficial gene blocks
|
14
|
+
# - Problems where related traits are located near each other
|
15
|
+
#
|
16
|
+
# Set your population to use this strategy during initialization with:
|
17
|
+
#
|
18
|
+
# ```ruby
|
19
|
+
# population = MyEvolvable.new_population(
|
20
|
+
# combination: Evolvable::PointCrossover.new(points_count: 2)
|
21
|
+
# )
|
22
|
+
# ```
|
23
|
+
#
|
24
|
+
# Or update an existing population:
|
25
|
+
#
|
26
|
+
# ```ruby
|
27
|
+
# population.combination = Evolvable::PointCrossover.new(points_count: 3)
|
28
|
+
# ```
|
9
29
|
#
|
10
30
|
class PointCrossover
|
11
31
|
def initialize(points_count: 1)
|