Skip to content

Artificial Ant Problem

Source Code

import copy
from functools import partial
from pathlib import Path

import numpy
from deap_er import Fitness, Toolbox, creator, gp, tools

tools.rng.seed(1234)  # disables randomization


class AntSimulator:
    direction = ["north", "east", "south", "west"]
    dir_row = [1, 0, -1, 0]
    dir_col = [0, 1, 0, -1]

    def __init__(self, max_moves):
        self.max_moves = max_moves

    def _reset(self):
        self.matrix_exc = copy.deepcopy(self.matrix)
        self.row = self.row_start
        self.col = self.col_start
        self.dir = 1
        self.moves = 0
        self.eaten = 0

    @staticmethod
    def _if_then_else(condition, out1, out2):
        out1() if condition() else out2()

    @property
    def position(self):
        return self.row, self.col, self.direction[self.dir]

    def turn_left(self):
        if self.moves < self.max_moves:
            self.moves += 1
            self.dir = (self.dir - 1) % 4

    def turn_right(self):
        if self.moves < self.max_moves:
            self.moves += 1
            self.dir = (self.dir + 1) % 4

    def move_forward(self):
        if self.moves < self.max_moves:
            self.moves += 1
            self.row = (self.row + self.dir_row[self.dir]) % self.matrix_row
            self.col = (self.col + self.dir_col[self.dir]) % self.matrix_col
            if self.matrix_exc[self.row][self.col] == "food":
                self.eaten += 1
            self.matrix_exc[self.row][self.col] = "passed"

    def sense_food(self):
        ahead_row = (self.row + self.dir_row[self.dir]) % self.matrix_row
        ahead_col = (self.col + self.dir_col[self.dir]) % self.matrix_col
        return self.matrix_exc[ahead_row][ahead_col] == "food"

    def if_food_ahead(self, out1, out2):
        return partial(self._if_then_else, self.sense_food, out1, out2)

    def run(self, routine):
        self._reset()
        while self.moves < self.max_moves:
            routine()

    def parse_matrix(self, matrix):
        self.matrix = []
        for i, line in enumerate(matrix):
            self.matrix.append([])
            for j, col in enumerate(line):
                if col == "#":
                    self.matrix[-1].append("food")
                elif col == ".":
                    self.matrix[-1].append("empty")
                elif col == "S":
                    self.matrix[-1].append("empty")
                    self.row_start = self.row = i
                    self.col_start = self.col = j
                    self.dir = 1
        self.matrix_row = len(self.matrix)
        self.matrix_col = len(self.matrix[0])
        self.matrix_exc = copy.deepcopy(self.matrix)


def prog(*args):
    for arg in args:
        arg()


def prog2(out1, out2):
    return partial(prog, out1, out2)


def prog3(out1, out2, out3):
    return partial(prog, out1, out2, out3)


def evaluate(individual, ant, prim_set):
    routine = gp.compile_tree(individual, prim_set)
    ant.run(routine)
    return (ant.eaten,)  # The comma is essential here.


def setup():
    ant = AntSimulator(max_moves=600)
    trail_path = Path(__file__).with_name("art_ant_trail.txt")
    with trail_path.open() as trail_file:
        ant.parse_matrix(trail_file)

    pset = gp.PrimitiveSet("MAIN", 0)
    pset.add_primitive(ant.if_food_ahead, 2)
    pset.add_primitive(prog2, 2)
    pset.add_primitive(prog3, 3)
    pset.add_terminal(ant.move_forward)
    pset.add_terminal(ant.turn_left)
    pset.add_terminal(ant.turn_right)

    creator.create_type("FitnessMax", Fitness, weights=(1.0,))
    creator.create_type("Individual", gp.PrimitiveTree, fitness=creator.FitnessMax)

    toolbox = Toolbox()
    toolbox.register("expr_init", gp.gen_full, prim_set=pset, min_depth=1, max_depth=2)
    toolbox.register("individual", tools.init_iterate, creator.Individual, toolbox.expr_init)
    toolbox.register("population", tools.init_repeat, list, toolbox.individual)
    toolbox.register("evaluate", evaluate, ant=ant, prim_set=pset)
    toolbox.register("select", tools.sel_tournament, contestants=7)
    toolbox.register("mate", gp.cx_one_point)
    toolbox.register("expr_mut", gp.gen_full, min_depth=0, max_depth=2)
    toolbox.register("mutate", gp.mut_uniform, expr=toolbox.expr_mut, prim_set=pset)

    stats = tools.Statistics(lambda ind: ind.fitness.values)
    stats.register("avg", numpy.mean)
    stats.register("std", numpy.std)
    stats.register("min", numpy.min)
    stats.register("max", numpy.max)

    return toolbox, stats


def print_results(best_ind):
    if best_ind.fitness.values <= (50,):
        raise RuntimeError("Evolution failed to converge.")
    print("\nEvolution converged correctly.")


def main():
    toolbox, stats = setup()
    pop = toolbox.population(size=300)
    hof = tools.HallOfFame(1)
    args = {
        "toolbox": toolbox,
        "population": pop,
        "generations": 40,
        "cx_prob": 0.5,
        "mut_prob": 0.1,
        "hof": hof,
        "stats": stats,
        "verbose": True,  # prints stats
    }
    tools.ea_simple(**args)
    print_results(hof[0])


if __name__ == "__main__":
    main()

Food Trail

S###............................
...#............................
...#.....................###....
...#....................#....#..
...#....................#....#..
...####.#####........##.........
............#................#..
............#.......#...........
............#.......#........#..
............#.......#...........
....................#...........
............#................#..
............#...................
............#.......#.....###...
............#.......#..#........
.................#..............
................................
............#...........#.......
............#...#..........#....
............#...#...............
............#...#...............
............#...#.........#.....
............#..........#........
............#...................
...##..#####....#...............
.#..............#...............
.#..............#...............
.#......#######.................
.#.....#........................
.......#........................
..####..........................
................................