{ "metadata": { "name": "Juego de la vida en Python, revisi\u00f3n y mejoras" }, "nbformat": 3, "nbformat_minor": 0, "worksheets": [ { "cells": [ { "cell_type": "heading", "level": 1, "metadata": {}, "source": "Juego de la vida en Python: revisi\u00f3n y mejoras" }, { "cell_type": "heading", "level": 2, "metadata": {}, "source": "Introducci\u00f3n" }, { "cell_type": "markdown", "metadata": {}, "source": "Hace meses public\u00e1bamos en Pybonacci una entrada sobre c\u00f3mo implementar el **Juego de la vida de Conway** en Python utilizando arrays de NumPy. Recientemente el gran Jake VanderPlas hizo lo mismo, dando un c\u00f3digo mucho m\u00e1s compacto y centr\u00e1ndose m\u00e1s en la creaci\u00f3n de animaciones. A ra\u00edz de ello, Chema Cort\u00e9s pregunt\u00f3 en nuestro blog si no existir\u00eda una manera m\u00e1s eficiente de calcular cada paso. En esta entrada vamos a explicar nuestros resultados, y ya os adelantamos que son muy buenos :)\n\nAviso a navegantes: hoy vamos a ver m\u00e1s matem\u00e1ticas que Python, porque creo que es valioso explicar c\u00f3mo hall\u00e9 este nuevo m\u00e9todo. Pero \u00bfa qui\u00e9n no le gustan las matem\u00e1ticas?\n\n(Si no te interesa esa parte, puedes saltar directamente a [c\u00f3mo construir la matriz de adyacencia](#Construyendo-la-matriz-de-adyacencia)).\n\n** *En esta entrada se han utilizado numpy 1.7.1, scipy 0.12.0 y matplotlib 1.3.* **" }, { "cell_type": "heading", "level": 2, "metadata": {}, "source": "Un poco de \u00e1lgebra lineal" }, { "cell_type": "markdown", "metadata": {}, "source": "Supongamos que tenemos un tablero muy peque\u00f1o, de 5 filas y 5 columnas nada m\u00e1s, y que hemos etiquetado las celdas. Las vamos a llamar $c_{ij}$, donde $i$ ser\u00e1 la fila y $j$ ser\u00e1 la columna (empezando por cero). Veamos c\u00f3mo quedar\u00eda, y fij\u00e9monos en la c\u00e9lula del centro. Por un momento olvid\u00e9monos de c\u00e9lulas muertas y vivas:" }, { "cell_type": "code", "collapsed": false, "input": "from IPython.display import HTML\nfrom jinja2 import Template\n\ndef make_table(M, N, id):\n table = Template(\"\"\"\n{% for row in rows %}\n\n {% for col in cols %}{% endfor %}\n\n{% endfor %}\n
$c_{ {{ row }}{{ col }} }$
\"\"\").render(rows=range(M), cols=range(N), id=id)\n return table\n\nstyle = \"\"\"\n\"\"\"\n\nHTML(style + make_table(5, 5, \"t1\"))", "language": "python", "metadata": {}, "outputs": [ { "html": "\n\n\n\n \n\n\n\n \n\n\n\n \n\n\n\n \n\n\n\n \n\n\n
$c_{ 00 }$$c_{ 01 }$$c_{ 02 }$$c_{ 03 }$$c_{ 04 }$
$c_{ 10 }$$c_{ 11 }$$c_{ 12 }$$c_{ 13 }$$c_{ 14 }$
$c_{ 20 }$$c_{ 21 }$$c_{ 22 }$$c_{ 23 }$$c_{ 24 }$
$c_{ 30 }$$c_{ 31 }$$c_{ 32 }$$c_{ 33 }$$c_{ 34 }$
$c_{ 40 }$$c_{ 41 }$$c_{ 42 }$$c_{ 43 }$$c_{ 44 }$
", "metadata": {}, "output_type": "pyout", "prompt_number": 1, "text": "" } ], "prompt_number": 1 }, { "cell_type": "markdown", "metadata": {}, "source": "Todas las c\u00e9lulas tienen 8 vecinos, tambi\u00e9n las de los bordes (imaginad el tablero como un nivel de _Asteroids_). Si ahora las c\u00e9lulas pueden estar vivas o muertas, el n\u00famero de vecinos vivos de $c_{22}$ ser\u00eda:\n\n$$ v_{22} = c_{11} + c_{12} + c_{13} + c_{21} + c_{23} + c_{31} + c_{32} + c_{33} $$\n\ndonde $c_{ij}$ puede valer 1 o 0, dependiendo de si est\u00e1 viva o no, y el n\u00famero $v_{22}$ es el n\u00famero de vecinos vivos de $c_{22}$. Tambi\u00e9n podemos escribir esto as\u00ed, aunque ahora parezca tonto:\n\n$$ v_{22} = 1 \u00b7 c_{11} + 1 \u00b7 c_{12} + 1 \u00b7 c_{13} + 1 \u00b7 c_{21} + 1 \u00b7 c_{23} + 1 \u00b7 c_{31} + 1 \u00b7 c_{32} + 1 \u00b7 c_{33} $$\n\no, incluyendo todas las c\u00e9lulas del tablero:\n\n$$ v_{22} = 0 \u00b7 c_{00} + 0 \u00b7 c_{01} +{}...{}+ 0 \u00b7 c_{10} + 1 \u00b7 c_{11} + 1 \u00b7 c_{12} +{}...{}+ 0 \u00b7 c_{44} $$" }, { "cell_type": "markdown", "metadata": {}, "source": "Por ejemplo, si la \u00fanica c\u00e9lula viva del tablero fuese $c_{12}$, obviamente esta operaci\u00f3n resulta 1:\n\n$$ v_{22} = 0 \u00b7 0 + 0 \u00b7 0 +{}...{}+ 0 \u00b7 0 + 1 \u00b7 0 + 1 \u00b7 1 +{}+ 0 \u00b7 0 = 1 $$" }, { "cell_type": "markdown", "metadata": {}, "source": "Vemos que podemos expresar esto como un producto escalar:\n\n$$ v_{22} = \\begin{pmatrix}0 & 0 &{}...{}& 0 & 1 & 1 &{}...{}& 0\\end{pmatrix} \u00b7 \\begin{pmatrix}c_{00} \\\\ c_{01} \\\\ \\vdots \\\\ c_{10} \\\\ c_{11} \\\\ c_{12} \\\\ \\vdots \\\\ c_{44}\\end{pmatrix} = R_{22} \u00b7 T$$" }, { "cell_type": "markdown", "metadata": {}, "source": "siendo $R_{22}$ el vector de unos y ceros que indica **cu\u00e1les c\u00e9lulas cuentan en la suma y cu\u00e1les no** y $T$ un vector columna donde tenemos todas las c\u00e9lulas de tablero. Esto se pone interesante ;)" }, { "cell_type": "markdown", "metadata": {}, "source": "Veamos c\u00f3mo es el vector $R_{22}$ completo:\n\n$$ R_{22} = \\small \\begin{pmatrix}0 & 0 & 0 & 0 & 0 & | & 0 & 1 & 1 & 1 & 0 & | & 0 & 1 & 0 & 1 & 0 & | & 0 & 1 & 1 & 1 & 0 & | & 0 & 0 & 0 & 0 & 0\\end{pmatrix}$$\n\nEsto es importante. He escrito un separador cada cinco elementos para que te imagines la situaci\u00f3n por filas. Los primeros cinco son todo ceros, porque ninguna de las celdas de la primera fila es vecina de la celda $v_{22}$. La segunda fila tiene los tres del centro, la tercera fila tiene dos (\u00a1la celda $v_{22}$ no es vecina de s\u00ed misma!), la cuarta vuelve a tener las tres del centro y la quinta de nuevo todo ceros." }, { "cell_type": "markdown", "metadata": {}, "source": "La pregunta es, \u00bfqu\u00e9 pinta tienen el resto de vectores $R$? \u00bfSe parecen entre s\u00ed? Veamos otro ejemplo:" }, { "cell_type": "code", "collapsed": false, "input": "style = \"\"\"\n\"\"\"\n\nHTML(style + make_table(5, 5, \"t2\"))", "language": "python", "metadata": {}, "outputs": [ { "html": "\n\n\n\n \n\n\n\n \n\n\n\n \n\n\n\n \n\n\n\n \n\n\n
$c_{ 00 }$$c_{ 01 }$$c_{ 02 }$$c_{ 03 }$$c_{ 04 }$
$c_{ 10 }$$c_{ 11 }$$c_{ 12 }$$c_{ 13 }$$c_{ 14 }$
$c_{ 20 }$$c_{ 21 }$$c_{ 22 }$$c_{ 23 }$$c_{ 24 }$
$c_{ 30 }$$c_{ 31 }$$c_{ 32 }$$c_{ 33 }$$c_{ 34 }$
$c_{ 40 }$$c_{ 41 }$$c_{ 42 }$$c_{ 43 }$$c_{ 44 }$
", "metadata": {}, "output_type": "pyout", "prompt_number": 2, "text": "" } ], "prompt_number": 2 }, { "cell_type": "markdown", "metadata": {}, "source": "$$ R_{21} = \\small \\begin{pmatrix}0 & 0 & 0 & 0 & 0 & | & 1 & 1 & 1 & 0 & 0 & | & 1 & 0 & 1 & 0 & 0 & | & 1 & 1 & 1 & 0 & 0 & | & 0 & 0 & 0 & 0 & 0\\end{pmatrix}$$\n\n\u00bfVen bien mis ojos? \u00bf**Es el mismo vector de antes, desplazado una posici\u00f3n a la izquierda**? \u00a1S\u00ed! :D Si repetimos este proceso con todas las celdas y apilamos todos esos vectores por filas, obtendremos una matriz que visualizada en matplotlib quedar\u00eda as\u00ed:" }, { "cell_type": "code", "collapsed": false, "input": "%matplotlib inline\nimport numpy as np\nimport matplotlib.pyplot as plt\nfrom matplotlib import cm\n\nA55 = np.array([\n[0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1],\n[1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0],\n[0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0],\n[0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1],\n[1, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1],\n[1, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],\n[1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],\n[0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],\n[0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],\n[0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0],\n[0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0],\n[0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0],\n[0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0],\n[0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0],\n[0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0],\n[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0],\n[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0],\n[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0],\n[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1],\n[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1],\n[1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 1],\n[1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0],\n[0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0],\n[0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1],\n[1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0]\n])\n\n\"\"\"\n$$ A = \\tiny \\begin{pmatrix}\n0 & 1 & 0 & 0 & 1 & 1 & 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 & 1 & 1 & 0 & 0 & 1 \\\\\n1 & 0 & 1 & 0 & 0 & 1 & 1 & 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 & 1 & 1 & 0 & 0 \\\\\n0 & 1 & 0 & 1 & 0 & 0 & 1 & 1 & 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 & 1 & 1 & 0 \\\\\n0 & 0 & 1 & 0 & 1 & 0 & 0 & 1 & 1 & 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 & 1 & 1 \\\\\n1 & 0 & 0 & 1 & 0 & 1 & 0 & 0 & 1 & 1 & 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 & 1 \\\\\n1 & 1 & 0 & 0 & 1 & 0 & 1 & 0 & 0 & 1 & 1 & 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 \\\\\n1 & 1 & 1 & 0 & 0 & 1 & 0 & 1 & 0 & 0 & 1 & 1 & 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\\\\n0 & 1 & 1 & 1 & 0 & 0 & 1 & 0 & 1 & 0 & 0 & 1 & 1 & 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\\\\n0 & 0 & 1 & 1 & 1 & 0 & 0 & 1 & 0 & 1 & 0 & 0 & 1 & 1 & 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\\\\n0 & 0 & 0 & 1 & 1 & 1 & 0 & 0 & 1 & 0 & 1 & 0 & 0 & 1 & 1 & 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\\\\n0 & 0 & 0 & 0 & 1 & 1 & 1 & 0 & 0 & 1 & 0 & 1 & 0 & 0 & 1 & 1 & 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\\\\n0 & 0 & 0 & 0 & 0 & 1 & 1 & 1 & 0 & 0 & 1 & 0 & 1 & 0 & 0 & 1 & 1 & 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\\\\n0 & 0 & 0 & 0 & 0 & 0 & 1 & 1 & 1 & 0 & 0 & 1 & \\normalsize{0} & 1 & 0 & 0 & 1 & 1 & 1 & 0 & 0 & 0 & 0 & 0 & 0 \\\\\n0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 & 1 & 1 & 0 & 0 & 1 & 0 & 1 & 0 & 0 & 1 & 1 & 1 & 0 & 0 & 0 & 0 & 0 \\\\\n0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 & 1 & 1 & 0 & 0 & 1 & 0 & 1 & 0 & 0 & 1 & 1 & 1 & 0 & 0 & 0 & 0 \\\\\n0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 & 1 & 1 & 0 & 0 & 1 & 0 & 1 & 0 & 0 & 1 & 1 & 1 & 0 & 0 & 0 \\\\\n0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 & 1 & 1 & 0 & 0 & 1 & 0 & 1 & 0 & 0 & 1 & 1 & 1 & 0 & 0 \\\\\n0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 & 1 & 1 & 0 & 0 & 1 & 0 & 1 & 0 & 0 & 1 & 1 & 1 & 0 \\\\\n0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 & 1 & 1 & 0 & 0 & 1 & 0 & 1 & 0 & 0 & 1 & 1 & 1 \\\\\n1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 & 1 & 1 & 0 & 0 & 1 & 0 & 1 & 0 & 0 & 1 & 1 \\\\\n1 & 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 & 1 & 1 & 0 & 0 & 1 & 0 & 1 & 0 & 0 & 1 \\\\\n1 & 1 & 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 & 1 & 1 & 0 & 0 & 1 & 0 & 1 & 0 & 0 \\\\\n0 & 1 & 1 & 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 & 1 & 1 & 0 & 0 & 1 & 0 & 1 & 0 \\\\\n0 & 0 & 1 & 1 & 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 & 1 & 1 & 0 & 0 & 1 & 0 & 1 \\\\\n1 & 0 & 0 & 1 & 1 & 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 & 1 & 1 & 0 & 0 & 1 & 0\n\\end{pmatrix} $$\n\"\"\"\n\nplt.matshow(A55, cmap=cm.gray_r)\nplt.grid(False)", "language": "python", "metadata": {}, "outputs": [ { "metadata": {}, "output_type": "display_data", "png": "iVBORw0KGgoAAAANSUhEUgAAAP4AAAD9CAYAAACcAsr/AAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAADItJREFUeJzt3UFI2/f/x/HXVz1UHEl0WAYZQboVJgzXsuhlpVqVwtrC\nHKw9OOjKDgXXMWoZGxvIBmEUVkIctNLLYHS71MuEXXrYWu1gh2px60E3kE3oZRNrjBO0zPn9HUr9\n07/W2G++32/y3fv5AKGN5ZsPIc9+v8n3nW8c13VdATClqtwLABA+wgcMInzAIMIHDCJ8wCDCBwyq\nCfPOfv75Z3311VdaX19XZ2enenp6wrx7T86cOaPa2lpVVVWpurpa58+fL/eSNhkaGtLk5KRisZiy\n2awkaXl5WblcTvPz82psbFR/f7/q6urKvNIHtlrv8PCwrl+/rlgsJknq7e3Vvn37yrnMR8zPz+vS\npUsqFApyHEddXV06cuRIRT/O23JD8u+//7rvvvuu+9dff7n//POP+/7777t3794N6+49e+edd9y/\n//673MvY1tTUlPv777+7586d27jt66+/dkdGRlzXdd1vv/3W/eabb8q1vE22Wu/w8LD73XfflXFV\n28vn8+4ff/zhuq7rrqysuO+995579+7din6ctxPaof7MzIyeeeYZ7d69WzU1NXrllVc0MTER1t2X\nxK3wGafm5uZNe5mJiQm1t7dLkjo6OjQ+Pl6OpW1pq/VKlf04JxIJNTU1SZJ27dqlZDKphYWFin6c\ntxPaof7CwoKefvrpjb83NDRoZmYmrLv3zHEcZTIZVVVVqbu7W93d3eVe0o4UCgUlEglJUjweV6FQ\nKPOKirt27Zpu3rypPXv26OTJkxV7yDw3N6fZ2Vnt3bs3ko+zFPJr/CjKZDKqr6/X0tKSMpmMksmk\nmpuby72sJ+I4TrmXUNThw4f1xhtvSJKuXr2qK1euqK+vr8yr2mx1dVXZbFanTp1SbW3tI7+LwuP8\nUGiH+g0NDbp3797G3+/du6eGhoaw7t6z+vp6SVIsFlNbW1skjlKkB3ufxcVFSVI+n1c8Hi/zirYX\nj8flOI4cx1FnZ2dFPs5ra2vKZrM6ePCg2traJEXvcX4otPCfe+45/fnnn5qbm9Pa2pp++uknpdPp\nsO7ek/v372tlZUXSg//p79y5o1QqVeZV7Uw6ndbo6KgkaWxsTK2treVdUBH5fH7jz7du3aq4x9l1\n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\n", "text": "" } ], "prompt_number": 3 }, { "cell_type": "markdown", "metadata": {}, "source": "**Lo especial de esta matriz es que si la multiplicamos por el vector $T$, nos da un vector $V$ que cuenta cu\u00e1ntas vecinas vivas tiene cada celda.**\n\n$$ V = A T $$\n\n\u00a1Ya tenemos el misterio resuelto! Podemos calcular cada paso en el Juego de la vida con una simple multiplicaci\u00f3n matricial. Esta matriz no es m\u00e1s que la **matriz de adyacencia** de un **grafo** tal que cada v\u00e9rtice es una c\u00e9lula del tablero y cada arista indica dos c\u00e9lulas adyacentes. Hemos pasado de aut\u00f3matas celulares a teor\u00eda de grafos, casi sin despeinarnos :)\n\nSin embargo, no vamos a utilizar el grafo del tablero porque nos complicar\u00edamos en exceso. En su lugar, vamos a construir la matriz directamente usando **matrices dispersas**." }, { "cell_type": "heading", "level": 2, "metadata": {}, "source": "Construyendo la matriz de adyacencia" }, { "cell_type": "markdown", "metadata": {}, "source": "Ya tenemos un art\u00edculo en Pybonacci sobre [c\u00f3mo construir matrices tridiagonales en Python](http://pybonacci.org/2012/05/10/como-crear-una-matriz-tridiagonal-en-python-con-numpy-y-scipy/). Visto por la gr\u00e1fica anterior que la matriz de adyacencia tiene una estructura diagonal, vamos a hacer algo muy similar a lo que ya hicimos entonces: solo tenemos que crear el vector $R$, y despu\u00e9s vamos a crear la matriz por diagonales utilizando el m\u00f3dulo `scipy.sparse`." }, { "cell_type": "code", "collapsed": false, "input": "M, N = 5, 5 # Dimensiones del tablero\n\nR_22 = np.zeros((5, 5)) # Idea de Chema Cort\u00e9s\nR_22[1:4, 1:4] = np.ones((3, 3))\nR_22[2, 2] = 0\nprint(R_22)\nR_22 = R_22.flatten()", "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": "[[ 0. 0. 0. 0. 0.]\n [ 0. 1. 1. 1. 0.]\n [ 0. 1. 0. 1. 0.]\n [ 0. 1. 1. 1. 0.]\n [ 0. 0. 0. 0. 0.]]\n" } ], "prompt_number": 4 }, { "cell_type": "markdown", "metadata": {}, "source": "Nos interesa $R_{00}$:" }, { "cell_type": "code", "collapsed": false, "input": "R_00 = np.roll(R_22, 2 * (-N - 1)) # Trasladamos el vector\nR_00", "language": "python", "metadata": {}, "outputs": [ { "metadata": {}, "output_type": "pyout", "prompt_number": 5, "text": "array([ 0., 1., 0., 0., 1., 1., 1., 0., 0., 0., 0., 0., 0.,\n 0., 0., 0., 0., 0., 0., 1., 1., 1., 0., 0., 1.])" } ], "prompt_number": 5 }, { "cell_type": "markdown", "metadata": {}, "source": "Y ahora viene el truco: como s\u00f3lo nos interesa almacenar los unos, hay que decir a la funci\u00f3n `spdiags` _cu\u00e1ntos hay_ y _en qu\u00e9 posiciones se encuentran_." }, { "cell_type": "code", "collapsed": false, "input": "cnt = np.count_nonzero(R_00)\nnonzero_idx = R_00.nonzero()[0]\nprint(cnt, nonzero_idx)", "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": "8 [ 1 4 5 6 19 20 21 24]\n" } ], "prompt_number": 6 }, { "cell_type": "code", "collapsed": false, "input": "import scipy.sparse as ss\n\nnp.set_printoptions(linewidth=100)\nss.diags(np.ones(cnt), # N\u00famero de unos\n nonzero_idx, # Posiciones\n shape=(M * N, M * N), # Tama\u00f1o de la matriz\n format=\"csr\", # Formato (CSR es mejor para productos matriz-vector)\n dtype=int # Tipo entero\n ).todense()", "language": "python", "metadata": {}, "outputs": [ { "metadata": {}, "output_type": "pyout", "prompt_number": 7, "text": "matrix([[0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1],\n [0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0],\n [0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0],\n [0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1],\n [0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1],\n [0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],\n [0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],\n [0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],\n [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],\n [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0],\n [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0],\n [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0],\n [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0],\n [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0],\n [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0],\n [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0],\n [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0],\n [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0],\n [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1],\n [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1],\n [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1],\n [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0],\n [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0],\n [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],\n [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]], dtype=int64)" } ], "prompt_number": 7 }, { "cell_type": "markdown", "metadata": {}, "source": "El problema es que los \u00edndices positivos solo representan el tri\u00e1ngulo superior de la matriz. Para rellenar tambi\u00e9n el tri\u00e1ngulo inferior podemos escribir esto:" }, { "cell_type": "code", "collapsed": false, "input": "ss.diags(np.ones(cnt * 2), # N\u00famero de unos\n list(-nonzero_idx) + list(nonzero_idx), # Posiciones\n shape=(M * N, M * N), # Tama\u00f1o de la matriz\n format=\"csr\", # Formato (CSR es mejor para productos matriz-vector)\n dtype=int # Tipo entero\n ).todense()", "language": "python", "metadata": {}, "outputs": [ { "metadata": {}, "output_type": "pyout", "prompt_number": 8, "text": "matrix([[0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1],\n [1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0],\n [0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0],\n [0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1],\n [1, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1],\n [1, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],\n [1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],\n [0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],\n [0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],\n [0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0],\n [0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0],\n [0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0],\n [0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0],\n [0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0],\n [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0],\n [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0],\n [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0],\n [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0],\n [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1],\n [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1],\n [1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 1],\n [1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0],\n [0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0],\n [0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1],\n [1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0]], dtype=int64)" } ], "prompt_number": 8 }, { "cell_type": "markdown", "metadata": {}, "source": "\u00a1Es el mismo resultado que hemos obtenido antes! Si generalizamos lo que hemos escrito a una funci\u00f3n:" }, { "cell_type": "code", "collapsed": false, "input": "def adjacency_matrix(M, N):\n \"\"\"Matriz de adyacencia de un tablero M x N.\n\n Reglas:\n\n * 8 vecinos por celda\n * Geometr\u00eda toroidal\n\n No he implementado el caso N < 3 o M < 3.\n\n \"\"\"\n if M < 3 or N < 3:\n raise NotImplementedError\n\n R_11 = np.zeros((M, N))\n R_11[0:3, 0:3] = np.ones((3, 3))\n R_11[1, 1] = 0\n R_00 = np.roll(R_11.flatten(), -N - 1)\n mn = M * N\n cnt = np.count_nonzero(R_00)\n nonzero_idx = R_00.nonzero()[0]\n A = ss.diags(np.ones(cnt * 2, dtype=int), list(-nonzero_idx) + list(nonzero_idx),\n shape=(mn, mn), format=\"csr\", dtype=int)\n return A", "language": "python", "metadata": {}, "outputs": [], "prompt_number": 9 }, { "cell_type": "code", "collapsed": false, "input": "def plot_board(A, axis=True):\n plt.matshow(A, cmap=cm.gray_r)\n plt.grid(False)\n if not axis:\n plt.axis('off')\n\nplot_board(adjacency_matrix(5, 5).todense())\nplot_board(adjacency_matrix(4, 12).todense())", "language": "python", "metadata": {}, "outputs": [ { "metadata": {}, "output_type": "display_data", "png": "iVBORw0KGgoAAAANSUhEUgAAAP4AAAD9CAYAAACcAsr/AAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAADItJREFUeJzt3UFI2/f/x/HXVz1UHEl0WAYZQboVJgzXsuhlpVqVwtrC\nHKw9OOjKDgXXMWoZGxvIBmEUVkIctNLLYHS71MuEXXrYWu1gh2px60E3kE3oZRNrjBO0zPn9HUr9\n07/W2G++32/y3fv5AKGN5ZsPIc9+v8n3nW8c13VdATClqtwLABA+wgcMInzAIMIHDCJ8wCDCBwyq\nCfPOfv75Z3311VdaX19XZ2enenp6wrx7T86cOaPa2lpVVVWpurpa58+fL/eSNhkaGtLk5KRisZiy\n2awkaXl5WblcTvPz82psbFR/f7/q6urKvNIHtlrv8PCwrl+/rlgsJknq7e3Vvn37yrnMR8zPz+vS\npUsqFApyHEddXV06cuRIRT/O23JD8u+//7rvvvuu+9dff7n//POP+/7777t3794N6+49e+edd9y/\n//673MvY1tTUlPv777+7586d27jt66+/dkdGRlzXdd1vv/3W/eabb8q1vE22Wu/w8LD73XfflXFV\n28vn8+4ff/zhuq7rrqysuO+995579+7din6ctxPaof7MzIyeeeYZ7d69WzU1NXrllVc0MTER1t2X\nxK3wGafm5uZNe5mJiQm1t7dLkjo6OjQ+Pl6OpW1pq/VKlf04JxIJNTU1SZJ27dqlZDKphYWFin6c\ntxPaof7CwoKefvrpjb83NDRoZmYmrLv3zHEcZTIZVVVVqbu7W93d3eVe0o4UCgUlEglJUjweV6FQ\nKPOKirt27Zpu3rypPXv26OTJkxV7yDw3N6fZ2Vnt3bs3ko+zFPJr/CjKZDKqr6/X0tKSMpmMksmk\nmpuby72sJ+I4TrmXUNThw4f1xhtvSJKuXr2qK1euqK+vr8yr2mx1dVXZbFanTp1SbW3tI7+LwuP8\nUGiH+g0NDbp3797G3+/du6eGhoaw7t6z+vp6SVIsFlNbW1skjlKkB3ufxcVFSVI+n1c8Hi/zirYX\nj8flOI4cx1FnZ2dFPs5ra2vKZrM6ePCg2traJEXvcX4otPCfe+45/fnnn5qbm9Pa2pp++uknpdPp\nsO7ek/v372tlZUXSg//p79y5o1QqVeZV7Uw6ndbo6KgkaWxsTK2treVdUBH5fH7jz7du3aq4x9l1\nXV2+fFnJZFJHjx7duD1qj/NDjhviOyqTk5OPnM57/fXXw7prT+bm5nThwgVJ0vr6ug4cOFCRax4c\nHNT09LSWlpaUSCR04sQJtba2Vuxppv+/3uPHj2tqakqzs7NyHEeNjY06ffr0xmvnSvDrr7/qk08+\nUSqV2jik7+3t1fPPP1+xj/N2Qg0fQGVgcg8wiPABgwgfMIjwAYMIHzCI8AGDCB8wyPOs/k4/W3/7\n9u2NkUYA4frll1907ty5Tbd7Cn99fV1ffvmlBgYG1NDQoI8++kjpdFrPPvvspn+7uLiorq6ubbe3\nkw83+DVnFOYHKZiNipYoPjeKrfn777/f8nZPh/pR/mw9AI/hb/XZ+oWFBd8WBSBYvLkHGOQp/Kh+\nth7AA57Cj+Jn6wH8H0/v6ldXV+vtt9/WZ599tnE6b6t39AFUJs/n8ffv36/9+/fv6N8WO+Wwk1Mb\nfp3y8+u+diLM05QoXRSfG8X+zQ8//LDl7by5BxhE+IBBhA8YRPiAQYQPGET4gEGEDxhE+IBBoXxp\nZrEhA7+GGRjyQdD+K88N9viAQYQPGET4gEGEDxhE+IBBhA8YRPiAQYQPGFQR4buuW/THcZyiP35t\nx681+8WvNSMclfTceJyKCB9AuAgfMIjwAYMIHzCI8AGDCB8wiPABgwgfMCiUK/D48RVaYV6Bhyv5\nIGhhPje2wh4fMIjwAYMIHzCI8AGDCB8wiPABgwgfMIjwAYNKGuA5c+aMamtrVVVVperqap0/f97T\ndiptYIYhH1SCIJ8bJU/uffrpp3rqqadK3QyAEJV8qM8eAoiekvb4juMok8moqqpK3d3d6u7u9mtd\nAAJUUviZTEb19fVaWlpSJpNRMplUc3OzX2sDEJCSDvXr6+slSbFYTG1tbZqZmfFlUQCC5Tn8+/fv\na2VlRZK0urqqO3fuKJVK+bYwAMHxfKhfKBR04cIFSdL6+roOHDigl156ybeFAQiO5/B37969ET6A\naInM5F6YX33F13XxdV1R4fV5EZnwAfiH8AGDCB8wiPABgwgfMIjwAYMIHzCI8AGDQvkKrWIDJpV2\nhRmu5FP6drhOQ2Vjjw8YRPiAQYQPGET4gEGEDxhE+IBBhA8YRPiAQaEM8BQTxeEThnxK3w5DPuXD\nHh8wiPABgwgfMIjwAYMIHzCI8AGDCB8wiPABgypigGcnojh8wpBP6dthyCcY7PEBgwgfMIjwAYMI\nHzCI8AGDCB8wiPABgwgfMKjoAM/Q0JAmJycVi8WUzWYlScvLy8rlcpqfn1djY6P6+/tVV1cX+GKL\nieLwCUM+pW+HIZ8nV3SPf+jQIX388ceP3DYyMqKWlhZ98cUXevHFFzUyMhLYAgH4r2j4zc3Nm/bm\nExMTam9vlyR1dHRofHw8mNUBCISn1/iFQkGJREKSFI/HVSgUfF0UgGCV/OaeX6/lAITHU/jxeFyL\ni4uSpHw+r3g87uuiAATLU/jpdFqjo6OSpLGxMbW2tvq5JgABK3o6b3BwUNPT01paWlJfX59OnDih\nnp4e5XI53bhxY+N0HoDoKBr+2bNnt7x9YGDA98UACEdkrsDjlygOnzDkU/p2GPJ5FCO7gEGEDxhE\n+IBBhA8YRPiAQYQPGET4gEGEDxhkboBnJ6I4fMKQT+nbsTTkwx4fMIjwAYMIHzCI8AGDCB8wiPAB\ngwgfMIjz+B5F8Rw05/pL385/5Vw/e3zAIMIHDCJ8wCDCBwwifMAgwgcMInzAIMIHDGKAJ0BRHD5h\nyKf07URhyIc9PmAQ4QMGET5gEOEDBhE+YBDhAwYRPmAQ4QMGMcBTZlEcPmHIp/TtlHvIp2j4Q0ND\nmpycVCwWUzablSQNDw/r+vXrisVikqTe3l7t27cv2JUC8E3R8A8dOqRXX31VFy9e3LjNcRwdO3ZM\nx44dC3RxAIJR9DV+c3Oz6urqNt1e7kMVAN55fo1/7do13bx5U3v27NHJkye3/M8BQGXy9K7+4cOH\ndfHiRX3++eeqr6/XlStX/F4XgAB5Cj8ej8txHDmOo87OTs3MzPi9LgAB8hR+Pp/f+POtW7eUSqV8\nWxCA4BV9jT84OKjp6WktLS2pr69Px48f19TUlGZnZ+U4jhobG3X69Okw1grAJ0XDP3v27KbbOjs7\nA1kMthbF4ROGfErfTpBnzhjZBQwifMAgwgcMInzAIMIHDCJ8wCDCBwwifMAgrsDzHxHF4ROGfErf\njtchH/b4gEGEDxhE+IBBhA8YRPiAQYQPGET4gEGEDxhE+Ia4rlv0xy8PL8a63Y9fa97Jffm1Hb/W\n7Bev6yV8wCDCBwwifMAgwgcMInzAIMIHDCJ8wCDCBwwK5Qo8xQYfgvyqIDyZKF5hhiv5PDn2+IBB\nhA8YRPiAQYQPGET4gEGEDxhE+IBBhA8YtO0Az/z8vC5duqRCoSDHcdTV1aUjR45oeXlZuVxO8/Pz\namxsVH9/v+rq6jwvIsivCoL/GPIJZztBPs7bhl9TU6O33npLTU1NWl1d1YcffqiWlhaNjo6qpaVF\nr732mkZGRjQyMqI333zT0wIAhG/bQ/1EIqGmpiZJ0q5du5RMJrWwsKCJiQm1t7dLkjo6OjQ+Ph74\nQgH4Z8ev8efm5jQ7O6u9e/eqUCgokUhIkuLxuAqFQmALBOC/HYW/urqqbDarU6dOqba29pHfBflB\nAgDBKBr+2tqastmsDh48qLa2NkkP9vKLi4uSpHw+r3g8HuwqAfhq2/Bd19Xly5eVTCZ19OjRjdvT\n6bRGR0clSWNjY2ptbQ10kQD8te27+r/99pt+/PFHpVIpffDBB5Kk3t5e9fT0KJfL6caNGxun8wBE\nx7bhv/DCC7p69eqWvxsYGAhkQQCCF5nJPb++3gjhqKSvkbL8dV2PE5nwAfiH8AGDCB8wiPABgwgf\nMIjwAYMIHzCI8AGDQvkKrWLDGpV2tRaEgyv5hLOdrbDHBwwifMAgwgcMInzAIMIHDCJ8wCDCBwwi\nfMCgigg/ildrQTii+NyopCv5PE5FhA8gXIQPGET4gEGEDxhE+IBBhA8YRPiAQYQPGBRK+GENRYS1\nXoZ8KksUnxthDfk8Dnt8wCDCBwwifMAgwgcMInzAIMIHDCJ8wCDCBwxy3IC/U+r27dtaXFwM8i4A\nPEYikdDLL7+86fbAwwdQeTjUBwwifMAgwgcMInzAIMIHDPofNY1pbndylFgAAAAASUVORK5CYII=\n", "text": "" }, { "metadata": {}, "output_type": "display_data", "png": "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\n", "text": "" } ], "prompt_number": 10 }, { "cell_type": "markdown", "metadata": {}, "source": "Vemos que calcular la matriz de adyacencia es un tanto costoso para dimensiones grandes:" }, { "cell_type": "code", "collapsed": false, "input": "%timeit adjacency_matrix(100, 200)", "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": "100 loops, best of 3: 16.3 ms per loop\n" } ], "prompt_number": 11 }, { "cell_type": "markdown", "metadata": {}, "source": "Pero no importa porque este c\u00e1lculo s\u00f3lo lo vamos a hacer una vez en toda la simulaci\u00f3n, puesto que las dimensiones del tablero no cambian. Podemos cachear nuestros resultados:" }, { "cell_type": "code", "collapsed": false, "input": "adj_matrices = {}\ndef get_adj_matrix(M, N):\n try:\n A = adj_matrices[(M, N)]\n except KeyError:\n A = adjacency_matrix(M, N)\n adj_matrices[(M, N)] = A\n return A", "language": "python", "metadata": {}, "outputs": [], "prompt_number": 12 }, { "cell_type": "code", "collapsed": false, "input": "%timeit -n1 -r1 get_adj_matrix(100, 200)", "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": "1 loops, best of 1: 20.5 ms per loop\n" } ], "prompt_number": 13 }, { "cell_type": "code", "collapsed": false, "input": "%timeit get_adj_matrix(100, 200)", "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": "1000000 loops, best of 3: 316 ns per loop\n" } ], "prompt_number": 14 }, { "cell_type": "markdown", "metadata": {}, "source": "Y ahora, los resultados :)" }, { "cell_type": "heading", "level": 2, "metadata": {}, "source": "Comparaci\u00f3n de m\u00e9todos" }, { "cell_type": "markdown", "metadata": {}, "source": "Jake VanderPlas propon\u00eda estos dos m\u00e9todos para calcular el paso:" }, { "cell_type": "code", "collapsed": false, "input": "from scipy.signal import convolve2d\ndef life_step_1(X):\n \"\"\"Game of life step using generator expressions\"\"\"\n nbrs_count = sum(np.roll(np.roll(X, i, 0), j, 1)\n for i in (-1, 0, 1) for j in (-1, 0, 1)\n if (i != 0 or j != 0))\n return (nbrs_count == 3) | (X & (nbrs_count == 2))\n\ndef life_step_2(X):\n \"\"\"Game of life step using scipy tools\"\"\"\n nbrs_count = convolve2d(X, np.ones((3, 3)), mode='same', boundary='wrap') - X\n return (nbrs_count == 3) | (X & (nbrs_count == 2))", "language": "python", "metadata": {}, "outputs": [], "prompt_number": 15 }, { "cell_type": "markdown", "metadata": {}, "source": "Veamos c\u00f3mo funcionan frente al nuestro:" }, { "cell_type": "code", "collapsed": false, "input": "def life_step_3(X):\n \"\"\"Calcula el paso usando la matriz de adyacencia.\"\"\"\n A = get_adj_matrix(*X.shape)\n nbrs_count = A.dot(X.flatten()).reshape(X.shape)\n return (nbrs_count == 3) | (X & (nbrs_count == 2))", "language": "python", "metadata": {}, "outputs": [], "prompt_number": 16 }, { "cell_type": "code", "collapsed": false, "input": "tab = np.round(np.random.rand(100, 200)).astype(int) # Tablero aleatorio\nget_adj_matrix(*tab.shape) # Cacheamos la matriz correspondiente", "language": "python", "metadata": {}, "outputs": [ { "metadata": {}, "output_type": "pyout", "prompt_number": 17, "text": "<20000x20000 sparse matrix of type ''\n\twith 160000 stored elements in Compressed Sparse Row format>" } ], "prompt_number": 17 }, { "cell_type": "code", "collapsed": false, "input": "%timeit life_step_1(tab)", "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": "1000 loops, best of 3: 1.53 ms per loop\n" } ], "prompt_number": 18 }, { "cell_type": "code", "collapsed": false, "input": "%timeit life_step_2(tab)", "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": "100 loops, best of 3: 2.7 ms per loop\n" } ], "prompt_number": 19 }, { "cell_type": "code", "collapsed": false, "input": "%timeit life_step_3(tab)", "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": "1000 loops, best of 3: 359 \u00b5s per loop\n" } ], "prompt_number": 20 }, { "cell_type": "markdown", "metadata": {}, "source": "Vencedor: **matriz de adyacencia** :D \u00a1Cinco veces m\u00e1s r\u00e1pido! (En mi ordenador)" }, { "cell_type": "markdown", "metadata": {}, "source": "Y para terminar:" }, { "cell_type": "code", "collapsed": false, "input": "from functools import reduce\n\nfor ii in range(0, 18, 6):\n plot_board(reduce(lambda x, _: life_step_3(x), range(ii), tab), False)\n plt.title(\"{} iteraciones\".format(ii))", "language": "python", "metadata": {}, "outputs": [ { "metadata": {}, "output_type": "display_data", "png": "iVBORw0KGgoAAAANSUhEUgAAAd4AAAEHCAYAAAAAryckAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAIABJREFUeJztnW/IZlXVxtc4lmUzU6NDiqZGMwaZaKFBZdknCykKxArD\n6oOZhBFGRYWiZYYZ6rdCmiJRCsGIsCQoigg/xHzIpizIRlBRQx38M46WZc374eV53tvd3muv61r7\n3ve8dv1gYO777L3WOvvss89ef879bDhw4MABE0IIIcQUDlm1AUIIIcR/E3rwCiGEEBPRg1cIIYSY\niB68QgghxET04BVCCCEmogevEEIIMRE9eIWYzPe+9z1717vetWozzMzs5JNPtl//+terNkOI/yo2\n6D1eIfI89thjdsEFF9jPf/5z27Ztm1199dV23nnnhfoecsghtmfPHnvNa16zZCuFEAcDh67aACFe\nCFx88cX2kpe8xB555BG788477d3vfredeuqpdtJJJ4X6s/vf5557zg49VLexEP+fUKhZiCRPP/20\n/fCHP7SvfOUrdvjhh9sZZ5xh73vf++zmm2+utr/xxhvt7W9/u5mZnXnmmWZmduqpp9rmzZvt1ltv\nNTOzn/zkJ/aGN7zBtm7dameccYb94Q9/WO//6le/2r7+9a/bKaecYps3b7Z//etf9rWvfc127Nhh\nW7Zssde//vX2ox/96Hk6d+7caSeddNL68d/97nfrsn7xi1+Ymdmzzz5rl1xyiR177LF27LHH2qc/\n/Wn7xz/+YWZmv/rVr+xVr3qVXX/99XbUUUfZMcccYzfeeOO6/GeffdY++9nP2gknnGBHH320feIT\nn7C///3vZma2d+9ee8973mNbt261I4880s4880x6oyHECwE9eIVIcvfdd9uhhx5qO3bsWP/u1FNP\ntT/+8Y/dvmv51d///vf21FNP2fvf/36788477YILLrCdO3faY489ZhdddJG9973vtX/+85/r/W65\n5Rb76U9/ak888YRt3LjRduzYYXfccYft27fPrrjiCjv//PPt4YcfNjOzW2+91b785S/bzTffbPv2\n7bPbbrvNjjjiCDMz27Bhg23YsMHMzL761a/arl27bPfu3bZ7927btWuXXXXVVes6H374Ydu3b589\n9NBD9p3vfMcuvvhie/LJJ83M7Atf+ILt2bPHdu/ebXv27LEHH3zQrrzySjMzu+666+y4446zvXv3\n2iOPPGJXX331uk4h/hvRg1eIJPv377ctW7Y877vNmzfbU089Rcn71re+ZRdddJG96U1vsg0bNthH\nPvIRO+yww+w3v/mNmf3vw/JTn/qUHXvssXbYYYeZmdm5555rRx99tJmZfeADH7ATTzzRdu3aZWZm\n3/72t+3zn/+8nXbaaWZmtn37djv++OP/Q+/3v/99u/zyy23btm22bds2u+KKK57ntb/oRS+yyy+/\n3DZu3Ghnn322bdq0yf785z/bgQMHbOfOnXb99dfbK17xCtu0aZN98YtftFtuucXMzF784hfbX//6\nV7v33ntt48aNdsYZZ1DjIsQLBT14hUiyadMm27dv3/O+e/LJJ23z5s2UvPvuu8+uu+4627p16/q/\nBx54wB566KH1Nscdd9zz+tx00032xje+cb39XXfdZXv37jUzswceeMC2b9/e1fvQQw/ZCSecsP75\n+OOPf57OI4880g455P+WjMMPP9z2799vjz76qD3zzDN22mmnres/++yz1/V/7nOfsx07dtg73/lO\n2759u11zzTXUuAjxQkEPXiGSvPa1r7XnnnvO9uzZs/7d7t277eSTT6bkHX/88XbppZfa448/vv5v\n//799sEPfnC9zWKo9r777rOPf/zj9o1vfMMee+wxe/zxx+3kk09ez6Med9xxz7OtxTHHHGP33nvv\n+uf777/fjjnmmG6/bdu22Utf+lL705/+tG7vE088sb4Z2bRpk1177bV2zz332G233WbXX3+9/fKX\nv4wOhxAvOPTgFSLJy172MjvnnHPs8ssvt2eeecbuuOMO+/GPf2wf/vCHQ/2POuoou+eee9Y/X3jh\nhXbDDTfYrl277MCBA/b000/b7bffbvv376/2f/rpp23Dhg22bds2+/e//23f/e537a677lo//rGP\nfcyuvfZa++1vf2sHDhywPXv22P333/8fcs477zy76qqrbO/evbZ371678sorQ+dwyCGH2IUXXmiX\nXHKJPfroo2Zm9uCDD9rPfvYzMzO7/fbbbc+ePXbgwAHbsmWLbdy40TZu3BgaGyFeiOjBK8QAvvnN\nb9rf/vY3e+UrX2nnn3++3XDDDfa6172u2naxoMnM7Etf+pJ99KMfta1bt9oPfvADO+2002znzp32\nyU9+0o444gg78cQT7aabbmoWJJ100kn2mc98xt7ylrfY0UcfbXfddZe97W1vWz9+7rnn2qWXXmof\n+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\n", "text": "" }, { "metadata": {}, "output_type": "display_data", "png": "iVBORw0KGgoAAAANSUhEUgAAAd4AAAEHCAYAAAAAryckAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAIABJREFUeJzt3VuobVX9wPHfUV+sNI5Kmnr0lMcCEzO8UGn2pBF0gSii\nKCLKLphgNxCOZopRhhoUQSChGIFkDyGZVFghESR0saSH2kKaWurBYyeNrHT9n/b+L1dzjjl+l/Gb\nY22/HzgPZ+85xxhrXtacvzF+Y+wdi8ViIQAAIMVBczcAAIDnEx68AAAk4sELAEAiHrwAACTiwQsA\nQCIevAAAJOLBCyT79re/LW9605vmboaIiJx66qly1113zd0M4HllB/N4gRi33HKLXHnllfKXv/xF\njjnmGLnpppvk3HPPndzvoIMOko2NDXn5y1+e0EoAcztk7gYA28GPf/xjufTSS+U73/mOnH322fLX\nv/5VNO+01vff//73v3LIIdzGwDqhqxkIcMUVV8gVV1whZ599toiIvPSlL5Vjjz12cNubbrpJ3vCG\nN4iIyHnnnSciIq9+9avlsMMOk1tvvVVERL7//e/L6aefLjt37pRzzjlHfv/732/tv3v3bvnyl78s\np512mhx22GHyzDPPyJe+9CXZs2ePHH744fKqV71Kvve97z2nzhtuuEFOOeWUrd//9re/3Srrzjvv\nFBGRp59+Wi655BI57rjj5LjjjpNPfvKT8u9//1tERH72s5/J8ccfL9dff70cffTRcuyxx8pNN920\nVf7TTz8tn/nMZ+TEE0+UY445Rj7+8Y/Lv/71LxER2bdvn7zlLW+RnTt3ypFHHinnnXee+UUD2A54\n8AJOzzzzjPzqV7+SRx99VE4++WTZtWuXXHzxxVsPnpLN8dXf/e538o9//EPe9a53yW9+8xv50Ic+\nJDfccIM8/vjj8tGPflTe9ra3yX/+85+t/W655Ra544475IknnpCDDz5Y9uzZIz//+c/lwIEDcsUV\nV8j73vc+eeSRR0RE5NZbb5Urr7xSvvWtb8mBAwfktttukyOOOEJERHbs2CE7duwQEZEvfOELcvfd\nd8s999wj99xzj9x9991y9dVXb9X5yCOPyIEDB+Thhx+Wb37zm3LRRRfJ3//+dxERufTSS2VjY0Pu\nuece2djYkIceekiuuuoqERG57rrrZNeuXbJv3z559NFH5Ytf/OJWncDz0gKAy0MPPbTYsWPH4qyz\nzlr87W9/W+zbt29xzjnnLPbu3Tu4/Y033rg499xzt/6/Y8eOxX333bf1/4997GOLyy+//Dn7vPKV\nr1zcddddi8Visdi9e/fixhtvLLbp9NNPX9x2222LxWKxuOCCCxZf/epXB7fbvXv34s4771wsFovF\nSSedtLjjjju2fvfDH/5wsXv37sVisVj89Kc/XRx66KGLZ555Zuv3L3nJSxa//OUvF88+++zihS98\n4XM+wy9+8YvFy172ssVisVh87nOfW7z97W9fbGxsFNsMPF8Q8QJOhx56qIiIXHzxxXL00UfLkUce\nKZ/61KfkBz/4gam8+++/X6677jrZuXPn1r8HH3xQHn744a1tdu3a9Zx9br75ZnnNa16ztf29994r\n+/btExGRBx98UE466aTJeh9++GE58cQTt/5/wgknPKfOI488Ug466P+/Ml7wghfIk08+KY899pj8\n85//lDPOOGOr/je/+c1b9X/2s5+VPXv2yAUXXCAnnXSSXHPNNabjAmwXPHgBp507d8rxxx8fVt4J\nJ5wge/fulf3792/9e/LJJ+Xd73731jbLXbX333+/fOQjH5Gvf/3r8vjjj8v+/fvl1FNP3RpH3bVr\nl2xsbEzWe+yxx8qf//znrf8/8MADo+PUy4466ig59NBD5Q9/+MNWe5944gk5cOCAiIi86EUvkmuv\nvVbuu+8+ue222+T666+Xn/zkJ7WHA9h2ePACAT74wQ/K1772NXnsscdk//798pWvfEXe+ta3Vu17\n9NFHy3333bf1/wsvvFC+8Y1vyN133y2LxUKeeuopuf322+XJJ58c3P+pp56SHTt2yFFHHSXPPvus\n3HjjjXLvvfdu/f7DH/6wXHvttfLrX/9aFouFbGxsyAMPPPA/5bznPe+Rq6++Wvbt2yf79u2Tq666\nSt7//vdPtv+ggw6SCy+8UC655BJ57LHHRETkoYcekh/96EciInL77bfLxsaGLBYLOfzww+Xggw+W\ngw8+uOrYANsRD14gwOWXXy5nnXWWvOIVr5BTTjlFzjjjDNm7d+/gtssJTSIin//85+UDH/iA7Ny5\nU7773e/KGWecITfccIN84hOfkCOOOEJOPvlkufnmm0cTkk455RT59Kc/La973evkmGOOkXvvvfc5\n84ff+c53yt69e+W9732vHH744fKOd7xD9u/f/z/lXHbZZXLmmWfKaaedJqeddpqceeaZctlllz2n\n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\ngg==\n", "text": "" }, { "metadata": {}, "output_type": "display_data", "png": "iVBORw0KGgoAAAANSUhEUgAAAd4AAAEHCAYAAAAAryckAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAIABJREFUeJzt3V+sHUUdwPFfaWl6SW+vLdBSCwIJMU3gwT9NRA2hYppQ\nLDFgQkwqUWxUhD4Q4wNoNCDxAQw+gA+Cmpi0aNWYmECq4QkxxqovaAI2TaNpsSjWlBYBi5d6fTDn\n5rDd3TPzm9/8Zvbc7+cJes/uzNndc3Z/M7/5nWULCwsLAgAAXJxTugMAACwl3HgBAHDEjRcAAEfc\neAEAcMSNFwAAR9x4AQBwxI0XKOyGG26QPXv2lO6G/OpXv5LNmzeX7gYw9bjxAgrf+ta3ZMuWLbJq\n1Sq57bbb3vK3AwcOyLZt2+T888+X9evXyy233CJ///vfO/e1f/9+ufXWW0VE5Pvf/75cc801Wfve\n5ZprrpGDBw8WaRtYSrjxAgqbNm2Sr3zlK/LpT3/6rL+dPHlSbr/9djly5IgcOXJEZmdnz7o55/Lm\nm2+6tANAjxsvoHDTTTfJRz/6UTn//PPP+tv1118vH/vYx2T16tUyMzMjd955p/z617/u3NfWrVvl\ne9/7nhw8eFBuv/12+c1vfiOzs7Oybt06ERF544035Itf/KJceumlctFFF8nnP/95OX36tIiIPP30\n03LxxRfLgw8+KBs3bpRdu3bJyZMnZceOHbJ+/XpZt26d3HjjjXLs2LHF9k6cOCG33XabbNq0Sdat\nWyc33XTT4r4uueSSxdf96U9/kq1bt8ratWvlqquukieeeGLxb5/61KfkzjvvlB07dsiaNWvk6quv\nlj//+c+Lfz948OBi1L9582b5yU9+svi3/fv3y5VXXilr1qyRiy++WB566KHYww8MGjdeIEFIxdVn\nnnlGrrrqqs6/L1u2TJYtWyabN2+WRx99VN7//vfLv/71Lzlx4oSIiNx9991y+PBh+cMf/iCHDx+W\nY8eOyde+9rXF7V966SV5+eWX5ejRo/Loo4/Kf//7X9m1a5ccPXpUjh49KjMzM7J79+7F1996661y\n+vRpef755+Uf//iHfOELXzirT/Pz83LjjTfK9ddfL8ePH5dHHnlEdu7cKYcOHVp8zY9+9CO59957\n5eWXX5YrrrhCvvzlL4uIyGuvvSbbtm2TT3ziE3L8+HHZt2+f3HHHHYvD2Lt27ZLHHntMXnnlFXnu\nuefkuuuum3gMgWnCjRdIsGzZst6///GPf5T7779fvvGNbwTtr3kjX1hYkO985zvyzW9+U972trfJ\n6tWr5Z577pF9+/Ytvuacc86R++67T84991xZtWrVYhS7atUqWb16tXzpS1+SX/7ylyIi8re//U1+\n8YtfyLe//W2Zm5uTFStWtM4pHzhwQF577TW5++67ZcWKFfKhD31IduzYIT/84Q8XX3PzzTfLli1b\nZPny5bJz50559tlnRUTkySeflMsvv1w++clPyjnnnCPvete75Oabb5Yf//jHIiKycuVKee655+SV\nV16Rubk5efe73x10bIBpwY0XSNAX8R4+fFhuuOEGefjhh+WDH/ygav/Hjx+X119/Xd773vfK2rVr\nZe3atbJ9+3b55z//ufiaCy+8UFauXLn4/6+//rp87nOfk8suu0zm5ubk2muvlVOnTsnCwoK88MIL\nsm7dOpmbm+tt98UXX3zLsLOIyKWXXiovvviiiPz/gWPDhg2Lf5uZmZFXX31VRESOHDkiv/3tbxf7\nu3btWvnBD34gL730koiI/PSnP5X9+/fLZZddJlu3bpUDBw6ojg0wVCtKdwAYsq6I98iRI7Jt2zb5\n6le/Kjt37lTv74ILLpCZmRl5/vnnZePGjUHbPPTQQ3Lo0CH53e9+J+vXr5dnn31W3vOe98jCwoJc\ncsklcuLECTl16lTvzfftb3+7vPDCC7KwsLC4/yNHjgQtN3rHO94h1157rTz11FOtf9+yZYv87Gc/\nkzNnzsgjjzwit9xyixw9enTifoFpQcQLKJw5c0ZOnz4tb775ppw5c0beeOMNOXPmjIiIHDt2TK67\n7jrZvXu3fPazn43a74YNG+Svf/2rzM/Pi8j/h5E/85nPyF133SXHjx9f3H/XTU1E5NVXX5WZmRmZ\nm5uTEydOyH333bf4t40bN8r27dvljjvukJMnT8r8/Lw888wzZ+3jfe97n5x33nny4IMPyvz8vDz9\n9NPy5JNPysc//nER6Y/0P/KRj8ihQ4dk7969Mj8/L/Pz8/L73/9eDh48KPPz8/L444/LqVOnZPny\n5TI7OyvLly+POkbA0HHjBRTuv/9+Oe+88+SBBx6QvXv3yszMjHz9618XEZHvfve78pe//EXuvfde\nmZ2dldnZWVmzZk3Qfj/84Q/LlVdeKRdddJGsX79eREQeeOABueKKK+Tqq6+Wubk52bZt21uSnJoR\n71133SX//ve/5YILLpAPfOADsn379re8Zs+ePXLuuefK5s2bZcOGDfLwww+fta+VK1fKE088IT//\n+c/lwgsvlN27d8uePXvkne985+Lrmu2O/n92dlaeeuop2bdvn2zatEk2btwo99xzj/znP/8REZG9\ne/fK5ZdfLnNzc/LYY4/J448/HnRsgGmxbCEkLRMAAJgg4gUAwBE3XgAAHHHjBQDAETdeAAAcceMF\nAMARN14AABxx4wUAwBE3XgAAHHHjBQDAETdeAAAcceMFAMARN14AABxx4wUAwBE3XgAAHHHjBQDA\n0QrPxkY/lD3+E8Bt/2a17/F/T22jr58x76H54+Hj2zX/5vlTyannxqrvVtfDpP236Wuz5Lmx0vXe\nc332vPfTdY603wGe12Lf91ZXH5r9a9tfzHVr9f1cw/dYH6vrtG1/oe/d9cbb1gmrE+p187K6kfcd\ni5CLJ5e+D2/f+7U6zrk/pEO8YVqp/b33XW+5HhpC+hMj5kYV84Xd9rlsvia2vynflX0BhBXNQ3Jq\nYBSi73s69DucoWYAABy5RrypNE+BudpO3Y92yDMXqyH0XEP9pQ2577lZDzGnDhHHjNQ0+9C3n7a+\ndPW9r43UkT9PXccyJqLP8R0wKaJsGw21aidmSqoLES8AAI648QIA4GjZQoExDu3E/FAyZ2P60EY7\nXOWlhj7kMK3vayj6snRHPL8nuvoV0/b4dp7XV4nvSuus8pi2mnK8X8v3R8QLAICjIslV2qeR1OU2\nXU/UMU83VrRrRkuv7e3qg+fTrgXra2j830qIubZrY71cLdeyPKu1/DFtjcSure1KcoxNDNIsKRy9\nVntMYhI1Q0ZJrKUu4xIh4gUAwFXxOV7Nk1nbfpr7s9qPpl9aKW2kLmhPPZZDibByRiZ9T+iWbfb1\nw+raGbGaH+3bb8poiTbC0Xw2PEY5cl2fqfPmzf21yT1/W2Jkso3FOSLiBQDAUfFazU2auRxt+yXn\nePto2tYeC+32KfsteWz7aOfBcqn9OI2kzr+GzM9pPrsh116f5ms9RnVCinZY7H9cyvvKcSwsCl7k\nzGS3yKMg4gUAwBE3XgAAHLkmV+UsGqEZJozZXy3JQynHIjYpZFJb2iS52hIiajm3TTFJJs1tPBK5\n+vpj0Y/Y4WjrIUrN8a9VyBB7SsJZLTyTJ2Ne00TECwCAI5eINybasEqMaJOy1CL3E3cOmqUEMU9v\nQzoWI6nLKXLL9YQ9vl1N77eNNjrTvL/UQhWT9te2fclrcIif2T7Wy6VS+hDTFhEvAACOiv8eb1f5\nLes5iVhdT7shc0CeYp7KU5dfpe6vBrX0OfdIT9v+anjvVnOpqZ+9ruOfYzQhpEBI6P7b9hOj9Ny/\nF8/3qUHECwCAI5eIVzP+XvrpvHT7ImXm5GqL6EPUUuxkUvGD1Pm11Ez2mLZC5CqlqM3ajrlerb53\nais+sdSUmL+lgAYAAAPDjRcAAEdFCmikpuzXIne945ghvNqXyXiIWcqhvQZjhotzF3sY34+mOIy2\nXyFD6Z5DgDFtWk8DpBSrsfrMpu6n1u9gzecyR+GMJoaaAQAYmGqWE9X61GUt5EmqbwnTpG3RLiTx\nJuQazHWc+6KWmGg2pn/aIhSa5TDWtH3oWz40/vc2IUu0ci7vC+nPpL/lPGfW3+Gae4NVH3J/nxLx\nAgDgyDXiLRFJeEiNMkZiiirgbLUdp76oTLP0pStKa9N37VhFQZriMh5zcNYjF55RbIqQeWXr/vXl\nG1hF9DFKFFXSIOIFAMBRNT+SMFJb1BLCOit2Go5J7awye62KPeTKrg157RAL54eUnswV3Xm2GaLE\nyFjq9R/Txkgt16LF8SbiBQDAETdeAAAcuQ41j9QyZFCr1OFoi5q1aGc1VZAyPWGVxFTLFJDVVM1I\n7mUnIQVMtP1qayN2W085kqtqfe+WCYNEvAAAOHL9dSKE6VvuEZIYE/MUXuvTZa2sSz1q2owZ0bAq\n45izHGTK/kpE6yFLtNrEjFxoSlDGskgSylEEpIYCLSMh36+ac0PECwCAo+IlI0sYWpSXWgggJjr2\nLHRfq9RIIHfxgpAoL6RNq8IvubRdi7Uvgaq1XyM5C15Yq60/XZjjBQCgcksq4p1UOLy2J6yYRept\nQp5oa33vnqxHQCwyjWP7UdO86zjN9ZVaDjJm36n9sxrd6Hp9zs9n7p/Ssy4dWeI7KvU669qeiBcA\nAEfceAEAcORSQKM2tSdXlViA3pd4ZUXz60ue/Urd/6Qh5pAhSuvktpBiDzmHMa3a8Bq27Dv+muIp\nba8J6Z9m22kxaUqw7W8lac45ES8AAI6WVHJVk8dTU67oQlNIw2ppiZamsEftCWExT7ttBRNyvwfP\na1xbWCJEDcU/+q5fq1/rqX2Jjwev92x9XcQg4gUAwNEg5ng1c4Mh++n6u2bfk/aTsmxB+z6bfaiF\n5kcEYuZ6U89jrmgjtl8h831dxzLkeKXOn1v/0EAuMaNBscctZh64axvEs7r2mrzODREvAACOBjHH\n2zdXphESQafM2VjN5aXOF/UpGRVrC4JM2t4qUo3JQm5rf9J+Y/uRImY+cfy/cxVVGFIRBE1xDO21\nM81CRghiczksIt2+kaPciHgBAHDEjRcAAEfVJFflqpna10bI0IN1QkqIaahxmsqqCEjM9jFqSaKx\nSDKxmhqJKRDieU2GFBFpE5KANcTPlhft8G3McHRKf3KeO2o1AwBQEZfkqpgnlpxPxDHJEtO4kH1I\nT+opyWjaZA7rtjxoR4jGt/UsJFDrdReSaKYtnLHUxY5WlbhOvUcZiXgBAHDkMsdrVU6ta5vY7bz2\nl9oPq2PBU3lY9DJSw7kP7YdnYZYY03DNxcxRanIQPPJZNG2lilm2M+Trow9zvAAAVKSarOam1KL/\nVm0N0TQ8VXpGTCGlGUv1wbIfubJDR0Lmzmr/rGn7lzuPJeT6qG0UJ+e5jll1EtMHr+8dIl4AABxV\nWzKypqfg3Kwj1KEdO+9ybV1i1nhaCVkjGpMj0cdqbjDm+mq+1qNMpZUSa/hD+tLsR8wPO2j7q9ne\nc+19TA5HSNttr7E810S8AAA44sYLAICjapOrlpJah9q8eJRf1AzXeiy3aQr5xZSc5S9rKhwTUuqx\nROJZ7PBoroIQIceixNLEEsvVcrXJUDMAAFOgeHJVzBOsZiI+NSEiV4F0jyij9h8ayL3fNtpjHLN8\nIaZ0olVyVYq+BKq+tkssFeo7/hZlBHMUorH6ntDsL/UamnTdh4zQ5BCStDdpm3FeEfQIES8AAI6K\nz/HmnlerrZSiZ1p/SOm2vie8Seem9mIIbXKe80lzedajOamsIhSrucU+tcwtTuJ5HnMdk5j3kGsO\ntBSvPA8iXgAAHBWf482tL8orQRMd5Jj3szgGVvM71k/NffvzOPdd800h/fKMIGKuxZCRo0n77tp/\nyrXsee1opWRkxxTHiN13l9TROM1+PK977aghWc0AAAwUN14AABxVN9Tsufg6dZuUZUkh+4mpJRqy\n39hhE82yBQ8hBQms29EcL03SS+kEFU0CokXxh9B+9W1nNWRtud/QNkskXpXkWaymyfpa1iDiBQDA\nUTXLiawWeo/kKkIR24Z14YVJ7Wj3Y530omk7ti3ra0ez3Ke2JUJLTY6CNhb7i23Los2cxX26XhOz\n/HDS9rmVjLKbiHgBAHBUzRyv9byr1ROV1VObVX80c4O1L4j3XBLVdvxCrpmYpV4x+61dyb7nzKuI\neY21vmVc1vsNEdO21Zx/iUhXs0SOAhoAAEyB4hGv9SJ16/025SgEXkNG45CjsRHPrNOY62CIx7ak\nvutXc9xLlHHM2ZYm36BvPzHXdu1zsjEFR5jjBQBgieDGCwCAo+JDzTFKpoOHDH/FFrHo2t5qqKem\n9PmcctfU9WgbYVKTqzR10XPRTltZL5tq/q32GsvatmLeX9t3AbWaAQAYqOIFNGJoo87cYpJBrPpe\n61KJXKwKHHgWSmi2WaI4yZB5FoSYtF3qtpO29xqdslpCo/3+0Wyn/TWhkdTzptnPJES8AAA4GtQc\nb98TkFfkkDPy0sxBWPerVkN6LyHRS9d8cs6neqtlKCGso8UU2pGokL+lsP4uCYkstSUeLfo5vl1M\nXkxqvzyv+1BEvAAAOBrUHO+0mvTkWaKM47SyyhOIiVZCIjjtvFrMtRPymtwFJWLmOUO30+jq1zTk\nEni0FSImEi9x7ol4AQBYIrjxAgDgaFDJVU21Da2kskrjnwbT9D5DhjFDhpHb/r25fXM/bfvre02I\nmCVytcuVVFXiuq3tsxKSONX277k/+6n7tegfES8AAI4GHfH2KRExpbY5TVGelvXi+RHrY1qivKSm\nzb7ttdebpuSeZolcCO2oV0jk1dUWhVDyGnIiXej2RLwAADgaxHIizZxSybdlVZbNU8j8Y59aiwzE\nLF/QzLFbFW5vkxrhdu3PSsixyPW5LBF91lqyttmH8X7U0C8PMaUo26QUeNEUJSHiBQDA0dTM8dbw\nRJfrJ7dKP2HHLHa3omkjJorVZvKGlODTzEdrs2tTrgPrKCHmPUzLioSaIsrUwi81vZdJJs3b9mVU\np75Pi9EzIl4AABxx4wUAwJFLclVMcpT1sJrnEpO2NlOGN3IO/cQs26khcW3afn3J6tz2JThp9h+T\nSBeSZJK6DKxt313bhrwmdL+Wcn9+YoaPS9Tu1rapuS6svydyTYkQ8QIA4GhqkqtCWBUQSGnbilVE\nkbvAQaqYZU4ekYSmrdgn/pi2Jh2L2AjAql+TxCbPhSS3dSk9MhITwWm+kzTL4IZA01erEbvcxZCI\neAEAcDSIAhojMU8uqVHGpG1DpURlqUUCNMUZPAsShPCMeGOOheeCfWsWBQA8TNu8fq00130bz8Il\nXt9NzPECADAFqpnjtc7yHNEWJpi039jtm/vJVRShT9+i8lqFZMCnFvTQZE+2tW1VrCM3j2IWXcc0\nJnKq7bgNkWZ0o+3c1Pa9paEZzcl1DRLxAgDgiBsvAACOqhlqjkla6RvmCxnKihlisZJ7oXfbvlP3\n51FgRNOOZkmJRuowsmbZQokaxiWGdD0TZWpNcrMSUp+4+dqu13dt3zyG2u+vkCTFkO8Hq6VGXf3q\ne41FX4h4AQBwVN1yopwLlzURrVUiSi1p+DUKKbGpLcNpVVgiN4/+aJZEWbVVQkopyiHSLqEMuR6s\nl9P1yZW06iF0NI+IFwAAR9XM8abqmoMYf+LQPOlbzftatd31BFv6Sa8pZO5mJPY1Fmo7Xh55Bl1i\nR3GGEklajW7UVFSkj/azFpNX0/X/WlbLRz3PicVnlIgXAABH1US8qdmdIXMZGiUjrZB5aqsCIda0\ni9NjIr+UucoS2cOT+iGSty8h8+ddYkp3trVRUg198GZVHCLlPKbm6aTsWzuXHbJPi3ltIl4AABxV\nE/FaZwiHRM4pWbGh20/aX1dfJ/F4ik+Zbw05R33HwiqbvKtfsU+tuY93zEhP6rHQzNeFzB/G7rPL\ntK+7TaUZsdAet9yZ9c1/72szZmQmRzRrNYogQsQLAIArbrwAADiqZqi5ySppKCSBJ2fJteZ2IcND\ntRR5sE5YS+l73xB/zLXSPOfjr7GeVuhjtb+alrpYJ7fVMqwcMi1Roq+5ExD72rBaDhmzvxCpw8Fe\n55OIFwAAR9WVjPSUK8LRljec1D9tf2Jok788+2WduJMaFaS03Sclia1vKVoNy6dClsqN8zpHbWo4\nbm1Sk0NT2so1YjMupC3r99e3H8vRJSJeAAAcVTvH60GzjKePdbq7dqF2rvmSElGGx/40bWlKf1os\nvG+aFBXkGCnwWmKifc2I58hFiWjYOt8gZL9dpXkt+xUT6aaMIKYui0xBxAsAgKMlPcdrpcTcYEjG\n8bSeWqv58klyRRTj+9aUStWe6xJ5C55Z1ykZuLUr/fnuOo+e/UptKyaCto62m4h4AQBwxI0XAABH\nUz/UnDMppK0Ni3ZyLQEo1Q9Nm57DlzFLCXItaxnSdIImGcoqgcqqf7naziXnuU9JrkpdghnzvrSv\njdlf6P5TEfECAOCIiLdCQ3mqTGWdJFR71GKllsh3JCZa70sisxqhSYl0h/h9UYJmOd3460sm3dWw\nbI2IFwAAR1MX8ZYsa2gtpnxjX1RsNZfhOQdnFSFNUxRcy7Udcz1ZRxupQgqNNFktW8tVeKRv/7n6\nU9uoS5N25ML6OujahogXAABHU1MyskQGbpecC701+wt5ErZuM6b4edvfmm21tR0S+ebKQi5hiH23\nHnXRyDGfPEnbCFSMkBGtmH5Ylz6s/VrU9i/X8Woi4gUAwBE3XgAAHA16qLlvAn3IQ4upfY5JbPFM\nqgoZPrbYr/Y1S1VsIkpKrebY/nS1HbMf7XI86+SjmO1CpmOa28Sex5BEs1o/N7mT5HK/byJeAAAc\nDTri7Us4KPmkVqLkXR/PggTNNnMnKbSpfalDmxIjNNoiCCOa6Dj1V19Sj9OkaN16VCdUzOek67tO\nm9DY3G/pJV+T2o79W4m+TmqTiBcAAEeDKKBRUzSbU+5C/CFtjxvqcR7ye0ktoxmzbeocqMU249vl\nKt84pDnLLtrSjKl9mebjVRIRLwAAjoh4M9PMo+XKMizxJFvr07NVVJz6/nL9MEDq/G2MXMctZNVC\nX39KFu3oE9N36+g/lmYkZci87jVEvAAAOOLGCwCAo0EsJ9IMl+VODsmhq/0cy4FyvWeroZpcv4uZ\n85hM2t6q4It26UnI9aX5NaFUIUUjuvT1T5vsFbO91a8vdS23KrEcb5xFomct001W15UFIl4AABy5\nRLweEaVmoX7Xtm1/S+1XjNTF4Na/NNS1f6v9tbE+J839WCVF1caqwETONlK37WJdACLke8I6oa6r\noEeO6y0mWs/13e3xXRKzTDO1wEgoIl4AABwNYo63S9+Tp+bprW+uS0uTjh+zdCKmDx7zK7ki33HW\nc4Ka46OZe/YogmA9J15C23lNWWrnMbeXO7fB6lx5RXSh/cgV0VuVkMw1skXECwCAI5cCGl4ZtJZt\n5I4OPDP9rJ/4rUoNWitdYrOLdRTlWRC+5HmN/YxYnBNtYY+UDPvQ11vQlurUjNz1fT+X/Kxqr6WQ\nkcjQ90PECwCAI268AAA4GkStZiuThu5yFpqoofaqVQGHvv0NcVohRUy/YocoQxJQJh0D7XVmVUyk\nybqohfaa9iz0onltc5s2NVz/bayPiednP+Yzl3JuiHgBAHA06OVEWjGL1K2XZ9RSeCF3coP1/nMs\n9fIWeywsiiloE+CsRjBSlovE9HO8jebyrbaoOKZfmqUvqSMNMdtaj8q1tR9ThCLm+1RzLcbSjByl\nFiGatD0RLwAAjopHvDUVd8jZhxrmYzyixlwL4vvaKLFEodmXNrmWHKVGnyGs5vz7XtNkNWKUe6lX\nzui/5PeEddvWSzutrsXU+duQUQnmeAEAqEjxiHfIaohiRzwzjJvtdPWjq1852w/lkSkZs+A+JrtT\nO287ab9t/fIQU4Qi9XhNkjp/mzr/rhGyGkN7ndRQGMdqpKYrByB2+75+hR4vIl4AABxx4wUAwFHx\nAhoWC+1z9cG7H9aGUj+2hNrOudXQaQ1Dg+P9aCqZcGPVhraes6bttn2kFP8oVVDFm7YmdXN77XQA\nQ80AAFSkeMRrTRPJhDzJ1HKYQiKJmNfU8r68eJQH7dpviNTkqtrOa0qUPoTRF837S02yi2nbmqbE\nY+lzZ9Vny/dFxAsAgKMluZwoJsIdvVYbKVkv97BahB+zhGMaWZWfC3mNZ2GW1KUzNS35qmWEJnUZ\nSUw0m6uQise5rmnkIvbzbXXcQ79PiXgBAHC0JCPekZgnl5jX5CyXOGL15Oj11JuDpi3tOe8qDBIz\n35r65D/k0QjNZ6JEUZiu9mO30ZQR1LbVpmskpG2bmGPadx5CMoNzS81G7mM5ckHECwCAo6mLeFOj\n2Jj9hsyTWjxtWa3ha5Oy1jd1XXDJJ+I2IeX/Yp5yU49l7ozlkqMSqdGsx7UTkk/Rtc0QxGRbd/17\nLevhY0YoU/sVE/V3IeIFAMARN14AABxNXQGNJo80eo3SxQFSy0k2twkZUuxSekjKeqlXzHaa4z6u\nxPKMkEI0k/pVaqg5ZbixhvOgpSkiMen1mjbwf0S8AAA4GnRyVcgTWm1PXyHLUGrRFeE2i4qM/60p\nV7Jb7H76rouUJCbPc6ZJ9kmVK7K0SnDpa9u6XatlYSWFFOPRvqfcx6L0cadkJAAAAzWoOd6YZTu1\nvS1N2bmckXDu4xWyTKaNxdxbSJshEZN1lJf6xJ56PVjM66dur5kzHN9Ocx617S81tRW/qe07nYgX\nAICBqi7i7XtKtYqUaii4HhOVWT251xIBxJRSrKXPKWqZw68tgkhRyzFdKrRZ0V1SMtlDX18zIl4A\nABxx4wUAwNGghpqt26jsrS+y7l/J4drYYeTma2N+BairnbY2SyeQdL33Wq/JPppjOk3DhtPM4rNl\nXXzFSsnvRSJeAAAcVVtAI3VJSOy+vcU8baU+mbVFhhaRX2oBjZjXa4tG1Dq6YVUMo9b3N4nHMjOk\ny7XszbMITJeS1xQRLwAAjqqJeGOWmORqs7Tc/Undb7N/HoUcajk3GiFLJqyL9cdsX9ux9exPzI96\nDHnp3oh+3voBAAAEVklEQVTHaELIsaz12vNGxAsAgKPiEW/zqajvKWnEOnKr5Sms64mxLaO3hmII\nqaMSIWUzY/YT8+MNnlmUVgVgrOfFrK770p+bGJMy4qed1WfOsv2liIgXAABH3HgBAHBUfKh5xLou\nsYbV8GPsMptJxRT6fqXFc+gmte2u92mVhKEdPkw5ljHDx33DfCF96Ht/Me+B4b6zWQ8913KMa5lG\n81b6O3NSW0S8AAA4col4YxIaPJIeNKUGQ4SUN9T0JyQ69qRd8mV9bruObUgCSUgfchQIsdB3PSzV\nCCfUpGQ7q++AmLZT24pJmIodUUwpB5paBMm67Zo+E0S8AAA4cp3j7YsI+14T8jfr12h4LMvQFqKP\n3TZmv21/iymKkWOkIeW1Q1pesVSXxWhNOk45ckws5hj7+mUVYaZKGX2JfX+1j+xM6hcRLwAAjlwi\n3r55Bk30Y13WLTWjeqkUd881Txpy3GPaSp3fGeI5izm2odtOC+1qhZiM877PRslSsCVXPYzkGI2Z\nFO17zGWnIOIFAMARN14AABwtW6hknClmOKdNDUPN2mU2ofvt+reaaYeam9uEJF9Mamepm1SoZdrE\nDHFaTXEN7fNZC6vvXqt+5D5/RLwAADgqHvHmWrhuveC7xNNXSFsjsW16RYmeT7LWS0E8ypfmPg+e\nJVhrSTK0/k7RRMOeEZjnOfZUSzGSHIh4AQBwVDziTdE3f2gVMXXt32K70P6ELIwPabN04fAYqUs4\nRmp9Mg5ZBlF7tN7Xpmb+Pdd8XWj7ofsrXagCOrWMyIgQ8QIA4KqanwVMVbIMZO6F8W2RhCZyGFLm\nX0hEronaa4nwvbJhh5ARX1O5y74o2TPfoMRojpWaIsta2m4i4gUAwBE3XgAAHE1NclUX7fBQrYel\nhtq7NfShrR/aRCxNDelah/5qH062XjY4rsR7tqoV3LVdTCJXLd9fuZYC1vqZ0yDiBQDA0SAj3r5S\niiPaEnAx25fksXxhUhu1PGF7siq2kotmGRb6lVgqFBP51nYePUeXahl9i0XECwCAo0EvJ4qdF+h6\n8qnxiWgSjzJ2k16f87hZFSvwjApqjHRjow3rebVao7IutcwjhkR0muVzHu8vJhqNuT5ClhT2vbam\na5GIFwAAR9VFvNonspinrJj9Ng1pXrh5TLQlI0s8Kfa1FVN8wjo6q3WeW1OisU1I8RXra6aGYhE1\nFfGIVVMkNy4k6tS+ZlJbfeezhuNFxAsAgCNuvAAAOHJdTpRzCCll+CCmX0P6hZ8QNfQ9drjWOrlq\nqf2yTIkCBzVcZ7FK9llzjYe+vqSuIeCQpaExSYCx3yPex4uIFwAAR9UlV2lplkpYtjONUiIj7XEK\neXKN2XdM0sU0l6gTSTs32uUeMduPpJZbrDUBrqmvX9M6QqNJlI0pkhGSVBXTlz4p3w9EvAAAOBpk\nyUgAAIaKiBcAAEfceAEAcMSNFwAAR9x4AQBwxI0XAABH3HgBAHD0P76eudFNHaLJAAAAAElFTkSu\nQmCC\n", "text": "" } ], "prompt_number": 21 }, { "cell_type": "markdown", "metadata": {}, "source": "\u00a1Un saludo! ;)" } ], "metadata": {} } ] }