{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Basic usage of the cnnclustering module"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2020-06-10T14:08:21.731048Z",
     "start_time": "2020-06-10T14:08:20.956678Z"
    }
   },
   "outputs": [],
   "source": [
    "import sys\n",
    "\n",
    "# Optional dependencies\n",
    "import matplotlib as mpl\n",
    "import matplotlib.pyplot as plt\n",
    "\n",
    "from cnnclustering import cluster"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The notebook was created using Python 3.8."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2020-06-10T14:08:21.738573Z",
     "start_time": "2020-06-10T14:08:21.734435Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "3.8.8 (default, Mar 11 2021, 08:58:19) \n",
      "[GCC 8.3.0]\n"
     ]
    }
   ],
   "source": [
    "# Version information\n",
    "print(sys.version)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Notebook configuration"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We use `matplotlib` to create plots. The `\"matplotlibrc\"` file in the root directory of the CommonNNClustering repository is used to customize the appearance of the plots."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2020-06-10T14:08:21.764830Z",
     "start_time": "2020-06-10T14:08:21.741591Z"
    }
   },
   "outputs": [],
   "source": [
    "# Matplotlib configuration\n",
    "mpl.rc_file(\n",
    "    \"../../matplotlibrc\",\n",
    "    use_default_template=False\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2020-06-10T14:08:21.785980Z",
     "start_time": "2020-06-10T14:08:21.773031Z"
    }
   },
   "outputs": [],
   "source": [
    "# Axis property defaults for the plots\n",
    "ax_props = {\n",
    "    \"aspect\": \"equal\"\n",
    "}\n",
    "\n",
    "# Property defaults for plotted lines\n",
    "dot_props = {\n",
    "    \"marker\": \"o\",\n",
    "    \"markeredgecolor\": \"k\"\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Getting started"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The `cnnclustering.cluster` main module provides the `Clustering` class. An instance of this class is used to bundle input data (e.g. data points) with cluster results (cluster label assignments) alongside the clustering methods and convenience functions for further analysis (not only in an Molecular Dynamics context). As a guiding principle, a `Clustering` cluster object is always associated with one particular data set and allows varying cluster parameters."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<div class=\"alert alert-info\">\n",
    "\n",
    "**Info:** The user is also refered to the [__scikit-learn-extra__ project](https://github.com/scikit-learn-contrib/scikit-learn-extra) for an alternative API following a parameter centered approach to clustering as `sklearn_extra.cluster.CommonNNClustering`.\n",
    "\n",
    "</div>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Commonly, a clustering object is recommended to be created via `cnnclustering.cluster.prepare_clustering` which takes care of the correct composition of the object. By default, data points that should be clustered are passed to the preparation function as a nested sequence, e.g. a list of lists. This will be understood as the coordinates of a number of data points. Similar data structures, like a two-dimensional `NumPy` array would be acceptable, as well. It is possible to use different kinds of input data formats instead, like for example pre-computed parwise distances, and it is described later how to do it (<font color=\"red\">refer to section</font>). "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2020-06-10T14:08:21.893872Z",
     "start_time": "2020-06-10T14:08:21.879177Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Clustering()\n"
     ]
    }
   ],
   "source": [
    "# 2D Data points (list of lists, 12 points in 2 dimensions)\n",
    "data_points = [   # Point index\n",
    "    [0, 0],       # 0\n",
    "    [1, 1],       # 1\n",
    "    [1, 0],       # 2\n",
    "    [0, -1],      # 3\n",
    "    [0.5, -0.5],  # 4\n",
    "    [2,  1.5],    # 5\n",
    "    [2.5, -0.5],  # 6\n",
    "    [4, 2],       # 7\n",
    "    [4.5, 2.5],   # 8\n",
    "    [5, -1],      # 9\n",
    "    [5.5, -0.5],  # 10\n",
    "    [5.5, -1.5],  # 11\n",
    "    ]\n",
    "\n",
    "clustering = cluster.prepare_clustering(data_points)\n",
    "print(clustering)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The data points that we passed to create the clustering object are stored on the instance and can be accessed via the `input_data` attribute."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2020-06-10T14:08:21.924442Z",
     "start_time": "2020-06-10T14:08:21.896014Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[ 0. ,  0. ],\n",
       "       [ 1. ,  1. ],\n",
       "       [ 1. ,  0. ],\n",
       "       [ 0. , -1. ],\n",
       "       [ 0.5, -0.5],\n",
       "       [ 2. ,  1.5],\n",
       "       [ 2.5, -0.5],\n",
       "       [ 4. ,  2. ],\n",
       "       [ 4.5,  2.5],\n",
       "       [ 5. , -1. ],\n",
       "       [ 5.5, -0.5],\n",
       "       [ 5.5, -1.5]])"
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "clustering.input_data"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "When we cluster, we are essentially interested in cluster label assignments for these points. These labels will be exposed as `labels` attribute on the instance, which is currently `None` because no clustering has been done yet."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 7,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "clustering.labels is None"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "To cluster these points we will use the `fit` method. The clustering depends on two parameters:\n",
    "  \n",
    "  - `radius_cutoff`: Points are considered neighbours if the distance between them is smaller than this cutoff radius $r$.\n",
    "  - `cnn_cutoff`: Points are assigned to the same cluster if they share at least this number of $c$ common neighbours.\n",
    "  \n",
    "For the clustering procedure, we ultimately need to compute the neighbouring points with respect to the `radius_cutoff` for each point in the data set. Then we can determine if two points fulfill the criterion of being part of the same cluster. How this is done, can be controlled in detail but by default the input data points are assumed to be given in euclidean space and the neighbours are computed brute force. For larger data sets it makes sense to use a different approach (<font color=\"red\">refer to section</font>)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2020-06-10T14:08:21.943519Z",
     "start_time": "2020-06-10T14:08:21.927207Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "-----------------------------------------------------------------------------------------------\n",
      "#points   r         c         min       max       #clusters %largest  %noise    time     \n",
      "12        1.500     1         None      None      2         0.417     0.333     00:00:0.000\n",
      "-----------------------------------------------------------------------------------------------\n",
      "\n"
     ]
    }
   ],
   "source": [
    "clustering.fit(radius_cutoff=1.5, cnn_cutoff=1)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "A clustering attempt returns and prints a comprehensive summary of the cluster parameters and the outcome. You can suppress the recording with the keyword argument `rec=False` and the printing with `v=False`:\n",
    "\n",
    "  - #points: Number of data points.\n",
    "  - r: Radius cutoff *r*.\n",
    "  - c: Common-nearest-neighour cutoff *c*.\n",
    "  - min: Member cutoff (valid clusters need to have at least this many members).\n",
    "  - max: Maximum cluster count (keep only the *max* largest clusters and disregard smaller clusters).\n",
    "  - #clusters: Number of identified clusters.\n",
    "  - %largest: Member share on the total number of points in the largest cluster.\n",
    "  - %noise: Member share on the total number of points identified as noise (not part of any cluster).\n",
    "  \n",
    "The `min` (keyword argument `member_cutoff`) and `max` (keyword argument `max_clusters`) only take effect in an optional post processing step when `sort_by_size=True` (default). Then the clusters are sorted in order by there size, so that the first cluster (cluster 1) has the highest member count. Optionally, they are trimmed in the way that valid clusters have a minimum number of members (`member_cutoff`) and only the largest clusters are kept (`max_clusters`)."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The outcome of the clustering are cluster label assignments for each point. Points classified as *noise* (not part of any cluster) are labeled 0. Integer labels larger than 0 indicate the membership to one of the identified cluster of each point. If clusters where sorted (`sort_by_size = True`), cluster 1 has the highest member count."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2020-06-10T14:08:22.011740Z",
     "start_time": "2020-06-10T14:08:22.001961Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Labels([1, 1, 1, 1, 1, 0, 0, 0, 0, 2, 2, 2])"
      ]
     },
     "execution_count": 9,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "clustering.labels"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The `labels` attribute of a cluster object always holds the result of the latest fit. All cluster results (from fits where `rec=True`) are collected in a summary without storing the actual labels."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2020-06-10T14:08:22.028063Z",
     "start_time": "2020-06-10T14:08:22.014786Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "-----------------------------------------------------------------------------------------------\n",
      "#points   r         c         min       max       #clusters %largest  %noise    time     \n",
      "12        1.500     1         None      None      2         0.417     0.333     00:00:0.000\n",
      "-----------------------------------------------------------------------------------------------\n",
      "\n",
      "-----------------------------------------------------------------------------------------------\n",
      "#points   r         c         min       max       #clusters %largest  %noise    time     \n",
      "12        2.000     1         None      None      2         0.583     0.167     00:00:0.000\n",
      "-----------------------------------------------------------------------------------------------\n",
      "\n"
     ]
    }
   ],
   "source": [
    "clustering.fit(radius_cutoff=2, cnn_cutoff=1, v=False)\n",
    "print(*clustering.summary, sep=\"\\n\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "If you have Pandas installed, the summary can be transformed into a handy `pandas.DataFrame`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2020-06-10T14:08:22.091142Z",
     "start_time": "2020-06-10T14:08:22.041812Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>n_points</th>\n",
       "      <th>radius_cutoff</th>\n",
       "      <th>cnn_cutoff</th>\n",
       "      <th>member_cutoff</th>\n",
       "      <th>max_clusters</th>\n",
       "      <th>n_clusters</th>\n",
       "      <th>ratio_largest</th>\n",
       "      <th>ratio_noise</th>\n",
       "      <th>execution_time</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>12</td>\n",
       "      <td>1.5</td>\n",
       "      <td>1</td>\n",
       "      <td>&lt;NA&gt;</td>\n",
       "      <td>&lt;NA&gt;</td>\n",
       "      <td>2</td>\n",
       "      <td>0.416667</td>\n",
       "      <td>0.333333</td>\n",
       "      <td>0.000140</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>12</td>\n",
       "      <td>2.0</td>\n",
       "      <td>1</td>\n",
       "      <td>&lt;NA&gt;</td>\n",
       "      <td>&lt;NA&gt;</td>\n",
       "      <td>2</td>\n",
       "      <td>0.583333</td>\n",
       "      <td>0.166667</td>\n",
       "      <td>0.000087</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "   n_points  radius_cutoff  cnn_cutoff  member_cutoff  max_clusters  \\\n",
       "0        12            1.5           1           <NA>          <NA>   \n",
       "1        12            2.0           1           <NA>          <NA>   \n",
       "\n",
       "   n_clusters  ratio_largest  ratio_noise  execution_time  \n",
       "0           2       0.416667     0.333333        0.000140  \n",
       "1           2       0.583333     0.166667        0.000087  "
      ]
     },
     "execution_count": 11,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "clustering.summary.to_DataFrame()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "A cluster object comes with a variety of convenience methods that allow for example a quick look at a plot of data points and a cluster result."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2020-06-10T14:08:23.721868Z",
     "start_time": "2020-06-10T14:08:22.093354Z"
    },
    "scrolled": true
   },
   "outputs": [
    {
     "data": {
      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAu4AAAFKCAYAAABCeNqvAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjMuNCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8QVMy6AAAACXBIWXMAABcSAAAXEgFnn9JSAAAxm0lEQVR4nO3de3hcd3no++8rY2U2cZIhntgI4hJi2sQykJy0hA0U4pC4BXZ0aROJZm/MLmopLSmlTQ60pdDNoXSXlksPZyf0wom4PS0gxaeWlMJunWJTLoU00NDUdsKuE0AExfbYGQeHrViRfuePGTmyJcu2bmuW5vt5nnnGWmvNWq+XZr3r1W/91m9FSglJkiRJ9a0p6wAkSZIknZqFuyRJkpQDFu6SJElSDli4S5IkSTlg4S5JkiTlgIW7JEmSlAMW7pIkSVIOWLhLkiRJOWDhLkmSJOWAhbskSZKUAxbukiRJUg5YuEuSJEk5YOEuSZIk5YCFuyRJkpQDFu6SjhMRH4+IFBGvyjoWSTpRRFxUy1FfyzqWehYRr6rtp49nHYsWjoW7tMCmFL7vnud6dtbW84sLE5kkScozC3dJkiQpByzcpYX3u8AG4NZ5ruf1tfX8zbwjkiTN2ZQrqRdlHYsa29OyDkBablJKI8DIAqznewsQjiRJWiZscZckSZJywMJdqomISyPiYxHx7YjYHxHbIuI1Myw3edNoISIuj4i+iPh+RFxVm//u2vxfneGzL4+IL0bEDyNiT0S8LiI6I+KTEfFwRPz7lGVnHN1lcjSFiHhaRPx+RNwdEY9GxPaIePlJ/m8/GRF/HRH3R8SPIuJbEfHbEbFi/ntOkhZORPx4RHw6IvZFxGhE7I2IT0TE80/xuV+s5cf3zTDvZPn0moi4q5aTj0bEdyLi1oh4UW3+zohIwH+tfeSh2nqmDUAQEU0R8daI+MeIeCwivhERb4uIlSeJ81cj4tzaOWN3RPzFCcu9OCL6a/vhodp54jkz/N8iIn4zIr4ZEZWI+EpE/GcgZttfyie7ykhARHQBHwNWAF8EHgdeDnRExJ8Av5NSSid87AbgdmAcuBvYd4ptvAr4W+AgMABcAXwKOAzsAP6uulisTCmNnSLk/0C17/sLgK8D5wLXAi+JiBeklB6ast0XAvfUtnNf7d9XAO8DLgduPMW2JGlJRMRmoI9qTvsm8L+A9UA30BkRxQXc1g3AZ4EK8PfARG1bNwHNwD8Dg8D9wNXATwB/Dfywtop7pqzrrNq8nwd281SO/xNgQ0T80gznkLXA16jey7QL+NaU9b2O6vnlR8A/AGcD/wW4JiKuTCk9PGU9/w/w67V/fxl4BPhT4Ikz3yuqdxbuangRcTHVAvq7QFtK6du16ecBnwDeTvUE8tkTPno78BXg9Sml759iGwHcBjwKvDCl9EitFeYu4D8CbznVOk7wQuAQ8OMppbGIaAL6qZ40Xku1KJ+0D/gN4PaU0o9q8awEPg/8QkT8ZUppxxlsW5IWXESsAf4/4EngZSmlr02Z9yzgF1JKqZpOF8RbqbZK/2RK6TtTtvUTQBEgpfSh2rSPUy3cf2/qslP8BtX8+wfAu1NKE7W8/yfA/wn8D+BfTvjMO4AjQHtKaWjK9n8M+Cjwb8BrUkr7atP/I9VzxttrsRMRL6VatD9G9fz1j7XpZwN3AuvOeK+ortlVRoIPAGcBvzxZtAOklA4DPUAZ+FBEFE743AGg8zQL7h8DLgb+NqX0SG39Y0Av1ZadV84h7ndNtsynlCao/vEB8ONTF0op7Usp/Y/Jon3Ktm+v/Xj1HLYtSQvtncAq4G1Ti3aAlNIPJovoBXQO8L+Bqa3XpJS+nVK6+3RXUmvkeSfV1u7/VsvH1FrY30H1HHLDDB9tBt44tWiveQ/Vc9KNk0V7bX1fo1rQX19rrAH4ndr72yaL9tqyj1NtLNIyY4u7Glqtj/fPAt9LKX3pxPkppUMR8bdU+ze+gOql00l/mlJ67DQ3dUHtff8J0w+dMP90jQP/dMK0w7X3s09zHY/U3p93htuWpMXQTrV7x18t0fa+CFwGDEbEnwFfSCkdmcN6rqDatWcl8GczXBE4Arx4hs/9GzMP93s11Xx+8wzrugh4NvAs4PtUr9gmlm6fKWMW7mp0FwNPp9qH8WQeqL2/kOML9xMve87mfwFjtXVMdVntfdcZrAvgUEpp/IRpJ/afPKbWNeZGYDPVRN9CtZ88eOVNUsYiYhXwHODbKaX/vUSb/b+o3sv0qtrryYj4OvCOqa3Xp2Fj7f3FzFygw/RGG4B/ObHfe0ScQ/UKLcCbZtnmObWW/guAh2ot7GoAnrDV6CaL1+ZZljmr9v70E6af9ljttW43nwB+JiLeHBHnRMTVVPs+/juw83TXVTNxugvWRiH4l9r2/wvV/qN/X3tJUj2YbFo+aQPEQkspHQKuBN4AfJrqTaovA3ZGxC+ewaom83FXSilO8pqpoJ/pHDL5///bWdYVKaU9Uz7z5BnEqpyzcFeju59qS/hPxMnveLqk9v6vJ0w/0xPM86jeQHRb7f0LVPtXvi6lNHqG6zoTH6XaIvQXwHNSShtSSj8P/PEiblOSTltK6YfAd4Dnnjh84gKZsYdBSunJlNLHU0r/meqVyBuodtd55xmse/Lc0HqGMU07h9S66uwFWmc5J00ue5jqKGWLtc9Uhyzc1dBSSkep3lD0LGCmMdufCVxHtb/hmXSNOXE9q4BNwH+iemL4E+DXgMtSSl+f63pP009THZ7s11JKw1Omn3gFQZKy9AWqVz+3zDSzNl756QwpM9MyPz7DtOPUivittTjWR8TqGRab6R6ie6kW0G+MiBnz6pSbSU/HXcBzqZ4vTrWuu6n+UfLzMyy66gy2qZywcJfgN6ne7Hnb1Ad8RMQzgI9TTX6/ewY3os5ksivO01NKW1NKv51S+vOU0kz9HhfaCNUhwY4NCxYRzcBblmDbknS63kX1Rs73RcSVU2dExCVUh7AtzvL5Su19zQmf3US1SwwnTP9Qbd7UaedRfb7F41PWB9WrpFAdpICIeHrUHrpXayX/HeBC4BO1oRgn17ei9jCkD8wS94l+v7btP4+IK06I71LgroiYvILwwcn3E85fF1NtINIy482pangppX+NiDdR7cJyT0R8iWqSfjnVG38+CvzlPLdxKCIeAj4VEf+TahcZau/fBr6UUvq3+WxjFh+nOrzYrtq2x4BrmP0EKElLKqX0g9qDhz4FfDkivgb8gGpXv1aqxfR/mGUV/0y1EebGiNgHfA94PtV7e75NdRx24NizNd4A/FZEPEB1gIAJqlcon0l1WMepAwBMPmzptojopvqHwH6qI8pAdWjfnwR+FXhFRHyZ6igzV1AdBeZPz2A/7K8V+58GvhYR/0R1yMpLausbpnrv1ZNUrw58iupVinsi4gtUHyS4CfgGZz5imeqcLe4SkFK6nepoAH9DtS/6y6legrwhpfQrM4zgckZqDw95nGpL0OupjhbwJqqt/R8B7ouIz84wVvxC+COqf5SMUj0p/RjV/u0/uwjbkqQ5SykNAD9FtWh9JtXuIuNUc9ZzU0o/mOWzDwO/QvUq428Cb6M6VvsrgO0nLJtq2/kg1T7t11Iddeuh2jr+4ITVf4bqw6GaqT5d9fNUc/nk+iZSSr9G9Qmv36BaOF9B9Wmor0wp3XyG++HzwP9BdZjHyS6b47X/009MjiJT+3+8AXg38CBPXVnoBBZ63HvVgZj+BF5JCy0idgAvoprUv5BSGq31U7yAat/LdwE/A7w9pfT+7CKVJEn1ysJdWmQRcS7V/or/M6U07QbY2jLPoTqiwlBKqX3popMkSXlhVxlp8f2Qal/LV0TEq04cFaHWPeZttR/nPHKNJEla3mxxl5ZARLwMuJPqDaF7qRboR6j2XfxpqiPX7AR+LqVUySRISZJU1yzcpSUSEWuBXwI6qD7au0h1pIA9VEd+GUgpjWUVnyRJqm8W7pIkSVIO2MddkiRJygELd0mSJCkHLNwlSZKkHLBwlyRJknLAwl2SJEnKgadlHcByEhGPAE8HhrOORVLDWQf8KKX0zKwDyQPztaQMzTlfOxzkAoqIx84666xz1q9fn3UokhrM3r17eeKJJ36YUjo361jywHwtKSvzyde2uC+s4fXr17fu2rUr6zgkNZiNGzeye/duW49Pn/laUibmk6/t4y5JkiTlgC3ukpaN0dFR+vr6GBwcpFKpUCwWaW9vp7u7m0KhkHV4kiTNi4W7pGVhaGiInp4eyuXycdO3bt3KLbfcQm9vL21tbRlFJ0nS/Fm4S8q9oaEhOjs7mZiYoHlViTUbNlM4r4XRwyPs37OdcrlMZ2cn27Zts3iXJOWWhbukXBsdHaWnp4eJiQkuuORqLt50E00rVh6b/+wrrufBnbdx4IEd9PT0MDw8bLcZSVIueXOqpFzr6+ujXC7TvKo0rWgHaFqxkos33UTz2aspl8v09/dnFKkk6UT79u1j9+7d7Nu3L+tQcsHCXVKuDQ4OArBmw+ZpRfukphUrWbNhMwADAwNLFpsk6eT27dvH3Xffzd69e7n77rst3k+DhbukXKtUKgAUzmuZdblCseW45SVJ2Tp48OCsP2s6C3dJuVYsFgEYPTwy63KjlZHjlpckZWv16tWz/qzpLNwl5Vp7ezsA+/dsZ2J8bMZlJsbH2L9nOwAdHR1LFpsk6eTWrl3LlVdeyfr167nyyitZu3Zt1iHVPQt3SbnW3d1NqVTi6JEyD+68bVrxPjE+xt4dt3L08YOUSiW6uroyilSSdKK1a9fS2tpq0X6aHA5SUq4VCgV6e3vp7OzkwAM7OPzwfay59FoKxRZGKyPsv/8ujh4p09TURG9vr0NBSpJyy8JdUu61tbWxbdu2Y09O/f49nzlufqlU8smpkqTcs3CXtCy0tbUxPDxMf38/AwMDVCoVisUiHR0ddHV12dIuSco9C3dJy0ahUGDLli1s2bIl61AkSVpw3pwqSZIk5YCFuyRJkpQDFu6SJElSDli4S5IkSTlg4S5JkiTlgIW7JEmSlAMW7pIkSVIOWLhLkiRJOWDhLkmSJOVAwxbuEfGTEfEPEVGJiPsj4iMRUcw6LknS8czXklT1tKwDyEJEvAD4CvAZ4K+B5wNvBn4mIl6cUjqYZXzScjY6OkpfXx+Dg4NUKhWKxSLt7e10d3dTKBSyDk91xnwtSU9pyMId+DDwFymlt05OiIg7gbuA3wfeerIPSpq7oaEhenp6KJfLx03funUrt9xyC729vbS1tWUUneqU+VqSahquq0zt8uorgP8+dXpK6R+ALwA3ZhCWtOwNDQ3R2dlJuVymeVWJC190I8+79mYufNGNNK8qUS6X6ezsZGhoKOtQVSfM15J0vIZrcU8pVSKiJ6W0b4bZ3wJeGRGrvfwqLZzR0VF6enqYmJjggkuu5uJNN9G0YuWx+c++4noe3HkbBx7YQU9PD8PDw3abkflakk7QcC3uACmlT55kVgswDhxZwnCkZa+vr+9YS/uJRTtA04qVXLzpJprPXk25XKa/vz+jSFVvzNdStvbt28fu3bvZt2+mv5+11Bquxf1kIuJpwEuBL6WUnjjFsrtOMmv9ggcmLQODg4MArNmweVrRPqlpxUrWbNjM9+/5DAMDA2zZsmUpQ1SOmK+lpbFv3z7uvvtuAPbu3cuVV17J2rVrM46qsTVki/tJ/DbVFpzfyzoQabmpVCoAFM5rmXW5QrHluOWlkzBfS0vg4MGDs/6spWeLOxARV1AdneANKaWvnmr5lNLGk6xnF9C6wOFJuVcsFgEYPTwy63KjlZHjlpdOZL6Wls7q1avZu3fvcT8rWw3f4h4RFwN3Au9IKf1V1vFIy1F7ezsA+/dsZ2J8bMZlJsbH2L9nOwAdHR1LFpvyw3wtLa21a9dy5ZVXsn79ervJ1ImGLtwjogXYDnw0pfTBrOORlqvu7m5KpRJHj5R5cOdt04r3ifEx9u64laOPH6RUKtHV1ZVRpKpX5mspG2vXrqW1tdWivU40bFeZiDgf+Hvgb4B3ZxuNtLwVCgV6e3vp7OzkwAM7OPzwfay59FoKxRZGKyPsv/8ujh4p09TURG9vr0NB6jjma0mqasjCPSJWAZ8Dvgy8LaWUMg5JWvba2trYtm3bsSenfv+ezxw3v1Qq+eRUTWO+lqSnNGThTrXVZjWwA7g+Ik6c//mU0uNLHpW0zLW1tTE8PEx/fz8DAwNUKhWKxSIdHR10dXXZ0q6ZmK8lqaZRC/dra++fPcn85wKeCKRFUCgU2LJli+O063SZryWppiEL95TStCYbSVL9MV9L0lMaelQZSZIkKS8s3CVJkqQcsHCXJEmScsDCXZIkScoBC3dJkiQpByzcJUmSpBywcJckSZJywMJdkiRJygELd0mSJCkHGvLJqWpso6Oj9PX1MTg4SKVSoVgs0t7eTnd3N4VCIevwJEk15mvpeBbuaihDQ0P09PRQLpePm75161ZuueUWent7aWtryyg6SdIk87U0nYW7GsbQ0BCdnZ1MTEzQvKrEmg2bKZzXwujhEfbv2U65XKazs5Nt27Z5MpCkDJmvpZlZuKshjI6O0tPTw8TEBBdccjUXb7qJphUrj81/9hXX8+DO2zjwwA56enoYHh72MqwkZcB8LZ2cN6eqIfT19VEul2leVZp2EgBoWrGSizfdRPPZqymXy/T392cUqSQ1NvO1dHIW7moIg4ODAKzZsHnaSWBS04qVrNmwGYCBgYEli02S9BTztXRyFu5qCJVKBYDCeS2zLlcothy3vCRpaZmvpZOzcFdDKBaLAIweHpl1udHKyHHLS5KWlvlaOjkLdzWE9vZ2APbv2c7E+NiMy0yMj7F/z3YAOjo6liw2SdJTzNfSyVm4qyF0d3dTKpU4eqTMgztvm3YymBgfY++OWzn6+EFKpRJdXV0ZRSpJjc18LZ2cw0GqIRQKBXp7e+ns7OTAAzs4/PB9rLn0WgrFFkYrI+y//y6OHinT1NREb2+vQ4tJUkbM19LJWbirYbS1tbFt27ZjT+L7/j2fOW5+qVTySXySVAfM19LMLNzVUNra2hgeHqa/v5+BgQEqlQrFYpGOjg66urpsuZGkOmG+lqazcFfDKRQKbNmyhS1btmQdiiRpFuZr6XjenCpJkiTlgIW7JEmSlAMW7pIkSVIOWLhLkiRJOWDhLkmSJOWAhbskSZKUAxbukiRJUg5YuEuSJEk5YOEuSZIk5UDDF+4RsTIiPhcRF2UdiyTp5MzXkhrd07IOIAsREcCFwGXA7wAvyzai/BkdHaWvr4/BwUEqlQrFYpH29na6u7spFApZhydpmTBfz5/5Wlo+GrJwB9YB3wUmgH0Zx5I7Q0ND9PT0UC6Xj5u+detWbrnlFnp7e2lra8soOknLjPl6HszX0vLSqF1l9gOXA88APpRtKPkyNDREZ2cn5XKZ5lUlLnzRjTzv2pu58EU30ryqRLlcprOzk6GhoaxDlbQ8mK/nyHwtLT8N2eKeUhoFvgVQvQqr0zE6OkpPTw8TExNccMnVXLzpJppWrDw2/9lXXM+DO2/jwAM76OnpYXh42MuwkubFfD035mtpeWrUFnfNQV9f37GWmxNPAgBNK1Zy8aabaD57NeVymf7+/owilaTGZr6WlicL9zmIiF0zvYD1Wce2mAYHBwFYs2HztJPApKYVK1mzYTMAAwMDSxabJM3EfG2+lpYTC3edtkqlAkDhvJZZlysUW45bXpK0tMzX0vLUkH3c5yultHGm6bVWnNYlDmfJFItFAEYPj8y63Ghl5LjlJSkr5mvztbSc2OKu09be3g7A/j3bmRgfm3GZifEx9u/ZDkBHR8eSxSZJeor5WlqeLNx12rq7uymVShw9UubBnbdNOxlMjI+xd8etHH38IKVSia6urowilaTGZr6Wlie7yui0FQoFent76ezs5MADOzj88H2sufRaCsUWRisj7L//Lo4eKdPU1ERvb69Di0lSRszX0vJk4a4z0tbWxrZt2449ie/793zmuPmlUskn8UlSHTBfS8tPwxbuEXFD7Z8vrL2/JiL2AwdSSl/MKKxcaGtrY3h4mP7+fgYGBqhUKhSLRTo6Oujq6rLlRtKCMl/PnflaWl4ipZR1DJmIiJP9x7+YUto0x3Xuam1tbd21a9fcA5OkOdi4cSO7d+/efbJRVPLMfC1pOZlPvm7YFveUks/OlqQcMF9LUpWjykiSJEk5YOEuSZIk5YCFuyRJkpQDFu6SJElSDli4S5IkSTlg4S5JkiTlgIW7JEmSlAMW7pIkSVIOWLhLkiRJOWDhLkmSJOXAvAv3iPhPCxGIJGlxma8lKd8WosV9KCLujIjnLcC6JEmLx3wtSTm2EIX7VuA1wL9FxPsi4uwFWKckaeGZryUpx+ZduKeUuoBrgX8H3g48EBGvm+96JUkLy3wtSfm2IDenppS+AFwG3AycDXwiIr4cEVcsxPolSQvDfC1J+bVgo8qklMZTSv838BPAJ4CXAHdHxF9GRGmhtiNJmh/ztSTl04IPB5lSOpBS6qF6Ivgm8MvAtyPiNyLC4SclqU6YryUpXxYtMaeU7gbagQGgCPwp8K2IeOVibVOSdObM15KUD09bqBVFxFnAFcCLp7yeM3URYCOwPSI+A7wlpXRoobYvSTo95mtJyqd5F+4RcRtwJfDCKesL4EngX4CvTnk9HfgAcCPwkoi4JqX00HxjkCSdmvlakvJtIVrcf632fgj4Gk8l/btTSj+aYfnrIqIH+CjwQeDnFyAGSdKpma8lKccWonD/ZeCrKaX7T/cDKaXeiHgtsGkBti9JOj3max1nbGyMjo4OPvKRj3DRRRdlHY6kU1iIBzD1nslJYIph4Lz5bl+SdHrM1wJIKTE8PMydd97J1Vdfzec///msQ5J0mhbs5tQ5+ADV4cckSfXNfL2MDA8P85znPIempibWrl2bdTiSzkBm4/SmlO5PKX0kq+1Lkk6P+Xp5WbNmDffeey+PPvooN998c9bhSDoDWba4S5KkJVYoFLjsssuyDkPSHPhkPEmSJCkHLNwlSZKkHLBwlyRJknLAwl2SJEnKAQt3SZIkKQcs3CVJkqQcaNjhICPi6cAfAa8FVgF3Ab+VUnpoKbY/OjpKX18fg4ODVCoVisUi7e3tdHd3UygUliKEecl7/JLyw3w9P7PFLylnUkoN9wIC+HugArwfeDPwbeBhYM081rurtbU1ncrg4GAqlUoJmPYqlUppcHDwlOvIUt7jl5aj1tbWBOxKdZBjF/Jlvp6fU8X/hje8IQHpoYceyjpUqWHMJ183aot7B7AZ+JmU0naAiPg74AHgXcBbFmvDQ0NDdHZ2MjExQfOqEms2bKZwXgujh0fYv2c75XKZzs5Otm3bRltb22KFMWd5j19S7piv52i2+B+5707K5TIf+9jHAPjc5z7HmjVruOCCC7jqqqsyjlzSyUSqtjw0lIi4HXhxSun5J0z/NHANsDbNYcdExK7W1tbWXbt2zTh/dHSUdevWUS6XueCSq7l40000rVh5bP7E+BgP7ryNAw/soFQqMTw8XFeXYfMev7Scbdy4kd27d+9OKW3MOpaFZL6em1PF/08f6Zjxc1dddRU7d+5coiilxjSffN2oN6duBr45w/R/Bi4ALlyMjfb19VEul2leVZqWRAGaVqzk4k030Xz2asrlMv39/YsRxpzlPX5JuWS+noNTxf+SNw/w4jfdQfPZqwH45Cc/SUrJol2qcw1XuEfESmAd8OAMsyenXXKKdeya6QWsn+1zg4ODAKzZsHlaEp3UtGIlazZsBmBgYGC21S25vMcvKV/M13OX9/glzazhCnfgnNr76AzzflR7X5TrnZVKpbry81pmXa5QbDlu+XqR9/gl5Y75eo7yHr+kmTXizaln194nZpg3Xntvnm0FJ+uTVGvFaT3Z54rFIgCjh0dmDXC0MnLc8vUi7/FLyh3z9RzlPX5JM2vEFvfHa+8z/dEyeT3xyGJsuL29HYD9e7YzMT424zIT42Ps37MdgI6OmW8eykre45eUO+brOcp7/JJm1oiF+2O19/NnmHdu7f3wYmy4u7ubUqnE0SNlHtx527RkOjE+xt4dt3L08YOUSiW6uroWI4w5y3v8knLHfD1HeY9f0swarqtMSunJiDjAzDcmTd7kNPu1xTkqFAr09vbS2dnJgQd2cPjh+1hz6bUUii2MVkbYf/9dHD1Spqmpid7e3roaWgzyH7+kfDFfz13e45d0EnN5alPeX8CngB8AK0+YPgTsmcd6fRJfDuKXlqNl/ORU8/U85D1+aTnyyalnbgB4HfALVE8KRMQlwGuADy32xtva2hgeHqa/v5+BgQEqlQrFYpGOjg66urrqvuUj7/FLyhXz9TzkPX5Jx2vUJ6euAL4IvBD4I6ojFvw61a5DL0wpHZjjemd9Ep8kLZZl/ORU87WkZWU++bohW9xTSuMR8WrgfcBvUR0H+C7g5rmeBCRJC898LUlPacjCHSCl9EPgptpLklSnzNeSVNWIw0FKkiRJuWPhLkmSJOWAhbskSZKUAxbukiRJUg5YuEuSJEk5YOEuSZIk5YCFuyRJkpQDFu6SJElSDjTsA5iWu9HRUfr6+hgcHKRSqVAsFmlvb6e7u5tCoZB1eMua+15z5XenMfl7z477XnOV2XcnpeRrgV7ArtbW1pS1wcHBVCqVEjDtVSqV0uDgYNYhLlvue83VfL87ra2tCdiV6iAX5uFlvpb7XnOVZb6OVE1gWgARsau1tbV1165dmcUwNDREZ2cnExMTNK8qsWbDZgrntTB6eIT9e7Zz9EiZpqYmtm3bRltbW2ZxLkfue83VQnx3Nm7cyO7du3enlDYucfi5ZL5ubO57zdXU707LuSu54fLVXHT+WXzn0BPcce9BRh4bW9R8beG+gLI+EYyOjrJu3TrK5TIXXHI1F2+6iaYVK4/Nnxgf48Gdt3HggR2USiWGh4e9FLhA3Peaq4X67li4nxnzdeNy32uupn53Ol/wDN573TqaVzx1u+jR8Qneeecw2+57dNHytTenLiN9fX2Uy2WaV5WmJSKAphUruXjTTTSfvZpyuUx/f39GkS4/7nvNld+dxuTvPTvue83V5Hen5dyV04p2gOYVTbz3unU885yVi/bdsXBfRgYHBwFYs2HztEQ0qWnFStZs2AzAwMDAksW23LnvNVd+dxqTv/fsuO81V5PfnRsuXz2taJ/UvKKJGy4/H1ic746F+zJSqVQAKJzXMutyhWLLcctr/tz3miu/O43J33t23Peaq8nvwkXnnzXrcpPzF+O7Y+G+jBSLRQBGD4/MutxoZeS45TV/7nvNld+dxuTvPTvue83V5HfhO4eemHW5yfmL8d2xcF9G2tvbAdi/ZzsT42MzLjMxPsb+PdsB6OjoWLLYljv3vebK705j8veeHfe95mryu3PHvQc5Oj4x4zJHxye4495DwOJ8dyzcl5Hu7m5KpRJHj5R5cOdt0xLSxPgYe3fcytHHD1Iqlejq6soo0uXHfa+58rvTmPy9Z8d9r7ma/O6MPDbGO+8cnla8Hx2f4PfuHOaRH44t2nfHJ6cuI4VCgd7eXjo7OznwwA4OP3wfay69lkKxhdHKCPvvv+vY2LS9vb0Ob7WA3PeaK787jcnfe3bc95qrqd+dbfc9yte/e4TrLzv/2DjuW7916Ng47ov23ZnLU5t8+SQ+zcx9r7nyyanma3PG0nLfa658cuoykfUDPaYaHR2lv7+fgYEBKpUKxWKRjo4Ourq6bD1YZO57zdV8vjs+gOnMmK8F7nvNXVb52sJ9AdXTiUBSY7FwPzPma0lZ8cmpkiRJ0jJn4S5JkiTlgIW7JEmSdBq+8Y1vcM0111AsFrn00kt585vfvKRP17VwlyRJkk7hvvvu42Uvexnr1q3jgx/8IK9+9au5/fbb+amf+ikOHjy4JDE4jrskSZJ0Cm9961t505vexIc//OFj06677jquvfZa3vOe9xw3fbHY4i5JkiTNolKp8I//+I+84x3vOG76Nddcwytf+Uo+/elPL0kcFu6SJEnSLIrFIr29vaxdu3bavMsuu4wDBw4sSXcZC3dJkiTpFF7/+tfPOH1kZIQVK1awatWqRY/Bwl2SJEmagyeffJKvfvWrvPzlL+ess85a9O01bOEeEedGxDeyjkOSNDvztaR69cd//MeMjIzwh3/4h0uyvYYaVSYimoCLgJ8C3gU8P6tYRkdH6evrY3BwkEqlQrFYpL29ne7ubgqFQlZhSZqFx+3SMV9Lmo+lOG6/+c1v8p73vIePfexjvPSlL12QdZ5SSqlhXsArgAQ8Aeyv/vcXdP27Wltb06kMDg6mUqmUarEc9yqVSmlwcPCU65C0tOr9uG1tbU3ArlQHuXYhXuZrSXO1FMft3r17U0tLS/rABz5wxp+dT75utK4y/0q11eZ8oC+LAIaGhujs7KRcLtO8qsSFL7qR5117Mxe+6EaaV5Uol8t0dnYyNDSURXiSZuBxmwnztaQzNvW4bTl3JW95xTP5YOdzeMsrnknLuSsX5LgdGRlh8+bNvPGNb+SWW25ZwOhPLVK15aHhRMStwE0ppVjAde5qbW1t3bVr14zzR0dHWbduHeVymQsuuZqLN91E04qVx+ZPjI/x4M7bOPDADkqlEsPDw16GlTKWl+N248aN7N69e3dKaeOSb3yRma8lnY6px23nC57Be69bR/OKp9qoj45P8M47h9l236NzPm4PHTrEVVddxc/+7M/y/ve/n4gzT0vzydeN1uKeqb6+vmMtNyeeBACaVqzk4k030Xz2asrlMv39/RlFKmmSx21j8vcu5c/kcdty7sppRTtA84om3nvdOp55zso5HbdHjhzhNa95DT/90z8956J9vizc5yAids30AtbP9rnBwUEA1mzYPO0kMKlpxUrWbNgMwMDAwMIGLumMedzmm/laahyTx+0Nl6+eVrRPal7RxA2Xnw+c+XH7cz/3cxw8eJCrr76arVu3cscddxz3evzxx+f3HzgNDTWqTNYqlQoAhfNaZl2uUGw5bnlJ2fG4bUz+3qX8mTwOLzp/9vHUJ+ef6XF71113AfDa1752xvkPPfQQZ5999hmt80xZuM/Byfok1VpxWk/2uWKxCMDo4ZFZ1z9aGTlueUnZ8bjNN/O11Dgmj8PvHHpi1uUm55/pcVsP94XmrqtMRPxZRNx7hq/fzTpugPb2dgD279nOxPjYjMtMjI+xf892ADo6OpYsNkkz87idO/O1pKU0edzece9Bjo5PzLjM0fEJ7rj3EJDP49ZRZepslIK9O26l/O2djlIg1Ym8HLeOKnPG6zRfS8vM6Ywq83t3DjMwj1FlFsJ88rVdZZZQoVCgt7eXzs5ODjywg8MP38eaS6+lUGxhtDLC/vvv4uiRMk1NTfT29noSkOqAx21j8vcu5c/U43bbfY/y9e8e4frLzuei88/iO4eeYOu3DjHy2Fi+j9u5PLVpObyAW/FJfJJOU70ft8vtyalTX+ZrSWei3o/b+eTrhmpxj4izgVfXfnxebdoNtZ+/m1L656WIo62tjeHhYfr7+xkYGKBSqVAsFuno6KCrqyuffwFKy5zH7dIyX0uaq+V83DZUH/eIuAh46CSzP5FS+sV5rn/WPpOStFiWWx9387Wk5co+7qcppfQdYOkfcyVJOiPma0maLnfDQUqSJEmNyMJdkiRJygELd0mSJCkHLNwlSZKkHLBwlyRJknLAwl2SJEnKAQt3SZIkKQcs3CVJkqQcsHCXJEmScqChnpwqSfVsdHSUvr4+BgcHqVQqFItF2tvb6e7uplAoZB2eJKkmq3xt4S5JdWBoaIienh7K5fJx07du3cott9xCb28vbW1tGUUnSZqUZb62cJekjA0NDdHZ2cnExATNq0qs2bCZwnktjB4eYf+e7ZTLZTo7O9m2bZvFuyRlaGq+bjl3JTdcvpqLzj+L7xx6gjvuPcjIIudrC3dJytDo6Cg9PT1MTExwwSVXc/Gmm2hasfLY/GdfcT0P7ryNAw/soKenh+HhYbvNSFIGpubrzhc8g/det47mFU/dLvqml63hnXcOs+2+RxctX3tzqiRlqK+vj3K5TPOq0rSiHaBpxUou3nQTzWevplwu09/fn1GkktTYJvN1y7krpxXtAM0rmnjvdet45jkrFy1fW7hLUoYGBwcBWLNh87SifVLTipWs2bAZgIGBgSWLTZL0lMl8fcPlq6cV7ZOaVzRxw+XnA4uTry3cJSlDlUoFgMJ5LbMuVyi2HLe8JGlpTebfi84/a9blJucvRr62cJekDBWLRQBGD4/MutxoZeS45SVJS2sy/37n0BOzLjc5fzHytYW7JGWovb0dgP17tjMxPjbjMhPjY+zfsx2Ajo6OJYtNkvSUyXx9x70HOTo+MeMyR8cnuOPeQ8Di5GsLd0nKUHd3N6VSiaNHyjy487ZpxfvE+Bh7d9zK0ccPUiqV6OrqyihSSWpsk/l65LEx3nnn8LTi/ej4BL935zCP/HBs0fK1w0FKUoYKhQK9vb10dnZy4IEdHH74PtZcei2FYgujlRH2338XR4+UaWpqore316EgJSkjU/P1tvse5evfPcL1l51/bBz3rd86xMhjY4uary3cJSljbW1tbNu27diT+L5/z2eOm18qlXxyqiTVgan5eqRc5tYv7Ttu/mLnawt3SaoDbW1tDA8P09/fz8DAAJVKhWKxSEdHB11dXba0S1KdyDJfW7hLUp0oFAps2bKFLVu2ZB2KJGkWWeXrSCkt6QaXs4h47Kyzzjpn/fr1WYciqcHs3buXJ5544ocppXOzjiUPzNeSsjKffG3hvoAi4hHg6cDwGXxs8qyxd+Ej0im477Pl/l9Y64AfpZSemXUgeWC+zh33fXbc9wtvzvnawj1jEbELIKW0MetYGo37Plvuf+WN39nsuO+z476vL47jLkmSJOWAhbskSZKUAxbukiRJUg5YuEuSJEk5YOEuSZIk5YCjykiSJEk5YIu7JEmSlAMW7pIkSVIOWLhLkiRJOWDhLkmSJOWAhbskSZKUAxbukiRJUg5YuEuSJEk5YOEuSZIk5YCFe4Yi4ukR8eGIeCQijkTEtoh4btZxNYKI+MmI+IeIqETE/RHxkYgoZh1Xo4mIl0bEuyNiU9axSLMxX2fHfF0fzNf1wcI9IxERwDbgvwKfAt4OtAJfjog1GYa27EXEC4CvAMPALcDngV8C7omI1VnG1kgiYiXw/wL/DdiUbTTSyZmvs2O+rg/m6/rxtKwDaGAdwGbgZ1JK2wEi4u+AB4B3AW/JMLbl7sPAX6SU3jo5ISLuBO4Cfh9468k+qAX1RuARYEPWgUinYL7Ojvm6Ppiv64Qt7tlpA3ZNngQAUkp7gX7gtbUWHi2w2uXVVwD/fer0lNI/AF8AbswgrIYTEecA7wa2ZhyKdDrM1xkwX9cH83V9sXDPzmbgmzNM/2fgAuDCpQ2nMaSUKkBPSmnfDLO/BVzg5dcl8XZgjGr3A6nema8zYL6uG+brOmLhnoFaX7F1wIMzzJ6cdsnSRdRYUkqfPMmsFmAcOLKE4TSciHgW1b6qfwo8kXE40qzM19kyX2fLfF1/LNyzcU7tfXSGeT+qvReWKBYBEfE04KXAl1JKJqfF9R6gAnwk4zik02G+rjPm6yVlvq4z3pyajbNr7xMzzBuvvTcvUSyq+m2qLTj2mVxEEfF84A3ATSmlH0XE07OOSToF83X9MV8vAfN1fbLFPRuP195n+sNpZe3dy39LJCKuoDo6wRtSSl/NOp5l7o+Bh4Dbsw5EOk3m6zpivl5S5us6ZIt7Nh6rvZ8/w7xza++HlyiWhhYRFwN3Au9IKf1V1vEsZxFxNfAa4FeAsyLiLGBVbXZzRKwCnkwpzdQlQcqK+bpOmK+Xjvm6ftninoGU0pPAAWD9DLMnb3IaWbqIGlNEtADbgY+mlD6YdTwN4Kra+18CP6y9HqpN+93az3+eQVzSSZmv64P5esmZr+tUpJSyjqEhRcSngGuA56SUxqZMHwKel1LyIQeLKCLOB74I/B3wtuSBsOgi4seAHzthchEYAj4G9AL7U0rfXuLQpFmZr7Nlvl565uv6ZVeZ7AwArwN+geojtImIS6hemvpQhnEte7VLfJ8DvowngSWTUvoe8L2p0yKiVPvn91JKX176qKTTYr7OiPk6G+br+mXhnp2/Ab4C3BYRF1IdseDXgf3An2QZWAP4G2A1sAO4foaHHn4+pfT4tE9JalTm6+yYr6UpLNwzklIaj4hXA+8DfovqOMB3ATenlA5kGtzyd23t/bMnmf9cnhpJQlKDM19nynwtTWEfd0mSJCkHHFVGkiRJygELd0mSJCkHLNwlSZKkHLBwlyRJknLAwl2SJEnKAQt3SZIkKQcs3CVJkqQcsHCXJEmScsDCXZIkScoBC3dJkiQpByzcJUmSpBywcJckSZJywMJdkiRJygELd0mSJCkHLNylDEXEX0VEioh3zjDvJRHxo4g4GBGXZhGfJMlcrfoRKaWsY5AaVkSsB/YAR4DnppQO16b/OPBV4Gzg2pTSV7OLUpIam7la9cIWdylDKaW9wO3AM4DfAoiIC4DP16bd6IlAkrJlrla9sMVdylhEPAv4d+AosBHYCrwYeFNK6S+zjE2SVGWuVj2wxV3KWErpB8CtwHnAvVRPBH/giUCS6oe5WvXAFnepDkREC/B9qn9Mfzyl9IaMQ5IkncBcrazZ4i5lLCIC+BBPHY9PZhiOJGkG5mrVAwt3KXvvB34B+BwwAvxibaQCSVL9MFcrcxbuUoYi4q3ALcDdQBfwPuBpwB9kGZck6SnmatUL+7hLGYmILuCzwIPAS1JKByKiQHXUgmcBV6SU7s0wRElqeOZq1RNb3KUMRMQrgE8BZeBVKaUDACmlUeCPgAD+MLsIJUnmatUbW9ylJRYRrcBXgGbglSmlr58wv5lqS8464OUppS8vfZSS1NjM1apHFu6SJElSDthVRpIkScoBC3dJkiQpByzcJUmSpBywcJckSZJywMJdkiRJygELd0mSJCkHLNwlSZKkHLBwlyRJknLAwl2SJEnKAQt3SZIkKQcs3CVJkqQcsHCXJEmScsDCXZIkScoBC3dJkiQpByzcJUmSpBywcJckSZJywMJdkiRJyoH/Hy1v5/85R1E4AAAAAElFTkSuQmCC\n",
      "text/plain": [
       "<Figure size 750x450 with 2 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "fig, ax = plt.subplots(1, 2)\n",
    "\n",
    "ax[0].set_title(\"original\")\n",
    "clustering.evaluate(\n",
    "    ax=ax[0], original=True,\n",
    "    ax_props=ax_props, plot_props=dot_props\n",
    "    )\n",
    "\n",
    "ax[1].set_title(\"clustered\")\n",
    "clustering.evaluate(\n",
    "    ax=ax[1],\n",
    "    ax_props=ax_props, plot_props=dot_props\n",
    "    )\n",
    "fig.tight_layout()"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "cnnclustering38",
   "language": "python",
   "name": "cnnclustering38"
  },
  "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.8.8"
  },
  "toc": {
   "base_numbering": 1,
   "nav_menu": {},
   "number_sections": true,
   "sideBar": true,
   "skip_h1_title": true,
   "title_cell": "Contents",
   "title_sidebar": "Contents",
   "toc_cell": true,
   "toc_position": {
    "height": "calc(100% - 180px)",
    "left": "10px",
    "top": "150px",
    "width": "164.988px"
   },
   "toc_section_display": true,
   "toc_window_display": true
  },
  "varInspector": {
   "cols": {
    "lenName": 16,
    "lenType": 16,
    "lenVar": 40
   },
   "kernels_config": {
    "python": {
     "delete_cmd_postfix": "",
     "delete_cmd_prefix": "del ",
     "library": "var_list.py",
     "varRefreshCmd": "print(var_dic_list())"
    },
    "r": {
     "delete_cmd_postfix": ") ",
     "delete_cmd_prefix": "rm(",
     "library": "var_list.r",
     "varRefreshCmd": "cat(var_dic_list()) "
    }
   },
   "types_to_exclude": [
    "module",
    "function",
    "builtin_function_or_method",
    "instance",
    "_Feature"
   ],
   "window_display": false
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
