def cost_function(inputs, targets, program, options=default_cost_options): """ Check whether a given program, when passed inputs, produces the corresponding outputs :param inputs: Inputs to pass :param targets: Expected targets :param options: A CostOptions namedtuple containing all options for cost function execution :return: int """ program_cost = 0 program_cost_addition = 0 time_cost = 0 for input_string_index in range(0, len(inputs)): program_cost_addition = 0 # Run the program, ensuring that it is not an infinite loop or a syntax error, then applying costs to it try: (output, runtime) = interpret.evaluate(program, inputs[input_string_index], options.program_timeout, return_time=True) except (interpret.BFSyntaxException, KeyError, interpret.TimeoutAbortException): # Program was not valid - mismatched brackets return False if options.time_cost: time_cost += int(runtime * 1000) # This will only be added to the cost if the program is not correct, i.e., the cost is not zero at the end. else: time_cost = 0 if output == targets[input_string_index]: # Program output is CORRECT for this input program_cost_addition = 0 # Ding ding ding we have a winner program_cost += program_cost_addition continue else: # This is here to ensure that incorrect programs cannot win unless someone changes the value :( program_cost_addition += options.cost_table['not_equal'] # Now, apply the simple cost value. if len(output) == 0: # No output - penalize at maximum for all expected chars program_cost_addition += options.cost_table['wrong_char'] * MAX_CODEPOINT * len(targets[input_string_index]) elif len(output) < len(targets[input_string_index]): # Missing some chars. Penalize for the difference between existing chars and target, then # for missing chars. for char_index in range(0, len(output)): # output is shorter, so this is safe expected_char = targets[input_string_index][char_index] actual_char = output[char_index] program_cost_addition += options.cost_table['wrong_char'] * abs(ord(expected_char) - ord(actual_char)) program_cost_addition += options.cost_table['wrong_char'] * MAX_CODEPOINT * \ abs(len(output) - len(targets[input_string_index])) elif len(targets[input_string_index]) < len(output): # Too many chars; penalize for the difference between existing chars and target, then # for missing chars. for char_index in range(0, len(targets[input_string_index])): # target is shorter, so this is safe expected_char = targets[input_string_index][char_index] actual_char = output[char_index] program_cost_addition += options.cost_table['wrong_char'] * abs(ord(expected_char) - ord(actual_char)) for char in output[len(targets[input_string_index]):]: program_cost_addition += options.cost_table['wrong_char'] * ord(char) else: # They are of equal lengths; just compare them. for char_index in range(0, len(targets[input_string_index])): # target is as long as output, so this is safe expected_char = targets[input_string_index][char_index] actual_char = output[char_index] program_cost_addition += options.cost_table['wrong_char'] * abs(ord(expected_char) - ord(actual_char)) program_cost += program_cost_addition if program_cost > 0: program_cost += time_cost return program_cost else: return 0
def old_cost_function(inputs, targets, program, options=default_cost_options): """ Check whether a given program, when passed inputs, produces the corresponding outputs :param inputs: Inputs to pass :param targets: Expected targets :param options: A CostOptions namedtuple containing all options for cost function execution :return: int """ program_cost = 0 program_cost_addition = 0 for input_string_index in range(0, len(inputs)): program_cost_addition = 0 # Run the program, ensuring that it is not an infinite loop or a syntax error, then applying costs to it try: output = interpret.evaluate(program, inputs[input_string_index], options.program_timeout) except interpret.TimeoutAbortException: # Program ran for too long program_cost_addition += options.cost_table['timeout'] program_cost += program_cost_addition continue # This is to prevent output being reffed after, since it is not assigned if the try fails except (interpret.BFSyntaxException, KeyError): # Program was not valid - mismatched brackets return False if output == targets[input_string_index]: # Program output is CORRECT for this input program_cost_addition = 0 # Ding ding ding we have a winner program_cost += program_cost_addition continue else: # This is here to ensure that incorrect programs cannot win unless someone changes the value :( program_cost_addition += options.cost_table['not_equal'] if output == '': # There's no output. program_cost_addition += options.cost_table['no output'] program_cost += program_cost_addition continue # Prevent double jeopardy else: # There is output, and it's not right. # Find an offset at which a correct character resides, preventing the "y-umlaut problem" # (ýHello, world! instead of Hello, world! creating an evolutionary boundary) if len(output) > len(targets[input_string_index]): max_offset = len(targets[input_string_index]) else: max_offset = len(output) correction_offset = 0 for char_index in range(0, max_offset): if output[correction_offset] == targets[input_string_index][0]: correction_offset = char_index break # The correction offset tells us about some wrong chars; penalize for them. #program_cost_addition += correction_offset * options.cost_table['wrong_char'] for char_index in range(correction_offset, len(output)): try: program_cost_addition += options.cost_table['wrong_char'] * \ abs(ord(output[char_index]) - ord(targets[input_string_index][char_index])) except IndexError: # Index out of range, one string is done; abort. break divergence_index = False if len(output) > len(targets[input_string_index]): # Output is longer, so we need to penalize for that as well for char_index in range(correction_offset, len(targets[input_string_index])): if output[char_index] != targets[input_string_index][char_index]: # Incorrectness penalty divergence_index = char_index if divergence_index is not False: # If there was a divergence, penalize based on incorrect chars within the correct length for char_index in range(divergence_index, len(targets[input_string_index])): # Add penalty per wrong char if output[char_index] != targets[input_string_index][char_index]: program_cost_addition += options.cost_table['wrong_char'] else: # Output is equal or shorter; apply penalty for missing chars and shortness if len(output) > 0: for char_index in range(correction_offset, len(output)): if output[char_index] != targets[input_string_index][char_index]: # We've found the divergence point divergence_index = char_index if divergence_index is not False: # There was a divergence; penalize based on incorrect chars within the correct length for char_index in range(divergence_index, len(output)): # Add penalty per wrong char if output[char_index] != targets[input_string_index][char_index]: program_cost_addition += options.cost_table['wrong_char'] if len(output) > len(targets[input_string_index]): # The output is too long. program_cost_addition += options.cost_table['too_long'] * \ (len(output) - len(targets[input_string_index])) elif len(output) < len(targets[input_string_index]): # The output is too short. program_cost_addition += options.cost_table['too_short'] * \ (len(targets[input_string_index]) - len(output)) if targets[input_string_index] in output: # Our desired output is in the output, penalize only for the extra chars program_cost_addition += (len(output) - len(targets[input_string_index])) * \ options.cost_table['extra_char'] elif output in targets[input_string_index]: # We have an incomplete output, penalize only for those missing chars program_cost_addition += (len(targets[input_string_index]) - len(output)) * \ options.cost_table['missing_char'] else: # Just find the intersection as sets, as a last ditch differentiator intersection = set_intersection(targets[input_string_index], output) program_cost_addition += len(output) - len(intersection) if options.ascii_only: # Skip this if we don't need it; it's SLOW for character in output: if character not in ascii_list: # Penalize for non-alphanumeric-ascii chars program_cost_addition += options.cost_table['non_ascii'] program_cost += program_cost_addition return program_cost
def evolve_bf_program(inputs, targets, options = default_evolve_options): """ Use the genetic algorithm to create a BF program that, given each input, computes the corresponding output. :param inputs: A list of inputs which produce a corresponding output :param targets: A list of outputs corresponding to inputs :param cull_ratio: What proportion of each generation should we kill? .5 induces no growth, while values less than .5 kill fewer, meaning that the population grows, and values greater than it kill more, :param population_size: The initial size of the population (P_0). May grow or shrink depending on other inputs :param initial_program_size: How long programs start out as being :param program_timeout: How long each organism may run for, ms :param verbose: Print on every generation, or no? """ # Check the inputs and outputs for nonstrings, convert them to strings # Generate an initial population current_population = generate_population(options.population_size, options.initial_program_size) # This is population P_0 and, at the beginning, P_g as well. interstitial_population = [] # This is I, the mutated but non-crossed generation new_population = [] # P_g+1 generations = 0 # This is g winner = ProgramReport("",0,0,"") last_cost = 0 flat_generations = 0 stagnant = False while True: # Test the cost of each member of P_g #print(current_population) cost_mapping = [] replacements_required = 0 # How many inviable programs need replacing. for program_index in range(0, len(current_population)): current_program = current_population[program_index] if stagnant: cost_options = options.cost_options._replace(time_cost=True) else: cost_options = options.cost_options current_program_cost = cost.cost_function(inputs, targets, current_program, options=cost_options) if current_program_cost is False: # In this case, the program is broken; cull it and replace it. replacements_required += 1 else: cost_mapping.append(MappedProgram(cost=current_program_cost, program=current_program)) # In this way, cost_mapping[0] is (cost_of_P_g[0], P_g[0]) # Test this program has a cost of zero; if so, return. We are done. if current_program_cost == 0: winner_output = "\n" for input_string_index in range(0, len(inputs)): winner_output += "{}:{}\n".format(inputs[input_string_index], interpret.evaluate(current_program, inputs[input_string_index])) winner = ProgramReport(program = current_program, cost = current_program_cost, generations = generations, output = winner_output) return winner # There is a winner, so break out of the loop # Sort the cost mapping to prepare for culling sorted_cost_mapping = sorted(cost_mapping, key=get_key_for_MappedProgram) # Figure out if we are stagnating if sorted_cost_mapping[0].cost == last_cost: flat_generations += 1 # We have been stagnant for this many generations if flat_generations >= options.stagnation_generations: stagnant = True else: stagnant = False flat_generations = 0 last_cost = sorted_cost_mapping[0].cost if options.verbose: # Report on the current winner: try: print("Gen. {}: {} programs, {} inviable. Min. cost {} \n{}\n{}\n".format(generations, len(current_population), replacements_required, sorted_cost_mapping[0].cost, sorted_cost_mapping[0].program, interpret.evaluate(sorted_cost_mapping[0].program, inputs[0], timeout=options.program_timeout))) except interpret.TimeoutAbortException: print("Timeout on gen. " + str(generations)) # Kill cull_ratio of P_g, starting with those with the largest cost, removing cost mappings in the process center_number = int(len(sorted_cost_mapping) * options.cull_ratio) culled_population = [mapped_program.program for mapped_program in sorted_cost_mapping[:center_number]] # Explanation: loop through sorted_cost_mapping, stripping cost mappings, until we hit center_number. # The rest are killed. # Now, we replace inviable programs with half mutated versions of the current winner and half new programs. for replacement_number in range(0, int(replacements_required/4)+1): culled_population.append(mutate.mutation_function(sorted_cost_mapping[0].program, options.mutate_options)) # Pick a random length of program, then generate replacements with that length. (culled_population # shuffled, so culled_population[0] is a random program. culled_population += generate_population(int(replacements_required/4)+1, len(culled_population[0])) # Replicate-with-errors from P_g to I interstitial_population = [mutate.mutation_function(program, options.mutate_options) for program in culled_population] #print(interstitial_population) # Cross P_g with I, creating P_g+1 shuffle(culled_population) shuffle(interstitial_population) for population_index in range(0, len(culled_population)): #print(population_index, len(culled_population), len(interstitial_population)) n, nprime = cross.crossing_function(culled_population[population_index], interstitial_population[population_index]) new_population.append(n) new_population.append(nprime) shuffle(new_population) # The new blood/old blood method grows the population; here we cut it down to size. new_population = new_population[:options.population_size] # g = g+1 current_population = new_population interstitial_population = [] new_population = [] generations += 1
def test_output(self): hello_world = "++++++++++[>+++++++>++++++++++>+++>+<<<<-]>++.>+.+++++++..+++.>++.<<+++++++++++++++.>.+++.------.--------.>+.>." result = interpret.evaluate(hello_world, "") self.assertEqual(result, "Hello World!\n")
def test_timeout(self): infinite_loop = "+[]" with self.assertRaises(interpret.TimeoutAbortException): # This should raise an exception for running too long interpret.evaluate(infinite_loop, "")
def test_input(self): cat = ",[.,]" cat_input = "This is both the input and the output." result = interpret.evaluate(cat, cat_input) self.assertEqual(result, cat_input)