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.
- checksums.yaml +7 -0
- data/lib/genetica/chromosome.rb +28 -13
- data/lib/genetica/chromosome_builder.rb +12 -10
- data/lib/genetica/population.rb +59 -63
- data/lib/genetica/population_builder.rb +39 -28
- data/spec/chromosome_builder_spec.rb +34 -0
- data/spec/population_builder_spec.rb +62 -0
- data/spec/spec_helper.rb +3 -0
- metadata +31 -15
checksums.yaml
ADDED
@@ -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
|
data/lib/genetica/chromosome.rb
CHANGED
@@ -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
|
-
|
12
|
+
def single_point_crossover(crossover_probability, chromosome)
|
13
|
+
locus = rand(chromosome.size) + 1
|
5
14
|
|
6
|
-
|
7
|
-
|
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
|
11
|
-
|
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
|
-
|
15
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
9
|
-
@length = 8
|
10
|
-
@alleles = [0, 1]
|
6
|
+
set_default_chromosome_attributes
|
11
7
|
end
|
12
|
-
|
8
|
+
|
13
9
|
def chromosome
|
14
|
-
|
15
|
-
|
16
|
-
|
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
|
data/lib/genetica/population.rb
CHANGED
@@ -1,113 +1,109 @@
|
|
1
1
|
module Genetica
|
2
|
-
class Population
|
3
|
-
|
4
|
-
|
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
|
-
|
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
|
-
@
|
17
|
+
@best_chromosome ||= at(population_fitness.index(best_fitness))
|
20
18
|
end
|
21
19
|
|
22
|
-
def best_chromosomes(quantity=1)
|
23
|
-
|
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.
|
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(:+) /
|
35
|
+
@average_fitness ||= population_fitness.inject(:+) / population_fitness.size.to_f
|
36
36
|
end
|
37
37
|
|
38
38
|
def population_fitness
|
39
|
-
@
|
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
|
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 +=
|
48
|
+
population += best_chromosomes(quantity = elitism) if elitism > 0
|
75
49
|
|
76
|
-
while 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 =
|
82
|
-
chromosome_b =
|
55
|
+
chromosome_a = fitness_proportionate_selection
|
56
|
+
chromosome_b = fitness_proportionate_selection
|
83
57
|
|
84
58
|
# 2. Crossover Step
|
85
|
-
|
86
|
-
|
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!
|
95
|
-
offspring_b.mutate!
|
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
|
71
|
+
population.delete_at rand population.size if size.odd?
|
103
72
|
|
104
73
|
# Replacing chromosome population with the new one
|
105
|
-
|
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
|
-
|
7
|
-
|
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
|
-
|
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
|
-
@
|
21
|
-
|
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
|
27
|
-
|
28
|
-
|
29
|
-
|
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
|
data/spec/spec_helper.rb
ADDED
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
|
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:
|
13
|
-
|
14
|
-
|
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
|
-
|
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:
|
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:
|
57
|
+
version: '0'
|
45
58
|
requirements: []
|
46
59
|
rubyforge_project:
|
47
|
-
rubygems_version:
|
60
|
+
rubygems_version: 2.4.5
|
48
61
|
signing_key:
|
49
|
-
specification_version:
|
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
|