Autómatas celulares, Aplicación WinCA III
En este artículo, tercero de la serie, continúo explicando el funcionamiento de la aplicación WinCA, para la construcción y ejecución de autómatas celulares. Esta vez voy a mostrar el lenguaje utilizado para definir las transiciones entre los diferentes estados de las celdas del autómata.
Aquí puedes encontrar el primer artículo de la serie sobre la aplicación WinCA.
En este enlace puedes descargar los ejecutables de la aplicación WinCA, y en este otro puedes descargar el código fuente de la solución WinCA, escrita en CSharp con Visual Studio 2015.
En los autómatas celulares, las celdas pueden cambiar de un estado a otro, dependiendo de diferentes condiciones, generalmente relacionadas con las propiedades de la celda actual y las celdas vecinas. La aplicación WinCA utiliza un lenguaje con el que se pueden definir las expresiones que determinan el paso de un estado a otro cualquiera.
Definición del lenguaje
Números y cadenas de texto
Podemos utilizar números, que pueden tener decimales, separados por el caracter punto decimal (.). También cadenas de texto constantes, encerrándolas entre caracteres de dobles comillas ("). Para usar un caracter de comillas dobles dentro de la constante de texto, se debe preceder del carácter de escape (\).
Referencias a propiedades y variables
El lenguaje de expresiones está diseñado para utilizar las propiedades de los estados y las variables definidas con el editor de propiedades. Para hacer referencia a una propiedad del estado de la celda actual, se utiliza su nombre precedido por el caracter barra invertida (\), y para hacer referencia a una variable, se utiliza también su nombre precedido por el caracter guión bajo (_). Si se quiere obtener el resultado con un determinado tipo de datos, se puede añadir, después del nombre, el caracter dos puntos (:) seguido del nombre del tipo (int, double, bool, string), por ejemplo:
\Alive:bool
Indica el valor de la propiedad Alive del estado actual de la celda con el tipo forzado a booleano.
Las propiedades que suelen interesar más son las de las celdas vecinas de la actual. Para hacer referencia a una de las celdas vecinas en concreto, se utiliza el índice de la misma (que está indicado en el editor del autómata) encerrado entre corchetes ([]), separado del nombre de la propiedad por un punto (.), por ejemplo:
[1].\Alive
Hace referencia a la propiedad Alive del vecino con el índice 1. En lugar del número se puede utilizar también una expresión aritmética, que veremos más adelante.
Algunas funciones realizan un agregado de las propiedades de todas las celdas vecinas, por ejemplo un sumatorio. En estas funciones debemos utilizar el nombre de la propiedad precedido por el carácter de punto y coma (;), por ejemplo:
;\Alive
Hay que tener cuidado de no olvidar utilizar el carácter ;, puesto que en una función agregada también se admite utilizar la referencia a las propiedades de la celda actual, que se deben referenciar sin usar este caracter.
Expresiones booleanas
La expresión para cambiar de un estado a otro debe ser una expresión booleana. Si esta expresión se evalúa como verdadera, se produce la transición entre los estados, en caso contrario, la celda permanece en su estado actual. Cada expresión define las condiciones de la transición desde un estado a otro determinado. Por cada estado, pueden existir tantas expresiones como estados restantes haya, aunque no es obligatorio definir transiciones entre todos los estados.
Las expresiones booleanas de cambio de estado pueden utilizar los operadores | (or), & (and) y ! (not) para enlazar subexpresiones. La precedencia de operadores es, primero !, después & y, por último |. Se puede modificar la precedencia encerrando una subexpresión entre llaves ({ y }).
Los operandos deben tener también un valor booleano. Los más sencillos son las constantes $t (true) y $f (false).
Podemos utilizar las funciones ist() e isf() para evaluar si el valor de una propiedad o variable es verdadero o falso, respectivamente. Se deben utilizar estas funciones en lugar de la propiedad directamente, por ejemplo:
ist(\Alive)
Comprueba si el valor de la propiedad Alive de la celda actual es verdadero. Si la propiedad no es de tipo booleano, se realiza automáticamente una conversión (los valores numéricos diferentes de cero son verdaderos, y las cadenas de texto no vacías también).
Existen funciones que trabajan con cadenas de texto que devuelven valores booleanos y también se pueden utilizar como operandos en una expresión booleana. Todas ellas utilizan dos argumentos, arg1 y arg2, que deben ser cadenas de texto y pueden ser cadenas constantes, expresiones que devuelvan una cadena de texto, que veremos más adelante, o referencias a propiedades o variables. Son las siguientes:
- cont(arg1, arg2): Devuelve true si arg1 contiene la cadena de texto arg2.
- stw(arg1, arg2): Devuelve true si arg1 comienza con la cadena de texto arg2.
- endw(arg1,arg2): Devuelve true si arg1 termina con la cadena de texto arg2.
- match(arg1,arg2): Devuelve true si arg1 se ajusta a la expresión regular pasada en arg2.
- streq(arg1,arg2): Devuelve true si arg1 y arg2 son iguales. No distingue mayúsculas y minúsculas.
- streqcs(arg1,arg2): Devuelve true si arg1 y arg2 son iguales. Distingue mayúsculas y minúsculas.
Existen dos funciones booleanas para realizar operaciones agregadas con todas las celdas vecinas de la actual. Son band() y bor(), que evalúan la expresión booleana pasada como argumento en todas las células vecinas y devuelven el resultado de aplicar las operaciones booleanas and y or a los resultados. Por ejemplo:
band(;\Alive)
Devuelve una operación and booleana del valor de la propiedad Alive de todas las celdas vecinas (no hay que olvidarse del carácter punto y coma (;), o solo procesará la celda actual).
Por último, también pueden utilizarse expresiones relacionales como operandos.
Expresiones relacionales
Se trata de expresiones que comparan dos expresiones aritméticas, utilizando los operadores << (menor), <= (menor o igual), == (igual), >= (mayor o igual), >> (mayor) y <> (diferente). Por ejemplo:
icount(;\Alive) >> 3
Devuelve true si existen más de tres vecinos de la celda actual con valor true en la propiedad Alive.
Expresiones aritméticas
Los operandos de las expresiones aritméticas son valores numéricos de tipo int o double. Si se utilizan referencias a propiedades o variables, se realiza la conversión correspondiente. Los operadores que se pueden utilizar son, en orden de menor a mayor precedencia, - (diferencia), + (suma), @ (or binario), # (xor binario), * (producto), / (cociente), % (módulo entero), ? (and binario), ^ (exponenciación) y – (menos unario).
Además de los números, existen las siguientes constantes que se pueden usar como operandos: $e (el número e), $pi (el número pi), y las pseudoconstantes $runi (un valor aleatorio entre 0 y 1, con distribución uniforme) y $rnorm (un valor aleatorio, con distribución normal de media 0 y desviación típica 1).
Existen funciones que devuelven un valor entero y aceptan como parámetro una expresión aritmética, son las siguientes:
- int(arg): Convierte el valor del argumento a un entero.
- isign(arg): Devuelve el signo del argumento, -1 para valores negativos y 1 para valores positivos.
- ifloor(arg): Devuelve el mayor entero que es menor que el argumento.
- iceil(arg): Devuelve el menor entero que es mayor que el argumento.
- iabs(arg): Devuelve el valor absoluto del argumento redondeado a un entero.
- red(arg), green(arg), blue(arg): Devuelven la componente roja, verde o azul del argumento, que se convierte a Color.
Otras funciones realizan agregados de las propiedades de las celdas vecinas y devuelven un valor entero. Todas, excepto icount, cuyo argumento es una expresión booleana, aceptan como argumento una expresión aritmética. Son las siguientes:
- icount(arg): Devuelve la cuenta de los vecinos en los que se cumple la expresión booleana pasada como parámetro.
- isum(arg): Devuelve la suma de los valores de la expresión para todas las celdas vecinas.
- iprod(arg): Devuelve el producto de los valores de la expresión para todas las celdas vecinas.
- imax(arg): Devuelve el valor máximo de la expresión para todas las celdas vecinas.
- imin(arg): Devuelve el valor mínimo de la expresión para todas las celdas vecinas.
- iavg(arg): Devuelve la media aritmética de la expresión para todas las celdas vecinas.
Por último, existen dos funciones que operan con dos argumentos de tipo cadena de texto y devuelven un entero, son cmp(arg1,arg2), que compara la cadena de texto arg1 con arg2 y devuelve -1 si arg1 < arg2, 0 si son iguales y 1 si arg1 > arg2, y pos(arg1,arg2), que devuelve la posición de la cadena de texto arg2 dentro de arg1, o -1 si no la contiene. 0 es la primera posición.
En cuanto a funciones que devuelven un resultado de tipo double, las siguientes tienen un solo argumento que es una expresión aritmética:
- double(arg): Convierte el argumento en un valor de tipo double.
- dsign(arg): Devuelve el signo del argumento, -1 o 1, como en la versión para números enteros.
- ln(arg), log(arg), log2(arg): Logaritmo neperiano, en base 10 y en base 2 de arg.
- sqrt(arg): Raíz cuadrada de arg.
- exp(arg): Devuelve el número e elevado a arg.
- dfloor(arg): Mayor entero que es menor que arg.
- dceil(arg): Menor entero que es mayor que arg.
- sin(arg), sinh(arg): Seno y seno hiperbólico de arg.
- cos(arg), cosh(arg): Coseno y coseno hiperbólico de arg.
- tan(arg), tanh(arg): Tangente y tangente hiperbólica de arg.
- dabs(arg): Valor absoluto de arg.
- hue(arg), sat(arg), bri(arg): Fuerzan arg a tipo Color y devuelven el matiz, saturación y brillo respectivamente.
Con valor double también existen funciones agregadas que procesan todos los vecinos de la celda actual:
- dsum(arg): Suma los resultados de aplicar arg a todas las celdas vecinas.
- dprod(arg): Producto de los resultados de aplicar arg a todas las celdas vecinas.
- dmax(arg), dmin(arg): Valores máximo y mínimo al aplicar arg a las celdas vecinas.
- var(arg), svar(arg): Varianza y varianza muestral de los resultados de aplicar arg a todas las celdas vecinas.
- sd(arg), ssd(arg): Desviación típica y desviación típica muestral de los resultados de aplicar arg a todas las celdas vecinas.
- davg(arg): Media aritmética de los resultados de aplicar arg a todas las celdas vecinas.
La función dcount(arg) admite como parámetro una expresión booleana, y devuelve cuantos de los vecinos la cumplen, como un valor de tipo double.
Para trabajar con cadenas de texto, devolviendo como resultado otra cadena de texto, se puede utilizar la función strc(arg1,arg2), que devuelve la concatenación de arg1 y arg2, que deben ser expresiones o constantes de tipo texto, subs(arg1,arg2,arg3) que devuelve la subcadena de la cadena de texto arg1, empezando en la posición de arg2 con longitud arg3, que son dos expresiones aritméticas. La función repl(arg1,arg2,arg3) devuelve la cadena de texto arg1 con las apariciones de la cadena de texto arg2 reemplazadas por arg3.
Editor de transiciones
Para ver el editor de transiciones, puedes abrir el autómata del juego de la vida de Conway, en el archivo Life-game.cauto del directorio Examples. Pulsa el último botón de la barra de herramientas superior para mostrar los diferentes editores y selecciona Transiciones en el menú Ventana. Verás un formulario como este:
En la parte superior izquierda aparece una matriz en la que se muestran todas las posibles combinaciones de dos estados. Los cuadros que aparecen en gris son las combinaciones de un estado consigo mismo, que no puede tener transiciones definidas, los cuadros blancos son transiciones entre estados sin definir, y los cuadros con dos flechas verdes son las transiciones entre estados con una expresión definida. Para seleccionar una transición cualquiera entre dos estados, haz doble click con el ratón sobre el cuadro correspondiente.
Debajo de esta matriz, se encuentra el control de árbol que muestra todos los estados en el primer nivel y todas sus transiciones definidas en el segundo nivel. También puedes editar una transición seleccionándola en esta lista.
A la derecha está el editor de expresiones. Una vez escrita la expresión, puedes comprobar la sintaxis compilándola con el primer botón de la barra de herramientas que está justo encima del editor de texto. Si todo es correcto, se activarán los dos botones restantes. Con el último botón puedes ver los mensajes de compilación, aunque ya te aviso que son bastantes crípticos, pues los uso para depurar el sistema de expresiones.
El segundo botón de esta barra de herramientas se puede utilizar para probar la expresión:
En la parte de la izquierda existe una barra de herramientas donde se debe seleccionar un estado para la celda actual. En la barra de herramientas que hay debajo de esta se pueden seleccionar estados para los vecinos. Es necesario seleccionar el estado de al menos un vecino. Cada vez que selecciones un estado para un vecino, se mostrará una nueva lista desplegable para que puedas añadir otro vecino más. Puedes eliminar el último vecino con el botón de borrado, al final de la barra de herramientas.
En la parte de la derecha está la expresión, que puedes desglosar en sus componentes desplegándolos en el árbol. Al seleccionar el elemento, puedes ver el resultado de la subexpresión correspondiente en el cuadro de texto de la izquierda. En este cuadro de diálogo no se puede modificar la expresión, tendrás que cerrarlo, modificarla y volverla a compilar para realizar cambios.
Y eso es todo por el momento. En el siguiente artículo comentaré el código fuente de las propiedades de los estados.