{
"cells": [
{
"cell_type": "markdown",
"metadata": {
"hideCode": false,
"hidePrompt": false
},
"source": [
"# Numerical operations with Numpy\n",
"\n",
"## 3.1 **Broadcasting**\n",
"## 3.2 **Array shape manipulation**\n",
"## 3.3 **Sorting data**\n",
"## **Summary**\n",
"## **Exercises**\n"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {
"hideCode": false,
"hidePrompt": false
},
"outputs": [],
"source": [
"import numpy as np\n",
"import matplotlib.pyplot as plt\n",
"%matplotlib inline"
]
},
{
"cell_type": "markdown",
"metadata": {
"hideCode": false,
"hidePrompt": false
},
"source": [
"## 3.1 Broadcasting Operations\n",
"\n",
"- We just covered basic operations (add, multiple, square etc) such are element-wise but that works on arrays of same size\n",
"- **Broadcasting** comes handy when we are dealing with different shapes. This time, we'll explore a more advanced concept in numpy called broadcasting. \n",
"\n",
"- The term broadcasting describes how numpy treats arrays with different shapes during arithmetic operations. Subject to certain constraints, *the smaller array is \"broadcast\" across the larger array so that they have compatible shapes*. \n",
"- Broadcasting provides a means of **vectorizing array operations** so that looping occurs in C instead of Python. It does this without making needless copies of data and usually leads to efficient algorithm implementations. There are also cases where broadcasting is a bad idea because it leads to inefficient use of memory that slows computation.\n",
"- In this little tutorial we will provide a gentle introduction to broadcasting with numerous examples ranging from simple to involved. \n",
"- We will also go through a few examples of when to and when not to use boradcasting.\n",
"\n",
"\n",
"\n",
"#### This example below shows how broadcasting works\n",
"\n",
""
]
},
{
"cell_type": "markdown",
"metadata": {
"hideCode": false,
"hidePrompt": false
},
"source": [
"### So, lets start taking baby steps...\n",
"\n",
"Here an element-wise multiplication occurs since the two arrays are of same shape"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {
"hideCode": false,
"hidePrompt": false
},
"outputs": [
{
"data": {
"text/plain": [
"array([2., 4., 6.])"
]
},
"execution_count": 2,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"e = np.array([1.0, 2.0, 3.0])\n",
"f = np.array([2.0, 2.0, 2.0])\n",
"e*f"
]
},
{
"cell_type": "markdown",
"metadata": {
"hideCode": false,
"hidePrompt": false
},
"source": [
"##### Hint / Try it?\n",
"\n",
"What would have happened if `f = np.array([2.0, 2.0])`. would it still multiply?"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {
"hideCode": false,
"hidePrompt": false
},
"outputs": [
{
"data": {
"text/plain": [
"array([2., 4., 6.])"
]
},
"execution_count": 3,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# But if it was like this\n",
"\n",
"e = np.array([1.0, 2.0, 3.0])\n",
"f = 2.0\n",
"e*f"
]
},
{
"cell_type": "markdown",
"metadata": {
"hideCode": false,
"hidePrompt": false
},
"source": [
"##### What happened here\n",
"\n",
"This is the most simplest example on numpy broadcasting where an array and a scalar values were combined in an operation.\n",
"\n",
"so it kind of *stechted in the row direction*! The scalar **f** is stretched to become an array of with the same shape as **e** so the shapes are compatible for element-by-element multiplication.\n",
"\n",
"\n",
"\n",
"** So what are the rules then?**\n",
"- They must either be equal / same shape\n",
"OR\n",
"- One of them must be 1, like f was above"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {
"hideCode": false,
"hidePrompt": false
},
"outputs": [
{
"data": {
"text/plain": [
"array([[ 0., 0., 0.],\n",
" [10., 10., 10.],\n",
" [20., 20., 20.],\n",
" [30., 30., 30.]])"
]
},
"execution_count": 4,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# Typical broadcasting in practice\n",
"g = np.array([[ 0.0, 0.0, 0.0], [10.0,10.0,10.0],[20.0,20.0,20.0],[30.0,30.0,30.0]])\n",
"g "
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {
"hideCode": false,
"hidePrompt": false
},
"outputs": [
{
"data": {
"text/plain": [
"array([1., 2., 3.])"
]
},
"execution_count": 5,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"h = np.array([1.0, 2.0, 3.0])\n",
"h"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {
"hideCode": false,
"hidePrompt": false
},
"outputs": [
{
"data": {
"text/plain": [
"array([[ 1., 2., 3.],\n",
" [11., 12., 13.],\n",
" [21., 22., 23.],\n",
" [31., 32., 33.]])"
]
},
"execution_count": 6,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"g + h"
]
},