evopop 0.0.2 → 0.0.3
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 +4 -4
- data/lib/evopop/candidate.rb +3 -3
- data/lib/evopop/crossover.rb +45 -0
- data/lib/evopop/population.rb +20 -9
- data/lib/evopop.rb +1 -0
- data/test/test_population.rb +36 -1
- metadata +2 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0da75754d1ffbe965685e6298e01c2e121d7f2d9
|
4
|
+
data.tar.gz: 518b5ad7c766dd68d25942b28aa94dab2df0958f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e70e9007decd9653104efc8f6e28f2d8f7a4495588e2def2a1fa31537cb54b31cbf588758ccfd9ddc58556819d0f3b69e20c3cd2de436329785212004b695183
|
7
|
+
data.tar.gz: 8757fab931278ad147efa01cb8c1440431c9c3af430f0c9b13e7a2005ee9bdf61215eb6ad3d79c93c479e2b3702224cb5799f8d77c81146c155592251f81547f
|
data/lib/evopop/candidate.rb
CHANGED
@@ -0,0 +1,45 @@
|
|
1
|
+
# Represents a collection of well known crossover functions.
|
2
|
+
#
|
3
|
+
module Crossover
|
4
|
+
|
5
|
+
# Perform 1 point crossover for a pair of candidates at the ordinal.
|
6
|
+
# http://en.wikipedia.org/wiki/Crossover_(genetic_algorithm)#One-point_crossover
|
7
|
+
def self.one_point(candidates, params)
|
8
|
+
ordinal = params[:ordinal]
|
9
|
+
|
10
|
+
# Compose the dna of the first child from the first chunk of the
|
11
|
+
# first candidate and the second chunk of the second candidate
|
12
|
+
dna0_left = candidates[0].dna.take(ordinal)
|
13
|
+
dna1_right = candidates[1].dna.drop(ordinal)
|
14
|
+
|
15
|
+
# Compose the dna of the second child from the first chunk of the
|
16
|
+
# first candidate and the second chunk of the second candidate
|
17
|
+
dna1_left = candidates[1].dna.take(ordinal)
|
18
|
+
dna0_right = candidates[0].dna.drop(ordinal)
|
19
|
+
|
20
|
+
# Initialize and assign DNA to children.
|
21
|
+
children = [Candidate.new(dna = dna0_left + dna1_right),
|
22
|
+
Candidate.new(dna = dna1_left + dna0_right)]
|
23
|
+
|
24
|
+
return children
|
25
|
+
end
|
26
|
+
|
27
|
+
# Perform n_point crossover for a pair of candidates. Will output two children from the n_point crossover.
|
28
|
+
#
|
29
|
+
# Example:
|
30
|
+
# n_point
|
31
|
+
def self.n_point(candidates, params)
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.average(candidates, params)
|
36
|
+
child = Candidate.new
|
37
|
+
dna_length = candidates[0].dna.length
|
38
|
+
(0...dna_length).each { |j|
|
39
|
+
child.dna << (candidates[0].dna[j] + candidates[1].dna[j])/2.0 # Initialize the dna of the child with the average of the parents' dna.
|
40
|
+
}
|
41
|
+
|
42
|
+
return [child]
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
data/lib/evopop/population.rb
CHANGED
@@ -10,7 +10,7 @@
|
|
10
10
|
# population.crossover
|
11
11
|
# population.mutate
|
12
12
|
class Population
|
13
|
-
attr_accessor :candidates, :population_size, :max_generations, :initial_range_min, :initial_range_max, :mutation_range_min, :mutation_range_max, :mutation_num, :fitness_function, :dna_len, :average_fitness
|
13
|
+
attr_accessor :candidates, :population_size, :max_generations, :crossover_function, :crossover_params, :initial_range_min, :initial_range_max, :mutation_range_min, :mutation_range_max, :mutation_num, :fitness_function, :dna_len, :average_fitness
|
14
14
|
|
15
15
|
# Initializes the attributes with default values. This is not guaranteed
|
16
16
|
# to reach maxima.
|
@@ -24,7 +24,9 @@ class Population
|
|
24
24
|
@mutation_range_max = 10
|
25
25
|
@mutation_num = (0.10*@population_size).to_i
|
26
26
|
@dna_len = 1
|
27
|
+
@crossover_params = {:ordinal => (@dna_len/2)}
|
27
28
|
|
29
|
+
@crossover_function = Crossover.method(:one_point)
|
28
30
|
@fitness_function = Proc.new { |dna|
|
29
31
|
Math.sin(dna[0])
|
30
32
|
}
|
@@ -61,22 +63,31 @@ class Population
|
|
61
63
|
end
|
62
64
|
|
63
65
|
# Performs simple mechanism of crossover - in this case picks two
|
64
|
-
# random candidates in from a top percentile of the population and
|
65
|
-
#
|
66
|
+
# random candidates in from a top percentile of the population and
|
67
|
+
# performs one point crossover, producing new offspring equal to the
|
66
68
|
# population size attribute.
|
67
69
|
def crossover
|
70
|
+
# Define the candidates that can have children.
|
68
71
|
@candidates = @candidates.take((@population_size*0.75).to_i)
|
69
72
|
|
70
73
|
new_generation = Array.new
|
74
|
+
|
71
75
|
(0...@population_size).each {|i|
|
76
|
+
# For each of the top 75% of the population take 2
|
72
77
|
couple = @candidates.sample(2)
|
73
|
-
|
74
|
-
(0...@dna_len).each {|j|
|
75
|
-
child.dna << (couple[0].dna[j] + couple[1].dna[j])/2.0 # Initialize the dna of the child with the average of the parents' dna.
|
76
|
-
}
|
77
|
-
new_generation << child
|
78
|
-
}
|
78
|
+
params = @crossover_params
|
79
79
|
|
80
|
+
children = @crossover_function.call(couple, params)
|
81
|
+
|
82
|
+
new_generation = new_generation + children
|
83
|
+
|
84
|
+
# When we go above set population_size, take the first population_size
|
85
|
+
# candidates, ignore the rest.
|
86
|
+
if new_generation.length >= self.population_size
|
87
|
+
new_generation = new_generation.take(self.population_size)
|
88
|
+
break
|
89
|
+
end
|
90
|
+
}
|
80
91
|
@candidates = new_generation
|
81
92
|
end
|
82
93
|
|
data/lib/evopop.rb
CHANGED
data/test/test_population.rb
CHANGED
@@ -14,6 +14,8 @@ class PopulationTest < Test::Unit::TestCase
|
|
14
14
|
population.mutation_range_min = -100.0
|
15
15
|
population.mutation_range_max = 100.0
|
16
16
|
population.mutation_num = 10
|
17
|
+
population.crossover_params = {:ordinal => (population.dna_len/2)}
|
18
|
+
population.crossover_function = Crossover.method(:one_point)
|
17
19
|
population.fitness_function = Proc.new { |dna|
|
18
20
|
Math.sin(dna[0]) + Math.cos(dna[1])
|
19
21
|
}
|
@@ -38,6 +40,10 @@ class PopulationTest < Test::Unit::TestCase
|
|
38
40
|
}
|
39
41
|
end
|
40
42
|
|
43
|
+
# Simple test of the training function. Ensure that when training
|
44
|
+
# finishes the fitness of the ith element of the population is
|
45
|
+
# less than or equal to the i-1th element of the population. I.e.
|
46
|
+
# fitness is becoming greater over the iteration of the popoulation.
|
41
47
|
def test_train
|
42
48
|
# Arrange: Initialize the population
|
43
49
|
population = initialize_population
|
@@ -55,6 +61,8 @@ class PopulationTest < Test::Unit::TestCase
|
|
55
61
|
}
|
56
62
|
end
|
57
63
|
|
64
|
+
# Simple test to ensure that only the exact number of candidates in the
|
65
|
+
# population are mutated.
|
58
66
|
def test_mutation
|
59
67
|
# Arrange: Initialize the population
|
60
68
|
population = initialize_population
|
@@ -76,10 +84,37 @@ class PopulationTest < Test::Unit::TestCase
|
|
76
84
|
assert_equal(population.mutation_num, counter)
|
77
85
|
end
|
78
86
|
|
79
|
-
|
87
|
+
# Simple
|
88
|
+
def test_one_point_crossover
|
80
89
|
# Arrange: Initialize the population
|
81
90
|
population = initialize_population
|
91
|
+
|
92
|
+
# Act: Train and corssover the population a number of times
|
93
|
+
5.times {
|
94
|
+
population.train
|
95
|
+
population.crossover
|
96
|
+
}
|
82
97
|
|
98
|
+
# Assert: The initial average fitness is less than what occurs after 100 generations.
|
99
|
+
# This is to ensure that over generations the average fitness does indeed go up, given
|
100
|
+
# no mutation.
|
101
|
+
assert_equal(true, population.average_fitness[0] < population.average_fitness[population.average_fitness.length-1])
|
83
102
|
end
|
84
103
|
|
104
|
+
def test_average_crossover
|
105
|
+
# Arrange: Initialize the population
|
106
|
+
population = initialize_population
|
107
|
+
population.crossover_function = Crossover.method(:average)
|
108
|
+
|
109
|
+
# Act: Train and corssover the population a number of times
|
110
|
+
100.times {
|
111
|
+
population.train
|
112
|
+
population.crossover
|
113
|
+
}
|
114
|
+
|
115
|
+
# Assert: The initial average fitness is less than what occurs after 100 generations.
|
116
|
+
# This is to ensure that over generations the average fitness does indeed go up, given
|
117
|
+
# no mutation.
|
118
|
+
assert_equal(true, population.average_fitness[0] < population.average_fitness[population.average_fitness.length-1])
|
119
|
+
end
|
85
120
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: evopop
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Elvin Lucero
|
@@ -19,6 +19,7 @@ files:
|
|
19
19
|
- lib/evopop.rb
|
20
20
|
- lib/evopop/population.rb
|
21
21
|
- lib/evopop/candidate.rb
|
22
|
+
- lib/evopop/crossover.rb
|
22
23
|
- test/test_population.rb
|
23
24
|
homepage: https://github.com/elvinlucero/evopop
|
24
25
|
licenses:
|