algorithm-genetic 0.0.1

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