evolvable 0.1.3 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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