<SYSTEM_TASK:>
Given the following text description, write Python code to implement the functionality described below step by step
<END_TASK>
<USER_TASK:>
Description:
 <h1>Table of Contents<span class="tocSkip"></span></h1>
<div class="toc"><ul class="toc-item"><li><span><a href="#Intro" data-toc-modified-id="Intro-1"><span class="toc-item-num">1&nbsp;&nbsp;</span>Intro</a></span></li><li><span><a href="#1D-Automata" data-toc-modified-id="1D-Automata-2"><span class="toc-item-num">2&nbsp;&nbsp;</span>1D Automata</a></span><ul class="toc-item"><li><span><a href="#Animation" data-toc-modified-id="Animation-2.1"><span class="toc-item-num">2.1&nbsp;&nbsp;</span>Animation</a></span></li></ul></li><li><span><a href="#Conway’s-Game-Of-Life" data-toc-modified-id="Conway’s-Game-Of-Life-3"><span class="toc-item-num">3&nbsp;&nbsp;</span>Conway’s Game Of Life</a></span><ul class="toc-item"><li><span><a href="#Animation-in-Matplotlib" data-toc-modified-id="Animation-in-Matplotlib-3.1"><span class="toc-item-num">3.1&nbsp;&nbsp;</span>Animation in Matplotlib</a></span><ul class="toc-item"><li><span><a href="#Interactive-Animation" data-toc-modified-id="Interactive-Animation-3.1.1"><span class="toc-item-num">3.1.1&nbsp;&nbsp;</span>Interactive Animation</a></span></li></ul></li><li><span><a href="#Game-of-Life-3D" data-toc-modified-id="Game-of-Life-3D-3.2"><span class="toc-item-num">3.2&nbsp;&nbsp;</span>Game of Life 3D</a></span></li><li><span><a href="#Performances-Profiling" data-toc-modified-id="Performances-Profiling-3.3"><span class="toc-item-num">3.3&nbsp;&nbsp;</span>Performances Profiling</a></span></li></ul></li><li><span><a href="#Multiple-Neighborhood-CA" data-toc-modified-id="Multiple-Neighborhood-CA-4"><span class="toc-item-num">4&nbsp;&nbsp;</span>Multiple Neighborhood CA</a></span><ul class="toc-item"><li><span><a href="#Performances-Profiling" data-toc-modified-id="Performances-Profiling-4.1"><span class="toc-item-num">4.1&nbsp;&nbsp;</span>Performances Profiling</a></span></li><li><span><a href="#Generate-Video" data-toc-modified-id="Generate-Video-4.2"><span class="toc-item-num">4.2&nbsp;&nbsp;</span>Generate Video</a></span></li></ul></li></ul></div>
Intro
Cellular Automata are discrete mathematical models of Artificial Life.
Discrete because they exist in a discrete space, for example a 2D cell grid for 2-Dimensional automata.
Other primary properties of a cellular automaton
Step3:  1D Automata
Step4:  Animation
Step7:  Conway’s Game Of Life
Game Of Life (GOL) is possibly one of the most notorious examples of a cellular automata.
Defined by mathematician John Horton Conway, it plays out on a two dimensional grid for which each cell can be in one of two possible states. Starting from an initial grid configuration the system evolves at each unit step taking into account only the immediate preceding configuration. If for each cell we consider the eight surrounding cells as neighbors, the system transition can be defined by four simple rules.
Step8:  Animation in Matplotlib
Step9:  Interactive Animation
Step12:  Game of Life 3D
Regarding the grid structure and neighbors counting is purely a matter of using a 3-dimensional numpy array and related indexing.
For the rules, original GOL ones are not so stable for a 3D setup.
Step13:  Performances Profiling
Relying on the utility code for generic CA
Step14:  Multiple Neighborhood CA
Expands further on CA like GOL by considering more neighbors or multiple combinations of neighbors.
See Multiple Neighborhood Cellular Automata (MNCA)
Step15:  Performances Profiling
Step16:  Generate Video
<ASSISTANT_TASK:>
Python Code:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from PIL import Image, ImageDraw
import tqdm
from pathlib import Path
%matplotlib notebook
Explanation: <h1>Table of Contents<span class="tocSkip"></span></h1>
<div class="toc"><ul class="toc-item"><li><span><a href="#Intro" data-toc-modified-id="Intro-1"><span class="toc-item-num">1&nbsp;&nbsp;</span>Intro</a></span></li><li><span><a href="#1D-Automata" data-toc-modified-id="1D-Automata-2"><span class="toc-item-num">2&nbsp;&nbsp;</span>1D Automata</a></span><ul class="toc-item"><li><span><a href="#Animation" data-toc-modified-id="Animation-2.1"><span class="toc-item-num">2.1&nbsp;&nbsp;</span>Animation</a></span></li></ul></li><li><span><a href="#Conway’s-Game-Of-Life" data-toc-modified-id="Conway’s-Game-Of-Life-3"><span class="toc-item-num">3&nbsp;&nbsp;</span>Conway’s Game Of Life</a></span><ul class="toc-item"><li><span><a href="#Animation-in-Matplotlib" data-toc-modified-id="Animation-in-Matplotlib-3.1"><span class="toc-item-num">3.1&nbsp;&nbsp;</span>Animation in Matplotlib</a></span><ul class="toc-item"><li><span><a href="#Interactive-Animation" data-toc-modified-id="Interactive-Animation-3.1.1"><span class="toc-item-num">3.1.1&nbsp;&nbsp;</span>Interactive Animation</a></span></li></ul></li><li><span><a href="#Game-of-Life-3D" data-toc-modified-id="Game-of-Life-3D-3.2"><span class="toc-item-num">3.2&nbsp;&nbsp;</span>Game of Life 3D</a></span></li><li><span><a href="#Performances-Profiling" data-toc-modified-id="Performances-Profiling-3.3"><span class="toc-item-num">3.3&nbsp;&nbsp;</span>Performances Profiling</a></span></li></ul></li><li><span><a href="#Multiple-Neighborhood-CA" data-toc-modified-id="Multiple-Neighborhood-CA-4"><span class="toc-item-num">4&nbsp;&nbsp;</span>Multiple Neighborhood CA</a></span><ul class="toc-item"><li><span><a href="#Performances-Profiling" data-toc-modified-id="Performances-Profiling-4.1"><span class="toc-item-num">4.1&nbsp;&nbsp;</span>Performances Profiling</a></span></li><li><span><a href="#Generate-Video" data-toc-modified-id="Generate-Video-4.2"><span class="toc-item-num">4.2&nbsp;&nbsp;</span>Generate Video</a></span></li></ul></li></ul></div>
Intro
Cellular Automata are discrete mathematical models of Artificial Life.
Discrete because they exist in a discrete space, for example a 2D cell grid for 2-Dimensional automata.
Other primary properties of a cellular automaton:
* dimensionality of the space/world it lives in
* evolutionary rules
* neighborhood. For example in a 2D setting Moore Neighboorhood consists of the 8 surrounding cells.
* finite number of states
Also in general updates are applied instantly to all units.
End of explanation
class Automaton_1D:
    def __init__(self, n: int, states: int=2):
        
        1D Automaton
        :param n: number of cells
        
        self.n = n
        self.space = np.zeros(n, dtype=np.uint8)
        self.space[n//2] = 1
        #np.array([0,0,0,0,1,0,0,0,0,0])#np.random.choice(2, n)
    
    def update(self, rule: dict):
        
        Update automaton state
        
        tmp_space = self.space.copy()
        for i in range(self.n):
            neighbours = self.get_neighbours(i)
            tmp_space[i] = rule["".join([str(s) for s in neighbours])]
        self.space = tmp_space
        
    def get_neighbours(self, i: int):
        if i == 0:
            return np.insert(self.space[:2], 0, self.space[-1])
        elif i == self.n - 1:
            return np.insert(self.space[-2:], 2, self.space[0])
        else: 
            return self.space[max(0, i-1):i+2]
rule_0 = {'111': 1, '110': 1, '101': 1, '100': 1, '011': 1, '010': 1, '001': 1, '000': 0}
rule_sierpinski = {'111': 0, '110': 1, '101': 0, '100': 1, '011': 1, '010': 0, '001': 1, '000': 0}
rule_x = {'111': 0, '110': 0, '101': 0, '100': 1, '011': 1, '010': 1, '001': 1, '000': 0}
Explanation: 1D Automata
End of explanation
automaton_size = 100
automaton_1d = Automaton_1D(automaton_size)
nb_frames = 100
img = Image.new('RGB', (automaton_size, nb_frames), 'white')
draw = ImageDraw.Draw(img)
fig, ax = plt.subplots(dpi=50, figsize=(5, 5))
#im = ax.imshow(img)
plt.axis('off')
def animate(i, automaton, draw, img):
    space_img = Image.fromarray(automaton_1d.space.reshape(1, automaton_size)*255)
    img.paste(space_img, (0, i)) #mask=space_img
    ax.imshow(img)
    automaton.update(rule_x)
ani = animation.FuncAnimation(fig, animate, frames=nb_frames, interval=1, 
                              fargs=[automaton_1d, draw, img])
Explanation: Animation
End of explanation
class ConwayGOL_2D:
    def __init__(self, N):
        
        2D Conway Game of Life
        :param N: grid side size (resulting grid will be a NxN matrix)
        
        self.N = N
        self.grid = np.random.choice(2, (N,N))
    
    def update(self):
        
        Update status of the grid
        
        tmpGrid = self.grid.copy()
        for i in range(self.N):
            for j in range(self.N):
                neighbours = self.grid[max(0, i-1):min(i+2,self.N), max(0, j-1):min(j+2,self.N)].sum()
                neighbours -= self.grid[i, j]
                if self.grid[i, j] == 1:
                    if neighbours > 3 or neighbours < 2:
                        tmpGrid[i, j] = 0
                elif neighbours == 3:
                    tmpGrid[i, j] = 1
        self.grid = tmpGrid
Explanation: Conway’s Game Of Life
Game Of Life (GOL) is possibly one of the most notorious examples of a cellular automata.
Defined by mathematician John Horton Conway, it plays out on a two dimensional grid for which each cell can be in one of two possible states. Starting from an initial grid configuration the system evolves at each unit step taking into account only the immediate preceding configuration. If for each cell we consider the eight surrounding cells as neighbors, the system transition can be defined by four simple rules.
End of explanation
gol = ConwayGOL_2D(100)
fig, ax = plt.subplots(dpi=100, figsize=(5, 4))
im = ax.imshow(gol.grid, cmap='Greys', interpolation='nearest')
plt.axis('off')
def animate(i):
    gol.update()
    im.set_data(gol.grid)
#ani = animation.FuncAnimation(fig, animate, frames=1000, interval=100).save('basic_animation.mp4', writer=animation.FFMpegFileWriter(fps=30))
animation.FuncAnimation(fig, animate, frames=1000, interval=100)
#plt.show()
Explanation: Animation in Matplotlib
End of explanation
from ipywidgets import interact, widgets
def run_conwayGOL_2D(size):
    gol = ConwayGOL_2D(size)
    fig, ax = plt.subplots(dpi=100, figsize=(5, 4))
    im = ax.imshow(gol.grid, cmap='Greys', interpolation='nearest')
    plt.axis('off')
    def animate(i):
        gol.update()
        im.set_data(gol.grid)
    return animation.FuncAnimation(fig, animate, frames=1000, interval=100)
from ipywidgets import interact, widgets
interact(run_conwayGOL_2D, size=(10,100))
Explanation: Interactive Animation
End of explanation
class ConwayGOL_3D:
    def __init__(self, N):
        
        3D Conway Game of Life
        :param N: 3D grid side size (resulting grid will be a NxNxN matrix)
        
        self.N = N
        self.grid = np.random.choice(2, (N,N,N))
    
    def update(self):
        
        Update status of the grid
        
        tmpGrid = self.grid.copy()
        for z in range(self.N):
            for y in range(self.N):
                for x in range(self.N):
                    neighbours = self.grid[max(0, z-1):min(z+2,self.N),
                                           max(0, y-1):min(y+2,self.N), 
                                           max(0, x-1):min(x+2,self.N)].sum()
                    neighbours -= self.grid[z, y, x]
                    if self.grid[z, y, x] == 1:
                        if neighbours > 3 or neighbours < 2:
                            tmpGrid[z, y, x] = 0
                    elif neighbours == 3:
                        tmpGrid[z, y, x] = 1
        self.grid = tmpGrid
Explanation: Game of Life 3D
Regarding the grid structure and neighbors counting is purely a matter of using a 3-dimensional numpy array and related indexing.
For the rules, original GOL ones are not so stable for a 3D setup.
End of explanation
%load_ext autoreload
%autoreload 2
from Automaton import AutomatonND
rule = {'neighbours_count_born': 3,  # count required to make a cell alive
        'neighbours_maxcount_survive': 3,  # max number (inclusive) of neighbours that a cell can handle before dying
        'neighbours_mincount_survive': 2,  # min number (inclusive) of neighbours that a cell needs in order to stay alive
            }
nb_rows = nb_cols = 400
%%prun -s cumulative -l 30 -r
# We profile the cell, sort the report by "cumulative
# time", limit it to 30 lines
ca_2d = AutomatonND((nb_rows, nb_cols), rule, seed=11)
simulation_steps = 100
for step in tqdm.tqdm(range(simulation_steps)):
    ca_2d.update()
plt.imshow(ca_2d.grid)
Explanation: Performances Profiling
Relying on the utility code for generic CA
End of explanation
%load_ext autoreload
%autoreload 2
import cv2
from PIL import Image as IMG
from Automaton import AutomatonND, MultipleNeighborhoodAutomaton, get_kernel_2d_square
from mnca_utils import *
from ds_utils.video_utils import generate_video
configs = [
    {'neighbours_count_born': [0.300, 0.350],  
            'neighbours_maxcount_survive': [0.350, 0.400],  
            'neighbours_mincount_survive': [0.750, 0.850],  
            },
]
kernels = [
    get_circle_grid(17, 17, radius_minmax=[2,10]),
    
]
nb_rows = nb_cols = 200
mnca = MultipleNeighborhoodAutomaton((nb_rows, nb_cols), configs=configs, kernels=kernels, seed=11)
grid = get_circle_grid(mnca.shape[0], mnca.shape[1], radius_minmax=[0,50])
mnca.set_init_grid(grid)
simulation_steps = 40
fig, ax = plt.subplots(dpi=100, figsize=(5, 4))
im = ax.imshow(mnca.grid, cmap='Greys', interpolation='nearest')
plt.axis('off')
def animate(i):
    mnca.update()
    im.set_data(mnca.grid)
    
animation.FuncAnimation(fig, animate, frames=simulation_steps, interval=10)
Explanation: Multiple Neighborhood CA
Expands further on CA like GOL by considering more neighbors or multiple combinations of neighbors.
See Multiple Neighborhood Cellular Automata (MNCA)
End of explanation
%%prun -s cumulative -l 30 -r
# We profile the cell, sort the report by "cumulative
# time", limit it to 30 lines
configs = [
    {'neighbours_count_born': [0.300, 0.350],  
            'neighbours_maxcount_survive': [0.350, 0.400],  
            'neighbours_mincount_survive': [0.750, 0.850],  
            },
    {'neighbours_count_born': [0.430, 0.550],  
            'neighbours_maxcount_survive': [0.100, 0.280],  
            'neighbours_mincount_survive': [0.120, 0.150],  
            },
]
kernels = [
    get_circle_grid(17, 17, radius_minmax=[2,10]),
    get_circle_grid(9, 9, radius_minmax=[1,3]),
    
]
nb_rows = nb_cols = 200
simulation_steps = 40
mnca = MultipleNeighborhoodAutomaton((nb_rows, nb_cols), configs=configs, kernels=kernels, seed=11)
grid = get_circle_grid(mnca.shape[0], mnca.shape[1], radius_minmax=[0,50])
mnca.set_init_grid(grid)
for _ in range(simulation_steps):
    mnca.update()
Explanation: Performances Profiling
End of explanation
def base_frame_gen(frame_count, automaton):
    automaton.update()
    img = cv2.normalize(automaton.grid, None, 255, 0, norm_type=cv2.NORM_MINMAX, dtype=cv2.CV_8U)
    return img
nb_rows = nb_cols = 300
simulation_steps = 120
automaton_name = 'mca_6polygon_kernel_fill_radinc'
out_path = Path.home() / f'Documents/graphics/generative_output/mnca/{automaton_name}/{nb_rows}x{nb_cols}_{simulation_steps}'
out_path.mkdir(exist_ok=False, parents=True)
img_num = [25,3,6,9,10,11,12,15,16,17]
with open(str(out_path / 'logs.txt'), 'w+') as f:
    for i in range(10):
        configs = [
            {'neighbours_count_born': [0.300, 0.350],  
                    'neighbours_maxcount_survive': [0.350, 0.400],  
                    'neighbours_mincount_survive': [0.750, 0.850],  
                    },
#         {'neighbours_count_born': [0.430, 0.550],  
#                 'neighbours_maxcount_survive': [0.100, 0.280],  
#                 'neighbours_mincount_survive': [0.120, 0.150],  
#                 },
        ]
        
#         grid1 = get_polygon_mask(17, 17, 4, 4, fill=0)
#         grid2 = get_polygon_mask(17, 17, 4, 1, fill=0)
#         grid3 = get_polygon_mask(17, 17, 4, 10, fill=0)
#         hexa_grid = (grid1 | grid2 | grid3)
#         img_path = Path.home() / 'Documents/graphics/generative_output/flat_hexa_logo/9/run_{img_num[i]}.png'
#         hexa_grid = get_image_init_grid(img_path, (17, 17))
        
        kernels = [
            #hexa_grid,
            get_polygon_mask(17, 17, segments=6, radius=i+1, fill=1)
            #get_circle_grid(17, 17, radius_minmax=[1+i,5+i]),
            #get_circle_grid(9, 9, radius_minmax=[1,3]),
        ]
        automaton = MultipleNeighborhoodAutomaton((nb_rows, nb_cols), configs=configs, kernels=kernels, seed=i)
        img_path = Path.home() / 'Documents/graphics/generative_output/flat_hexa_logo/9/run_{img_num[i]}.png'
        #grid = get_image_init_grid(img_path, automaton.shape)
        #grid = get_perlin_grid(automaton.shape, 50, seed=i)
        grid = get_circle_grid(automaton.shape[0], automaton.shape[1], radius_minmax=[0,80])
        automaton.set_init_grid(grid)
        generate_video(str(out_path/f'run_{i}.mp4'), (automaton.shape[1], automaton.shape[0]),
                       frame_gen_fun = lambda i: base_frame_gen(i, automaton),
                       nb_frames = simulation_steps, is_color=False)
        f.write(str(configs) + '\n')
grid = get_polygon_mask(17, 17, segments=120, radius=6, fill=0)
plt.imshow(grid)
Explanation: Generate Video
End of explanation
<END_TASK>