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 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