#encoding: utf-8 module NudgeGP # Abstract class that from which specific SearchOperator subclasses inherit initialization class SearchOperator attr_accessor :incoming_options def initialize(options={}) @incoming_options = options end end class RandomGuessOperator < SearchOperator # returns an Array of random Answers # # the first (optional) parameter specifies how many to make, and defaults to 1 # the second (also optional) parameter is a hash that # can temporarily override those set in the initialization # # For example, if # myRandomGuesser = RandomGuessOperator.new(:randomIntegerLowerBound => -90000) # # [myRandomGuesser.generate()] # produces a list of 1 Answer, and if it has any IntType samples they will be in [-90000,100] # (since the default +:randomIntegerLowerBound+ is 100) # [myRandomGuesser.generate(1,:randomIntegerLowerBound => 0)] # makes one Answer whose IntType samples (if any) will be between [0,100] def generate(howMany = 1, overridden_options ={}) result = Batch.new howMany.times do newGenome = CodeType.any_value(@incoming_options.merge(overridden_options)) newDude = Answer.new(newGenome, progress:0) result << newDude end result end end class ResampleAndCloneOperator < SearchOperator # returns an Array of clones of Answers randomly selected from the crowd passed in # # the first (required) parameter is an Array of Answers # the second (optional) parameter is how many samples to take, and defaults to 1 # # For example, if # @currentPopulation = [a list of 300 Answers] and # myRandomSampler = ResampleAndCloneOperator.new(@currentPopulation) # [myRandomSampler.generate()] # produces a list of 1 Answer, which is a clone of somebody from @currentPopulation # [myRandomGuesser.generate(11)] # returns a list of 11 Answers cloned from @currentPopulation, # possibly including repeats def generate(crowd, howMany = 1) result = Batch.new howMany.times do donor = crowd.sample clone = Answer.new(donor.blueprint, progress:donor.progress + 1) result << clone end return result end end class ResampleValuesOperator < SearchOperator def generate(crowd, howManyCopies = 1, overridden_options = {}) crowd.each {|dude| raise(ArgumentError) if !dude.kind_of?(Answer) } result = Batch.new regenerating_options = @incoming_options.merge(overridden_options) crowd.each do |dude| howManyCopies.times do wildtype_program = dude.program starting_footnotes = wildtype_program.footnote_section.split( /^(?=«)/ ) breaker = /^«([a-zA-Z][a-zA-Z0-9_]*)»\s*(.*)\s*/m type_value_pairs = starting_footnotes.collect {|fn| fn.match(breaker)[1..2]} mutant_blueprint = wildtype_program.code_section type_value_pairs.each do |pair| begin type_name = pair[0] type_class = "#{type_name}_type".camelize.constantize reduced_size = regenerating_options[:target_size_in_points] || rand(dude.points/2) reduced_option = {target_size_in_points:reduced_size} resampled_value = type_class.any_value(regenerating_options.merge(reduced_option)).to_s rescue NameError resampled_value = pair[1] end mutant_blueprint << "\n«#{pair[0].strip}» #{resampled_value.strip}" end mutant = Answer.new(mutant_blueprint, progress:dude.progress + 1) result << mutant end end return result end end class UniformBackboneCrossoverOperator < SearchOperator # Returns a Batch of new Answers whose programs are made by stitching together # the programs of pairs of 'parents'. The incoming Batch is divided into pairs based on # adjacency (modulo the Batch.length), one pair for each 'offspring' to be made. To make # an offspring, the number of backbone program points is determined in each parent; 'backbone' # refers to the number of branches directly within the root of the program, not the entire tree. # # To construct an offspring's program, program points are copied from the first parent with # probability p, or the second parent with probability (1-p), for each point in the first # parent's backbone. So if there are 13 and 6 points, respectively, the first six points are # selected randomly, but the last 7 are copied from the first parent. If there are 8 and 11 # respectively, then the last 3 will be ignored from the second parent in any case. # # the first (required) parameter is an Array of Answers # the second (optional) parameter is how many crossovers to make, # which defaults to the number of Answers in the incoming Batch def generate(crowd, howMany = crowd.length, prob = 0.5) result = Batch.new howMany.times do where = rand(crowd.length) mom = crowd[where] dad = crowd[ (where+1) % crowd.length ] mom_backbone_length = mom.program[1].contents.length dad_backbone_length = dad.program[1].contents.length baby_blueprint_parts = ["",""] (0..mom_backbone_length-1).each do |backbone_point| if rand() < prob next_chunks = mom.program[1].contents[backbone_point].blueprint_parts || ["",""] else if backbone_point < dad_backbone_length next_chunks = (dad.program[1].contents[backbone_point].blueprint_parts || ["", ""]) else next_chunks = ["",""] end end baby_blueprint_parts[0] << " #{next_chunks[0]}" baby_blueprint_parts[1] << " \n#{next_chunks[1]}" end mom.program.unused_footnotes.each {|fn| baby_blueprint_parts[1] += "\n#{fn}"} baby_blueprint = "block {#{baby_blueprint_parts[0]}} #{baby_blueprint_parts[1]}" baby = Answer.new(baby_blueprint, progress:[mom.progress,dad.progress].max + 1) result << baby end return result end end class PointCrossoverOperator < SearchOperator def generate(crowd, howManyBabies = 1) raise(ArgumentError) if !crowd.kind_of?(Array) raise(ArgumentError) if crowd.empty? crowd.each {|dude| raise(ArgumentError) if !dude.kind_of?(Answer) } result = Batch.new production = crowd.length*howManyBabies production.times do mom = crowd.sample dad = crowd.sample mom_receives = rand(mom.points) + 1 dad_donates = rand(dad.points) + 1 baby_blueprint = mom.replace_point_or_clone(mom_receives,dad.program[dad_donates]) baby = Answer.new(baby_blueprint, progress:[mom.progress,dad.progress].max + 1) result << baby end return result end end class PointDeleteOperator < SearchOperator def generate(crowd, howManyCopies = 1) raise(ArgumentError) if !crowd.kind_of?(Array) crowd.each {|dude| raise(ArgumentError) if !dude.kind_of?(Answer) } result = Batch.new crowd.each do |dude| howManyCopies.times do where = rand(dude.points)+1 variant = dude.delete_point_or_clone(where) baby = Answer.new(variant, progress:dude.progress + 1) result << baby end end return result end end class PointMutationOperator < SearchOperator def generate(crowd, howManyCopies = 1, overridden_options ={}) raise(ArgumentError) if !crowd.kind_of?(Array) raise(ArgumentError) if crowd.empty? crowd.each {|dude| raise(ArgumentError) if !dude.kind_of?(Answer) } result = Batch.new crowd.each do |dude| howManyCopies.times do where = rand(dude.points)+1 newCode = CodeType.any_value(@incoming_options.merge(overridden_options)) variant = dude.replace_point_or_clone(where,newCode) baby = Answer.new(variant, progress:dude.progress + 1) result << baby end end return result end end end