genetica 0.0.1.beta.2 → 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 9b8e281a009a9819b4c780b57560498e4561a38a
4
+ data.tar.gz: a1f2c7977d9b164a39aa04faf27e93b66ad4daf7
5
+ SHA512:
6
+ metadata.gz: 66c0b5c5b0dfe9fc2080e2a7acc5103be24e452b2ad13df46a67e9ccfbe12cc053e93bbd5ce8a2b48d53ade3180d58c32a57c4b30d3fffc20892f76b669376bc
7
+ data.tar.gz: 982f38678edc5df1205f3f9d7da574a4d2bceddbbbda389694499a1a23280b0941dc4724384dd759d6cf0116d1a2f4d0a17cddb991d9497880dae1355431295c
@@ -1,25 +1,42 @@
1
1
  module Genetica
2
- class Chromosome
2
+ class Chromosome < Array
3
+ def crossover(crossover_method, crossover_probability, chromosome)
4
+ if crossover_probability > 0 && rand.between?(0, crossover_probability)
5
+ self.send(crossover_method, crossover_probability, chromosome)
6
+ else
7
+ # There is no crossover, return chromosomes without changes
8
+ return self, chromosome
9
+ end
10
+ end
3
11
 
4
- attr_reader :chromosome
12
+ def single_point_crossover(crossover_probability, chromosome)
13
+ locus = rand(chromosome.size) + 1
5
14
 
6
- def initialize(chromosome)
7
- @chromosome = chromosome
15
+ offspring_a = take(locus) + chromosome.last(size - locus)
16
+ offspring_b = chromosome.take(locus) + last(size - locus)
17
+
18
+ return Chromosome.new(offspring_a), Chromosome.new(offspring_b)
8
19
  end
9
20
 
10
- def single_point_crossover(chromosome)
11
- other_chromosome = chromosome.chromosome
12
- locus = rand(other_chromosome.size) + 1
21
+ def uniform_crossover(crossover_probability, chromosome)
22
+ offspring_a, offspring_b = Array.new, Array.new
13
23
 
14
- offspring_a = @chromosome.take(locus) + other_chromosome.last(@chromosome.size - locus)
15
- offspring_b = other_chromosome.take(locus) + @chromosome.last(@chromosome.size - locus)
24
+ chromosome.size.times do |i|
25
+ if rand(2) == 0
26
+ offspring_a << self[i]
27
+ offspring_b << chromosome[i]
28
+ else
29
+ offspring_a << chromosome[i]
30
+ offspring_b << self[i]
31
+ end
32
+ end
16
33
 
17
34
  return Chromosome.new(offspring_a), Chromosome.new(offspring_b)
18
35
  end
19
36
 
20
37
  def mutate!(mutation_probability, alleles)
21
38
  if mutation_probability > 0
22
- @chromosome.collect! do |gene|
39
+ map! do |gene|
23
40
  if rand.between? 0, mutation_probability
24
41
  # Mutated Gene, we select a different gene from the alleles
25
42
  (alleles - [gene]).sample
@@ -32,9 +49,7 @@ module Genetica
32
49
  end
33
50
 
34
51
  def to_s
35
- return @chromosome.join
52
+ join
36
53
  end
37
-
38
54
  end
39
55
  end
40
-
@@ -1,20 +1,22 @@
1
1
  module Genetica
2
2
  class ChromosomeBuilder
3
-
4
- attr_accessor :length
5
- attr_accessor :alleles
3
+ attr_accessor :length, :alleles
6
4
 
7
5
  def initialize
8
- # Default Chromosome values
9
- @length = 8
10
- @alleles = [0, 1]
6
+ set_default_chromosome_attributes
11
7
  end
12
-
8
+
13
9
  def chromosome
14
- chromosome = Array.new
15
- @length.times { chromosome << @alleles.sample }
16
- return Chromosome.new(chromosome)
10
+ Chromosome.new.tap do |chromosome|
11
+ length.times { chromosome << alleles.sample }
12
+ end
17
13
  end
