> -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- > > ## Genetic Programming (#212) > > Buon giorno Rubyists, > > Let's say that we have a table of outputs for an unknown function, and > we want to find out what that function is. One possible method of > finding a solution is to use genetic programming[1]. In genetic > programming we begin with a random population of programs, then rank > them according to how well they meet the solution criteria. The top > ranked programs are then modified to create a new generation of > programs. The new generation is ranked by how well the solve the > problem in the same way and the process repeats until, hopefully, > we'll have evolved a strong solution. Some time ago, I ran across DRP, Directed Ruby Programming[1], and thought that might be something to look at later; I thought this quiz might be a bit of a chance. (Though I only barely scratched the surface of the library's documentation.) First, here are some sample functions my programs generated (fitness: function): 89416: 4312 + y * 7 * y * x 82779: ((x * 4 * (93 + y * 6 + x + 9 - x)) - 3) * y / 8 - 62 + 1 * x - 3 - 80 + 1395 + 0 / 870 + y * 6 + y - y - ((y)) - x - y + y - 16 - 657 + 880 - 7 * (6 / 6) * (y / 1) - 870 + 19 + y * y * (y) - y - (62935) / (((x - x) - y / 1 * y / y) - x) - ((y + 4 - y + y * y + x)) + 6 - x * 6 * (((8 + 6 / 2563 * (y) + x / 9871671)) - y - 7 - x - x - 8) - 3361 + 9 - (43) / 675670 - y - x - 45 + 8 + (y / 1) - 870 + 19 + y * y * (y) - y + y * x * (6 / 6) + 870 + 19 + y + x * x - ((((10)))) - 8 + y - (x) 17387: x + (y) * 5 * y * y - ((x + y)) / 65 / y / y + y + 2 / (y * x + 95381 - x + x) - y * 0 / 50 * x * ((x)) * 7 - x * 4 + 4 * 9 * 6 * y - 0 * (((y + x + x))) * (x + y) * (y + 78 * ((x / y * (y + 22 - 9) - ((x + y) - 684) - x - y - x + ((7 + y * ((785 / 4 - x * 8)) + (8 - 4) + 8 / 7 + x + x))) / 4) - (8 - 9 * x - 0) * x + 2) * 91 * 76 * x + 220 + x * 4 - 2 * ((x)) * (y) * 4 - y * ((x / (y * 34 + y + 4 - x * 8)) + 24) + y * 1 - (y) * (((y + x + x))) 8570: 5 * y * + + y * y - 29 8424: y * + 5 * + y * y + - x 8360: 5 * y * y * y / y * y - x / - - - y 8346: 5 * y * y * y - - - 1 + x - x 8338: y * ((5 * y)) * ((y)) 8102: (8) - y + 60 + y * y * 5 * y 8056: 47 + 5 * y * y * y 7288: 988 + 5 * y * y * y - y * y / y + 0 * 894 / 2 - x * x / - + 1 - - 7 3948: y * 5 * (y + (y) * y - x) Following the first example in drp's INTRO file, I created a simple class to generate random initial functions: require 'drp' class ProgramGenerator extend DRP::RuleEngine begin_rules def program "Class.new{ #{function} }" end def function "def calculate(x, y); #{expression}; end" end def expression; "#{expression} #{binaryop} #{expression}"; end def expression; "#{atom} #{binaryop} #{expression}"; end def expression; "#{expression} #{binaryop} #{atom}"; end def expression; "(#{expression})"; end def expression; "#{atom}"; end def binaryop; "+"; end def binaryop; "-"; end def binaryop; "*"; end def binaryop; "/"; end def unaryop; "-"; end def atom; "#{variable}"; end def atom; "#{literal}"; end def variable; "x"; end def variable; "y"; end def literal; "#{rand(10)}"; end def literal; "#{rand(9) + 1}#{literal}"; end end_rules end irb(main):001:0> require 'program_generator' => true irb(main):002:0> pg = ProgramGenerator.new ... irb(main):003:0> pg.expression => "x" irb(main):004:0> pg.expression => "(y * 4 + 924)" irb(main):006:0> pg.function => "def calculate(x, y); 26 / x + (60 - x) + y; end" irb(main):007:0> pg.program => "Class.new{ def calculate(x, y); 136 * x + 14; end }" I setup a "magic" hash to store my population of programs and a few helpful methods: Programs = Hash.new{|hash, program| hash[program] = fitness(program) } class <<Programs def generate_some_more(n = 100, pg = ProgramGenerator.new) n.times{ self[pg.expression] } self end def reject_the_unfit self.delete_if{|program, fit| !fit} end def select_the_best(n = 5) self.sort_by_fitness[0, n] end def sort_by_fitness(dont_randomize = true) self.reject_the_unfit self.sort_by{|program, fitness| dont_randomize ? fitness : rand(fitness)} end end irb(main):001:0> Programs => {} irb(main):002:0> Programs.generate_some_more(2) => {"y"=>687122, "(x - 4 * x / 31 + (0 * 964 / 65 / y - 8 + 4 + (y * ((y / x * 54 - (x / ((986 - y + x - 24 + ((6 / 197 / 29 - 287)) / 9 + y * 2 / 2 * 7)) * 23 - 7) + x - 44 * 659 + (y * 1 - 8 / y / ((5 * x)) - 5) * 7632 / ((y)) / x / 8856))) / x + x) - ((x))) - 7 - 0 - x"=>nil} irb(main):003:0> Programs.select_the_best(1) => [["y", 687122]] And wrapped that in a loop to generate/mutate a bunch of functions: pm = ProgramMutator.new 1000.times do Programs.generate_some_more. sort_by_fitness(true). each_cons(2). each do |(p1, f1), (p2, f2)| new_program = case rand(10) when 3..9 then pm.cross_breed(p1, p2) when 2 then pm.mutate(p2) when 1 then pm.mutate(p1) else ProgramGenerator.new.expression end new_fitness = Programs[new_program] #puts "#{f1} X #{f2}\t#{new_fitness}" end program, fitness = Programs.select_the_best(1).flatten puts "Best: #{fitness}\t#{program[0..60]}" end > ruby1.9 -rubygems 212.rb Best: 569018 28298 Best: 494580 3170 * x - y Best: 494580 3170 * x - y Best: 494580 3170 * x - y Best: 357554 (4257 * y - 1) Best: 357554 (4257 * y - 1) Best: 357554 (4257 * y - 1) Best: 357554 (4257 * y - 1) Best: 212330 (y) * - 142 / - 25 * y * x Best: 212330 (y) * - 142 / - 25 * y * x Best: 187980 0 / y - - 8 * 25 * y * x Best: 173870 248 * y * (x) Best: 104800 26 - 2 + x * 7 * y * (y) Best: 104800 26 - 2 + x * 7 * y * (y) Best: 104800 26 - 2 + x * 7 * y * (y) Best: 92312 8526 - x + y * + 8 * y * x ^C That leaves the ProgramMutator class: class ProgramMutator def cross_breed(p1, p2) #... end def mutate(program) #... end end The machine I'm using today doesn't have ParseTree ("ParseTree doesn't work with ruby 1.9.0 (LoadError)"). So, I was just doing string manipulations. I don't know which functions produced which of the results from above, but a few samples: def cross_breed(p1, p2) p1 + %{w + - * /}[rand(4)] + p2 end def cross_breed(p1, p2) s1 = p1[0..rand(p1.size)] s2 = p2[rand(p2.size)..-1] s1 + %w{ + - * / }[rand(4)] + s2 end def mutate(program) program.gsub( "#{%w{ + - * / }[rand(4)]}", "#{%w{ + - * / }[rand(4)]}" ) end [1] http://drp.rubyforge.org/