gene_genie 0.0.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 +7 -0
- data/README.md +59 -0
- data/lib/gene_genie/gene.rb +38 -0
- data/lib/gene_genie/gene_factory.rb +36 -0
- data/lib/gene_genie/gene_pool.rb +81 -0
- data/lib/gene_genie/genie.rb +67 -0
- data/lib/gene_genie/mutator/null_mutator.rb +12 -0
- data/lib/gene_genie/mutator/simple_gene_mutator.rb +22 -0
- data/lib/gene_genie/version.rb +3 -0
- data/lib/gene_genie.rb +1 -0
- metadata +110 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 1f634d03e4f8bce6759898a2048719471aff1e0b
|
4
|
+
data.tar.gz: ff4ebfd191e17b182149935e1c544b51ceb0f46f
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: a918e63beb5265d671b2e4b46b28358fb7e57727c590f9b37300eae2b0b57fc7d19abd30b90d0d0a5fea815c6a98bbf1ddb2e65e0b0a2f1e9915fbe88467fa21
|
7
|
+
data.tar.gz: 86719523c06e1fd0e35c242385a833c236745d3fe43973494d15cdab567bb43467f34bfda9211f5db1d6f1e00f820109c18f66139115e1252875357e0b2c5346
|
data/README.md
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
[](https://travis-ci.org/MEHColeman/gene_genie)
|
2
|
+
[](http://badge.fury.io/rb/gene_genie)
|
3
|
+
[](https://codeclimate.com/github/MEHColeman/gene_genie)
|
4
|
+
|
5
|
+
# Gene Genie
|
6
|
+
|
7
|
+
Hey, I wrote a genetic algorithm gem. Goals:
|
8
|
+
* Have fun
|
9
|
+
* Be easy and intuitive to use
|
10
|
+
* Be open to extension and experimentation
|
11
|
+
|
12
|
+
## Installation
|
13
|
+
|
14
|
+
Add this line to your application's Gemfile:
|
15
|
+
|
16
|
+
gem 'gene_genie'
|
17
|
+
|
18
|
+
And then execute:
|
19
|
+
|
20
|
+
$ bundle
|
21
|
+
|
22
|
+
Or install it yourself as:
|
23
|
+
|
24
|
+
$ gem install gene_genie
|
25
|
+
|
26
|
+
## Usage
|
27
|
+
Basic usage is designed to be as simple as possible. You provide two things: an exemplar and an evaluator.
|
28
|
+
An exemplar is a list of variables along with their possible range of values.
|
29
|
+
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
|
+
|
32
|
+
```ruby
|
33
|
+
require 'gene_genie'
|
34
|
+
|
35
|
+
exemplar = {
|
36
|
+
range_of_ints: 1..10,
|
37
|
+
range_of_floats: 1.0..4.5,
|
38
|
+
set_of_items: [:apple, :banana, :orange],
|
39
|
+
ordered_set_of_items: [:one, :two, :three],
|
40
|
+
circular_ordered_set: [:early_morning, :morning, :noon, :afternoon,
|
41
|
+
:evening, :midnight]
|
42
|
+
}
|
43
|
+
```
|
44
|
+
|
45
|
+
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.
|
46
|
+
* Population size
|
47
|
+
* Gene pools
|
48
|
+
* Initialisation
|
49
|
+
* Optimisation Criteria
|
50
|
+
|
51
|
+
Custom objects for crossover, gene selection, etc.
|
52
|
+
|
53
|
+
## Contributing
|
54
|
+
|
55
|
+
1. Fork it ( https://github.com/MEHColeman/gene_genie/fork )
|
56
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
57
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
58
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
59
|
+
5. Create a new Pull Request
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module GeneGenie
|
2
|
+
# A Gene is the basic unit of the genetic algorithm. Genes hold the
|
3
|
+
# information used to evaluate their fitness.
|
4
|
+
# They are combined into new Genes during the optimisation process.
|
5
|
+
# @since 0.0.1
|
6
|
+
class Gene
|
7
|
+
def initialize(information, fitness_evaluator)
|
8
|
+
@information = information
|
9
|
+
@fitness_evaluator = fitness_evaluator
|
10
|
+
end
|
11
|
+
|
12
|
+
def to_hash
|
13
|
+
@information
|
14
|
+
end
|
15
|
+
|
16
|
+
def fitness
|
17
|
+
@fitness ||= @fitness_evaluator.fitness(@information)
|
18
|
+
end
|
19
|
+
|
20
|
+
def mutate(mutator)
|
21
|
+
@information = mutator.call @information
|
22
|
+
self
|
23
|
+
end
|
24
|
+
|
25
|
+
def combine(other_gene)
|
26
|
+
other_gene_hash = other_gene.to_hash
|
27
|
+
new_hash = {}
|
28
|
+
@information.each do | k, v |
|
29
|
+
new_hash[k] = (rand > 0.5) ? @information[k] : other_gene_hash[k]
|
30
|
+
end
|
31
|
+
Gene.new(new_hash, @fitness_evaluator)
|
32
|
+
end
|
33
|
+
|
34
|
+
def <=>(gene)
|
35
|
+
fitness <=> gene.fitness
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require_relative 'gene'
|
2
|
+
|
3
|
+
module GeneGenie
|
4
|
+
# GeneFactory
|
5
|
+
# This is a helper class that will create a specified number of genes, given
|
6
|
+
# a template.
|
7
|
+
# The default implementation will produce random genes, but other approaches
|
8
|
+
# could be taken.
|
9
|
+
class GeneFactory
|
10
|
+
def initialize(template, fitness_evaluator)
|
11
|
+
@template = template
|
12
|
+
@fitness_evaluator = fitness_evaluator
|
13
|
+
end
|
14
|
+
|
15
|
+
def create(size = 1)
|
16
|
+
genes = []
|
17
|
+
size.times do
|
18
|
+
hash = create_hash_from_template
|
19
|
+
genes << Gene.new(hash, @fitness_evaluator)
|
20
|
+
end
|
21
|
+
|
22
|
+
genes
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def create_hash_from_template
|
28
|
+
new_hash = {}
|
29
|
+
@template.each do |k, v|
|
30
|
+
new_hash[k] = rand(v)
|
31
|
+
end
|
32
|
+
|
33
|
+
new_hash
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
require_relative 'gene_factory'
|
2
|
+
require_relative 'mutator/simple_gene_mutator'
|
3
|
+
require_relative 'mutator/null_mutator'
|
4
|
+
|
5
|
+
module GeneGenie
|
6
|
+
class GenePool
|
7
|
+
def initialize(template, fitness_evaluator, gene_factory,
|
8
|
+
mutator = NullMutator.new)
|
9
|
+
unless template.instance_of? Hash
|
10
|
+
fail ArgumentError, 'template must be a hash of ranges'
|
11
|
+
end
|
12
|
+
unless fitness_evaluator.respond_to?(:fitness)
|
13
|
+
fail ArgumentError, 'fitness_evaluator must respond to fitness'
|
14
|
+
end
|
15
|
+
|
16
|
+
@template = template
|
17
|
+
@fitness_evaluator = fitness_evaluator
|
18
|
+
@mutator = mutator
|
19
|
+
|
20
|
+
#size = template_evaluator.recommended_size
|
21
|
+
size ||= 10
|
22
|
+
@pool = gene_factory.create(size)
|
23
|
+
end
|
24
|
+
|
25
|
+
# build a GenePool with a reasonable set of defaults.
|
26
|
+
# You only need to specily the minimum no. of parameters
|
27
|
+
def self.build(template, fitness_evaluator)
|
28
|
+
gene_mutator = SimpleGeneMutator.new(template)
|
29
|
+
gene_factory = GeneFactory.new(template, fitness_evaluator)
|
30
|
+
GenePool.new(template, fitness_evaluator, gene_factory,
|
31
|
+
gene_mutator)
|
32
|
+
end
|
33
|
+
|
34
|
+
def size
|
35
|
+
@pool.size
|
36
|
+
end
|
37
|
+
|
38
|
+
def best
|
39
|
+
@pool.max_by { |gene| gene.fitness }
|
40
|
+
end
|
41
|
+
|
42
|
+
def evolve
|
43
|
+
old_best_fitness = best.fitness
|
44
|
+
new_pool = []
|
45
|
+
size.times do
|
46
|
+
first_gene, second_gene = select_genes
|
47
|
+
new_gene = combine_genes(first_gene, second_gene)
|
48
|
+
new_pool << new_gene.mutate(@mutator)
|
49
|
+
end
|
50
|
+
@pool = new_pool
|
51
|
+
best.fitness > old_best_fitness
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
# a very simple selection - pick by sorted order
|
56
|
+
# pick two different genes
|
57
|
+
def select_genes
|
58
|
+
selectees = @pool.sort.reverse
|
59
|
+
first, second = nil, nil
|
60
|
+
probability = [(( 1.0/size ) * 3), 0.8].min
|
61
|
+
while !first || !second do
|
62
|
+
selectees.each do |s|
|
63
|
+
if rand < probability
|
64
|
+
selectees.delete(s)
|
65
|
+
if !first
|
66
|
+
first = s
|
67
|
+
break
|
68
|
+
else
|
69
|
+
second = s
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
[first, second]
|
75
|
+
end
|
76
|
+
|
77
|
+
def combine_genes(first, second)
|
78
|
+
first.combine(second)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require_relative 'gene_pool'
|
2
|
+
|
3
|
+
# Namespace for GeneGenie genetic algorithm optimisation gem
|
4
|
+
# @since 0.0.1
|
5
|
+
module GeneGenie
|
6
|
+
|
7
|
+
# Top level, basic interface for GA optimisation.
|
8
|
+
# Genie will attempt to optimise based on best-guess defaults if none are
|
9
|
+
# provided
|
10
|
+
# @since 0.0.1
|
11
|
+
class Genie
|
12
|
+
|
13
|
+
DEFAULT_NO_OF_GENERATIONS = 50
|
14
|
+
IMPROVEMENT_THRESHOLD = 0.1 # %
|
15
|
+
|
16
|
+
def initialize(template, fitness_evaluator)
|
17
|
+
@template = template
|
18
|
+
@fitness_evaluator = fitness_evaluator
|
19
|
+
@gene_pool = GenePool.build(template, fitness_evaluator)
|
20
|
+
end
|
21
|
+
|
22
|
+
# Optimise the genes until the convergence criteria are met.
|
23
|
+
# A reasonable set of defaults for criteria will be applied.
|
24
|
+
# @param [Integer] number_of_generations
|
25
|
+
def optimise(number_of_generations = 0)
|
26
|
+
previous_best = best_fitness
|
27
|
+
|
28
|
+
# optimise
|
29
|
+
if number_of_generations > 0
|
30
|
+
evolve_n_times(number_of_generations)
|
31
|
+
else
|
32
|
+
optimise_by_strategy
|
33
|
+
end
|
34
|
+
|
35
|
+
@best_fitness = @fitness_evaluator.fitness(best)
|
36
|
+
|
37
|
+
@best_fitness > previous_best
|
38
|
+
end
|
39
|
+
alias_method :optimize, :optimise
|
40
|
+
|
41
|
+
def best
|
42
|
+
@gene_pool.best.to_hash
|
43
|
+
end
|
44
|
+
|
45
|
+
def best_fitness
|
46
|
+
@gene_pool.best.fitness
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
def evolve_n_times(n)
|
51
|
+
n.times { @gene_pool.evolve }
|
52
|
+
end
|
53
|
+
|
54
|
+
def optimise_by_strategy
|
55
|
+
DEFAULT_NO_OF_GENERATIONS.times do
|
56
|
+
current_fitness = best_fitness
|
57
|
+
@gene_pool.evolve
|
58
|
+
end
|
59
|
+
DEFAULT_NO_OF_GENERATIONS.times do
|
60
|
+
current_fitness = best_fitness
|
61
|
+
@gene_pool.evolve
|
62
|
+
break if best_fitness < current_fitness *
|
63
|
+
(1 + (IMPROVEMENT_THRESHOLD / 100 ))
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module GeneGenie
|
2
|
+
# A SimpleGeneMutator loops through each member of a hash, and has a 1%
|
3
|
+
# chance of swapping the value for another valid value (based on the
|
4
|
+
# template)
|
5
|
+
# @since 0.0.1
|
6
|
+
class SimpleGeneMutator
|
7
|
+
def initialize(template, mutation_rate = 0.01)
|
8
|
+
@template = template
|
9
|
+
@mutation_rate = mutation_rate
|
10
|
+
end
|
11
|
+
|
12
|
+
def call(hash)
|
13
|
+
hash.each do |k, v|
|
14
|
+
if rand < @mutation_rate
|
15
|
+
hash[k] = rand(@template[k])
|
16
|
+
end
|
17
|
+
end
|
18
|
+
hash
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
data/lib/gene_genie.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require_relative 'gene_genie/genie'
|
metadata
ADDED
@@ -0,0 +1,110 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: gene_genie
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Mark Coleman
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-08-28 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
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
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: minitest
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: minitest-spec
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
description: JUST A PROTOTYPE WORK IN PROGRESS! Optimise anything that responds to
|
70
|
+
'fitness' and takes a hash
|
71
|
+
email:
|
72
|
+
- m@rkcoleman.co.uk
|
73
|
+
executables: []
|
74
|
+
extensions: []
|
75
|
+
extra_rdoc_files: []
|
76
|
+
files:
|
77
|
+
- README.md
|
78
|
+
- lib/gene_genie.rb
|
79
|
+
- lib/gene_genie/gene.rb
|
80
|
+
- lib/gene_genie/gene_factory.rb
|
81
|
+
- lib/gene_genie/gene_pool.rb
|
82
|
+
- lib/gene_genie/genie.rb
|
83
|
+
- lib/gene_genie/mutator/null_mutator.rb
|
84
|
+
- lib/gene_genie/mutator/simple_gene_mutator.rb
|
85
|
+
- lib/gene_genie/version.rb
|
86
|
+
homepage: https://github.com/MEHColeman/gene_genie
|
87
|
+
licenses:
|
88
|
+
- MIT
|
89
|
+
metadata: {}
|
90
|
+
post_install_message:
|
91
|
+
rdoc_options: []
|
92
|
+
require_paths:
|
93
|
+
- lib
|
94
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
95
|
+
requirements:
|
96
|
+
- - ">="
|
97
|
+
- !ruby/object:Gem::Version
|
98
|
+
version: '0'
|
99
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
requirements: []
|
105
|
+
rubyforge_project:
|
106
|
+
rubygems_version: 2.4.5
|
107
|
+
signing_key:
|
108
|
+
specification_version: 4
|
109
|
+
summary: Genetic algorithm optimisation gem
|
110
|
+
test_files: []
|