gene_genie 0.1.0 → 0.1.1

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