algorithm-genetic 0.0.1

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.
@@ -0,0 +1,29 @@
1
+ # Algorithm::Genetic
2
+
3
+ A Generic Algorithm Library for Ruby Language
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'algorithm-genetic'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install algorithm-genetic
18
+
19
+ ## Usage
20
+
21
+ TODO: Write usage instructions here
22
+
23
+ ## Contributing
24
+
25
+ 1. Fork it ( https://github.com/tdtds/algorithm-genetic/fork )
26
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
27
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
28
+ 4. Push to the branch (`git push origin my-new-feature`)
29
+ 5. Create a new Pull Request
@@ -0,0 +1,7 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
7
+
@@ -0,0 +1,25 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'algorithm/genetic/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "algorithm-genetic"
8
+ spec.version = Algorithm::Genetic::VERSION
9
+ spec.authors = ["TADA Tadashi"]
10
+ spec.email = ["t@tdtds.jp"]
11
+ spec.summary = %q{A Generic Algorithm Library for Ruby Language}
12
+ spec.description = %q{A Generic Algorithm Library for Ruby Language}
13
+ spec.homepage = ""
14
+ spec.license = "GPL"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.6"
22
+ spec.add_development_dependency "rake"
23
+ spec.add_development_dependency "rspec"
24
+ spec.add_development_dependency "pry"
25
+ end
@@ -0,0 +1,10 @@
1
+ require "algorithm/genetic/version"
2
+ require "algorithm/genetic/evaluator"
3
+ require "algorithm/genetic/gene"
4
+ require "algorithm/genetic/population"
5
+
6
+ module Algorithm
7
+ module Genetic
8
+ # Your code goes here...
9
+ end
10
+ end
@@ -0,0 +1,22 @@
1
+ #
2
+ # Crossover::Order: order crossover
3
+ #
4
+ module Algorithm::Genetic::Crossover
5
+ module Order
6
+ def crossover(parent1, parent2)
7
+ cut_point = rand(parent1.code.length)
8
+ child1 = mate(parent1.code, parent2.code, cut_point)
9
+ child2 = mate(parent2.code, parent1.code, cut_point)
10
+ return child1, child2
11
+ end
12
+
13
+ private
14
+ def mate(p1, p2, point)
15
+ child = p1[0, point]
16
+ p2.each do |g|
17
+ child << g unless child.index(g)
18
+ end
19
+ return child
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,13 @@
1
+ #
2
+ # Crossover::Point: (one) point crossover
3
+ #
4
+ module Algorithm::Genetic::Crossover
5
+ module Point
6
+ def crossover(parent1, parent2, cut_num)
7
+ pivot = (parent1.code.length / 2.0).round
8
+ child1 = parent1.code[0, pivot] + parent2.code[pivot, pivot]
9
+ child2 = parent2.code[0, pivot] + parent1.code[pivot, pivot]
10
+ return child1, child2
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,24 @@
1
+
2
+ module Algorithm
3
+ module Genetic
4
+ #
5
+ # = evaluate fitness of a gene
6
+ #
7
+ class Evaluator
8
+ # evaluate fitness, implement by yourself
9
+ #
10
+ # gene :: a instance of Gene class
11
+ def fitness(gene)
12
+ raise NotImplementedError.new("implement 'fitness' method by yourself.")
13
+ end
14
+
15
+ # judgment of termination
16
+ #
17
+ # gene :: a instance of Gene class
18
+ def terminated?(gene)
19
+ raise NotImplementedError.new("implement 'terminated?' method by yourself.")
20
+ end
21
+ end
22
+ end
23
+ end
24
+
@@ -0,0 +1,63 @@
1
+ module Algorithm
2
+ module Genetic
3
+ #
4
+ # = A gene class
5
+ #
6
+ class Gene
7
+ attr_reader :code, :fitness
8
+
9
+ # constructor of Gene
10
+ #
11
+ # code :: initial code as Array
12
+ # evaluator :: a Evaluator instance or a Proc instance returns Evaluator instance
13
+ # opts :: hash of options
14
+ #
15
+ # options:
16
+ # :crossover :: an array of module name including crossover method and params
17
+ # :mutation :: an array of module name including mutate method and params
18
+ # :mutation_chance :: mutation chance (float of 0 to 1)
19
+ def initialize(code, evaluator, opts = {})
20
+ @code, @evaluator_org, @opts = code, evaluator, opts
21
+ if @evaluator_org.respond_to?(:fitness) # Evaluator instance
22
+ @evaluator = @evaluator_org
23
+ else # Proc instance
24
+ @evaluator = @evaluator_org.call
25
+ end
26
+ @fitness = @evaluator.fitness(self)
27
+
28
+ if opts[:crossover]
29
+ @crossover_params = opts[:crossover].dup
30
+ crossover_module = @crossover_params.shift.to_s.capitalize
31
+ self.extend(Algorithm::Genetic::Crossover.const_get(crossover_module))
32
+ end
33
+ if opts[:mutation]
34
+ @mutation_params = opts[:mutation].dup
35
+ mutation_module = @mutation_params.shift.to_s.capitalize
36
+ self.extend(Algorithm::Genetic::Mutation.const_get(mutation_module))
37
+ end
38
+ @mutation_chance = opts[:mutation_chance] || 0.5
39
+ end
40
+
41
+ # crossover with a partner, returning a couple of children
42
+ #
43
+ # partner :: a partner's gene
44
+ def crossover_with(partner)
45
+ code1, code2 = crossover(self, partner, *@crossover_params)
46
+ return Gene.new(code1, @evaluator_org, @opts), Gene.new(code2, @evaluator_org, @opts)
47
+ end
48
+
49
+ # mutate the code
50
+ def mutate!
51
+ return if rand > @mutation_chance
52
+ @code = mutate(@code, *@mutation_params)
53
+ @fitness = @evaluator.fitness(self)
54
+ end
55
+
56
+ # judgement termination
57
+ def terminated?
58
+ @evaluator.terminated?(self)
59
+ end
60
+ end
61
+ end
62
+ end
63
+
@@ -0,0 +1,16 @@
1
+
2
+ module Algorithm::Genetic::Mutation
3
+ module Shift
4
+ def mutate(code)
5
+ index = (rand * code.length).to_i
6
+ direction = rand <= 0.5 ? -1 : 1
7
+ begin
8
+ new_char = (code[index].ord + direction).chr
9
+ rescue
10
+ return(code)
11
+ end
12
+ code[index] = new_char
13
+ return code
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,11 @@
1
+
2
+ module Algorithm::Genetic::Mutation
3
+ module Swap
4
+ def mutate(code)
5
+ i1, i2 = (rand * code.length).to_i, (rand * code.length).to_i
6
+ code[i1], code[i2] = code[i2], code[i1]
7
+ return code
8
+ end
9
+ end
10
+ end
11
+
@@ -0,0 +1,94 @@
1
+ module Algorithm
2
+ module Genetic
3
+ #
4
+ # = terminated exception
5
+ #
6
+ class Terminated < StandardError
7
+ attr_reader :gene
8
+
9
+ def initialize(gene)
10
+ @gene = gene
11
+ super('terminated.')
12
+ end
13
+ end
14
+
15
+ #
16
+ # = population management class
17
+ #
18
+ class Population
19
+ include Enumerable
20
+ attr_reader :generation
21
+
22
+ # constructor of population
23
+ #
24
+ # code_length :: size of code
25
+ # population_size :: size of population
26
+ # evaluator :: an Evaluator instance or Proc instance returns Evaluator instance
27
+ # opts :: hash of options
28
+ #
29
+ # options:
30
+ # :selection :: an array of module name including select method and params
31
+ # :crossover :: an array of module name including crossover method and params
32
+ # :mutation :: an array of module name including mutate method and params
33
+ # :mutation_chance :: mutation chance (float of 0 to 1)
34
+ #
35
+ # need block for generate an initial (random) code of a gene
36
+ def initialize(population_size, evaluator, opts = {})
37
+ @evaluator = evaluator
38
+ @members = Array.new(population_size){
39
+ Algorithm::Genetic::Gene.new(yield, evaluator, opts)
40
+ }
41
+ @generation = 0
42
+
43
+ if opts[:selection]
44
+ @selection_params = opts[:selection].dup
45
+ selection_module = @selection_params.shift.to_s.capitalize
46
+ self.extend(Algorithm::Genetic::Selection.const_get(selection_module))
47
+ end
48
+ end
49
+
50
+ # increment the generation: senection, crossover and mutation
51
+ def generate
52
+ @generation += 1
53
+ select!
54
+ crossover
55
+ mutate
56
+ sort!
57
+ end
58
+
59
+ # iterate each member
60
+ def each
61
+ return @members.each unless block_given?
62
+ @members.each{|m| yield m }
63
+ end
64
+
65
+ private
66
+ def sort!
67
+ @members.sort! do |a, b|
68
+ b.fitness <=> a.fitness
69
+ end
70
+ end
71
+
72
+ def select!
73
+ @members = select(@members, *@selection_params) do |a, b|
74
+ b.fitness <=> a.fitness
75
+ end
76
+ end
77
+
78
+ def crossover
79
+ @members += @members[0].crossover_with(@members[1])
80
+ end
81
+
82
+ def mutate
83
+ @members.each do |m|
84
+ m.mutate!
85
+ if m.terminated?
86
+ sort!
87
+ raise Terminated.new(m)
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
94
+
@@ -0,0 +1,8 @@
1
+
2
+ module Algorithm::Genetic::Selection
3
+ module Elite
4
+ def select(members, num)
5
+ (block_given? ? members.sort{|a, b| yield a, b } : members.sort)[0, num]
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,5 @@
1
+ module Algorithm
2
+ module Genetic
3
+ VERSION = "0.0.1"
4
+ end
5
+ end
@@ -0,0 +1,55 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # Algorithm::Genetic sample: say 'I Love Ruby'
4
+ #
5
+ require 'algorithm/genetic'
6
+ require 'algorithm/genetic/selection/elite'
7
+ require 'algorithm/genetic/crossover/point'
8
+ require 'algorithm/genetic/mutation/shift'
9
+
10
+ class StringEvaluator < Algorithm::Genetic::Evaluator
11
+ def initialize(goal)
12
+ @goal = goal
13
+ end
14
+
15
+ def fitness(gene)
16
+ total = 0
17
+ gene.code.each_with_index do |c, i|
18
+ total -= (c.ord - @goal[i].ord) ** 2
19
+ end
20
+ return total
21
+ end
22
+
23
+ def terminated?(gene)
24
+ gene.code == @goal
25
+ end
26
+ end
27
+
28
+ def show(population)
29
+ puts "Generation: #{population.generation}"
30
+ population.each do |gene|
31
+ puts " #{gene.code.join.inspect} (#{gene.fitness})"
32
+ end
33
+ puts
34
+ end
35
+
36
+ goal = 'I love Ruby'.split(//)
37
+ size = 10
38
+ evaluator = StringEvaluator.new(goal)
39
+ population = Algorithm::Genetic::Population.new(
40
+ size, evaluator,
41
+ selection: [:elite, size - 2],
42
+ crossover: [:point, 1],
43
+ mutation: [:shift]
44
+ ) do
45
+ (' ' * goal.length).each_byte.map{|c| (rand * 255).to_i.chr}
46
+ end
47
+ show(population)
48
+ begin
49
+ loop do
50
+ population.generate
51
+ show(population)
52
+ end
53
+ rescue Algorithm::Genetic::Terminated => e
54
+ puts "Got '#{e.gene.code.join}' at generation #{population.generation}."
55
+ end
@@ -0,0 +1,94 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # Algorithm::Genetic sample: traveling salesman problem
4
+ #
5
+ require 'algorithm/genetic'
6
+ require 'algorithm/genetic/selection/elite'
7
+ require 'algorithm/genetic/crossover/order'
8
+ require 'algorithm/genetic/mutation/swap'
9
+
10
+ class City
11
+ attr_reader :name, :x, :y
12
+
13
+ def initialize(name, x, y)
14
+ @name, @x, @y = name, x, y
15
+ end
16
+
17
+ def distance_with(city)
18
+ Math.sqrt((@x - city.x) ** 2 + (@y - city.y) ** 2)
19
+ end
20
+ end
21
+
22
+ class RouteEvaluator < Algorithm::Genetic::Evaluator
23
+ def initialize(start)
24
+ @start = start
25
+ end
26
+
27
+ def fitness(gene)
28
+ total = 0.0
29
+ prev = @start
30
+ gene.code.each do |city|
31
+ total += prev.distance_with(city)
32
+ prev = city
33
+ end
34
+ total += prev.distance_with(@start)
35
+ return -total
36
+ end
37
+
38
+ def terminated?(gene)
39
+ # cannot know termination by gene.
40
+ # stop yourself by limitation of generation count
41
+ return false
42
+ end
43
+ end
44
+
45
+ def distance_char(city1, city2)
46
+ '_' * (city1.distance_with(city2) / 20)
47
+ end
48
+
49
+ def show(population, start)
50
+ puts "Generation: #{population.generation}"
51
+ population.each do |cities|
52
+ printf('%7.2f %s', -cities.fitness, start.name)
53
+ prev = start
54
+ cities.code.map do |city|
55
+ print distance_char(prev, city)
56
+ print city.name
57
+ prev = city
58
+ end
59
+ print distance_char(prev, start)
60
+ puts start.name
61
+ end
62
+ puts
63
+ end
64
+
65
+ #
66
+ # create all cities
67
+ #
68
+ name = '@' # previous of 'A'
69
+ cities = Array.new(20).map do
70
+ City.new(name.succ!.dup, 0.0 + rand(100), 0.0 + rand(100))
71
+ end
72
+ start = cities.shift
73
+
74
+ size = 10
75
+ evaluator = RouteEvaluator.new(start)
76
+ population = Algorithm::Genetic::Population.new(
77
+ size, evaluator,
78
+ selection: [:elite, size - 2],
79
+ crossover: [:order],
80
+ mutation: [:swap],
81
+ mutation_chance: 0.3
82
+ ) do
83
+ cities.shuffle
84
+ end
85
+
86
+ show(population, start)
87
+ print "press any key to start."
88
+ gets
89
+ 1000.times do
90
+ population.generate
91
+ show(population, start)
92
+ end
93
+ printf('distance: %1.2f ', -population.first.fitness)
94
+ puts("#{start.name}-#{population.first.code.map{|city| city.name}.join('-')}-#{start.name}")