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.
@@ -3,48 +3,12 @@
3
3
  require 'bundler/setup'
4
4
  require 'evolvable'
5
5
 
6
- # You can add fixtures and/or initialization code here to make experimenting
7
- # with your gem easier. You can also use a different console, if you like.
8
-
9
- # (If you use this, don't forget to add pry to your Gemfile!)
10
- # require "pry"
11
- # Pry.start
12
-
13
-
14
- class BandMember
15
- include Evolvable
16
-
17
- SYNTHS = [:synth_1, :synth_2]
18
-
19
- # SYNTH_OPTIONS = { synth_1: [[:cutoff, (1..100).to_a],
20
- # [:reverb: (1..5).to_a]],
21
- # synth_2: []}
22
-
23
- SAMPLES = [:sample_1, :sample_2]
24
-
25
- # SAMPLE_OPTIONS = { sample_1: [[:cutoff, (1..100).to_a],
26
- # [:reverb: (1..5).to_a]],
27
- # sample_2: []}
28
-
29
- def self.evolvable_gene_pool
30
-
31
- end
32
-
33
- def base.evolvable_random_genes(count = nil)
34
- gene_pool = evolvable_gene_pool_cache
35
- count ||= evolvable_genes_count
36
- gene_pool = gene_pool.sample(count) if count < gene_pool.size
37
- genes = {}
38
- gene_pool.each { |name, potentials| genes[name] = potentials.sample }
39
- genes
40
- end
41
-
42
- def fitness
43
- 0
44
- end
45
- end
46
-
6
+ require './examples/evolvable_string'
47
7
 
8
+ puts "To get started, try this:\n\n" \
9
+ " population = EvolvableString.new_population\n" \
10
+ " population.mutation.probability = 0.8\n" \
11
+ " population.evolve(goal_value: EvolvableString::TARGET_STRING.length)\n\n"
48
12
 
49
13
  require 'irb'
50
14
  IRB.start(__FILE__)
@@ -29,4 +29,3 @@ Gem::Specification.new do |spec|
29
29
  spec.add_development_dependency 'byebug', '~> 11.0'
30
30
  spec.add_development_dependency 'rubocop', '~> 0.64.0'
31
31
  end
32
-
@@ -0,0 +1,32 @@
1
+ require './examples/evolvable_string/char_gene'
2
+
3
+ class EvolvableString
4
+ include Evolvable
5
+
6
+ TARGET_STRING = 'supercalifragilisticexpialidocious'
7
+
8
+ def self.gene_space
9
+ { char_genes: { type: 'CharGene', count: TARGET_STRING.length } }
10
+ end
11
+
12
+ def self.before_evolution(population)
13
+ best_instance = population.best_instance
14
+ puts "#{best_instance} | #{best_instance.value} matches | Generation #{population.evolutions_count}"
15
+ end
16
+
17
+ def to_s
18
+ find_genes(:char_genes).join
19
+ end
20
+
21
+ def value
22
+ @value ||= compute_value
23
+ end
24
+
25
+ def compute_value
26
+ value = 0
27
+ find_genes(:char_genes).each_with_index do |gene, index|
28
+ value += 1 if gene.to_s == TARGET_STRING[index]
29
+ end
30
+ value
31
+ end
32
+ end
@@ -0,0 +1,9 @@
1
+ class CharGene
2
+ include Evolvable::Gene
3
+
4
+ CHARS = ('a'..'z').to_a
5
+
6
+ def to_s
7
+ @to_s ||= CHARS.sample
8
+ end
9
+ end
@@ -1,86 +1,73 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'forwardable'
4
- require 'logger'
5
-
6
4
  require 'evolvable/version'
7
- require 'evolvable/population'
8
- require 'evolvable/crossover'
5
+ require 'evolvable/error/undefined_method'
6
+ require 'evolvable/gene'
7
+ require 'evolvable/gene_space'
8
+ require 'evolvable/goal'
9
+ require 'evolvable/goal/equalize'
10
+ require 'evolvable/goal/maximize'
11
+ require 'evolvable/goal/minimize'
12
+ require 'evolvable/evaluation'
13
+ require 'evolvable/evolution'
14
+ require 'evolvable/selection'
15
+ require 'evolvable/gene_crossover'
16
+ require 'evolvable/point_crossover'
17
+ require 'evolvable/uniform_crossover'
9
18
  require 'evolvable/mutation'
