darwinning 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
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