gegene 1.0.0
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/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: []
|