Skip to content

Commit d233172

Browse files
committed
Program Evolution
1 parent b58b294 commit d233172

File tree

8 files changed

+336
-26
lines changed

8 files changed

+336
-26
lines changed
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
#!/usr/bin/env python
2+
3+
import sys
4+
5+
from character_set import (CHARACTER_SET_SIZE, CHARACTER_TO_VALUE,
6+
VALUE_TO_CHARACTER)
7+
8+
9+
MEMORY_SIZE = 30000
10+
11+
12+
class SegmentationFault(Exception):
13+
pass
14+
15+
16+
class Break(Exception):
17+
pass
18+
19+
20+
class Continue(Exception):
21+
pass
22+
23+
24+
def _increment_data_pointer():
25+
global memory_index
26+
memory_index += 1
27+
if memory_index >= MEMORY_SIZE:
28+
raise SegmentationFault
29+
30+
31+
def _decrement_data_pointer():
32+
global memory_index
33+
memory_index -= 1
34+
if memory_index < 0:
35+
raise SegmentationFault
36+
37+
38+
def _increment_val():
39+
memory[memory_index] = (memory[memory_index] + 1) % CHARACTER_SET_SIZE
40+
41+
42+
def _decrement_val():
43+
memory[memory_index] = (memory[memory_index] - 1) % CHARACTER_SET_SIZE
44+
45+
46+
def _output_val():
47+
sys.stdout.write(VALUE_TO_CHARACTER[memory[memory_index]])
48+
49+
50+
def _input_val():
51+
while True:
52+
user_input = raw_input("Enter a single character:")
53+
if len(user_input) == 1:
54+
memory[memory_index] = CHARACTER_TO_VALUE[user_input]
55+
break
56+
57+
58+
def _while_non_zero():
59+
if memory[memory_index] == 0:
60+
raise Break
61+
62+
63+
def _end_while():
64+
if memory[memory_index] != 0:
65+
raise Continue
66+
67+
68+
COMMANDS = {">": _increment_data_pointer,
69+
"<": _decrement_data_pointer,
70+
"+": _increment_val,
71+
"-": _decrement_val,
72+
".": _output_val,
73+
",": _input_val,
74+
"[": _while_non_zero,
75+
"]": _end_while}
76+
77+
78+
def _find_braces(program_string):
79+
find_opening_brace = {}
80+
find_closing_brace = {}
81+
82+
opening_braces = []
83+
for (position, character) in enumerate(program_string):
84+
if character == "[":
85+
opening_braces.append(position)
86+
87+
elif character == "]":
88+
try:
89+
opening_brace_position = opening_braces.pop()
90+
except IndexError:
91+
opening_brace_position = None
92+
93+
find_opening_brace[position] = opening_brace_position
94+
95+
if opening_brace_position is not None:
96+
find_closing_brace[opening_brace_position] = position
97+
98+
if len(opening_braces) != 0:
99+
for opening_brace_position in opening_braces:
100+
find_closing_brace[opening_brace_position] = None
101+
102+
return find_opening_brace, find_closing_brace
103+
104+
105+
def _initialise_memory():
106+
global memory, memory_index
107+
memory = [0] * MEMORY_SIZE
108+
memory_index = 0
109+
110+
111+
def bf_interpreter(program_string):
112+
_initialise_memory()
113+
# Generate mappings to lookup location of matching braces.
114+
find_opening_brace, find_closing_brace = _find_braces(program_string)
115+
116+
program_position = 0
117+
while True:
118+
try:
119+
instruction = program_string[program_position]
120+
except IndexError:
121+
# We've reached the end of the program.
122+
break
123+
124+
try:
125+
COMMANDS[instruction]()
126+
except KeyError:
127+
# Brainfuck just ignores any characters that are not in it's operator set.
128+
pass
129+
except SegmentationFault:
130+
# Treat this as a valid way to trigger program exit.
131+
break
132+
except Break:
133+
program_position = find_closing_brace[program_position]
134+
except Continue:
135+
program_position = find_opening_brace[program_position]
136+
137+
if program_position is None:
138+
# i.e. program is syntactically invalid as there is no matching
139+
# opening/closing brace to go to - exit.
140+
break
141+
else:
142+
program_position += 1
143+
144+
145+
if __name__ == "__main__":
146+
bf_interpreter(sys.argv[1])
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#!/usr/bin/env python
2+
3+
import string
4+
5+
6+
CHARACTER_SET = string.lowercase # change to string.printable for grater output variety
7+
CHARACTER_SET_SIZE = len(CHARACTER_SET)
8+
CHARACTER_TO_VALUE = {}
9+
VALUE_TO_CHARACTER = {}
10+
11+
12+
for index, printable_char in enumerate(CHARACTER_SET):
13+
CHARACTER_TO_VALUE[printable_char] = index
14+
VALUE_TO_CHARACTER[index] = printable_char

