darwinning 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.
data/Gemfile ADDED
@@ -0,0 +1,12 @@
1
+ source "http://rubygems.org"
2
+
3
+ gemspec
4
+
5
+ group :test do
6
+ gem 'rspec', '~> 2.14.0'
7
+ end
8
+
9
+ group :development, :test do
10
+ gem 'pry'
11
+ gem 'pry-nav'
12
+ end
data/README.md CHANGED
@@ -27,9 +27,9 @@ class Triple < Darwinning::Organism
27
27
 
28
28
  @name = "Triple"
29
29
  @genes = [
30
- Darwinning::Gene.new("first digit", (0..9)),
31
- Darwinning::Gene.new("second digit", (0..9)),
32
- Darwinning::Gene.new("third digit", (0..9))
30
+ Darwinning::Gene.new(name: "first digit", value_range: (0..9)),
31
+ Darwinning::Gene.new(name: "second digit", value_range: (0..9)),
32
+ Darwinning::Gene.new(name: "third digit", value_range: (0..9))
33
33
  ]
34
34
 
35
35
  def fitness
@@ -38,7 +38,10 @@ class Triple < Darwinning::Organism
38
38
  end
39
39
  end
40
40
 
41
- p = Darwinning::Population.new(Triple, 10, 0, 0.1, 100)
41
+ p = Darwinning::Population.new(
42
+ organism: Triple, population_size: 10,
43
+ fitness_goal: 0, generations_limit: 100
44
+ )
42
45
  p.evolve!
43
46
 
44
47
  p.best_member.nice_print # prints the member representing the solution
@@ -57,22 +60,25 @@ class Cookie < Darwinning::Organism
57
60
 
58
61
  @name = "Chocolate Chip Cookie"
59
62
  @genes = [
60
- Darwinning::Gene.new("white sugar", (0..1), [], "cup"),
61
- Darwinning::Gene.new("brown sugar", (0..1), [], "cup"),
62
- Darwinning::Gene.new("flour", (0..3), [], "cup"),
63
- Darwinning::Gene.new("eggs", (0..3)),
64
- Darwinning::Gene.new("baking powder", (0..2), [], "teaspoon"),
65
- Darwinning::Gene.new("salt", (0..2), [], "teaspoon"),
66
- Darwinning::Gene.new("butter", (0..2), [], "cup"),
67
- Darwinning::Gene.new("vanilla extract", (0..2), [], "teaspoon"),
68
- Darwinning::Gene.new("chocolate chips", (0..20), [], "ounce"),
69
- Darwinning::Gene.new("oven temp", (300..400), [], "degrees F"),
70
- Darwinning::Gene.new("cook time", (5..20), [], "minute")
63
+ Darwinning::Gene.new(name: "white sugar", value_range: (0..1), units: "cup"),
64
+ Darwinning::Gene.new(name: "brown sugar", value_range: (0..1), units: "cup"),
65
+ Darwinning::Gene.new(name: "flour", value_range: (0..3), units: "cup"),
66
+ Darwinning::Gene.new(name: "eggs", value_range: (0..3)),
67
+ Darwinning::Gene.new(name: "baking powder", value_range: (0..2), units: "teaspoon"),
68
+ Darwinning::Gene.new(name: "salt", value_range: (0..2), units: "teaspoon"),
69
+ Darwinning::Gene.new(name: "butter", value_range: (0..2), units: "cup"),
70
+ Darwinning::Gene.new(name: "vanilla extract", value_range: (0..2), units: "teaspoon"),
71
+ Darwinning::Gene.new(name: "chocolate chips", value_range: (0..20), units: "ounce"),
72
+ Darwinning::Gene.new(name: "oven temp", value_range: (300..400), units: "degrees F"),
73
+ Darwinning::Gene.new(name: "cook time", value_range: (5..20), units: "minute")
71
74
  ]
72
75
 
73
76
  end
74
77
 
75
- p = Darwinning::Population.new(Cookie, 10, 5, 0.1, 100, true)
78
+ p = Darwinning::Population.new(
79
+ organism: Cookie, population_size: 10,
80
+ fitness_goal: 5, generations_limit: 100
81
+ )
76
82
 
77
83
  first_gen_ratings = [1.5, 4, 3, 3.5, 2, 1, 1.5, 3, 2.5, 0.5]
78
84
  p.set_members_fitness!(first_gen_ratings)
