Example #1
0
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
Example #2
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
Example #3
0
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)