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 +5 -5
- data/README.md +79 -12
- data/lib/gene_genie/combiner/one_point_combiner.rb +34 -0
- data/lib/gene_genie/combiner/uniform_combiner.rb +21 -0
- data/lib/gene_genie/combiner/weighted_average_combiner.rb +26 -0
- data/lib/gene_genie/gene.rb +16 -10
- data/lib/gene_genie/gene_factory.rb +6 -2
- data/lib/gene_genie/gene_pool.rb +30 -20
- data/lib/gene_genie/genie.rb +10 -2
- data/lib/gene_genie/mutator/nudge_mutator.rb +29 -0
- data/lib/gene_genie/selector/proportional_selector.rb +2 -11
- data/lib/gene_genie/template_evaluator.rb +27 -7
- data/lib/gene_genie/version.rb +1 -1
- metadata +12 -23
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: ea71d46d33da67b5dff6976fbcf4aa6dfd73eecd0fd1c2a0fce2fe70ff02de52
|
4
|
+
data.tar.gz: 18034001ed08324f708e0395d1970aae79880f24949c1e954b453737333b04b5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 19139fef4bb51b4ca40d6f949f716bba442b5fc3490ccc26c0cd555fe0eb0db09ad4bdb8366a1562b63bdc9072a4425bd71989a3af0c4cb7fd22f9ddc247eb78
|
7
|
+
data.tar.gz: 1c7686c788280f0cf40f8abfdb2fee8f267e1b5cbfc087b913042553cde7245fc2f24ce9778c481bee6b19b03e99483add0f1f9d5484be4542f80ab5d318cd47
|
data/README.md
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
[](https://travis-ci.org/MEHColeman/gene_genie)
|
2
1
|
[](http://badge.fury.io/rb/gene_genie)
|
3
2
|
[](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
|
28
|
-
|
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
|
-
|
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
|
-
|
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
|
data/lib/gene_genie/gene.rb
CHANGED
@@ -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
|
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
|
-
|
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,
|
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)
|
data/lib/gene_genie/gene_pool.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
require_relative 'gene_factory'
|
2
|
-
require_relative '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 =
|
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
|
-
|
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
|
97
|
-
@
|
104
|
+
def total_normalised_fitness
|
105
|
+
@total_normalised_fitness ||= normalised_fitness_values.reduce(:+)
|
98
106
|
end
|
99
107
|
|
100
|
-
def
|
101
|
-
@pool
|
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
|
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 =
|
138
|
+
new_gene = first_gene.combine(second_gene)
|
129
139
|
new_gene.mutate(@mutator)
|
130
140
|
end
|
131
141
|
end
|
data/lib/gene_genie/genie.rb
CHANGED
@@ -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
|
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(
|
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
|
-
#
|
17
|
-
#
|
18
|
-
#
|
19
|
-
#
|
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
|
23
|
-
[
|
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
|
data/lib/gene_genie/version.rb
CHANGED
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.
|
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:
|
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:
|
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:
|
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
|
-
|
109
|
-
|
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: []
|