10
- require 'evolvable/helper_methods'
11
- require 'evolvable/hooks'
12
- require 'evolvable/errors/not_implemented'
19
+ require 'evolvable/population'
13
20
 
14
21
  module Evolvable
15
- extend HelperMethods
16
-
17
22
  def self.included(base)
18
- base.extend Hooks
19
-
20
- def base.evolvable_gene_pool
21
- raise Errors::NotImplemented, __method__
22
- end
23
-
24
- def base.evolvable_genes_count
25
- evolvable_gene_pool_size
26
- end
27
-
28
- def base.evolvable_evaluate!(_objects); end
29
-
30
- def base.evolvable_population_attrs
31
- {}
23
+ def base.new_population(keyword_args = {})
24
+ keyword_args[:evolvable_class] = self
25
+ Population.new(**keyword_args)
32
26
  end
33
27
 
34
- def base.evolvable_population(args = {})
35
- clear_evolvable_gene_pool_caches
36
- args = evolvable_population_attrs.merge!(args)
37
- args[:evolvable_class] = self
38
- Population.new(args)
39
- end
40
-
41
- def base.evolvable_initialize(genes, population, _object_index)
42
- evolvable = new
43
- evolvable.genes = genes
28
+ def base.new_instance(population: nil, genes: [], population_index: nil)
29
+ evolvable = initialize_instance
44
30
  evolvable.population = population
31
+ evolvable.genes = genes
32
+ evolvable.population_index = population_index
33
+ evolvable.initialize_instance
45
34
  evolvable
46
35
  end
47
36
 
48
- def base.evolvable_gene_pool_cache
49
- @evolvable_gene_pool_cache ||= evolvable_gene_pool
37
+ def base.initialize_instance
38
+ new
50
39
  end
51
40
 
52
- def base.evolvable_gene_pool_size
53
- @evolvable_gene_pool_size ||= evolvable_gene_pool_cache.size
41
+ def base.new_gene_space
42
+ GeneSpace.build(gene_space)
54
43
  end
55
44
 
56
- def base.clear_evolvable_gene_pool_caches
57
- @evolvable_gene_pool_cache = nil
58
- @evolvable_gene_pool_size = nil
45
+ def base.gene_space
46
+ {}
59
47
  end
60
48
 
61
- def base.evolvable_random_genes(count = nil)
62
- gene_pool = evolvable_gene_pool_cache
63
- count ||= evolvable_genes_count
64
- gene_pool = gene_pool.sample(count) if count < gene_pool.size
65
- genes = {}
66
- gene_pool.each { |name, potentials| genes[name] = potentials.sample }
67
- genes
68
- end
69
- end
49
+ def base.before_evaluation(population); end
50
+
51
+ def base.before_evolution(population); end
70
52
 
71
- def self.logger
72
- @logger ||= Logger.new(STDOUT)
53
+ def base.after_evolution(population); end
73
54
  end
74
55
 
75
- attr_accessor :genes,
76
- :population
56
+ def initialize_instance; end
57
+
58
+ attr_accessor :population,
59
+ :genes,
60
+ :population_index
61
+
62
+ def value
63
+ raise Errors::UndefinedMethod, "#{self.class.name}##{__method__}"
64
+ end
77
65
 
78
- def fitness
79
- raise Errors::NotImplemented, __method__
66
+ def find_gene(key)
67
+ @genes.detect { |g| g.key == key }
80
68
  end
81
69
 
82
- def evolvable_progress(info = nil)
83
- info ||= "Generation: #{population.generation_count} | Fitness: #{fitness}"
84
- Evolvable.logger.info(info)
70
+ def find_genes(key)
71
+ @genes.select { |g| g.key == key }
85
72
  end
86
73
  end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Evolvable
