evolvable 0.1.3 → 1.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/.gitignore +0 -1
- data/.rubocop.yml +1 -1
- data/CHANGELOG.md +14 -0
- data/Gemfile.lock +9 -26
- data/README.md +167 -254
- data/bin/console +5 -41
- data/evolvable.gemspec +0 -1
- data/examples/evolvable_string.rb +32 -0
- data/examples/evolvable_string/char_gene.rb +9 -0
- data/lib/evolvable.rb +45 -58
- data/lib/evolvable/error/undefined_method.rb +7 -0
- data/lib/evolvable/evaluation.rb +51 -0
- data/lib/evolvable/evolution.rb +26 -0
- data/lib/evolvable/gene.rb +13 -0
- data/lib/evolvable/gene_crossover.rb +28 -0
- data/lib/evolvable/gene_space.rb +37 -0
- data/lib/evolvable/goal.rb +19 -0
- data/lib/evolvable/goal/equalize.rb +19 -0
- data/lib/evolvable/goal/maximize.rb +19 -0
- data/lib/evolvable/goal/minimize.rb +19 -0
- data/lib/evolvable/mutation.rb +22 -44
- data/lib/evolvable/point_crossover.rb +57 -0
- data/lib/evolvable/population.rb +70 -91
- data/lib/evolvable/selection.rb +18 -0
- data/lib/evolvable/uniform_crossover.rb +22 -0
- data/lib/evolvable/version.rb +1 -1
- metadata +18 -7
- 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
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Evolvable::Goal
|
4
|
+
class Maximize
|
5
|
+
include Evolvable::Goal
|
6
|
+
|
7
|
+
def value
|
8
|
+
@value ||= Float::INFINITY
|
9
|
+
end
|
10
|
+
|
11
|
+
def evaluate(instance)
|
12
|
+
instance.value
|
13
|
+
end
|
14
|
+
|
15
|
+
def met?(instance)
|
16
|
+
instance.value >= value
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Evolvable::Goal
|
4
|
+
class Minimize
|
5
|
+
include Evolvable::Goal
|
6
|
+
|
7
|
+
def value
|
8
|
+
@value ||= -Float::INFINITY
|
9
|
+
end
|
10
|
+
|
11
|
+
def evaluate(instance)
|
12
|
+
-instance.value
|
13
|
+
end
|
14
|
+
|
15
|
+
def met?(instance)
|
16
|
+
instance.value <= value
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
data/lib/evolvable/mutation.rb
CHANGED
@@ -1,64 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Evolvable
|
2
4
|
class Mutation
|
3
5
|
extend Forwardable
|
4
6
|
|
5
|
-
def initialize(rate:
|
6
|
-
@
|
7
|
+
def initialize(probability: nil, rate: nil)
|
8
|
+
@probability = probability || (rate ? 1 : 0.03)
|
9
|
+
@rate = rate || 0
|
7
10
|
end
|
8
11
|
|
9
|
-
attr_accessor :
|
10
|
-
|
11
|
-
def_delegators :@evolvable_class,
|
12
|
-
:evolvable_genes_count,
|
13
|
-
:evolvable_gene_pool_size,
|
14
|
-
:evolvable_random_genes
|
12
|
+
attr_accessor :probability,
|
13
|
+
:rate
|
15
14
|
|
16
|
-
def call
|
17
|
-
|
18
|
-
mutations_count = find_mutations_count(objects)
|
19
|
-
return if mutations_count.zero?
|
15
|
+
def call(population)
|
16
|
+
return population if probability.zero?
|
20
17
|
|
21
|
-
|
22
|
-
|
23
|
-
object_mutations_count = 1 if object_mutations_count.zero?
|
24
|
-
|
25
|
-
mutant_genes.each_slice(object_mutations_count).with_index do |m_genes, index|
|
26
|
-
object = objects[index] || objects.sample
|
27
|
-
genes = object.genes
|
28
|
-
genes.merge!(m_genes.to_h)
|
29
|
-
rm_genes_count = genes.count - evolvable_genes_count
|
30
|
-
genes.keys.sample(rm_genes_count).each { |key| genes.delete(key) }
|
18
|
+
population.instances.each do |instance|
|
19
|
+
mutate_instance(instance) if rand <= probability
|
31
20
|
end
|
32
|
-
|
33
|
-
|
34
|
-
def inspect
|
35
|
-
"#<#{self.class.name} #{as_json.map { |a| a.join(': ') }.join(', ')} >"
|
36
|
-
end
|
37
|
-
|
38
|
-
def as_json
|
39
|
-
{ type: self.class.name,
|
40
|
-
rate: @rate }
|
21
|
+
population
|
41
22
|
end
|
42
23
|
|
43
24
|
private
|
44
25
|
|
45
|
-
def
|
46
|
-
|
26
|
+
def mutate_instance(instance)
|
27
|
+
genes_count = instance.genes.count
|
28
|
+
return if genes_count.zero?
|
47
29
|
|
48
|
-
|
49
|
-
return count.to_i if count >= 1
|
30
|
+
return mutate_gene(instance, rand(genes_count)) if rate.zero?
|
50
31
|
|
51
|
-
|
32
|
+
genes_count.times { |index| mutate_gene(instance, index) if rand <= rate }
|
52
33
|
end
|
53
34
|
|
54
|
-
def
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
mutant_genes.concat evolvable_random_genes(genes_count).to_a
|
60
|
-
end
|
61
|
-
mutant_genes
|
35
|
+
def mutate_gene(instance, gene_index)
|
36
|
+
gene = instance.genes[gene_index]
|
37
|
+
mutant_gene = gene.class.new
|
38
|
+
mutant_gene.key = gene.key
|
39
|
+
instance.genes[gene_index] = mutant_gene
|
62
40
|
end
|
63
41
|
end
|
64
42
|
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Evolvable
|
4
|
+
class PointCrossover
|
5
|
+
def initialize(points_count: 1)
|
6
|
+
@points_count = points_count
|
7
|
+
end
|
8
|
+
|
9
|
+
attr_accessor :points_count
|
10
|
+
|
11
|
+
def call(population)
|
12
|
+
population.instances = initialize_offspring(population)
|
13
|
+
population
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def initialize_offspring(population)
|
19
|
+
parent_genes = population.instances.map!(&:genes)
|
20
|
+
parent_gene_couples = parent_genes.combination(2).cycle
|
21
|
+
offspring = []
|
22
|
+
population_index = 0
|
23
|
+
loop do
|
24
|
+
genes_1, genes_2 = parent_gene_couples.next
|
25
|
+
crossover_genes(genes_1, genes_2).each do |genes|
|
26
|
+
offspring << population.new_instance(genes: genes, population_index: population_index)
|
27
|
+
population_index += 1
|
28
|
+
return offspring if population_index == population.size
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def crossover_genes(genes_1, genes_2)
|
34
|
+
offspring_genes = [[], []]
|
35
|
+
generate_ranges(genes_1.length).each do |range|
|
36
|
+
offspring_genes.reverse!
|
37
|
+
offspring_genes[0][range] = genes_1[range]
|
38
|
+
offspring_genes[1][range] = genes_2[range]
|
39
|
+
end
|
40
|
+
offspring_genes
|
41
|
+
end
|
42
|
+
|
43
|
+
def generate_ranges(genes_count)
|
44
|
+
current_point = rand(genes_count)
|
45
|
+
range_slices = [0...current_point]
|
46
|
+
(points_count - 1).times do
|
47
|
+
new_point = rand(current_point...genes_count)
|
48
|
+
break if new_point.nil?
|
49
|
+
|
50
|
+
range_slices << (current_point...new_point)
|
51
|
+
current_point = new_point
|
52
|
+
end
|
53
|
+
range_slices << (current_point..-1)
|
54
|
+
range_slices
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
data/lib/evolvable/population.rb
CHANGED
@@ -4,115 +4,94 @@ module Evolvable
|
|
4
4
|
class Population
|
5
5
|
extend Forwardable
|
6
6
|
|
7
|
-
def initialize(
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
7
|
+
def initialize(id: nil,
|
8
|
+
evolvable_class:,
|
9
|
+
name: nil,
|
10
|
+
size: 40,
|
11
|
+
evolutions_count: 0,
|
12
|
+
gene_space: nil,
|
13
|
+
evolution: Evolution.new,
|
14
|
+
evaluation: Evaluation.new,
|
15
|
+
instances: [])
|
16
|
+
@id = id
|
15
17
|
@evolvable_class = evolvable_class
|
18
|
+
@name = name
|
16
19
|
@size = size
|
17
|
-
@
|
18
|
-
@
|
19
|
-
@
|
20
|
-
@
|
21
|
-
|
22
|
-
assign_objects(objects)
|
20
|
+
@evolutions_count = evolutions_count
|
21
|
+
@gene_space = initialize_gene_space(gene_space)
|
22
|
+
@evolution = evolution
|
23
|
+
@evaluation = evaluation || Evaluation.new
|
24
|
+
initialize_instances(instances)
|
23
25
|
end
|
24
26
|
|
25
|
-
attr_accessor :
|
27
|
+
attr_accessor :id,
|
28
|
+
:evolvable_class,
|
29
|
+
:name,
|
26
30
|
: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
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
end
|
62
|
-
|
63
|
-
def evaluate_objects!
|
64
|
-
evolvable_evaluate!(@objects)
|
65
|
-
if @fitness_goal
|
66
|
-
@objects.sort_by! { |i| -(i.fitness - @fitness_goal).abs }
|
67
|
-
else
|
68
|
-
@objects.sort_by!(&:fitness)
|
31
|
+
:evolutions_count,
|
32
|
+
:gene_space,
|
33
|
+
:evolution,
|
34
|
+
:evaluation,
|
35
|
+
:instances
|
36
|
+
|
37
|
+
def_delegators :evolvable_class,
|
38
|
+
:before_evaluation,
|
39
|
+
:before_evolution,
|
40
|
+
:after_evolution
|
41
|
+
|
42
|
+
def_delegators :evolution,
|
43
|
+
:selection,
|
44
|
+
:selection=,
|
45
|
+
:crossover,
|
46
|
+
:crossover=,
|
47
|
+
:mutation,
|
48
|
+
:mutation=
|
49
|
+
|
50
|
+
def_delegators :evaluation,
|
51
|
+
:goal,
|
52
|
+
:goal=
|
53
|
+
|
54
|
+
def evolve(count: Float::INFINITY, goal_value: nil)
|
55
|
+
goal.value = goal_value if goal_value
|
56
|
+
(1..count).each do
|
57
|
+
before_evaluation(self)
|
58
|
+
evaluation.call(self)
|
59
|
+
before_evolution(self)
|
60
|
+
break if met_goal?
|
61
|
+
|
62
|
+
evolution.call(self)
|
63
|
+
self.evolutions_count += 1
|
64
|
+
after_evolution(self)
|
69
65
|
end
|
70
66
|
end
|
71
67
|
|
72
|
-
def
|
73
|
-
|
68
|
+
def best_instance
|
69
|
+
evaluation.best_instance(self)
|
74
70
|
end
|
75
71
|
|
76
|
-
def
|
77
|
-
|
72
|
+
def met_goal?
|
73
|
+
evaluation.met_goal?(self)
|
78
74
|
end
|
79
75
|
|
80
|
-
def
|
81
|
-
|
76
|
+
def new_instance(genes: [], population_index: nil)
|
77
|
+
evolvable_class.new_instance(population: self,
|
78
|
+
genes: genes,
|
79
|
+
population_index: population_index)
|
82
80
|
end
|
83
81
|
|
84
|
-
|
85
|
-
parent_genes = @objects.map(&:genes)
|
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
|
90
|
-
end
|
91
|
-
|
92
|
-
def mutate_objects!
|
93
|
-
@mutation.call!(@objects)
|
94
|
-
end
|
82
|
+
private
|
95
83
|
|
96
|
-
def
|
97
|
-
|
98
|
-
end
|
84
|
+
def initialize_gene_space(gene_space)
|
85
|
+
return GeneSpace.build(gene_space) if gene_space
|
99
86
|
|
100
|
-
|
101
|
-
{ evolvable_class: @evolvable_class.name,
|
102
|
-
size: @size,
|
103
|
-
selection_count: @selection_count,
|
104
|
-
crossover: @crossover.as_json,
|
105
|
-
mutation: @mutation.as_json,
|
106
|
-
generation_count: @generation_count }
|
87
|
+
evolvable_class.new_gene_space
|
107
88
|
end
|
108
89
|
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
genes = evolvable_random_genes
|
115
|
-
@objects << evolvable_initialize(genes, self, n)
|
90
|
+
def initialize_instances(instances)
|
91
|
+
@instances = instances || []
|
92
|
+
(@size - instances.count).times do |n|
|
93
|
+
genes = gene_space.new_genes
|
94
|
+
@instances << new_instance(genes: genes, population_index: n)
|
116
95
|
end
|
117
96
|
end
|
118
97
|
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Evolvable
|
4
|
+
class Selection
|
5
|
+
extend Forwardable
|
6
|
+
|
7
|
+
def initialize(size: 2)
|
8
|
+
@size = size
|
9
|
+
end
|
10
|
+
|
11
|
+
attr_accessor :size
|
12
|
+
|
13
|
+
def call(population)
|
14
|
+
population.instances.slice!(0..-1 - @size)
|
15
|
+
population
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Evolvable
|
4
|
+
class UniformCrossover
|
5
|
+
def call(population)
|
6
|
+
population.instances = initialize_offspring(population)
|
7
|
+
population
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
def initialize_offspring(population)
|
13
|
+
parent_genes = population.instances.map!(&:genes)
|
14
|
+
parent_gene_couples = parent_genes.combination(2).cycle
|
15
|
+
Array.new(population.size) do |index|
|
16
|
+
genes_1, genes_2 = parent_gene_couples.next
|
17
|
+
genes = genes_1.zip(genes_2).map!(&:sample)
|
18
|
+
population.new_instance(genes: genes, population_index: index)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
data/lib/evolvable/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: evolvable
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Matt Ruzicka
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2020-09-11 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -67,13 +67,24 @@ files:
|
|
67
67
|
- bin/console
|
68
68
|
- bin/setup
|
69
69
|
- evolvable.gemspec
|
70
|
+
- examples/evolvable_string.rb
|
71
|
+
- examples/evolvable_string/char_gene.rb
|
70
72
|
- lib/evolvable.rb
|
71
|
-
- lib/evolvable/
|
72
|
-
- lib/evolvable/
|
73
|
-
- lib/evolvable/
|
74
|
-
- lib/evolvable/
|
73
|
+
- lib/evolvable/error/undefined_method.rb
|
74
|
+
- lib/evolvable/evaluation.rb
|
75
|
+
- lib/evolvable/evolution.rb
|
76
|
+
- lib/evolvable/gene.rb
|
77
|
+
- lib/evolvable/gene_crossover.rb
|
78
|
+
- lib/evolvable/gene_space.rb
|
79
|
+
- lib/evolvable/goal.rb
|
80
|
+
- lib/evolvable/goal/equalize.rb
|
81
|
+
- lib/evolvable/goal/maximize.rb
|
82
|
+
- lib/evolvable/goal/minimize.rb
|
75
83
|
- lib/evolvable/mutation.rb
|
84
|
+
- lib/evolvable/point_crossover.rb
|
76
85
|
- lib/evolvable/population.rb
|
86
|
+
- lib/evolvable/selection.rb
|
87
|
+
- lib/evolvable/uniform_crossover.rb
|
77
88
|
- lib/evolvable/version.rb
|
78
89
|
homepage: https://github.com/mattruzicka/evolvable
|
79
90
|
licenses:
|
@@ -97,7 +108,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
97
108
|
- !ruby/object:Gem::Version
|
98
109
|
version: '0'
|
99
110
|
requirements: []
|
100
|
-
rubygems_version: 3.
|
111
|
+
rubygems_version: 3.1.2
|
101
112
|
signing_key:
|
102
113
|
specification_version: 4
|
103
114
|
summary: Add evolutionary behavior to any Ruby object
|