18
14
 
15
+ private
16
+
17
+ def set_default_chromosome_attributes
18
+ @length = 8
19
+ @alleles = [0, 1]
20
+ end
19
21
  end
20
22
  end
@@ -1,113 +1,109 @@
1
1
  module Genetica
2
- class Population
3
-
4
- attr_reader :population
5
- attr_reader :generation
6
-
7
- attr_accessor :alleles
8
- attr_accessor :elitism
9
- attr_accessor :crossover_probability
10
- attr_accessor :mutation_probability
11
- attr_accessor :fitness_function
2
+ class Population < Array
3
+ attr_reader :generation, :population_fitness
4
+ attr_accessor :alleles, :elitism, :crossover_method, :crossover_probability, :mutation_probability
12
5
 
13
6
  def initialize(population)
7
+ super population
8
+
14
9
  @generation = 0
15
- @population = population
10
+ end
11
+
12
+ def fitness
13
+ raise NotImplementedError, "Your class should implement #{__method__} class method"
16
14
  end
17
15
 
18
16
  def best_chromosome
19
- @population.at @population_fitness.index(self.best_fitness)
17
+ @best_chromosome ||= at(population_fitness.index(best_fitness))
20
18
  end
21
19
 
22
- def best_chromosomes(quantity=1)
23
- self.best_fitnesses(quantity).collect { |fitness| @population.at(@population_fitness.index fitness) }
20
+ def best_chromosomes(quantity = 1)
21
+ @best_chromosomes ||= best_fitnesses(quantity).map do |fitness|
22
+ at population_fitness.index(fitness)
23
+ end
24
24
  end
25
25
 
26
26
  def best_fitness
27
- @population_fitness.max
27
+ @best_fitness ||= population_fitness.max
28
28
  end
29
29
 
30
- def best_fitnesses(quantity=1)
31
- @population_fitness.sort.reverse.take(quantity)
32
- end
30
+ def best_fitnesses(quantity = 1)
31
+ @best_fitnesses ||= population_fitness.sort.reverse.first(quantity)
32
+ end
33
33
 
34
34
  def average_fitness
35
- @population_fitness.inject(:+) / @population_fitness.size.to_f
35
+ @average_fitness ||= population_fitness.inject(:+) / population_fitness.size.to_f
36
36
  end
37
37
 
38
38
  def population_fitness
39
- @population.collect { |chromosome| @fitness_function.call(chromosome) }
40
- end
41
-
42
- def fitness_function=(new_fitness_function)
43
- @fitness_function = new_fitness_function
44
- @population_fitness = self.population_fitness
45
- end
46
-
47
- def population=(new_population)
48
- @population = new_population
49
- @population_fitness = self.population_fitness
39
+ @population_fitness ||= map { |chromosome| fitness(chromosome) }
50
40
  end
51
41
 
52
- def fitness_proportionate_selection
53
- # FUTURE: With Ruby 1.9.3 you can use rand with ranges, e.g. rand 0.0..3.4
54
- # Get random number
55
- random_generator = Random.new
56
- random_number = random_generator.rand 0.0..@population_fitness.inject(:+)
57
-
58
- # Chromosome selection
59
- fitness_counter = 0
60
- @population.each_with_index do |chromosome, i|
61
- fitness_counter += @population_fitness[i]
62
- if fitness_counter >= random_number
63
- return chromosome
64
- end
65
- end
66
- end
67
-
68
- def run(generations=1)
42
+ def run(generations = 1)
69
43
  generations.times do
70
44
  # Generate a new chromosome population
71
45
  population = Array.new
72
46
 
73
47
  # If elitism if greater than 0 then we save the same number of chromosomes to the next generation
74
- population += self.best_chromosomes(quantity=@elitism) if @elitism > 0
48
+ population += best_chromosomes(quantity = elitism) if elitism > 0
75
49
 
