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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 43f55266a4ef8f115db03cc3dd2f075ce4189110
4
- data.tar.gz: e66bcb2525067d93ae87c8c3355c5a1f32030d05
3
+ metadata.gz: 0da75754d1ffbe965685e6298e01c2e121d7f2d9
4
+ data.tar.gz: 518b5ad7c766dd68d25942b28aa94dab2df0958f
5
5
  SHA512:
6
- metadata.gz: 5e9fda825a2389dde2ef427cbd2d8d283e8d03ca42227d8266a99244d01799d782a2ca9cd51615cbcf4687e01796c891125951e8654c549c40b5485c7be5d391
7
- data.tar.gz: 18c4d946acf6c631db4784d10177ec01762f8a16524bbe794b1cdf97fb62002680c251fd939fa9c3a072182729555fb163055527c11ffd4e8a10d498aaf15880
6
+ metadata.gz: e70e9007decd9653104efc8f6e28f2d8f7a4495588e2def2a1fa31537cb54b31cbf588758ccfd9ddc58556819d0f3b69e20c3cd2de436329785212004b695183
7
+ data.tar.gz: 8757fab931278ad147efa01cb8c1440431c9c3af430f0c9b13e7a2005ee9bdf61215eb6ad3d79c93c479e2b3702224cb5799f8d77c81146c155592251f81547f
@@ -5,7 +5,7 @@ class Candidate
5
5
  attr_accessor :dna, :fitness
6
6
 
7
7
  # Simple initialization of candidate object.
8
- def initialize
9
- @dna = []
8
+ def initialize(dna=[])
9
+ @dna = dna
10
10
  end
11
- end
11
+ end
@@ -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
@@ -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 averages
65
- # out their DNA per dimension, producing new offspring equal to the
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
- child = Candidate.new
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
@@ -1,5 +1,6 @@
1
1
  require 'evopop/population'
2
2
  require 'evopop/candidate'
3
+ require 'evopop/crossover'
3
4
 
4
5
  class Evopop
5
6
 
@@ -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
- def test_cross_over
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.2
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: