genetic_algorithms 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/README.md CHANGED
@@ -6,31 +6,105 @@ This project contains my work in progress on Genetic Algorithms. The basic premi
6
6
 
7
7
  A chromosome is a solution to a problem, and represented as a bit string. It is intentionally general in order to lend itself to multiple domains. The only thing linking it to a particular problem set is the fitness function. A fitness function evaluates a chromosome, that is, the effectiveness of a proposed solution.
8
8
 
9
- ## Usage
9
+ ## The Engine
10
10
 
11
- In the GeneticAlgorithms module, the Engine class encapsulates the entire evolution process. You can quickly use it with default values like this:
11
+ In the GeneticAlgorithms module, the Engine class encapsulates the entire evolution process. When you construct one, by default it gives you a population of size 10, a chromosome length of 10, and 5 generations of evolution. You'll notice that modifying these values can have a tremendous effect on the algorithm itself, and that is one of the interesting characteristics of Genetic Algorithms. In the future I will be opening up more parameters as well; currently many of them are tightly coupled to their respective classes.
12
+
13
+ ## Knapsack, An Example Use Case
14
+
15
+ I stumbled on an interesting use case and decided to implement it for demonstrational purposes. Let's say we have a knapsack, and furthermore, let's say it can only hold a finite number of items. Assuming we know how much space each item takes, and how much total space the knapsack has, then how can we most efficiently utilize the knapsack? That is, we want to put as many items in it as possible with the least amount of free space left over. Enter genetic algorithms.
16
+
17
+ ### Encoding
18
+
19
+ First of all, we need a way to express solutions to this problem as a chromosome. Since genetic_algorithms uses binary encoding, that's how we're going to approach it. Let's decide that there are only 4 items that we're considering for this exclusive knapsack. The items are a hammer, coin, pen, and wallet. The chromosome will then have 4 bits. An "ON" bit indicates that the item is in the knapsack, and an "OFF" means it is not. So, if we see something like "1010" that means a hammer(left one) and pen(right one) are in the knapsack, but the coin(left zero) and wallet(right zero) are not.
20
+
21
+ ### The Fitness Function
22
+
23
+ From a high level perspective, the fitness function should return how efficient the knapsack is being utilized. The implementation is covered below.
24
+
25
+ Currently, genetic_algorithms is expecting better solutions to return greater numbers than worse solutions. This implies that the highest number returned from a fitness function is the best solution.
26
+
27
+ ### Finally...some code!
28
+
29
+ Here is the Knapsack class:
12
30
 
13
31
  ```ruby
14
- GeneticAlgorithms::Engine.new.start "AllOffSample"
32
+ class Knapsack
33
+ CAPACITY, BEST_SCORE = 24, 1000
34
+
35
+ def initialize chromosome
36
+ @contents = decode chromosome
37
+ end
38
+
39
+ def utilization
40
+ space_used = @contents.inject(0) do |accum, item|
41
+ accum += item.space_needed
42
+ end
43
+
44
+ (space_used.to_f / CAPACITY * 1000).round
45
+ end
46
+
47
+ private
48
+
49
+ def decode chromosome
50
+ all_items = Item.create_items({ hammer: 17, coin: 4, pen: 9, wallet: 11 })
51
+ index = -1
52
+
53
+ filtered_items = all_items.select do |item|
54
+ index += 1
55
+ chromosome[index] == "1"
56
+ end
57
+ end
58
+ end
15
59
  ```
16
- And you'll see something like:
60
+
61
+ And the Item class:
17
62
 
18
63
  ```ruby
19
- {"1000000000"=>9}
64
+ class Item
65
+ def self.create_items item_hash
66
+ item_hash.inject(Array.new) do |items, (name, space_needed)|
67
+ items << Item.new(name, space_needed)
68
+ end
69
+ end
70
+
71
+ def initialize name, space_needed
72
+ @name = name
73
+ @space_needed = space_needed
74
+ end
75
+
76
+ attr_reader :name, :space_needed
77
+ end
20
78
  ```
21
79
 
22
- This is a Hash containing the best chromosome found, and its score based on the fitness function. The argument to the start method is the name of a fitness function module. I have included a few with this project as a basic reference for creating your own. Here is an example fitness function where the best solution is where all bits are off:
80
+ ### Enter genetic_algorithms
23
81
 
24
82
  ```ruby
25
- lambda do |chromosome|
26
- chromosome.each_char.inject(0) do |accum, char|
27
- accum += 1 if char == Chromosome::OFF
28
- accum
29
- end
83
+ require 'genetic_algorithms'
84
+ include GeneticAlgorithms
85
+
86
+ Engine.new(10,4).start(Knapsack::BEST_SCORE) do |chromosome|
87
+ score = Knapsack.new(chromosome).utilization
88
+ score = Knapsack::BEST_SCORE - score if score > Knapsack::BEST_SCORE
89
+ score
30
90
  end
31
91
  ```
32
92
 
33
- The engine has three optional parameters that can be utilized during construction. The population size, chromosome length, and number of generations to evolve (in that order). You'll notice that modifying these values can have a tremendous effect on the algorithm itself, and that is one of the interesting characteristics of genetic algorithms. In the future I will be opening up more parameters as well; currently many of them are tightly coupled to their respective classes.
93
+ Take note of the line checking whether the score is greater than the best score. It is necessary because we don't want genetic_algorithms to think that the over-utilized knapsacks are better solutions. A knapsack with too much stuff in it would otherwise have a higher score.
94
+
95
+ The result of this run will be something like:
96
+
97
+ ```ruby
98
+ {"1100"=>875}
99
+ ```
100
+
101
+ We see here that the best solution found is a hammer and a coin, totalling 21 out of 24 (0.875) in terms of knapsack capacity.
102
+
103
+ ### Um, that's not the best solution...
104
+
105
+ You are correct! That's why I chose to show it. The best solution is actually "0111", but given the parameters that we used for the engine, it wasn't able to discover it. This illustrates an important point. Genetic Algorithms may not find the best solution, in fact, some people refer to them as "good enough" algorithms.
106
+
107
+ You will get the best solution if you run it a few times as shown, but if you tweak the engine parameters a bit, the ideal solution will be found every time.
34
108
 
35
109
  ## Logging
36
110
 
@@ -10,9 +10,7 @@ module GeneticAlgorithms
10
10
  @population = Population.new @chromosomes
11
11
  end
12
12
 
13
- def start(fitness_function_type)
14
- extend GeneticAlgorithms::FitnessFunctions.const_get(fitness_function_type)
15
-
13
+ def start(best_possible_score, &fitness_function)
16
14
  highest_score, best_gen = 0, nil
17
15
 
18
16
  (0...@num_generations).inject(@population) do |newest_population, i|
@@ -20,7 +18,7 @@ module GeneticAlgorithms
20
18
 
21
19
  if newest_population.highest_score > highest_score
22
20
  highest_score, best_gen = newest_population.highest_score, newest_population
23
- break if highest_score == self.best_possible_score
21
+ break if highest_score == best_possible_score
24
22
  end
25
23
 
26
24
  next_gen
@@ -1,3 +1,3 @@
1
1
  module GeneticAlgorithms
2
- VERSION = '0.0.2'
2
+ VERSION = '0.0.3'
3
3
  end
@@ -14,11 +14,5 @@ module GeneticAlgorithms
14
14
  require 'genetic_algorithms/exceptions'
15
15
  require 'genetic_algorithms/roulette_wheel'
16
16
  require 'genetic_algorithms/population'
17
-
18
- ff_path = File.join(File.expand_path("..", __FILE__), "genetic_algorithms/fitness_functions")
19
- Dir[File.join ff_path, "**/*.rb"].each do |f|
20
- require f
21
- end
22
-
23
17
  require 'genetic_algorithms/engine'
24
18
  end
data/spec/engine_spec.rb CHANGED
@@ -2,35 +2,55 @@ require 'spec_helper'
2
2
  include GeneticAlgorithms
3
3
 
4
4
  describe Engine do
5
- subject { Engine.new }
6
-
5
+
7
6
  # TODO: DRY out this code
8
7
  describe "#start" do
9
8
  context "AllOffSample fitness function" do
