Advertisement
Guest User

Brainfuck Program Generator

a guest
Jul 14th, 2013
315
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C++ 13.80 KB | None | 0 0
  1. /************************************************************************************************************************************************
  2.  *
  3.  * This program attemps to write programs of its own in the brainfuck language, using a genetic algorithm.
  4.  * Since brainfuck isn't fully defined, some assumptions had to be made:
  5.  *
  6.  *      -The size of the 'tape' holding cells will be fixed-length (1000 cells, chosesn arbitrarily). This is primarily for a speed boost.
  7.  *      -Each cell can hold an unsigned char (so up to a value of 255). Going over will cause wrap-around.
  8.  *
  9.  * A set of inputs and outputs will be fed to each potential solution.
  10.  * If the genome can get matching output for the specified inputs, it will have written the correct program.
  11.  * For example, if you want a brainfuck program that halves integers, you can feed the GA the following sets of inputs and outputs:
  12.  *
  13.  *      I: 10, O: 5
  14.  *      I: 22, O: 11
  15.  *      I: 34, O: 17
  16.  *
  17.  * The fitness function will take into account how close the output matches, and how concise the program is.
  18.  * Shorter programs receive a slight bonus.
  19.  *
  20.  *************************************************************************************************************************************************/
  21. #include <iostream>
  22. #include <string>
  23. #include <map>
  24. #include <cstdlib>
  25. #include <ctime>
  26. #include <cmath>
  27. #include "interpreter.h"
  28.  
  29. /* Don't modify this group of constants. */
  30. const int CHAR_SIZE = 255;  // The max value of a 'cell' in the memory tape
  31. const int NUM_MUTATIONS = 3;  // The number of types of mutations: (ADD, DELETE, CHANGE)
  32. const char INSTRUCTIONS[] = {'+', '-', '>', '<', '[', ']', '.'};  // The list of brainfuck instructions.
  33. const int NUM_INSTRUCTIONS = (sizeof(INSTRUCTIONS) / sizeof(INSTRUCTIONS[0]));  // Holds the number of instructions allowed.
  34. const int NUM_CHILDREN = 2;  // Number of children two parents create upon reproduction.
  35.  
  36. /* Modify any constants below. */
  37. const int MAX_GENERATIONS = 1000000000;  // The number of generations to repeat the genetic algorithm process.
  38. const int POP_SIZE = 5;  // The size of the population. This always remains the same between generations.
  39. const int MIN_PROGRAM_SIZE = 10;  // The minimum size a possible program can be.
  40. const int MAX_PROGRAM_SIZE = 500;  // The maximum size a possible program can be.
  41. const double MUTATION_RATE = 0.01;  // The chance of a 'gene' in a child being mutated
  42.  
  43. const double LENGTH_PENALTY = 0.001;  // The size of the program is multiplied by this then added to score.
  44. const int DISPLAY_RATE = 10000;  // How often to display the best program so far.
  45.  
  46. const std::string GOAL_OUTPUT = "Artificial";
  47. const int GOAL_OUTPUT_SIZE = GOAL_OUTPUT.length();
  48.  
  49. /*const std::string INPUT_SETS[] = {"10", "4", "16"};
  50. const int NUM_INPUT_SETS = 3;*/
  51.  
  52.  
  53. /* Generates a random double between low and high. */
  54. double get_random(double low, double high)
  55. {
  56.     return low + static_cast<double>(rand()) / static_cast<double>(static_cast<double>(RAND_MAX) / (high - low));
  57. }
  58.  
  59. int get_random_int(int low, int high)
  60. {
  61.     return (rand() % (high - low + 1)) + low;
  62. }
  63.  
  64.  
  65. /* Returns a random brainfuck instruction. */
  66. char get_random_instruction()
  67. {
  68.     return INSTRUCTIONS[get_random_int(0, NUM_INSTRUCTIONS - 1)];
  69. }
  70.  
  71.  
  72. void add_instruction(std::string &program, int index)
  73. {
  74.     std::string instr(1, get_random_instruction());  // Need to convert char to string for use by insert
  75.     if((program.length() + 1) <= MAX_PROGRAM_SIZE)
  76.         program.insert(index, instr);
  77. }
  78.  
  79.  
  80. void remove_instruction(std::string &program, int index)
  81. {
  82.     // Subtract 2 instead of 1 to account for the off-by-one difference between a string length and the last element index
  83.     if((program.length() - 2) >= MIN_PROGRAM_SIZE)
  84.         program.erase(index, 1);
  85. }
  86.  
  87.  
  88. void mutate_instruction(std::string &program, int index)
  89. {
  90.     program[index] = get_random_instruction();
  91. }
  92.  
  93.  
  94. /* Creates a random program by first randomly determining its size and then adding that many instructions randomly. */
  95. std::string create_random_program()
  96. {
  97.     std::string program;
  98.     int program_size = get_random_int(MIN_PROGRAM_SIZE, MAX_PROGRAM_SIZE);
  99.  
  100.     for(int i = 1; i <= program_size; ++i)
  101.         program += get_random_instruction();
  102.  
  103.     return program;
  104. }
  105.  
  106.  
  107. /* Creates the first generation's population by randomly creating programs. */
  108. void initialize_population(std::string programs[])
  109. {
  110.     for(int i = 0; i < POP_SIZE; ++i)
  111.         programs[i] = create_random_program();
  112. }
  113.  
  114.  
  115. /* The Fitness Function. Determines how 'fit' a program is using a few different criteria. */
  116. double calculate_fitness(const std::string &program, Interpreter &bf, std::map<std::string, double> &programs_scored)
  117. {
  118.     // If program is in the cache, return that value instead of recalculating.
  119.     if(programs_scored.find(program) != programs_scored.end()) return programs_scored[program];
  120.  
  121.     double score = 0;
  122.     double max_score = GOAL_OUTPUT_SIZE * CHAR_SIZE;  // The score of the worse program possible.
  123.  
  124.     std::string output = bf.run(program, "");
  125.  
  126.     std::string min_str = (output.length() < GOAL_OUTPUT.length()) ? output : GOAL_OUTPUT;
  127.     std::string max_str = (output.length() >= GOAL_OUTPUT.length()) ? output : GOAL_OUTPUT;
  128.  
  129.     // The more each character of output is similar to its corresponding character in the goal output, the higher the score.
  130.     for(int i = 0; i < max_str.length(); ++i)
  131.     {
  132.         int output_char = (i < min_str.length()) ? min_str[i] : max_str[i] + CHAR_SIZE;
  133.         score += abs(output_char - max_str[i]);
  134.     }
  135.  
  136.     score += (program.length() * LENGTH_PENALTY);  // Impose a slight penalty for longer programs.
  137.  
  138.     // Impose a large penalty for error programs, but still allow them a chance at reproduction for genetic variation.
  139.     if(output == "Error") return 1;//return (max_score * 0.01);
  140.  
  141.     // Subtract from max score because the lower the score, the better.
  142.     double final_score = max_score - score;
  143.  
  144.     programs_scored[program] = final_score;  // Cache the result to save for later.
  145.  
  146.     return final_score;
  147. }
  148.  
  149.  
  150. /* Generates a fitness score for each program in the population.
  151.    This is done by running the program through the brainfuck interpreter and scoring its output.
  152.    Also returns the best program produced. */
  153. std::string score_population(std::string programs[], double scores[], int &worst_index, Interpreter &bf, std::map<std::string, double> &programs_scored)
  154. {
  155.     std::string best_program;
  156.     double best_score = 0;
  157.     double worst_score = 999;
  158.  
  159.     for(int i = 0; i < POP_SIZE; ++i)
  160.     {
  161.         scores[i] = calculate_fitness(programs[i], bf, programs_scored);
  162.         if(scores[i] > best_score)
  163.         {
  164.             best_program = programs[i];
  165.             best_score = scores[i];
  166.         }
  167.         else if(scores[i] < worst_score)
  168.         {
  169.             worst_index = i;
  170.             worst_score = scores[i];
  171.         }
  172.     }
  173.     return best_program;
  174. }
  175.  
  176.  
  177. /* Adds every program's fitness score together. */
  178. double pop_score_total(const double scores[])
  179. {
  180.     double total = 0;
  181.     for(int i = 0; i < POP_SIZE; ++i)
  182.         total += scores[i];
  183.     return total;
  184. }
  185.  
  186.  
  187. /* Selects a parent to mate using fitness proportionate selection. Basically, the more fit a program it is, the more likely to be selected. */
  188. std::string select_parent(std::string programs[], double scores[], std::string other_parent = "")
  189. {
  190.     double program_chances[POP_SIZE];  // Holds each program's chance of being selected (a # between 0 and 1)
  191.     double score_total = pop_score_total(scores);  // The sum of all programs' fitness scores
  192.     double rand_val = get_random(0.0, 1.0);
  193.  
  194.     for(int i = 0; i < POP_SIZE; ++i)
  195.     {
  196.         double prev_chance = ((i - 1) < 0) ? 0 : program_chances[i - 1];
  197.         // We add the previous program's chance to this program's chance because that is its range of being selected.
  198.         // The higher the fitness score, the bigger this program's range is.
  199.         program_chances[i] = (scores[i] / score_total) + (prev_chance);
  200.  
  201.         // Need to subtract a small amount from rand_val due to floating-point precision errors. Without it, equality check could fail.
  202.         if(program_chances[i] >= (rand_val - 0.001) && (programs[i] != other_parent))
  203.             return programs[i];
  204.     }
  205.     // If the other parent was the last program in the list, we might get here.
  206.     // In that case, just return the 1st program.
  207.     return programs[0];
  208.    /* // Debugging output. Function should never get to this part.
  209.     std::cout << "Score total: " << score_total << std::endl;
  210.     std::cout << "Last score: " << scores[POP_SIZE - 1] << std::endl;
  211.     std::cout << "Rand val: " << rand_val << std::endl;
  212.     std::cout << "Last chance: " << program_chances[POP_SIZE - 1] << std::endl;
  213.     std::cin.get();*/
  214. }
  215.  
  216.  
  217. std::string mutate(std::string child)
  218. {
  219.     int program_length = child.length();
  220.     for(int i = 0; i < child.length(); ++i)
  221.     {
  222.         bool do_mutate = MUTATION_RATE >= get_random(0.0, 1.0) ? true : false;
  223.         if(do_mutate)
  224.         {
  225.             int mutation_type = get_random_int(1, NUM_MUTATIONS);
  226.             switch(mutation_type)
  227.             {
  228.             case 1:
  229.                 mutate_instruction(child, i);
  230.                 break;
  231.             case 2:
  232.                 add_instruction(child, i);
  233.                 break;
  234.             case 3:
  235.                 remove_instruction(child, i);
  236.                 break;
  237.             default:
  238.                 break;
  239.             }
  240.         }
  241.     }
  242.     return child;
  243. }
  244.  
  245.  
  246. /* Performs crossover between two parents to produce two children. */
  247. void mate(std::string parent1, std::string parent2, std::string children[])
  248. {
  249.     // We need to find which program is biggest
  250.     std::string min_str = (parent1.length() < parent2.length()) ? parent1 : parent2;
  251.     std::string max_str = (parent1.length() >= parent2.length()) ? parent1 : parent2;
  252.  
  253.     // Determine a crossover point at random
  254.     int crosspoint = get_random_int(1, max_str.length() - 1);
  255.  
  256.     // Find the substring of the program after the crossover point
  257.     std::string max_str_contrib = max_str.substr(crosspoint);
  258.     // Then erase after that point
  259.     max_str.erase(crosspoint);
  260.  
  261.     // If the crosspoint is less than the length of the smaller program, then we need to combine part of it with the larger program.
  262.     // If not, then we do nothing and just take part of the larger program and add it to it.
  263.     if(crosspoint <= min_str.length())
  264.     {
  265.         max_str += min_str.substr(crosspoint);
  266.         min_str.erase(crosspoint);
  267.     }
  268.  
  269.     // Add the 2nd part of the larger program to the smaller program.
  270.     min_str += max_str_contrib;
  271.  
  272.     // Call the mutate function on the children which has a small chance of actually causing a mutation.
  273.     children[0] = mutate(min_str);
  274.     children[1] = mutate(max_str);
  275. }
  276.  
  277.  
  278. bool program_exists(std::string program, std::string programs[])
  279. {
  280.     for(int i = 0; i < POP_SIZE; ++i)
  281.     {
  282.         if(program == programs[i])
  283.             return true;
  284.     }
  285.     return false;
  286. }
  287.  
  288.  
  289. void replace_program(std::string parent, std::string child, std::string programs[])
  290. {
  291.     for(int i = 0; i < POP_SIZE; ++i)
  292.     {
  293.         if(parent == programs[i])
  294.         {
  295.             programs[i] = child;
  296.             break;
  297.         }
  298.     }
  299. }
  300.  
  301.  
  302. int main()
  303. {
  304.     // Initialize the brainfuck interpreter.
  305.     Interpreter brainfuck;
  306.     srand(time(0));
  307.  
  308.     std::string programs[POP_SIZE];  // All programs of the current generation.
  309.     double fitness_scores[POP_SIZE];  // The fitness scores of all programs.
  310.  
  311.     // Below variables used for memoization; cache results that have already been computed
  312.     // Not sure exactly if it is any fater lolz, due to having to search that the program is in the cache every iteration
  313.     std::map<std::string, double> programs_scored;
  314.  
  315.     // Generates POP_SIZE number of random programs
  316.     initialize_population(programs);
  317.  
  318.     std::string best_program;
  319.     for(int g = 0; g < MAX_GENERATIONS; ++g)
  320.     {
  321.         int worst_program_index = 0;
  322.  
  323.         // Generate the scores of each program.
  324.         best_program = score_population(programs, fitness_scores, worst_program_index, brainfuck, programs_scored);
  325.  
  326.         // Select two parents randomly using fitness proportionate selection
  327.         std::string parent1 = select_parent(programs, fitness_scores);
  328.         std::string parent2 = select_parent(programs, fitness_scores, parent1);
  329.  
  330.         // Mate them to create children
  331.         std::string children[NUM_CHILDREN];
  332.         mate(parent1, parent2, children);
  333.  
  334.         // Replace the parent programs with the children. We replace the parents to prevent premature convergence.
  335.         // This works because by replacing the parents, which are most similar to the children, genetic diversity is maintained.
  336.         // If the parents were not replaced, the population would quickly fill with similar genetic information.
  337.         replace_program(parent1, children[0], programs);
  338.         replace_program(parent2, children[1], programs);
  339.  
  340.         // Remove the parent programs from the cache to save space
  341.         programs_scored.erase(parent1);
  342.         programs_scored.erase(parent2);
  343.  
  344.         // Replace the worst program with the best program if it was replaced by its child. (Elitism).
  345.         // This is done to ensure the best program is never lost.
  346.         if(!program_exists(best_program, programs))
  347.             programs[worst_program_index] = best_program;
  348.  
  349.         // Report on the current best program every so often.
  350.         if(!(g % DISPLAY_RATE))
  351.         {
  352.             std::cout << "\n\nBest program evolved so far: " << std::endl;
  353.             std::cout << best_program << std::endl;
  354.  
  355.             std::cout << "\nOutput: " << brainfuck.run(best_program, "") << std::endl;
  356.         }
  357.     }
  358.  
  359.     return 0;
  360. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement