gegene 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/example/adding_gene_type.rb +34 -0
- data/example/one_max.rb +34 -0
- data/example/simple.rb +10 -0
- data/lib/allele.rb +16 -0
- data/lib/chromosome.rb +26 -0
- data/lib/gegene.rb +2 -0
- data/lib/gene.rb +72 -0
- data/lib/genome.rb +27 -0
- data/lib/karyotype.rb +45 -0
- data/lib/population.rb +101 -0
- metadata +53 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 41e8a0e177bba73f066e17c27edcd7577d43633c
|
4
|
+
data.tar.gz: 4c23284c8be074707981392045deb5db19243fe3
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 3ff2f8fa8335626a395e0c5aeb4b6b46d8d2cf65ba032773b3fb7e9e51acbc8b163be6e663cb34cc8f875559d990b574b1c155a4f726c61b3d1fe3583d509e20
|
7
|
+
data.tar.gz: be8ee9a256f9f2a927b0aa0ad4faf1cb45bf10ba355c1dbdde8390fd5a85abf1f65d009fa3c544961a0c38c27ce80c1cc512c1305e823ea835ba346ecf81599e
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'gegene'
|
2
|
+
|
3
|
+
class PalindromeGene < Gene
|
4
|
+
def initialize(size)
|
5
|
+
@size = size
|
6
|
+
end
|
7
|
+
|
8
|
+
# Will raise an error in Gene class if not overloaded here
|
9
|
+
def random_allele_value
|
10
|
+
prefix = Array.new(@size/2){ rand(97..122).chr}.join("")
|
11
|
+
prefix + (@size%2 == 0 ? "" : rand(97..122).chr) +prefix.reverse()
|
12
|
+
end
|
13
|
+
|
14
|
+
# Will raise an error in Gene class if not overloaded here
|
15
|
+
def mutate(previous_value)
|
16
|
+
self.random_allele_value
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# The following code is not mandatory
|
21
|
+
# use it if you'd rather have Gene.Palyndrome(9) instead of a ctor call
|
22
|
+
#class Gene
|
23
|
+
# Gene.Palyndrome(size) PalyndromeGene.new(size) end
|
24
|
+
#end
|
25
|
+
|
26
|
+
genome_description = [{palyndrome:PalindromeGene.new(9)}]
|
27
|
+
|
28
|
+
def fitness(karyotype)
|
29
|
+
karyotype[:palyndrome].split(//).select{|c| c =~ /[aeiou]/}.size
|
30
|
+
end
|
31
|
+
|
32
|
+
population = Population.new(25, genome_description, method(:fitness))
|
33
|
+
population.evolve(10)
|
34
|
+
puts population.karyotypes[0][:palyndrome]
|
data/example/one_max.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'gegene'
|
2
|
+
|
3
|
+
# Follow a genome description for the one max problem:
|
4
|
+
genome_description = [
|
5
|
+
{ v1: Gene.Integer(0,1) },
|
6
|
+
{ v2: Gene.Integer(0,1) },
|
7
|
+
{ v3: Gene.Integer(0,1) }
|
8
|
+
]
|
9
|
+
|
10
|
+
# Here is the fitness function of the one max problem:
|
11
|
+
def fitness(karyotype)
|
12
|
+
karyotype[:v1] + karyotype[:v2] + karyotype[:v3]
|
13
|
+
end
|
14
|
+
|
15
|
+
# We create a population of six individuals with the previous desc & func:
|
16
|
+
population = Population.new(6, genome_description, method(:fitness))
|
17
|
+
|
18
|
+
# As we known the best solution for the one max problem, we set a
|
19
|
+
# fitness target of 3 (1+1+1).
|
20
|
+
population.fitness_target = 3
|
21
|
+
|
22
|
+
# As the population is quite small, we add a little more funk to our
|
23
|
+
# evolution process by setting a 30% mutation rate
|
24
|
+
population.mutation_rate = 0.3
|
25
|
+
|
26
|
+
# Let's go for some darwinist fun !
|
27
|
+
population.evolve(10)
|
28
|
+
|
29
|
+
# population.karyotypes is sorted by fitness score, so we can assume that
|
30
|
+
# the first element is the fittest
|
31
|
+
best_karyotype = population.karyotypes[0]
|
32
|
+
|
33
|
+
puts "Best karyotype scored #{best_karyotype.fitness}:"
|
34
|
+
[:v1, :v2, :v3].each {|x| puts " #{x.to_s}:#{best_karyotype[x]}" }
|
data/example/simple.rb
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
require 'gegene'
|
2
|
+
FITNESS_TARGET = 1 / 0.001
|
3
|
+
population = Population.new(
|
4
|
+
50,
|
5
|
+
[{a:Gene.Integer(0, 5)},{b:Gene.Integer(-5,5)}],
|
6
|
+
lambda {|k| 1 / (0.001 + (12-(k[:a]**2+k[:b])).abs) }
|
7
|
+
)
|
8
|
+
population.set_mutation_rate(0.5).set_fitness_target(FITNESS_TARGET).evolve(50)
|
9
|
+
bk = population.karyotypes[0]
|
10
|
+
warn "a:#{bk[:a]} b:#{bk[:b]} => a*a + b = #{bk[:a]**2+bk[:b]}"
|
data/lib/allele.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
class Allele
|
2
|
+
attr_accessor :value
|
3
|
+
def initialize(gene, value)
|
4
|
+
@gene = gene
|
5
|
+
@value = value
|
6
|
+
end
|
7
|
+
|
8
|
+
def mutate
|
9
|
+
@value = @gene.mutate(@value)
|
10
|
+
end
|
11
|
+
|
12
|
+
def copy
|
13
|
+
# /!\ if @value is a ref, its underlying object won't be copied
|
14
|
+
Allele.new(@gene, @value)
|
15
|
+
end
|
16
|
+
end
|
data/lib/chromosome.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'gene'
|
2
|
+
require 'allele'
|
3
|
+
class Chromosome
|
4
|
+
|
5
|
+
def initialize(alleles)
|
6
|
+
@alleles = alleles
|
7
|
+
end
|
8
|
+
|
9
|
+
def copy
|
10
|
+
Chromosome.new(@alleles.map{|allele| allele.copy })
|
11
|
+
end
|
12
|
+
|
13
|
+
def Chromosome.create_random_from(description)
|
14
|
+
Chromosome.new(description.map{|gene| gene.create_random() })
|
15
|
+
end
|
16
|
+
|
17
|
+
def [](gene_position)
|
18
|
+
@alleles[gene_position]
|
19
|
+
end
|
20
|
+
|
21
|
+
def mutate
|
22
|
+
allele_index = rand @alleles.size
|
23
|
+
@alleles[allele_index].mutate()
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
data/lib/gegene.rb
ADDED
data/lib/gene.rb
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
|
2
|
+
class Gene
|
3
|
+
def mutate(allele)
|
4
|
+
raise "the 'mutate' function must be overloaded in the inheriting class."
|
5
|
+
end
|
6
|
+
|
7
|
+
def random_allele_value
|
8
|
+
raise "the 'random_allele_value' function must be overloaded in the inheriting class."
|
9
|
+
end
|
10
|
+
def create_random
|
11
|
+
Allele.new(self, self.random_allele_value)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class NumericGene < Gene
|
16
|
+
attr_accessor :min, :max
|
17
|
+
def initialize(min, max)
|
18
|
+
raise "max (#{max}) must be greater than min (#{min})" if max <= min
|
19
|
+
@min = min
|
20
|
+
@max = max
|
21
|
+
end
|
22
|
+
|
23
|
+
def mutate(previous_value)
|
24
|
+
next_value = random_allele_value
|
25
|
+
while next_value == previous_value do
|
26
|
+
next_value = random_allele_value
|
27
|
+
end
|
28
|
+
next_value
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
class IntegerGene < NumericGene
|
33
|
+
def initialize(min, max) super(min, max) end
|
34
|
+
|
35
|
+
def random_allele_value() rand(self.min..self.max) end
|
36
|
+
end
|
37
|
+
|
38
|
+
class FloatGene < NumericGene
|
39
|
+
def initialize(min, max) super(min, max) end
|
40
|
+
|
41
|
+
def random_allele_value
|
42
|
+
rand() * (self.max - self.min) + self.min
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
class EnumGene < Gene
|
47
|
+
attr_accessor :enum_values
|
48
|
+
|
49
|
+
def initialize(enum_values)
|
50
|
+
raise "EnumGene initialization require an Array" unless enum_values.is_a? Array
|
51
|
+
raise "EnumGene require at least two values" unless enum_values.size > 1
|
52
|
+
@enum_values = enum_values
|
53
|
+
end
|
54
|
+
|
55
|
+
def random_allele_value
|
56
|
+
@enum_values[rand @enum_values.size]
|
57
|
+
end
|
58
|
+
|
59
|
+
def mutate(previous_value)
|
60
|
+
new_value = self.random_allele_value
|
61
|
+
while new_value == previous_value
|
62
|
+
new_value = self.random_allele_value
|
63
|
+
end
|
64
|
+
new_value
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
class Gene
|
69
|
+
def self.Integer(min, max) IntegerGene.new(min, max) end
|
70
|
+
def self.Float(min, max) FloatGene.new(min, max) end
|
71
|
+
def self.Enum(enum_values) EnumGene.new(enum_values) end
|
72
|
+
end
|
data/lib/genome.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'Karyotype'
|
2
|
+
|
3
|
+
class Genome
|
4
|
+
|
5
|
+
def initialize(genome_description)
|
6
|
+
raise "Genome description MUST be an Array" unless genome_description.is_a? Array
|
7
|
+
@chromosomes_description = []
|
8
|
+
@gene_positions = {}
|
9
|
+
genome_description.each_with_index do |chomosome_hash, chromosome_position|
|
10
|
+
gene_array = []
|
11
|
+
chomosome_hash.keys.each_with_index do |gene_name, gene_position|
|
12
|
+
@gene_positions[gene_name] = [chromosome_position, gene_position]
|
13
|
+
gene_array << chomosome_hash[gene_name]
|
14
|
+
end
|
15
|
+
@chromosomes_description << gene_array
|
16
|
+
end
|
17
|
+
end
|
18
|
+
attr_accessor :gene_positions
|
19
|
+
def get_gene_position(gene_name)
|
20
|
+
@gene_positions[gene_name]
|
21
|
+
end
|
22
|
+
|
23
|
+
def create_random_karyotype
|
24
|
+
Karyotype.create_random_from(self, @chromosomes_description)
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
data/lib/karyotype.rb
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'chromosome'
|
2
|
+
class Karyotype
|
3
|
+
attr_accessor :chromosomes
|
4
|
+
attr_accessor :fitness
|
5
|
+
def initialize(genome, chromosomes_description)
|
6
|
+
@genome = genome
|
7
|
+
@chromosomes_description = chromosomes_description
|
8
|
+
end
|
9
|
+
private :initialize
|
10
|
+
def copy
|
11
|
+
karyotype = Karyotype.new(@genome, @chromosomes_description)
|
12
|
+
karyotype.chromosomes = self.chromosomes.map{|chromosome| chromosome.copy}
|
13
|
+
karyotype
|
14
|
+
end
|
15
|
+
def to_s
|
16
|
+
@genome.gene_positions.keys.map{|gn| "#{gn}=>#{self[gn]}"}.join';'
|
17
|
+
end
|
18
|
+
def Karyotype.create_random_from(genome, chromosomes_description)
|
19
|
+
karyotype = Karyotype.new(genome, chromosomes_description)
|
20
|
+
karyotype.chromosomes = chromosomes_description.map {|description|
|
21
|
+
Chromosome.create_random_from(description)
|
22
|
+
}
|
23
|
+
karyotype
|
24
|
+
end
|
25
|
+
|
26
|
+
def [](gene_name)
|
27
|
+
return nil if @chromosomes.nil?
|
28
|
+
chromosome_position, gene_position = @genome.get_gene_position(gene_name)
|
29
|
+
return nil if chromosome_position.nil? || gene_position.nil?
|
30
|
+
return @chromosomes[chromosome_position][gene_position].value
|
31
|
+
end
|
32
|
+
|
33
|
+
def +(other)
|
34
|
+
child = copy
|
35
|
+
other.chromosomes.each_with_index {
|
36
|
+
|chromosome, index| child.chromosomes[index] = chromosome if rand(2) == 0
|
37
|
+
}
|
38
|
+
child
|
39
|
+
end
|
40
|
+
|
41
|
+
def mutate
|
42
|
+
@chromosomes[rand @chromosomes.size].mutate
|
43
|
+
self
|
44
|
+
end
|
45
|
+
end
|
data/lib/population.rb
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
require 'genome'
|
2
|
+
|
3
|
+
class Population
|
4
|
+
DEFAULT_MUTATION_RATE = 0.01
|
5
|
+
DEFAULT_KEEP_ALIVE_RATE = 0.1
|
6
|
+
DEFAULT_EVOLVE_ITERATIONS = 1
|
7
|
+
|
8
|
+
attr_accessor :mutation_rate, :keep_alive_rate, :fitness_target, :karyotypes
|
9
|
+
|
10
|
+
def evaluate
|
11
|
+
@karyotypes.each { |k| k.fitness = @fitness_calculator.call(k) if k.fitness.nil? }
|
12
|
+
@karyotypes.sort! { |x,y| y.fitness <=> x.fitness }
|
13
|
+
end
|
14
|
+
|
15
|
+
private :evaluate
|
16
|
+
|
17
|
+
def initialize(size, genome, fitness_calculator)
|
18
|
+
@mutation_rate = DEFAULT_MUTATION_RATE
|
19
|
+
@keep_alive_rate = DEFAULT_KEEP_ALIVE_RATE
|
20
|
+
@genome = Genome.new(genome)
|
21
|
+
@fitness_calculator = fitness_calculator
|
22
|
+
@karyotypes = Array.new(size){ @genome.create_random_karyotype }
|
23
|
+
evaluate
|
24
|
+
end
|
25
|
+
|
26
|
+
def set_mutation_rate(rate)
|
27
|
+
@mutation_rate = rate
|
28
|
+
self
|
29
|
+
end
|
30
|
+
|
31
|
+
def set_keep_alive_rate(rate)
|
32
|
+
@keep_alive_rate = rate
|
33
|
+
self
|
34
|
+
end
|
35
|
+
|
36
|
+
def set_fitness_target(target)
|
37
|
+
@fitness_target = target
|
38
|
+
self
|
39
|
+
end
|
40
|
+
|
41
|
+
def size
|
42
|
+
@karyotypes.size
|
43
|
+
end
|
44
|
+
|
45
|
+
def linear_random_select
|
46
|
+
@karyotypes[rand @karyotypes.size]
|
47
|
+
end
|
48
|
+
|
49
|
+
def create_random_mutation
|
50
|
+
linear_random_select.copy.mutate
|
51
|
+
end
|
52
|
+
|
53
|
+
private :linear_random_select, :create_random_mutation
|
54
|
+
|
55
|
+
def random_select
|
56
|
+
@karyotypes[
|
57
|
+
@karyotypes.size - Integer(Math.sqrt(Math.sqrt(1 + rand(@karyotypes.size**4 - 1))))
|
58
|
+
]
|
59
|
+
end
|
60
|
+
|
61
|
+
def random_breed
|
62
|
+
random_select + random_select
|
63
|
+
end
|
64
|
+
|
65
|
+
private :random_select, :random_breed
|
66
|
+
|
67
|
+
def evolve(iterations = DEFAULT_EVOLVE_ITERATIONS)
|
68
|
+
i = 1
|
69
|
+
while (i <= iterations) &&
|
70
|
+
(@fitness_target.nil? || @fitness_target > @karyotypes[0].fitness) do
|
71
|
+
evolve_impl
|
72
|
+
i += 1
|
73
|
+
end
|
74
|
+
self
|
75
|
+
end
|
76
|
+
|
77
|
+
def evolve_impl
|
78
|
+
new_population = []
|
79
|
+
|
80
|
+
# Keeping alive a specific amount of the best karyotypes
|
81
|
+
keep_alive_count = Integer(@karyotypes.size * @keep_alive_rate)
|
82
|
+
if keep_alive_count > 0 then
|
83
|
+
@karyotypes[0, keep_alive_count].each {|karyotype| new_population.push(karyotype)}
|
84
|
+
end
|
85
|
+
|
86
|
+
mutation_count = Integer(@karyotypes.size * @mutation_rate)
|
87
|
+
(0..mutation_count-1).each {
|
88
|
+
new_population.push create_random_mutation
|
89
|
+
}
|
90
|
+
|
91
|
+
remaining = @karyotypes.size-mutation_count-keep_alive_count
|
92
|
+
(0..remaining-1).each {
|
93
|
+
child = random_breed
|
94
|
+
new_population.push child
|
95
|
+
}
|
96
|
+
@karyotypes = new_population
|
97
|
+
evaluate
|
98
|
+
end
|
99
|
+
private :evolve_impl
|
100
|
+
|
101
|
+
end
|
metadata
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: gegene
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Alexandre Ignjatovic
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2013-10-27 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: Framework for fast genetic algorithm development
|
14
|
+
email: alexandre.ignjatovic@gmail.com
|
15
|
+
executables: []
|
16
|
+
extensions: []
|
17
|
+
extra_rdoc_files: []
|
18
|
+
files:
|
19
|
+
- lib/allele.rb
|
20
|
+
- lib/chromosome.rb
|
21
|
+
- lib/gegene.rb
|
22
|
+
- lib/gene.rb
|
23
|
+
- lib/genome.rb
|
24
|
+
- lib/karyotype.rb
|
25
|
+
- lib/population.rb
|
26
|
+
- example/adding_gene_type.rb
|
27
|
+
- example/one_max.rb
|
28
|
+
- example/simple.rb
|
29
|
+
homepage: https://github.com/bankair/gegene
|
30
|
+
licenses:
|
31
|
+
- MIT
|
32
|
+
metadata: {}
|
33
|
+
post_install_message:
|
34
|
+
rdoc_options: []
|
35
|
+
require_paths:
|
36
|
+
- lib
|
37
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - ">="
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: '0'
|
42
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - ">="
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '0'
|
47
|
+
requirements: []
|
48
|
+
rubyforge_project:
|
49
|
+
rubygems_version: 2.1.4
|
50
|
+
signing_key:
|
51
|
+
specification_version: 4
|
52
|
+
summary: Genetic algorithm helpers
|
53
|
+
test_files: []
|