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