|
| 1 | +#!/usr/bin/env python |
| 2 | +# coding: utf-8 |
| 3 | + |
| 4 | +# # Requirements |
| 5 | + |
| 6 | +import argparse |
| 7 | +import random |
| 8 | +import sys |
| 9 | + |
| 10 | +# # Implementation |
| 11 | + |
| 12 | +# First we define a class to evolve the cellular autotomaton. A runner is created for a specific rule, specified by a number between 0 and 255. For instance, rule 47 would translate to: |
| 13 | +# * $000 \mapsto 1$ |
| 14 | +# * $001 \mapsto 1$ |
| 15 | +# * $010 \mapsto 1$ |
| 16 | +# * $011 \mapsto 1$ |
| 17 | +# * $100 \mapsto 0$ |
| 18 | +# * $101 \mapsto 1$ |
| 19 | +# * $110 \mapsto 0$ |
| 20 | +# * $111 \mapsto 0$ |
| 21 | + |
| 22 | +class AutomatonRunner: |
| 23 | + |
| 24 | + def __init__(self, rule_nr): |
| 25 | + self._compute_rules(rule_nr) |
| 26 | + |
| 27 | + def _compute_rules(self, rule_nr): |
| 28 | + self._rules = [] |
| 29 | + for _ in range(8): |
| 30 | + self._rules.append(rule_nr % 2) |
| 31 | + rule_nr //= 2 |
| 32 | + |
| 33 | + def _apply_rule(self, environment): |
| 34 | + env_nr = 4*environment[0] + 2*environment[1] + environment[2] |
| 35 | + return self._rules[env_nr] |
| 36 | + |
| 37 | + def _make_environment(self, automaton, i): |
| 38 | + if 0 < i < len(automaton) - 1: |
| 39 | + return automaton[i - 1:i + 2] |
| 40 | + elif i == 0: |
| 41 | + return [automaton[-1]] + automaton[:2] |
| 42 | + elif i == len(automaton) - 1: |
| 43 | + return automaton[i - 1:] + [automaton[0]] |
| 44 | + |
| 45 | + def next_generation(self, automaton): |
| 46 | + ng_automaton = [] |
| 47 | + for i in range(len(automaton)): |
| 48 | + environment = self._make_environment(automaton, i) |
| 49 | + ng_automaton.append(self._apply_rule(environment)) |
| 50 | + return ng_automaton |
| 51 | + |
| 52 | + def evolve(self, automaton, nr_generations): |
| 53 | + generations = [automaton] |
| 54 | + for _ in range(nr_generations): |
| 55 | + generations.append(self.next_generation(generations[-1])) |
| 56 | + return generations |
| 57 | + |
| 58 | + def __str__(self): |
| 59 | + auto_str = '' |
| 60 | + for i, result in enumerate(self._rules): |
| 61 | + auto_str += f'{i//4 % 2}{i//2 % 2}{i % 2} -> {result}\n' |
| 62 | + return auto_str |
| 63 | + |
| 64 | + |
| 65 | +def automaton2str(automaton): |
| 66 | + return ''.join(str(c) for c in automaton).replace('0', ' ').replace('1', 'X') |
| 67 | + |
| 68 | + |
| 69 | +if __name__ == '__main__': |
| 70 | + arg_parser = argparse.ArgumentParser(description='run cellular automaton') |
| 71 | + arg_parser.add_argument('--nr_cells', type=int, default=10, |
| 72 | + help='number of cells in automaton') |
| 73 | + arg_parser.add_argument('--nr_steps', type=int, default=50, |
| 74 | + help='number of evolution steps') |
| 75 | + arg_parser.add_argument('--rule_nr', type=int, default=129, |
| 76 | + help='number between 0 and 255 that defines the rul') |
| 77 | + arg_parser.add_argument('--seed', type=int, help='random number generator seed') |
| 78 | + arg_parser.add_argument('--verbose', action='store_true', |
| 79 | + help='verbose output for debugging') |
| 80 | + options = arg_parser.parse_args() |
| 81 | + if options.seed: |
| 82 | + random.seed(options.seed) |
| 83 | + runner = AutomatonRunner(options.rule_nr) |
| 84 | + if options.verbose: |
| 85 | + print(runner, file=sys.stderr) |
| 86 | + automaton = random.choices((0, 1), k=options.nr_cells) |
| 87 | + if options.verbose: |
| 88 | + print(automaton2str(automaton), file=sys.stderr) |
| 89 | + generations = runner.evolve(automaton, options.nr_steps) |
| 90 | + for generation in generations: |
| 91 | + print(automaton2str(generation)) |
0 commit comments