gegene 1.0.0 → 1.1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 41e8a0e177bba73f066e17c27edcd7577d43633c
4
- data.tar.gz: 4c23284c8be074707981392045deb5db19243fe3
3
+ metadata.gz: 0599f2cbca99f724dacc4eb7f106122174108ea8
4
+ data.tar.gz: c32d27d42638c9e12e292f615002663b8eae4581
5
5
  SHA512:
6
- metadata.gz: 3ff2f8fa8335626a395e0c5aeb4b6b46d8d2cf65ba032773b3fb7e9e51acbc8b163be6e663cb34cc8f875559d990b574b1c155a4f726c61b3d1fe3583d509e20
7
- data.tar.gz: be8ee9a256f9f2a927b0aa0ad4faf1cb45bf10ba355c1dbdde8390fd5a85abf1f65d009fa3c544961a0c38c27ce80c1cc512c1305e823ea835ba346ecf81599e
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 = @genome.get_gene_position(gene_name)
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 = copy
35
- other.chromosomes.each_with_index {
36
- |chromosome, index| child.chromosomes[index] = chromosome if rand(2) == 0
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
- attr_accessor :mutation_rate, :keep_alive_rate, :fitness_target, :karyotypes
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
- @karyotypes.each { |k| k.fitness = @fitness_calculator.call(k) if k.fitness.nil? }
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
- @mutation_rate = rate
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 random_select
100
+ def fitness_weighted_random_select
56
101
  @karyotypes[
57
- @karyotypes.size - Integer(Math.sqrt(Math.sqrt(1 + rand(@karyotypes.size**4 - 1))))
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
- random_select + random_select
108
+ fitness_weighted_random_select + fitness_weighted_random_select
63
109
  end
64
110
 
65
- private :random_select, :random_breed
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
- evolve_impl
72
- i += 1
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.0.0
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-10-27 00:00:00.000000000 Z
11
+ date: 2013-11-01 00:00:00.000000000 Z
12
12
  dependencies: []
13
- description: Framework for fast genetic algorithm development
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.4
50
+ rubygems_version: 2.1.10
50
51
  signing_key:
51
52
  specification_version: 4
52
53
  summary: Genetic algorithm helpers