Autómatas celulares

En el pasado Juanlu ya habló sobre autómatas celulares en su artículo sobre el juego de la vida de Conway. Voy a recuperar un poco el concepto porque estoy estudiando sobre un tema e igual salen una serie de artículos de este tema y este sería el primero de ellos.

¿Qué es un autómata celular?

Como indican en la Wikipedia, es un modelo matemático y computacional para un sistema dinámico que evoluciona en pasos discretos. Es adecuado para modelar sistemas naturales que puedan ser descritos como una colección masiva de objetos simples que interactúen localmente unos con otros.

El autómata celular dispone de:

  • una rejilla, grilla o cuadrícula (en inglés lo encontraréis como lattice).
  • cada celda o célula puede tener un valor o estado en un espacio finito de estados.
  • cada celda o célula evolucionará en función de los valores de sus vecinos.
  • una función de transición que define como evolucionará cada celda en función de su valor actual y el valor de sus vecinos.

En al artículo de hoy no será importante pero las celdas de la frontera pueden tener distintos comportamientos que podemos definir en función del problema.

La función de transición

Existen muchas reglas para definir la función de transición. Yo solo os voy a presentar una considerando un autómata celular binario (\(k = 2\), tiene dos estados), la regla de paridad. Funciona de la siguiente forma. Se consideran solo las celdas que están al norte, al sur, al este y al oeste de la celda que vamos a actualizar. Cada celda puede tener solo dos estados, 0 o 1. Si la suma de las cuatro celdas adyacentes a la celda a actualizar es par su nuevo valor será 0 y si es impar será 1.

Por ejemplo, en la siguiente imagen tenemos varios posibles casos. A la izquierda tenemos el momento \(t\) y a la derecha el momento \(t + 1\) para la celda central (resaltada en gris). Si las celdas al norte, sur, este y oeste suman un valor par, la celda central, que es la celda evaluada, tendrá valor 0. Por contra, si las celdas adyacentes suman un valor impar la celda central se actualizará al valor 1.

Un autómata celular con Python

Voy a meter algo de código para implementar estas ideas. Más adelante lo explico más en detalle:

He creado una clase llamada Lattice. Esta clase se inicializa con el número de celdas que queremos que tenga nuestra rejilla, el número de pasos temporales que quieres que se creen y cómo quieres inicializar la rejilla. He dejado tres valores por defecto que se definen en los métodos _init_array0, que es el valor por defecto, e _init_array1 o _init_array2. Estos valores son solo unos ejemplos, puedes modificarlo como consideres.

El ejemplo usando _init_array0 en una rejilla de 51×51 se inicializa así:

Para el segundo, _init_array1, y tercer caso, _init_array2, tendremos las siguientes rejillas de inicialización, respectivamente.

El método apply_parity_rule es el que se encarga de actualizar la rejilla a cada paso temporal. Lo que hacemos es aplicar la función convolve2d que se encuentra en el módulo scipy.signal.

Como el kernel que uso son unos y ceros con los unos colocados en el norte, sur, este y oeste de la matriz lo que hace la convolución es sumar cuantas celdas alrededor de cada celda objetivo tiene un valor uno.

En la siguiente línea lo que hago es actualizar la rejilla con los nuevos valores después de aplicar la convolución, como la rejilla es binaria me vale con los valores True o False que me retorna la expresión ~(arr % 2 == 0) para actualizar los valores de la rejilla.

Los dos siguientes métodos, update y create_anim se explican más en detalle en el anterior artículo donde explicaba cómo hacer animaciones.

Una vez que tenemos lo anterior podemos crear un nuevo ejemplo y obtener una animación usando unas pocas líneas de código. Para la primera inicialización, _init_array0 podemos hacer lo siguiente:

Para obtener la animación en el notebook podemos hacer:

Para incrustar la animación en un nuevo documento HTML puedes revisar lo que mostré en el anterior artículo. El resultado de la animación sería algo así:

Si repetimos el mismo procedimiento pero usando init=1 en la instanciación:

El resultado para este paso quedaría así: