gegene 1.0.0 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/LICENSE.txt +21 -0
- data/lib/chromosome.rb +44 -0
- data/lib/genome.rb +11 -0
- data/lib/karyotype.rb +42 -4
- data/lib/population.rb +74 -21
- metadata +5 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0599f2cbca99f724dacc4eb7f106122174108ea8
|
4
|
+
data.tar.gz: c32d27d42638c9e12e292f615002663b8eae4581
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0144049db0f4c7634d21815c3b1c0d3e8b5347fb2167d82219b25e52112d1594f21710948e63e58c4196420ea63148d5fa0fab7ec14ade177982316f0e1c6583
|
7
|
+
data.tar.gz: 3f54ddadf7ced812bd993cdc939a73ddebc26063302a502f753cef97e4d2d03c212616c241e9ecd1f97d130b9a8119661317b5491deed85006da3ef12a408049
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2013 Alexandre Ignjatovic
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/lib/chromosome.rb
CHANGED
@@ -1,26 +1,70 @@
|
|
1
1
|
require 'gene'
|
2
2
|
require 'allele'
|
3
|
+
|
4
|
+
# This class represent a chromosome, which contains several allele (gene's values)
|
5
|
+
# and is able to mutate, and to cross over.
|
3
6
|
class Chromosome
|
4
7
|
|
8
|
+
# Construct a chromosome from an array of alleles
|
5
9
|
def initialize(alleles)
|
10
|
+
raise "this constructor expect an array of alleles as input" unless
|
11
|
+
alleles.is_a?(Array)
|
6
12
|
@alleles = alleles
|
7
13
|
end
|
8
14
|
|
15
|
+
# Copy the current chromosome and all its alleles
|
9
16
|
def copy
|
10
17
|
Chromosome.new(@alleles.map{|allele| allele.copy })
|
11
18
|
end
|
12
19
|
|
20
|
+
# Create a random chromosome from a description
|
13
21
|
def Chromosome.create_random_from(description)
|
14
22
|
Chromosome.new(description.map{|gene| gene.create_random() })
|
15
23
|
end
|
16
24
|
|
25
|
+
# Returns the allele at a specific position
|
17
26
|
def [](gene_position)
|
18
27
|
@alleles[gene_position]
|
19
28
|
end
|
20
29
|
|
30
|
+
# Number of underlying alleles
|
31
|
+
def size
|
32
|
+
@alleles.size
|
33
|
+
end
|
34
|
+
|
35
|
+
# Mutate a randomly selected allele of the current chromosome
|
21
36
|
def mutate
|
22
37
|
allele_index = rand @alleles.size
|
23
38
|
@alleles[allele_index].mutate()
|
24
39
|
end
|
25
40
|
|
41
|
+
# Aggregate all alleles values
|
42
|
+
def aggregated_alleles()
|
43
|
+
@alleles.map {|a| a.value}.join(";")
|
44
|
+
end
|
45
|
+
|
46
|
+
# Cross over two chromosomes to provide a new one
|
47
|
+
def Chromosome.cross_over(chromosome_a, chromosome_b)
|
48
|
+
if rand(2) == 0 then
|
49
|
+
chromosome_a, chromosome_b =
|
50
|
+
[chromosome_b, chromosome_a]
|
51
|
+
end
|
52
|
+
size = chromosome_a.size
|
53
|
+
if size < 2 then
|
54
|
+
return chromosome_a.copy
|
55
|
+
elsif size == 2 then
|
56
|
+
return Chromosome.new([chromosome_a[0],chromosome_b[1]])
|
57
|
+
else
|
58
|
+
swap_index = rand(size-1)
|
59
|
+
index = 0
|
60
|
+
new_alleles = []
|
61
|
+
while (index < size) do
|
62
|
+
new_alleles.push(
|
63
|
+
(index <= swap_index ? chromosome_a : chromosome_b)[index].copy)
|
64
|
+
index += 1
|
65
|
+
end
|
66
|
+
return Chromosome.new(new_alleles)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
26
70
|
end
|
data/lib/genome.rb
CHANGED
@@ -1,7 +1,11 @@
|
|
1
1
|
require 'Karyotype'
|
2
2
|
|
3
|
+
# This class stands for the genome. It is a description of all
|
4
|
+
# the gene describing a specific population.
|
3
5
|
class Genome
|
4
6
|
|
7
|
+
DEFAULT_CROSS_OVER_RATE = 0.01
|
8
|
+
|
5
9
|
def initialize(genome_description)
|
6
10
|
raise "Genome description MUST be an Array" unless genome_description.is_a? Array
|
7
11
|
@chromosomes_description = []
|
@@ -14,8 +18,15 @@ class Genome
|
|
14
18
|
end
|
15
19
|
@chromosomes_description << gene_array
|
16
20
|
end
|
21
|
+
@cross_over_rate = DEFAULT_CROSS_OVER_RATE
|
17
22
|
end
|
18
23
|
attr_accessor :gene_positions
|
24
|
+
attr_reader :cross_over_rate
|
25
|
+
def cross_over_rate=(rate)
|
26
|
+
raise "cross_over_rate must be included in [0,1]" unless rate.between?(0,1)
|
27
|
+
@cross_over_rate = rate
|
28
|
+
end
|
29
|
+
|
19
30
|
def get_gene_position(gene_name)
|
20
31
|
@gene_positions[gene_name]
|
21
32
|
end
|
data/lib/karyotype.rb
CHANGED
@@ -1,20 +1,30 @@
|
|
1
1
|
require 'chromosome'
|
2
|
+
require 'digest/md5'
|
3
|
+
# This class represent a karyotype, which could be seen as a set of
|
4
|
+
# chromosomes representing a specific individual of a population
|
2
5
|
class Karyotype
|
3
6
|
attr_accessor :chromosomes
|
4
7
|
attr_accessor :fitness
|
8
|
+
|
5
9
|
def initialize(genome, chromosomes_description)
|
6
10
|
@genome = genome
|
7
11
|
@chromosomes_description = chromosomes_description
|
8
12
|
end
|
9
13
|
private :initialize
|
14
|
+
|
15
|
+
# Copy self and all its chromosomes to a new karyotype
|
10
16
|
def copy
|
11
17
|
karyotype = Karyotype.new(@genome, @chromosomes_description)
|
12
18
|
karyotype.chromosomes = self.chromosomes.map{|chromosome| chromosome.copy}
|
13
19
|
karyotype
|
14
20
|
end
|
21
|
+
|
15
22
|
def to_s
|
16
23
|
@genome.gene_positions.keys.map{|gn| "#{gn}=>#{self[gn]}"}.join';'
|
17
24
|
end
|
25
|
+
|
26
|
+
# Create a random karyotype from a specific genome an its associated
|
27
|
+
# chromosomes description
|
18
28
|
def Karyotype.create_random_from(genome, chromosomes_description)
|
19
29
|
karyotype = Karyotype.new(genome, chromosomes_description)
|
20
30
|
karyotype.chromosomes = chromosomes_description.map {|description|
|
@@ -23,21 +33,49 @@ class Karyotype
|
|
23
33
|
karyotype
|
24
34
|
end
|
25
35
|
|
36
|
+
# Return the allele value of a specific named gene
|
37
|
+
# We strongly recommand using symbols as gene name
|
26
38
|
def [](gene_name)
|
27
39
|
return nil if @chromosomes.nil?
|
28
|
-
chromosome_position, gene_position =
|
40
|
+
chromosome_position, gene_position =
|
41
|
+
@genome.get_gene_position(gene_name)
|
29
42
|
return nil if chromosome_position.nil? || gene_position.nil?
|
30
43
|
return @chromosomes[chromosome_position][gene_position].value
|
31
44
|
end
|
32
45
|
|
46
|
+
# Breeding function
|
47
|
+
# Create a new karyotype based on self and an other
|
33
48
|
def +(other)
|
34
|
-
child =
|
35
|
-
|
36
|
-
|
49
|
+
child = Karyotype.new(@genome, @chromosomes_description)
|
50
|
+
child.chromosomes = []
|
51
|
+
other.chromosomes.each_with_index { |chromosome, index|
|
52
|
+
child_chromosome = nil
|
53
|
+
# Randomly copy chromosomes from dad or mom to child
|
54
|
+
if (rand(100) / 100.0) < @genome.cross_over_rate then
|
55
|
+
# Crossing over required
|
56
|
+
child_chromosome =
|
57
|
+
Chromosome.cross_over(chromosome, chromosomes[index])
|
58
|
+
else
|
59
|
+
# Standard breeding via random selection
|
60
|
+
child_chromosome = (rand(2)==0?chromosome : chromosomes[index]).copy
|
61
|
+
end
|
62
|
+
child.chromosomes.push(child_chromosome)
|
37
63
|
}
|
38
64
|
child
|
39
65
|
end
|
40
66
|
|
67
|
+
# Aggregate all the allele into a md5 hash value
|
68
|
+
def to_md5()
|
69
|
+
if (@hash_value.nil?) then
|
70
|
+
@hash_value = @chromosomes.map{ |chromosome|
|
71
|
+
chromosome.aggregated_alleles
|
72
|
+
}.join(";")
|
73
|
+
@hash_value = Digest::MD5.hexdigest(@hash_value)
|
74
|
+
end
|
75
|
+
@hash_value
|
76
|
+
end
|
77
|
+
|
78
|
+
# Randomly mutate an allele of a randomly selected chromosome
|
41
79
|
def mutate
|
42
80
|
@chromosomes[rand @chromosomes.size].mutate
|
43
81
|
self
|
data/lib/population.rb
CHANGED
@@ -4,17 +4,54 @@ class Population
|
|
4
4
|
DEFAULT_MUTATION_RATE = 0.01
|
5
5
|
DEFAULT_KEEP_ALIVE_RATE = 0.1
|
6
6
|
DEFAULT_EVOLVE_ITERATIONS = 1
|
7
|
+
DEFAULT_FORCE_FITNESS_RECALCULATION = false
|
8
|
+
|
9
|
+
attr_accessor :fitness_target, :karyotypes, :force_fitness_recalculation
|
10
|
+
attr_reader :mutation_rate, :keep_alive_rate
|
11
|
+
|
12
|
+
# mutation rate setter function
|
13
|
+
# Accept values in the range [0,1]
|
14
|
+
def mutation_rate=(value)
|
15
|
+
raise "mutation_rate value must be included in [0,1]" unless value.between?(0,1)
|
16
|
+
@mutation_rate = value
|
17
|
+
end
|
7
18
|
|
8
|
-
|
19
|
+
# keep alive rate setter function
|
20
|
+
# Accept values in the range [0,1]
|
21
|
+
def keep_alive_rate=(value)
|
22
|
+
raise "mutation_rate value must be included in [0,1]" unless value.between?(0,1)
|
23
|
+
@keep_alive_rate = value
|
24
|
+
end
|
25
|
+
|
9
26
|
|
27
|
+
# Run the fitness function for all karyotypes, and sort it by fitness
|
10
28
|
def evaluate
|
11
|
-
|
29
|
+
if @force_fitness_recalculation then
|
30
|
+
@karyotypes.each do |karyotype|
|
31
|
+
karyotype.fitness = @fitness_calculator.call(karyotype)
|
32
|
+
@fitness_hash[karyotype.to_md5()] = karyotype.fitness
|
33
|
+
end
|
34
|
+
else
|
35
|
+
@karyotypes.each do |karyotype|
|
36
|
+
if karyotype.fitness.nil? then
|
37
|
+
if @fitness_hash[karyotype.to_md5()].nil? then
|
38
|
+
karyotype.fitness = @fitness_calculator.call(karyotype)
|
39
|
+
@fitness_hash[karyotype.to_md5()] = karyotype.fitness
|
40
|
+
else
|
41
|
+
karyotype.fitness = @fitness_hash[karyotype.to_md5()]
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
12
46
|
@karyotypes.sort! { |x,y| y.fitness <=> x.fitness }
|
13
47
|
end
|
14
|
-
|
48
|
+
|
15
49
|
private :evaluate
|
16
|
-
|
50
|
+
|
17
51
|
def initialize(size, genome, fitness_calculator)
|
52
|
+
raise "size must be strictly positive." if size < 1
|
53
|
+
@fitness_hash = {}
|
54
|
+
@force_fitness_recalculation = DEFAULT_FORCE_FITNESS_RECALCULATION
|
18
55
|
@mutation_rate = DEFAULT_MUTATION_RATE
|
19
56
|
@keep_alive_rate = DEFAULT_KEEP_ALIVE_RATE
|
20
57
|
@genome = Genome.new(genome)
|
@@ -22,22 +59,30 @@ class Population
|
|
22
59
|
@karyotypes = Array.new(size){ @genome.create_random_karyotype }
|
23
60
|
evaluate
|
24
61
|
end
|
25
|
-
|
62
|
+
|
26
63
|
def set_mutation_rate(rate)
|
27
|
-
|
64
|
+
raise "mutation_rate value must be included in [0,1]" unless rate.between?(0,1)
|
65
|
+
|
66
|
+
@mutation_rate= rate
|
28
67
|
self
|
29
68
|
end
|
30
|
-
|
69
|
+
|
31
70
|
def set_keep_alive_rate(rate)
|
71
|
+
raise "keep_alive_rate must be included in [0,1]" unless rate.between?(0,1)
|
32
72
|
@keep_alive_rate = rate
|
33
73
|
self
|
34
74
|
end
|
35
|
-
|
75
|
+
|
36
76
|
def set_fitness_target(target)
|
37
77
|
@fitness_target = target
|
38
78
|
self
|
39
79
|
end
|
40
|
-
|
80
|
+
|
81
|
+
def set_force_fitness_recalculation(target)
|
82
|
+
@force_fitness_recalculation = target
|
83
|
+
self
|
84
|
+
end
|
85
|
+
|
41
86
|
def size
|
42
87
|
@karyotypes.size
|
43
88
|
end
|
@@ -45,49 +90,57 @@ class Population
|
|
45
90
|
def linear_random_select
|
46
91
|
@karyotypes[rand @karyotypes.size]
|
47
92
|
end
|
48
|
-
|
93
|
+
|
49
94
|
def create_random_mutation
|
50
95
|
linear_random_select.copy.mutate
|
51
96
|
end
|
52
97
|
|
53
98
|
private :linear_random_select, :create_random_mutation
|
54
99
|
|
55
|
-
def
|
100
|
+
def fitness_weighted_random_select
|
56
101
|
@karyotypes[
|
57
|
-
@karyotypes.size -
|
102
|
+
@karyotypes.size -
|
103
|
+
Integer(Math.sqrt(Math.sqrt(1 + rand(@karyotypes.size**4 - 1))))
|
58
104
|
]
|
59
105
|
end
|
60
106
|
|
61
107
|
def random_breed
|
62
|
-
|
108
|
+
fitness_weighted_random_select + fitness_weighted_random_select
|
63
109
|
end
|
64
110
|
|
65
|
-
private :
|
66
|
-
|
111
|
+
private :fitness_weighted_random_select, :random_breed
|
112
|
+
|
113
|
+
# This function make ou population evolving by:
|
114
|
+
# * Selecting and breeding the fittest karyotypes
|
115
|
+
# * Running the fitness evaluation on all the newly created karyotyopes
|
116
|
+
# The selection process include three subprocesses:
|
117
|
+
# * Selecting the fittest individuals to keep alive
|
118
|
+
# * Mutating randomly (linear) selected individuals
|
119
|
+
# * Breeding randomly (fitness weighted) selected individuals
|
67
120
|
def evolve(iterations = DEFAULT_EVOLVE_ITERATIONS)
|
68
121
|
i = 1
|
69
122
|
while (i <= iterations) &&
|
70
123
|
(@fitness_target.nil? || @fitness_target > @karyotypes[0].fitness) do
|
71
|
-
|
72
|
-
|
124
|
+
evolve_impl
|
125
|
+
i += 1
|
73
126
|
end
|
74
127
|
self
|
75
128
|
end
|
76
|
-
|
129
|
+
|
77
130
|
def evolve_impl
|
78
131
|
new_population = []
|
79
|
-
|
132
|
+
|
80
133
|
# Keeping alive a specific amount of the best karyotypes
|
81
134
|
keep_alive_count = Integer(@karyotypes.size * @keep_alive_rate)
|
82
135
|
if keep_alive_count > 0 then
|
83
136
|
@karyotypes[0, keep_alive_count].each {|karyotype| new_population.push(karyotype)}
|
84
137
|
end
|
85
|
-
|
138
|
+
|
86
139
|
mutation_count = Integer(@karyotypes.size * @mutation_rate)
|
87
140
|
(0..mutation_count-1).each {
|
88
141
|
new_population.push create_random_mutation
|
89
142
|
}
|
90
|
-
|
143
|
+
|
91
144
|
remaining = @karyotypes.size-mutation_count-keep_alive_count
|
92
145
|
(0..remaining-1).each {
|
93
146
|
child = random_breed
|
metadata
CHANGED
@@ -1,16 +1,16 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: gegene
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Alexandre Ignjatovic
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2013-
|
11
|
+
date: 2013-11-01 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
|
-
description: Framework for
|
13
|
+
description: Framework for genetic algorithm fast development
|
14
14
|
email: alexandre.ignjatovic@gmail.com
|
15
15
|
executables: []
|
16
16
|
extensions: []
|
@@ -26,6 +26,7 @@ files:
|
|
26
26
|
- example/adding_gene_type.rb
|
27
27
|
- example/one_max.rb
|
28
28
|
- example/simple.rb
|
29
|
+
- LICENSE.txt
|
29
30
|
homepage: https://github.com/bankair/gegene
|
30
31
|
licenses:
|
31
32
|
- MIT
|
@@ -46,7 +47,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
46
47
|
version: '0'
|
47
48
|
requirements: []
|
48
49
|
rubyforge_project:
|
49
|
-
rubygems_version: 2.1.
|
50
|
+
rubygems_version: 2.1.10
|
50
51
|
signing_key:
|
51
52
|
specification_version: 4
|
52
53
|
summary: Genetic algorithm helpers
|