9
+
10
+ subject do
11
+ Engine.new(10,10,10).start(10) do |chromosome|
12
+ chromosome.each_char.inject(0) do |accum, char|
13
+ accum += 1 if char == Chromosome::OFF
14
+ accum
15
+ end
16
+ end
17
+ end
18
+
10
19
  it "returns a hash" do
11
- subject.start("AllOffSample").is_a?(Hash).should == true
20
+ subject.is_a?(Hash).should == true
12
21
  end
13
22
 
14
23
  it "returns the best solution as a Chromosome" do
15
- subject.start("AllOffSample").keys.first.is_a?(Chromosome).should == true
24
+ subject.keys.first.is_a?(Chromosome).should == true
16
25
  end
17
26
 
18
27
  it "returns the best score as a Fixnum" do
19
- subject.start("AllOffSample").values.first.is_a?(Fixnum).should == true
28
+ subject.values.first.is_a?(Fixnum).should == true
20
29
  end
21
30
  end
22
31
 
23
32
  context "AlternatingOnOffSample fitness function" do
33
+
34
+ subject do
35
+ Engine.new(10,10,10).start(10) do |chromosome|
36
+ (0...chromosome.length).inject(0) do |accum, index|
37
+ accum += 1 if index % 2 == 0 and chromosome[index] == Chromosome::ON
38
+ accum += 1 if index % 2 == 1 and chromosome[index] == Chromosome::OFF
39
+ accum
40
+ end
41
+ end
42
+ end
43
+
24
44
  it "returns a hash" do
25
- subject.start("AlternatingOnOffSample").is_a?(Hash).should == true
45
+ subject.is_a?(Hash).should == true
26
46
  end
27
47
 
28
48
  it "returns the best solution as a Chromosome" do
29
- subject.start("AlternatingOnOffSample").keys.first.is_a?(Chromosome).should == true
49
+ subject.keys.first.is_a?(Chromosome).should == true
30
50
  end
31
51
 
32
52
  it "returns the best score as a Fixnum" do
33
- subject.start("AlternatingOnOffSample").values.first.is_a?(Fixnum).should == true
53
+ subject.values.first.is_a?(Fixnum).should == true
34
54
  end
35
55
  end
36
56
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: genetic_algorithms
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: 2012-11-11 00:00:00.000000000 Z
12
+ date: 2012-11-13 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: logging
@@ -76,8 +76,6 @@ files:
76
76
  - lib/genetic_algorithms/chromosome.rb
77
77
  - lib/genetic_algorithms/engine.rb
78
78
  - lib/genetic_algorithms/exceptions.rb
79
- - lib/genetic_algorithms/fitness_functions/all_off_sample.rb
80
- - lib/genetic_algorithms/fitness_functions/alternating_on_off_sample.rb
81
79
  - lib/genetic_algorithms/natural_hash.rb
82
80
  - lib/genetic_algorithms/population.rb
83
81
  - lib/genetic_algorithms/roulette_wheel.rb
@@ -1,19 +0,0 @@
1
- module GeneticAlgorithms
2
- module FitnessFunctions
3
- module AllOffSample
4
-
5
- def best_possible_score
6
- @chromosome_length
7
- end
8
-
9
- def fitness_function
10
- lambda do |chromosome|
11
- chromosome.each_char.inject(0) do |accum, char|
12
- accum += 1 if char == Chromosome::OFF
13
- accum
14
- end
15
- end
16
- end
17
- end
18
- end
19
- end
@@ -1,20 +0,0 @@
1
- module GeneticAlgorithms
2
- module FitnessFunctions
3
- module AlternatingOnOffSample
4
-
5
- def best_possible_score
6
- @chromosome_length
7
- end
8
-
9
- def fitness_function
10
- lambda do |chromosome|
11
- (0...chromosome.length).inject(0) do |accum, index|
12
- accum += 1 if index % 2 == 0 and chromosome[index] == Chromosome::ON
13
- accum += 1 if index % 2 == 1 and chromosome[index] == Chromosome::OFF
14
- accum
15
- end
16
- end
17
- end
18
- end
19
- end
20
- end