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 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]
@@ -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
@@ -0,0 +1,2 @@
1
+ require 'gene'
2
+ require 'population'
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: []