genetica 0.0.1.beta.2 → 0.0.1

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