{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Import necessary libraries\n",
    "%matplotlib inline\n",
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "\n",
    "%load_ext autoreload\n",
    "%autoreload 2\n",
    "\n",
    "# Load test module for sanity check\n",
    "from test_utils import test"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Data Generation\n",
    "==="
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[[0.77132064 0.02075195]\n",
      " [0.63364823 0.74880388]\n",
      " [0.49850701 0.22479665]\n",
      " [0.19806286 0.76053071]] \n",
      "\n",
      " [[0.16911084 0.08833981]\n",
      " [0.68535982 0.95339335]\n",
      " [0.00394827 0.51219226]\n",
      " [0.81262096 0.61252607]\n",
      " [0.72175532 0.29187607]]\n"
     ]
    }
   ],
   "source": [
    "np.random.seed(10)\n",
    "P, Q = (np.random.rand(i, 2) for i in (4, 5))\n",
    "P_big, Q_big = (np.random.rand(i, 80) for i in (100, 120))\n",
    "\n",
    "print(P, \"\\n\\n\", Q)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Solution\n",
    "==="
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "✅ Your `naive` passed 1 tests.\n"
     ]
    }
   ],
   "source": [
    "def naive(P, Q):\n",
    "    \"\"\"\n",
    "    A naive solution for finding pairwise distances between poins in P and Q\n",
    "\n",
    "    Args:\n",
    "        P: numpy array of shape=(p, 2)\n",
    "        Q: numpy array of shape=(q, 2)\n",
    "    Returns:\n",
    "        D: numpy array of shape=(p, q)\n",
    "\n",
    "    >>> naive(np.array([[0, 1]]), np.array([[2, 3], [4, 5]]))\n",
    "    array([[2.82842712, 5.65685425]])\n",
    "    \"\"\"\n",
    "    ### SOLUTION\n",
    "    result = np.zeros((P.shape[0], Q.shape[0]))\n",
    "    for i in range(P.shape[0]):\n",
    "        for j in range(Q.shape[0]):\n",
    "            tmp = 0\n",
    "            for k in range(P.shape[1]):\n",
    "                tmp += (P[i, k] - Q[j, k]) ** 2\n",
    "            result[i, j] = tmp\n",
    "    return np.sqrt(result)\n",
    "    ### TEMPLATE\n",
    "    # # ***************************************************\n",
    "    # # INSERT YOUR CODE HERE\n",
    "    # # TODO: implement a naive solution\n",
    "    # # ***************************************************\n",
    "    # raise NotImplementedError\n",
    "    ### END SOLUTION\n",
    "\n",
    "\n",
    "test(naive)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "### SOLUTION\n",
    "def naive_2(P, Q):\n",
    "    \"\"\"\n",
    "    An alternative possible solution\n",
    "    \"\"\"\n",
    "    result = np.zeros((P.shape[0], Q.shape[0]))\n",
    "    for i in range(P.shape[0]):\n",
    "        for j in range(Q.shape[0]):\n",
    "            result[i, j] = np.sum((P[i] - Q[j]) ** 2)\n",
    "    return np.sqrt(result)\n",
    "\n",
    "\n",
    "### TEMPLATE\n",
    "### END SOLUTION"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Use matching indices\n",
    "\n",
    "Instead of iterating through indices, one can use them directly to parallelize the operations with Numpy."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[[0 0 0 0 0]\n",
      " [1 1 1 1 1]\n",
      " [2 2 2 2 2]\n",
      " [3 3 3 3 3]]\n",
      "\n",
      "[[0 1 2 3 4]\n",
      " [0 1 2 3 4]\n",
      " [0 1 2 3 4]\n",
      " [0 1 2 3 4]]\n"
     ]
    }
   ],
   "source": [
    "rows, cols = np.indices((P.shape[0], Q.shape[0]))\n",
    "print(rows, end=\"\\n\\n\")\n",
    "print(cols)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[[0.77132064 0.02075195]\n",
      " [0.77132064 0.02075195]\n",
      " [0.77132064 0.02075195]\n",
      " [0.77132064 0.02075195]\n",
      " [0.77132064 0.02075195]\n",
      " [0.63364823 0.74880388]\n",
      " [0.63364823 0.74880388]\n",
      " [0.63364823 0.74880388]\n",
      " [0.63364823 0.74880388]\n",
      " [0.63364823 0.74880388]\n",
      " [0.49850701 0.22479665]\n",
      " [0.49850701 0.22479665]\n",
      " [0.49850701 0.22479665]\n",
      " [0.49850701 0.22479665]\n",
      " [0.49850701 0.22479665]\n",
      " [0.19806286 0.76053071]\n",
      " [0.19806286 0.76053071]\n",
      " [0.19806286 0.76053071]\n",
      " [0.19806286 0.76053071]\n",
      " [0.19806286 0.76053071]]\n",
      "\n",
      "[[0.16911084 0.08833981]\n",
      " [0.68535982 0.95339335]\n",
      " [0.00394827 0.51219226]\n",
      " [0.81262096 0.61252607]\n",
      " [0.72175532 0.29187607]\n",
      " [0.16911084 0.08833981]\n",
      " [0.68535982 0.95339335]\n",
      " [0.00394827 0.51219226]\n",
      " [0.81262096 0.61252607]\n",
      " [0.72175532 0.29187607]\n",
      " [0.16911084 0.08833981]\n",
      " [0.68535982 0.95339335]\n",
      " [0.00394827 0.51219226]\n",
      " [0.81262096 0.61252607]\n",
      " [0.72175532 0.29187607]\n",
      " [0.16911084 0.08833981]\n",
      " [0.68535982 0.95339335]\n",
      " [0.00394827 0.51219226]\n",
      " [0.81262096 0.61252607]\n",
      " [0.72175532 0.29187607]]\n"
     ]
    }
   ],
   "source": [
    "print(P[rows.ravel()], end=\"\\n\\n\")\n",
    "print(Q[cols.ravel()])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "✅ Your `with_indices` passed 1 tests.\n"
     ]
    }
   ],
   "source": [
    "def with_indices(P, Q):\n",
    "    \"\"\"\n",
    "    An optimized solution using matching indices\n",
    "\n",
    "    Args:\n",
    "        P: numpy array of shape=(p, 2)\n",
    "        Q: numpy array of shape=(q, 2)\n",
    "    Returns:\n",
    "        D: numpy array of shape=(p, q)\n",
    "\n",
    "    >>> with_indices(np.array([[0, 1]]), np.array([[2, 3], [4, 5]]))\n",
    "    array([[2.82842712, 5.65685425]])\n",
    "    \"\"\"\n",
    "    ### SOLUTION\n",
    "    rows, cols = np.indices((P.shape[0], Q.shape[0]))\n",
    "    distances = np.sqrt(np.sum((P[rows.ravel(), :] - Q[cols.ravel(), :]) ** 2, axis=1))\n",
    "    return distances.reshape((P.shape[0], Q.shape[0]))\n",
    "\n",
    "    ### TEMPLATE\n",
    "    # # ***************************************************\n",
    "    # # INSERT YOUR CODE HERE\n",
    "    # # TODO: implement an optimized solution\n",
    "    # # ***************************************************\n",
    "    # raise NotImplementedError\n",
    "    ### END SOLUTION\n",
    "\n",
    "\n",
    "test(with_indices)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [],
   "source": [
    "### SOLUTION\n",
    "def with_indices_2(P, Q):\n",
    "    \"\"\"\n",
    "    An optimized solution using matching indices\n",
    "\n",
    "    Args:\n",
    "        P: numpy array of shape=(p, 2)\n",
    "        Q: numpy array of shape=(q, 2)\n",
    "    Returns:\n",
    "        D: numpy array of shape=(p, q)\n",
    "\n",
    "    >>> with_indices(np.array([[0, 1]]), np.array([[2, 3], [4, 5]]))\n",
    "    array([[2.82842712, 5.65685425]])\n",
    "    \"\"\"\n",
    "    rows, cols = np.indices((P.shape[0], Q.shape[0]))\n",
    "    distances = np.sqrt(np.sum((P[rows, :] - Q[cols, :]) ** 2, axis=2))\n",
    "    return distances\n",
    "\n",
    "\n",
    "### TEMPLATE\n",
    "### END SOLUTION"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Use a library\n",
    "\n",
    "`scipy` is the equivalent of matlab toolboxes and have a lot to offer. Actually the pairwise computation is part of the library through the `spatial` module."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [],
   "source": [
    "from scipy.spatial.distance import cdist\n",
    "\n",
    "\n",
    "def scipy_version(P, Q):\n",
    "    \"\"\"\n",
    "    A solution using scipy\n",
    "\n",
    "    Args:\n",
    "        P: numpy array of shape=(p, 2)\n",
    "        Q: numpy array of shape=(q, 2)\n",
    "\n",
    "    Returns:\n",
    "        D: numpy array of shape=(p, q)\n",
    "\n",
    "    >>> scipy_version(np.array([[0, 1]]), np.array([[2, 3], [4, 5]]))\n",
    "    array([[2.82842712, 5.65685425]])\n",
    "    \"\"\"\n",
    "    return cdist(P, Q)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Numpy Magic"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [],
   "source": [
    "def tensor_broadcasting(P, Q):\n",
    "    \"\"\"\n",
    "    A solution using tensor broadcasting\n",
    "\n",
    "    Args:\n",
    "        P: numpy array of shape=(p, 2)\n",
    "        Q: numpy array of shape=(q, 2)\n",
    "\n",
    "    Returns:\n",
    "        D: numpy array of shape=(p, q)\n",
    "\n",
    "    >>> tensor_broadcasting(np.array([[0, 1]]), np.array([[2, 3], [4, 5]]))\n",
    "    array([[2.82842712, 5.65685425]])\n",
    "    \"\"\"\n",
    "    return np.sqrt(np.sum((P[:, np.newaxis, :] - Q[np.newaxis, :, :]) ** 2, axis=2))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Compare methods"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "189 ms ± 4.21 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)\n",
      "17.9 ms ± 201 μs per loop (mean ± std. dev. of 7 runs, 100 loops each)\n",
      "1.56 ms ± 19.8 μs per loop (mean ± std. dev. of 7 runs, 1,000 loops each)\n",
      "1.54 ms ± 20.5 μs per loop (mean ± std. dev. of 7 runs, 1,000 loops each)\n",
      "191 μs ± 1.49 μs per loop (mean ± std. dev. of 7 runs, 10,000 loops each)\n",
      "1.03 ms ± 105 μs per loop (mean ± std. dev. of 7 runs, 1,000 loops each)\n"
     ]
    }
   ],
   "source": [
    "methods = [\n",
    "    naive,\n",
    "    naive_2,  # This is another possible solution. Feel free to comment it out if you have only one solution.\n",
    "    with_indices,\n",
    "    with_indices_2,  # This is another possible solution. Feel free to comment it out if you have only one solution.\n",
    "    scipy_version,\n",
    "    tensor_broadcasting,\n",
    "]\n",
    "timers = []\n",
    "for f in methods:\n",
    "    r = %timeit -o f(P_big, Q_big)\n",
    "    timers.append(r)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "iVBORw0KGgoAAAANSUhEUgAAA1oAAAJUCAYAAAAM31daAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjExLjEsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvctoD+AAAAAlwSFlzAAAPYQAAD2EBqD+naQAAVUhJREFUeJzt3Qe4VNXZP+yFgKDSVOyi2IINe++9996jiEZjibFj7NGgiTVqeEXsJvYudmzxVUSxK3ZpCtgpIigw3/Ws/zvnm0NRxA2nzH1f1wizZ8+4OTNnz/6t8qwmpVKplAAAACjMbMW9FAAAAIIWAADATKBHCwAAoGCCFgAAQMEELQAAgIIJWgAAAAUTtAAAAAomaAEAABSsWdEv2FBNmjQpff7556l169apSZMmdX04AABAHSmVSmn06NFp4YUXTrPNNmN9U4LW/4mQ1aFDhyLfHwAAoAEbMmRIWnTRRWfouYLW/4merPIPs02bNsW9OwAAQIMyatSo3AlTzggzQtD6P+XhghGyBC0AAKDJb5hSpBgGAABAwQQtAACAgglaAAAABRO0AAAACiZoAQAAFEzQAgAAKJigBQAAUDBBCwAAoGCCFgAAQMEELQAAgIIJWgAAAAUTtAAAAAomaAEAABRM0AIAACiYoAUAAFAwQQsAAKBgghYAAEDBBC0AAICCCVoAAAAFa1b0C1KMjqf29qOsYwMv2L6uDwEAgAZKjxYAAEDBBC0AAICCCVoAAAAFE7QAAAAKJmgBAAAUTNACAAAomKAFAABQMEELAACgYIIWAABAwQQtAACAgglaAAAABRO0AAAACiZoAQAAFEzQAgAAKJigBQAAUDBBCwAAoGCCFgAAQMEELQAAgIIJWgAAAAVrluqBTz75JH3wwQdprbXWSvPMM0+tx5588sk0YcKEKZ7ToUOHtMIKK+S/v/vuu2nw4MG1Hm/dunVaf/31Z/KRAwAA1LOg9cILL6Rzzz03vfXWW+nzzz9PTz/9dNpkk01q7XPVVVelH374oeb+uHHj0rPPPpvOOOOM/Nzwz3/+M913331plVVWqdmvY8eOghYAAFB9QWvQoEHpT3/6U+6ZWnzxxae6z7333lvr/s0335yee+659Pvf/77W9g022CDdddddM/V4AQAA6n3Q2nffffOfQ4cOne7n9OrVK2222WZpqaWWqrX9+++/zwGsbdu2qVOnTqlly5aFHy8AAECDmaM1vT766KMcpm677bYpHovtI0eOzEMQx44dm3r06JF23333ab7W+PHj861s1KhRM+24AQCA6tKgqg5ee+21ad5550277rprre077bRTDlgx5+vTTz9NRx11VNp///3T+++/P83X6t69e+79Kt+iuAYAAEBVBa2JEyemG2+8Mc/Nmn322Ws9tt122+WwFJo0aZILZcw555zpoYcemubrdevWLfeAlW9DhgyZ6f8GAACgOjSYoYMPP/xwGjZsWOratesv7jvbbLOldu3a5f2npUWLFvkGAABQtT1aMWwwKgsut9xytbZPmjRpivlV77zzTq5oWFnuHQAAoCp6tD777LO8htZXX32V7/fr1y+vk7X00kvnW9mIESNS7969c9ia2pDCWOh4v/32y2XiY+Hiiy66KIeyvffee5b+ewAAAOq8R+vdd99Nl112WbrlllvS1ltvnZ566ql8v2/fvrX2e+WVV/Lje+655xSv0bx581wEo1mzZrka4YABA9IFF1yQFz+OxwAAAGa1JqVSqTTL/6/1UAw/jIIaURijTZs2dX04qeOpvev6EKrewAu2r/qfAQBANRpVQDZoMHO0AAAAGgpBCwAAoGCCFgAAQMEELQAAgIIJWgAAAAUTtAAAAAomaAEAABRM0AIAACiYoAUAAFAwQQsAAKBgghYAAEDBBC0AAICCCVoAAAAFE7QAAAAKJmgBAAAUTNACAAAomKAFAABQMEELAACgYIIWAABAwQQtAACAgglaAAAABRO0AAAACiZoAQAAFEzQAgAAKJigBQAAUDBBCwAAoGCCFgAAQMEELQAAgIIJWgAAAAUTtAAAAAomaAEAABRM0AIAACiYoAUAAFAwQQsAAKBgghYAAEDBBC0AAICCCVoAAAAFE7QAAAAKJmgBAAAUTNACAAAomKAFAAAgaAEAANRverQAAAAKJmgBAAAUTNACAAAomKAFAABQMEELAACgYM1SPTB+/Pj05Zdfpvnmmy+1aNGi1mPfffddGjNmTK1tzZs3TwsssMAUr/Pjjz+mUaNGpXnnnTc1adJkph83AABAvevR+vTTT9OJJ56YFltssdShQ4f04osvTrHPqaeemn73u9+lddZZp+a2//7719pn0qRJ6YQTTkjt2rVLHTt2TIsuumi69957Z+G/BAAAoJ4ErQhD0TP12GOP/ex+2223XRo6dGjN7cknn6z1+MUXX5yuv/769MILL+QerVNOOSXtvffeacCAATP5XwAAAFDPgtbxxx+fTjrppNS+fftf3Pebb77JQwOn5qqrrkpdu3ZNq6yySpptttnSsccem3vJevbsOROOGgAAoBEUw4ieryWWWCK1atUqrbfeeunVV1+teeyLL75IgwYNSuuvv36t52ywwQapX79+dXC0AABAtav3QWuNNdZIr7zySho5cmQumLHkkkumLbfcMg0fPjw/HtvC5L1icb/82LQKcMQww8obAABAVQStGBK46qqr5r+3bds2XXPNNTkk3XXXXXlbDBUMEyZMqPW8n376KTVt2nSar9u9e/f8euVbFOMAAACoiqA1uTnmmCMttNBCaeDAgfn+Iosskv8s93CVjRgxouaxqenWrVvuJSvfhgwZMpOPHAAAqBb1PmiVSqVa94cNG5bnZMUQwtCmTZtcBOOJJ56o2Sd6t/r06ZM22mijab5urNcVz628AQAANPigNXbs2FyuvXK+Vdwvz5eKIYJR/OKee+5J77//fg5TO+64Y14n64ADDqh5nTPOOCPdeOON6eqrr05vvvlm6tKlS95+5JFH1tG/DAAAqGZ1GrR69+6dFyDeZZdd8jC/P//5z/l+r169anqdevTokedjxT5nnXVW2nzzzVP//v1r9UDttttu6ZZbbslha9ddd81B7dlnn03zzTdfHf7rAACAatWkNPnYvCoV4SyKYsR8rfowjLDjqb3r+hCq3sALtq/6nwEAQDUaVUA2qPdztAAAABoaQQsAAKBgghYAAEDBBC0AAICCCVoAAAAFE7QAAAAKJmgBAAAUTNACAAAomKAFAABQMEELAACgYIIWAABAwQQtAACAgglaAAAABRO0AAAACiZoAQAAFEzQAgAAKJigBQAAUDBBCwAAoGCCFgAAQMEELQAAgIIJWgAAAAUTtAAAAAomaAEAABRM0AIAACiYoAUAAFAwQQsAAKBgghYAAEDBBC0AAICCCVoAAAAFE7QAAAAKJmgBAAAUTNACAAAomKAFAABQMEELAACgYIIWAABAwQQtAACAgglaAAAABRO0AAAACiZoAQAAFEzQAgAAKJigBQAAUDBBCwAAoGCCFgAAQMEELQAAgIIJWgAAAAUTtAAAAAomaAEAABRM0AIAAGhMQeuHH35IN9xwQ1pnnXVSu3bt0vPPPz/FPq+//no68MAD01JLLZU6deqUDj300DR06NBa+xx//PH5+ZW39dZbbxb+SwAAAOpJ0Dr77LPT008/nU466aQ0cuTINGHChFqPT5w4MQerbbbZJj3xxBPp7rvvToMGDUqbb755Gjt2bM1+8feNN944DRw4sOb22GOP1cG/CAAAIKVmdflDuOCCC1KTJk2m6KEqa9q0aerfv3+tbT179sy9W3379k2bbbZZzfbmzZvnniwAAICq7tGKkPVrjRkzJv85xxxz1Nrep0+ftPDCC6flllsuHX744WnEiBGFHScAAECjLYYxadKkdPLJJ+cwteaaa9ZsX2SRRdI///nP3Mt13XXXpXfffTetv/766fvvv5/ma40fPz6NGjWq1g0AAKDBDx38tY477rjUr1+/9Nxzz6Vmzf7/Qz/jjDNq/r7YYoule++9Ny266KLptttuy3O8pqZ79+7pnHPOmSXHDQAAVJcG06N14oknpptvvjk9/vjjacUVV/zZfeebb760+OKLp/fff3+a+3Tr1i0X4CjfhgwZMhOOGgAAqEYNokcrhgv26tUrVx5cY401fnH/GDIYBTbmn3/+ae7TokWLfAMAAKi6Hq3oebrmmmtyyKqcl1U51+qQQw5JH3zwQSqVSmnYsGHpoIMOSrPPPnvad9996+SYAQCA6lanQes///lPLsm+wgor5Ps77LBDvh9l38PXX3+d/x7rZG255Za1FiSOYYQheqW22GKLtMcee6Q555wzL2o8bty49L//+7+5SAYAAMCs1qQU3UB15Mcff6y18HBZy5Yt8y0OLeZPTU2Equi1mvz1Jt82vaLqYNu2bfP/r02bNqmudTy1d10fQtUbeMH2Vf8zAACoRqMKyAZ1OkcrQtHPBaNYZ+vXLEI8oyELAACgquZoAQAANDSCFgAAQMEELQAAgIIJWgAAAAUTtAAAAAomaAEAABRM0AIAACiYoAUAAFAwQQsAAKBgghYAAEDBBC0AAICCCVoAAAAFE7QAAAAKJmgBAAAUTNACAAAomKAFAABQMEELAACgYIIWAABAwQQtAACAgglaAAAABRO0AAAACiZoAQAAFEzQAgAAKFizX/uEN998M912223pueeeS0OHDs3bOnTokDbaaKO0zz77pM6dOxd9jAAAAI2zRysC1hZbbJHWXHPN9Pzzz6e11147/fGPf8y3tdZaKwev1VdfPW255ZbprbfemrlHDQAA0Bh6tLbddtt0/PHH596s9u3bT3Wfr776Kt14441pm222SZ999lmRxwkAAND4gtYHH3yQ5pprrp/dJwLYCSeckI444ogijg0AAKBxDx38uZA1ceLE9Morr6Thw4f/4r4AAACN3QxVHYz5WF27dq25v/POO+e5Wx07dkyPP/54kccHAABQHUHr1FNPTYcffnj+e//+/VPfvn3TRx99lC666KJ01llnFX2MAAAAjT9ovfHGG2mllVbKf3/yySfTbrvtlpZaaqnUpUuX9Pbbbxd9jAAAAI0/aM0zzzxpwIAB+e/3339/2myzzfLfv/zyy/wYAABANfvVCxaHAw88MJdwX3LJJdOnn36att9++7z9oYceyvO1AAAAqtkMBa3zzz8/rbDCCmnQoEFpr732Sq1bt87bv/3223T66acXfYwAAACNP2g1adIk7b///lNsF7IAAABmMGiVff/997kXa3KLLrqony0AAFC1Zihovf766+mQQw7Jf05NqVT6rccFAABQXUEryrivuOKKqUePHqldu3bFHxUAAEC1Ba133303PfvsszVFMAAAAPiN62gts8wyafDgwTPyVAAAgEZvhnq0LrzwwnTwwQens846Ky211FK5CmGlZZddtqjjAwAAqI6gNXHixDRgwIC04447TvVxxTAAAIBqNkNB67jjjsvraB1zzDGKYQAAABQRtIYPH54uvvji1KpVqxl5OgAAQKM2Q8UwYg7WRx99VPzRAAAAVGuP1u6775723XffdO6556all156imIYq6yySlHHBwAAUB1B6y9/+Uv+c6+99prq44phAAAA1WyGhg5++eWXP3v7Nfr27ZtLxa+zzjrp1Vdfneo+zz33XNp7773TJptskgtwDBs2bIb2AQAAqLdBq3379j97+zU9Y3/605/Scsstl1566aU0atSoKfZ5+umn0+abb54XST7ppJPShx9+mNZff/00evToX7UPAABAvQtaW265ZQ5Dv+TFF1/M+06Pk08+Ob9mlIqfltNPPz3PCTvvvPPS9ttvn+6555709ddfp549e/6qfQAAAOrdHK199tknL1C8yCKL5D9XX331tMACC+T5WFHu/eWXX04PPPBA+uKLL9Lf/va36XrNtm3b/uzjY8eOzUML//jHP9Zsm3POOdMWW2yRnnzyyXTCCSdM1z4AAAD1MmgdeuihOWzdeOON6bbbbksXXnhh+vHHH/NjLVq0SGuvvXY6+uij04EHHpjmmmuuQg5uyJAhadKkSWnhhReutT3u9+nTZ7r3mZrx48fnW9nUhi0CAADM9KqDEaCi5yhuEyZMyL1XUdp9vvnmS82azVABw5/1008/1QS5SnPMMUfNY9Ozz9R07949nXPOOYUfMwAAwAwVwwgRrKLXaKGFFpopISvMM888+c9vvvmm1vaYfzXvvPNO9z5T061btzRy5MiaW/SMAQAA1GnQmhUiyC244IJ5/lelKKCx6qqrTvc+UxM9YG3atKl1AwAAaPRBK3Tp0iX16tUrffbZZ/n+3Xffnd599928/dfsAwAAMKvMnDF/06l3797pr3/9a01RjZj7FT1LXbt2zbdw5pln5nWxll566dShQ4c0dOjQdOWVV6Y111yz5nWmZx8AAICqCFprrbVWuuyyy6bYvuiii9Ya4nfHHXekzz//PI0YMSKHqdatW9faf3r2AQAAqPdB66uvvkr3339/+uSTT9L555+ft8V6VtGL1LRp0+l6jahWGLfpEXOxJi/hPiP7AAAA1Ms5Wq+//npabrnl0sUXX1xrceIbbrghr7MFAABQzWYoaJ1wwgnp+OOPzwUnKh1xxBHp0ksvLerYAAAAqmfo4CuvvJLuu+++/PdYsLhsmWWWSR988EFxRwcAAFAtPVoxB2v06NFTbH/vvfdqFhAGAACoVjMUtLbbbrt09tlnp0mTJtX0aA0aNCgdeeSRaccddyz6GAEAABp/0IoiGC+++GJaaKGFctjq3LlzLqk+bty41L179+KPEgAAoLHP0VpggQVS//79c3n3mK8VYeu0005Lu+++e5p99tmLP0oAAIBqWEcrAtWee+6ZbwAAABQQtML333+fvv322ym2L7roor/lZQEAAKovaMWCxYccckj+c2pKpdJvPS4AAIDqClpdunRJK664YurRo0dq165d8UcFAABQbUHr3XffTc8++2xq3bp18UcEAABQjeXdl1lmmTR48ODijwYAAKBae7QuvPDCdPDBB6ezzjorLbXUUjWLFpctu+yyRR0fAABAdQStiRMnpgEDBqQdd9xxqo8rhgEAAFSzGQpaxx13XNp///3TMcccoxgGAABAEUFr+PDh6eKLL06tWrWakacDAAA0ajNUDCPmYH300UfFHw0AAEC19mjtvvvuad99903nnntuWnrppacohrHKKqsUdXwAAADVEbT+8pe/5D/32muvqT6uGAYAAFDNZihoffnll8UfCQAAQDUHrfbt2xd/JAAAANUWtJ5//vn85wYbbFDz92mJfQAAAKrVdAetDTfcsGb+Vfnv02KOFgAAUM2mO2j98MMPU/07AAAAM7iOVsuWLdMBBxxQ8/efuwEAAFSzX7Vg8d133z3zjgQAAKAagxYAAAAzobz7mDFjfnGfVq1a/dqXBQAAqN6g1bp161/cR9VBAACgmv3qoHXrrbfOnCMBAACo1qC1zz77zJwjAQAAaCQUwwAAAKjLoNWiRYui//8AAADVHbTGjRs3844EAACgkTB0EAAAoGCCFgAAQMEELQAAgIIJWgAAAAUTtAAAAAomaAEAABRM0AIAACiYoAUAAFAwQQsAAKBgghYAAEDBBC0AAICCCVoAAAAFa5bquaOPPjqNGzduiu0bbrhh+v3vf5//fsstt6Rnnnmm1uOLLLJIOuecc2bZcQIAADSYoLXmmmumn376qeb+0KFDc4Bae+21a7Y9//zz6fXXX09HHHFEzbZ55plnlh8rAABAgwha5V6rsnPPPTe1atUq7bPPPrW2d+zYMXXt2nUWHx0AAEADDFqVSqVSuv7663PIat26da3H3n///XTsscemtm3b5mGFW221VZ0dJwAAUN0aVDGMPn36pIEDB6bDDjus1vYmTZqkJZdcMvdqjR07Nu2+++7p4IMP/tnXGj9+fBo1alStGwAAQNX1aF177bVppZVWSmuttVat7WeeeWZaaKGFau5H0Npggw1yz9c222wz1dfq3r27YhkAAEB192h988036d57752iNytUhqyw3nrrpQ4dOqQXXnhhmq/XrVu3NHLkyJrbkCFDZspxAwAA1afB9GhFCfcYInjAAQdM1/4//PBDntM1LS1atMg3AACAqu3Ruu6669Kee+6Z2rVrV2v7hAkTUu/evWttu+aaa9KXX36Ztt1221l8lAAAAA2kR+uVV15Jb7zxRrriiiumeCx6uWLu1mmnnZaWX375NHjw4LzvpZdemocQAgAAzGoNImg1b948l3WPsu2Ta9q0abrnnntyefdYtHjuuedOq622Wmrfvn2dHCsAAECDCForr7xyvv2cTp065RsAAEBdazBztAAAABoKQQsAAKBgghYAAEDBBC0AAICCCVoAAAAFE7QAAAAKJmgBAAAUTNACAAAomKAFAABQMEELAACgYIIWAABAwQQtAACAgglaAAAABRO0AAAABC0AAID6TY8WAABAwQQtAACAgglaAAAABRO0AAAACiZoAQAAFEzQAgAAKJigBQAAUDBBCwAAoGCCFgAAQMEELQAAgIIJWgAAAAUTtAAAAAomaAEAABRM0AIAACiYoAUAAFAwQQsAAKBgghYAAEDBBC0AAICCCVoAAAAFE7QAAAAKJmgBAAAUTNACAAAomKAFAABQMEELAACgYIIWAABAwQQtAACAgglaAAAABRO0AAAACiZoAQAAFEzQAgAAKJigBQAAUDBBCwAAoGDNUj331FNPpTfffLPWtvbt26cDDjig1rYffvghPfroo2nEiBGpc+fOaf3115/FRwoAANBAgtYdd9yR+vTpk7bffvuabePHj6+1z/Dhw9PGG2+cZp999rTKKquk008/Pe2www7phhtuqIMjBgAAql29D1ph5ZVXTpdddtk0Hz/11FPTHHPMkfr27ZtatmyZe8BWXXXVtOuuu6add955lh4rAABAg5ijNWzYsNSzZ890++23p4EDB9Z6bNKkSenuu+9OBx98cA5ZYaWVVspDB6M3DAAAYFZrEEHryy+/zL1V1113XVp22WXTP/7xj5rHBg8enMaMGZO3V4r777777jRfM4Yfjho1qtYNAACgKoYOHn744elf//pXmm22/5cJ//Of/+RCGBtttFFae+210+jRo/P2du3a1Xre3HPPXfPY1HTv3j2dc845M/noAQCAalTve7RWW221mpAV9ttvvzT//PPnAhlhzjnnzH9OHqqih6r82NR069YtjRw5suY2ZMiQmfZvAAAAqku9D1pTE9UFIxyFxRZbLLVo0SJ9/PHHtfaJ+8sss8w0XyOe06ZNm1o3AACARh+0Jk6cmD788MNa255++unc+7TBBhvk+82bN0/bbbddHlIYhTHK87aeeeaZtMsuu9TJcQMAANWtXs/RKpVKac8990zLL798WmGFFXKAuummm9Khhx6a18kqu/DCC9N6662Xtt122zxvK0JXzOGKYYYAAACzWr3u0WrWrFnq379/2mOPPdK4ceNSp06d0nPPPZd69eqVmjRpUrNfDBF8++2309Zbb52rCZ599tnp0UcfTU2bNq3T4wcAAKpTk1J0G5GLZ7Rt2zbP/aoP87U6ntq7rg+h6g28YPuq/xkAAFSjUQVkg3rdowUAANAQCVoAAAAFE7QAAAAKJmgBAAAUTNACAAAomKAFAABQMEELAACgYIIWAABAwQQtAACAgglaAAAABRO0AAAACiZoAQAAFEzQAgAAKJigBQAAUDBBCwAAoGCCFgAAQMEELQAAgIIJWgAAAAUTtAAAAAomaAEAABRM0AIAACiYoAUAAFAwQQsAAKBgghYAAEDBBC0AAICCCVoAAAAFE7QAAAAKJmgBAAAUTNACAAAomKAFAABQMEELAACgYIIWAABAwQQtAACAgglaAAAABRO0AAAACiZoAQAAFEzQAgAAKJigBQAAUDBBCwAAoGCCFgAAQMEELQAAgIIJWgAAAAUTtAAAAAomaAEAABRM0AIAACiYoAUAAFAwQQsAAKBgzVIDMH78+PTuu++mZs2apd/97nepRYsWtR5/77330tChQ2tta9WqVVpnnXVm8ZECAADU86BVKpXSmWeemXr27JkWWWSRNHbs2PTtt9+mHj16pN12261mv8suuyzde++9qXPnzjXbllhiCUELAACoE/U+aEXv1YcffpjatGmTt3Xv3j3tv//+6eOPP04LL7xwzb4bbrhhuuuuu+rwaAEAABrAHK3ZZpstnX766TUhK3Tp0iWNGzcuvf7667X2jd6uF154Ib3zzjvpxx9/rIOjBQAAaAA9WlPTt2/f/OcyyyxTa/vTTz+dvvrqqzRs2LA0YcKEPLxwl112+dl5X3ErGzVq1Ew8agAAoJrU6x6tyQ0fPjwdffTR6YADDqgVtHbYYYf0+eefp379+qXBgwenrl27pn333Td98MEH03ytGILYtm3bmluHDh1m0b8CAABo7BpM0Prmm2/SNttsk4tcXH311bUei6A199xz5783adIknXPOOally5bpwQcfnObrdevWLY0cObLmNmTIkJn+bwAAAKpDgxg6GJUGt9hii1yy/eGHH05zzjnnL87tmmeeefIwwmmJIhuTl4kHAACoih6tcsiKcPXII4/ksFVp0qRJafTo0bW2DRgwIA0cODCtvPLKs/hoAQAA6nmPVlQP3HrrrfNixDFc8KWXXqp5bPnll8/l3SdOnJjWWmutdOCBB6YVVlghz9H6+9//ntZdd92099571+nxAwAA1aleB60o4x6l3WMh4iuvvLLWYyeccEIOWs2bN0/PPfdcrjJ4ww035Lla5557bjrooINS06ZN6+zYAQCA6lWvg1aErCeffPIX95tvvvnSmWeeOUuOCQAAoMHP0QIAAGhoBC0AAICCCVoAAAAFE7QAAAAKJmgBAAAUTNACAAAomKAFAABQMEELAACgYIIWAABAwQQtAACAgglaAAAABWtW9AsC06fjqb39qOrYwAu2r+tDAAAaKT1aAAAABRO0AAAACiZoAQAAFEzQAgAAKJigBQAAUDBBCwAAoGCCFgAAQMEELQAAgIIJWgAAAAUTtAAAAAomaAEAABRM0AIAACiYoAUAAFAwQQsAAKBgghYAAEDBBC0AAICCCVoAAAAFE7QAAAAKJmgBAAAUTNACAAAomKAFAABQMEELAACgYIIWAABAwQQtAACAgglaAAAABRO0AAAACiZoAQAAFEzQAgAAKJigBQAAUDBBCwAAoGCCFgAAQMEELQAAgIIJWgAAAAUTtAAAAAomaAEAABSsUQWtCRMmpO+++66uDwMAAKhyjSJolUqldMopp6S2bdumhRZaKC222GLpgQceqOvDAgAAqlSz1AhceumlqWfPnunZZ59Nq666arr88svTHnvskd56663UqVOnuj48oIp1PLV3XR9C1Rt4wfYz9WfgPW787zFA1QatK664InXt2jWtscYa+f7xxx+frrzyynT11VenSy65pK4PDwCAek6jSd0b2MgaTRr80MEvv/wyDRw4MG2wwQa1tm+44YapX79+dXZcAABA9WrWGIJWaN++fa3t8803X3rxxRen+bzx48fnW9nIkSPzn6NGjUr1waTxY+v6EKrezP4seI/r3qz4ffc+1z2/y43frPhdXvGsx2b6/4Of9/Y5W8/UH5Hzdd0bVU+uwyuPJWpBVG3QatKkSU3FwUpxv2nTptN8Xvfu3dM555wzxfYOHTrMhKOkIWp7WV0fATOb97g6eJ8bP+9xdfA+N35t6+G11+jRo3PBvaoMWgsvvHD+c8SIEbW2x/3yY1PTrVu3PJerbNKkSembb75J8847b014o/6KVoYIxUOGDElt2rSp68NhJvAeVwfvc+PnPa4O3ufGr9re41KplEPWz+WJRh+0ImGuvPLK6Yknnkh77bVXTW9Wnz590lFHHTXN57Vo0SLfKrVr126mHy/Fil/0avhlr2be4+rgfW78vMfVwfvc+FXTe9x2BnuyGk3QCqeffnrab7/90jrrrJPWXXfddNFFF+UeqiOPPLKuDw0AAKhCjSJoxZpZUdgi1s8699xzU+fOndMzzzyT5p9//ro+NAAAoAo1iqAV9t9//3yjOsSwz7POOmuK4Z80Ht7j6uB9bvy8x9XB+9z4eY9/vSal31KzEAAAgMa3YDEAAEB9I2gBAAAUTNACAAAomKAFAABQMEELAACgYIIW0Gj1798/L2a+7LLLpi233DLfB/jiiy9St27d0korrZRWXXXVdP311/uhADUmTZqUPvroo/RbCVpUhbfffjvdfffd6eOPP67rQ2EWifd7xx13TOuuu266+OKL86Lm22yzTRo1apT3gBpDhgxJBx10UFpiiSXStttum79cadwGDRqU1lprrTR27Nh00UUXpQ033DB16dIlPfbYY3V9aBTo3XffTZdffnl65pln/Fz5VWLlqyuvvDJttNFGacyYMb/5xaDR+uabb0o77bRTaaGFFiqtueaapWbNmpW6d+9e14fFTDZw4MDS4osvXvroo49qtg0dOjTWDCzdddddfv5k77//fmn++ecvnXPOOaXhw4eXxo8fX5o0aZKfTiMW7++6665buvnmm2ttX3XVVUsHH3xwnR0XxTrzzDNL8803X2nzzTcvNW3atPS3v/3Nj5jpumY89NBDS23bti21bNkyXzN069at9Fs0+20xDeq3vfbaKy211FK5BbN58+bp1ltvzUPJ1lxzzbT55pvX9eExk/zwww/pkksuye992SKLLJLatGmTe7YgdO/ePe2yyy7pzDPP9AOpEtFjufPOO6cDDjig1vblllvOuaEROP/881OTJk3SQw89lN5///0099xz59ENcS2wxRZb5O9+mJYYBbPgggumgQMH5vtHHnlkvpY47LDD8qiHGdEk0tYMPRPqoYkTJ6ZXX301n0zfe++9PGwsxuLPNtts6eqrr07nnHNOPuGee+65+QRM9fjqq6/SfPPNl+dprbbaanV9ONQDxx57bHr55ZfTNddckz744IP06KOPptdffz3P6YvzxRxzzFHXh8gssvbaa6ftt99e6G7gDjzwwHTLLbekXr16pUMPPbRm+2677ZaGDx+eXnjhhTo9Puqv+A7o1KlTGjx4cOrQoUPe9tNPP6X11lsv37/nnntm6HXN0aJRueKKK9IRRxyR/x4BK/z3v//NF9b3339/evrpp/M+EbIilFE9XnzxxdyjtfLKK9f1oTCLffbZZ+moo47KhQ922GGH9NJLL+XtUQxh3Lhx+SL7sssuSx07dsz7xVydSy+91PtUJeIz8MYbb+T5GDRsF154YZprrrnyxXKlmKcbjbD//ve/6+zYqP/ngXK4KouRUGeccUa6995701NPPTVDryto0WBE9ZcRI0b87D7LLLNMngAbIapz587p+++/T4ccckg677zz8sXT8ssvn/eLbuFjjjlmFh059UGcJDfZZJPUtGnTmpNpTIancXvrrbdyQ0urVq3yEJDFFlssbbrpprnnaqGFFkqvvfZaPk8899xz6bTTTku///3v8+fkm2++qetDZxb53//93zzqIUZAlH333Xd+/vXY119/nUeoxDDQk046KTemhIUXXjg3oPTo0SONHDmyZv8Y9nX88cenU045Jf++U73GjBmTjjvuuLTAAgukFVdcMZ/7y8OH55lnnnTzzTfX2n/99dfPf8ZzZqSBXtCiwdh3333zSbLyl6Vfv3619oneimiVKI/NjpAV+0V3cFmEtRg+GBXoqB59+vTJJd7DI488koP45CdUGp/ooTrrrLNyS3fMy4xhIOHhhx+eYt8I3z179syhPJ5H9ZwbojerRYsWuUEvLt5jCBp1K3qf4uJ2cgMGDMgXyJ9++mn+fe7du3daZZVV8nSBcMIJJ+SGlQhilaIhJXoonn/++Vn2b6Bu3HTTTflzMPn5PYaP7rTTTmn06NF5KGA0rsTve3yW4rNx4oknpr///e+5wb4s/h7zvaNq9R133PHrD+Y3ldKAWahPnz6lLl261FQFu+yyy3IVwSuuuKLWfvPOO2/p1ltvzX//7rvvcjWpueaaq3TEEUeU/vjHP5YWWGCB0sUXX+y9qyJRUS5Od/fff39pu+22Ky277LKlRx55pK4Pi5lswoQJpdlmmy1XoXzllVdytbl11lmn9NJLL9Xa77HHHittuOGGuQLh1ltvXfrwww+9N1UkKtKeddZZpZNOOilXqvvHP/6RK1BSt5544on8+xvVQSttttlmpeOOO67m/tixY0trrLFGafXVV6+5Pojqss2bNy+99957tZ47bty4WXT01KVHHnkkf3YqKw937dq1tOSSS5a22GKLmm0TJ04srb/++rk6dfjxxx/z52ueeeYpXXDBBfk6c+GFFy7dfffdpd///vel3Xbb7Vcfi6BFg/L222/nwFR20003lWafffbS0UcfnS+qwqabblo69dRTa/b54YcfSldddVUOaVGmM16Dhim+UOMEGrdf84UZwTuCVlxIX3rppaWffvppph4ns95XX32VSzrvuuuupWuuuaZme5TpjRDVoUOHXNK7snz7M888k/+Mc8eQIUNKo0aN8tY1YP/5z39Ka621Vqljx46lO+64Y7qeM3LkyFz+u0WLFrms84gRI2b6cTL9Xn755dInn3xSa9ucc845xfv7xhtv5HP8448/XrMtrgWiYY3q/ex8/PHHNffffffd3DhfeQ0Z+vfvn0NZ+bMT1xZ//etfS2uvvXZp++23r/meOOGEE2qFtOklaNGgROtEBKt///vfNduee+653IsVJ9S4UIqWLifXxufBBx8sLbjggrnVcpFFFiktscQStVqrfs5rr72WT65ffvnlTD9OZp6ePXvWakQp+/TTT0uLLrpo6Q9/+EPphRdeyO932SGHHFJq1apV6fPPP6/1nOjhit5uGof4XCy33HL5YikaUqa3ISZ6rg444IDSq6++OtOPkV/v22+/zb+/5VEqIXolTjvttCn2XWGFFXJjS9mbb75Zuvzyy62NVyXrXz344IM19+Mc8PXXX+dQfuedd9ZsP/bYY/P1w+Tnh2hkic/PtBphI+zH9cfVV1/9q49N0KLBOfnkk/NF1ffff1+zLS64YzhY586d8+Kj8TiNq2Wqffv2pf/93//N9+NkGC3XMVwkuv6pDvfee29ukYzep0q77LJL7rGemsGDB5fatWtX2mijjXKL5pgxY0o33nhjHg7y4osvzqIjZ2b64osvSk2aNCl98MEHftCNUAzpjB7pGNEQ/vKXv+TG1cl7H6PhJIZ9Un1uv/323Agfw0z/53/+p7T44ovnoPXnP/8593DHyKZycI9rie7du9d6fnyW2rRpU/rnP/9ZqxEmGnAihMVzzj777Bk6NkGLeuvhhx8uHXnkkfkkW3lhFb1W0bJwxhln1No/foFiFfi4EIshBDE/i4YreimGDRuW/x4nunJLZbQsxfCwpZZaKl9403jFsK74nY6AVNkTVTkcpDw8sLLFe3IxNGSllVbK54W4rbfeerV6vWj4n5MY+tejR4/8+bjoootK2267bZ6PF3N1aDj69euXR6REsDrxxBNr3t+YWx3z6Mr3F1tssfx7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tEaXh4OVe7ciaE2tN5PGK+bhxXy8cpiOz8Ecc8xRuv7662vtFy3fcUF+xx131NGRUvQ5Pn7XY7hwvPf33Xdf7tWqnKsZ26Ka4FtvvVWrIEK0dtfX4UDVLoqSxFDQ8nyZEL1UcW4vz8uKC+gIy9FjFd8JsSB9FESJhhSNatWlW7du+TqhsiH26quvzkPIf269rBjhEGuolSuMxtpY8d3RkBpeBC1ghvTr1y9PZl9llVVyi3WIL9PopYySvGU77rhjrWpj0RIVJ8wo21+56CiNv1UzWrhj7kblxVq0aE5+MR1Vyiws2zjE7/rka1/FhdPkZdxjSHFUpKNhiDlV8bsboamydzJ6LCuXbok5WzHUKwJY/P7HUgymB1Sf0aNH589LjGwpKy/h8nNiaHH0hpU/Ow2xEmWT+E8C+JVeeumltO6666aVV145vfbaazXbn3766bT55punfv36pTXWWCM99dRTaeutt0477bRT+u6779JXX32VHn/88bTAAgv4mVeZ888/P1122WXpww8/TO3atUtjx45Nyy67bNp///1T9+7d6/rwmAm22267tNBCC6Vrr7221vYVVlghLbLIIvlcEN555518PnnllVfS7373O+9FAzB06NC04447pu+//z499NBD+X3r3LlzOuSQQ9Lxxx9fs9+PP/6Yhg0blj8Hs88+e50eM7Pea6+9lj8fzz77bHrhhRfSgAED0uKLL54efvjhtP322+ff/eWXX77Wc+677760yy671Hx+mjRpkpo3b94g377Z6voAgIZp7bXXzhfI8QU6YcKEmu2bbrppPkEee+yx+f5mm22Ww9fSSy+dDjjggBzAhKzqdMIJJ6TWrVuns88+O9+fc84504UXXphuuummNG7cuLo+PH6DuJBac80107zzzpuOO+64NHHixLx9gw02yBdNEaorrbfeeumJJ55IDzzwQE3wigt3IavhWHTRRdPzzz+fG0siJMeFdDS8vfnmm7X2i3AVF9ZCVvX5+9//nhtb4vwen5fx48enk08+OT+2zTbb5GB+6KGH5u1ljz76aOrVq1fN/fjcNNSQldV1lxrQ8IeDxVjrSjEkMLr7b7nlljo7NooVw70mL7s9I2Lh0hg2FvNvyiYv+Uz9E/NuYnhfLMFRafDgwXmB8SWWWCKvZxNrYcUC1Keddlp+POZhxhytynmaYf311y+tuuqq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IL3kkkuWjjvuuFl0lNSl6OWM3/HHHnssF6qI0vwxiiF6vWMUw/nnn19TMGXttdcu7bnnnlO8Rjz2+uuv59dSHOXXEbQAqkiUao+wFT1cMSer3KI5YsSI0rLLLlvaZptt6voQ+Y3VJN9///38fsZcnFjLrCx6NWJbPFYphohusMEG03zNuLCKqpM9evTQu1mHevfunYf8Lr744jVz6KJnOoZ8Te3iN8q8h+jFjN7M6667ruaxWPcoergHDBgwC/8F1JXDDjssDy+NhpL77rtvirL/Dz/8cL4f3wcRvp599llvVkEELYAqExfccZEVF2xR0rusctFSGqYYMhgLyYYYBhrzsr766qtpDhWMluoYGhTzdqLFmvrp0ksvzb+vcZEcPVLlYaERqqO3onL+ZbnsfhTHqLw/uRga6j2vngaYKGoRIWryUL7PPvuU1lxzzZr7USwnimIYMl4MQQugCsUQsmjJ/KVhR9R/lQtJxwV3zKEo92Qss8wypSOPPLLm8T59+uSLrRg+GpPaI3hFMQTzdeqvGPrZsmXLac6LOf7440tzzjln6dZbb80X0RG+Ntpoo+mqHEn1iLWx4pw/+WLTMRQ4tpcbZKIwUpw34nPEb9ck/lOr3jsAVeHWW2/N6+0stNBCdX0ozKBjjz02XX311XltpG233TYNHTo0dejQIb3//vvpd7/7XXrwwQfTrrvuml577bXUuXPn/Jy//vWv6R//+EdaZZVV0mmnnZa22WYbP/96LNY0i9/RWNtoxx13zOsXPfHEE2nYsGHp8MMPT4ccckg6+eST05VXXpkmTJiQ5pxzznT66aenU045pa4PnXok1kxcccUV03rrrZeuv/76mu3vvfdeWm655fLnqbyeWkSDWHuN307QAoAG6pxzzkk33nhj+vbbb9Ndd92VNt988xyounbtmv70pz/lfSJIxUVWnz596vpwmUHx3sZC0rG4+KabbppvgwcPTt27d0+ffvppXjQ2Fp397LPP0pJLLplatGjhZ11lpicc9e7dO+2www6pR48e+fMU/vznP6e+ffumF198cRYdaXURtACggXj22Wdzb8Wdd96ZW5/79euXdtppp9SzZ8900EEHpQceeCA99NBD6c0330yPPvpofs6AAQPSSiutlG6//fa022671fU/gYKMGzcuzTXXXKl///65d5LqNHz48HTwwQenPffcMx166KG/uH/0fMe5YYUVVsgNMG3atEn33ntvWnTRRWfJ8Vab2er6AACA6bPWWmul2WefPfdcffnll2nNNdfMLdlxkdSrV6+08847p7Zt2+ZANnbs2PycGBZ0xRVXpGWWWcaPuZEYMWJEOuCAA9J2220nZFWpb775Jg8JjuC04YYb5rA1PS699NLUrFmztPfee6e77747vfzyy0LWTKRHCwAakO+//z4PB4yhYk8//XQ67rjj0rLLLpvnW/373/9Of/jDH/I+MUwoLsRpPE488cT03//+Nw8X7NKlSzr33HNz8Kb6HH/88XmuVfRmxnng14gerJifO/fcc8+04+P/EbQAoIEZPXp02mKLLXLxg5iP9Z///CdfgIdrr702z7u4+eabcw8XjUeE6wjR7du3T82bN6/rw6EOff3117mXOopbxHBh6idBCwAaoCiAEUMII3QNHDgwffXVV3nYYBgzZkxq1apVXR8iUIAYJhzDgVu3bp1/52PoX/jnP/+ZK04OGjQoLbDAAtP9etFAU34NZi5ztACgAYphP48//nhq2bJlvnAq92gFIQsah8svvzz3XF1wwQVp9913zyXaP/nkk/zYH//4x7TUUkulM844Y7peKxplouz/xhtvPJOPmjJBCwAaqBhC9uSTT+a5Wdtvv31dHw5QoKgiet555+WCFa+88kr64IMPcqXJqDT6448/5l6pKG4Rw4Vff/31ab5OFMy54YYb8lzO6Pm+5557vE+ziKGDAABQz+y7775pjjnmSNddd13NthgmHIEphg3GgtUhgtfIkSPz8MLJxfpYsaZeFE2J3rHVV199lv4bqp0eLQAAqOOS/RdffHE69dRTc1XJMHHixDwXs1LHjh3zeniVvVKXXHJJGjZsWBo6dGitfaMqZZRxj8qkzz//vJBVBwQtAACoI4899lheVPz999/Pc7CiomDYcsst08MPP5w+++yzWvvHnK2oQFm29NJL5+dOvuhwrLX23nvvpf32228W/UuYnKGDAABQB6JARYcOHfISDZOvezdu3Li0wgor5F6sWBcvCt/89NNPuZhFLO8QPVbUb4IWAADUgaggGJUD488lllgiDRkyJPXp0yfPudpnn33S559/nnu2ovDN1ltvnauLzjvvvOn+++9Pc845p/esnhO0AABgJoshgb169cph6uCDD05rrLFG3r7VVlvlqoGxTlZUBVx77bVz8IoerLfffjsNHz48XX/99XkeV/RmxULks81m9k9DIGgBAMBMFKXZN9tssxyUYkHxRx55JD399NNp/fXXz6Xan3jiidSuXbu01lprpebNm6cBAwak5ZdfPvdoLbTQQt6bBkrQAgCAmSAC01FHHZWaNm2aunTpkku2hz/84Q85XMXjLVq0mOJ5sX5WVBZ89dVXvS8NmKAFAAAzwffff586deqUvvjii9xzVRZzsKJ6YJReP+200/K2WC+rvKDwpEmT0l133ZUWW2wx70sDZoAnAADMBHPNNVe64IILcrXAmG9V1rZt2/TXv/41de/ePQ8PDDF0MBYWjm19+/YVshoBPVoAAPAb3XDDDenCCy9MH330UVpnnXXS//zP/+Ty7KVSKc/FirlWd999d83+0Wu1+uqrp86dO6ebbrrJz78R0qMFAAC/QQz/u+SSS3K4inlXiy++eNp8883zUMAmTZqkyy+/PN177725AEbNRfhss6XLLrssDRs2LPd40fgIWgAA8AsGDRqUe6zKotx6GDp0aLryyitzcYuoKhiFL6KyYKtWrfJzwpprrpkOOuigPCdr4sSJNa8R+8fzotIgjY+gBQAAv+C7777LPVdRrOKMM85IK664Yg5Sb775Zh4WGOtgxfZYB2vddddN77zzTh4aWBZztT799NN0zTXX+FlXCUELAAB+wUorrZQ22WSTtPvuu+fA9MYbb+QhggsvvHD68MMP03LLLZc+++yzHLxOOeWUXLY9qg7ecccd+fkLLrhguu222/KCw1SHZnV9AAAAUN9FdcAobBHVAddYY40csMLKK6+ci160adMm91bF0MEQ+5500km1qgdut912dXb8zHqqDgIAwHS69NJL07nnnpt7sdq3b5+3Pf/887n4xRZbbJG6deuWJkyYkPf74YcfUu/evc3BqlKCFgAATKeoEFgeRtijR4+a7c8991weMhhDChdZZJF0yCGHpJNPPjk1a2YAWbUStAAA4Fd49NFH0w477JD69++fhw6GUaNG5eGDUCZoAQDArxRFLT7++OP0l7/8JfXs2TN16tQpr6MFZYIWAAD8Sl9//XXq0qVLGj16dB4muP/+++dFiKFM0AIAACiY2A0AAFAwQQsAAKBgghYAAEDBBC0AAICCCVoAAAAFE7QAAAAKJmgBAAAUTNACAAAomKAFABW+//77dNttt6UxY8YU/nOZNGlSfu2vv/7azxygkRO0AGgQSqVSDilx++GHH6Z4/PHHH8+PDR06dLpfc9y4cfk5I0eOrNn25Zdfpn333TcNHz48Fe3HH3/Mr/3hhx8W/toA1C+CFgANwsSJE3NIidudd95Z67Evvvgi7bjjjvmxvn37Tvdrfvfdd/k5Q4YMmQlHDEA1E7QAaFA22mijdO2119badtNNN6V11113qvuPGjUqPfbYY/k2YsSIWj1k999/f/77o48+mnu2+vTpU+u5n3zySerdu3d64403pvra0ev1wAMP5NcePXr0VPeJ3qv4/7z99tu/+t8KQMMlaAHQoOy///6pX79+6aOPPqrZFsHr0EMPnWLfu+++O3Xs2DH97W9/S5dddlnq1KlTuuqqq2qC1iOPPJL/HgHrvvvuS//9739rnnv88cfnXrIePXqkDTbYIB177LG1XvvKK69MSy65ZLr00ktTt27d8v/nmWeeqbXPhRdemDp37pyuuOKK3HO22267Ff7zAKCeKgFAA/DTTz+V4mvrzjvvLO2zzz6lbt265e3//e9/S/POO29p5MiRNY+HTz75pDTXXHOVnnnmmZrXePnll0stW7YsDRgwIN8fNmxYfs5bb71Vs8+nn36at3Xp0qU0adKkvC1eo0mTJqVBgwbl+x9++GGpefPmpdtuu63mecccc0ypY8eOpXHjxuX7H3zwQalp06alBx98MN+P19p3333za7/44ouz4CcGQF3SowVAgxO9VzfeeGOet9WrV6904IEHptlnn73WPrfeemuad955c3GLmNN1xx135KGA88wzT62eq2n5wx/+kJo0aZL/Hj1as802W/rggw9qeso6dOiQ9t5775r9//KXv6SBAweml156qWafZZZZJu2www75frzWSSedVOjPAYD6q1ldHwAA/Fqbb755DlYRniJETa0ARoSeqPJ311131dq+4YYbpvbt2//i/yMCWVnTpk1Ts2bNcpXCMGjQoDxssNICCyyQ5pprrvxYGDx4cB5OWGmJJZb4lf9SABoqQQuABid6hw455JB05JFHphVXXDHPgyqHoLI2bdqkueeeOxe5KFoEtXLPVdn48ePT2LFja0Jc9Ka9+uqrtfb59ttvCz8WAOonQwcBaJAiaG2zzTZ5yN7UxGMDBgxIzz77bK3tUR2wXCGwVatW+c/JQ9oviaGEUYnw448/rtkWPWctW7ZMq622Ws0+EbQqS8ffc889v+r/A0DDpUcLgAYp5kj9XG/VFltskQ4//PA8R+qYY45JSy21VHr//ffTvffemxc3bt26dQ5aUYkwqgPuuuuuefhf7PdLttpqq7TddtulLbfcMh133HF5weN4jTPOOCO/Rth6661zKfo4jvj/Ryn466+/vtCfAQD1lx4tABqEKEYRxSciYE1NzKOa/PGrr7469zSNGTMmvfDCCzkEvfjii7XmSj300EN5vtXDDz+ci2TEPKt4nQhilfbaa6+0yCKL1NyPYhfRm/b666+nzz//PN1+++25zHulBx98MB122GGpf//+ubcr/t/x2tMzRwyAhq1JlB6s64MAAABoTPRoAQAAFEzQAgAAKJigBQAAUDBBCwAAoGCCFgAAQMEELQAAgIIJWgAAAAUTtAAAAAomaAEAABRM0AIAACiYoAUAAFAwQQsAACAV6/8Du8nxAHZXLXkAAAAASUVORK5CYII=",
      "text/plain": [
       "<Figure size 1000x600 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "plt.figure(figsize=(10, 6))\n",
    "plt.bar(\n",
    "    np.arange(len(methods)), [r.best * 1000 for r in timers], log=False\n",
    ")  # Set log to True for logarithmic scale\n",
    "plt.xticks(np.arange(len(methods)) + 0.2, [f.__name__ for f in methods], rotation=30)\n",
    "plt.xlabel(\"Method\")\n",
    "plt.ylabel(\"Time (ms)\")\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "base",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.2"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 1
}