4
+ module Errors
5
+ class UndefinedMethod < StandardError; end
6
+ end
7
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Evolvable
4
+ class Evaluation
5
+ GOALS = { maximize: Evolvable::Goal::Maximize.new,
6
+ minimize: Evolvable::Goal::Minimize.new,
7
+ equalize: Evolvable::Goal::Equalize.new }.freeze
8
+
9
+ def initialize(goal = :maximize)
10
+ @goal = normalize_goal(goal)
11
+ end
12
+
13
+ attr_accessor :goal
14
+
15
+ def call(population)
16
+ population.instances.sort_by! { |instance| goal.evaluate(instance) }
17
+ end
18
+
19
+ def best_instance(population)
20
+ population.instances.max_by { |instance| goal.evaluate(instance) }
21
+ end
22
+
23
+ def met_goal?(population)
24
+ goal.met?(population.instances.last)
25
+ end
26
+
27
+ private
28
+
29
+ def normalize_goal(goal_arg)
30
+ case goal_arg
31
+ when Symbol
32
+ goal_from_symbol(goal_arg)
33
+ when Hash
34
+ goal_from_hash(goal_arg)
35
+ else
36
+ goal
37
+ end
38
+ end
39
+
40
+ def goal_from_symbol(goal_arg)
41
+ GOALS[goal_arg]
42
+ end
43
+
44
+ def goal_from_hash(goal_arg)
45
+ goal_type, value = goal_arg.first
46
+ goal = GOALS[goal_type]
47
+ goal.value = value
48
+ goal
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Evolvable
4
+ class Evolution
5
+ extend Forwardable
6
+
7
+ def initialize(selection: Selection.new,
8
+ crossover: GeneCrossover.new,
9
+ mutation: Mutation.new)
10
+ @selection = selection
11
+ @crossover = crossover
12
+ @mutation = mutation
13
+ end
14
+
15
+ attr_accessor :selection,
16
+ :crossover,
17
+ :mutation
18
+
19
+ def call(population)
20
+ @selection.call(population)
21
+ @crossover.call(population)
22
+ @mutation.call(population)
23
+ population
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Evolvable
4
+ module Gene
5
+ def self.included(base)
6
+ def base.crossover(gene_a, gene_b)
7
+ [gene_a, gene_b].sample
8
+ end
9
+ end
10
+
11
+ attr_accessor :instance, :key
12
+ end
13
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Evolvable
4
+ class GeneCrossover
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 = crossover_genes(genes_1, genes_2)
18
+ population.new_instance(genes: genes, population_index: index)
19
+ end
20
+ end
21
+
22
+ def crossover_genes(genes_1, genes_2)
23
+ genes_1.zip(genes_2).map! do |gene_a, gene_b|
24
+ gene_a.class.crossover(gene_a, gene_b)
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Evolvable
4
+ class GeneSpace
5
+ def self.build(config)
6
+ return config if config.respond_to?(:new_genes)
7
+
8
+ new(config: config)
9
+ end
10
+
11
+ def initialize(config: {})
12
+ @config = normalize_config(config)
13
+ end
14
+
15
+ attr_reader :config
16
+
17
+ def new_genes
18
+ genes = []
19
+ config.each do |gene_key, gene_config|
20
+ (gene_config[:count] || 1).times do
21
+ gene = gene_config[:class].new
22
+ gene.key = gene_key
23
+ genes << gene
24
+ end
25
+ end
26
+ genes
27
+ end
28
+
29
+ private
30
+
31
+ def normalize_config(config)
32
+ config.each do |_gene_key, gene_config|
33
+ gene_config[:class] = Kernel.const_get(gene_config[:type]) if gene_config[:type]
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Evolvable
4
+ module Goal
5
+ def initialize(value: nil)
6
+ @value = value if value
7
+ end
8
+
9
+ attr_accessor :value
10
+
11
+ def evaluate(_instance)
12
+ raise Errors::UndefinedMethod, "#{self.class.name}##{__method__}"
13
+ end
14
+
15
+ def met?(_instance)
16
+ raise Errors::UndefinedMethod, "#{self.class.name}##{__method__}"
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Evolvable::Goal
4
+ class Equalize
5
+ include Evolvable::Goal
6
+
7
+ def value
8
+ @value ||= 0
9
+ end
10
+
11
+ def evaluate(instance)
12
+ -(instance.value - value).abs
13
+ end
14
+
15
+ def met?(instance)
16
+ instance.value == value
17
+ end
18
+ end
19
+ end