@@ -98,4 +104,8 @@ end
98
104
  ```
99
105
 
100
106
  ## Built by:
101
- * [Dave Schwantes](https://github.com/dorkrawk "dorkrawk")
107
+ * [Dave Schwantes](https://github.com/dorkrawk "dorkrawk")
108
+
109
+ ### With help from:
110
+ * [Cameron Dutro](https://github.com/camertron "camertron")
111
+ * [Maurizio Del Corno](https://github.com/druzn3k "druzn3k")
@@ -1,8 +1,9 @@
1
1
  require_relative 'darwinning/gene'
2
2
  require_relative 'darwinning/organism'
3
+ require_relative 'darwinning/evolution_types'
3
4
  require_relative 'darwinning/population'
4
5
  require_relative 'darwinning/config'
5
6
 
6
7
  module Darwinning
7
8
  extend Config
8
- end
9
+ end
@@ -3,4 +3,4 @@ module Darwinning
3
3
  # crossover mask?
4
4
  # ordered vs weighted fitness
5
5
  end
6
- end
6
+ end
@@ -0,0 +1,6 @@
1
+ module Darwinning
2
+ module EvolutionTypes
3
+ autoload :Mutation, 'darwinning/evolution_types/mutation'
4
+ autoload :Reproduction, 'darwinning/evolution_types/reproduction'
5
+ end
6
+ end
@@ -0,0 +1,41 @@
1
+ module Darwinning
2
+ module EvolutionTypes
3
+
4
+ class Mutation
5
+ attr_reader :mutation_rate
6
+
7
+ def initialize(options = {})
8
+ @mutation_rate = options.fetch(:mutation_rate, 0.0)
9
+ end
10
+
11
+ def evolve(members)
12
+ mutate(members)
13
+ end
14
+
15
+ def pairwise?
16
+ false
17
+ end
18
+
19
+ protected
20
+
21
+ def mutate(members)
22
+ members.map do |member|
23
+ if (0..100).to_a.sample < mutation_rate * 100
24
+ re_express_random_genotype(member)
25
+ else
26
+ member
27
+ end
28
+ end
29
+ end
30
+
31
+ # Selects a random genotype from the organism and re-expresses its gene
32
+ def re_express_random_genotype(member)
33
+ random_index = (0..member.genotypes.length - 1).to_a.sample
34
+ new_gene = member.genes[random_index].express
35
+ member.genotypes[random_index] = member.genes[random_index].express
36
+ member
37
+ end
38
+ end
39
+
40
+ end
41
+ end
@@ -0,0 +1,34 @@
1
+ module Darwinning
2
+ module EvolutionTypes
3
+
4
+ class Reproduction
5
+ def evolve(organism, m1, m2)
6
+ sexytimes(organism, m1, m2)
7
+ end
8
+
9
+ def pairwise?
10
+ true
11
+ end
12
+
13
+ protected
14
+
15
+ def sexytimes(organism, m1, m2)
16
+ genotypes1 = []
17
+ genotypes2 = []
18
+
19
+ m1.genotypes.zip(m2.genotypes).each do |g|
20
+ if m1.genotypes.index(g[0]) % 2 == 0
21
+ genotypes1 << g[0]
22
+ genotypes2 << g[1]
23
+ else
24
+ genotypes1 << g[1]
25
+ genotypes2 << g[0]
26
+ end
27
+ end
28
+
29
+ [organism.new(genotypes1), organism.new(genotypes2)]
30
+ end
31
+ end
32
+
33
+ end
34
+ end
@@ -1,21 +1,23 @@
1
1
  module Darwinning
2
- class Gene
3
- attr_accessor :name, :value, :value_range, :invalid_values, :units
4
2
 
5
- def initialize(name = "", value_range = [], invalid_values = [], units = "")
3
+ class Gene
4
+ attr_accessor :name, :value_range, :invalid_values, :units
5
+ attr_accessor :value
6
6
 
7
- @name = name
8
- @value_range = value_range.to_a
9
- @invalid_values = invalid_values.to_a
10
- @units = units
7
+ def initialize(options = {})
8
+ @name = options.fetch(:name, '')
9
+ @value_range = Array(options.fetch(:value_range, []))
10
+ @invalid_values = Array(options.fetch(:invalid_values, []))
11
+ @units = options.fetch(:units, '')
11
12
  end
12
13
 
13
14
  def express
14
- (@value_range - @invalid_values).sample
15
+ (value_range - invalid_values).sample
15
16
  end
16
17
 
17
18
  def is_valid_value?(value)
18
- @value_range.include?(value) and not @invalid_values.include?(value)
19
+ value_range.include?(value) && !invalid_values.include?(value)
19
20
  end
20
21
  end
21
- end
22
+
23
+ end
@@ -26,8 +26,8 @@ module ClassLevelInheritableAttributes
26
26
  end
27
27
 
28
28
  module Darwinning
29
- class Organism
30
29
 
30
+ class Organism
31
31
  include ClassLevelInheritableAttributes
32
32
  inheritable_attributes :genes, :name
33
33
  attr_accessor :genotypes, :fitness, :name, :genes
@@ -45,14 +45,8 @@ module Darwinning
45
45
  else
46
46
  @genotypes = genotypes
47
47
  end
48
- @fitness = -1
49
- end
50
48
 
51
- # Selects a random genotype from the organism and rexpresses its gene
52
- def mutate!
53
- random_index = (0..@genotypes.length-1).to_a.sample
54
- @genotypes[random_index] = self.class.genes[random_index].express
55
- self
49
+ @fitness = -1
56
50
  end
57
51
 
58
52
  def nice_print
@@ -69,4 +63,5 @@ module Darwinning
69
63
  self.class.genes
70
64
  end
71
65
  end
72
- end
66
+
67
+ end
@@ -1,23 +1,30 @@
1
1
  module Darwinning
2
2
 
3
3
  class Population
4
- attr_accessor :members, :mutation_rate, :generations_limit, :fitness_goal, :organism, :generation
5
-
6
- def initialize(organism, population_size, fitness_goal, mutation_rate = 0.0, generations_limit = 0, manual_fitness = false)
7
- @organism = organism
8
- @fitness_goal = fitness_goal
9
- @mutation_rate = mutation_rate
10
- @generations_limit = generations_limit
11
- @manual_fitness = manual_fitness
4
+ attr_accessor :members, :generations_limit, :fitness_goal
5
+ attr_accessor :organism, :generation, :population_size
6
+ attr_accessor :evolution_types
7
+
8
+ DEFAULT_EVOLUTION_TYPES = [
9
+ Darwinning::EvolutionTypes::Reproduction.new,
10
+ Darwinning::EvolutionTypes::Mutation.new(mutation_rate: 0.10)
11
+ ]
12
+
13
+ def initialize(options = {})
14
+ @organism = options.fetch(:organism)
15
+ @population_size = options.fetch(:population_size)
16
+ @fitness_goal = options.fetch(:fitness_goal)
17
+ @generations_limit = options.fetch(:generations_limit, 0)
18
+ @evolution_types = options.fetch(:evolution_types, DEFAULT_EVOLUTION_TYPES)
12
19
  @members = []
13
20
  @generation = 0 # initial population is generation 0
14
21
 
15
- build_population(population_size)
22
+ build_population(@population_size)
16
23
  end
17
24
 
18
25
  def build_population(population_size)
19
26
  population_size.times do |i|
20
- @members << @organism.new
27
+ @members << organism.new
21
28
  end
22
29
  end
23
30
 
@@ -27,32 +34,15 @@ module Darwinning
27
34
  end
28
35
  end
29
36
 
30
- def crossover(m1, m2)
31
- genotypes1 = []
32
- genotypes2 = []
33
-
34
- m1.genotypes.zip(m2.genotypes).each do |g|
35
- if m1.genotypes.index(g[0]) % 2 == 0
36
- genotypes1 << g[0]
37
- genotypes2 << g[1]
38
- else
39
- genotypes1 << g[1]
40
- genotypes2 << g[0]
41
- end
42
- end
43
-
44
- [@organism.new(genotypes1), @organism.new(genotypes2)]
45
- end
46
-
47
- def sexytimes(m1, m2)
48
- crossover(m1, m2)
49
- end
50
-
51
37
  def weighted_select(members)
52
38
  e = 0.01
53
39
  fitness_sum = members.inject(0) { |sum, m| sum + m.fitness }
54
40
 
55
- weighted_members = members.sort_by { |m| (m.fitness - @fitness_goal).abs }.map { |m| [m, fitness_sum / ((m.fitness - @fitness_goal).abs + e)] }
41
+ weighted_members = members.sort_by do |m|
42
+ (m.fitness - fitness_goal).abs
43
+ end.map do |m|
44
+ [m, fitness_sum / ((m.fitness - fitness_goal).abs + e)]
45
+ end
56
46
 
57
47
  weight_sum = weighted_members.inject(0) { |sum, m| sum + m[1] }
58
48
  pick = (0..weight_sum).to_a.sample
@@ -65,56 +55,68 @@ module Darwinning
65
55
  pick_sum += selected_member[1]
66
56
  end
67
57
 
68
- selected_member[0]
69
- end
70
-
71
- def mutate!
72
- @members.map! { |m|
73
- if (0..100).to_a.sample < @mutation_rate*100
74
- m.mutate!
75
- else
76
- m
77
- end
78
- }
58
+ selected_member.first
79
59
  end
80
60
 
81
61
  def set_members_fitness!(fitness_values)
82
- @members.to_enum.each_with_index { |m, i| m.fitness = fitness_values[i] }
62
+ members.to_enum.each_with_index { |m, i| m.fitness = fitness_values[i] }
83
63
  end
84
64
 
85
65
  def make_next_generation!
86
- temp_members = @members
66
+ temp_members = members
87
67
  used_members = []
88
68
  new_members = []
89
69
 
90
- until new_members.length == @members.length/2
91
- m1 = weighted_select(@members - used_members)
70
+ until new_members.length == members.length / 2
71
+ m1 = weighted_select(members - used_members)
92
72
  used_members << m1
93
- m2 = weighted_select(@members - used_members)
73
+ m2 = weighted_select(members - used_members)
94
74
  used_members << m2
95
75
 
96
- new_members << crossover(m1,m2)
76
+ new_members << apply_pairwise_evolutions(organism, m1, m2)
97
77
  end
98
78
 
99
79
  new_members.flatten!
80
+ @members = apply_non_pairwise_evolutions(new_members)
81
+ @generation += 1
82
+ end
100
83
 
101
- @members = new_members
84
+ def apply_pairwise_evolutions(organism, m1, m2)
85
+ evolution_types.inject([m1, m2]) do |ret, evolution_type|
86
+ if evolution_type.pairwise?
87
+ evolution_type.evolve(organism, *ret)
88
+ else
89
+ ret
90
+ end
91
+ end
92
+ end
102
93
 
103
- mutate!
104
- @generation += 1
94
+ def apply_non_pairwise_evolutions(members)
95
+ evolution_types.inject(members) do |ret, evolution_type|
96
+ if evolution_type.pairwise?
97
+ ret
98
+ else
99
+ evolution_type.evolve(ret)
100
+ end
101
+ end
105
102
  end
106
103
 
107
104
  def evolution_over?
108
105
  # check if the fiteness goal or generation limit has been met
109
- @generation == @generations_limit or best_member.fitness == @fitness_goal
106
+ if generations_limit > 0
107
+ generation == generations_limit || best_member.fitness == fitness_goal
108
+ else
109
+ generation == generations_limit || best_member.fitness == fitness_goal
110
+ end
110
111
  end
111
112
 
112
113
  def best_member
113
- @members.sort_by { |m| m.fitness }[0]
114
+ @members.sort_by { |m| m.fitness }.first
114
115
  end
115
116
 
116
117
  def size
117
118
  @members.length
118
119
  end
119
120
  end
120
- end
121
+
122
+ end
@@ -1,3 +1,3 @@
1
1
  module Darwinning
2
- VERSION = "0.0.2"
3
- end
2
+ VERSION = "0.0.3"
3
+ end
@@ -0,0 +1,13 @@
1
+ class Triple < Darwinning::Organism
2
+ @name = "Triple"
3
+ @genes = [
4
+ Darwinning::Gene.new(name: "first digit", value_range: (0..9)),
5
+ Darwinning::Gene.new(name: "second digit", value_range: (0..9)),
6
+ Darwinning::Gene.new(name: "third digit", value_range: (0..9))
7
+ ]
8
+
9
+ def fitness
10
+ # Try to get the sum of the 3 digits to add up to 15
11
+ (genotypes.inject{ |sum, x| sum + x } - 15).abs
12
+ end
13
+ end
@@ -1,9 +1,15 @@
1
1
  require 'darwinning'
2
+ require './spec/classes/triple'
2
3
 
3
4
  describe Darwinning::Gene do
4
5
  before do
5
- @digit = Darwinning::Gene.new("digit", (0..9))
6
- @day_hour = Darwinning::Gene.new("hour", (0..23), [0,1,2,3,4,20,21,22,23], "o'clock")
6
+ @digit = Darwinning::Gene.new(name: "digit", value_range: (0..9))
7
+ @day_hour = Darwinning::Gene.new(
8
+ name: "hour",
9
+ value_range: (0..23),
10
+ invalid_values: [0, 1, 2, 3, 4, 20, 21, 22, 23],
11
+ units: "o'clock"
12
+ )
7
13
  end
8
14
 
9
15
  it "name should be set" do
@@ -15,43 +21,17 @@ describe Darwinning::Gene do
15
21
  end
16
22
 
17
23
  it "invalid values should be set" do
18
- @day_hour.invalid_values.should == [0,1,2,3,4,20,21,22,23]
24
+ @day_hour.invalid_values.should == [0, 1, 2, 3, 4, 20, 21, 22, 23]
19
25
  end
20
26
 
21
27
  it "units should be set" do
22
28
  @day_hour.units.should == "o'clock"
23
29
  end
24
-
25
- describe "#express" do
26
-
27
- it "expressed value should be within range" do
28
- (0..9).to_a.include?(@digit.express).should == true # uncertain test
29
- end
30
-
31
- it "expressed value should not be invalid value" do
32
- @day_hour.invalid_values.include?(@digit.express).should == false # uncertain test
33
- end
34
- end
35
-
36
30
  end
37
31
 
38
32
  describe Darwinning::Organism do
39
33
  before do
40
34
  @org = Darwinning::Organism.new
41
-
42
- class Triple < Darwinning::Organism
43
- @name = "Triple"
44
- @genes = [
45
- Darwinning::Gene.new("first digit", (0..9)),
46
- Darwinning::Gene.new("second digit", (0..9)),
47
- Darwinning::Gene.new("third digit", (0..9))
48
- ]
49
-
50
- def fitness
51
- # Try to get the sum of the 3 digits to add up to 15
52
- (genotypes.inject{ |sum, x| sum + x } - 15).abs
53
- end
54
- end
55
35
  @triple = Triple.new
56
36
  end
57
37
 
@@ -86,5 +66,29 @@ describe Darwinning::Organism do
86
66
  end
87
67
 
88
68
  describe Darwinning::Population do
69
+ before do
70
+ @pop_triple = Darwinning::Population.new(
71
+ organism: Triple, population_size: 10, fitness_goal: 0
72
+ )
73
+ end
89
74
 
90
- end
75
+ it "fitness goal should be set to 0" do
76
+ @pop_triple.fitness_goal.should == 0
77
+ end
78
+
79
+ it "population size should be 10" do
80
+ @pop_triple.members.length.should == 10
81
+ end
82
+
83
+ it "population should start on generation 0" do
84
+ @pop_triple.generation.should == 0
85
+ end
86
+
87
+ it "make_next_generation! should evolve population by one generation" do
88
+ old_members = @pop_triple.members
89
+ @pop_triple.make_next_generation!
90
+ @pop_triple.generation.should == 1
91
+ @pop_triple.members.should_not == old_members
92
+ end
93
+
94
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: darwinning
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.0.3
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-05-29 00:00:00.000000000 Z
12
+ date: 2014-12-02 00:00:00.000000000 Z
13
13
  dependencies: []
14
14
  description: Darwinning provides tools to build genetic algorithm solutions using
15
15
  a Gene, Organism, and Population structure.
@@ -19,14 +19,19 @@ extensions: []
19
19
  extra_rdoc_files: []
20
20
  files:
21
21
  - lib/darwinning/config.rb
22
+ - lib/darwinning/evolution_types/mutation.rb
23
+ - lib/darwinning/evolution_types/reproduction.rb
24
+ - lib/darwinning/evolution_types.rb
22
25
  - lib/darwinning/gene.rb
23
26
  - lib/darwinning/organism.rb
24
27
  - lib/darwinning/population.rb
25
28
  - lib/darwinning/version.rb
26
29
  - lib/darwinning.rb
30
+ - Gemfile
27
31
  - Rakefile
28
32
  - README.md
29
- - test/darwinning_spec.rb
33
+ - spec/classes/triple.rb
34
+ - spec/darwinning_spec.rb
30
35
  homepage: https://github.com/dorkrawk/darwinning
31
36
  licenses: []
32
37
  post_install_message:
@@ -52,4 +57,5 @@ signing_key:
52
57
  specification_version: 3
53
58
  summary: A Ruby gem to aid in the use of genetic algorithms.
54
59
  test_files:
55
- - test/darwinning_spec.rb
60
+ - spec/classes/triple.rb
61
+ - spec/darwinning_spec.rb