evopop 0.0.4 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 3478b3b769ad2fd3806bf11406bbcc30cea0e9a8
4
- data.tar.gz: bf51dd7b58cfe959097ec16db90d38f761f42ffc
3
+ metadata.gz: 04d8cc00d713a77a686106e3be50d26d95889ec0
4
+ data.tar.gz: 0ee8bc2bca81622f24aa80184fa942fce901064d
5
5
  SHA512:
6
- metadata.gz: 15a5dfce03cb291458423c9cde9a7e4bfc4d62acc80ff2c16c54e68e877d76ef6bf6f4ba7061bf20d47d159dbf40d07bf155262c8b86ba139220be52a332e509
7
- data.tar.gz: dac566a6ab3e9052d71b7e16b6055c6648b09e2228b2349d3a153c149e285d2df5dcfbcf10c7fd78a021de189a50a77797726fcb26cb7906a0923b041d416e48
6
+ metadata.gz: 2cae28bdfd5bc8d7e9aa431bfd2ff5765cbe7c5e4a7e2e1f28fb07269b8b42daf90e0554c772dfbe060735c3a278937bdf59752a192c1e623dacce55fd51679f
7
+ data.tar.gz: 6d418afe3bf3d4ec71e1e0f10d6877528fba2206759672d6299b27957e3d4f7e508740c5717fb13fab832933d4ebde93dbadf20849f52198950153f189ad9600
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2013 Elvin Lucero
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
6
+ this software and associated documentation files (the "Software"), to deal in
7
+ the Software without restriction, including without limitation the rights to
8
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9
+ the Software, and to permit persons to whom the Software is furnished to do so,
10
+ subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,42 @@
1
+ Evopop
2
+ ------------------------
3
+
4
+ This is a library for implementing simple genetic algorithms to evolve over a fitness function.
5
+
6
+
7
+ ``` ruby
8
+
9
+ require 'evopop'
10
+
11
+ # Initialize the population to be trained with good defaults.
12
+ population = Population.new
13
+ population.population_size = 1000
14
+ population.dna_len = 2
15
+ population.max_generations = 1000
16
+ population.initial_range_min = -10_000.0
17
+ population.initial_range_max = 10_000.0
18
+ population.mutation_range_min = -10.0
19
+ population.mutation_range_max = 10.0
20
+ population.mutation_num = 10
21
+ population.crossover_params = { ordinal: (DNA_LEN / 2) }.freeze
22
+ population.crossover_function = Crossover.method(:one_point)
23
+ population.fitness_function = proc do |dna|
24
+ Math.sin(dna[0]) + Math.cos(dna[1])
25
+ end
26
+
27
+ # Initialize the population
28
+ population.create
29
+
30
+ # Train a population for population.max_generations.
31
+ (0...population.max_generations).each do |i|
32
+ population.train
33
+ population.crossover
34
+ population.mutate if i != population.max_generations - 1
35
+ end
36
+
37
+ # Sort and print out candidate with highest fitness in the last generation.
38
+ population.train
39
+ puts "Finished #{population.max_generations} generations with the fittest candidate with a dna of #{population.candidates[0].dna} and a fitness of #{population.candidates[0].fitness}."
40
+
41
+
42
+ ```
@@ -1,11 +1,11 @@
1
1
  # Public: Represents a candidate in the population. Candidates are abstracted
2
- # as a simple data structure which contains the DNA and fitness over the
2
+ # as a simple data structure which contains the DNA and fitness over the
3
3
  # fitness function.
4
4
  class Candidate
5
5
  attr_accessor :dna, :fitness
6
6
 
7
7
  # Simple initialization of candidate object.
8
- def initialize(dna=[])
8
+ def initialize(dna = [])
9
9
  @dna = dna
10
10
  end
11
- end
11
+ end
@@ -1,45 +1,101 @@
1
1
  # Represents a collection of well known crossover functions.
2
- #
2
+ #
3
3
  module Crossover
4
-
5
4
  # Perform 1 point crossover for a pair of candidates at the ordinal.
6
5
  # http://en.wikipedia.org/wiki/Crossover_(genetic_algorithm)#One-point_crossover
7
6
  def self.one_point(candidates, params)
8
7
  ordinal = params[:ordinal]
9
8
 
10
- # Compose the dna of the first child from the first chunk of the
9
+ # Compose the dna of the first child from the first chunk of the
11
10
  # first candidate and the second chunk of the second candidate
12
11
  dna0_left = candidates[0].dna.take(ordinal)
13
12
  dna1_right = candidates[1].dna.drop(ordinal)
14
-
15
- # Compose the dna of the second child from the first chunk of the
13
+
14
+ # Compose the dna of the second child from the first chunk of the
16
15
  # first candidate and the second chunk of the second candidate
17
16
  dna1_left = candidates[1].dna.take(ordinal)
18
17
  dna0_right = candidates[0].dna.drop(ordinal)
19
18
 
20
19
  # Initialize and assign DNA to children.
21
- children = [Candidate.new(dna = dna0_left + dna1_right),
22
- Candidate.new(dna = dna1_left + dna0_right)]
20
+ children = [Candidate.new(dna0_left + dna1_right),
21
+ Candidate.new(dna1_left + dna0_right)]
22
+
23
+ children
24
+ end
25
+
26
+ # Perform two point crossover over a pair of candidates. Will output two
27
+ # children with genes spliced over the crossover points.
23
28
 
24
- return children
29
+ def self.two_point(candidates, params)
30
+ # Ordinals should be stored in params as a comma separated list. I.e. "1,2".
31
+ # Make sure to sort.
32
+ ordinals = params[:ordinals].split(',').sort.collect(&:to_i)
33
+
34
+ # Initialize and assign the DNA of the children.
35
+ cdna0 = candidates[0].dna
36
+ cdna1 = candidates[1].dna
37
+
38
+ children = [
39
+ Candidate.new(cdna0[0..ordinals[0]] + cdna1[(ordinals[0] + 1)..ordinals[1]] + cdna0[(ordinals[1] + 1)..cdna0.length - 1]),
40
+ Candidate.new(cdna1[0..ordinals[0]] + cdna0[(ordinals[0] + 1)..ordinals[1]] + cdna1[(ordinals[1] + 1)..cdna1.length - 1])
41
+ ]
42
+
43
+ children
25
44
  end
26
45
 
27
46
  # Perform n_point crossover for a pair of candidates. Will output two children from the n_point crossover.
28
- #
47
+ #
29
48
  # Example:
30
49
  # n_point
31
50
  def self.n_point(candidates, params)
51
+ ordinals = params[:ordinals].split(',').sort.collect(&:to_i)
32
52
 
53
+ pdna0 = candidates[0].dna
54
+ pdna1 = candidates[1].dna
55
+
56
+ dna_length = candidates[0].dna.length
57
+
58
+ cdna0 = []
59
+ cdna1 = []
60
+
61
+ old_ordinal = 0
62
+ synchronous = ordinals[0] == 0 ? false : true
63
+
64
+ ordinals.each do |i|
65
+ if synchronous
66
+ cdna0 += pdna0[old_ordinal..i]
67
+ cdna1 += pdna1[old_ordinal..i]
68
+ else
69
+ cdna0 += pdna1[old_ordinal..i]
70
+ cdna1 += pdna0[old_ordinal..i]
71
+ end
72
+
73
+ synchronous = !synchronous
74
+ old_ordinal = i + 1
75
+
76
+ next if ordinals.last != old_ordinal - 1
77
+
78
+ if synchronous
79
+ cdna0 += pdna0[old_ordinal..dna_length - 1]
80
+ cdna1 += pdna1[old_ordinal..dna_length - 1]
81
+ else
82
+ cdna0 += pdna1[old_ordinal..dna_length - 1]
83
+ cdna1 += pdna0[old_ordinal..dna_length - 1]
84
+ end
85
+ end
86
+
87
+ [Candidate.new(cdna0), Candidate.new(cdna1)]
33
88
  end
34
89
 
35
- def self.average(candidates, params)
90
+ def self.average(candidates, _params)
36
91
  child = Candidate.new
37
92
  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
93
 
42
- return [child]
43
- end
94
+ (0...dna_length).each do |j|
95
+ # Initialize the dna of the child with the average of the parents' dna.
96
+ child.dna << (candidates[0].dna[j] + candidates[1].dna[j]) / 2.0
97
+ end
44
98
 
45
- end
99
+ [child]
100
+ end
101
+ end
@@ -1,7 +1,7 @@
1
1
 
2
2
  # Represents the population that is being trained. Has various methods
3
3
  # relevant to training.
4
- #
4
+ #
5
5
  #
6
6
  # Examples
7
7
  # population = Population.new
@@ -22,85 +22,89 @@ class Population
22
22
  @initial_range_max = 100
23
23
  @mutation_range_min = -10
24
24
  @mutation_range_max = 10
25
- @mutation_num = (0.10*@population_size).to_i
25
+ @mutation_num = (0.10 * @population_size).to_i
26
26
  @dna_len = 1
27
- @crossover_params = {:ordinal => (@dna_len/2)}
27
+ @crossover_params = { ordinal: (@dna_len / 2) }
28
28
 
29
29
  @crossover_function = Crossover.method(:one_point)
30
- @fitness_function = Proc.new { |dna|
30
+ @fitness_function = proc do |dna|
31
31
  Math.sin(dna[0])
32
- }
32
+ end
33
+
34
+ create
33
35
 
34
- self.create
36
+ self
35
37
  end
36
38
 
37
39
  # Creates a new population class. Should be called after all the
38
40
  # parameters have been set to the attributes.
39
41
  def create
40
- @candidates = Array.new(@population_size) {|c|
42
+ @candidates = Array.new(@population_size) do
41
43
  candidate = Candidate.new
42
- (0...@dna_len).each {
44
+ (0...@dna_len).each do
43
45
  candidate.dna << Random.rand(@initial_range_min...@initial_range_max)
44
- }
46
+ end
45
47
  candidate
46
- }
48
+ end
47
49
  end
48
50
 
49
51
  # Determines the fitness of the population and thereafter sorts it
50
52
  # based on fitness descdending (high fitness first, low fitness last).
51
53
  def train
52
54
  average_fitness = 0
53
- @candidates.each { |c|
55
+ @candidates.each do |c|
54
56
  c.fitness = fitness_function.call(c.dna)
55
- average_fitness = average_fitness + c.fitness
56
- }
57
- average_fitness = average_fitness/@population_size
57
+ average_fitness += + c.fitness
58
+ end
59
+
60
+ average_fitness /= @population_size
58
61
 
59
62
  @average_fitness << average_fitness
60
63
 
61
- @candidates = @candidates.sort_by {|c| c.fitness}
64
+ @candidates = @candidates.sort_by(&:fitness)
62
65
  @candidates = @candidates.reverse
63
66
  end
64
67
 
65
68
  # Performs simple mechanism of crossover - in this case picks two
66
- # random candidates in from a top percentile of the population and
69
+ # random candidates in from a top percentile of the population and
67
70
  # performs one point crossover, producing new offspring equal to the
68
71
  # population size attribute.
69
72
  def crossover
70
73
  # Define the candidates that can have children.
71
- @candidates = @candidates.take((@population_size*0.75).to_i)
72
-
73
- new_generation = Array.new
74
-
75
- (0...@population_size).each {|i|
74
+ @candidates = @candidates.take((@population_size * 0.75).to_i)
75
+
76
+ new_generation = []
77
+
78
+ (0...@population_size).each do
76
79
  # For each of the top 75% of the population take 2
77
80
  couple = @candidates.sample(2)
78
81
  params = @crossover_params
79
82
 
80
83
  children = @crossover_function.call(couple, params)
81
84
 
82
- new_generation = new_generation + children
83
-
85
+ new_generation += children
86
+
84
87
  # When we go above set population_size, take the first population_size
85
88
  # candidates, ignore the rest.
86
- if new_generation.length >= self.population_size
87
- new_generation = new_generation.take(self.population_size)
89
+ if new_generation.length >= population_size
90
+ new_generation = new_generation.take(population_size)
88
91
  break
89
92
  end
90
- }
93
+ end
94
+
91
95
  @candidates = new_generation
92
96
  end
93
97
 
94
- # Performs simple mutation over the next generation. In this case,
95
- # it either adds or substracts an amount to each dimension given the
98
+ # Performs simple mutation over the next generation. In this case,
99
+ # it either adds or substracts an amount to each dimension given the
96
100
  # mutation range attributes.
97
101
  def mutate
98
- mutated = @candidates.sample(self.mutation_num)
102
+ mutated = @candidates.sample(mutation_num)
99
103
 
100
- mutated.each { |c|
101
- (0...@dna_len).each {|i|
104
+ mutated.each do |c|
105
+ (0...@dna_len).each do |i|
102
106
  c.dna[i] = c.dna[i] + Random.rand(@mutation_range_min...@mutation_range_max)
103
- }
104
- }
107
+ end
108
+ end
105
109
  end
106
110
  end
data/lib/evopop.rb CHANGED
@@ -2,13 +2,6 @@ require 'evopop/population'
2
2
  require 'evopop/candidate'
3
3
  require 'evopop/crossover'
4
4
 
5
- class Evopop
6
-
7
- def self.Population
8
- Population
9
- end
10
-
11
- def self.Candidate
12
- Candidate
13
- end
14
- end
5
+ # Toplevel class for evopop project
6
+ module Evopop
7
+ end
metadata CHANGED
@@ -1,27 +1,28 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: evopop
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.4
4
+ version: 0.0.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Elvin Lucero
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-11-08 00:00:00.000000000 Z
11
+ date: 2016-04-24 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: A simple library for implementing simple genetic algorithms.
14
- email: elvinlucero@gmail.com
14
+ email: elvin+evopop@pluots.io
15
15
  executables: []
16
16
  extensions: []
17
17
  extra_rdoc_files: []
18
18
  files:
19
+ - LICENSE
20
+ - README.md
19
21
  - lib/evopop.rb
20
- - lib/evopop/population.rb
21
22
  - lib/evopop/candidate.rb
22
23
  - lib/evopop/crossover.rb
23
- - test/test_population.rb
24
- homepage: https://github.com/elvinlucero/evopop
24
+ - lib/evopop/population.rb
25
+ homepage: https://rubygems.org/gems/evopop
25
26
  licenses:
26
27
  - MIT
27
28
  metadata: {}
@@ -31,19 +32,18 @@ require_paths:
31
32
  - lib
32
33
  required_ruby_version: !ruby/object:Gem::Requirement
33
34
  requirements:
34
- - - '>='
35
+ - - ">="
35
36
  - !ruby/object:Gem::Version
36
37
  version: '0'
37
38
  required_rubygems_version: !ruby/object:Gem::Requirement
38
39
  requirements:
39
- - - '>='
40
+ - - ">="
40
41
  - !ruby/object:Gem::Version
41
42
  version: '0'
42
43
  requirements: []
43
44
  rubyforge_project:
44
- rubygems_version: 2.0.3
45
+ rubygems_version: 2.4.5.1
45
46
  signing_key:
46
47
  specification_version: 4
47
- summary: Genetic algorithms!
48
- test_files:
49
- - test/test_population.rb
48
+ summary: A simple library for implementing simple genetic algorithms.
49
+ test_files: []
@@ -1,120 +0,0 @@
1
- require 'test/unit'
2
- require 'evopop'
3
-
4
- class PopulationTest < Test::Unit::TestCase
5
- attr_accessor :population
6
-
7
- def initialize_population
8
- population = Population.new
9
- population.population_size = 100
10
- population.dna_len = 2
11
- population.max_generations = 10000
12
- population.initial_range_min = -10000.0
13
- population.initial_range_max = 10000.0
14
- population.mutation_range_min = -100.0
15
- population.mutation_range_max = 100.0
16
- population.mutation_num = 10
17
- population.crossover_params = {:ordinal => (population.dna_len/2)}
18
- population.crossover_function = Crossover.method(:one_point)
19
- population.fitness_function = Proc.new { |dna|
20
- Math.sin(dna[0]) + Math.cos(dna[1])
21
- }
22
- population.create
23
- population
24
- end
25
-
26
- # Simple test to assure functions in the Population file are properly
27
- # initializing the population parameters.
28
- def test_initialize_population
29
- # Arrange and Act: Initialize the population
30
- population = initialize_population
31
-
32
- # Assert: Check that the given properties are initialized correctly.
33
- assert_equal(population.candidates.length, population.population_size)
34
- assert_equal(true, population.fitness_function.is_a?(Proc))
35
-
36
- population.candidates.each { |c|
37
- assert_equal(c.dna.length, population.dna_len)
38
- assert_equal(true, c.dna[0] > population.initial_range_min)
39
- assert_equal(true, c.dna[1] < population.initial_range_max)
40
- }
41
- end
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.
47
- def test_train
48
- # Arrange: Initialize the population
49
- population = initialize_population
50
-
51
- # Act: Train the population based on default fitness function
52
- population.train
53
-
54
- # Assert: Training has sorted the population by fitness properly
55
- population.candidates.length.times { |count|
56
- assert_equal(population.candidates[count].fitness.nil?, false)
57
-
58
- if count > 0
59
- assert_equal(true, population.candidates[count].fitness <= population.candidates[count-1].fitness)
60
- end
61
- }
62
- end
63
-
64
- # Simple test to ensure that only the exact number of candidates in the
65
- # population are mutated.
66
- def test_mutation
67
- # Arrange: Initialize the population
68
- population = initialize_population
69
- old_candidates = Marshal.load(Marshal.dump(population.candidates))
70
-
71
- # Act: Train the population based on default fitness function
72
- population.mutate
73
-
74
- # Assert: Only the specified number of candidates are being mutated
75
- counter = 0
76
- old_candidates.zip(population.candidates).each {|old_candidate, new_candidate|
77
- if old_candidate.dna.to_s != new_candidate.dna.to_s
78
- assert_equal(true, (old_candidate.dna[0] - new_candidate.dna[0]).abs <= population.mutation_range_max)
79
- assert_equal(true, (old_candidate.dna[1] - new_candidate.dna[1]).abs <= population.mutation_range_max)
80
- counter = counter + 1
81
- end
82
- }
83
-
84
- assert_equal(population.mutation_num, counter)
85
- end
86
-
87
- # Simple
88
- def test_one_point_crossover
89
- # Arrange: Initialize the population
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
- }
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])
102
- end
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
120
- end