darwinning 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,90 @@
1
+ Darwinning
2
+ ==========
3
+ A Ruby gem to aid in the use of genetic algorithms.
4
+
5
+ Examples
6
+ --------
7
+
8
+ ### Fifteen
9
+
10
+ Here's an dumb example of how you might use Darwinning to solve a pointless problem:
11
+
12
+ Let's say for some reason you need a set of 3 number that add up to 15. This is a strange problem to have, but let's solve it anyway.
13
+
14
+ ```ruby
15
+ class Triple < Darwinning::Organism
16
+
17
+ @name = "Triple"
18
+ @genes = [
19
+ Darwinning::Gene.new("first digit", (0..9)),
20
+ Darwinning::Gene.new("second digit", (0..9)),
21
+ Darwinning::Gene.new("third digit", (0..9))
22
+ ]
23
+
24
+ def fitness
25
+ # Try to get the sum of the 3 digits to add up to 15
26
+ (genotypes.inject{ |sum, x| sum + x } - 15).abs
27
+ end
28
+ end
29
+
30
+ p = Darwinning::Population.new(Triple, 10, 0, 0.1, 100)
31
+ p.evolve
32
+
33
+ p.best_member.nice_print # prints the member representing the solution
34
+ ```
35
+
36
+ This code declares an organism class that inherits from Darwinning's Organism parent class to represent solutions. Then we create a population of these solution organisms and evolve the population until a solution meets the fitness threshold or the generation limit is met.
37
+
38
+ ### Cookies
39
+
40
+ Or let's say you want to find the perfect chocolate chip cookie recipie. Sure you could ask your grandmother, but why not let a genetic algorithm do all the work for you? Some baking may be required for this one.
41
+
42
+ Define a cookie Organism class, generate an initial population, bake a batch of each and have your friends rate each batch. Use that rating as the fitness value for each recipie and then generate the next generation of cookie recipies. Repeat until you have optimized the recipie or you are sick from eating too many cookies.
43
+
44
+ ```ruby
45
+ class Cookie < Darwinning::Organism
46
+
47
+ @name = "Chocolate Chip Cookie"
48
+ @genes = [
49
+ Darwinning::Gene.new("white sugar", (0..1), [], "cup"),
50
+ Darwinning::Gene.new("brown sugar", (0..1), [], "cup"),
51
+ Darwinning::Gene.new("flour", (0..3), [], "cup"),
52
+ Darwinning::Gene.new("eggs", (0..3)),
53
+ Darwinning::Gene.new("baking powder", (0..2), [], "teaspoon"),
54
+ Darwinning::Gene.new("salt", (0..2), [], "teaspoon"),
55
+ Darwinning::Gene.new("butter", (0..2), [], "cup"),
56
+ Darwinning::Gene.new("vanilla extract", (0..2), [], "teaspoon"),
57
+ Darwinning::Gene.new("chocolate chips", (0..20), [], "ounce"),
58
+ Darwinning::Gene.new("oven temp", (300..400), [], "degrees F"),
59
+ Darwinning::Gene.new("cook time", (5..20), [], "minute")
60
+ ]
61
+
62
+ end
63
+
64
+ p = Darwinning::Population.new(Cookie, 10, 5, 0.1, 100, true)
65
+
66
+ first_gen_ratings = [1.5, 4, 3, 3.5, 2, 1, 1.5, 3, 2.5, 0.5]
67
+ p.set_members_fitness!(first_gen_ratings)
68
+
69
+ p.make_next_generation!
70
+
71
+ p.members.each { |m| m.nice_print } # print second generation of cookie recipies
72
+ ```
73
+
74
+ ### Binary String Organism
75
+
76
+ A simple binary string representation of an organism can be easily created thusly:
77
+
78
+ ```ruby
79
+ class BinaryOrganism < Darwinning::Organism
80
+
81
+ 10.times { |s| @genes << Darwinning::Gene.new("", [0,1]) }
82
+
83
+ def fitness
84
+ # whatever makes sense here
85
+ end
86
+ end
87
+ ```
88
+
89
+ ## Built by:
90
+ * [Dave Schwantes](https://github.com/dorkrawk "dorkrawk")
File without changes
@@ -0,0 +1,8 @@
1
+ require_relative 'darwinning/gene'
2
+ require_relative 'darwinning/organism'
3
+ require_relative 'darwinning/population'
4
+ require_relative 'darwinning/config'
5
+
6
+ module Darwinning
7
+ extend Config
8
+ end
@@ -0,0 +1,6 @@
1
+ module Darwinning
2
+ module Config
3
+ # crossover mask?
4
+ # ordered vs weighted fitness
5
+ end
6
+ end
@@ -0,0 +1,21 @@
1
+ module Darwinning
2
+ class Gene
3
+ attr_accessor :name, :value, :value_range, :invalid_values, :units
4
+
5
+ def initialize(name = "", value_range = [], invalid_values = [], units = "")
6
+
7
+ @name = name
8
+ @value_range = value_range.to_a
9
+ @invalid_values = invalid_values.to_a
10
+ @units = units
11
+ end
12
+
13
+ def express
14
+ (@value_range - @invalid_values).sample
15
+ end
16
+
17
+ def is_valid_value?(value)
18
+ @value_range.include?(value) and not @invalid_values.include?(value)
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,72 @@
1
+ # Found from http://www.railstips.org/blog/archives/2006/11/18/class-and-instance-variables-in-ruby/
2
+ module ClassLevelInheritableAttributes
3
+ def self.included(base)
4
+ base.extend(ClassMethods)
5
+ end
6
+
7
+ module ClassMethods
8
+ def inheritable_attributes(*args)
9
+ @inheritable_attributes ||= [:inheritable_attributes]
10
+ @inheritable_attributes += args
11
+ args.each do |arg|
12
+ class_eval %(
13
+ class << self; attr_accessor :#{arg} end
14
+ )
15
+ end
16
+ @inheritable_attributes
17
+ end
18
+
19
+ def inherited(subclass)
20
+ @inheritable_attributes.each do |inheritable_attribute|
21
+ instance_var = "@#{inheritable_attribute}"
22
+ subclass.instance_variable_set(instance_var, instance_variable_get(instance_var))
23
+ end
24
+ end
25
+ end
26
+ end
27
+
28
+ module Darwinning
29
+ class Organism
30
+
31
+ include ClassLevelInheritableAttributes
32
+ inheritable_attributes :genes, :name
33
+ attr_accessor :genotypes, :fitness, :name, :genes
34
+
35
+ @genes = [] # Gene instances
36
+ @name = ""
37
+
38
+ def initialize(genotypes = [])
39
+ #TODO: catch errors if genotype.length != @genotypes.length
40
+ # catch if genotype[x] is not a valid value for @gene[x]
41
+
42
+ if genotypes == []
43
+ # fill genotypes with expressed Genes
44
+ @genotypes = self.class.genes.map { |g| g.express } # Gene expressions
45
+ else
46
+ @genotypes = genotypes
47
+ end
48
+ @fitness = -1
49
+ end
50
+
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
56
+ end
57
+
58
+ def nice_print
59
+ puts self.class.name == "" ? "[no name]" : self.class.name
60
+ self.class.genes.to_enum.each_with_index { |g, i| puts " #{g.name}: #{@genotypes[i]} #{g.units}" }
61
+ puts " fitness: #{fitness}"
62
+ end
63
+
64
+ def name
65
+ self.class.name
66
+ end
67
+
68
+ def genes
69
+ self.class.genes
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,120 @@
1
+ module Darwinning
2
+
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
12
+ @members = []
13
+ @generation = 0 # initial population is generation 0
14
+
15
+ build_population(population_size)
16
+ end
17
+
18
+ def build_population(population_size)
19
+ population_size.times do |i|
20
+ @members << @organism.new
21
+ end
22
+ end
23
+
24
+ def evolve
25
+ until evolution_over?
26
+ make_next_generation!
27
+ end
28
+ end
29
+
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
+ def weighted_select(members)
52
+ e = 0.01
53
+ fitness_sum = members.inject(0) { |sum, m| sum + m.fitness }
54
+
55
+ weighted_members = members.sort_by { |m| (m.fitness - @fitness_goal).abs }.map { |m| [m, fitness_sum / ((m.fitness - @fitness_goal).abs + e)] }
56
+
57
+ weight_sum = weighted_members.inject(0) { |sum, m| sum + m[1] }
58
+ pick = (0..weight_sum).to_a.sample
59
+
60
+ weighted_members.reverse! # In order to pop from the end we need the lowest ranked first
61
+ pick_sum = 0
62
+
63
+ until pick_sum > pick do
64
+ selected_member = weighted_members.pop
65
+ pick_sum += selected_member[1]
66
+ end
67
+
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
+ }
79
+ end
80
+
81
+ def set_members_fitness!(fitness_values)
82
+ @members.to_enum.each_with_index { |m, i| m.fitness = fitness_values[i] }
83
+ end
84
+
85
+ def make_next_generation!
86
+ temp_members = @members
87
+ used_members = []
88
+ new_members = []
89
+
90
+ until new_members.length == @members.length/2
91
+ m1 = weighted_select(@members - used_members)
92
+ used_members << m1
93
+ m2 = weighted_select(@members - used_members)
94
+ used_members << m2
95
+
96
+ new_members << crossover(m1,m2)
97
+ end
98
+
99
+ new_members.flatten!
100
+
101
+ @members = new_members
102
+
103
+ mutate!
104
+ @generation += 1
105
+ end
106
+
107
+ def evolution_over?
108
+ # check if the fiteness goal or generation limit has been met
109
+ @generation == @generations_limit or best_member.fitness == @fitness_goal
110
+ end
111
+
112
+ def best_member
113
+ @members.sort_by { |m| m.fitness }[0]
114
+ end
115
+
116
+ def size
117
+ @members.length
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,3 @@
1
+ module Darwinning
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,90 @@
1
+ require 'darwinning'
2
+
3
+ describe Darwinning::Gene do
4
+ 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")
7
+ end
8
+
9
+ it "name should be set" do
10
+ @digit.name.should == "digit"
11
+ end
12
+
13
+ it "value range should be set" do
14
+ @digit.value_range.should == (0..9).to_a
15
+ end
16
+
17
+ it "invalid values should be set" do
18
+ @day_hour.invalid_values.should == [0,1,2,3,4,20,21,22,23]
19
+ end
20
+
21
+ it "units should be set" do
22
+ @day_hour.units.should == "o'clock"
23
+ 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
+ end
37
+
38
+ describe Darwinning::Organism do
39
+ before do
40
+ @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
+ @triple = Triple.new
56
+ end
57
+
58
+ it "name should default to blank" do
59
+ @org.name.should == ""
60
+ end
61
+
62
+ it "genes should default to empty array" do
63
+ @org.genes.should == []
64
+ end
65
+
66
+ it "genotypes should initialize to empty array if genes is empty" do
67
+ @org.genotypes.should == []
68
+ end
69
+
70
+ it "fitness should default to -1" do
71
+ @org.fitness.should == -1
72
+ end
73
+
74
+ it "child class should set name" do
75
+ @triple.name.should == "Triple"
76
+ end
77
+
78
+ it "child class should set genes" do
79
+ @triple.genes.length.should == 3 # not the best test...
80
+ end
81
+
82
+ it "child class should initialize genotypes from genes" do
83
+ @triple.genotypes.length.should == 3 # not the best test...
84
+ end
85
+
86
+ end
87
+
88
+ describe Darwinning::Population do
89
+
90
+ end
metadata ADDED
@@ -0,0 +1,55 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: darwinning
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Dave Schwantes
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-05-23 00:00:00.000000000 Z
13
+ dependencies: []
14
+ description: Darwinning provides tools to build genetic algorithm solutions using
15
+ a Gene, Organism, and Population structure.
16
+ email: dave.schwantes@gmail.com
17
+ executables: []
18
+ extensions: []
19
+ extra_rdoc_files: []
20
+ files:
21
+ - lib/darwinning/config.rb
22
+ - lib/darwinning/gene.rb
23
+ - lib/darwinning/organism.rb
24
+ - lib/darwinning/population.rb
25
+ - lib/darwinning/version.rb
26
+ - lib/darwinning.rb
27
+ - Rakefile
28
+ - README.md
29
+ - test/darwinning_spec.rb
30
+ homepage: https://github.com/dorkrawk/darwinning
31
+ licenses: []
32
+ post_install_message:
33
+ rdoc_options: []
34
+ require_paths:
35
+ - lib
36
+ required_ruby_version: !ruby/object:Gem::Requirement
37
+ none: false
38
+ requirements:
39
+ - - ! '>='
40
+ - !ruby/object:Gem::Version
41
+ version: '0'
42
+ required_rubygems_version: !ruby/object:Gem::Requirement
43
+ none: false
44
+ requirements:
45
+ - - ! '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ requirements: []
49
+ rubyforge_project:
50
+ rubygems_version: 1.8.24
51
+ signing_key:
52
+ specification_version: 3
53
+ summary: A Ruby gem to aid in the use of genetic algorithms.
54
+ test_files:
55
+ - test/darwinning_spec.rb