Skip to content

Instantly share code, notes, and snippets.

@astrojuanlu
Last active December 21, 2015 05:59
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save astrojuanlu/6260832 to your computer and use it in GitHub Desktop.
Save astrojuanlu/6260832 to your computer and use it in GitHub Desktop.
Juego de la vida en Python: revisión y mejoras He estado explorando métodos matriciales para calcular pasos sucesivos en el juego de la vida y he obtenido incrementos sustanciales de la velocidad (resultados preliminares).
Display the source blob
Display the rendered blob
Raw
{
"metadata": {
"name": ""
},
"nbformat": 3,
"nbformat_minor": 0,
"worksheets": [
{
"cells": [
{
"cell_type": "heading",
"level": 1,
"metadata": {},
"source": [
"Introducci\u00f3n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"A ra\u00edz de que Jake VanderPlas publicara [el otro d\u00eda](http://jakevdp.github.io/blog/2013/08/07/conways-game-of-life/) su propia aproximaci\u00f3n al juego de la vida de Conway, mucho mejor que aquella que [publicamos en Pybonacci](http://pybonacci.wordpress.com/2012/11/30/juego-de-la-vida-de-conway-con-python-usando-numpy/), se vio que nuestro art\u00edculo necesitaba una revisi\u00f3n importante. Chema Cort\u00e9s adem\u00e1s me dio una idea en los comentarios de un m\u00e9todo para calcular la matriz vecindario y aqu\u00ed voy a explorar si la mejora es significativa (con respecto a los otros dos m\u00e9todos que dio JvdP)."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Vamos a utilizar nosotros tambi\u00e9n una geometr\u00eda toroidal."
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"import numpy as np\n",
"\n",
"def 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",
"\n",
"def life_step_2(X):\n",
" \"\"\"Game of life step using scipy tools\"\"\"\n",
" from scipy.signal import convolve2d\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": 1
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Vamos a construir un tablero aleatorio y a comparar los dos m\u00e9todos."
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"tab = np.round(np.random.rand(60, 60)).astype(int)"
],
"language": "python",
"metadata": {},
"outputs": [],
"prompt_number": 2
},
{
"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: 451 \u00b5s per loop\n"
]
}
],
"prompt_number": 3
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"%timeit life_step_2(tab)"
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "stream",
"stream": "stdout",
"text": [
"1000 loops, best of 3: 541 \u00b5s per loop\n"
]
}
],
"prompt_number": 4
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Se ve que el segundo es claramente m\u00e1s lento. Veamos ahora c\u00f3mo funciona el m\u00edo."
]
},
{
"cell_type": "heading",
"level": 2,
"metadata": {},
"source": [
"Matriz de adyacencia de un grafo"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Si consideramos el tablero del Juego de la vida como un grafo donde cada nodo es una c\u00e9lula y cada arista indica c\u00e9lulas adyacentes seg\u00fan nuestras reglas, su matriz de adyacencia $A$ nos da el n\u00famero de vecinos vivos, sin m\u00e1s que hacer\n",
"\n",
"$$ A v_T = v_V $$\n",
"\n",
"siendo $v_T$ el vector columna resultante de aplanar la matriz $T$."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Por ejemplo, para este tablero 3\u00d73:\n",
"\n",
"$$ T = \\begin{pmatrix} 1 & 1 & 0 \\\\ 0 & 0 & 0 \\\\ 0 & 0 & 0 \\end{pmatrix} $$"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"%matplotlib inline\n",
"import matplotlib.pyplot as plt\n",
"from matplotlib import cm\n",
"\n",
"T = np.zeros((3, 3), dtype=int)\n",
"T[0, 0:2] = 1\n",
"\n",
"plt.matshow(T, cmap=cm.gray_r)\n",
"plt.grid(False)"
],
"language": "python",
"metadata": {},
"outputs": [
{
"metadata": {},
"output_type": "display_data",
"png": "iVBORw0KGgoAAAANSUhEUgAAAPgAAAD9CAYAAACRHLq4AAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAABv5JREFUeJzt3b9rU40ex/FvrxkKgoYIIlSkUARd/IHiIg5VJ13q4tBF\ncRNERETBxcmxxUVxecDVP6KT6CTUyR8gKuhQi20RBwvWnjtcEC7cp+mladLnw+u1HRIOn6FvcpKT\n0KGmaZoCIv1r0AOAzSNwCCZwCCZwCCZwCCZwCNYa9IDN8urVq3ry5Emtrq7W6dOna2JiYtCTtqRH\njx7V7Oxs7dixo6ampgY9Z8v69u1bPXz4sL5//15DQ0N15syZOnfu3KBnddcE+v37d3Pt2rXm69ev\nza9fv5pbt241nz9/HvSsLen169fNhw8fmps3bw56ypa2tLTUfPz4sWmapvn582dz/fr1f8TfVOQl\n+vv372vPnj21e/fuarVadfLkyXr58uWgZ21JBw8erO3btw96xpbXbrdrdHS0qqqGh4drZGSklpaW\nBjtqHSIDX1xcrF27dv057nQ6tbi4OMBFJJmfn69Pnz7V/v37Bz2lq8jAYbMsLy/X9PR0Xb58uYaH\nhwc9p6vIwDudTi0sLPw5XlhYqE6nM8BFJFhZWampqak6depUnThxYtBz1iUy8LGxsZqbm6v5+fla\nWVmpFy9e1PHjxwc9i3+wpmnq8ePHNTIyUufPnx/0nHUbaprMX5PNzs7+122yCxcuDHrSlvTgwYN6\n8+ZN/fjxo3bu3FkXL16s8fHxQc/act6+fVv37t2rffv21dDQUFVVTU5O1pEjRwa8bG2xgQOhl+jA\nfwgcggkcggkcggkcggkcggkcgvXs9+DT09N1+PDhXp0OWKd2u13Hjh37n4/1LPDDhw/X2bNne3U6\nKN/BWp+ZmZm/fcwlOgQTOAQTOAQTOAQTOAQTOAQTOAQTOAQTOAQTOAQTOAQTOAQTOAQTOAQTOAQT\nOAQTOAQTOAQTOAQTOAQTOAQTOAQTOAQTOAQTOAQTOAQTOAQTOAQTOAQTOATr+u+DX716VU+ePKnV\n1dU6ffp0TUxM9GMX0ANrvoKvrq7WX3/9VXfv3q3p6el6/vx5ffnypV/bgA1aM/D379/Xnj17avfu\n3dVqterkyZP18uXLfm0DNmjNwBcXF2vXrl1/jjudTi0uLm76KKA3fMgGwdYMvNPp1MLCwp/jhYWF\n6nQ6mz4K6I01Ax8bG6u5ubman5+vlZWVevHiRR0/frxf24ANWvM22bZt2+rKlSt1//79P7fJ9u7d\n269twAZ1vQ9+9OjROnr0aD+2AD3mQzYIJnAIJnAIJnAIJnAIJnAIJnAIJnAIJnAIJnAIJnAIJnAI\nJnAIJnAIJnAIJnAIJnAIJnAIJnAIJnAIJnAIJnAIJnAIJnAIJnAIJnAIJnAIJnAIJnAIJnAIJnAI\nJnAIJnAI1urlyZqm6eXpgA3yCg7BBA7BBA7BBA7BBA7BBA7BBA7BBA7BBA7BBA7BBA7BBA7BBA7B\nBA7BBA7BBA7BBA7BBA7BBA7BBA7BBA7BBA7BBA7BBA7BBA7BBA7BBA7BBA7BBA7BBA7BBA7BBA7B\nWt2e8OjRo5qdna0dO3bU1NRUPzYBPdL1FXx8fLzu3r3bjy1Aj3UN/ODBg7V9+/Z+bAF6zHtwCCZw\nCCZwCCZwCNb1NtmDBw/qzZs39ePHj7p69WpdvHixxsfH+7EN2KCugd+4caMfO4BN4BIdggkcggkc\nggkcggkcggkcggkcggkcggkcggkcggkcggkcggkcggkcggkcggkcggkcggkcggkcggkcggkcggkc\nggkcggkcggkcggkcggkcggkcggkcggkcggkcggkcggkcggkcggkcggkcggkcggkcggkcggkcggkc\nggkcggkcggkcggkcggkcggkcggkcggkcggkcggkcggkcggkcggkcggkcggkcggkcggkcggkcggkc\nggkcggkcggkcggkcggkcgrW6PeHbt2/18OHD+v79ew0NDdWZM2fq3Llz/dgGbFDXwFutVl26dKlG\nR0dreXm57ty5U4cOHaq9e/f2Yx+wAV0v0dvtdo2OjlZV1fDwcI2MjNTS0tJm7wJ64P96Dz4/P1+f\nPn2q/fv3b9YeoIfWHfjy8nJNT0/X5cuXa3h4eDM3AT2yrsBXVlZqamqqTp06VSdOnNjsTUCPdA28\naZp6/PhxjYyM1Pnz5/uxCeiRrp+iv3v3rp49e1b79u2r27dvV1XV5ORkHTlyZNPHARvTNfADBw7U\n06dP+7EF6DHfZINgAodgAodgAodgAodgAodgAodgAodgAodgAodgAodgAodgAodgAodgAodgAodg\nAodgAodgAodgAodgAodgAodgAodgAodgAodgAodgAodgAodgAodgAodgAodgAodgAodgrV6dqN1u\n18zMTK9OB6xTu93+28eGmqZp+rgF6COX6BBM4BBM4BBM4BBM4BDs354h48xMxMJ5AAAAAElFTkSu\nQmCC\n",
"text": [
"<matplotlib.figure.Figure at 0x7f942072b5d0>"
]
}
],
"prompt_number": 5
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"La matriz de adyacencia (dada solo por la geometr\u00eda) ser\u00e1:\n",
"\n",
"$ A = \\begin{pmatrix} 0 & 1 & 1 & 1 & 1 & 1 & 1 & 1 & 1 \\\\ 1 & 0 & 1 & 1 & 1 & 1 & 1 & 1 & 1 \\\\ 1 & 1 & 0 & 1 & 1 & 1 & 1 & 1 & 1 \\\\ 1 & 1 & 1 & 0 & 1 & 1 & 1 & 1 & 1 \\\\ 1 & 1 & 1 & 1 & 0 & 1 & 1 & 1 & 1 \\\\ 1 & 1 & 1 & 1 & 1 & 0 & 1 & 1 & 1 \\\\ 1 & 1 & 1 & 1 & 1 & 1 & 0 & 1 & 1 \\\\ 1 & 1 & 1 & 1 & 1 & 1 & 1 & 0 & 1 \\\\ 1 & 1 & 1 & 1 & 1 & 1 & 1 & 1 & 0 \\\\ \\end{pmatrix} $\n",
"\n",
"El vector ser\u00e1:\n",
"\n",
"$ v_T = \\begin{pmatrix} 1 \\\\ 1 \\\\ 0 \\\\ 0 \\\\ 0 \\\\ 0 \\\\ 0 \\\\ 0 \\\\ 0 \\\\ \\end{pmatrix} $\n",
"\n",
"y por tanto el vecindario:\n",
"\n",
"$ v_V = A v_T = \\begin{pmatrix} 1 \\\\ 1 \\\\ 2 \\\\ 2 \\\\ 2 \\\\ 2 \\\\ 2 \\\\ 2 \\\\ 2 \\\\ \\end{pmatrix} $\n",
"\n",
"y la matriz correspondiente:\n",
"\n",
"$ V = \\begin{pmatrix} 1 & 1 & 2 \\\\ 2 & 2 & 2 \\\\ 2 & 2 & 2 \\\\ \\end{pmatrix} $"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Tomemos la funci\u00f3n para calcular el vecindario de `game_step_1`:"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"def nbrs_1(X):\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"
],
"language": "python",
"metadata": {},
"outputs": [],
"prompt_number": 6
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"nbrs_1(T)"
],
"language": "python",
"metadata": {},
"outputs": [
{
"metadata": {},
"output_type": "pyout",
"prompt_number": 7,
"text": [
"array([[1, 1, 2],\n",
" [2, 2, 2],\n",
" [2, 2, 2]])"
]
}
],
"prompt_number": 7
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Nuestro ejemplo funciona."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"La ventaja de este m\u00e9todo es que la matriz de adyacencia se construye una sola vez, cuando conocemos las dimensiones del tablero y las condiciones de contorno, y calcular el paso siguiente solo es un producto matriz por vector. Pero **\u00bfc\u00f3mo construimos la matriz de adyacencia?** Seguro que hay m\u00e9todos m\u00e1s eficientes, pero este es el que he utilizado yo:"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"def adjacency(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",
" block_ij = np.ones((3, 3), dtype=int)\n",
" block_ij[1, 1] = 0\n",
" row_11 = np.vstack([block_ij] + [np.zeros(3, dtype=int)] * (M - 3))\n",
" row_11 = np.column_stack([row_11] + [np.zeros(M, dtype=int)] * (N - 3)).flatten()\n",
" row_00 = np.roll(row_11, -N - 1)\n",
" A = np.vstack(np.roll(row_00, ii) for ii in range(M * N)) \n",
" return A\n",
"\n",
"from numpy.testing import assert_raises\n",
"\n",
"def test_adjacency_fails_small_board():\n",
" assert_raises(NotImplementedError, adjacency, 3, 2)\n",
" assert_raises(NotImplementedError, adjacency, 2, 3)\n",
" assert_raises(NotImplementedError, adjacency, 2, 2)\n",
"\n",
"def test_adjacency_shape():\n",
" M = 50\n",
" N = 30\n",
" assert adjacency(M, N).shape == (M * N, M * N)\n",
"\n",
"def test_adjacency_int():\n",
" M = 50\n",
" N = 30\n",
" assert adjacency(M, N).dtype == np.dtype(int)\n",
"\n",
"def test_adjacency_diag_zero():\n",
" M = 4\n",
" N = 3\n",
" assert np.all(np.diag(adjacency(M, N)) == 0)\n",
"\n",
"def test_adjacency_symmetric():\n",
" M = 42\n",
" N = 31\n",
" assert np.all(adjacency(M, N).T == adjacency(M, N))\n",
"\n",
"test_adjacency_fails_small_board()\n",
"test_adjacency_shape()\n",
"test_adjacency_int()\n",
"test_adjacency_diag_zero()\n",
"test_adjacency_symmetric()"
],
"language": "python",
"metadata": {},
"outputs": [],
"prompt_number": 8
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"%timeit adjacency(*tab.shape)"
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "stream",
"stream": "stdout",
"text": [
"1 loops, best of 3: 210 ms per loop\n"
]
}
],
"prompt_number": 9
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Vemos que construir la matriz de adyacencia es bastante lento. Sin embargo, como hemos dicho, solo vamos a construirla una vez: podemos cachearla."
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"adjacency_matrices = {}\n",
"def get_adj_matrix(M, N):\n",
" try:\n",
" A = adjacency_matrices[(M, N)]\n",
" except KeyError:\n",
" A = adjacency(M, N)\n",
" adjacency_matrices[(M, N)] = A\n",
" return A"
],
"language": "python",
"metadata": {},
"outputs": [],
"prompt_number": 10
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"%timeit -n1 -r1 get_adj_matrix(*tab.shape) # Sin cachear"
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "stream",
"stream": "stdout",
"text": [
"1 loops, best of 1: 234 ms per loop\n"
]
}
],
"prompt_number": 11
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"%timeit get_adj_matrix(*tab.shape) # Cacheado"
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "stream",
"stream": "stdout",
"text": [
"1000000 loops, best of 3: 530 ns per loop\n"
]
}
],
"prompt_number": 12
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Un mill\u00f3n de veces m\u00e1s r\u00e1pido. Probemos este m\u00e9todo:"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"def life_step_3(X):\n",
" \"\"\"Game of life step using adjacency matrices\"\"\"\n",
" A = get_adj_matrix(*X.shape)\n",
" nbrs_count = np.dot(A, X.flatten()).reshape(X.shape)\n",
" return (nbrs_count == 3) | (X & (nbrs_count == 2))"
],
"language": "python",
"metadata": {},
"outputs": [],
"prompt_number": 13
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"%timeit life_step_3(tab)"
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "stream",
"stream": "stdout",
"text": [
"100 loops, best of 3: 13.7 ms per loop\n"
]
}
],
"prompt_number": 14
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**\u00a1Mal!** El m\u00e9todo resulta ser 100 veces m\u00e1s lento que los anteriores. Adem\u00e1s, vemos que hay problemas cuando las dimensiones de la matriz empiezan a crecer:"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"# \u00a1Cuidado! Esto puede congelar tu ordenador unos instantes\n",
"#A = adjacency(90, 90)\n",
"#print(A.nbytes / 1024. / 1024.) # Megabytes"
],
"language": "python",
"metadata": {},
"outputs": [],
"prompt_number": 15
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Pero en realidad, casi todos los elementos de la matriz son nulos:"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"def sparsity(A):\n",
" return np.count_nonzero(A) / A.size\n",
"\n",
"print(\"Nonzero elements: {:.2f} %\".format(sparsity(get_adj_matrix(*tab.shape)) * 100))\n",
"plt.figure(1, figsize=(12, 12))\n",
"plt.matshow(get_adj_matrix(*tab.shape), fignum=1, cmap=cm.gray_r)\n",
"plt.grid(False)"
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "stream",
"stream": "stdout",
"text": [
"Nonzero elements: 0.22 %\n"
]
},
{
"metadata": {},
"output_type": "display_data",
"png": "iVBORw0KGgoAAAANSUhEUgAAAsoAAAK8CAYAAAD27ZmcAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAIABJREFUeJzt3V+IlfeB//HPGYcm1sUZZ1AErSTtWCi00aEqiK1JYxC2\nDbstbAvJQpupIeC27OLSbZZcbAOSDTSrNiHTDaQLS3ZvNhersDf5XRiTilI0JdJ/aRnrytYLI86M\n4yat1annd+FmGuM4f8+f53nO63Wl55/P8/XrnPd8/c5zavV6vR4AAOAmXe0+AAAAKCKhDAAA0xDK\nAAAwDaEMAADTEMoAADANoQwAANPobvcBfNCpU6fyr//6r7l+/Xruv//+fPGLX2z3IVXGN77xjSxd\nujRdXV1ZsmRJnn766bzzzjs5cOBALl68mJUrV2bPnj1ZtmxZkuTgwYM5cuRIurq6MjQ0lA0bNrT5\nDIrt+9//ft58880sX748+/btS5IFje+ZM2cyPDyca9euZXBwMENDQ207p6KbbsxffvnlvPrqq1m+\nfHmS5KGHHsrg4GASY94IFy9ezPDwcCYmJlKr1bJjx458/vOfN9eb6HZjbq43z9WrV/Pkk0/m2rVr\nmZyczObNm/Pwww+b5010uzFv+zyvF8gf/vCH+je/+c3622+/Xb927Vr9W9/6Vv03v/lNuw+rMv7q\nr/6q/r//+7833fZv//Zv9UOHDtXr9Xr94MGD9X//93+v1+v1+m9+85v6t771rfq1a9fqb7/9dv2b\n3/xm/Q9/+EPLj7lMfvGLX9TPnDlT/9u//dup2+YzvtevX6/X6/X63//939dHRkbq9Xq9/o//+I/1\nN998s8VnUh7TjfnLL79c/6//+q9bHmvMG2N8fLz+3//93/V6vV7/3e9+V//rv/7r+m9+8xtzvYlu\nN+bmenNduXKlXq/X65OTk/Unnnii/tZbb5nnTTbdmLd7nhdq68Xp06ezevXqrFq1Kt3d3dm2bVve\neOONdh9WpdQ/8Pkyb7zxRu69994kyX333ZeTJ08mSU6ePJlt27alu7s7q1atyurVq3P69OmWH2+Z\nfOITn5haWXjPfMZ3ZGQk4+PjuXLlSgYGBpIk27dvz4kTJ1p7IiUy3Zgnt87zxJg3Sm9vb+66664k\nyZ133pk1a9ZkbGzMXG+i2415Yq430x133JEkmZyczPXr17Ns2TLzvMmmG/OkvfO8UFsvxsbG0t/f\nP/X7vr4+cdZAtVote/fuTVdXVx544IE88MADmZiYSG9vb5Kkp6cnExMTSZLx8fGsX79+6rn9/f1T\nX5iZu/mOb3d3d/r6+qZu7+vrM+4L8Morr+SHP/xhPvrRj+arX/1qli1bZsyb4MKFCzl79mzWr19v\nrrfIe2P+8Y9/PL/61a/M9Sa6fv16Hn/88bz99tvZuXNnPvKRj5jnTTbdmP/oRz9q6zwvVCjTXHv3\n7s2KFSty+fLl7N27N2vWrLnp/lqtNuPzZ7ufmRm/1ti5c2f+4i/+IknyH//xH3nppZeye/fuNh9V\n9Vy5ciX79u3LI488kqVLl950n7neHFeuXMn+/fvzyCOP5M477zTXm6yrqyvPPPNMfvvb3+app57K\nz372s5vuN88b74Nj/vOf/7zt87xQWy/6+voyOjo69fvR0dGbvitgcVasWJEkWb58ebZs2ZLTp0+n\np6cnly5dSnLjO+Kenp4k/i4aZT7j29/ff8t3vsZ9/np6elKr1VKr1XL//fdP/a+UMW+cycnJ7Nu3\nL9u3b8+WLVuSmOvN9t6Yf/azn71pzM315vvwhz+cwcHBnDlzxjxvkffG/Ne//nXb53mhQvljH/tY\nzp8/nwsXLmRycjLHjx/Ppk2b2n1YlfD73/8+v/vd75LcWJX4yU9+knXr1mXTpk157bXXkiSvv/56\nNm/enCTZtGlTjh07lsnJyVy4cCHnz5+f2u/D3M13fHt7e7N06dKMjIykXq/n6NGjU2+KzM34+PjU\nr0+cOJF169YlMeaNUq/X88ILL2TNmjX5whe+MHW7ud48txtzc715Ll++nHfffTfJjasx/PSnP83d\nd99tnjfR7cb8vW9MkvbM81p9uh3SbfTmm2/edHm4L33pS+0+pEq4cOFCnnnmmSQ39gB95jOfyZe+\n9KUZL3Xzn//5nzly5EiWLFmSRx55JBs3bmznKRTe9773vbz11lu5fPlyent785WvfCWbN2+e9/i+\nd1mbq1evZnBwMF//+tfbeVqF9sEx//KXv5xf/OIXOXv2bGq1WlauXJnHHntsak+hMV+8X/7yl/nO\nd76TdevWTf3X88MPP5yBgQFzvUmmG/OHHnoox44dM9eb5H/+538yPDyc69evp16vZ/v27fmzP/uz\nBb1nGvO5ud2YP//8822d54ULZQAAKIJCbb0AAICiEMoAADCNll4ezsdTAwBQFi1bUb5+/Xr+5V/+\nJU888UT279+fY8eO5dy5c6364wEAYF5aFso+nhoAgDJpWShP9/HUPsYRAICi8sN8AAAwjZb9MN98\nPhL5xz/+8U2fxAIAAM3Q29ubT3/609Pe17JQfv/HU/f19eX48eP5m7/5m2kfe+nSpezYsWNer1+r\n1eKzUwAAeL/ZGvHw4cO3va9lobxkyZJ8/etfz1NPPTV1ebi1a9c27PXr9bpYBgDgJotpxJZeR3lw\ncDCDg4NNe32xDADABy20ESv3w3zvDQQAALxnIY1YuVBOxDIAALeabyNWMpQTsQwAwK3m04iVDeVE\nLAMAcKu5NmKlQzkRywAA3GoujVj5UE7EMgAAt5rtKhgdEcqJWAYAYH4KG8rNiFqxDADAXBU2lJsV\ntWIZAIC5KGwoJ2IZAID2KXQoJ2IZAID2KHwoJ2IZAIDWK0UoJ2IZAIDWKk0oJ2IZAIDWKVUoJ2IZ\nAIDWKF0oJ2IZAIDmK2UoJ2IZAIDmKm0oJ2IZAIDmKXUoJ2IZAIDmKH0oJ2IZAIDGq0QoJ2IZAIDG\nqkwoJ2IZAIDGqVQoJ2IZAIDGqFwoJ2IZAIDFq2QoJ2IZAIDFqWwoJ2IZAICFq3QoJ/OP2rk+ViwD\nAFRb5UM5mV/U1uv1prwuAADl0hGhnNiGAQDA/HRMKCdiGQCAueuoUE7EMgAAc9NxoZyIZQAAZteR\noZyIZQAAZtaxoZyIZQAAbq+jQzkRywAATK/jQzkRywAA3Eoo/x+xDADA+wnl9xHLAAC8Ryh/gFgG\nACARytMSywAACOXbEMsAAJ1NKM9ALAMAdC6hPAuxDADQmYTyHIhlAIDOI5TnSCwDAHQWoTwPYhkA\noHMI5XkSywAAnUEoL4BYBgCoPqG8QGIZAKDahPIiiGUAgOoSyosklgEAqkkoN4BYBgCoHqHcIGIZ\nAKBahHIDiWUAgOoQyg0mlgEAqkEoN4FYBgAoP6HcJGIZAKDchHITiWUAgPISyk0mlgEAykkot4BY\nBgAoH6HcImIZAKBchHILiWUAgPIQyi0mlgEAykEot4FYBgAoPqHcJmIZAKDYhHIbiWUAgOISym0m\nlgEAikkoF4BYBgAoHqFcEGIZAKBYhHKBiGUAgOIQygUjlgEAikEoF5BYBgBoP6FcUGIZAKC9hHKB\niWUAgPYRygUnlgEA2kMol4BYBgBoPaFcEmIZAKC1hHKJiGUAgNYRyiUjlgEAWkMol5BYBgBoPqFc\nUmIZAKC5hHKJiWUAgOYRyiUnlgEAmkMoV4BYBgBovO7FPPkb3/hGli5dmq6urixZsiRPP/103nnn\nnRw4cCAXL17MypUrs2fPnixbtixJcvDgwRw5ciRdXV0ZGhrKhg0bGnIS/DFq6/V6KV4XAKDoFhXK\nSfLkk0/mT/7kT6Z+f+jQodxzzz358z//8xw6dCiHDh3KX/7lX+bcuXM5fvx49u/fn7GxsezduzfP\nPvtsurosajeKWAYAaJxFV+oH4+mNN97IvffemyS57777cvLkySTJyZMns23btnR3d2fVqlVZvXp1\nTp8+vdg/ng+wDQMAoDEWtaJcq9Wyd+/edHV15YEHHsgDDzyQiYmJ9Pb2Jkl6enoyMTGRJBkfH8/6\n9eunntvf35+xsbHF/PHchpVlAIDFW1Qo7927NytWrMjly5ezd+/erFmz5qb7Z1uBtELZPGIZAGBx\nFrX1YsWKFUmS5cuXZ8uWLTl9+nR6enpy6dKlJDdWkXt6epIkfX19GR0dnXru6Oho+vr6FvPHMwvb\nMAAAFm7Bofz73/8+v/vd75IkV65cyU9+8pOsW7cumzZtymuvvZYkef3117N58+YkyaZNm3Ls2LFM\nTk7mwoULOX/+fAYGBhZ/BsxILAMALMyCt15MTEzkmWeeSZJcv349n/nMZ7Jhw4Z87GMfy4EDB3Lk\nyJGpy8Mlydq1a7N169bs2bMnS5Ysya5du4RWi9iGAQAwf7V6ASvn8OHD2bFjR7sPo3KaFbViGQAo\nq5m600WMO4htGAAAcyeUO4xYBgCYG6HcgcQyAMDshHKHEssAADMTyh1MLAMA3J5Q7nBiGQBgekIZ\nsQwAMA2hTBKxDADwQUKZKWIZAOCPhDI3EcsAADcIZW4hlgEAhDK3IZYBgE4nlLktsQwAdDKhzIzE\nMgDQqYQysxLLAEAnEsrMiVgGADqNUGbOxDIA0EmEMvMilgGATiGUmTexDAB0AqHMgohlAKDqhDIL\nJpYBgCoTyiyKWAYAqkoos2hiGQCoIqFMQ4hlAKBqhDINI5YBgCoRyjSUWAYAqkIo03BiGQCoAqFM\nU4hlAKDshDJNI5YBgDITyjSVWAYAykoo03RiGQAoI6FMS4hlAKBshDItI5YBgDIRyrSUWAYAykIo\n03JiGQAoA6FMW4hlAKDohDJtI5YBgCITyrSVWAYAikoo03ZiGQAoIqFMIYhlAKBohDKFIZYBgCIR\nyhSKWAYAikIoUzhiGQAoAqFMIYllAKDdhDKFJZYBgHYSyhSaWAYA2kUoU3hiGQBoB6FMKYhlAKDV\nhDKlIZYBgFYSypSKWAYAWkUoUzpiGQBoBaFMKYllAKDZhDKlJZYBgGYSypSaWAYAmkUoU3piGQBo\nBqFMJYhlAKDRhDKVIZYBgEYSylSKWAYAGkUoUzliGQBoBKFMJYllAGCxhDKVJZYBgMUQylSaWAYA\nFkooU3liGQBYCKFMRxDLAMB8CWU6hlgGAOZDKNNRxDIAMFdCmY4jlgGAuRDKdCSxDADMRijTscQy\nADAToUxHE8sAwO0IZTqeWAYApiOUIWIZALiVUIb/I5YBgPcTyvA+YhkAeI9Qhg8QywBAIpRhWmIZ\nABDKcBtiGQA6m1CGGYhlAOhcQhlmIZYBoDMJZZgDsQwAnUcowxyJZQDoLEIZ5kEsA0DnEMowT2IZ\nADqDUIYFEMsAUH3dsz3g+9//ft58880sX748+/btS5K88847OXDgQC5evJiVK1dmz549WbZsWZLk\n4MGDOXLkSLq6ujI0NJQNGzYkSc6cOZPh4eFcu3Ytg4ODGRoaauJpQfO9F7X1er0UrwsAzM+sK8qf\n+9zn8sQTT9x026FDh3LPPffk2WefzSc/+ckcOnQoSXLu3LkcP348+/fvzxNPPJEf/OAHU2/2L774\nYnbv3p3nnnsu58+fz6lTp5pwOtBaVpYBoLpmDeVPfOITU6vF73njjTdy7733Jknuu+++nDx5Mkly\n8uTJbNu2Ld3d3Vm1alVWr16dkZGRjI+P58qVKxkYGEiSbN++PSdOnGj0uUBbiGUAqKYF7VGemJhI\nb29vkqSnpycTExNJkvHx8fT39089rr+/P2NjYxkfH09fX9/U7X19fRkbG1vMcUOhiGUAqJ5F/zCf\nN3G4QSwDQLUsKJR7enpy6dKlJDdWkXt6epLcWCkeHR2detzo6Gj6+/tvWUEeHR29aYUZqkIsA0B1\nLCiUN23alNdeey1J8vrrr2fz5s1Ttx87diyTk5O5cOFCzp8/n4GBgfT29mbp0qUZGRlJvV7P0aNH\ns2XLloadBBSJWAaAapj18nDf+9738tZbb+Xy5cvZvXt3vvKVr+SLX/xiDhw4kCNHjkxdHi5J1q5d\nm61bt2bPnj1ZsmRJdu3aNfXG/uijj2Z4eDhXr17N4OBgNm7c2NwzgzZy6TgAKL9avYDvuIcPH86O\nHTvafRiwaM2KWrEMAI0xU3f6ZD5oItswAKC8hDI0mVgGgHISytACYhkAykcoQ4uIZQAoF6EMLSSW\nAaA8hDK0mFgGgHIQytAGYhkAik8oQ5uIZQAoNqEMbSSWAaC4hDK0mVgGgGISylAAYhkAikcoQ0GI\nZQAoFqEMBSKWAaA4hDIUjFgGgGIQylBAYhkA2k8oQ0GJZQBoL6EMBSaWAaB9hDIUnFgGgPYQylAC\nYhkAWk8oQ0mIZQBoLaEMJSKWAaB1hDKUjFgGgNYQylBCYhkAmk8oQ0mJZQBoLqEMJSaWAaB5hDKU\nnFgGgOYQylABYhkAGk8oQ0WIZQBoLKEMFSKWAaBxhDJUjFgGgMYQylBBYhkAFk8oQ0WJZQBYHKEM\nFSaWAWDhhDJUnFgGgIURytABxDIAzJ9Qhg4hlgFgfoQydBCxDABzJ5Shw4hlAJgboQwdSCwDwOyE\nMnQosQwAMxPK0MHEMgDcnlCGDieWAWB6QhkQywAwDaEMJBHLAPBBQhmYIpYB4I+EMnATsQwANwhl\n4BZiGQCEMnAbYhmATieUgdsSywB0MqEMzEgsA9CphDIwK7EMQCcSysCciGUAOo1QBuZMLAPQSYQy\nMC9iGYBOIZSBeRPLAHQCoQwsiFgGoOqEMrBgYhmAKhPKwKKIZQCqSigDiyaWAagioQw0hFgGoGqE\nMtAwYhmAKhHKQEOJZQCqQigDDSeWAagCoQw0hVgGoOyEMtA0YhmAMhPKQFOJZQDKSigDTSeWASgj\noQy0hFgGoGyEMtAyYhmAMhHKQEuJZQDKQigDLSeWASgDoQy0hVgGoOiEMtA2YhmAIhPKQFuJZQCK\nSigDbSeWASgioQwUglgGoGiEMlAYYhmAIhHKQKGIZQCKQigDhSOWASgCoQwUklgGoN2EMlBYYhmA\ndhLKQKGJZQDaRSgDhSeWAWgHoQyUglgGoNW6Z3vA97///bz55ptZvnx59u3blyR5+eWX8+qrr2b5\n8uVJkoceeiiDg4NJkoMHD+bIkSPp6urK0NBQNmzYkCQ5c+ZMhoeHc+3atQwODmZoaKhZ5wRU1HtR\nW6/XS/G6AJTbrKH8uc99Ln/6p3+a559/fuq2Wq2WBx98MA8++OBNjz137lyOHz+e/fv3Z2xsLHv3\n7s1zzz2XWq2WF198Mbt3787AwECefvrpnDp1Khs3bmz8GQGVJpYBaJVZt1584hOfyLJly265fbo3\nk5MnT2bbtm3p7u7OqlWrsnr16oyMjGR8fDxXrlzJwMBAkmT79u05ceJEAw4f6ES2YQDQCrOuKN/O\nK6+8kh/+8If56Ec/mq9+9atZtmxZxsfHs379+qnH9Pf3Z2xsLN3d3enr65u6va+vL2NjY4s7cqCj\nWVkGoNkW9MN8O3fuzPPPP5/vfve7WbFiRV566aVGHxfArKwsA9BMCwrlnp6e1Gq11Gq13H///Tl9\n+nSSGyvFo6OjU48bHR1Nf3//LSvIo6OjN60wAyyUWAagWRYUyuPj41O/PnHiRNatW5ck2bRpU44d\nO5bJyclcuHAh58+fz8DAQHp7e7N06dKMjIykXq/n6NGj2bJlS2POAOh4YhmAZph1j/L3vve9vPXW\nW7l8+XJ2796dL3/5y/nFL36Rs2fPplarZeXKlXnssceSJGvXrs3WrVuzZ8+eLFmyJLt27Zp6k3n0\n0UczPDycq1evZnBw0BUvgIayZxmARqvVC/jV//Dhw9mxY0e7DwMooWZFrVgGqKaZutMn8wGVYhsG\nAI0ilIHKEcsANIJQBipJLAOwWEIZqCyxDMBiCGWg0sQyAAsllIHKE8sALIRQBjqCWAZgvoQy0DHE\nMgDzIZSBjiKWAZgroQx0HLEMwFwIZaAjiWUAZiOUgY4llgGYiVAGOppYBuB2hDLQ8cQyANMRygAR\nywDcSigD/B+xDMD7CWWA9xHLALxHKAN8gFgGIBHKANMSywAIZYDbEMsAnU0oA8xALAN0LqEMMAux\nDNCZhDLAHIhlgM4jlAHmSCwDdBahDDAPYhmgcwhlgHkSywCdQSgDLIBYBqg+oQywQGIZoNqEMsAi\niGWA6hLKAIsklgGqSSgDNIBYBqgeoQzQIGIZoFqEMkADiWWA6hDKAA0mlgGqQSgDNIFYBig/oQzQ\nJGIZoNyEMkATiWWA8hLKAE0mlgHKSSgDtIBYBigfoQzQImIZoFyEMkALiWWA8hDKAC0mlgHKQSgD\ntIFYBig+oQzQJmIZoNiEMkAbiWWA4hLKAG0mlgGKSSgDFIBYBigeoQxQEGIZoFiEMkCBiGWA4hDK\nAAUjlgGKQSgDFJBYBmg/oQxQUPON2rk+ViwDzI1QBiiw+URtvV5vyusCdCqhDFBwtmEAtIdQBigB\nsQzQekIZoCTEMkBrCWWAEhHLAK0jlAFKRiwDtIZQBighsQzQfEIZoKTEMkBzCWWAEhPLAM0jlAFK\nTiwDNIdQBqgAsQzQeEIZoCLEMkBjCWWAChHLAI0jlAEqRiwDNIZQBqggsQyweEIZoKLEMsDiCGWA\nChPLAAsnlAEqTiwDLIxQBugAYhlg/oQyQIcQywDzI5QBOohYBpg7oQzQYcQywNwIZYAOJJYBZieU\nATqUWAaYmVAG6GBiGeD2hDJAhxPLANMTygCIZYBpCGUAkohlgA8SygBMEcsAfySUAbiJWAa4QSgD\ncAuxDCCUAbgNsQx0uu6Z7rx48WKGh4czMTGRWq2WHTt25POf/3zeeeedHDhwIBcvXszKlSuzZ8+e\nLFu2LEly8ODBHDlyJF1dXRkaGsqGDRuSJGfOnMnw8HCuXbuWwcHBDA0NNf/sAFiU96K2Xq+X4nUB\nGmnGFeXu7u587Wtfy/79+/PUU0/l//2//5dz587l0KFDueeee/Lss8/mk5/8ZA4dOpQkOXfuXI4f\nP579+/fniSeeyA9+8IOpL4Ivvvhidu/eneeeey7nz5/PqVOnmn92ACyalWWgU80Yyr29vbnrrruS\nJHfeeWfWrFmTsbGxvPHGG7n33nuTJPfdd19OnjyZJDl58mS2bduW7u7urFq1KqtXr87IyEjGx8dz\n5cqVDAwMJEm2b9+eEydONPG0AGgksQx0ojnvUb5w4ULOnj2b9evXZ2JiIr29vUmSnp6eTExMJEnG\nx8fT398/9Zz+/v6MjY1lfHw8fX19U7f39fVlbGysUecAQAuIZaDTzCmUr1y5kn379uWRRx7J0qVL\nb7rPFzeAziGWgU4yayhPTk5m37592b59e7Zs2ZLkxirypUuXktxYRe7p6UlyY6V4dHR06rmjo6Pp\n7++/ZQV5dHT0phVmAMpDLAOdYsZQrtfreeGFF7JmzZp84QtfmLp906ZNee2115Ikr7/+ejZv3jx1\n+7FjxzI5OZkLFy7k/PnzGRgYSG9vb5YuXZqRkZHU6/UcPXp0KroBKB+xDHSCGS8P96tf/SpHjx7N\nunXr8u1vfztJ8vDDD+eLX/xiDhw4kCNHjkxdHi5J1q5dm61bt2bPnj1ZsmRJdu3aNfUF79FHH83w\n8HCuXr2awcHBbNy4scmnBkAzuXQcUHW1egG/Eh0+fDg7duxo92EAMAfNilqxDLTCTN3pk/kAWBTb\nMICqEsoALJpYBqpIKAPQEGIZqBqhDEDDiGWgSoQyAA0lloGqEMoANJxYBqpAKAPQFGIZKDuhDEDT\niGWgzIQyAE0lloGyEsoANJ1YBspIKAPQEmIZKBuhDEDLiGWgTIQyAC0lloGyEMoAtJxYBspAKAPQ\nFmIZKDqhDEDbiGWgyIQyAG0lloGiEsoAtJ1YBopIKANQCGIZKBqhDEBhiGWgSIQyAIUiloGiEMoA\nFI5YBopAKANQSGIZaDehDEBhiWWgnYQyAIUmloF2EcoAFJ5YBtpBKANQCmIZaDWhDEBpiGWglYQy\nAKUiloFWEcoAlI5YBlpBKANQSmIZaDahDEBpiWWgmYQyAKUmloFmEcoAlJ5YBppBKANQCWIZaDSh\nDEBliGWgkYQyAJUiloFGEcoAVI5YBhpBKANQSWIZWCyhDEBliWVgMYQyAJUmloGFEsoAVJ5YBhZC\nKAPQEcQyMF9CGYCOIZaB+RDKAHQUsQzMlVAGoOOIZWAuhDIAHUksA7MRygB0LLEMzEQoA9DRxDJw\nO0IZgI4nloHpCGUAiFgGbiWUAeD/iGXg/YQyALyPWAbeI5QB4APEMpAIZQCYllgGhDIA3IZYhs4m\nlAFgBmIZOpdQBoBZiGXoTEIZAOZALEPnEcoAMEdiGTqLUAaAeRDL0DmEMgDMk1iGziCUAWABxDJU\nn1AGgAUSy1BtQhkAFkEsQ3UJZQBYJLEM1SSUAaABxDJUj1AGgAYRy1AtQhkAGkgsQ3UIZQBoMLEM\n1SCUAaAJxDKUn1AGgCYRy1BuQhkAmkgsQ3kJZQBoMrEM5SSUAaAFxDKUj1AGgBYRy1AuQhkAWkgs\nQ3kIZQBoMbEM5SCUAaANxDIUn1AGgDYRy1BsQhkA2kgsQ3EJZQBoM7EMxSSUAaAAxDIUj1AGgIIQ\ny1As3TPdefHixQwPD2diYiK1Wi07duzI5z//+bz88st59dVXs3z58iTJQw89lMHBwSTJwYMHc+TI\nkXR1dWVoaCgbNmxIkpw5cybDw8O5du1aBgcHMzQ01ORTA4DyeS9q6/V6KV4XqmzGUO7u7s7Xvva1\n3HXXXbly5Uoef/zx3HPPPanVannwwQfz4IMP3vT4c+fO5fjx49m/f3/Gxsayd+/ePPfcc6nVannx\nxReze/fuDAwM5Omnn86pU6eycePGpp4cAJSRWIZimHHrRW9vb+66664kyZ133pk1a9ZkbGwsSab9\nR3by5Mls27Yt3d3dWbVqVVavXp2RkZGMj4/nypUrGRgYSJJs3749J06caPCpAEB12IYB7TfnPcoX\nLlzI2bNn8/GPfzxJ8sorr+Tv/u7v8s///M959913kyTj4+Pp7++fek5/f3/GxsYyPj6evr6+qdv7\n+vqmghuXOoLeAAANEUlEQVQAmJ5YhvaaUyhfuXIl+/fvzyOPPJI777wzO3fuzPPPP5/vfve7WbFi\nRV566aVmHycAdCSxDO0zayhPTk5m3759+exnP5stW7YkSXp6elKr1VKr1XL//ffn9OnTSW6sFI+O\njk49d3R0NP39/besII+Ojt60wgwA3J5YhvaYMZTr9XpeeOGFrFmzJl/4whembh8fH5/69YkTJ7Ju\n3bokyaZNm3Ls2LFMTk7mwoULOX/+fAYGBtLb25ulS5dmZGQk9Xo9R48enYpuAGB2Yhlab8arXvzq\nV7/K0aNHs27dunz7299OcuNScMeOHcvZs2dTq9WycuXKPPbYY0mStWvXZuvWrdmzZ0+WLFmSXbt2\nTf3je/TRRzM8PJyrV69mcHDQFS8AYJ5cDQNaq1Yv4L+Kw4cPZ8eOHe0+DAAopGZFrVimE83UnT6Z\nDwBKxjYMaA2hDAAlJJah+YQyAJSUWIbmEsoAUGJiGZpHKANAyYllaA6hDAAVIJah8YQyAFSEWIbG\nEsoAUCFiGRpHKANAxYhlaAyhDAAVJJZh8YQyAFSUWIbFEcoAUGFiGRZOKANAxYllWBihDAAdQCzD\n/AllAOgQYhnmRygDQAcRyzB3QhkAOoxYhrkRygDQgcQyzE4oA0CHEsswM6EMAB1MLMPtCWUA6HBi\nGaYnlAEAsQzTEMoAQBKxDB8klAGAKWIZ/kgoAwA3Ectwg1AGAG4hlkEoAwC3IZbpdEIZALgtsUwn\nE8oAwIzEMp1KKAMAsxLLdCKhDADMiVim0whlAGDOxDKdRCgDAPMilukUQhkAmDexTCcQygDAgohl\nqk4oAwALJpapMqEMACyKWKaqhDIAsGhimSoSygBAQ4hlqkYoAwANI5apEqEMADSUWKYqhDIA0HBi\nmSoQygBAU4hlyk4oAwBNI5YpM6EMADSVWKashDIA0HRimTISygBAS4hlykYoAwAtI5YpE6EMALSU\nWKYshDIA0HJimTIQygBAW4hlik4oAwBtI5YpMqEMALSVWKaohDIA0HZimSISygBAIYhlikYoAwCF\nIZYpEqEMABSKWKYohDIAUDhimSIQygBAIYll2k0oAwCFJZZpJ6EMABSaWKZdhDIAUHhimXYQygBA\nKYhlWk0oAwClIZZpJaEMAJSKWKZVhDIAUDpimVYQygBAKYllmk0oAwClJZZpJqEMAJSaWKZZhDIA\nUHpimWYQygBAJYhlGk0oAwCVIZZpJKEMAFSKWKZRhDIAUDlimUYQygBAJYllFksoAwCVJZZZDKEM\nAFSaWGahhDIAUHlimYUQygBARxDLzJdQBgA6hlhmPoQyANBRxDJzJZQBgI4jlpkLoQwAdCSxzGyE\nMgDQscQyM+me6c6rV6/mySefzLVr1zI5OZnNmzfn4YcfzjvvvJMDBw7k4sWLWblyZfbs2ZNly5Yl\nSQ4ePJgjR46kq6srQ0ND2bBhQ5LkzJkzGR4ezrVr1zI4OJihoaHmnx0AwCzei9p6vV6K16V1ZlxR\n/tCHPpTvfOc7eeaZZ/JP//RP+fnPf55f/vKXOXToUO655548++yz+eQnP5lDhw4lSc6dO5fjx49n\n//79eeKJJ/KDH/xganK8+OKL2b17d5577rmcP38+p06dav7ZAQDMgZVlpjPr1os77rgjSTI5OZnr\n169n2bJleeONN3LvvfcmSe67776cPHkySXLy5Mls27Yt3d3dWbVqVVavXp2RkZGMj4/nypUrGRgY\nSJJs3749J06caNY5AQDMm1jmg2bcepEk169fz+OPP5633347O3fuzEc+8pFMTEykt7c3SdLT05OJ\niYkkyfj4eNavXz/13P7+/oyNjaW7uzt9fX1Tt/f19WVsbKzR5wIAsCi2YfB+s4ZyV1dXnnnmmfz2\nt7/NU089lZ/97Gc33e87JACgSsQy75nzVS8+/OEPZ3BwMGfOnElPT08uXbqU5MYqck9PT5IbK8Wj\no6NTzxkdHU1/f/8tK8ijo6M3rTADABSJbRgks4Ty5cuX8+677ya5cQWMn/70p7n77ruzadOmvPba\na0mS119/PZs3b06SbNq0KceOHcvk5GQuXLiQ8+fPZ2BgIL29vVm6dGlGRkZSr9dz9OjRbNmypbln\nBgCwCGKZGbdeXLp0KcPDw7l+/Xrq9Xq2b9+eT33qU7n77rtz4MCBHDlyZOrycEmydu3abN26NXv2\n7MmSJUuya9euqYnw6KOPZnh4OFevXs3g4GA2btzY/LMDAFgE2zA6W61ewL+hw4cPZ8eOHe0+DACA\nJGla1Irl9pupO30yHwDALGzD6ExCGQBgDsRy5xHKAABzJJY7i1AGAJgHsdw5hDIAwDyJ5c4glAEA\nFkAsV59QBgBYILFcbUIZAGARxHJ1CWUAgEUSy9UklAEAGkAsV49QBgBoELFcLUIZAKCBxHJ1CGUA\ngAYTy9UglAEAmkAsl59QBgBoErFcbkIZAKCJxHJ5CWUAgCYTy+UklAEAWkAsl49QBgBoEbFcLkIZ\nAKCFxHJ5CGUAgBYTy+UglAEA2kAsF59QBgBoE7FcbEIZAKCNxHJxCWUAgDYTy8UklAEACkAsF49Q\nBgAoCLFcLEIZAKBAxHJxCGUAgIIRy8UglAEACkgst59QBgAoKLHcXkIZAKDAxHL7CGUAgIITy+0h\nlAEASkAst55QBgAoCbHcWkIZAKBExHLrCGUAgJIRy60hlAEASkgsN59QBgAoKbHcXEIZAKDExHLz\nCGUAgJITy80hlAEAKkAsN55QBgCoCLHcWEIZAKBCxHLjCGUAgIoRy40hlAEAKkgsL55QBgCoKLG8\nOEIZAKDCxPLCCWUAgIoTywsjlAEAOoBYnj+hDADQIcTy/AhlAIAOIpbnTigDAHQYsTw3QhkAoAOJ\n5dkJZQCADiWWZyaUAQA6mFi+PaEMANDhxPL0hDIAAGJ5GkIZAIAkYvmDhDIAAFPE8h8JZQAAbiKW\nbxDKAADcQiwLZQAAbqPTY1koAwBwW50cy0IZAIAZdWosC2UAAGbVibEslAEAmJNOi2WhDADAnHVS\nLAtlAADmpVNiWSgDADBvnRDLQhkAgAWpeiwLZQAAFqzKsSyUAQBYlKrGslAGAGDRqhjLQhkAgIao\nWiwLZQAAGqZKsSyUAQBoqPlG7Vwf2+pYFsoAADTcfKK2Xq835XUXSygDANAUZd+GIZQBAGiaMsey\nUAYAoKnKGstCGQCApitjLAtlAABaomyxLJQBAGiZMsVy90x3Xr16NU8++WSuXbuWycnJbN68OQ8/\n/HBefvnlvPrqq1m+fHmS5KGHHsrg4GCS5ODBgzly5Ei6uroyNDSUDRs2JEnOnDmT4eHhXLt2LYOD\ngxkaGmroiQAAUA7vRe18LgvXjtedMZQ/9KEP5Tvf+U7uuOOO/OEPf8g//MM/5Je//GVqtVoefPDB\nPPjggzc9/ty5czl+/Hj279+fsbGx7N27N88991xqtVpefPHF7N69OwMDA3n66adz6tSpbNy4sSEn\nAQBAuZQhlmfdenHHHXckSSYnJ3P9+vUsW7Zs6iA+6OTJk9m2bVu6u7uzatWqrF69OiMjIxkfH8+V\nK1cyMDCQJNm+fXtOnDix6IMHAKC8ir4NY8YV5SS5fv16Hn/88bz99tvZuXNnPvKRj+RHP/pRXnnl\nlfzwhz/MRz/60Xz1q1/NsmXLMj4+nvXr1089t7+/P2NjY+nu7k5fX9/U7X19fRkbG1v0wQMAUG5F\nXlmedUW5q6srzzzzTF544YW89dZb+fnPf56dO3fm+eefz3e/+92sWLEiL7300oIPAACAzlbUleU5\nX/Xiwx/+cAYHB/PrX/86PT09qdVqqdVquf/++3P69OkkN1aKR0dHp54zOjqa/v7+W1aQR0dHb1ph\nBgCgsxUxlmcM5cuXL+fdd99NcuMKGD/96U9z991359KlS1OPOXHiRNatW5ck2bRpU44dO5bJyclc\nuHAh58+fz8DAQHp7e7N06dKMjIykXq/n6NGj2bJly4IOGACAaipaLM+4R/nSpUsZHh7O9evXU6/X\ns3379nzqU5/K888/n7Nnz6ZWq2XlypV57LHHkiRr167N1q1bs2fPnixZsiS7du2aOqhHH300w8PD\nuXr1agYHB13xAgCAWxRpz3Kt3uijaIDDhw9nx44d7T4MAADapBmxPN3rztSdPpkPAIDCKcI2DKEM\nAEAhtTuWhTIAAIXVzlgWygAAFFozY3kmQhkAgMJrVizPpLCh3OqBAACg2Fody4UN5XZ81wAAQLG1\nshELG8qJWAYA4FatasRCh3IilgEAuFUrGrHwoZyIZQAAbtXsRixFKCdiGQCAWzWzEUsTyolYBgDg\nVs1qxFKFciKWAQC4VTMasXShnIhlAABu1ehGLGUoJ2IZAIBbNbIRa/XZPuS6DX784x/n0qVL7T4M\nAAAqrre3N5/+9Kenva+QoQwAAO1W2q0XAADQTEIZAACmIZQBAGAaQhkAAKYhlAEAYBr/HykMa03o\nVrDQAAAAAElFTkSuQmCC\n",
"text": [
"<matplotlib.figure.Figure at 0x7f9420722450>"
]
}
],
"prompt_number": 16
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Lo cual sugiere que podemos usar matrices dispersas, construidas a partir de las diagonales. Solo tenemos que reescribir la funci\u00f3n `adjacency` utilizando `scipy.sparse`."
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"from scipy import sparse as ss\n",
"\n",
"def adjacency2(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",
" block_ij = np.ones((3, 3), dtype=int)\n",
" block_ij[1, 1] = 0\n",
" row_11 = np.pad(block_ij, ((0, M - 3), (0, N - 3)), mode='constant').flatten()\n",
" row_00 = np.roll(row_11, -N - 1)\n",
" mn = M * N\n",
" A = ss.diags(np.concatenate([row_00, row_00[1:][::-1]]), np.concatenate([np.arange(mn), -np.arange(mn)[1:]]),\n",
" shape=(mn, mn), dtype=int)\n",
" return A"
],
"language": "python",
"metadata": {},
"outputs": [],
"prompt_number": 17
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"adjacency2(*tab.shape)"
],
"language": "python",
"metadata": {},
"outputs": [
{
"metadata": {},
"output_type": "pyout",
"prompt_number": 18,
"text": [
"<3600x3600 sparse matrix of type '<class 'numpy.int64'>'\n",
"\twith 12960000 stored elements (7199 diagonals) in DIAgonal format>"
]
}
],
"prompt_number": 18
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Todav\u00eda podemos hacerlo mucho mejor. Aunque hemos ganado algo, estamos guardando una cantidad enorme de ceros innecesarios en la matriz. Tenemos que idear un m\u00e9todo m\u00e1s inteligente de construirla."
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"row_00 = np.ones(9, dtype=int)\n",
"row_00[0] = 0\n",
"row_00"
],
"language": "python",
"metadata": {},
"outputs": [
{
"metadata": {},
"output_type": "pyout",
"prompt_number": 19,
"text": [
"array([0, 1, 1, 1, 1, 1, 1, 1, 1])"
]
}
],
"prompt_number": 19
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"np.nonzero(row_00)[0]"
],
"language": "python",
"metadata": {},
"outputs": [
{
"metadata": {},
"output_type": "pyout",
"prompt_number": 20,
"text": [
"array([1, 2, 3, 4, 5, 6, 7, 8])"
]
}
],
"prompt_number": 20
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"M, N = tab.shape\n",
"block_ij = np.ones((3, 3), dtype=int)\n",
"block_ij[1, 1] = 0\n",
"row_11 = np.vstack([block_ij] + [np.zeros(3, dtype=int)] * (M - 3))\n",
"row_11 = np.column_stack([row_11] + [np.zeros(M, dtype=int)] * (N - 3)).flatten()\n",
"row_00 = np.roll(row_11, -N - 1)\n",
"row_00"
],
"language": "python",
"metadata": {},
"outputs": [
{
"metadata": {},
"output_type": "pyout",
"prompt_number": 21,
"text": [
"array([0, 1, 0, ..., 0, 0, 1])"
]
}
],
"prompt_number": 21
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"list(-row_00.nonzero()[0]) + list(row_00.nonzero()[0])"
],
"language": "python",
"metadata": {},
"outputs": [
{
"metadata": {},
"output_type": "pyout",
"prompt_number": 22,
"text": [
"[-1,\n",
" -59,\n",
" -60,\n",
" -61,\n",
" -3539,\n",
" -3540,\n",
" -3541,\n",
" -3599,\n",
" 1,\n",
" 59,\n",
" 60,\n",
" 61,\n",
" 3539,\n",
" 3540,\n",
" 3541,\n",
" 3599]"
]
}
],
"prompt_number": 22
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"def adjacency3(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",
" block_ij = np.ones((3, 3), dtype=int)\n",
" block_ij[1, 1] = 0\n",
" row_11 = np.pad(block_ij, ((0, M - 3), (0, N - 3)), mode='constant').flatten()\n",
" row_00 = np.roll(row_11, -N - 1)\n",
" mn = M * N\n",
" cnt = np.count_nonzero(row_00)\n",
" nonzero_idx = row_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": 23
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"adjacency2(100, 20)"
],
"language": "python",
"metadata": {},
"outputs": [
{
"metadata": {},
"output_type": "pyout",
"prompt_number": 24,
"text": [
"<2000x2000 sparse matrix of type '<class 'numpy.int64'>'\n",
"\twith 4000000 stored elements (3999 diagonals) in DIAgonal format>"
]
}
],
"prompt_number": 24
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"adjacency3(100, 20)"
],
"language": "python",
"metadata": {},
"outputs": [
{
"metadata": {},
"output_type": "pyout",
"prompt_number": 25,
"text": [
"<2000x2000 sparse matrix of type '<class 'numpy.int64'>'\n",
"\twith 16000 stored elements in Compressed Sparse Row format>"
]
}
],
"prompt_number": 25
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Ahora parece que s\u00ed :)"
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"def test_adjacency3_fails_small_board():\n",
" assert_raises(NotImplementedError, adjacency3, 3, 2)\n",
" assert_raises(NotImplementedError, adjacency3, 2, 3)\n",
" assert_raises(NotImplementedError, adjacency3, 2, 2)\n",
"\n",
"def test_adjacency3_shape():\n",
" M = 50\n",
" N = 30\n",
" assert adjacency3(M, N).shape == (M * N, M * N)\n",
"\n",
"def test_adjacency3_int():\n",
" M = 50\n",
" N = 30\n",
" assert adjacency3(M, N).dtype == np.dtype(int)\n",
"\n",
"def test_adjacency3_diag_zero():\n",
" M = 4\n",
" N = 3\n",
" assert np.all(np.diag(adjacency3(M, N).todense()) == 0)\n",
"\n",
"def test_adjacency3_symmetric():\n",
" M = 42\n",
" N = 31\n",
" assert np.all(adjacency3(M, N).todense().T == adjacency3(M, N).todense())\n",
"\n",
"test_adjacency3_fails_small_board()\n",
"test_adjacency3_shape()\n",
"test_adjacency3_int()\n",
"test_adjacency3_diag_zero()\n",
"test_adjacency3_symmetric()"
],
"language": "python",
"metadata": {},
"outputs": [],
"prompt_number": 26
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"%timeit adjacency(*tab.shape)"
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "stream",
"stream": "stdout",
"text": [
"1 loops, best of 3: 186 ms per loop\n"
]
}
],
"prompt_number": 27
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"%timeit adjacency3(*tab.shape)"
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "stream",
"stream": "stdout",
"text": [
"100 loops, best of 3: 6.25 ms per loop\n"
]
}
],
"prompt_number": 28
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"adjacency3_matrices = {}\n",
"def get_adj3_matrix(M, N):\n",
" try:\n",
" A = adjacency3_matrices[(M, N)]\n",
" except KeyError:\n",
" A = adjacency3(M, N)\n",
" adjacency3_matrices[(M, N)] = A\n",
" return A"
],
"language": "python",
"metadata": {},
"outputs": [],
"prompt_number": 29
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"%timeit -n1 -r1 get_adj3_matrix(*tab.shape)"
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "stream",
"stream": "stdout",
"text": [
"1 loops, best of 1: 22.9 ms per loop\n"
]
}
],
"prompt_number": 30
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"%timeit get_adj3_matrix(*tab.shape)"
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "stream",
"stream": "stdout",
"text": [
"1000000 loops, best of 3: 507 ns per loop\n"
]
}
],
"prompt_number": 31
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"def life_step_33(X):\n",
" \"\"\"Game of life step using adjacency matrices\"\"\"\n",
" A = get_adj3_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": 32
},
{
"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: 437 \u00b5s per loop\n"
]
}
],
"prompt_number": 33
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"%timeit life_step_33(tab)"
],
"language": "python",
"metadata": {},
"outputs": [
{
"output_type": "stream",
"stream": "stdout",
"text": [
"10000 loops, best of 3: 92.6 \u00b5s per loop\n"
]
}
],
"prompt_number": 34
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"**\u00a1\u00c9xito!** El nuevo m\u00e9todo es cinco veces m\u00e1s r\u00e1pido que los anteriores."
]
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"from functools import reduce\n",
"from time import sleep\n",
"\n",
"def random_board(M, N):\n",
" return np.round(np.random.rand(M, N)).astype(int)\n",
"\n",
"def plot_board(bb, num=None, save=False):\n",
" plt.matshow(bb, cmap=cm.gray_r)\n",
" plt.grid(False)\n",
" if save:\n",
" plt.savefig(\"bb{:03d}.png\".format(num or 0))\n",
"\n",
"bb = random_board(48, 92)\n",
"\n",
"for ii in range(0, 18, 6):\n",
" plot_board(reduce(lambda x, _: life_step_33(x), range(ii), bb), ii)"
],
"language": "python",
"metadata": {},
"outputs": [
{
"metadata": {},
"output_type": "display_data",
"png": "iVBORw0KGgoAAAANSUhEUgAAAcsAAAD9CAYAAADTVlxDAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAGfhJREFUeJzt3U9sXFfZx/HfNKVN4zQzmmL+JY1i0QoIxVEUuyAhxcZF\nXbRCMRsWTiUiVhCpSr0Ah0qmCKmLCrlDVVzSBRKVumFDh78rW7Gp2o1tGbVVSaAKQapQsZx4pnIa\np3Hsd4HwS6bXOSfH5zn3Ov5+JC86vfecM3fuzJN7z3OfU1pdXV0VAABY1215DwAAgKIjWAIA4ECw\nBADAgWAJAIADwRIAAAeCJQAADren6ugvf/mLfvWrX2llZUV9fX3q7+9P1fWWMD8/r9HRUTWbTZVK\nJT300EN65JFHtLi4qFqtpvn5ebW3t2twcFBtbW15D/eWsrKyopMnT6parerkyZMcc2OXLl3SqVOn\n9O6770qSjh8/rk9/+tMcc0OvvPKKXn31VZVKJe3du1fHjx/XlStXttQxL6V4znJlZUUnTpzQ8PCw\nqtWqfvjDH+rEiRPas2ePdddbRqPRUKPR0L59+7S0tKShoSF9//vf18TEhO6++24dOXJE9Xpdly5d\n0tGjR/Me7i3lD3/4g86dO6fLly9raGhIL7/8Msfc0M9//nPt379ffX19unbtmq5cuaLf/OY3HHMj\nc3Nz+slPfqJaraaPfexjqtVqOnjwoN59990tdcyT3IZ955139KlPfUqf+MQndPvtt+urX/2qpqen\nU3S9ZVQqFe3bt0+StH37du3evVsXL17U9PS0enp6JEm9vb2amprKcZS3ngsXLmh2dlZ9fX367787\nOeZ2PvjgA505c0Z9fX2SpG3btmnHjh0cc0M7duzQtm3bdOXKlbV/nFSr1S13zJPchr148aLuueee\ntf+uVqt65513UnS9Jc3Nzen8+fO6//771Ww2ValUJEnlclnNZjPn0d1aXnrpJT322GO6fPny2msc\ncztzc3PatWuXXnjhBf3zn/9UR0eHjh07xjE3tHPnTn3jG9/Q8ePHdccdd+jAgQPq7OzccsecBJ9b\nzNLSkkZGRnTs2DHddddd1/2/UqmU06huTTMzM9q1a5c6Ojq03mwGxzyua9eu6R//+IcefvhhPfPM\nM9q+fbvq9fp123DM43rvvff0xz/+UaOjo3rxxRe1tLSkP//5z9dtsxWOeZIry2q1qgsXLqz994UL\nF1StVlN0vaUsLy9rZGREhw8f1oMPPijpP//iazQaqlQqWlhYULlcznmUt46zZ89qZmZGs7Ozunr1\nqi5fvqznn3+eY27onnvuUbVa1X333SdJ+spXvqJXXnlFlUqFY27k3Llz+tznPqe7775bkvTlL39Z\nf/vb37bcMU9yZfnZz35W7733nubm5rS8vKzXX39dXV1dKbreMlZXV3Xq1Cnt3r1bjz766NrrXV1d\nmpiYkCRNTk6qu7s7pxHeegYGBvSLX/xCo6OjeuKJJ/TFL35Rjz/+OMfcUKVS0cc//nH961//kiS9\n8cYbuvfee3Xo0CGOuZHPfOYz+vvf/64PP/xQq6ureuONN7Rnz54td8yTZMNK0uzs7HWPjnzzm99M\n0e2WcebMGT311FPau3fv2i2RgYEB3XfffVsqvTsvb7/9tn7/+99raGiIR0eMnT9/Xi+++KKWl5f1\nyU9+UsePH9fKygrH3NBvf/tbTU5OqlQqqaOjQ9/97ne1tLS0pY55smAJAMBmRYIPAAAOBEsAABwI\nlgAAOBAsAQBwIFgCAOCwoaIErCQCANgKgq8sV1ZW9Mtf/lJPPvmknn32Wb322mtrS+YAAHArCb6y\n/N+VRCStrSSStezWs88+qwMHDoSPEgCABCqVig4dOvSR14OD5c2sJHLgwAF9/etfv+61WLUQilDA\nN2Zdh9b3k9V21ntu3c5nm9AxbUTqcVr2FyrW8SzCuH3PT5/9Qtrxlbo/nzGk/o4W9XyJ1Vbo+dm6\nzfj4eOYYSPABAMAhOFiykggAYKsIvg37vyuJVKtVvf766zpx4sS62/tcMrv2Cd3GV8xbGbFup8ZU\nhFs1qY9LaH+p5X27OLS/0GNnecytv6M+/aXez2e7WLcpiyLW59e6zdjYWOZ2wcFy27Zt+s53vqOn\nn3567dGRrOQeAAA2uw09Z3nw4EEdPHgw1lgAACgkEnwAAHDY0JXlzYg1R2E5B2WZ1hzaVmg7qd9L\nEef9pHjnQurHJnykfrQidC7Qt63Qc9/qt+VmxuCzn+UjNTE/v7zzJrJY/g76jokrSwAAHAiWAAA4\nECwBAHAgWAIA4JAswaeVTwJFaD3AkP7Xk/ekvC/L42CdUOAjNPkjdTGDvIsnWCbOWD+cHvrdjvWb\nEFPqYg2p33MREw9j/QZSGxYAgEAESwAAHAiWAAA4ECwBAHDILcHHR+pq/pZt+yrCagyx9gs9nj7H\n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\n+vIqjwAAAABJRU5ErkJggg==\n",
"text": [
"<matplotlib.figure.Figure at 0x7f9412b17590>"
]
},
{
"metadata": {},
"output_type": "display_data",
"png": "iVBORw0KGgoAAAANSUhEUgAAAcsAAAD9CAYAAADTVlxDAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAFq9JREFUeJzt3U9snMX9x/HPkhRCHLKrpe6/hCgWICAFV1FsWqlS7DoS\nB1AVc+nBQWrEiUZCwQfqFMlQVeKAKneLqGk4VCoSl17K9u/JVuyicrEtV4AgaVEaJFSB5cS7yCEO\ncXZ/B1T/mrBkhsnMPPPsvl+SD9l4n2eeeZ7dr+f5fp+ZQrPZbAoAAHyuG7JuAAAAqSNYAgBgQLAE\nAMCAYAkAgAHBEgAAA4IlAAAGm2Pt6B//+Id++9vfqtFoaGhoSMPDw7F23RGWl5c1OTmper2uQqGg\nAwcO6MEHH9Tq6qoqlYqWl5fV3d2t0dFRdXV1Zd3cttJoNHTs2DGVy2UdO3aMPg/s/PnzOn78uN5/\n/31J0pEjR/T1r3+dPg/o1Vdf1WuvvaZCoaBdu3bpyJEjunjxYkf1eSHGc5aNRkNHjx7V+Pi4yuWy\nfvKTn+jo0aPauXNn6F13jFqtplqtpt27d2ttbU1jY2N68sknNTMzo1tuuUUHDx5UtVrV+fPndejQ\noayb21b+/Oc/6/Tp07pw4YLGxsb0yiuv0OcB/epXv9KePXs0NDSky5cv6+LFi/r9739PnweytLSk\nn/3sZ6pUKvrSl76kSqWivXv36v333++oPo9yG/bdd9/V1772NX3lK1/R5s2b9d3vflfz8/Mxdt0x\nSqWSdu/eLUnasmWLduzYoXPnzml+fl4DAwOSpMHBQc3NzWXYyvZz9uxZLS4uamhoSP/9u5M+D+fj\njz/WyZMnNTQ0JEnatGmTtm7dSp8HtHXrVm3atEkXL17c+OOkXC53XJ9HuQ177tw53XrrrRv/LpfL\nevfdd2PsuiMtLS3pzJkzuvPOO1Wv11UqlSRJxWJR9Xo949a1l5dfflmPPPKILly4sPEafR7O0tKS\ntm/frhdffFHvvfeeenp6dPjwYfo8oG3btun73/++jhw5ohtvvFHf+ta31Nvb23F9ToFPm1lbW9PE\nxIQOHz6sm2+++Yr/KxQKGbWqPS0sLGj79u3q6enR52Uz6HO/Ll++rH//+9964IEH9Nxzz2nLli2q\nVqtX/A597tcHH3ygv/zlL5qcnNRLL72ktbU1/e1vf7vidzqhz6OMLMvlss6ePbvx77Nnz6pcLsfY\n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\n",
"text": [
"<matplotlib.figure.Figure at 0x7f94195c9110>"
]
},
{
"metadata": {},
"output_type": "display_data",
"png": "iVBORw0KGgoAAAANSUhEUgAAAcsAAAD9CAYAAADTVlxDAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAFfNJREFUeJzt3UFsnMX5x/HfkhRCHLIrU1NKQhSroEKgRlFsWgkpdh2J\nA6iKe+HgIBFxKpFQ8AGcIrlUlTigyt0iMA0HJJC49FK2hfYUK3ZRe7Eto4BoQlEaJITAcuJd5BCH\nOLv/A6r/tbNkJuOZ95333e9H2kM2u+8777zv7uPZ53lnCo1GoyEAAPCtrku7AQAAxI5gCQCAAcES\nAAADgiUAAAYESwAADAiWAAAYbExqR++9955ef/111et19ff3a2BgIKldt4T5+XmNjY2pVqupUCho\n3759euihh7S4uKhyuaz5+Xl1dHRoaGhIbW1taTc3V+r1uo4cOaL29nYdOXKEPg/s/PnzOnr0qD79\n9FNJ0qFDh/T973+fPg/orbfe0rvvvqtCoaAdO3bo0KFDunjxYkv1eSGJ+yzr9boOHz6skZERtbe3\n65e//KUOHz6s7du3h951y6hWq6pWq9q5c6eWlpY0PDysp59+WhMTE7rpppu0f/9+VSoVnT9/XgcO\nHEi7ubnyzjvv6PTp07pw4YKGh4f15ptv0ucBvfzyy9q1a5f6+/t1+fJlXbx4UX/605/o80Dm5ub0\nm9/8RuVyWd/5zndULpe1e/duffrppy3V54n8DPvxxx/r1ltv1S233KKNGzfqgQce0PT0dBK7bhml\nUkk7d+6UJG3atEnbtm3TuXPnND09rd7eXklSX1+fpqamUmxl/pw9e1azs7Pq7+/Xf//upM/D+eqr\nr3Ty5En19/dLkjZs2KDNmzfT5wFt3rxZGzZs0MWLF1f+OGlvb2+5Pk/kZ9hz587p5ptvXvl3e3u7\nPv744yR23ZLm5uZ05swZ3XnnnarVaiqVSpKkYrGoWq2Wcuvy5Y033tCjjz6qCxcurDxHn4czNzen\nrVu36pVXXtEnn3yizs5OHTx4kD4PaMuWLfrZz36mQ4cO6frrr9d9992nrq6ulutzCnxyZmlpSaOj\nozp48KBuvPHGVf9XKBRSalU+zczMaOvWrers7NS3ZTPoc78uX76s//znP3rwwQf1wgsvaNOmTapU\nKqteQ5/79fnnn+uvf/2rxsbG9Oqrr2ppaUl///vfV72mFfo8kZFle3u7zp49u/Lvs2fPqr29PYld\nt5Tl5WWNjo5q7969uv/++yV98xdftVpVqVTSwsKCisViyq3Mj1OnTmlmZkazs7O6dOmSLly4oJde\neok+D+jmm29We3u77rjjDknST37yE7311lsqlUr0eSCnT5/WD3/4Q910002SpB//+Mf66KOPWq7P\nExlZ/uAHP9Dnn3+uubk5LS8v65///Ke6u7uT2HXLaDQaOnr0qLZt26aHH3545fnu7m5NTExIkiYn\nJ9XT05NSC/NncHBQf/jDHzQ2NqannnpK99xzj5588kn6PKBSqaTvfve7+uyzzyRJJ06c0O233649\ne/bQ54Hcdttt+ve//62vv/5ajUZDJ06c0Pbt21uuzxOphpWk2dnZVbeO/PznP09ity3j5MmTeu65\n57Rjx46Vn0QGBwd1xx13tFR5d1o+/PBDvf322xoeHubWkcDOnDmjV199VcvLy/re976nQ4cOqV6v\n0+cB/fnPf9bk5KQKhYI6Ozv1i1/8QktLSy3V54kFSwAAsooCHwAADAiWAAAYECwBADAgWAIAYECw\nBADAYF2TErCSCACgFTiPLOv1ul577TU9++yz+t3vfqd//OMfK0vmAACQJ84jy/9dSUTSykoizZbd\nmpmZUbVadW8lAAAJKJVK2rNnzxXPOwfLa1lJpFqtat++faueWzvxbrO5EWwm53WdU6HZtm3awBwO\n9nxOrry2323Pnw3Xbdm8LyvXmc/+9NUG2++EkNcGWs/4+HjT5ynwAQDAwDlYspIIAKBVOAfL9a4k\n0mg0Vj0KhcIVD1fNtuVr21kWYz+svQ5sfy5zfV+zPgjZBtttu+zfJ9c+CMn2XMV2TePaZOX8Oecs\nN2zYoMcff1zPP//8yq0jzYp7AADIunXdZ7l7927t3r3bV1sAAIgSBT4AABisa2Tpk88ciWtpfp5K\nzmO4ZcHX7Re28nT+8s71trCQt/m0oqT7JeacpAkjSwAADAiWAAAYECwBADAgWAIAYBBNgU9oSSet\n055X0+d2Qh6LzyKOvJ/jvPN1jjlX3/BVRBXjnMtpYGQJAIABwRIAAAOCJQAABgRLAAAMMl/g4zMZ\n7Jp8jkGWE+drxboQuK/3IWz/xlpcl3S7bBbFDnkNJ/05Do2RJQAABgRLAAAMCJYAABhkPmcZ+nfx\nkKtyJC3vbbfJv4TMZzdrQ5b73JfQOb0s58GS5PpdGboNWcHIEgAAA4IlAAAGBEsAAAwIlgAAGGS+\n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\nYQAAAABJRU5ErkJggg==\n",
"text": [
"<matplotlib.figure.Figure at 0x7f9412b77e10>"
]
}
],
"prompt_number": 35
},
{
"cell_type": "code",
"collapsed": false,
"input": [],
"language": "python",
"metadata": {},
"outputs": [],
"prompt_number": 35
}
],
"metadata": {}
}
]
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment