1from deap_er import creator
2from deap_er import tools
3from deap_er import base
4from deap_er import gp
5from functools import partial
6import random
7import numpy
8import copy
9
10
11random.seed(1234) # disables randomization
12
13
14class AntSimulator:
15 direction = ["north", "east", "south", "west"]
16 dir_row = [1, 0, -1, 0]
17 dir_col = [0, 1, 0, -1]
18
19 def __init__(self, max_moves):
20 self.max_moves = max_moves
21
22 def _reset(self):
23 self.matrix_exc = copy.deepcopy(self.matrix)
24 self.row = self.row_start
25 self.col = self.col_start
26 self.dir = 1
27 self.moves = 0
28 self.eaten = 0
29
30 @staticmethod
31 def _if_then_else(condition, out1, out2):
32 out1() if condition() else out2()
33
34 @property
35 def position(self):
36 return self.row, self.col, self.direction[self.dir]
37
38 def turn_left(self):
39 if self.moves < self.max_moves:
40 self.moves += 1
41 self.dir = (self.dir - 1) % 4
42
43 def turn_right(self):
44 if self.moves < self.max_moves:
45 self.moves += 1
46 self.dir = (self.dir + 1) % 4
47
48 def move_forward(self):
49 if self.moves < self.max_moves:
50 self.moves += 1
51 self.row = (self.row + self.dir_row[self.dir]) % self.matrix_row
52 self.col = (self.col + self.dir_col[self.dir]) % self.matrix_col
53 if self.matrix_exc[self.row][self.col] == "food":
54 self.eaten += 1
55 self.matrix_exc[self.row][self.col] = "passed"
56
57 def sense_food(self):
58 ahead_row = (self.row + self.dir_row[self.dir]) % self.matrix_row
59 ahead_col = (self.col + self.dir_col[self.dir]) % self.matrix_col
60 return self.matrix_exc[ahead_row][ahead_col] == "food"
61
62 def if_food_ahead(self, out1, out2):
63 return partial(self._if_then_else, self.sense_food, out1, out2)
64
65 def run(self, routine):
66 self._reset()
67 while self.moves < self.max_moves:
68 routine()
69
70 def parse_matrix(self, matrix):
71 self.matrix = list()
72 for i, line in enumerate(matrix):
73 self.matrix.append(list())
74 for j, col in enumerate(line):
75 if col == "#":
76 self.matrix[-1].append("food")
77 elif col == ".":
78 self.matrix[-1].append("empty")
79 elif col == "S":
80 self.matrix[-1].append("empty")
81 self.row_start = self.row = i
82 self.col_start = self.col = j
83 self.dir = 1
84 self.matrix_row = len(self.matrix)
85 self.matrix_col = len(self.matrix[0])
86 self.matrix_exc = copy.deepcopy(self.matrix)
87
88
89def prog(*args):
90 for arg in args:
91 arg()
92
93
94def prog2(out1, out2):
95 return partial(prog, out1, out2)
96
97
98def prog3(out1, out2, out3):
99 return partial(prog, out1, out2, out3)
100
101
102def evaluate(individual, ant, prim_set):
103 routine = gp.compile_tree(individual, prim_set)
104 ant.run(routine)
105 return ant.eaten, # The comma is essential here.
106
107
108def setup():
109 ant = AntSimulator(max_moves=600)
110 with open("art_ant_trail.txt") as trail_file:
111 ant.parse_matrix(trail_file)
112
113 pset = gp.PrimitiveSet("MAIN", 0)
114 pset.add_primitive(ant.if_food_ahead, 2)
115 pset.add_primitive(prog2, 2)
116 pset.add_primitive(prog3, 3)
117 pset.add_terminal(ant.move_forward)
118 pset.add_terminal(ant.turn_left)
119 pset.add_terminal(ant.turn_right)
120
121 creator.create("FitnessMax", base.Fitness, weights=(1.0,))
122 creator.create("Individual", gp.PrimitiveTree, fitness=creator.FitnessMax)
123
124 toolbox = base.Toolbox()
125 toolbox.register("expr_init", gp.gen_full, prim_set=pset, min_depth=1, max_depth=2)
126 toolbox.register("individual", tools.init_iterate, creator.Individual, toolbox.expr_init)
127 toolbox.register("population", tools.init_repeat, list, toolbox.individual)
128 toolbox.register("evaluate", evaluate, ant=ant, prim_set=pset)
129 toolbox.register("select", tools.sel_tournament, contestants=7)
130 toolbox.register("mate", gp.cx_one_point)
131 toolbox.register("expr_mut", gp.gen_full, min_depth=0, max_depth=2)
132 toolbox.register("mutate", gp.mut_uniform, expr=toolbox.expr_mut, prim_set=pset)
133
134 stats = tools.Statistics(lambda ind: ind.fitness.values)
135 stats.register("avg", numpy.mean)
136 stats.register("std", numpy.std)
137 stats.register("min", numpy.min)
138 stats.register("max", numpy.max)
139
140 return toolbox, stats
141
142
143def print_results(best_ind):
144 if not best_ind.fitness.values > (50,):
145 raise RuntimeError('Evolution failed to converge.')
146 print(f'\nEvolution converged correctly.')
147
148
149def main():
150 toolbox, stats = setup()
151 pop = toolbox.population(size=300)
152 hof = tools.HallOfFame(1)
153 args = dict(
154 toolbox=toolbox,
155 population=pop,
156 generations=40,
157 cx_prob=0.5,
158 mut_prob=0.1,
159 hof=hof,
160 stats=stats,
161 verbose=True # prints stats
162 )
163 tools.ea_simple(**args)
164 print_results(hof[0])
165
166
167if __name__ == "__main__":
168 main()