gene_genie 0.1.0 → 0.1.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: ec1b4f9a01e789dfccd2c2ae244d88d024c2a1a1
4
- data.tar.gz: 23d0def3b38095501ea43abf43eef535b5377cb0
2
+ SHA256:
3
+ metadata.gz: ea71d46d33da67b5dff6976fbcf4aa6dfd73eecd0fd1c2a0fce2fe70ff02de52
4
+ data.tar.gz: 18034001ed08324f708e0395d1970aae79880f24949c1e954b453737333b04b5
5
5
  SHA512:
6
- metadata.gz: 910757fa425a73ab14759642d5f28cbe0535390b0f8735439a38976941944c680de0cf7924164b19eb6f7dbea71c52f59394fe980b34c109ec89815165a1bc34
7
- data.tar.gz: 727fe8801c9eaed03834693bc4d38601fb66b3ea9ec9bd39f423dddc7c99d39db7e5c3f0b35966020e6baf302bcf9601ea6aa23a9a9b23009676acd29fee316b
6
+ metadata.gz: 19139fef4bb51b4ca40d6f949f716bba442b5fc3490ccc26c0cd555fe0eb0db09ad4bdb8366a1562b63bdc9072a4425bd71989a3af0c4cb7fd22f9ddc247eb78
7
+ data.tar.gz: 1c7686c788280f0cf40f8abfdb2fee8f267e1b5cbfc087b913042553cde7245fc2f24ce9778c481bee6b19b03e99483add0f1f9d5484be4542f80ab5d318cd47
data/README.md CHANGED
@@ -1,4 +1,3 @@
1
- [![Build Status](https://travis-ci.org/MEHColeman/gene_genie.svg?branch=master)](https://travis-ci.org/MEHColeman/gene_genie)
2
1
  [![Gem Version](https://badge.fury.io/rb/gene_genie.svg)](http://badge.fury.io/rb/gene_genie)
3
2
  [![Code Climate](https://codeclimate.com/github/MEHColeman/gene_genie.png)](https://codeclimate.com/github/MEHColeman/gene_genie)
4
3
 
@@ -24,34 +23,102 @@ Or install it yourself as:
24
23
  $ gem install gene_genie
25
24
 
26
25
  ## Usage
27
- Basic usage is designed to be as simple as possible. You provide two things: a template and an evaluator.
28
- A template is a list of variables along with their possible range of values.
26
+ Basic usage is designed to be as simple as possible. You provide two things: a
27
+ template and an evaluator.
28
+
29
+ A template is an array of hashes, representing a list of variables along with
30
+ their possible range of values that you wish to optimise.
31
+
29
32
  An evaluator implements a fitness method that returns a numeric value.
30
- The genetic algorithm will then search for the set of values that maximises the fitness.
31
33
 
32
- ```ruby
34
+ The genetic algorithm will then search for the set of values that maximises the
35
+ fitness.
36
+
37
+ ~~~ruby
33
38
  require 'gene_genie'
34
39
 
35
- template = {
40
+ template = [{
36
41
  range_of_ints: 1..10,
42
+ more_ints: 3..100
43
+ }]
44
+ ~~~
45
+
37
46
  <!---
38
47
  range_of_floats: 1.0..4.5,
39
- set_of_items: [:apple, :banana, :orange],
48
+ set_of_items: [:apple, :banana, :orange],
40
49
  ordered_set_of_items: [:one, :two, :three],
41
50
  circular_ordered_set: [:early_morning, :morning, :noon, :afternoon,
42
51
  :evening, :midnight]
43
52
  -->
44
- }
45
- ```
46
-
47
- If you use the simple genie interface, the genetic algorithm will come up with a reasonable best-guesses for various algorthm parameters, but you can dive under the covers to give yourself more flexibility.
53
+ Typically, your fitness function will create a model represented by the values
54
+ specified in the template, evaluate the performance of that model, and return a
55
+ fitness score. But, it can be as complicated or simple as you need.
56
+
57
+ A fitness function should return a float or integer
58
+ ~~~ruby
59
+ class Summer
60
+ def fitness(params)
61
+ params.inject(0) {|acc, values| acc + values.each_value.inject(&:+)}
62
+ end
63
+ end
64
+ ~~~
65
+ Then, simply create a Genie, and optimise:
66
+ ~~~ruby
67
+ genie = GeneGenie::Genie.new(template, Summer.new)
68
+ genie.optimise
69
+
70
+ puts genie.best.inspect
71
+ ~~~
72
+ If you want to monitor progress of the optimisation algorithm, you can register
73
+ a listener:
74
+ ~~~ruby
75
+ genie.register_listener(Proc.new do |g|
76
+ puts "Best score: '#{g.best.to_hashes.to_s}', Score: #{g.best_fitness}"
77
+ end)
78
+ ~~~
79
+ See the examples directory for more details.
80
+
81
+ If you use the simple `genie` interface, the genetic algorithm will come up with
82
+ reasonable best-guesses for various algorithm parameters, but you can dive under
83
+ the covers to give yourself more flexibility.
48
84
  * Population size
49
85
  * Gene pools
50
86
  * Initialization
51
87
  * Optimisation Criteria
52
88
 
53
- Custom objects for crossover, gene selection, etc.
54
89
 
90
+ ## Advanced Use
91
+
92
+ If you want more control over your algorithm, you can skip the `Genie`, and use
93
+ `GenePool` directly.
94
+
95
+ This allows you to create objects that are used to control the methods used for
96
+ mutation, crossover, gene selection, gene pool parameters like population size
97
+ and convergence criteria.
98
+ ~~~ruby
99
+ gene_mutator = CustomMutator.new(gm_args)
100
+ gene_factory = CustomGeneFactory.new(gf_args)
101
+
102
+ template_evaluator = CustomTemplateEvaluator.new(template)
103
+ size = template_evaluator.recommended_size
104
+
105
+ GenePool.new(template: template,
106
+ fitness_evaluator: fitness_evaluator,
107
+ gene_factory: gene_factory,
108
+ size: size,
109
+ mutator: gene_mutator)
110
+ ~~~
111
+ The `mutator` operates on a `Gene` to alter it slightly, mimicking natural gene
112
+ mutations.
113
+
114
+ The `gene_factory` creates a population of genes of a given size. Genes are
115
+ typically generated randomly across the parameter space, but this allows you to
116
+ have more control over how genes are distributed across this space.
117
+
118
+ The `template_evaluator` is used to provide other configuration options to the
119
+ GenePool, such as recommended size.
120
+
121
+ ---
55
122
  Note:
56
123
  Due to the non-deterministic nature of the algorithm, some of the tests don't
57
124
  pass every time at the moment! This is a known issue.
@@ -0,0 +1,34 @@
1
+ module GeneGenie
2
+ module Combiner
3
+ # Picks alleles from each Gene randomly
4
+ class OnePointCombiner
5
+ def call(gene_a, gene_b)
6
+ if rand >= 0.5
7
+ first_gene = gene_a
8
+ second_gene = gene_b
9
+ else
10
+ first_gene = gene_b
11
+ second_gene = gene_a
12
+ end
13
+ first_gene_hashes = first_gene.to_hashes
14
+ second_gene_hashes = second_gene.to_hashes
15
+
16
+ total_length = first_gene_hashes.map(&:size).reduce(:+)
17
+ crossover_point = rand(0..(total_length - 1))
18
+
19
+ count = 0
20
+ new_information = first_gene_hashes.map.with_index do |part, index|
21
+ new_hash = {}
22
+ part.each do |k, v|
23
+ new_hash[k] = (count >= crossover_point) ? second_gene_hashes[index][k] : v
24
+ count += 1
25
+ end
26
+ new_hash
27
+ end
28
+ Gene.new(information: new_information,
29
+ fitness_evaluator: first_gene.fitness_evaluator,
30
+ gene_combiner: self)
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,21 @@
1
+ module GeneGenie
2
+ module Combiner
3
+ # Picks alleles from each Gene randomly
4
+ class UniformCombiner
5
+ def call(first_gene, second_gene)
6
+ first_gene_hashes = first_gene.to_hashes
7
+ second_gene_hashes = second_gene.to_hashes
8
+ new_information = first_gene_hashes.map.with_index do |part, index|
9
+ new_hash = {}
10
+ part.each do |k, v|
11
+ new_hash[k] = (rand > 0.5) ? v : second_gene_hashes[index][k]
12
+ end
13
+ new_hash
14
+ end
15
+ Gene.new(information: new_information,
16
+ fitness_evaluator: first_gene.fitness_evaluator,
17
+ gene_combiner: self)
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,26 @@
1
+ module GeneGenie
2
+ module Combiner
3
+ # Creates new allele value by creating a (random) weighted
4
+ # average of the two parent genes. Good for genes that represent numeric
5
+ # scalar values, but not for genes representing discrete info.
6
+ class WeightedAverageCombiner
7
+ def call(first_gene, second_gene)
8
+ first_gene_hashes = first_gene.to_hashes
9
+ second_gene_hashes = second_gene.to_hashes
10
+ new_information = first_gene_hashes.map.with_index do |part, index|
11
+ new_hash = {}
12
+ part.each do |k, v|
13
+ p_first = rand(0.0..100.0)
14
+ p_second = 100 - p_first
15
+ new_hash[k] = (((p_first * v) +
16
+ (p_second * second_gene_hashes[index][k]))/100).round
17
+ end
18
+ new_hash
19
+ end
20
+ Gene.new(information: new_information,
21
+ fitness_evaluator: first_gene.fitness_evaluator,
22
+ gene_combiner: self)
23
+ end
24
+ end
25
+ end
26
+ end
@@ -1,15 +1,20 @@
1
+ require_relative 'combiner/one_point_combiner'
2
+
1
3
  module GeneGenie
2
4
  # A Gene is the basic unit of the genetic algorithm. Genes hold the
3
5
  # information used to evaluate their fitness.
4
6
  # They are combined into new Genes during the optimisation process.
5
7
  # @since 0.0.1
6
8
  class Gene
7
- def initialize(information, fitness_evaluator)
9
+ def initialize(information:,
10
+ fitness_evaluator:,
11
+ gene_combiner: GeneGenie::Combiner::OnePointCombiner.new)
8
12
  fail ArgumentError, 'information must be Array' unless information.kind_of? Array
9
13
  fail ArgumentError, 'information must be Array of Hashes' unless information[0].kind_of? Hash
10
14
 
11
15
  @information = information
12
16
  @fitness_evaluator = fitness_evaluator
17
+ @combiner = gene_combiner
13
18
  end
14
19
 
15
20
  def to_hashes
@@ -20,22 +25,23 @@ module GeneGenie
20
25
  @fitness ||= @fitness_evaluator.fitness(@information)
21
26
  end
22
27
 
28
+ def fitness_evaluator
29
+ @fitness_evaluator
30
+ end
31
+
32
+ def normalised_fitness(minimum)
33
+ @normalised_fitness ||= fitness - minimum
34
+ end
35
+
23
36
  def mutate(mutator)
24
37
  @information = mutator.call @information
25
38
  @fitness = nil
39
+ @normalised_fitness = nil
26
40
  self
27
41
  end
28
42
 
29
43
  def combine(other_gene)
30
- other_gene_hash = other_gene.to_hashes
31
- new_information = @information.map.with_index do |part, index|
32
- new_hash = {}
33
- part.each do |k, v|
34
- new_hash[k] = (rand > 0.5) ? v : other_gene_hash[index][k]
35
- end
36
- new_hash
37
- end
38
- Gene.new(new_information, @fitness_evaluator)
44
+ @combiner.call(self, other_gene)
39
45
  end
40
46
 
41
47
  def <=>(other)
@@ -1,4 +1,5 @@
1
1
  require_relative 'gene'
2
+ require_relative 'combiner/weighted_average_combiner'
2
3
 
3
4
  module GeneGenie
4
5
  # GeneFactory
@@ -7,9 +8,10 @@ module GeneGenie
7
8
  # The default implementation will produce random genes, but other approaches
8
9
  # could be taken.
9
10
  class GeneFactory
10
- def initialize(template, fitness_evaluator)
11
+ def initialize(template, fitness_evaluator, gene_combiner=nil)
11
12
  @template = template
12
13
  @fitness_evaluator = fitness_evaluator
14
+ @combiner = gene_combiner || GeneGenie::Combiner::WeightedAverageCombiner.new
13
15
  end
14
16
 
15
17
  def create(size = 1)
@@ -26,7 +28,9 @@ module GeneGenie
26
28
  gene_array = @template.map do |part|
27
29
  create_hash_from_template_part(part)
28
30
  end
29
- Gene.new(gene_array, @fitness_evaluator)
31
+ Gene.new(information: gene_array,
32
+ fitness_evaluator: @fitness_evaluator,
33
+ gene_combiner: @combiner)
30
34
  end
31
35
 
32
36
  def create_hash_from_template_part(part)
@@ -1,5 +1,5 @@
1
1
  require_relative 'gene_factory'
2
- require_relative 'mutator/simple_gene_mutator'
2
+ require_relative 'mutator/nudge_mutator'
3
3
  require_relative 'mutator/null_mutator'
4
4
  require_relative 'selector/proportional_selector'
5
5
  require_relative 'template_evaluator'
@@ -34,7 +34,7 @@ module GeneGenie
34
34
  unless (template.instance_of? Array) && (template[0].instance_of? Hash)
35
35
  fail ArgumentError, 'template must be an array of hashes of ranges'
36
36
  end
37
- gene_mutator = SimpleGeneMutator.new(template)
37
+ gene_mutator = NudgeMutator.new(template, 0.01)
38
38
  gene_factory = GeneFactory.new(template, fitness_evaluator)
39
39
 
40
40
  template_evaluator = TemplateEvaluator.new(template)
@@ -55,13 +55,21 @@ module GeneGenie
55
55
  end
56
56
 
57
57
  def best
58
- @pool.max_by(&:fitness)
58
+ @best ||= @pool.max_by(&:fitness)
59
59
  end
60
60
 
61
61
  def best_fitness
62
62
  best.fitness
63
63
  end
64
64
 
65
+ def worst
66
+ @worst ||= @pool.min_by(&:fitness)
67
+ end
68
+
69
+ def worst_fitness
70
+ worst.fitness
71
+ end
72
+
65
73
  def best_ever
66
74
  @best_ever ||= best
67
75
  end
@@ -73,7 +81,7 @@ module GeneGenie
73
81
  new_pool << select_genes_combine_and_mutate
74
82
  end
75
83
  @pool = new_pool
76
- check_best_ever
84
+ update_stats
77
85
  @generation += 1
78
86
 
79
87
  @listeners.each { |l| l.call(self) }
@@ -86,28 +94,30 @@ module GeneGenie
86
94
  end
87
95
 
88
96
  def average_fitness
89
- total_fitness / @pool.size
97
+ @average_fitness ||= total_fitness / @pool.size
90
98
  end
91
99
 
92
100
  def total_fitness
93
- fitness_values.reduce(:+)
101
+ @total_fitness ||= fitness_values.reduce(:+)
94
102
  end
95
103
 
96
- def genes
97
- @pool
104
+ def total_normalised_fitness
105
+ @total_normalised_fitness ||= normalised_fitness_values.reduce(:+)
98
106
  end
99
107
 
100
- def worst
101
- @pool.min_by(&:fitness)
102
- end
103
-
104
- def worst_fitness
105
- worst.fitness
108
+ def genes
109
+ @pool
106
110
  end
107
111
 
108
112
  private
109
113
 
110
- def check_best_ever
114
+ def update_stats
115
+ @best = nil
116
+ @worst = nil
117
+ @total_fitness = nil
118
+ @total_normalised_fitness = nil
119
+ @average_fitness = nil
120
+
111
121
  @best_ever = best if best.fitness > best_ever.fitness
112
122
  end
113
123
 
@@ -115,17 +125,17 @@ module GeneGenie
115
125
  @selector.call(self)
116
126
  end
117
127
 
118
- def combine_genes(first, second)
119
- first.combine(second)
120
- end
121
-
122
128
  def fitness_values
123
129
  @pool.map(&:fitness)
124
130
  end
125
131
 
132
+ def normalised_fitness_values
133
+ @pool.map{ |gene| gene.normalised_fitness(worst_fitness) }
134
+ end
135
+
126
136
  def select_genes_combine_and_mutate
127
137
  first_gene, second_gene = select_genes
128
- new_gene = combine_genes(first_gene, second_gene)
138
+ new_gene = first_gene.combine(second_gene)
129
139
  new_gene.mutate(@mutator)
130
140
  end
131
141
  end
@@ -1,4 +1,5 @@
1
1
  require_relative 'gene_pool'
2
+ require_relative 'listener/logging_listener'
2
3
 
3
4
  # Namespace for GeneGenie genetic algorithm optimisation gem
4
5
  # @since 0.0.1
@@ -6,6 +7,9 @@ module GeneGenie
6
7
  # Top level, basic interface for GA optimisation.
7
8
  # Genie will attempt to optimise based on best-guess defaults if none are
8
9
  # provided
10
+ # Genie is basically a wrapper around GenePool that lets you get going as
11
+ # quickly as possible by providing a reasonable set of defaults.
12
+ # For more control and customisation, go straight to using GenePoo
9
13
  # @since 0.0.1
10
14
  class Genie
11
15
  DEFAULT_NO_OF_GENERATIONS = 50
@@ -35,6 +39,10 @@ module GeneGenie
35
39
  end
36
40
  alias_method :optimize, :optimise
37
41
 
42
+ def register_listener(listener)
43
+ @gene_pool.register_listener(listener)
44
+ end
45
+
38
46
  def best
39
47
  @gene_pool.best_ever.to_hashes
40
48
  end
@@ -56,8 +64,8 @@ module GeneGenie
56
64
  DEFAULT_NO_OF_GENERATIONS.times do
57
65
  current_fitness = best_fitness
58
66
  @gene_pool.evolve
59
- break if best_fitness < current_fitness *
60
- (1 + (IMPROVEMENT_THRESHOLD / 100))
67
+ break if (best_fitness < current_fitness *
68
+ (1 + (IMPROVEMENT_THRESHOLD / 100)) && best_fitness > 0)
61
69
  end
62
70
  end
63
71
  end
@@ -0,0 +1,29 @@
1
+ module GeneGenie
2
+ # A NudgeMutator is very similar to a simple nutator, except that it will
3
+ # only change a value by a small amount, rather than to any valid amount.
4
+ # So, an allele with a rather specified in the template of 1..100, with a
5
+ # current value of 50 might change in the range 45..55 instead of 1..100.
6
+ # @since 0.2.0
7
+ class NudgeMutator
8
+ def initialize(template, mutation_rate = 0.04)
9
+ @template = template
10
+ @mutation_rate = mutation_rate * 1
11
+ end
12
+
13
+ def call(genes)
14
+ genes.each_with_index do |hash, index|
15
+ hash.each do |k, v|
16
+ if rand < @mutation_rate
17
+ nudge_max = (@template[index][k].size * 0.05).ceil
18
+ hash[k] = rand(
19
+ [@template[index][k].min, (v - nudge_max).floor].max..
20
+ [@template[index][k].max, (v + nudge_max).ceil].min
21
+ )
22
+ end
23
+ end
24
+ end
25
+ genes
26
+ end
27
+ end
28
+ end
29
+
@@ -9,21 +9,12 @@ module GeneGenie
9
9
  private
10
10
 
11
11
  def pick_one(pool)
12
- proportional_index = rand(total_normalised_fitness(pool))
12
+ proportional_index = rand(pool.total_normalised_fitness)
13
13
  total = 0
14
14
  pool.genes.each_with_index do |gene, index|
15
- total += normalised_fitness(gene,pool)
15
+ total += gene.normalised_fitness(pool.worst_fitness)
16
16
  return gene if total >= proportional_index || index == (pool.size - 1)
17
17
  end
18
18
  end
19
-
20
- def total_normalised_fitness(pool)
21
- pool.genes.map { |gene| normalised_fitness(gene,pool) }.reduce(:+)
22
- end
23
-
24
- def normalised_fitness(gene,pool)
25
- gene.fitness -
26
- pool.worst_fitness + 1
27
- end
28
19
  end
29
20
  end
@@ -1,6 +1,7 @@
1
1
  module GeneGenie
2
2
  # A Template Evaluator provides certain analysis and useful information
3
- # about templates
3
+ # about templates.
4
+ # A template is always treated internally as an Array of Hashes.
4
5
  # @since 0.0.2
5
6
  class TemplateEvaluator
6
7
  def initialize(template)
@@ -13,15 +14,34 @@ module GeneGenie
13
14
  }.reduce(:*)
14
15
  end
15
16
 
16
- # returns a minimum of 10 unless the total number of permutations
17
- # is below that
18
- # otherwise, returns 1/1000th of the number of permutations up to a
19
- # maximum of 1000
17
+ # Suggests a recommended GenePool size.
18
+ # returns a minimum of 6 unless the total number of permutations
19
+ # is below that.
20
+ # Otherwise, returns 1/1000th of the number of permutations up to a
21
+ # maximum of 20000
20
22
  def recommended_size
21
23
  [
22
- [(permutations / 100_000), 5000].min,
23
- [10, permutations].min,
24
+ [((Math.log(permutations))**2).ceil, 20000].min,
25
+ [6, permutations].min,
26
+ 3
24
27
  ].max
25
28
  end
29
+
30
+ # Verifies that the given hash conforms to the constraints specified in the
31
+ # hash template
32
+ def hash_valid?(hash_under_test)
33
+ begin
34
+ @template.each_with_index do |h, index|
35
+ h.each do |k, v|
36
+ return false unless hash_under_test[index][k]
37
+ return false unless hash_under_test[index][k] >= v.min
38
+ return false unless hash_under_test[index][k] <= v.max
39
+ end
40
+ end
41
+ rescue
42
+ return false
43
+ end
44
+ return true
45
+ end
26
46
  end
27
47
  end
@@ -1,3 +1,3 @@
1
1
  module GeneGenie
2
- VERSION = '0.1.0'
2
+ VERSION = '0.1.1'
3
3
  end
metadata CHANGED
@@ -1,31 +1,17 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gene_genie
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mark Coleman
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-08-01 00:00:00.000000000 Z
11
+ date: 2022-10-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
15
- requirement: !ruby/object:Gem::Requirement
16
- requirements:
17
- - - "~>"
18
- - !ruby/object:Gem::Version
19
- version: '1.6'
20
- type: :development
21
- prerelease: false
22
- version_requirements: !ruby/object:Gem::Requirement
23
- requirements:
24
- - - "~>"
25
- - !ruby/object:Gem::Version
26
- version: '1.6'
27
- - !ruby/object:Gem::Dependency
28
- name: rake
29
15
  requirement: !ruby/object:Gem::Requirement
30
16
  requirements:
31
17
  - - ">="
@@ -39,7 +25,7 @@ dependencies:
39
25
  - !ruby/object:Gem::Version
40
26
  version: '0'
41
27
  - !ruby/object:Gem::Dependency
42
- name: minitest
28
+ name: rake
43
29
  requirement: !ruby/object:Gem::Requirement
44
30
  requirements:
45
31
  - - ">="
@@ -53,7 +39,7 @@ dependencies:
53
39
  - !ruby/object:Gem::Version
54
40
  version: '0'
55
41
  - !ruby/object:Gem::Dependency
56
- name: minitest-spec
42
+ name: rspec
57
43
  requirement: !ruby/object:Gem::Requirement
58
44
  requirements:
59
45
  - - ">="
@@ -75,11 +61,15 @@ extra_rdoc_files: []
75
61
  files:
76
62
  - README.md
77
63
  - lib/gene_genie.rb
64
+ - lib/gene_genie/combiner/one_point_combiner.rb
65
+ - lib/gene_genie/combiner/uniform_combiner.rb
66
+ - lib/gene_genie/combiner/weighted_average_combiner.rb
78
67
  - lib/gene_genie/gene.rb
79
68
  - lib/gene_genie/gene_factory.rb
80
69
  - lib/gene_genie/gene_pool.rb
81
70
  - lib/gene_genie/genie.rb
82
71
  - lib/gene_genie/listener/logging_listener.rb
72
+ - lib/gene_genie/mutator/nudge_mutator.rb
83
73
  - lib/gene_genie/mutator/null_mutator.rb
84
74
  - lib/gene_genie/mutator/simple_gene_mutator.rb
85
75
  - lib/gene_genie/selector/coin_flip_selector.rb
@@ -90,7 +80,7 @@ homepage: https://github.com/MEHColeman/gene_genie
90
80
  licenses:
91
81
  - MIT
92
82
  metadata: {}
93
- post_install_message:
83
+ post_install_message:
94
84
  rdoc_options: []
95
85
  require_paths:
96
86
  - lib
@@ -105,9 +95,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
105
95
  - !ruby/object:Gem::Version
106
96
  version: '0'
107
97
  requirements: []
108
- rubyforge_project:
109
- rubygems_version: 2.5.1
110
- signing_key:
98
+ rubygems_version: 3.1.6
99
+ signing_key:
111
100
  specification_version: 4
112
101
  summary: Genetic algorithm optimisation gem
113
102
  test_files: []