76
- while population.size < @population.size
50
+ while population.size < size
77
51
  # 1. Selection Step
78
52
  # Select a pair of parent chromosomes from the current population.
79
53
  # This selection is 'with replacement', the same chromosome can be selected
80
54
  # more than once to become a parent.
81
- chromosome_a = self.fitness_proportionate_selection
82
- chromosome_b = self.fitness_proportionate_selection
55
+ chromosome_a = fitness_proportionate_selection
56
+ chromosome_b = fitness_proportionate_selection
83
57
 
84
58
  # 2. Crossover Step
85
- # TODO: Maybe crossover probability check would be in the single_point_crossover of
86
- # Chromosome class.
87
- if @crossover_probability > 0 and rand.between? 0, @crossover_probability
88
- offspring_a, offspring_b = chromosome_a.single_point_crossover chromosome_b
89
- else
90
- offspring_a, offspring_b = chromosome_a, chromosome_b
91
- end
59
+ offspring_a, offspring_b = chromosome_a.crossover(crossover_method, crossover_probability,
60
+ chromosome_b)
92
61
 
93
62
  # 3. Mutation Step
94
- offspring_a.mutate! @mutation_probability, @alleles
95
- offspring_b.mutate! @mutation_probability, @alleles
63
+ offspring_a.mutate!(mutation_probability, alleles)
64
+ offspring_b.mutate!(mutation_probability, alleles)
96
65
 
97
66
  # 4. Adding offsprings to new chromosome population
98
- population << offspring_a << offspring_b
67
+ population << offspring_a << offspring_b
99
68
  end
100
69
 
101
70
  # If original population size is odd discard a random chromosome
102
- population.delete_at rand population.size if @population.size.odd?
71
+ population.delete_at rand population.size if size.odd?
103
72
 
104
73
  # Replacing chromosome population with the new one
105
- self.population = population
74
+ replace population
106
75
 
107
76
  # A new generation has been created
108
77
  @generation += 1
109
78
  end
110
79
  end
111
80
 
81
+ private
82
+
83
+ def fitness_proportionate_selection
84
+ random_number = rand 0.0..population_fitness.inject(:+)
85
+
86
+ # Chromosome selection
87
+ fitness_counter = 0
88
+ each_with_index do |chromosome, i|
89
+ fitness_counter += population_fitness[i]
90
+ return chromosome if fitness_counter >= random_number
91
+ end
92
+ end
93
+
94
+ def replace(other_ary)
95
+ super other_ary
96
+
97
+ reset_memoizes
98
+ end
99
+
100
+ def reset_memoizes
101
+ @population_fitness = nil
102
+ @average_fitness = nil
103
+ @best_fitnesses = nil
104
+ @best_fitness = nil
105
+ @best_chromosomes = nil
106
+ @best_chromosome = nil
107
+ end
112
108
  end
113
109
  end
@@ -1,47 +1,58 @@
1
1
  module Genetica
2
2
  class PopulationBuilder
3
+ class PopulationClassError < StandardError; end
3
4
 
4
5
  # Population attributes
5
- attr_accessor :size
6
- attr_accessor :elitism
7
- attr_accessor :crossover_probability
8
- attr_accessor :mutation_probability
9
- attr_accessor :fitness_function
6
+ attr_accessor :size, :elitism, :crossover_method, :crossover_probability, :mutation_probability,
7
+ :population_class
8
+
10
9
  # Chromosome attributes
11
- attr_accessor :chromosome_length
12
- attr_accessor :chromosome_alleles
10
+ attr_accessor :chromosome_length, :chromosome_alleles
13
11
 
14
12
  def initialize
15
- # Default Population values
13
+ set_default_population_attributes
14
+ set_default_chromosome_attributes
15
+ end
16
+
17
+ def population
18
+ raise PopulationClassError, 'You must assign a population class' if population_class.nil?
19
+
20
+ population_class.new(chromosome_population).tap do |population|
21
+ population.alleles = chromosome_alleles
22
+ population.elitism = elitism
23
+ population.crossover_method = crossover_method
24
+ population.crossover_probability = crossover_probability
25
+ population.mutation_probability = mutation_probability
26
+ end
27
+ end
28
+
29
+ private
30
+
31
+ def set_default_population_attributes
16
32
  @size = 20
17
33
  @elitism = 0
34
+ @crossover_method = :uniform_crossover
18
35
  @crossover_probability = 0.7
19
36
  @mutation_probability = 0.001
20
- @fitness_function = nil
21
- # Default Chromosome values
37
+ @population_class = nil
38
+ end
39
+
40
+ def set_default_chromosome_attributes
22
41
  @chromosome_length = 8
23
42
  @chromosome_alleles = [0, 1]
24
43
  end
25
44
 
26
- def population
27
- # Generating Chromosome population
28
- chromosome_builder = ChromosomeBuilder.new
29
- chromosome_builder.length = @chromosome_length
30
- chromosome_builder.alleles = @chromosome_alleles
31
-
32
- chromosome_population = Array.new
33
- @size.times { chromosome_population << chromosome_builder.chromosome }
34
-
35
- # Generating Population
36
- population = Population.new chromosome_population
37
- population.alleles = @chromosome_alleles
38
- population.elitism = @elitism
39
- population.crossover_probability = @crossover_probability
40
- population.mutation_probability = @mutation_probability
41
- population.fitness_function = @fitness_function
42
-
43
- return population
45
+ def chromosome_population
46
+ Array.new.tap do |chromosome_population|
47
+ size.times { chromosome_population << chromosome_builder.chromosome }
48
+ end
44
49
  end
45
50
 
51
+ def chromosome_builder
52
+ ChromosomeBuilder.new.tap do |chromosome_builder|
53
+ chromosome_builder.length = chromosome_length
54
+ chromosome_builder.alleles = chromosome_alleles
55
+ end
56
+ end
46
57
  end
47
58
  end
@@ -0,0 +1,34 @@
1
+ require 'spec_helper'
2
+
3
+ shared_examples 'correct chromosome attribute values' do
4
+ its('chromosome') { should be_an_instance_of Genetica::Chromosome }
5
+ its('chromosome.size') { should be == subject.length }
6
+
7
+ it 'should have correct alleles' do
8
+ subject.chromosome.each do |allele|
9
+ subject.alleles.include?(allele).should be_true
10
+ end
11
+ end
12
+ end
13
+
14
+ describe Genetica::ChromosomeBuilder do
15
+ subject { described_class.new }
16
+
17
+ describe '#chromosome' do
18
+ context 'build chromosome with default values' do
19
+ it_behaves_like 'correct chromosome attribute values'
20
+ end
21
+
22
+ context 'build chromosome with custom values' do
23
+ let(:length) { 120 }
24
+ let(:alleles) { ('a'..'z').to_a }
25
+
26
+ before do
27
+ subject.length = length
28
+ subject.alleles = alleles
29
+ end
30
+
31
+ it_behaves_like 'correct chromosome attribute values'
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,62 @@
1
+ require 'spec_helper'
2
+
3
+ class PopulationClass < Genetica::Population
4
+ def fitness(chromosome)
5
+ end
6
+ end
7
+
8
+ shared_examples 'correct population attribute values' do
9
+ its(:population_class) { should be == PopulationClass }
10
+ its(:population) { should be_an_instance_of PopulationClass }
11
+ its('population.size') { should be == subject.size }
12
+ its('population.elitism') { should be == subject.elitism }
13
+ its('population.crossover_method') { should be == subject.crossover_method }
14
+ its('population.crossover_probability') { should be == subject.crossover_probability }
15
+ its('population.mutation_probability') { should be == subject.mutation_probability }
16
+ its('population.first.size') { should be == subject.chromosome_length }
17
+ its('population.alleles') { should be == subject.chromosome_alleles }
18
+ end
19
+
20
+ describe Genetica::PopulationBuilder do
21
+ subject { described_class.new }
22
+
23
+ describe '#population' do
24
+ context 'assigned population class' do
25
+ before { subject.population_class = PopulationClass }
26
+
27
+ context 'build population with default values' do
28
+ it_behaves_like 'correct population attribute values'
29
+ end
30
+
31
+ context 'build population with custom values' do
32
+ let(:size) { 12 }
33
+ let(:elitism) { 2 }
34
+ let(:crossover_method) { :single_point_crossover }
35
+ let(:crossover_probability) { 1.0 }
36
+ let(:mutation_probability) { 0.5 }
37
+ let(:chromosome_length) { 7 }
38
+ let(:chromosome_alleles) { ['a', 'b', 'c'] }
39
+
40
+ before do
41
+ subject.size = size
42
+ subject.elitism = elitism
43
+ subject.crossover_method = :single_point_crossover
44
+ subject.crossover_probability = crossover_probability
45
+ subject.mutation_probability = mutation_probability
46
+ subject.chromosome_length = chromosome_length
47
+ subject.chromosome_alleles = chromosome_alleles
48
+ end
49
+
50
+ it_behaves_like 'correct population attribute values'
51
+ end
52
+ end
53
+
54
+ context 'not assigned population class' do
55
+ it 'should raise an error when trying to build population' do
56
+ expect do
57
+ subject.population
58
+ end.to raise_error(described_class::PopulationClassError)
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,3 @@
1
+ $: << File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+
3
+ require 'genetica'
metadata CHANGED
@@ -1,17 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: genetica
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1.beta.2
5
- prerelease: 6
4
+ version: 0.0.1
6
5
  platform: ruby
7
6
  authors:
8
7
  - José Francisco Calvo
9
8
  autorequire:
10
9
  bindir: bin
11
10
  cert_chain: []
12
- date: 2011-11-23 00:00:00.000000000 +01:00
13
- default_executable:
14
- dependencies: []
11
+ date: 2015-03-22 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rspec
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '3.0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '3.0'
15
27
  description: Genetica is a library to create and use Genetics Algorithms with Ruby.
16
28
  email: josefranciscocalvo@gmail.com
17
29
  executables: []
@@ -23,29 +35,33 @@ files:
23
35
  - lib/genetica/chromosome_builder.rb
24
36
  - lib/genetica/population.rb
25
37
  - lib/genetica/population_builder.rb
26
- has_rdoc: true
38
+ - spec/chromosome_builder_spec.rb
39
+ - spec/population_builder_spec.rb
40
+ - spec/spec_helper.rb
27
41
  homepage: http://dev.monsterzen.com/projects/genetica.html
28
42
  licenses: []
43
+ metadata: {}
29
44
  post_install_message:
30
45
  rdoc_options: []
31
46
  require_paths:
32
47
  - lib
33
48
  required_ruby_version: !ruby/object:Gem::Requirement
34
- none: false
35
49
  requirements:
36
- - - ! '>='
50
+ - - ">="
37
51
  - !ruby/object:Gem::Version
38
- version: '0'
52
+ version: 2.0.0
39
53
  required_rubygems_version: !ruby/object:Gem::Requirement
40
- none: false
41
54
  requirements:
42
- - - ! '>'
55
+ - - ">="
43
56
  - !ruby/object:Gem::Version
44
- version: 1.3.1
57
+ version: '0'
45
58
  requirements: []
46
59
  rubyforge_project:
47
- rubygems_version: 1.6.2
60
+ rubygems_version: 2.4.5
48
61
  signing_key:
49
- specification_version: 3
62
+ specification_version: 4
50
63
  summary: The Ruby Genetic Algorithms Gem.
51
- test_files: []
64
+ test_files:
65
+ - spec/chromosome_builder_spec.rb
66
+ - spec/population_builder_spec.rb
67
+ - spec/spec_helper.rb