evolvable 0.1.3 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -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: 0.03)
6
- @rate = rate
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 :rate
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!(objects)
17
- @evolvable_class = objects.first.class
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
- mutant_genes = generate_mutant_genes(mutations_count)
22
- object_mutations_count = mutations_count / objects.count
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
- end
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 find_mutations_count(objects)
46
- return 0 if @rate.zero?
26
+ def mutate_instance(instance)
27
+ genes_count = instance.genes.count
28
+ return if genes_count.zero?
47
29
 
48
- count = (objects.count * evolvable_genes_count * @rate)
49
- return count.to_i if count >= 1
30
+ return mutate_gene(instance, rand(genes_count)) if rate.zero?
50
31
 
51
- rand <= count ? 1 : 0
32
+ genes_count.times { |index| mutate_gene(instance, index) if rand <= rate }
52
33
  end
53
34
 
54
- def generate_mutant_genes(mutations_count)
55
- gene_pool_size = evolvable_gene_pool_size
56
- mutant_genes = []
57
- while mutant_genes.count < mutations_count
58
- genes_count = [gene_pool_size, mutations_count - mutant_genes.count].min
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
@@ -4,115 +4,94 @@ module Evolvable
4
4
  class Population
5
5
  extend Forwardable
6
6
 
7
- def initialize(evolvable_class:,
8
- size: 20,
9
- selection_count: 2,
10
- crossover: Crossover.new,
11
- mutation: Mutation.new,
12
- generation_count: 0,
13
- log_progress: false,
14
- objects: [])
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
- @selection_count = selection_count
18
- @crossover = crossover
19
- @mutation = mutation
20
- @generation_count = generation_count
21
- @log_progress = log_progress
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 :evolvable_class,
27
+ attr_accessor :id,
28
+ :evolvable_class,
29
+ :name,
26
30
  :size,
27
- :selection_count,
28
- :crossover,
29
- :mutation,
30
- :generation_count,
31
- :log_progress,
32
- :objects
33
-
34
- def_delegators :@evolvable_class,
35
- :evolvable_evaluate!,
36
- :evolvable_initialize,
37
- :evolvable_random_genes,
38
- :evolvable_before_evolution,
39
- :evolvable_after_select,
40
- :evolvable_after_evolution
41
-
42
- def evolve!(generations_count: 1, fitness_goal: nil)
43
- @fitness_goal = fitness_goal
44
- generations_count.times do
45
- @generation_count += 1
46
- evolvable_before_evolution(self)
47
- evaluate_objects!
48
- log_evolvable_progress if log_progress
49
- break if fitness_goal_met?
50
-
51
- select_objects!
52
- evolvable_after_select(self)
53
- crossover_objects!
54
- mutate_objects!
55
- evolvable_after_evolution(self)
56
- end
57
- end
58
-
59
- def strongest_object
60
- objects.max_by(&:fitness)
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 log_evolvable_progress
73
- @objects.last.evolvable_progress
68
+ def best_instance
69
+ evaluation.best_instance(self)
74
70
  end
75
71
 
76
- def fitness_goal_met?
77
- @fitness_goal && @objects.last.fitness >= @fitness_goal
72
+ def met_goal?
73
+ evaluation.met_goal?(self)
78
74
  end
79
75
 
80
- def select_objects!
81
- @objects.slice!(0..-1 - @selection_count)
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
- def crossover_objects!
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 inspect
97
- "#<#{self.class.name} #{as_json} >"
98
- end
84
+ def initialize_gene_space(gene_space)
85
+ return GeneSpace.build(gene_space) if gene_space
99
86
 
100
- def as_json
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
- private
110
-
111
- def assign_objects(objects)
112
- @objects = objects || []
113
- (@size - objects.count).times do |n|
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Evolvable
4
- VERSION = '0.1.3'
4
+ VERSION = '1.0.0'
5
5
  end
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.1.3
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: 2019-03-11 00:00:00.000000000 Z
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/crossover.rb
72
- - lib/evolvable/errors/not_implemented.rb
73
- - lib/evolvable/helper_methods.rb
74
- - lib/evolvable/hooks.rb
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.0.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