05-genetic-algorithms/python/ga/function_evolution.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
INBREEDING_RATE = 0.25
1111
MAX_TREE_DEPTH = 25
12+
POPULATION_SIZE = 100
1213

1314

1415
def target(x):
@@ -164,4 +165,4 @@ def stop_condition(candidate):
164165
breed_func=breed,
165166
fitness_func=calc_fitness,
166167
stop_condition=stop_condition,
167-
population_size=100)
168+
population_size=POPULATION_SIZE)

05-genetic-algorithms/python/ga/ga.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ def run_genetic_algorithm(spawn_func,
6868
for candidate in candidates:
6969
if stop_condition(candidate):
7070
end = time.time()
71-
print candidate
71+
print candidate.this
7272
print "Number of Iterations: %d" % num_iter
7373
print "Time Taken: %.1f seconds" % (end - start)
7474
sys.exit(0)
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
#!/usr/bin/env python
2+
3+
import random
4+
5+
6+
def generate_random_string(character_list, max_length):
7+
return "".join([random.choice(character_list)
8+
for i in xrange(random.randint(1, max_length))])
9+
10+
11+
def breed_strings(parent1, parent2, character_list, mutation_rate):
12+
(a1, a2) = split_string(parent1)
13+
(b1, b2) = split_string(parent2)
14+
child = mutate_string(a1 + b2, character_list, mutation_rate)
15+
return child
16+
17+
18+
def split_string(dna):
19+
return (dna[:len(dna)/2], dna[len(dna)/2:])
20+
21+
22+
def mutate_string(dna, character_list, mutation_rate):
23+
new_dna = dna
24+
if random.random() < mutation_rate:
25+
cut_point = random.randint(0, len(dna))
26+
new_char = random.choice(character_list)
27+
new_dna = dna[:cut_point-1] + new_char + dna[cut_point:]
28+
return new_dna
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
#!/usr/bin/env python
2+
3+
import contextlib
4+
import sys
5+
import StringIO
6+
7+
import ga
8+
from brainfuck import bf_interpreter
9+
from character_set import CHARACTER_TO_VALUE, CHARACTER_SET_SIZE
10+
from ga_utils import generate_random_string, breed_strings
11+
from timeout import timelimit, TimeoutError
12+
13+
14+
MAX_PROGRAM_LEN = 200
15+
PROGRAM_EXEC_TIMEOUT = 10
16+
WEIGHTED_COMMANDS = (2 * ["+", "+++", "+++++", "-", "---", "-----"]) + [">>>", ">", "<", "<<<", "[", "]", "."] # ","
17+
TARGET_PROGRAM_OUTPUT = "hello" # for speed, target characters are limited to lowercase letters (see character_set.py)
18+
MUTATION_RATE = 1
19+
POPULATION_SIZE = 20
20+
21+
22+
@contextlib.contextmanager
23+
def stdout_redirect(where):
24+
sys.stdout = where
25+
try:
26+
yield where
27+
finally:
28+
sys.stdout = sys.__stdout__
29+
30+
31+
def generate_random_program():
32+
return generate_random_string(WEIGHTED_COMMANDS, MAX_PROGRAM_LEN)
33+
34+
35+
@timelimit(PROGRAM_EXEC_TIMEOUT)
36+
def run(program):
37+
bf_interpreter(program)
38+
39+
40+
def character_fitness(output_char, target_char):
41+
if output_char == target_char:
42+
fitness = 1
43+
44+
else:
45+
output_char_val = CHARACTER_TO_VALUE[output_char]
46+
target_char_val = CHARACTER_TO_VALUE[target_char]
47+
48+
offset = abs(output_char_val - target_char_val)
49+
if output_char_val < target_char_val:
50+
wrapped_offset = (output_char_val + CHARACTER_SET_SIZE) - target_char_val
51+
else:
52+
wrapped_offset = (target_char_val + CHARACTER_SET_SIZE) - output_char_val
53+
54+
char_offset = min(offset, wrapped_offset)
55+
fitness = 1 - (float(char_offset) / CHARACTER_SET_SIZE)
56+
57+
return fitness
58+
59+
60+
def calculate_fitness(program):
61+
fitness = 0
62+
63+
try:
64+
with stdout_redirect(StringIO.StringIO()) as new_stdout:
65+
run(program)
66+
67+
except TimeoutError:
68+
print "timeout"
69+
fitness = -sys.maxint
70+
71+
else:
72+
new_stdout.seek(0)
73+
output = new_stdout.read()
74+
print output
75+
76+
for (output_char, target_char) in zip(output, TARGET_PROGRAM_OUTPUT):
77+
fitness += character_fitness(output_char, target_char)
78+
79+
return fitness
80+
81+
82+
def breed_programs(prog1, prog2):
83+
return breed_strings(prog1, prog2, WEIGHTED_COMMANDS, MUTATION_RATE)
84+
85+
86+
def stop_condition(candidate):
87+
if candidate.fitness == len(TARGET_PROGRAM_OUTPUT):
88+
return True
89+
else:
90+
return False
91+
92+
93+
if __name__ == "__main__":
94+
ga.run_genetic_algorithm(spawn_func=generate_random_program,
95+
breed_func=breed_programs,
96+
fitness_func=calculate_fitness,
97+
stop_condition=stop_condition,
98+
population_size=POPULATION_SIZE)
Lines changed: 11 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,20 @@
11
#!/usr/bin/env python
22

33
import ga
4-
import random
54
import string
65

6+
from ga_utils import generate_random_string, breed_strings
7+
78

89
TARGET = "I LOVE GENETIC ALGORITHMS"
10+
CHARACTERS = string.ascii_letters + " "
911
MUTATION_RATE = 0.75
1012
MAX_STRING_LENGTH = 100
13+
POPULATION_SIZE = 100
1114

1215

13-
def generate_random_string():
14-
return "".join([random.choice(string.ascii_letters + " ")
15-
for i in xrange(random.randint(1, MAX_STRING_LENGTH))])
16+
def generate_candidate():
17+
return generate_random_string(CHARACTERS, MAX_STRING_LENGTH)
1618

1719

1820
def calculate_fitness(dna):
@@ -25,23 +27,8 @@ def calculate_fitness(dna):
2527
return fitness
2628

2729

28-
def breed_strings(string1, string2):
29-
(a1, a2) = split_string(string1)
30-
(b1, b2) = split_string(string2)
31-
child_dna = mutate(a1 + b2)
32-
return child_dna
33-
34-
35-
def split_string(dna):
36-
return (dna[:len(dna)/2], dna[len(dna)/2:])
37-
38-
39-
def mutate(dna):
40-
new_dna = dna
41-
if random.random() < MUTATION_RATE:
42-
cut_point = random.randint(0, len(dna))
43-
new_dna = dna[:cut_point-1] + random.choice(string.ascii_letters + " ") + dna[cut_point:]
44-
return new_dna
30+
def crossover(string1, string2):
31+
return breed_strings(string1, string2, CHARACTERS, MUTATION_RATE)
4532

4633

4734
def stop_condition(candidate):
@@ -52,8 +39,8 @@ def stop_condition(candidate):
5239

5340

5441
if __name__ == "__main__":
55-
ga.run_genetic_algorithm(spawn_func=generate_random_string,
56-
breed_func=breed_strings,
42+
ga.run_genetic_algorithm(spawn_func=generate_candidate,
43+
breed_func=crossover,
5744
fitness_func=calculate_fitness,
5845
stop_condition=stop_condition,
59-
population_size=100)
46+
population_size=POPULATION_SIZE)

0 commit comments

Comments
 (0)