
{
"cell_type": "markdown",
"metadata": {
"hideCode": false,
"hidePrompt": false
},
"source": [
"### What happened above?\n",
"\n",
"A 2-D (two-dimensional) array multiplied by 1-D (one-dimensional) array. It got stretched in the column direction so as to match the elements of the 2D array columns.\n",
"\n",
"\n",
"Would the same be possible for different shapes? Does broadcasting magically understands and fixes our assumptions?\n",
"\n",
"Let's take a look...\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {
"hideCode": false,
"hidePrompt": false
},
"outputs": [
{
"ename": "ValueError",
"evalue": "operands could not be broadcast together with shapes (4,3) (4,) ",
"output_type": "error",
"traceback": [
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[0;31mValueError\u001b[0m Traceback (most recent call last)",
"\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[0mg\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mnp\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0marray\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m[\u001b[0m \u001b[0;36m0.0\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m0.0\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m0.0\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m[\u001b[0m\u001b[0;36m10.0\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;36m10.0\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;36m10.0\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m20.0\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;36m20.0\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;36m20.0\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m30.0\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;36m30.0\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;36m30.0\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 2\u001b[0m \u001b[0mi\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mnp\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0marray\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m0.0\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m1.0\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m2.0\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m3.0\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 3\u001b[0;31m \u001b[0mg\u001b[0m\u001b[0;34m+\u001b[0m\u001b[0mi\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
"\u001b[0;31mValueError\u001b[0m: operands could not be broadcast together with shapes (4,3) (4,) "
]
}
],
"source": [
"g = np.array([[ 0.0, 0.0, 0.0], [10.0,10.0,10.0],[20.0,20.0,20.0],[30.0,30.0,30.0]])\n",
"i = np.array([0.0, 1.0, 2.0, 3.0])\n",
"g+i "
]
},
{
"cell_type": "markdown",
"metadata": {
"hideCode": false,
"hidePrompt": false
},
"source": [
"### We had a mismatch...\n",
"\n",
"\n",
"\n",
"Explanation: When the trainling dimensions of the arrays are different as you saw above, then broadcasting will fail making it impossible to align the values in the rows of the first array with the elements of the second array for an **element-by-element** addition or multiplication.\n",
"\n",
"### Also, is there a way to do this in one line of code\n",
"\n",
"Tip: look up more into np.tile and np.arange"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {
"hideCode": false,
"hidePrompt": false
},
"outputs": [
{
"data": {
"text/plain": [
"array([[ 0, 0, 0],\n",
" [10, 10, 10],\n",
" [20, 20, 20],\n",
" [30, 30, 30]])"
]
},
"execution_count": 8,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"a = np.tile(np.arange(0, 40, 10), (3, 1))\n",
"a = a.T # transpose this\n",
"a"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {
"hideCode": false,
"hidePrompt": false
},
"outputs": [
{
"data": {
"text/plain": [
"array([0, 1, 2])"
]
},
"execution_count": 9,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"b = np.array([0, 1, 2])\n",
"b"
]
},
{
"cell_type": "markdown",
"metadata": {
"hideCode": false,
"hidePrompt": false
},
"source": [
"##### Now, we add these two"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {
"hideCode": false,
"hidePrompt": false
},
"outputs": [
{
"data": {
"text/plain": [
"array([[ 0, 1, 2],\n",
" [10, 11, 12],\n",
" [20, 21, 22],\n",
" [30, 31, 32]])"
]
},
"execution_count": 10,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"a + b"
]
},
{
"cell_type": "markdown",
"metadata": {
"hideCode": false,
"hidePrompt": false
},
"source": [
"##### So you see that broadcasting was applied magically...\n",
"\n",
"Ask yourself, why couldn't we add original `a` and `b` ?\n",
"\n",
"Note, original a was:\n",
"```python\n",
"array([[ 0, 10, 20, 30],\n",
" [ 0, 10, 20, 30],\n",
" [ 0, 10, 20, 30]])\n",
"```"
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {
"hideCode": false,
"hidePrompt": false
},
"outputs": [
{
"data": {
"text/plain": [
"array([[1., 1., 1., 1., 1., 1.],\n",
" [1., 1., 1., 1., 1., 1.],\n",
" [1., 1., 1., 1., 1., 1.],\n",
" [1., 1., 1., 1., 1., 1.],\n",
" [1., 1., 1., 1., 1., 1.]])"
]
},
"execution_count": 11,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"c = np.ones((5, 6))\n",
"c"
]
},
{
"cell_type": "markdown",
"metadata": {
"hideCode": false,
"hidePrompt": false
},
"source": [
"##### Let's assign an array of dimension 0 to an array of dimension 1"
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {
"hideCode": false,
"hidePrompt": false
},
"outputs": [
{
"data": {
"text/plain": [
"array([[2., 2., 2., 2., 2., 2.],\n",
" [1., 1., 1., 1., 1., 1.],\n",
" [1., 1., 1., 1., 1., 1.],\n",
" [1., 1., 1., 1., 1., 1.],\n",
" [1., 1., 1., 1., 1., 1.]])"
]
},
"execution_count": 12,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"c[0] = 2\n",
"c"
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {
"hideCode": false,
"hidePrompt": false
},
"outputs": [
{
"data": {
"text/plain": [
"array([ 0, 10, 20])"
]
},
"execution_count": 13,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"d = np.arange(0, 30, 10)\n",
"d"
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {
"hideCode": false,
"hidePrompt": false
},
"outputs": [
{
"data": {
"text/plain": [
"(3,)"
]
},
"execution_count": 14,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"d.shape"
]
},
{
"cell_type": "code",
"execution_count": 15,
"metadata": {
"hideCode": false,
"hidePrompt": false
},
"outputs": [
{
"data": {
"text/plain": [
"(3, 1)"
]
},
"execution_count": 15,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"d = d[:, np.newaxis] # Here we add a new axis and make it a 2D array\n",
"d.shape"
]
},
{
"cell_type": "code",
"execution_count": 16,
"metadata": {
"hideCode": false,
"hidePrompt": false
},
"outputs": [
{
"ename": "ValueError",
"evalue": "operands could not be broadcast together with shapes (4,3) (3,1) ",
"output_type": "error",
"traceback": [
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[0;31mValueError\u001b[0m Traceback (most recent call last)",
"\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0ma\u001b[0m \u001b[0;34m+\u001b[0m \u001b[0md\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
"\u001b[0;31mValueError\u001b[0m: operands could not be broadcast together with shapes (4,3) (3,1) "
]
}
],
"source": [
"a + d"
]
},
{
"cell_type": "markdown",
"metadata": {
"hideCode": false,
"hidePrompt": false
},
"source": [
"#### Another example on broadcasting\n",
"\n",
"Let’s construct an array of distances (in miles) between cities of Route 66: Chicago, Springfield, Saint-Louis, Tulsa, Oklahoma City, Amarillo, Santa Fe, Albuquerque, Flagstaff and Los Angeles."
]
},
{
"cell_type": "code",
"execution_count": 17,
"metadata": {
"hideCode": false,
"hidePrompt": false
},
"outputs": [
{
"data": {
"text/plain": [
"array([[ 0, 198, 303, 736, 871, 1175, 1475, 1544, 1913, 2448],\n",
" [ 198, 0, 105, 538, 673, 977, 1277, 1346, 1715, 2250],\n",
" [ 303, 105, 0, 433, 568, 872, 1172, 1241, 1610, 2145],\n",
" [ 736, 538, 433, 0, 135, 439, 739, 808, 1177, 1712],\n",
" [ 871, 673, 568, 135, 0, 304, 604, 673, 1042, 1577],\n",
" [1175, 977, 872, 439, 304, 0, 300, 369, 738, 1273],\n",
" [1475, 1277, 1172, 739, 604, 300, 0, 69, 438, 973],\n",
" [1544, 1346, 1241, 808, 673, 369, 69, 0, 369, 904],\n",
" [1913, 1715, 1610, 1177, 1042, 738, 438, 369, 0, 535],\n",
" [2448, 2250, 2145, 1712, 1577, 1273, 973, 904, 535, 0]])"
]
},
"execution_count": 17,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"mileposts = np.array([0, 198, 303, 736, 871, 1175, 1475, 1544, 1913, 2448])\n",
"distance_array = np.abs(mileposts - mileposts[:, np.newaxis])\n",
"distance_array"
]
},
{
"cell_type": "markdown",
"metadata": {
"hideCode": false,
"hidePrompt": false
},
"source": [
"#### Another example\n",
"\n",
"A lot of grid-based or network-based problems can also use broadcasting. For instance, if we want to compute the distance from the origin of points on a 10x10 grid, we can do"
]
},
{
"cell_type": "code",
"execution_count": 18,
"metadata": {
"hideCode": false,
"hidePrompt": false
},
"outputs": [
{
"data": {
"text/plain": [
"array([[0. , 1. , 2. , 3. , 4. ],\n",
" [1. , 1.41421356, 2.23606798, 3.16227766, 4.12310563],\n",
" [2. , 2.23606798, 2.82842712, 3.60555128, 4.47213595],\n",
" [3. , 3.16227766, 3.60555128, 4.24264069, 5. ],\n",
" [4. , 4.12310563, 4.47213595, 5. , 5.65685425]])"
]
},
"execution_count": 18,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"x, y = np.arange(5), np.arange(5)[:, np.newaxis]\n",
"distance = np.sqrt(x**2 + y**2)\n",
"distance"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"##### Or in color... "
]
},
{
"cell_type": "code",
"execution_count": 19,
"metadata": {
"hideCode": false,
"hidePrompt": false,
"scrolled": true
},
"outputs": [
{
"data": {
"text/plain": [
""
]
},
"execution_count": 19,
"metadata": {},
"output_type": "execute_result"
},
{
"data": {
"image/png": "iVBORw0KGgoAAAANSUhEUgAAAW0AAAD8CAYAAAC8TPVwAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADh0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uMy4xLjMsIGh0dHA6Ly9tYXRwbG90bGliLm9yZy+AADFEAAAK20lEQVR4nO3c0aufB33H8c+3J5mxaa0Ts1JtWYeMDhFWt+Bg3QYrTjoVt0sFvRJys43KBjIv/QfEm10sqGxDp4haGI65dWjRglqbGrVt1DnXsVIhK05sFJ2p312cX5fSnPj71Z5fnvNtXi845Jzk6emHh/SdX5/f86S6OwDMcNXSAwDYnGgDDCLaAIOINsAgog0wiGgDDHJok4Oq6pEkTyR5Msn57j6+zVEA7G2jaK/8fnc/vrUlAKzl8gjAILXJE5FV9R9J/idJJ/nr7j65xzEnkpxIkp0jh3/z6C//4j5PneeqeNr0KS/YOb/0hAPj2qt+tPSEA+O6q3669IQD49RXf/x4dx9bd9ym0X5Zdz9WVb+U5O4kf9bdn73U8df92vX92yff/KwGPx9dfegnS084MF5x9L+XnnBg/O6131h6woHxhqv9AfaUnRv+7dQm7xdudHmkux9b/Xg2yV1JXvPc5gHw81gb7ao6WlXXPvV5ktcleXDbwwC42CZ3j1yf5K6qeur4v+/uT211FQB7Whvt7v52kl+/DFsAWMMtfwCDiDbAIKINMIhoAwwi2gCDiDbAIKINMIhoAwwi2gCDiDbAIKINMIhoAwwi2gCDiDbAIKINMIhoAwwi2gCDiDbAIKINMIhoAwwi2gCDiDbAIKINMIhoAwwi2gCDiDbAIKINMIhoAwwi2gCDiDbAIKINMIhoAwwi2gCDiDbAIKINMMjG0a6qnar6clV9cpuDALi0Z/NK+84kZ7Y1BID1Nop2Vd2Y5A1J3rfdOQD8LIc2PO69Sd6Z5NpLHVBVJ5KcSJKdl7w4Zx6+6bmvm+7Ik0svODC++ZJjS084ML5z/XVLTzg4jt239IJx1r7Srqo3Jjnb3ad+1nHdfbK7j3f38Z1rrtm3gQBcsMnlkduSvKmqHknykSS3V9UHt7oKgD2tjXZ3v6u7b+zum5O8Ocmnu/utW18GwEXcpw0wyKZvRCZJuvueJPdsZQkAa3mlDTCIaAMMItoAg4g2wCCiDTCIaAMMItoAg4g2wCCiDTCIaAMMItoAg4g2wCCiDTCIaAMMItoAg4g2wCCiDTCIaAMMItoAg4g2wCCiDTCIaAMMItoAg4g2wCCiDTCIaAMMItoAg4g2wCCiDTCIaAMMItoAg4g2wCCiDTCIaAMMsjbaVXWkqu6rqq9U1UNV9e7LMQyAix3a4JgfJ7m9u89V1eEk91bVP3X3F7a8DYBnWBvt7u4k51ZfHl599DZHAbC3ja5pV9VOVZ1OcjbJ3d39xT2OOVFV91fV/U+eO3fxNwHgOdvk8ki6+8kkt1bVi5PcVVWv6u4Hn3HMySQnk+Tq62/q6x7e2fex05y/2jl4yg9ettFvtSvC6aUHHCA3HLll6QkHyNc2OupZ3T3S3d9Lck+SO579IACeq03uHjm2eoWdqnphktcm+fq2hwFwsU3+n/WGJH9bVTvZjfxHu/uT250FwF42uXvkq0lefRm2ALCGJyIBBhFtgEFEG2AQ0QYYRLQBBhFtgEFEG2AQ0QYYRLQBBhFtgEFEG2AQ0QYYRLQBBhFtgEFEG2AQ0QYYRLQBBhFtgEFEG2AQ0QYYRLQBBhFtgEFEG2AQ0QYYRLQBBhFtgEFEG2AQ0QYYRLQBBhFtgEFEG2AQ0QYYRLQBBhFtgEFEG2CQtdGuqpuq6jNVdaaqHqqqOy/HMAAudmiDY84n+YvufqCqrk1yqqru7u6Ht7wNgGdY+0q7u7/T3Q+sPn8iyZkkL9/2MAAutskr7f9XVTcneXWSL+7xayeSnEiSIy+4LsdO/3Af5s32o2MvWHrCAbKz9IAD44kXXb30hAPj3196bOkJ42z8RmRVXZPk40ne0d3ff+avd/fJ7j7e3ccPHz66nxsBWNko2lV1OLvB/lB3f2K7kwC4lE3uHqkk709yprvfs/1JAFzKJq+0b0vytiS3V9Xp1cfrt7wLgD2sfSOyu+9NUpdhCwBreCISYBDRBhhEtAEGEW2AQUQbYBDRBhhEtAEGEW2AQUQbYBDRBhhEtAEGEW2AQUQbYBDRBhhEtAEGEW2AQUQbYBDRBhhEtAEGEW2AQUQbYBDRBhhEtAEGEW2AQUQbYBDRBhhEtAEGEW2AQUQbYBDRBhhEtAEGEW2AQUQbYBDRBhhkbbSr6gNVdbaqHrwcgwC4tE1eaf9Nkju2vAOADayNdnd/Nsl3L8MWANbYt2vaVXWiqu6vqvt/8pMf7Ne3BeBpDu3XN+ruk0lOJsmL6iVd957er2891pHfuXXpCQfGoZdfvfSEg+NHO0svODB+eP7w0hPGcfcIwCCiDTDIJrf8fTjJ55PcUlWPVtXbtz8LgL2svabd3W+5HEMAWM/lEYBBRBtgENEGGES0AQYRbYBBRBtgENEGGES0AQYRbYBBRBtgENEGGES0AQYRbYBBRBtgENEGGES0AQYRbYBBRBtgENEGGES0AQYRbYBBRBtgENEGGES0AQYRbYBBRBtgENEGGES0AQYRbYBBRBtgENEGGES0AQYRbYBBRBtgENEGGGSjaFfVHVX1jar6VlX95bZHAbC3tdGuqp0kf5XkD5O8MslbquqV2x4GwMU2eaX9miTf6u5vd/f/JvlIkj/a7iwA9nJog2NenuS/nvb1o0l+65kHVdWJJCdWX/74X/tjDz73ecN97mNJ8tIkjy+8ZHmfcx6exrlY+U/n4ulu2eSgTaJde/xcX/QT3SeTnEySqrq/u49vMuD5zrnY5Txc4Fxc4FxcUFX3b3LcJpdHHk1y09O+vjHJYz/PKACem02i/aUkv1pVv1JVv5DkzUn+YbuzANjL2ssj3X2+qv40yT8n2Unyge5+aM0/dnI/xj1POBe7nIcLnIsLnIsLNjoX1X3R5WkADihPRAIMItoAg+xrtD3uvquqPlBVZ6vqir9XvapuqqrPVNWZqnqoqu5cetNSqupIVd1XVV9ZnYt3L71paVW1U1VfrqpPLr1lSVX1SFV9rapOr7v1b9+uaa8ed/9mkj/I7m2CX0rylu5+eF/+BYNU1e8lOZfk77r7VUvvWVJV3ZDkhu5+oKquTXIqyR9fob8vKsnR7j5XVYeT3Jvkzu7+wsLTFlNVf57keJIXdfcbl96zlKp6JMnx7l77oNF+vtL2uPtKd382yXeX3nEQdPd3uvuB1edPJDmT3adsrzi969zqy8Orjyv2ToCqujHJG5K8b+ktk+xntPd63P2K/I+TvVXVzUleneSLyy5ZzupywOkkZ5Pc3d1X7LlI8t4k70zy06WHHACd5F+q6tTqrwS5pP2M9kaPu3Nlqqprknw8yTu6+/tL71lKdz/Z3bdm98ni11TVFXn5rKremORsd59aessBcVt3/0Z2/zbVP1ldYt3Tfkbb4+7saXX99uNJPtTdn1h6z0HQ3d9Lck+SOxaespTbkrxpdS33I0lur6oPLjtpOd392OrHs0nuyu7l5j3tZ7Q97s5FVm++vT/Jme5+z9J7llRVx6rqxavPX5jktUm+vuyqZXT3u7r7xu6+Obut+HR3v3XhWYuoqqOrN+lTVUeTvC7JJe8827dod/f5JE897n4myUc3eNz9eamqPpzk80luqapHq+rtS29a0G1J3pbdV1KnVx+vX3rUQm5I8pmq+mp2X+Tc3d1X9K1uJEmuT3JvVX0lyX1J/rG7P3Wpgz3GDjCIJyIBBhFtgEFEG2AQ0QYYRLQBBhFtgEFEG2CQ/wPQ5UE7R3OP2AAAAABJRU5ErkJggg==\n",
"text/plain": [
"