diff --git a/.DS_Store b/.DS_Store
deleted file mode 100644
index 632526b..0000000
Binary files a/.DS_Store and /dev/null differ
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..b6c03e5
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,166 @@
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+wheels/
+share/python-wheels/
+*.egg-info/
+.installed.cfg
+*.egg
+MANIFEST
+
+# PyInstaller
+# Usually these files are written by a python script from a template
+# before PyInstaller builds the exe, so as to inject date/other infos into it.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.nox/
+.coverage
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+*.cover
+*.py,cover
+.hypothesis/
+.pytest_cache/
+cover/
+
+# Translations
+*.mo
+*.pot
+
+# Django stuff:
+*.log
+local_settings.py
+db.sqlite3
+db.sqlite3-journal
+
+# Flask stuff:
+instance/
+.webassets-cache
+
+# Scrapy stuff:
+.scrapy
+
+# Sphinx documentation
+docs/_build/
+
+# PyBuilder
+.pybuilder/
+target/
+
+# Jupyter Notebook
+.ipynb_checkpoints
+
+# IPython
+profile_default/
+ipython_config.py
+
+# pyenv
+# For a library or package, you might want to ignore these files since the code is
+# intended to run in multiple environments; otherwise, check them in:
+# .python-version
+
+# pipenv
+# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
+# However, in case of collaboration, if having platform-specific dependencies or dependencies
+# having no cross-platform support, pipenv may install dependencies that don't work, or not
+# install all needed dependencies.
+#Pipfile.lock
+
+# poetry
+# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
+# This is especially recommended for binary packages to ensure reproducibility, and is more
+# commonly ignored for libraries.
+# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
+#poetry.lock
+
+# pdm
+# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
+#pdm.lock
+# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
+# in version control.
+# https://pdm.fming.dev/latest/usage/project/#working-with-version-control
+.pdm.toml
+.pdm-python
+.pdm-build/
+
+# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
+__pypackages__/
+
+# Celery stuff
+celerybeat-schedule
+celerybeat.pid
+
+# SageMath parsed files
+*.sage.py
+
+# Environments
+.env
+.venv
+env/
+venv/
+ENV/
+env.bak/
+venv.bak/
+
+# Spyder project settings
+.spyderproject
+.spyproject
+
+# Rope project settings
+.ropeproject
+
+# mkdocs documentation
+/site
+
+# mypy
+.mypy_cache/
+.dmypy.json
+dmypy.json
+
+# Pyre type checker
+.pyre/
+
+# pytype static type analyzer
+.pytype/
+
+# Cython debug symbols
+cython_debug/
+
+# PyCharm
+# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
+# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
+# and can be added to the global gitignore or merged into this file. For a more nuclear
+# option (not recommended) you can uncomment the following to ignore the entire idea folder.
+.idea/
+
+
+# OSX
+.DS_Store
diff --git a/notebooks/.ipynb_checkpoints/notebook-1-sorting-examples-checkpoint.ipynb b/notebooks/.ipynb_checkpoints/notebook-1-sorting-examples-checkpoint.ipynb
deleted file mode 100644
index 39070d5..0000000
--- a/notebooks/.ipynb_checkpoints/notebook-1-sorting-examples-checkpoint.ipynb
+++ /dev/null
@@ -1,386 +0,0 @@
-{
- "cells": [
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "8685ea3a",
- "metadata": {
- "slideshow": {
- "slide_type": "slide"
- }
- },
- "outputs": [],
- "source": [
- "import numpy as np\n",
- "import timeit\n",
- "import matplotlib.pyplot as plt"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "048881d0",
- "metadata": {
- "slideshow": {
- "slide_type": "slide"
- }
- },
- "source": [
- "# Example: Find common words"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "2464a282",
- "metadata": {
- "slideshow": {
- "slide_type": "fragment"
- }
- },
- "source": [
- "Problem: given two lists of words, extract all the words that are in common"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "71740eab",
- "metadata": {
- "slideshow": {
- "slide_type": "slide"
- }
- },
- "source": [
- "# Implementation with 2x for-loops"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "f175c775",
- "metadata": {
- "slideshow": {
- "slide_type": "fragment"
- }
- },
- "outputs": [],
- "source": [
- "%%timeit\n",
- "\n",
- "scaling_factor = 1 #10, 100\n",
- "\n",
- "words1 = ['apple', 'orange', 'banana', 'melon', 'peach'] * scaling_factor\n",
- "words2 = ['orange', 'kiwi', 'avocado', 'apple', 'banana'] * scaling_factor\n",
- "\n",
- "common_for = []\n",
- "for w in words1:\n",
- " if w in words2:\n",
- " common_for.append(w) # 612 ns, 12.3 us, 928 us "
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "affab857",
- "metadata": {
- "slideshow": {
- "slide_type": "subslide"
- }
- },
- "outputs": [],
- "source": [
- "input_size = [1, 10, 100]\n",
- "results_for_loop = [(612/10**9)/(612/10**9), (12.4 /10**6)/(612/10**9), (928/10**6)/(612/10**9)] # in seconds\n",
- "\n",
- "x = np.linspace(0,100,100)\n",
- "fit1 = np.polyfit(input_size,results_for_loop,2)\n",
- "eval1 = np.polyval(fit1, x)\n",
- "\n",
- "plt.plot(x,eval1,c = 'orange')\n",
- "plt.scatter(input_size, results_for_loop, c = 'orange', s = 100, label = '2 for loops')\n",
- "\n",
- "plt.xlabel('input size')\n",
- "plt.ylabel('processing time')\n",
- "plt.yticks(results_for_loop, ['T', str(int((12.4 /10**6)/(513/10**9)))+ 'x T', str(int((928/10**6)/(513/10**9))) + 'x T'])\n",
- "plt.legend()"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "2a61bf38",
- "metadata": {
- "slideshow": {
- "slide_type": "skip"
- }
- },
- "outputs": [],
- "source": [
- "print('Data increase 1x, 10x, 100x')\n",
- "print('Time increase 513 ns, 12.4 µs, 928 µs')\n",
- "print('time1, ~ 24x time1, ~ 1800x time1')"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "38e47397",
- "metadata": {
- "slideshow": {
- "slide_type": "-"
- }
- },
- "source": [
- "What is the big-O complexity of this implementation? "
- ]
- },
- {
- "cell_type": "markdown",
- "id": "4118b38d",
- "metadata": {
- "slideshow": {
- "slide_type": "skip"
- }
- },
- "source": [
- "n * n ~ O(n2)"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "31cd0e74",
- "metadata": {
- "slideshow": {
- "slide_type": "slide"
- }
- },
- "source": [
- "# Implementation with sorted lists"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "c13a24f4",
- "metadata": {
- "slideshow": {
- "slide_type": "fragment"
- }
- },
- "outputs": [],
- "source": [
- "%%timeit\n",
- "scaling_factor = 100 #10, 100\n",
- "words1 = ['apple', 'orange', 'banana', 'melon', 'peach'] * scaling_factor\n",
- "words2 = ['orange', 'kiwi', 'avocado', 'apple', 'banana'] *scaling_factor\n",
- "words1 = sorted(words1)\n",
- "words2 = sorted(words2)\n",
- "\n",
- "common_sort_list = []\n",
- "idx2 = 0\n",
- "for w in words1:\n",
- " while idx2 < len(words2) and words2[idx2] < w:\n",
- " idx2 += 1\n",
- " if idx2 >= len(words2):\n",
- " break\n",
- " if words2[idx2] == w:\n",
- " common_sort_list.append(w) #1.94 ns, 17.3 us, 204 us"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "f1e8fed2",
- "metadata": {
- "slideshow": {
- "slide_type": "notes"
- }
- },
- "outputs": [],
- "source": [
- "# 1.9 * 10**6\n",
- "# 17.9 * 10**6\n",
- "# 205 * 10**6"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "8ce798ab",
- "metadata": {
- "slideshow": {
- "slide_type": "subslide"
- }
- },
- "outputs": [],
- "source": [
- "input_size = [1, 10, 100]\n",
- "results_sorted_lists = [(1.9 * 10**6)/(1.9 * 10**6), (17.9 * 10**6)/(1.9 * 10**6), (205 * 10**6)/(1.9 * 10**6)]\n",
- "fit2 = np.polyfit(input_size, results_sorted_lists, 2)\n",
- "eval2 = np.polyval(fit2, x)\n",
- "plt.plot(x,eval1,c = 'orange')\n",
- "plt.plot(x,eval2,c = 'pink')\n",
- "plt.scatter(input_size, results_for_loop, c = 'orange', s = 100, label = '2 for loops')\n",
- "plt.scatter(input_size, results_sorted_lists, c = 'pink', s = 100, label = 'sorted lists')\n",
- "plt.xlabel('input size')\n",
- "plt.ylabel('processing time')\n",
- "plt.yticks(results_for_loop + results_sorted_lists[1:], ['T', str(int((12.4 /10**6)/(513/10**9)))+ 'x T', str(int((928/10**6)/(513/10**9))) + 'x T',\n",
- " str(int((17.9 * 10**6)/(1.9 * 10**6)))+ 'x T', str(int((205 * 10**6)/(1.9 * 10**6))) + 'x T',])\n",
- "plt.legend()"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "1da4c22f",
- "metadata": {
- "slideshow": {
- "slide_type": "-"
- }
- },
- "source": [
- "What is the big-O complexity of this implementation? "
- ]
- },
- {
- "cell_type": "markdown",
- "id": "4b068a1b",
- "metadata": {
- "slideshow": {
- "slide_type": "-"
- }
- },
- "source": [
- "2 * sorting + traversing two lists = 2*n log2 + 2*n ~ O(n * logn)"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "13c96239",
- "metadata": {
- "slideshow": {
- "slide_type": "slide"
- }
- },
- "source": [
- "# Implementation with sets"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "61edb9f3",
- "metadata": {
- "slideshow": {
- "slide_type": "fragment"
- }
- },
- "outputs": [],
- "source": [
- "%%timeit\n",
- "\n",
- "scaling_factor = 1\n",
- "\n",
- "words1 = ['apple', 'orange', 'banana', 'melon', 'peach'] * scaling_factor\n",
- "words2 = ['orange', 'kiwi', 'avocado', 'apple', 'banana'] *scaling_factor\n",
- "\n",
- "words2 = set(words2)\n",
- "\n",
- "common_sets = []\n",
- "for w in words1:\n",
- " if w in words2:\n",
- " common_sets.append(w) # 630 ns, 3.13 us, 28.6 us"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "c90d8e68",
- "metadata": {
- "slideshow": {
- "slide_type": "notes"
- }
- },
- "outputs": [],
- "source": [
- "# 630 * 10**9\n",
- "# 3.13 * 10**6\n",
- "# 28.6 * 10**6"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "236c132d",
- "metadata": {
- "scrolled": true,
- "slideshow": {
- "slide_type": "subslide"
- }
- },
- "outputs": [],
- "source": [
- "results_sets = [(630 * 10**9)/(630 * 10**9), (3.13 * 10**6)/(630 * 10**9), (28.6 * 10**6)/(630 * 10**9)]\n",
- "fit3 = np.polyfit(input_size, results_sets, 2)\n",
- "eval3 = np.polyval(fit3, x)\n",
- "plt.plot(x,eval1,c = 'orange')\n",
- "plt.plot(x,eval2,c = 'pink')\n",
- "plt.plot(x, eval3, c = 'blue')\n",
- "plt.scatter(input_size, results_for_loop, c = 'orange', s = 100, label = '2 for loops')\n",
- "plt.scatter(input_size, results_sorted_lists, c = 'pink', s = 100, label = 'sorted lists')\n",
- "plt.scatter(input_size, results_sets, c = 'blue', s = 100, label = 'sets')\n",
- "plt.xlabel('input size')\n",
- "plt.ylabel('processing time')\n",
- "plt.yticks(results_for_loop + results_sorted_lists[1:], ['T', str(int((12.4 /10**6)/(513/10**9)))+ 'x T', str(int((928/10**6)/(513/10**9))) + 'x T', str(int((17.9 * 10**6)/(1.9 * 10**6)))+ 'x T', str(int((205 * 10**6)/(1.9 * 10**6))) + 'x T'])\n",
- "plt.legend()\n",
- "plt.show()"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "c9780532",
- "metadata": {
- "slideshow": {
- "slide_type": "-"
- }
- },
- "source": [
- "What is the big-O complexity of this implementation? "
- ]
- },
- {
- "cell_type": "markdown",
- "id": "297bcd7d",
- "metadata": {
- "slideshow": {
- "slide_type": "-"
- }
- },
- "source": [
- "transforming one list to set + 1 for loop = 2 * n ~ O(n)\n",
- "\n",
- "It’s the exact same code as for lists, but now looking up an element in sets \u000b",
- "(if w in words2) takes constant time!\n",
- "How could you have known that set lookup is fast? Learning about data structures!"
- ]
- }
- ],
- "metadata": {
- "celltoolbar": "Slideshow",
- "kernelspec": {
- "display_name": "Python 3 (ipykernel)",
- "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.11.3"
- }
- },
- "nbformat": 4,
- "nbformat_minor": 5
-}
diff --git a/notebooks/.ipynb_checkpoints/notebook-2-numpy_SOLUTIONS-checkpoint.ipynb b/notebooks/.ipynb_checkpoints/notebook-2-numpy_SOLUTIONS-checkpoint.ipynb
deleted file mode 100644
index 13ce2b1..0000000
--- a/notebooks/.ipynb_checkpoints/notebook-2-numpy_SOLUTIONS-checkpoint.ipynb
+++ /dev/null
@@ -1,1070 +0,0 @@
-{
- "cells": [
- {
- "cell_type": "markdown",
- "id": "81bfa588",
- "metadata": {
- "slideshow": {
- "slide_type": "slide"
- }
- },
- "source": [
- "# Numpy arrays"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 1,
- "id": "87f078ef",
- "metadata": {
- "slideshow": {
- "slide_type": "fragment"
- }
- },
- "outputs": [],
- "source": [
- "import numpy as np"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "4670f195",
- "metadata": {
- "slideshow": {
- "slide_type": "slide"
- }
- },
- "source": [
- "# Numpy arrays in memory (representation)\n",
- "**reminder**"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 4,
- "id": "f006625e",
- "metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "[[0 1 2]\n",
- " [3 4 5]\n",
- " [6 7 8]]\n"
- ]
- }
- ],
- "source": [
- "X = np.arange(0,9).reshape(3,3)\n",
- "print(X)"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "0dfd22f3",
- "metadata": {
- "slideshow": {
- "slide_type": "fragment"
- }
- },
- "source": [
- "![memory_lists1](images/memory_layout_array3.png)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 5,
- "id": "08bf5b5e",
- "metadata": {
- "slideshow": {
- "slide_type": "fragment"
- }
- },
- "outputs": [
- {
- "data": {
- "text/plain": [
- "array([0, 1, 2, 3, 4, 5, 6, 7, 8])"
- ]
- },
- "execution_count": 5,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "# flatten\n",
- "X.ravel()"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 8,
- "id": "45e1988e",
- "metadata": {
- "slideshow": {
- "slide_type": "fragment"
- }
- },
- "outputs": [
- {
- "data": {
- "text/plain": [
- "array([[0, 3, 6],\n",
- " [1, 4, 7],\n",
- " [2, 5, 8]])"
- ]
- },
- "execution_count": 8,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "# transpose\n",
- "X.T"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 9,
- "id": "0aab72b3",
- "metadata": {
- "slideshow": {
- "slide_type": "fragment"
- }
- },
- "outputs": [
- {
- "data": {
- "text/plain": [
- "array([[0, 2],\n",
- " [6, 8]])"
- ]
- },
- "execution_count": 9,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "# slice\n",
- "X[::2, ::2]"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "d9518a25",
- "metadata": {
- "slideshow": {
- "slide_type": "slide"
- }
- },
- "source": [
- "![numpy_meta](images/numpy_metadata.png)"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "7d938ab8",
- "metadata": {
- "slideshow": {
- "slide_type": "slide"
- }
- },
- "source": [
- "![numpy_magic](images/numpy_views.png)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 12,
- "id": "5c8ad61e",
- "metadata": {
- "slideshow": {
- "slide_type": "-"
- }
- },
- "outputs": [],
- "source": []
- },
- {
- "cell_type": "markdown",
- "id": "cecb3364",
- "metadata": {
- "slideshow": {
- "slide_type": "slide"
- }
- },
- "source": [
- "# Views and Copies: an important distinction!\n",
- "\n",
- "\n",
- "**View**\n",
- "\n",
- "- accessing the array without changing the databuffer \n",
- "- **regular indexing** and **slicing** give views\n",
- "- *in-place* operations can be done in views\n",
- "\n",
- "\n",
- "**Copy**\n",
- "- when a new array is created by duplicating the data buffer as well as the array metadata\n",
- "- **fancy indexing** give always copies\n",
- "- a copy can be forced by method **.copy()**\n",
- "\n",
- "How to know? with ```base```"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 11,
- "id": "a169f158",
- "metadata": {
- "slideshow": {
- "slide_type": "subslide"
- }
- },
- "outputs": [],
- "source": [
- "def is_view(a, x): #checks if the base of a is the same as the base of x\n",
- " return a.base is x"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 12,
- "id": "23f95dca",
- "metadata": {
- "slideshow": {
- "slide_type": "slide"
- }
- },
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "a = [1 2 3 4 5 6]\n"
- ]
- }
- ],
- "source": [
- "a = np.arange(1,7)\n",
- "print('a = ',a)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 13,
- "id": "c3150638",
- "metadata": {
- "lines_to_next_cell": 2,
- "slideshow": {
- "slide_type": "subslide"
- }
- },
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "a_slice = [3 4 5]\n",
- "The base of a_slice is [1 2 3 4 5 6]\n",
- "Is a_slice a view of a? True\n"
- ]
- }
- ],
- "source": [
- "# create slice of a and print its base\n",
- "a_slice = a[2:5]\n",
- "\n",
- "print('a_slice = ', a_slice)\n",
- "print('The base of a_slice is ', a_slice.base)\n",
- "\n",
- "print('Is a_slice a view of a?', is_view(a_slice, a))\n"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 14,
- "id": "19d2ae59",
- "metadata": {
- "slideshow": {
- "slide_type": "subslide"
- }
- },
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "a_copy = [[1 2 3]\n",
- " [4 5 6]]\n",
- "the base of a_copy None\n",
- "a and a_copy have the same base False\n"
- ]
- }
- ],
- "source": [
- "# create a copy of a and print its base\n",
- "\n",
- "a_copy = np.reshape(a, (2,3)).copy()\n",
- "\n",
- "print('a_copy = ', a_copy)\n",
- "print('the base of a_copy ', a_copy.base)\n",
- "print('a and a_copy have the same base ', is_view(a_copy, a))\n"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 15,
- "id": "c2e2e7ab",
- "metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "[[1 2 3]\n",
- " [4 5 6]]\n",
- "True\n",
- "True\n",
- "False\n"
- ]
- }
- ],
- "source": [
- "## ! DON'T understand\n",
- "\n",
- "# create a copy of a and print its base\n",
- "a_2_3 = np.reshape(a, (2,3))\n",
- "\n",
- "b = np.reshape(a_2_3, (2,3))\n",
- "print(b)\n",
- "print(is_view(b, a))\n",
- "print(is_view(a_2_3, a))\n",
- "print(is_view(b, a_2_3)) #??????"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "6446c6a7",
- "metadata": {
- "slideshow": {
- "slide_type": "slide"
- }
- },
- "source": [
- "As a copy is a different array in memory, modifiying it will *not* change the base array"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 17,
- "id": "728a2740",
- "metadata": {
- "slideshow": {
- "slide_type": "fragment"
- }
- },
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "a [1 2 3 4 5 6]\n",
- "a_copy [[ 1 2 3]\n",
- " [ 4 666 6]]\n"
- ]
- }
- ],
- "source": [
- "a = np.arange(1, 7)\n",
- "\n",
- "# create a copy\n",
- "a_copy = np.reshape(a, (2,3)).copy()\n",
- "\n",
- "a_copy[1,1] = 666\n",
- "\n",
- "print('a ', a)\n",
- "print('a_copy ', a_copy)"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "3978394b",
- "metadata": {
- "slideshow": {
- "slide_type": "fragment"
- }
- },
- "source": [
- "# change an element in the copy, print original array\n"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 18,
- "id": "4763cc05",
- "metadata": {
- "slideshow": {
- "slide_type": "fragment"
- }
- },
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "a [ 1 2 3 4 101 6]\n",
- "a_view: [[ 1 2 3]\n",
- " [ 4 101 6]]\n",
- "a_view strides: (24, 8)\n",
- "a_copy [[ 1. 2. 3. ]\n",
- " [ 4. 666.44 6. ]]\n",
- "a_copy strides: (24, 8)\n",
- "a_copy base: None\n"
- ]
- }
- ],
- "source": [
- "a = np.arange(1, 7)\n",
- "\n",
- "# create a copy\n",
- "a_copy = np.reshape(a, (2,3)).astype('float64')\n",
- "a_view = np.reshape(a, (2,3))\n",
- "\n",
- "a_copy[1,1] = 666.44\n",
- "a_view[1,1] = 101.6555 # the data type in the original array (int) stays the same \n",
- "\n",
- "print('a ', a)\n",
- "print('a_view: ', a_view)\n",
- "print('a_view strides: ', a_view.strides)\n",
- "print('a_copy ', a_copy)\n",
- "print('a_copy strides: ', a_view.strides)\n",
- "print('a_copy base: ', a_copy.base)"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "adaf1f45",
- "metadata": {
- "slideshow": {
- "slide_type": "fragment"
- }
- },
- "source": [
- "The same operation with a *view*, however, will carry the change "
- ]
- },
- {
- "cell_type": "markdown",
- "id": "94fc8724",
- "metadata": {
- "slideshow": {
- "slide_type": "fragment"
- }
- },
- "source": [
- "**Take-away**: you **do** need to know if you are using a **view** or a **copy**, particularly when you are operating on the array **in-place**"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "512588d1",
- "metadata": {
- "slideshow": {
- "slide_type": "slide"
- }
- },
- "source": [
- "### 1.2.1 Strides - why some indexing gives copies and others views?\n",
- "\n",
- "- how does numpy arrange data in memory? - When you create an array, numpy allocates certain memory that depends on the type you choose"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 20,
- "id": "f94ffd04",
- "metadata": {
- "slideshow": {
- "slide_type": "fragment"
- }
- },
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "[[0 1 2]\n",
- " [3 4 5]\n",
- " [6 7 8]]\n"
- ]
- }
- ],
- "source": [
- "a = np.arange(9).reshape(3,3)\n",
- "print(a)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 21,
- "id": "2f3e051a",
- "metadata": {
- "slideshow": {
- "slide_type": "fragment"
- }
- },
- "outputs": [
- {
- "data": {
- "text/plain": [
- "8"
- ]
- },
- "execution_count": 21,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "a.itemsize"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "b141572c",
- "metadata": {
- "slideshow": {
- "slide_type": "subslide"
- }
- },
- "source": [
- "In this example the array has 8 bytes allocated per item.\n",
- "\n",
- "Memory is *linear*, that means, the 2-D array will look in memory something like this (blue boxes) \n",
- "\n",
- "![linear_mem](images/memory_linear.png)\n",
- "\n",
- "However, the user 'sees' the array in 2D (green boxes).\n",
- "\n",
- "How does numpy accomplishes this? By defining ```strides```.\n"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 22,
- "id": "f8dffd3a",
- "metadata": {
- "slideshow": {
- "slide_type": "fragment"
- }
- },
- "outputs": [
- {
- "data": {
- "text/plain": [
- "(24, 8)"
- ]
- },
- "execution_count": 22,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "a.strides"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "10883b65",
- "metadata": {
- "slideshow": {
- "slide_type": "subslide"
- }
- },
- "source": [
- "Strides tell you by how many bytes you should move in memory when moving one step in that dimension.\n",
- "\n",
- "![strides](images/strides.png)\n",
- "\n",
- "To go from the first item in the first row to the first item in the second row, you need to move (3*8) 24 bytes. To move from the column-wise, you just need to move 8 bytes."
- ]
- },
- {
- "cell_type": "markdown",
- "id": "7be8064c",
- "metadata": {
- "slideshow": {
- "slide_type": "slide"
- }
- },
- "source": [
- "**Views** are created when you use other strides to read your data. Slicing and regular indexing allows that, as you know how many byte steps you need to take to get the data.\n",
- "\n",
- "**Fancy indexing** does not allow that, because the data you are asking **cannot** be obtained by just changing the strides. Thus, numpy need to make a **copy** of it in memory."
- ]
- },
- {
- "cell_type": "markdown",
- "id": "66a2711b",
- "metadata": {
- "slideshow": {
- "slide_type": "fragment"
- }
- },
- "source": [
- "Now, you can change the strides of an array at will."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 23,
- "id": "541bb33c",
- "metadata": {
- "slideshow": {
- "slide_type": "fragment"
- }
- },
- "outputs": [
- {
- "data": {
- "text/plain": [
- "array([[0, 3, 6],\n",
- " [1, 4, 7],\n",
- " [2, 5, 8]])"
- ]
- },
- "execution_count": 23,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "a.strides=(8,24)\n",
- "a"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "05a3e933",
- "metadata": {
- "slideshow": {
- "slide_type": "fragment"
- }
- },
- "source": [
- " But be careful! Changing the strides to something non-sensical will also **give you non-sense**. And numpy will not complain. "
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 24,
- "id": "8c20fea5",
- "metadata": {
- "slideshow": {
- "slide_type": "fragment"
- }
- },
- "outputs": [],
- "source": [
- "a.strides=(8, 9)"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "e7bccdc1",
- "metadata": {
- "slideshow": {
- "slide_type": "slide"
- }
- },
- "source": [
- "# Exercises on indexing, views/copies\n"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "694ed250",
- "metadata": {
- "slideshow": {
- "slide_type": "slide"
- }
- },
- "source": [
- "### Exercise 1: indexing, dimensionality of the output, view or copy?\n",
- "\n",
- "Look at the following code examples and before running it, try to answer for each case: \\\n",
- "(1) what is the dimensionality of v? \\\n",
- "(2) is v a view or a copy?"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 25,
- "id": "cc625fdd",
- "metadata": {},
- "outputs": [],
- "source": [
- "x = np.arange(0,12).reshape(3,4)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 26,
- "id": "02319316",
- "metadata": {
- "slideshow": {
- "slide_type": "fragment"
- }
- },
- "outputs": [
- {
- "data": {
- "text/plain": [
- "array([[ 0, 1, 2, 3],\n",
- " [ 8, 9, 10, 11]])"
- ]
- },
- "execution_count": 26,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "x[::2, :] #dim, view or copy\n",
- "\n",
- "#is_view(x[::2, :], x.base)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 27,
- "id": "c3336d1e",
- "metadata": {
- "slideshow": {
- "slide_type": "fragment"
- }
- },
- "outputs": [
- {
- "data": {
- "text/plain": [
- "array([4, 5, 6, 7])"
- ]
- },
- "execution_count": 27,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "x[1, :]\n",
- "\n",
- "#is_view(x[1, :], x.base)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 45,
- "id": "1c8449f8",
- "metadata": {
- "slideshow": {
- "slide_type": "fragment"
- }
- },
- "outputs": [
- {
- "data": {
- "text/plain": [
- "False"
- ]
- },
- "execution_count": 45,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "x[1]\n",
- "\n",
- "#is_view(x[1], x.base)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 59,
- "id": "b635ea0e",
- "metadata": {
- "slideshow": {
- "slide_type": "fragment"
- }
- },
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "[[ 0 1 2 3]\n",
- " [ 4 5 6 7]\n",
- " [ 8 9 10 11]]\n"
- ]
- },
- {
- "data": {
- "text/plain": [
- "array([5, 9, 2])"
- ]
- },
- "execution_count": 59,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "print(x)\n",
- "x[[1, 2, 0], [1, 1, 2]]\n",
- "\n",
- "#is_view(x[[1, 2, 0], [1, 1, 2]], x.base)"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "ab7c3609",
- "metadata": {},
- "source": [
- "### Fancy indexing\n",
- "\n",
- "![fancy](images/fancy_indexing_lookup.png)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 61,
- "id": "1371c65a",
- "metadata": {
- "slideshow": {
- "slide_type": "fragment"
- }
- },
- "outputs": [
- {
- "data": {
- "text/plain": [
- "False"
- ]
- },
- "execution_count": 61,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "x[[0, 2], :]\n",
- "\n",
- "#is_view(x[[0, 2], :], x.base)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 62,
- "id": "9db68cf6",
- "metadata": {
- "slideshow": {
- "slide_type": "fragment"
- }
- },
- "outputs": [
- {
- "data": {
- "text/plain": [
- "True"
- ]
- },
- "execution_count": 62,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "x.reshape((6, 2))\n",
- "\n",
- "#is_view(x.reshape((6, 2)), x.base)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "9374de16",
- "metadata": {
- "slideshow": {
- "slide_type": "fragment"
- }
- },
- "outputs": [],
- "source": [
- "x.ravel()\n",
- "\n",
- "#is_view(x.ravel(), x.base)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "4dce1b10",
- "metadata": {
- "slideshow": {
- "slide_type": "fragment"
- }
- },
- "outputs": [],
- "source": [
- "x.T.ravel()\n",
- "\n",
- "#is_view(x.T.ravel(), x.base)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 68,
- "id": "fd4bd129",
- "metadata": {
- "slideshow": {
- "slide_type": "fragment"
- }
- },
- "outputs": [
- {
- "data": {
- "text/plain": [
- "(32, 8)"
- ]
- },
- "execution_count": 68,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "x[(x % 2) == 1]\n",
- "\n",
- "#is_view(x[(x % 2) == 1], x.base)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 82,
- "id": "baf5b337",
- "metadata": {
- "slideshow": {
- "slide_type": "fragment"
- }
- },
- "outputs": [
- {
- "data": {
- "text/plain": [
- "False"
- ]
- },
- "execution_count": 82,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "y = x + 2\n",
- "\n",
- "##### Is this because \n",
- "\n",
- "#is_view(y, x)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 83,
- "id": "a5e79e50",
- "metadata": {
- "slideshow": {
- "slide_type": "fragment"
- }
- },
- "outputs": [
- {
- "data": {
- "text/plain": [
- "False"
- ]
- },
- "execution_count": 83,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "y = np.sort(x, axis=1)\n",
- "\n",
- "#is_view(y, x)"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "08b9c593",
- "metadata": {
- "slideshow": {
- "slide_type": "slide"
- }
- },
- "source": [
- "# Sources + other resources\n",
- "\n",
- "\n",
- "ASPP Bilbao 2022 - Lisa Schwetlick & Aina Frau-Pascual\n",
- "https://github.com/ASPP/2022-bilbao-advanced-numpy\n",
- "\n",
- "\n",
- "Scipy lecture notes, 2022.1\n",
- "- Basic Numpy: http://scipy-lectures.org/intro/numpy/index.html\n",
- "- Advanced Numpy: http://scipy-lectures.org/advanced/advanced_numpy/index.html\n",
- "\n",
- "Numpy chapter in \"Python Data Science Handbook\"\n",
- "https://jakevdp.github.io/PythonDataScienceHandbook/02.00-introduction-to-numpy.html\n",
- "\n",
- "\n",
- "\n",
- "Further resources on strides: \n",
- "- https://scipy-lectures.org/advanced/advanced_numpy/#indexing-scheme-strides\n",
- "- https://ajcr.net/stride-guide-part-1/\n"
- ]
- }
- ],
- "metadata": {
- "celltoolbar": "Slideshow",
- "kernelspec": {
- "display_name": "Python 3 (ipykernel)",
- "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.11.3"
- },
- "rise": {
- "scroll": true
- }
- },
- "nbformat": 4,
- "nbformat_minor": 5
-}
diff --git a/notebooks/.ipynb_checkpoints/numpy_views_and_copies-checkpoint.ipynb b/notebooks/.ipynb_checkpoints/numpy_views_and_copies-checkpoint.ipynb
deleted file mode 100644
index c785239..0000000
--- a/notebooks/.ipynb_checkpoints/numpy_views_and_copies-checkpoint.ipynb
+++ /dev/null
@@ -1,693 +0,0 @@
-{
- "cells": [
- {
- "cell_type": "markdown",
- "id": "20df51b1",
- "metadata": {},
- "source": [
- "# NumPy views and copies\n",
- "\n",
- "- Operations that only require changing the metadata always do so, and return a **view**\n",
- "- Operations that cannot be executed by changing the metadata create a new memory block, and return a **copy**"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 13,
- "id": "4ed67e38",
- "metadata": {},
- "outputs": [],
- "source": [
- "import numpy as np\n",
- "\n",
- "\n",
- "def print_info(a):\n",
- " \"\"\" Print the content of an array, and its metadata. \"\"\"\n",
- " \n",
- " txt = f\"\"\"\n",
- "dtype\\t{a.dtype}\n",
- "ndim\\t{a.ndim}\n",
- "shape\\t{a.shape}\n",
- "strides\\t{a.strides}\n",
- " \"\"\"\n",
- "\n",
- " print(a)\n",
- " print(txt)\n"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 32,
- "id": "53bd92f9",
- "metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "[[ 0 1 2 3]\n",
- " [ 4 5 6 7]\n",
- " [ 8 9 10 11]]\n",
- "\n",
- "dtype\tint64\n",
- "ndim\t2\n",
- "shape\t(3, 4)\n",
- "strides\t(32, 8)\n",
- " \n"
- ]
- }
- ],
- "source": [
- "x = np.arange(12).reshape(3, 4).copy()\n",
- "print_info(x)"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "d2ee43d7",
- "metadata": {},
- "source": [
- "# Views"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "f4838e77",
- "metadata": {},
- "source": [
- "Operations that only require changing the metadata always do so, and return a **view**"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 33,
- "id": "f1b82845",
- "metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "[[ 1 3]\n",
- " [ 9 11]]\n",
- "\n",
- "dtype\tint64\n",
- "ndim\t2\n",
- "shape\t(2, 2)\n",
- "strides\t(64, 16)\n",
- " \n"
- ]
- }
- ],
- "source": [
- "y = x[0::2, 1::2]\n",
- "print_info(y)"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "3199b45b",
- "metadata": {},
- "source": [
- "A view shares the same memory block as the original array. \n",
- "\n",
- "CAREFUL: Modifying the view changes the original array and all an other views of that array as well!"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 34,
- "id": "28ea1c71",
- "metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "[[ 0 1 2 3 4 5 6 7 8 9 10 11]]\n",
- "\n",
- "dtype\tint64\n",
- "ndim\t2\n",
- "shape\t(1, 12)\n",
- "strides\t(96, 8)\n",
- " \n"
- ]
- }
- ],
- "source": [
- "z = x.reshape(1, 12)\n",
- "print_info(z)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 35,
- "id": "46822b5a",
- "metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "[[101 103]\n",
- " [109 111]]\n",
- "\n",
- "dtype\tint64\n",
- "ndim\t2\n",
- "shape\t(2, 2)\n",
- "strides\t(64, 16)\n",
- " \n"
- ]
- }
- ],
- "source": [
- "y += 100\n",
- "print_info(y)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 37,
- "id": "ad9a7950",
- "metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "[[ 0 101 2 103]\n",
- " [ 4 5 6 7]\n",
- " [ 8 109 10 111]]\n",
- "\n",
- "dtype\tint64\n",
- "ndim\t2\n",
- "shape\t(3, 4)\n",
- "strides\t(32, 8)\n",
- " \n",
- "[[ 0 101 2 103 4 5 6 7 8 109 10 111]]\n",
- "\n",
- "dtype\tint64\n",
- "ndim\t2\n",
- "shape\t(1, 12)\n",
- "strides\t(96, 8)\n",
- " \n"
- ]
- }
- ],
- "source": [
- "print_info(x)\n",
- "print_info(z)"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "4fc789c1",
- "metadata": {},
- "source": [
- "Functions that take an array as an input should avoid modifying it in place! \n",
- "\n",
- "Always make a copy or be super extra clear in the docstring."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 45,
- "id": "aa25ac4b",
- "metadata": {},
- "outputs": [],
- "source": [
- "def robust_log(a, cte=1e-10):\n",
- " \"\"\" Returns the log of an array, avoiding troubles when a value is 0.\n",
- " \n",
- " Add a tiny constant to the values of `a` so that they are not 0. \n",
- " `a` is expected to have non-negative values.\n",
- " \"\"\"\n",
- " a[a == 0] += cte\n",
- " return np.log(a)\n",
- " \n"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 57,
- "id": "471d9d6b",
- "metadata": {},
- "outputs": [
- {
- "name": "stderr",
- "output_type": "stream",
- "text": [
- "/tmp/ipykernel_48764/1018405258.py:2: RuntimeWarning: divide by zero encountered in log\n",
- " np.log(a)\n"
- ]
- },
- {
- "data": {
- "text/plain": [
- "array([[-1.2039728 , -4.60517019],\n",
- " [ -inf, 0. ]])"
- ]
- },
- "execution_count": 57,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "a = np.array([[0.3, 0.01], [0, 1]])\n",
- "np.log(a)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 58,
- "id": "6c05d356",
- "metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "[0. 1.]\n",
- "\n",
- "dtype\tfloat64\n",
- "ndim\t1\n",
- "shape\t(2,)\n",
- "strides\t(8,)\n",
- " \n"
- ]
- }
- ],
- "source": [
- "# This is a view of `a`\n",
- "b = a[1, :]\n",
- "print_info(b)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 59,
- "id": "9d96fb61",
- "metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "array([[ -1.2039728 , -4.60517019],\n",
- " [-23.02585093, 0. ]])"
- ]
- },
- "execution_count": 59,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "robust_log(a)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 60,
- "id": "35d0327d",
- "metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "array([[3.e-01, 1.e-02],\n",
- " [1.e-10, 1.e+00]])"
- ]
- },
- "execution_count": 60,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "a"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 61,
- "id": "4a2b95c5",
- "metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "array([1.e-10, 1.e+00])"
- ]
- },
- "execution_count": 61,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "b"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "fa8cf77a",
- "metadata": {},
- "source": [
- "Better to make a copy!"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 62,
- "id": "c5359eac",
- "metadata": {},
- "outputs": [],
- "source": [
- "def robust_log(a, cte=1e-10):\n",
- " \"\"\" Returns the log of an array, avoiding troubles when a value is 0.\n",
- " \n",
- " Add a tiny constant to the values of `a` so that they are not 0. \n",
- " `a` is expected to have non-negative values.\n",
- " \"\"\"\n",
- " a = a.copy()\n",
- " a[a == 0] += cte\n",
- " return np.log(a)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 66,
- "id": "0bf9b2d5",
- "metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "array([[ -1.2039728 , -4.60517019],\n",
- " [-23.02585093, 0. ]])"
- ]
- },
- "execution_count": 66,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "a = np.array([[0.3, 0.01], [0, 1]])\n",
- "b = a[1, :]\n",
- "\n",
- "robust_log(a)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 67,
- "id": "895209ce",
- "metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "array([[0.3 , 0.01],\n",
- " [0. , 1. ]])"
- ]
- },
- "execution_count": 67,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "a"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 68,
- "id": "18004050",
- "metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "array([0., 1.])"
- ]
- },
- "execution_count": 68,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "b"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "d664b462",
- "metadata": {},
- "source": [
- "# Copies\n",
- "\n",
- "Operations that cannot be executed by changing the metadata create a new memory block, and return a **copy**"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 72,
- "id": "8c8f77e1",
- "metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "[[ 0 1 2 3]\n",
- " [ 4 5 6 7]\n",
- " [ 8 9 10 11]]\n",
- "\n",
- "dtype\tint64\n",
- "ndim\t2\n",
- "shape\t(3, 4)\n",
- "strides\t(32, 8)\n",
- " \n"
- ]
- }
- ],
- "source": [
- "x = np.arange(12).reshape(3, 4).copy()\n",
- "print_info(x)"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "716aec53",
- "metadata": {},
- "source": [
- "Choosing row, columns, or individual elements of an array by giving explicitly their indices (a.k.a \"fancy indexing\") it's an operation that in general cannot be executed by changing the metadata alone.\n",
- "\n",
- "Therefore, **fancy indexing always returns a copy**."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 77,
- "id": "40fb1777",
- "metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "[[0 1]\n",
- " [4 5]\n",
- " [8 9]]\n",
- "\n",
- "dtype\tint64\n",
- "ndim\t2\n",
- "shape\t(3, 2)\n",
- "strides\t(8, 24)\n",
- " \n"
- ]
- }
- ],
- "source": [
- "# Get the first and second column\n",
- "y = x[:, [0, 1]]\n",
- "print_info(y)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 79,
- "id": "b8ed81d5",
- "metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "[[2000 2001]\n",
- " [2004 2005]\n",
- " [2008 2009]]\n",
- "\n",
- "dtype\tint64\n",
- "ndim\t2\n",
- "shape\t(3, 2)\n",
- "strides\t(8, 24)\n",
- " \n",
- "[[ 0 1 2 3]\n",
- " [ 4 5 6 7]\n",
- " [ 8 9 10 11]]\n",
- "\n",
- "dtype\tint64\n",
- "ndim\t2\n",
- "shape\t(3, 4)\n",
- "strides\t(32, 8)\n",
- " \n"
- ]
- }
- ],
- "source": [
- "y += 1000\n",
- "print_info(y)\n",
- "# the original array is unchanged => not a view!\n",
- "print_info(x)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 80,
- "id": "6c50e46e",
- "metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "[ 1 0 11]\n",
- "\n",
- "dtype\tint64\n",
- "ndim\t1\n",
- "shape\t(3,)\n",
- "strides\t(8,)\n",
- " \n"
- ]
- }
- ],
- "source": [
- "y = x[[0, 0, 2], [1, 0, 3]]\n",
- "print_info(y)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 81,
- "id": "9d65a5c3",
- "metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "[1001 1000 1011]\n",
- "\n",
- "dtype\tint64\n",
- "ndim\t1\n",
- "shape\t(3,)\n",
- "strides\t(8,)\n",
- " \n",
- "[[ 0 1 2 3]\n",
- " [ 4 5 6 7]\n",
- " [ 8 9 10 11]]\n",
- "\n",
- "dtype\tint64\n",
- "ndim\t2\n",
- "shape\t(3, 4)\n",
- "strides\t(32, 8)\n",
- " \n"
- ]
- }
- ],
- "source": [
- "y += 1000\n",
- "print_info(y)\n",
- "# the original array is unchanged => not a view!\n",
- "print_info(x)"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "5e76ea7a",
- "metadata": {},
- "source": [
- "Any operation that computes new values also returns a copy."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 82,
- "id": "b8a3d44c",
- "metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "[[ 0. 7.1 14.2 21.3]\n",
- " [28.4 35.5 42.6 49.7]\n",
- " [56.8 63.9 71. 78.1]]\n",
- "\n",
- "dtype\tfloat64\n",
- "ndim\t2\n",
- "shape\t(3, 4)\n",
- "strides\t(32, 8)\n",
- " \n"
- ]
- }
- ],
- "source": [
- "y = x * 7.1\n",
- "print_info(y)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "9e50edfd",
- "metadata": {},
- "outputs": [],
- "source": []
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "022e7b98",
- "metadata": {},
- "outputs": [],
- "source": []
- }
- ],
- "metadata": {
- "kernelspec": {
- "display_name": "Python 3 (ipykernel)",
- "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.11.3"
- }
- },
- "nbformat": 4,
- "nbformat_minor": 5
-}
diff --git a/notebooks/.ipynb_checkpoints/when_copying_is_convenient-checkpoint.ipynb b/notebooks/.ipynb_checkpoints/when_copying_is_convenient-checkpoint.ipynb
deleted file mode 100644
index 363fcab..0000000
--- a/notebooks/.ipynb_checkpoints/when_copying_is_convenient-checkpoint.ipynb
+++ /dev/null
@@ -1,6 +0,0 @@
-{
- "cells": [],
- "metadata": {},
- "nbformat": 4,
- "nbformat_minor": 5
-}
diff --git a/notebooks/.ipynb_checkpoints/which_data_structure_intro-checkpoint.ipynb b/notebooks/.ipynb_checkpoints/which_data_structure_intro-checkpoint.ipynb
deleted file mode 100644
index 38856a6..0000000
--- a/notebooks/.ipynb_checkpoints/which_data_structure_intro-checkpoint.ipynb
+++ /dev/null
@@ -1,103 +0,0 @@
-{
- "cells": [
- {
- "cell_type": "code",
- "execution_count": 1,
- "id": "3ae332a0",
- "metadata": {},
- "outputs": [],
- "source": [
- "import numpy as np"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 5,
- "id": "aa7bbab6",
- "metadata": {
- "scrolled": false
- },
- "outputs": [],
- "source": [
- "sound_data = np.random.rand(100)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 6,
- "id": "626eafc7",
- "metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "array([0.66709183, 0.55973494, 0.95416669, 0.60810949, 0.05188879,\n",
- " 0.58619063, 0.25555136, 0.72451477, 0.2646681 , 0.08694215,\n",
- " 0.75592186, 0.67261696, 0.62847452, 0.06232598, 0.20549438,\n",
- " 0.11718457, 0.25184725, 0.48625729, 0.8103058 , 0.18100915,\n",
- " 0.81113341, 0.62055231, 0.9046905 , 0.56664205, 0.73235338,\n",
- " 0.74382869, 0.64856368, 0.80644398, 0.46199345, 0.78516632,\n",
- " 0.91298397, 0.48290914, 0.20847714, 0.99162659, 0.26374781,\n",
- " 0.3602381 , 0.07173351, 0.8584085 , 0.32248766, 0.39167573,\n",
- " 0.67944923, 0.00930429, 0.21714217, 0.58810089, 0.17668711,\n",
- " 0.57444803, 0.25760187, 0.43785728, 0.39119371, 0.68268063,\n",
- " 0.95954499, 0.45934239, 0.03616905, 0.23896063, 0.61872801,\n",
- " 0.76332531, 0.96272817, 0.57169277, 0.50225193, 0.01361629,\n",
- " 0.15357459, 0.8057233 , 0.0642748 , 0.95013941, 0.38712684,\n",
- " 0.97231498, 0.20261775, 0.74184693, 0.26629893, 0.84672705,\n",
- " 0.67662718, 0.96055977, 0.64942314, 0.66487937, 0.86867536,\n",
- " 0.40815661, 0.1139344 , 0.95638066, 0.87436447, 0.18407227,\n",
- " 0.64457074, 0.19233097, 0.24012179, 0.90399279, 0.39093908,\n",
- " 0.26389161, 0.97537645, 0.14209784, 0.75261696, 0.10078122,\n",
- " 0.87468408, 0.77990102, 0.92983283, 0.45841805, 0.61470669,\n",
- " 0.87939755, 0.09266009, 0.41177209, 0.46973971, 0.43152144])"
- ]
- },
- "execution_count": 6,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "sound_data"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "ef55bee9",
- "metadata": {},
- "outputs": [],
- "source": [
- "synonyms = {\n",
- " 'hot': ['blazing', 'boiling', 'heated'],\n",
- " 'airplane': ['aircraft', 'airliner', \n",
- " 'cab', 'jet', 'plane'],\n",
- " 'beach': ['coast', 'shore', 'waterfront'],\n",
- " # ...\n",
- "}"
- ]
- }
- ],
- "metadata": {
- "kernelspec": {
- "display_name": "Python 3 (ipykernel)",
- "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.11.3"
- }
- },
- "nbformat": 4,
- "nbformat_minor": 5
-}
diff --git a/notebooks/010_data_structures/.ipynb_checkpoints/big_O_example_with_plots-checkpoint.ipynb b/notebooks/010_data_structures/.ipynb_checkpoints/big_O_example_with_plots-checkpoint.ipynb
deleted file mode 100644
index 8e9ac97..0000000
--- a/notebooks/010_data_structures/.ipynb_checkpoints/big_O_example_with_plots-checkpoint.ipynb
+++ /dev/null
@@ -1,446 +0,0 @@
-{
- "cells": [
- {
- "cell_type": "code",
- "execution_count": 3,
- "id": "8685ea3a",
- "metadata": {
- "slideshow": {
- "slide_type": "slide"
- }
- },
- "outputs": [],
- "source": [
- "%matplotlib inline\n",
- "\n",
- "import matplotlib.pyplot as plt\n",
- "import numpy as np\n",
- "import timeit"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 164,
- "id": "cd404fc3",
- "metadata": {},
- "outputs": [],
- "source": [
- "def plot_style(figsize=(12, 6), labelsize=20, titlesize=24, ticklabelsize=14, **kwargs):\n",
- " basic_style = {\n",
- " 'figure.figsize': figsize,\n",
- " 'axes.labelsize': labelsize,\n",
- " 'axes.titlesize': titlesize,\n",
- " 'xtick.labelsize': ticklabelsize,\n",
- " 'ytick.labelsize': ticklabelsize,\n",
- " 'axes.spines.top': False,\n",
- " 'axes.spines.right': False,\n",
- " 'axes.spines.left': True,\n",
- " 'axes.grid': True,\n",
- " 'axes.grid.axis': 'both',\n",
- " }\n",
- " basic_style.update(kwargs)\n",
- " return plt.rc_context(rc=basic_style)\n",
- "\n",
- "\n",
- "def plot_time_increase(multipliers, timing, ax=None, color='red', ls='-'): \n",
- " if ax is None:\n",
- " ax = plt.gca()\n",
- " ax.plot(multipliers, timing, lw=3, ls=ls, color=color)\n",
- " ax.set_xticks(list(range(1, multipliers[-1] + 2, 2)))\n",
- " ax.set_xlabel('N')\n",
- " ax.set_ylabel('Time (s)')\n",
- " ax.set_xlim(0, multipliers[-1] + 1.1)\n",
- " ax.set_ylim(0, 0.06)\n",
- "\n",
- "\n",
- "def measure_time_increase(func, words1, words2, max_mult=20, z=1):\n",
- " timing = []\n",
- " multipliers = list(range(1, max_mult + 1))\n",
- " for mult in multipliers:\n",
- " mult_words1 = words1 * mult\n",
- " mult_words2 = words2 * mult\n",
- " t = timeit.repeat(\n",
- " 'func(words1, words2)', \n",
- " globals={'func': func, 'words1': mult_words1, 'words2': mult_words2}, \n",
- " repeat=3,\n",
- " number=3000\n",
- " )\n",
- " timing.append(min(t) * z)\n",
- " return multipliers, timing"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "048881d0",
- "metadata": {
- "slideshow": {
- "slide_type": "slide"
- }
- },
- "source": [
- "# Example: Find common words"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "2464a282",
- "metadata": {
- "slideshow": {
- "slide_type": "fragment"
- }
- },
- "source": [
- "Problem: given two lists of words, extract all the words that are in common"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 165,
- "id": "b8d7a1eb",
- "metadata": {},
- "outputs": [],
- "source": [
- "results = {}"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 168,
- "id": "01d8c3d7",
- "metadata": {
- "scrolled": false
- },
- "outputs": [
- {
- "data": {
- "image/png": "iVBORw0KGgoAAAANSUhEUgAAAyIAAAIuCAYAAABD+LyRAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/bCgiHAAAACXBIWXMAAA9hAAAPYQGoP6dpAACCyklEQVR4nO3dd3xUVf7/8fckmVQSILRACCX0GghVIkUELCBFRNFVVMC6igqCAoooKigu4ro/C4gdUEFpyipdadICgdB7r4EQSspk5v7+4JtZhiRkUiaTSV7PxyOPNefee+5nZpnJvOfec47JMAxDAAAAAFCIvNxdAAAAAICShyACAAAAoNARRAAAAAAUOoIIAAAAgEJHEAEAAABQ6AgiAAAAAAodQQQAAABAoSOIAAAAACh0BBEAAAAAhY4gAgAAAKDQFYsgsnDhQnXp0kWhoaEKCgpSdHS0Pv74Y9lstjz1t3btWvXq1UsVKlRQQECAGjZsqHHjxiklJSXHYxcvXqy+ffuqSpUq8vPzU1hYmDp16qSJEyfmqRYAAACgODIZhmG4u4j8mDBhgkaOHClJioyMVKlSpRQfHy+bzaaePXtqzpw58vJyPm9Nnz5djz76qKxWq8LDw1WxYkXFx8fLYrGoVatWWrFihQIDAzMdZxiGnn32WX322WeSpKpVq6py5co6e/asjh07ptKlS+vcuXMF86ABAAAAD+fRV0TWrl2rUaNGycvLSzNmzND+/fsVFxen2NhYVapUSfPnz9ekSZOc7u/QoUMaNGiQrFar3n//fR09elSxsbHau3ev6tWrpw0bNmjEiBFZHjt69Gh99tlnaty4sdavX6+jR49q/fr1OnjwoBISEvTVV18V1MMGAAAAPJ5HXxHp3r27Fi5cqCeffFKff/65w7YZM2boH//4h8qVK6eTJ0/KbDbn2N8///lPffLJJ+rWrZv++OMPh21r1qxRTEyMzGazjh49qkqVKtm3xcfHq1mzZgoNDVV8fLwqVqxYMA8QAAAAKKY89opIUlKSlixZIkkaNGhQpu39+vVTSEiIEhIStHz58hz7MwxDc+bMyba/du3aqX79+rJYLJo3b57Dtv/85z+yWq164YUXCCEAAACAEzw2iGzevFlpaWny9/dXdHR0pu1ms1mtWrWSJK1bty7H/o4cOaKTJ09KkmJiYrLcJ6P9xv4WLFggSerRo4diY2P1z3/+U127dlWvXr307rvv6syZM84/MAAAAKAE8NggsnfvXklStWrV5OPjk+U+kZGRDvs605+fn5+qVKnidH+nTp3SiRMnZDKZtHz5crVu3VqffPKJlixZovnz52v06NGqU6eO/eoNAAAAAA8OIhcuXJAklS1bNtt9MrZl7OtMf2XKlJHJZHK6v4yrKCaTScOGDVPr1q0VGxur1NRUbd++XV27dlVSUpL69u2ro0eP3rSG1NRUJSUl2X8uXryos2fPyoOH8QAAAABZyvpSggfIWNPD19c32338/PwkScnJyS7r78qVK5Ikm82mkJAQ/fbbb/bA0rBhQ82bN0+1a9fWiRMnNHnyZP3rX//Ktv/x48frzTffzNQ+Y8aMLKcMBgAA2evVq5e7SwBwEx4bRPz9/SVJaWlp2e6TmpoqSQoICHBZfxnHSdKAAQMyXaEJCAjQ008/rTFjxuj333+/aRAZOXKkhg4dav89KSlJERER6tatm0JCQnJ8DM6yWCxavHixunbt6tRsYu5Gva5Fva5Fva5Fva5FvQBcyWODiDO3XTlz+9aN/SUmJsowjCxvz8qqv+v/u379+ln23aBBA0nX1im5GT8/P/tVl+uZzWaXvKG6ql9XoV7Xol7Xol7Xol7Xol4AruCxY0Tq1Kkj6dpsV+np6Vnuc+DAAYd9nekvNTVVJ06ccLq/GjVq2MNDViHi+nar1ZpjHQAAAEBJ4LFBpHnz5jKbzUpJSVFsbGym7RaLRRs2bJAktWnTJsf+qlWrprCwMEnS6tWrs9wno/36/ry9ve3TBGcElRtltIeHh+dYBwAAAFASeGwQCQkJUZcuXSRJ06ZNy7R91qxZSkpKUrly5dSpU6cc+zOZTOrTp0+2/a1Zs0a7du2S2WxWz549Hbbdf//9kqSZM2fKYrFkOvabb76RJHXu3DnHOgAAAICSwGODiCSNHj1aJpNJX3zxhWbOnGlvj4uLsw/6HjFihMNMWJMnT1aNGjXUv3//TP0NHz5cvr6+WrRokSZOnGifNvfw4cMaOHCgJGnw4MH2KycZBg8erIiICB06dEgvvPCCfcC71WrV6NGjtXnzZvn6+uqll14q2CcAAAAA8FAeHURiYmI0btw42Ww2PfTQQ6pVq5aioqIUHR2t06dPq3v37ho2bJjDMYmJiTp8+LBOnTqVqb+aNWtq6tSp8vLy0ogRIxQREaHo6GjVqVNHu3fvVosWLTRx4sRMxwUEBOiXX35RSEiIPv30U4WFhal169aqXLmy3n33XXl7e2vKlClq2LChy54LAAAAwJN4dBCRrl0VWbBggTp37qyEhATt27dPTZo00eTJkzVv3jx5e3vnqr8BAwZo5cqV6tGjh5KTk7Vjxw5FRkZq7NixWrVqlYKCgrI8rmXLltq6dasGDx6soKAgbdmyRZJ07733as2aNXr00Ufz+1ABAACAYsNjp++9Xo8ePdSjRw+n9h07dqzGjh17033atWunBQsW5LqO6tWra+rUqbk+DgAAAChpPP6KCAAAAADPQxABAAAAUOgIIgAAAAAKHUEEAAAAQKEjiAAAAAAodAQRAAAAAIWOIAIAAACg0BFEAAAAABQ6gggAAACAQkcQAQAAAFDoCCIAAAAACh1BBAAAAEChI4gAAAAAKHQEEQAAAACFjiACAAAAoNARRAAAAAAUOoIIAAAAgEJHEAEAAABQ6AgiAAAAAAodQQQAAABAoSOIAAAAACh0BBEAAAAAhY4gAgAAAKDQEUQAAAAAFDqCCAAAAIBCRxABAAAAUOgIIgAAAAAKHUEEAAAAQKEjiAAAAAAodAQRAAAAAIWOIAIAAAqVYRjuLgFAEUAQAQAAheqp7zZp3K87lHg1zd2lAHAjH3cXAAAASo5Ve89p0Y7TkqTZm47p+c61NeCWGvL14btRoKThVQ8AAAqF1WbonYU77b9fTLbosz8PyGK1ubEqAO5CEAEAAIVizubj2nkyyaFtWLe6CvLjBg2gJCKIAAAAl0tOs+qDP3Y7tNWrFKz7W0a4qSIA7kYQAQAALjdt1QGdSkpxaBt5d315e5ncVBEAdyOIAAAAlzpzKUWfrtjv0Na+Tnl1rFvBTRUBKAoIIgAAwKUmL9mrK2lW++8mkzTyrgYymbgaApRkBBEAAOAye09f0g/rjzi03RddVQ2rhLipIgBFBUEEAAC4zPj/7pLtuoXU/c1eGtatnvsKAlBkEEQAAIBLrN53Tst2nXFoe7J9pMJK+7upIgBFCUEEAAAUOJvN0Du/7XRoK1/KT092rOWmigAUNQQRAABQ4OZsPq4dNyxeOLRrXZVi8UIA/4cgAgAAClRymlUfLHJcvLBOxVK6v2VVN1UEoCgiiAAAgAL15eqDOnnRcfHCUd0byMebjx0A/od3BAAAUGDOXkrVJ8v3ObTdWru8OrF4IYAbEEQAAECBmbxkT+bFC++uz+KFADIhiAAAgAKx9/Ql/bDhqENb3+iqalSltJsqAlCUEUQAAECBmPDfXbJet3rhtcUL67qxIgBFGUEEAADk25p957T0hsULn2gfqcqlA9xUEYCijiACAADyxWYz9M7CGxcv9NVTLF4I4CYIIgAAIF/mbjmu7SccFy98icULAeSAIAIAAPIsxWLVxD8cFy+sXbGUHmgZ4aaKAHgKgggAAMizaauyWLzw7vosXgggR7xLAACAPDl3OVWfrtjv0BZTu5xuq1fRTRUB8CQEEQAAkCeTl+zR5dR0++8mkzTq7gYsXgjAKQQRAACQa/vOXNLM9Y6LF97bnMULATiPIAIAAHLtxsUL/Xy89PIdLF4IwHnFIogsXLhQXbp0UWhoqIKCghQdHa2PP/5YNpstT/2tXbtWvXr1UoUKFRQQEKCGDRtq3LhxSklJyXL/r7/+WiaT6aY/v//+e34eIgAARcaa/ee0ZCeLFwLIH4+f4HvChAkaOXKkJCkyMlKlSpVSXFychgwZoiVLlmjOnDny8nI+b02fPl2PPvqorFarwsPDFRERofj4eI0ZM0YLFizQihUrFBgYmOWxFStWVJ06dbLcVrZs2dw/OAAAihibzdC7WSxe+HQnFi8EkDsefUVk7dq1GjVqlLy8vDRjxgzt379fcXFxio2NVaVKlTR//nxNmjTJ6f4OHTqkQYMGyWq16v3339fRo0cVGxurvXv3ql69etqwYYNGjBiR7fF33XWXVq1aleVPmzZtCuIhAwDgVvPijiv+uOPihS92YfFCALnn0UHk7bfflmEYGjx4sB588EF7e1RUlD2ATJgwQRaLxan+Jk6cqNTUVHXr1k3Dhw+3z/pRvXp1ffnll5KkKVOm6PTp0wX8SAAAKPpSLFZN/N1x8cJaFYLUvxWLFwLIPY8NIklJSVqyZIkkadCgQZm29+vXTyEhIUpISNDy5ctz7M8wDM2ZMyfb/tq1a6f69evLYrFo3rx5+aweAADPM23VQZ3ItHhhAxYvBJAnHvvOsXnzZqWlpcnf31/R0dGZtpvNZrVq1UqStG7duhz7O3LkiE6ePClJiomJyXKfjPbs+ouLi9NDDz2kzp07q3fv3nrzzTe1f//+LPcFAMCTZLV44S2R5dS5PosXAsgbjw0ie/fulSRVq1ZNPj5Z35caGRnpsK8z/fn5+alKlSp56m/Lli2aOXOmli9frnnz5mns2LGqV6+e3nnnnRzPDwBAUfbRkr2ZFi8c3Z3FCwHknceOLLtw4YKkm89GlbEtY19n+itTpky2b6rZ9VemTBk9//zz6t+/v2rXrq3SpUtr586dmjRpkr777ju99tprKl26tJ577rmb1pCamqrU1FT770lJ1wYDWiwWp8e5OCOjr4Ls05Wo17Wo17Wo17Wo17Uy6tx9MlEz1h9x2NY7qrLqVQwsUo/lxufXbDa7sxwAOfDYIJKxpoevr2+2+/j5+UmSkpOTXdpf79691bt3b4e2Zs2a6dtvv1W5cuU0efJkvfbaa3r00UcVHBycbf/jx4/Xm2++mal90aJF2U4ZnB+LFy8u8D5diXpdi3pdi3pdi3pd69WZf8tq+99NFGaToSivo1q48OhNjnKfjOe3V69ebq4EwM14bBDx9/eXJKWlpWW7T8bVhYCAnBdYKuj+Mrz55pv69NNPdfHiRS1btuymb4ojR47U0KFD7b8nJSUpIiJC3bp1U0hIiNPnzInFYtHixYvVtWtXj/i2iHpdi3pdi3pdi3pdy2Kx6JPZSxR/wfFO7sHtI/WPrlmvm+VOnvb8AiWdxwYRZ267cub2rRv7S0xMlGEYWd6elZv+MoSEhKhRo0aKjY3Vvn37brqvn5+f/arL9cxms0veUF3Vr6tQr2tRr2tRr2tRr2vYbIbmHXYMIeWCfPVs5zpFun5PeX6Bks5jB6tnrGB+5MgRpaenZ7nPgQMHHPZ1pr/U1FSdOHEi3/1dL+PNMLs6AQAoihZsO6WjVxy/mHuxa10F+/MhH0D+eWwQad68ucxms1JSUhQbG5tpu8Vi0YYNGyTJqVXNq1WrprCwMEnS6tWrs9wnoz03q6RbrVbt3n1t8aeqVas6fRwAAO6UYrFq0mLHWSJZvBBAQfLYIBISEqIuXbpIkqZNm5Zp+6xZs5SUlKRy5cqpU6dOOfZnMpnUp0+fbPtbs2aNdu3aJbPZrJ49ezpd57Rp05SYmChvb2+n6gAAoCjIavHCkXc1kJnFCwEUEI9+Nxk9erRMJpO++OILzZw5094eFxdnH/Q9YsQIh5mwJk+erBo1aqh///6Z+hs+fLh8fX21aNEiTZw4UYZhSJIOHz6sgQMHSpIGDx5sv3IiXRtQ/uCDD2r9+vUOfVmtVk2dOlUvvPCCpGurtYeHhxfQIwcAwHWOnr+qfy91vBrSNjJUtzdg8UIABcejg0hMTIzGjRsnm82mhx56SLVq1VJUVJSio6N1+vRpde/eXcOGDXM4JjExUYcPH9apU6cy9VezZk1NnTpVXl5eGjFihCIiIhQdHa06depo9+7datGihSZOnOhwjM1m0w8//KA2bdqobNmyio6OVuvWrVW+fHk9+eSTSklJ0V133aWPPvrIpc8FAAAFwTAMjZkXr9R0m73NZJJe696QxQsBFCiPDiLStasiCxYsUOfOnZWQkKB9+/apSZMmmjx5subNmydvb+9c9TdgwACtXLlSPXr0UHJysnbs2KHIyEiNHTtWq1atUlBQkMP+QUFBev/999W7d2+VL19e+/fv15YtW+Tv76/u3bvrxx9/1G+//WafHhgAgKLsj+2ntHz3WYe2h1tHqHF4aTdVBKC48tjpe6/Xo0cP9ejRw6l9x44dq7Fjx950n3bt2mnBggVO9Wc2mzV8+HCn9gUAoCi7nJqusfN3OLSFmA291KW2myoCUJx5/BURAABQMD5cvEenkhwHqPepYWO6XgAuQRABAACKP35RX60+6NAWU6ucmpcz3FQRgOKOIAIAQAlntRkaPTdetusyh6+Pl8beU1+MTwfgKgQRAABKuJnrjyjuaKJD2z871VaNckFZHwAABYAgAgBACXb2Uqre+32XQ1tk+SA93SnSTRUBKCkIIgAAlGDv/LZDl1LSHdrG9W4sP5/cTX8PALlFEAEAoIRas++c5m454dDWu1kVxdQu76aKAJQkBBEAAEqg1HSrXpsb79AW7O+j0d0buqkiACUNQQQAgBLo8z8P6MC5Kw5tr9xZXxWC/dxUEYCShiACAEAJc+jcFf1n+T6HtmYRZfRQ62puqghASUQQAQCgBDEMQ6/Pi1daus3e5mWS3u7dWF5eLBoCoPAQRAAAKEF+3XpSK/eec2h7rF1NNQ4v7aaKAJRUBBEAAEqIpBSL3vp1h0NbWIi/hnar66aKAJRkBBEAAEqIf/2xW2cvpTq0vXFPQ5Xy83FTRQBKMoIIAAAlwNZjifr278MObbfVq6A7G4e5qSIAJR1BBACAYs5qMzR6TrwM439tfj5eeqtXY5lMDFAH4B4EEQAAirnv/z6sbccvOrQNub2OIkID3VQRABBEAAAo1k4npWjiH7sd2mpXLKUn2ke6qSIAuIYgAgBAMTbu1x26nJru0PZO78by9eEjAAD34l0IAIBi6s89Z/Xr1pMObX2jq6pNZDk3VQQA/0MQAQCgGEqxWDVmXrxDW+kAs0bdXd9NFQGAI4IIAADF0CfL9+lwwlWHtpF31Ve5Un5uqggAHBFEAAAoZvaduaxP/9zv0Naielnd3zLCTRUBQGYEEQAAihHDMPT63HhZrP9bNMTby6R3+jSWlxdrhgAoOggiAAAUI/O2nNDaAwkObYNvran6YSFuqggAskYQAQCgmLh41aK3f9vh0BZeJkAvdKnjpooAIHsEEQAAion3/9ilc5fTHNrG9mykQF8fN1UEANkjiAAAUAzEHrmgGeuPOLR1aVBJXRtWclNFAHBzBBEAADxcutWm0XPiZfxvfLoCzN4a27Oh+4oCgBwQRAAA8HBfrzmknSeTHNpe7FJHVcsGuqkiAMgZQQQAAA92IjFZkxbvcWirVylYA2+t6aaKAMA5BBEAADzYmwu262qa1aHtnT6NZfbmTzyAoo13KQAAPNTv8af0x/bTDm39W0WoZY1QN1UEAM4jiAAA4IEupVj0xvx4h7bQIF+9cmd9N1UEALlDEAEAwANN/GO3TielOrSN6dFQZYN83VQRAOQOQQQAAA+z6fAFfff3YYe29nXKq1ezKm6qCAByjyACAIAHsVhtGvXLNoc1Q/zNXnqndxOZTCb3FQYAuUQQAQDAg0z564B2n77k0PZSl7qqVo41QwB4FoIIAAAe4uC5K/po6V6HtoaVQzSINUMAeCCCCAAAHsAwDI2es01p6TZ7m5dJGn9vE/mwZggAD8Q7FwAAHuDn2ONasz/Boe3RdjUUFVHGPQUBQD4RRAAAKOISLqfq7d92OLRVKe2vYd3quakiAMg/gggAAEXcO7/tVOJVi0PbW70aq5Sfj5sqAoD8I4gAAFCErdx7Vr9sPu7Q1r1JZXVpWMlNFQFAwSCIAABQRCWnWTV6TrxDW7C/j964p6GbKgKAgkMQAQCgiPpo6V4dOX/Voe3Vu+qrYoi/myoCgIJDEAEAoAjacSJJU1cecGhrWb2sHmxVzU0VAUDBIogAAFDEWG2GRv6yVVabYW8ze5s0/t4m8vIyubEyACg4BBEAAIqY79YeUtyxiw5tz3SqrTqVgt1UEQAUPIIIAABFyInEZE38Y7dDW2SFID3bqZabKgIA1yCIAABQRBiGoTHz4nUlzerQ/m6fJvI3e7upKgBwDYIIAABFxO/xp7Rk5xmHtgdaRqhtZDk3VQQArkMQAQCgCLiYbNEb87c7tJUv5auRd9d3U0UA4FoEEQAAioD3f9+lM5dSHdrG3NNIZQJ93VQRALgWQQQAADfbeOi8pq874tDWqV4F3dO0spsqAgDXI4gAAOBGaek2jfxlm0NbgNlb43o1lsnEmiEAii+CCAAAbvT5n/u198xlh7ahXesqIjTQTRUBQOEgiAAA4Cb7z17Wx8v2ObQ1qhKix2NquKcgAChEBBEAANzAMAyN+mWb0qw2e5uXSZpwb1P5ePPnGUDxVyze6RYuXKguXbooNDRUQUFBio6O1scffyybzZbzwVlYu3atevXqpQoVKiggIEANGzbUuHHjlJKS4nQfS5YskclkkslkUpcuXfJUBwCg+Jq18ZjWHTzv0DYwpqaaVC3tpooAoHB5fBCZMGGCunfvrqVLl6ps2bKqXbu24uLiNGTIEPXp0yfXYWT69Olq37695s+fLz8/PzVo0ED79u3TmDFj1KFDB129ejXHPlJSUvTMM8/k9SEBAIq5hMupemfhToe28DIBeqlrXTdVBACFz6ODyNq1azVq1Ch5eXlpxowZ2r9/v+Li4hQbG6tKlSpp/vz5mjRpktP9HTp0SIMGDZLVatX777+vo0ePKjY2Vnv37lW9evW0YcMGjRgxIsd+3n77be3bt089e/bMz8MDABRT7/x3ty4mWxza3u7dWEF+Pm6qCAAKn0cHkbfffluGYWjw4MF68MEH7e1RUVH2ADJhwgRZLJbsunAwceJEpaamqlu3bho+fLh92sTq1avryy+/lCRNmTJFp0+fzraPnTt3auLEibrrrrvUp0+fvD40AEAxtfOCSQu2nnJo69G0sm6rX9FNFQGAe3hsEElKStKSJUskSYMGDcq0vV+/fgoJCVFCQoKWL1+eY3+GYWjOnDnZ9teuXTvVr19fFotF8+bNy7aPp556Sl5eXvrPf/6Tm4cDACgBrqal66eDjn96Q/x9NOaehm6qCADcx2ODyObNm5WWliZ/f39FR0dn2m42m9WqVStJ0rp163Ls78iRIzp58qQkKSYmJst9Mtqz62/atGlauXKlRo4cqcjISKceBwCg5Pj3sv06n+q4SOGouxuoYrC/myoCAPfx2CCyd+9eSVK1atXk45P1PbUZYSBjX2f68/PzU5UqVXLd39mzZ/XKK6+odu3aeuWVV3J+AACAEiX++EV9vfaIQ1vrmqG6v2WEmyoCAPfy2FFxFy5ckCSVLVs2230ytmXs60x/ZcqUsY8NyU1/L730ks6fP68ZM2bIz88vx/NlJTU1Vampqfbfk5KSJEkWi8XpcS7OyOirIPt0Jep1Lep1Lep1LU+pN8Vi1dAft8hqM+xtZm+T3rqngazWdFmtbizuJjzl+c1wY71ms9md5QDIgccGkYw1PXx9fbPdJyMQJCcnu7S/pUuXavr06brvvvt0xx135Hiu7IwfP15vvvlmpvZFixYpMDAwz/1mZ/HixQXepytRr2tRr2tRr2sV9Xp/PuilPWccb0K4vXK6dm/4U7vdVFNuFPXn90YZ9fbq1cvNlQC4GY8NIv7+1+6nTUtLy3afjKsLAQEBLusvJSVFTz/9tEqVKqUPP/ww58JvYuTIkRo6dKj996SkJEVERKhbt24KCQnJV9/Xs1gsWrx4sbp27eoR3xZRr2tRr2tRr2t5Qr0r9pzVX2s3O7TVqxSkDwbdIj+fon2HtCc8v9fztHqBks5jg4gzt105c/vWjf0lJibKMIwsb8/Kqr/33ntP+/bt08SJE1W1alXnH0AW/Pz8sryty2w2u+QN1VX9ugr1uhb1uhb1ulZRrffspVSNnLPdoc1sMvTh/VEqFZC323jdoag+v9nxtHqBkspjg0idOnUkXZvtKj09PcsB6wcOHHDY15n+UlNTdeLECYWHhzvV3+bN177lev/99/XBBx847J9xC9fKlSsVFhYmSdqwYYMiIhiYCADFnWEYGj47TucuO15p71XDpjoVS7mpKgAoOor2NeGbaN68ucxms1JSUhQbG5tpu8Vi0YYNGyRJbdq0ybG/atWq2cPC6tWrs9wnoz2r/s6ePavTp087/GQMNk9LS7O3WYvqiEQAQIH6Zs0hrdh91qGtc70KurWSkc0RAFCyeGwQCQkJUZcuXSRdW7/jRrNmzVJSUpLKlSunTp065difyWSyr4SeVX9r1qzRrl27ZDab1bNnT3v73LlzZRhGlj9fffWVJOn222+3t9WoUSMPjxYA4El2nUrSu//d5dBWIdhP7/ZppGwmZgSAEsdjg4gkjR49WiaTSV988YVmzpxpb4+Li7MP+h4xYoTDTFiTJ09WjRo11L9//0z9DR8+XL6+vlq0aJEmTpwow7j2rdXhw4c1cOBASdLgwYPtV04AALhRisWqF2ZuUVq6zaH9X/2iVC4o+5kZAaCk8eggEhMTo3Hjxslms+mhhx5SrVq1FBUVpejoaJ0+fVrdu3fXsGHDHI5JTEzU4cOHderUqUz91axZU1OnTpWXl5dGjBihiIgIRUdHq06dOtq9e7datGihiRMnFtbDAwB4oAn/3aXdpy85tA26taY61K3gpooAoGjy6CAiXbsqsmDBAnXu3FkJCQnat2+fmjRposmTJ2vevHny9vbOVX8DBgzQypUr1aNHDyUnJ2vHjh2KjIzU2LFjtWrVKgUFBbnokQAAPN3yXWf09ZpDDm31w4I1/I567ikIAIowj50163o9evRQjx49nNp37NixGjt27E33adeunRYsWJDvuh577DE99thj+e4HAFD0nb2UquGz4xza/Hy89PGDzeVvzt2XYgBQEnj8FREAANzNMAyNyGKq3te6N1CdSsFuqgoAijaCCAAA+fTNmkNafsNUvV0aVNTDbau7qSIAKPoIIgAA5EN2U/W+17epTMzVCwDZIogAAJBHN52qt5Sfm6oCAM9AEAEAII+YqhcA8o4gAgBAHizfnXmq3gaVQzTiTqbqBQBnEEQAAMils5dSNXxW5ql6/92/mfx8mKoXAJxBEAEAIBeYqhcACkaBLWh45swZbd26VYcOHdL58+eVnJysgIAAhYaGqkaNGoqKilKFCtwzCwDwbEzVCwAFI89BxDAMLVmyRHPmzNHvv/+uw4cP53hMjRo1dMcdd6hPnz7q0qUL0xoCADwKU/UCQMHJdRA5f/68Pv30U3322Wc6ceKEvd0wjByPPXTokD7//HN9/vnnqlKlip5++mk988wzCg0NzW0ZAAAUKqbqBYCC5XQQuXTpkiZOnKjJkyfrypUrDsEjMDBQLVu2VIMGDVSuXDmFhoYqJCRESUlJOn/+vBISErRz505t3LhRV69elSQdP35cY8aM0YQJE/TSSy/p5ZdfVkhISME/QgAACgBT9QJAwXIqiHz77bd65ZVXdObMGXsAueWWW3TfffepU6dOatq0qby9c54lxGq1auvWrfrrr780e/ZsrVmzRleuXNE777yjqVOn6v3339cjjzySv0cEAEABY6peACh4TgWRxx57TJIUHBysJ598Uk899ZRq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paU7vo9/cF+UqpThNg7A0z333HMymUy67777Mm2zWCwqVaqUTCaTKlSokOUdN3fccYdMJpPGjBmTaZvVatWXX36pzp07q3z58vLz81N4eLj69eunFStWuOLh2K1YsUL9+vVTeHi4/Pz8VL58ed1+++366quvZLXe/Cru/v379cwzz6hOnToKCAhQSEiIoqOj9dZbbykpKSnb82WMtZGkjRs36r777lPlypXl7++v2rVra/jw4UpMTMz2vLt27dKTTz6punXrKjAwUAEBAYqIiFDbtm01atQo7dq1K9tj8yNfQeQ///mPrFarypQpoz/++EN169Z16riWLVvKMAxt27YtP6fX2rVrNWrUKHl5eWnGjBnav3+/4uLiFBsbq0qVKmn+/PmaNGmS0/0dOnRIgwYNktVq1fvvv6+jR48qNjZWe/fuVb169bRhwwaNGDEi03EdO3bUL7/8ovPnz+vYsWPauHGj9u3bp/3796tz585KSEjQI488kq/HCgAliWEYem3ONh04d8WhffCtNdWlYdb3mAPwLJ07d5Z07YP0jUFj/fr19nEl586dy/SZ0WKxaPXq1ZKk2267zWHbxYsX1aVLFw0aNEjLly9XYmKiAgMDdfLkSc2ePVu33Xabhg8f7pLHNHToUN12222aPXu2Tp48qcDAQCUmJmrZsmUaOHCgunXrpkuXLmV57E8//aRGjRrps88+0759+2Q2m5WWlqbNmzfrjTfeUOPGjbVz586bnn/evHmKiYnRzz//rKtXr8owDO3fv18ffPCBmjVrpkOHDmU6ZvHixWrWrJmmTp2qvXv3Kj09Xf7+/jp27JjWrVun8ePH64cffiiIpyeTfAWRZcuWyWQyacCAAdkOPspK9erVJUlHjx7Nz+n19ttvyzAMDR48WA8++KC9PSoqyh5AJkyYIIvF4lR/EydOVGpqqrp166bhw4fbk2X16tX15ZdfSpKmTJmS6Varrl27qk+fPgoJCXFor169umbOnCmTyaTt27dr7969eX6sAFCSzNp4THO3nHBoi6paWiPurO+milDS2WyGEi6nFssfm+3m43tdpVOnTjKZTEpISFBcXJzDtoy7ZjI+Wy1btsxh+7p163TlyhX5+fnplltucdg2aNAgrVixQr6+vvr3v/+tpKQkXbhwQSdOnLBPlPTBBx/os88+K9DH85///EcffvihJOnJJ5/UiRMndOHCBV28eFEffvihfHx8tGzZMj3xxBOZjo2NjdXDDz+s1NRUxcTEKC4uTklJSbp69armz5+vypUr6+jRo7rnnnt0+fLlbGt49NFH1a5dO+3YsUMXL17UlStX9OOPP6ps2bI6fPiw7r///kxXZZ599ln7599t27YpLS1NFy5cUHJysrZt26axY8faP7sXtHwNVs8IEi1btszVcUFBQZJ00ycyJ0lJSVqyZImka//gbtSvXz8988wzSkhI0PLly9WtW7eb9mcYhubMmZNtf+3atVP9+vW1a9cuzZs3T08++aRTdVasWFFly5bV+fPns7ytCwDgaM/pSxozP96hLdjfR/95KFq+PowLgXtcuJqmFm8vcXcZLrHptS4qV8qv0M8bGhqqqKgobdmyRcuWLVOzZs3s2zKCx4svvqi33npLy5Yt04svvphp+y233CJ/f397+/r16/Xzzz9Lkj7++GOHz2thYWGaNm2aLl68qJ9//lmvv/66HnvsMYfj8yo5OVlvvPGGJOnBBx/U559/bt8WFBSkF198Ud7e3hoyZIh+/PFHvfzyyw6fn0ePHi2LxaLatWtr0aJFCgwMlCR5eXnpnnvuUdWqVdW6dWvt379fn332mV5++eUs66hUqZIWLlxon4XMx8dH999/v0JDQ9W1a1dt2LBBv/zyi/r16ydJOnPmjPbt2ydJ+vrrr1W5cmV7X/7+/mrcuLEaN26c7+cnO/l6R09NTZUk+frmbhL3jEtSGYEkLzZv3qy0tDT5+/srOjo603az2axWrVpJupaac3LkyBGdPHlSkhQTE5PlPhntzvSXYc+ePTp//ryCg4NVp04dp48DgJIoOc2qf06PVYrFcVzIe32bKiI00E1VAXCVjNuqrr/ikZqaqrVr1yooKEhDhw6Vr6+v/vrrL4dv8jOumNx4W1bGLURVq1bV4MGDszznuHHjJF275Wvx4sUF8jgWL16s8+fPS7o2S1hWnn32WfsH/ZkzZ9rbExMT9ccff0iShg8fbg8h12vevLnuvffeTMfeaPjw4VlOhdylSxe1a9dOkhxuswoODpaX17U4kPE5uDDlK4hUqFBBknTs2LFcHbd161ZJ2c8l7YyM25yqVasmH5+sL+xERkY67OtMf35+fqpSpUq++zt37pzmz5+vnj17SpLGjx+f5T8sAMD/jJ2/XXvPOF4tf6Rtdd3dpHI2RwDwZBnjRP766y/72nNr1qxRSkqKbr31VpUuXVpt2rTRxYsXtWnTJklSSkqK1q5dKylzENm4caO9PeMD9o0aNGig8PBwh/3zK6OfiIiIbMdMe3t72x/v9eeNjY21j5Hp0qVLtufo2rWrpGufo7MbdpDR/822XX/ugIAA+yRSd955p8aMGaN169YpLS0t234KUr5uzYqKitKxY8f0xx9/6KWXXnLqmPT0dM2aNUsmk0lt27bN87kvXLggSSpbtmy2+2Rsy9jXmf7KlCljHxuS2/62bNmi5s2bO7Q1a9ZMCxYsUI8ePXKsITU11X6VSZJ9dgSLxeL0OBdnZPRVkH26EvW6FvW6FvU6b17cSf240XHsYIOwYL3SrXa29fD8upan12s2m91ZDpzQoUMHeXt769KlS9q4caPatm1rv9qR8cG5c+fOWrlypZYtW6bWrVtrzZo1Sk1NVWBgoNq0aePQ35kzZyTJHjSyU7VqVR0/fty+f37l5rzX73/jf9/s+Ixj09PTdf78+Sy/0L/Z8RnbbnzMX3zxhXr27Km4uDiNGzdO48aNk6+vr1q1aqVevXpp0KBBCg0Nvenjyqt8BZF77rlHv/32m5YsWaI///xTHTt2zPGY119/XcePH5fJZFKvXr3yfO6MNT1udluYn9+1+x2Tk5Oz3acg+ytVqpRiYmJkGIZOnDiho0ePKj4+Xt9++63atWuX4/+J48ePty/4c73r7xUsSAV1ObKwUK9rUa9rUe/NnUmWPtjqLel/XwT5eRm6N+yCli7+I8fjeX5dy1Przc/njBuVDfTVptey/7bak5UNzN0t9gUpY3raDRs2aNmyZWrbtq39Nq3rg8ibb76pZcuW6dVXX7Vvj4mJyfZzW3ZfKud1P2cV1nkLsu5q1aopNjZWixcv1sKFC7V69WrFxcVp9erVWr16tcaPH6/Zs2ff9GpLXuUriDz66KN66623dPLkSfXp00ffffedunfvnuW+58+f1+jRozVlyhSZTCbVr19fffr0yfO5MwYW3ezSUcbVhazulXNFf7Vr19aqVavsvx89elTDhg3TrFmztGvXLsXGxmZ7G5kkjRw5UkOHDrX/npSUpIiICHXr1i3TjFz5YbFYtHjxYnXt2tUjvi2iXteiXtei3pylWqy6b8p6pdocp7R8996m6hl181uyeH5di3r/x8vL5JYB3SVB586d7UHkhRde0Pr161WmTBn7GOC2bdsqICBAq1evVlpamj2I3HhblnRtkqDdu3fnODNrxrCCjGEG+VWxYkVJOc8Im9V5M47N2F6rVq2bHuvj45PtHUHHjx+3DyXIatuN58vg5eWlO+64Q3fccYeka+O5FyxYoJEjR+rIkSN66KGHdOTIkVyPC89JvoKIn5+fpk+frm7duunixYvq2bOn6tWrp7CwMPs+w4YNU3x8vFauXKnU1FQZhqGAgADNmDEjX4U7c9uVM7dv3dhfYmKiDMPIMmnmpj/p2n2CP/zwg/bs2aO4uDj98MMPevjhh7Pd38/Pz37V5Xpms9klfwBc1a+rUK9rUa9rUW/23vptt3adcgwhD7SMUN+W1Zzug+fXtagXrnTbbbfpvffe05o1a7R06VJZLBZ17NjRPsbD19dXMTExWrJkiZYsWaINGzbYj7tRy5YttXLlSi1fvlw2my3LcSK7du2yfyjPmNgovzJmwDp27Jj27NmT5TgRq9Vqv+3s+vNGR0fLy8tLNptNS5cuzTaIZMwWGxUVle2/7+XLl2cbRDLO7cxst8HBwXrooYdUsWJFde3aVadPn9a2bdvUokWLHI/NjXzPg9ixY0fNnTtXZcuWlWEY2r17t/7880/7B/nJkydryZIlSklJkWEYCg0N1a+//qqoqKh8nTdjBqojR47YBzfd6MCBAw77OtNfamqqTpw4keU+uekvg5eXl+68805J1wYjAQD+Z+G2k/ru78MObXUqltLYno3cVBGAwta+fXuZzWYlJyfr3XfflZR50HVG6HjrrbeUnp6uUqVKZfmBun///pKuffv/xRdfZHm+jJXYy5cvf9PB4bnRtWtXlStXTlL2s2Z9/vnn9s+Y169/V6ZMGfuViIkTJ2a53ENcXJx9WuLrj73RBx98YB9ucL3ly5fbF4B84IEH7O05DUq//i4gb2/vm+6bFwUyIftdd92l+Ph4vfjiiypXrpwMw8j0U6ZMGT333HOKj4/PMsHmVvPmzWU2m5WSkpLlB3yLxWJPzDcOZMpKtWrV7FdyMv6PulFGuzP9XS8jKGUXmACgJDqScFWvzN7q0OZv9tL/+0e0AnwL/g8egKIpMDBQrVu3lvS/JRJuDCIZv2dsb9++fZa3u7du3Vp9+/aVJD3//PP6z3/+Y/9gf+rUKT3xxBOaNWuWpGvT+BbEGiLStQ/sGQFk5syZevrpp+0LYF+9elUff/yxfR2UBx54INOVhXfeeUdms1n79u3THXfcYV9J3mazaeHChbr77ruVnp6uWrVq6amnnsq2jpMnT6p79+7avXu3pGufPWfPnq377rtP0rWrLxnTAEvXZihr2rSpPvzwQ+3cuVM227Wp0w3D0Jo1a/TMM89IujZQvkmTJvl8ljIrsJWhwsLCNGnSJJ05c0bx8fH69ddf9f3332vu3LnauHGjzp07p3//+98Ot23lR0hIiD3FTps2LdP2WbNmKSkpSeXKlVOnTp1y7M9kMtnHrGTV35o1a7Rr1y6ZzWb7lLzOSE9P12+//SZJDgv1AEBJlpZu0/MzY3Up1fELmrd6NlbdSsFuqgqAu1wfPCpWrJhpEb2WLVsqOPh/7w03+1J72rRp6tixo9LS0vT888+rdOnSCg0NVZUqVexXSV5++WU9/fTTBfoYnnvuOfsssp9//rkqV66s0NBQlS5dWkOGDJHFYtFtt92mqVOnZjq2efPm+u677+Tr66tVq1apadOmKl26tIKCgtS9e3edOHFCERERWrBggUqVKpVtDd98841Wrlyp+vXrq0yZMipVqpT69eun8+fPq1q1apo9e3amALdt2zYNHTpUDRs2lL+/v8qXL2+/HW7btm0KCQnRjBkziu4VkRs1bNhQd999tx566CH17NnTfu9bQRs9erRMJpO++OILh8Vd4uLi7IO+R4wY4TCwZvLkyapRo4b90t31hg8fLl9fXy1atEgTJ060z+l8+PBhDRw4UJI0ePDgTGHqscce0/r16+37Z9i+fbt69eqlXbt2KSwszJ5GAaCke//3XYo7dtGhrXezKurXsqqbKgLgTtcHi6xCho+Pj9q3b3/TfTKULl1aS5cu1bRp09SpUycFBwfr8uXLCgsLU9++fbV8+XJNnDixYB/A/5k0aZKWLVumvn37qlKlSrp8+bKCg4N122236csvv9TixYsdAtX1HnjgAW3fvl1PPfWUatWqpdTUVPn4+KhZs2Z68803FR8frwYNGtz0/L169dKaNWvUt29f+fv7yzAM1axZU8OGDdOWLVtUs2ZNh/1btWqln376Sc8884xatGih8uXL6+LFi/L391ezZs00YsQI7dy50+G5L1CGh3v77bcNSYYkIzIy0mjatKnh5eVlSDK6d+9upKenO+z/xhtvGJKMjh07ZtnfN998Yz8+PDzcaN68uWE2mw1JRosWLYzLly9nOibj/MHBwUZUVJTRokULIywszDCZTIYko2LFisb69etz/dguXrxoSDIuXryY62NvJi0tzZg7d66RlpZWoP26CvW6FvW6FvVmtnj7KaP6K786/Nw2cblxKcWS6754fl2LeoGib/ny5fbPop7GJVdECtPo0aO1YMECde7cWQkJCdq3b5+aNGmiyZMna968ebm+jDRgwACtXLlSPXr0UHJysnbs2KHIyEiNHTtWq1atUlBQUKZjvv32Wz366KOKiIjQ0aNHFRcXp7S0NLVr107vvPOOdu3aVWCzMgCAJzuemKxhs+Ic2nx9vPTxQ81Vyi9fEzkCADxMgb7rnzhxQtu3b9eFCxeyHLGflQEDBuT7vD169HBq5XLp2kwG2c1mkKFdu3ZasGCB0+d/5JFH9Mgjjzi9PwCURBarTUNmbtbFZMdVul/v0VCNqpR2U1UAAHcpkCDy3Xff6YMPPlB8fHyujjOZTAUSRAAARd+Hi/do02HHtZ/ubhKmh9s4v14IAKD4yHcQefzxx/Xtt99KUqbB2gAASNKfe87qkxX7HdoiQgM0/t6mWS4gCwAo/vIVRL799lt988039t9vv/12tW/fXmFhYVmuEA4AKHnOJKVo6I9bHNrM3ib958FolQ5g9WsAyI9OnTp57MWAfAWRKVOmSLq2iEvGgHEAADJYrDY9N2OzEq44rt77yp31FRVRxj1FAQCKhHzNmhUfHy+TyaSnnnqKEAIAyGTcrzu0/tB5h7YuDSpq0K01szkCAFBS5CuIZCwD37Zt2wIpBgBQfPy44Yi+XXvYoa1KaX9NvC+KcSEAgPwFkerVq0uSUlNTC6QYAEDxsOnwBb0+d7tDm5+Plz5/pKXKBvm6qSoAQFGSryBy9913yzAM/f333wVVDwDAw51OStEz329SmtXm0D6hbxM1qcp6IQCAa/IVRJ577jkFBwfrm2++0YEDBwqqJgCAh0pNt+rp7zfpzCXHK+WDb62pPs2ruqkqAEBRlK8gEhERoZkzZyotLU2333671qxZU1B1AQA8jGEYGjN3uzYfSXRoj6ldTq/eVd89RQEAiqx8L2h49913a/Xq1XrooYfUvn17NW/eXG3btlX58uXl5ZVzzhkzZkx+SwAAFAHfrzuiHzcedWirWjZA/3kwWj7e+freCwBQDOU7iFitVi1evFjnz5+XYRjavHmzNm/e7PTxBBEA8HzrD57Xm/MdB6cHmL01hcHpAIBs5CuIWK1W9e3bVwsWLLC35WZlR6ZvBADPdyIxWc9O36R0m+P7/8R+TdWwSoibqgIAFHX5CiLffPON5s+fL0ny9/fXP/7xD7Vv315hYWHy8/MrkAIBAEVXisWqp77bpHOXHVdOf6ZTLfVoWsVNVQEAPEG+gsiUKVMkSWXLltXKlSvVsGHDAikKAFD0GYahUXO2advxiw7tnepV0Mvd6rmpKgCAp8jX6ME9e/bIZDLpn//8JyEEAEqYr1Yf0i+xxx3aapYP0kf9m8vbi1tvAQA3l68gYrNdW6yqSZMmBVIMAMAzrNl3Tu8s3OnQFuTrrSmPtFDpALObqgIAeJJ8BZHq1atLkq5cuVIgxQAAir6j56/qnzNiZb1hcPqkB5qpTqVgN1UFAPA0+QoivXv3lmEYWr58eUHVAwAowpLTrHryu026cNXi0P7C7XV0R6MwN1UFAPBE+Qoizz33nMLCwjRz5kytX7++oGoCABRBhmFoxM9btfNkkkN7lwaV9MLtddxUFQDAU+UriFSoUEFz5sxR2bJldeedd+r777+3jxsBABQvn/91QAviTji01aoQpA8fiJIXg9MBALmUr+l7Bw4cKOnaYPVly5bp0Ucf1bBhw9SqVSuVL19eXl43zzkmk0nTpk3LTwkAgEKwYvcZvff7Loe2YD8fTR3QUsH+DE4HAORevoLI119/bV8dPeN/z507p//+979O90EQAYCi7dC5Kxoyc7OM68amm0zSRw82U2SFUu4rDADg0fIVRKRr9wznVUZ4AQAUTZdT0/XkdxuVlJLu0P5yt3rqXL+Sm6oCABQH+QoiBw8eLKg6AABFjM1maNhPW7Tn9GWH9rubhOnZTrXcVBUAoLjIVxDJWEcEAFD8/L/l+/TH9tMObfUqBWvifVFc0QYA5Fu+Zs0CABRPS3ed0aQlexzaSgeYNWVACwX55fuuXgAA8j9GBABQvJxOlv49O95hcLqXSfr4weaqXi7IfYUBAIoVrogAAOwupVj0xS5vXU51HJz+6l311aFuBTdVBQAojpy6IvLXX3/Z/7tDhw5ZtufV9f0BANzHZjM0bPY2nUlxHP/RM6qKnmgf6aaqAADFlVNBpFOnTjKZTDKZTEpPT8/Unlc39gcAcJ+Ji3Zr+e5zDm2NqoTovb5NGZwOAChwTo8RyW69kPysIwIAKBq+Wn1Qn67Y79AWGuSrzx9poQBfbzdVBQAozpwKIm+88YakzAsQZrQDADzX/LgTeuvXHQ5t3l4m/b+HolW1bKCbqgIAFHdOB5HbbrtNJpNJXbp0Ubt27eztAADPtXLvWQ37aYtuvLj91j0NdEutcu4pCgBQIjh9a9aff/4pk8mkc+fO5bwzAKDI23osUU9/t0kWq2MK6R5h1f0tq7qpKgBAScE6IgBQAh08d0WPf7VBV9KsDu2PtIlQC9NBN1UFAChJWEcEAEqYM0kpGvDlOiVcSXNo7960skbfXV9MkAUAKAxcEQGAEiQpxaJHv9qgo+eTHdpjapfTpPuj5GXY3FQZAKCk4YoIAJQQKRarnvhmo3aeTHJobxweos8ebiE/H6bpBQAUHoIIAJQAVpuhF3/YonUHzzu0Vy8XqK8ea61gf7ObKgMAlFQEEQAo5gzD0Ovz4vX79lMO7eVL+em7gW1UIdjPTZUBAEqyXI8Ree211zR58uQCObnJZNLSpUsLpC8AQNYmL9mrGeuOOLSV8vPRNwNbqVo5FiwEALhHroPI9u3bC+TEhmFkWqkdAFCwvvv7sD5autehzdfbS1MGtFCjKqXdVBUAAHkIIsaNy+8CAIqkhdtOasy8eIc2k0n6qH8ztatV3k1VAQBwTa6DyNtvv62YmBhX1AIAKCBr9p/Tiz9s0Y3fHY3r1Vh3NansnqIAALhOroNI48aN1bFjR1fUAgAoAPHHL+rJbzcpzeq4JsgLt9fRw22ru6kqAAAcMWsWABQjRxKu6rGvNuhyarpD+0NtqunFLnXcVBUAAJkRRACgmDh7KVWPfLlO5y6nOrTf2ShM43o1ZoIQAECRQhABgGLgUopFj321XocTrjq0t6kZqsn9m8nbixACAChaCCIA4OFS0616+vtN2n4iyaG9QeUQTX20pfzN3m6qDACA7BFEAMCDWW2Ghv4Up9X7EhzaI0ID9M3jrRTib3ZTZQAA3FyugghriABA0WEYht5csF2/bT3p0F4uyFffDmyjiiH+bqoMAICcOT1978GDByVJFStWdFkxAADn/b/l+/Tt2sMObUG+3vr68daqWT7ITVUBAOAcp4NI9erMPQ8ARcUP64/og0V7HNrM3iZ9/khLNala2k1VAQDgPMaIAICH+WP7KY2as82hzWSSJt3fTLfWKe+mqgAAyB2CCAB4kBW7z+j5GZtlu2HI3hs9GuqeqCruKQoAgDwgiACAh1iz75ye+m6T0qw2h/bnbqutx2JquqkqAADyhiACAB5g46HzGvTNRqWmO4aQB1tHaFi3um6qCgCAvCOIAEARF3c0UY99tUHJFqtDe5/m4Xq7dxOZTKyaDgDwPMUiiCxcuFBdunRRaGiogoKCFB0drY8//lg2my3ng7Owdu1a9erVSxUqVFBAQIAaNmyocePGKSUlJcv99+zZo/Hjx6tbt24KCwuT2WxWaGiobrvtNn311Vd5rgMAdpxI0oAv1+tyarpDe/cmlTXxvqby9iKEAAA8k8cHkQkTJqh79+5aunSpypYtq9q1aysuLk5DhgxRnz59ch0Cpk+frvbt22v+/Pny8/NTgwYNtG/fPo0ZM0YdOnTQ1atXHfa3Wq2qV6+eRo0apcWLF8tsNqtZs2Yym81asWKFBg4cqLvuuivbEAMA2dl7+pIenrZOF5MtDu1dGlTU5P7N5OPt8W/hAIASzKP/iq1du1ajRo2Sl5eXZsyYof379ysuLk6xsbGqVKmS5s+fr0mTJjnd36FDhzRo0CBZrVa9//77Onr0qGJjY7V3717Vq1dPGzZs0IgRIxyOMQxDZcqU0Wuvvab9+/fr6NGj2rBhg06fPq0ff/xRAQEBWrRokV577bWCfvgAirGD567ooS/W6fyVNIf29nXK6z8PRctMCAEAeDiP/kv29ttvyzAMDR48WA8++KC9PSoqyh5AJkyYIIvFkl0XDiZOnKjU1FR169ZNw4cPt993Xb16dX355ZeSpClTpuj06dP2Y7y9vXXgwAGNGzdOkZGRDv3df//9euONNyRJX375JbdoAXDK0fNX9Y+pf+vspVSH9raRoZrySEv5m73dVBkAAAXHY4NIUlKSlixZIkkaNGhQpu39+vVTSEiIEhIStHz58hz7MwxDc+bMyba/du3aqX79+rJYLJo3b5693WQyqWzZstn2261bN0nShQsXdPbs2RzrAFCynbyYrIe++FsnLjrezhldrYymPdpKAb6EEABA8eCxQWTz5s1KS0uTv7+/oqOjM203m81q1aqVJGndunU59nfkyBGdPHlSkhQTE5PlPhntzvSX4fqxIQEBAU4fB6DkOXspVf+Yuk5Hzyc7tDcJL62vB7ZWkJ+PmyoDAKDgeWwQ2bt3rySpWrVq8vHJ+o9zxq1SGfs605+fn5+qVMl6deLc9Jfhp59+kiQ1btxYISEhTh8HoGQ5fyVND3+xTgfOXXForx8WrG8HtlaIv9lNlQEA4Boe+/XahQsXJOmmt0VlbMvY15n+ypQpk+2c/LnpT5Li4+P1ySefSFKmQe5ZSU1NVWrq/+4JT0pKkiRZLBanx7k4I6OvguzTlajXtajXtZypNynZoke+2qjdpy85tEeWD9JXj0arlK+p0B5vcXx+ixLqda0b6zWbCfBAUeaxQSTjlidfX99s9/Hz85MkJScnZ7uPq/pLTExU3759lZaWprvvvluPPPJIjseMHz9eb775Zqb2RYsWKTAwMMfjc2vx4sUF3qcrUa9rUa9rZVdvilX6dIe3Dl12/AKknJ+hR6td1Pq/lhZGeZkUl+e3qKJe18qot1evXm6uBMDNeGwQ8ff3lySlpaVlu0/G1QVnxmYUZH+pqanq3bu39uzZo0aNGun777/P8fySNHLkSA0dOtT+e1JSkiIiItStW7cCva3LYrFo8eLF6tq1q0d8W0S9rkW9rnWzepPTrBr0XawOXXa8ylqltL9mDG6l8DKFP66sOD2/RRH1upan1QuUdB4bRJy5TcqZ27du7C8xMVGGYWR5e5Yz/aWnp+uBBx7Qn3/+qRo1amjRokVOnV+6dsUl46rL9cxms0veUF3Vr6tQr2tRr2vdWG+KxapnZ8ZqwyHH97CKwX6a8URb1SgfVNglOvD057eoo17X8rR6gZLKYwer16lTR9K12a7S09Oz3OfAgQMO+zrTX2pqqk6cOJGn/gzD0OOPP6558+apcuXKWrJkSbYD3wGUXGnpNj07PVar9p1zaC8X5KsZT7RxewgBAKAweGwQad68ucxms1JSUhQbG5tpu8Vi0YYNGyRJbdq0ybG/atWqKSwsTJK0evXqLPfJaM+uv+eee07ff/+9ypUrp8WLF6tWrVpOPRYAJUe61aYXftisZbvOOLSXCTTr+8FtVLtisJsqAwCgcHlsEAkJCVGXLl0kSdOmTcu0fdasWUpKSlK5cuXUqVOnHPszmUzq06dPtv2tWbNGu3btktlsVs+ePTNtHz16tD755BMFBwfr999/V6NGjXL5iAAUd1aboWGz4vTf+FMO7cF+PvpuYBs1qMwU3wCAksNjg4h07cO/yWTSF198oZkzZ9rb4+Li7IO+R4wY4TAT1uTJk1WjRg31798/U3/Dhw+Xr6+vFi1apIkTJ8owDEnS4cOHNXDgQEnS4MGD7VdOMkyaNEnvvvuuAgIC9Ouvv6ply5YF/lgBeDabzdDIX7Zq3hbHWz8Dfb319cBWalK1tJsqAwDAPTw6iMTExGjcuHGy2Wx66KGHVKtWLUVFRSk6OlqnT59W9+7dNWzYMIdjEhMTdfjwYZ06dSpTfzVr1tTUqVPl5eWlESNGKCIiQtHR0apTp452796tFi1aaOLEiQ7HnDhxQi+//LIkKTg4WKNGjdKtt96a5U9W5wRQ/BmG9NZvu/TTxmMO7X4+Xpr2aCu1qB7qpsoAAHAfj501K8Po0aMVFRWlDz/8UJs2bdKpU6fUpEkTPf7443ruuefk7e2dq/4GDBig2rVra/z48VqzZo127NihyMhIPfjgg3rllVfs0/xmSEtLs185OXPmjM6cOZNVt5L+t1YJgJLDMAzNO+yl5SePOrT7entp6oCWuqVWOTdVBgCAe3l8EJGkHj16qEePHk7tO3bsWI0dO/am+7Rr104LFixwqr8aNWrYgwgA3Gjy0v1aftLx4rOPl0mf/CNaHepWcFNVAAC4n0ffmgUARdl/lu3VJ38ecGjzMkkf9W+uLg0ruakqAACKBoIIALjA/1u+Tx8s2uPQZjJJ/7o/St2bVnZTVQAAFB0EEQAoYJ+s2KeJf+zO1D6+TxP1aV7VDRUBAFD0EEQAoAB9umK/3v89cwgZ072++reu5oaKAAAomggiAFBAPvtzv977fVem9r41rHqkLSEEAIDrFYtZswDA3T7/c78m/DdzCHm9e32VPx/vhooAACjauCICAPk05a/9Gp9FCHnjnoYawJUQAACyRBABgHyY+tcBvbswcwgZ06OhHo+p6YaKAADwDAQRAMijL1Ye0DsLd2Zqf71HQw28lRACAMDNEEQAIA++WHlAb/+WOYS81r2BBhFCAADIEUEEAHLpZiFkcPtIN1QEAIDnIYgAQC58uepgliFk9N2EEAAAcoMgAgBO+mr1Qb31645M7aPurq8nOhBCAADIDYIIADjh69UH9eaCzCFk5F319WSHWm6oCAAAz0YQAYAcfLPmkMZmEUJeubO+nupICAEAIC8IIgBwE9+uPaQ35m/P1D7iznp6phMhBACAvCKIAEA2vlt7SGPmZQ4hw++op2c71XZDRQAAFB8EEQDIwnd/H9br2YSQf95GCAEAIL8IIgBwg+//PqzX58Znan+5W11CCAAABYQgAgDXmb7usF7LIoQM61pXz3Wu44aKAAAonggiAPB/Zqw7otFzMoeQoV3r6vnbCSEAABQkgggA6FoIGTVnW6b2l7rU1RBCCAAABY4gAqDEm77ucJYh5MUudfRCF0IIAACu4OPuAgDAXQzD0P9bvk8fLNqTadsLt9fRi13quqEqAABKBoIIgBLJZjM07rcd+mr1oUzbhtxeRy91JYQAAOBKBBEAJY7FatPwWXGau+VEpm3XroRwOxYAAK5GEAFQolxNS9ez02O1YvfZTNvG3tNQj8XUdENVAACUPAQRACVG4tU0Pf71Bm0+kujQ7uNl0r/uj1KvZuHuKQwAgBKIIAKgRDh5MVkDpq3X3jOXHdoDzN767JEW6li3gpsqAwCgZCKIACj29p+9rAHT1ut4YrJDe5lAs756rJWaVyvrpsoAACi5CCIAirWtxxL12FcbdP5KmkN75dL++nZga9WpFOymygAAKNkIIgCKrVV7z+mp7zbqSprVoT2yQpC+G9RG4WUC3FQZAAAgiAAoln7belIv/rhZFqvh0B5VtbS+ery1QoN83VQZAACQCCIAiqHv/j6sMfPiZThmELWvU16fPdxCQX689QEA4G78NQZQbBiGoX8v3acPl+zJtK1H08r61/1R8vPxdkNlAADgRgQRAMWCzWbozQXb9c3aw5m2PdK2usb2bCRvL5MbKgMAAFkhiADweGnpNg2bFacFcScybXuxSx29cHsdmUyEEAAAihKCCACPdiU1XU9/v0kr955zaDeZpLd6NtIjt9RwT2EAAOCmCCIAPNb5K2l6/OsNijua6NBu9jbpwweaqUfTKu4pDAAA5IggAsAjnUhM1sBvY7X/7BWH9kBfb33+SAu1r1PBTZUBAABnEEQAeJxTV6UHpq7XqaRUh/aygWZ9/XhrRUWUcU9hAADAaQQRAB5ly9FEfbTdW1fTHUNIldL++nZQG9WuWMpNlQEAgNwgiADwGH/tOaunv9+kq+mOM2DVrlhK3w1qrcqlA9xUGQAAyC2CCACPMGfzMQ2ftVXpNsfl0ptXK6MvH22lskG+bqoMAADkBUEEQJFmGIamrjygdxfuyrStQ90K+uzhaAX68lYGAICn4a83gCLLZjP0zsKdmrbqYKZt9zQN07/uby5fHy83VAYAAPKLIAKgSEpNt2r4rK2an8Vq6Z0q2/RB3yaEEAAAPBhBBECRcynFoqe/36TV+xIybXv1zrqqfHGHvLxMWRwJAAA8BV8nAihSzlxKUf8pf2cKIT5eJk1+oJkGxdRwT2EAAKBAcUUEQJFx8NwVDfhynY6eT3ZoD/T11mcPt1CHuhVksVjcVB0AAChIBBEARULc0UQ9/vUGnb+S5tBeLshXXz3eSk2rlnFPYQAAwCUIIgDcbsXuM3rm+1glW6wO7dXLBeqbx1urRvkgN1UGAABchSACwK1+3nRMr/yceaHCJuGl9eVjrVQh2M9NlQEAAFciiABwC8Mw9PlfBzThv5kXKmxfp7w+fbiFSvnxFgUAQHHFX3kAhc5mMzTutx36avWhTNt6N6ui9++LYo0QAACKOYIIgEKVmm7V0J/i9NvWk5m2PdkhUq/eWZ81QgAAKAEIIgAKTVKKRU99u0lrD2ReqPC17g00uH2kG6oCAADuQBABUCjOJKXo0a82aOfJJId2s7dJH/SLUq9m4W6qDAAAuANBBIDL7T97WY9+uV7HLjguVBjk663PH2mpW+uUd1NlAADAXYrFaNCFCxeqS5cuCg0NVVBQkKKjo/Xxxx/LZrPlqb+1a9eqV69eqlChggICAtSwYUONGzdOKSkpWe6fmJioH3/8UcOGDdOtt96qwMBAmUwmdenSJT8PCygWNh+5oPs+XZMphJQv5acfn7qFEAIAQAnl8VdEJkyYoJEjR0qSIiMjVapUKcXFxWnIkCFasmSJ5syZIy8v5/PW9OnT9eijj8pqtSo8PFwRERGKj4/XmDFjtGDBAq1YsUKBgYEOx6xYsUL9+/cv0McFFAfLd53Rs9MzL1RYo1ygvh3YRtXKBWZzJAAAKO48+orI2rVrNWrUKHl5eWnGjBnav3+/4uLiFBsbq0qVKmn+/PmaNGmS0/0dOnRIgwYNktVq1fvvv6+jR48qNjZWe/fuVb169bRhwwaNGDEi03EBAQHq0KGDXn75Zf3000969913C/JhAh5p1sajGvztxkwhpGnV0pr9TDtCCAAAJZxHB5G3335bhmFo8ODBevDBB+3tUVFR9gAyYcIEWSwWp/qbOHGiUlNT1a1bNw0fPlwm07UpRKtXr64vv/xSkjRlyhSdPn3a4bg77rhDf/75pyZOnKh+/fqpcuXKBfHwAI+UbrXpX4t2a/jsrbLesFp6h7oVNPOJtipfitXSAQAo6Tw2iCQlJWnJkiWSpEGDBmXa3q9fP4WEhCghIUHLly/PsT/DMDRnzpxs+2vXrp3q168vi8WiefPm5bN6oHg6ev6qHpjytz5eti/Ttnubh2vaoy0VxGrpAABAHhxENm/erLS0NPn7+ys6OjrTdrPZrFatWkmS1q1bl2N/R44c0cmT1xZYi4mJyXKfjHZn+gNKmrmbj+vuj1Zq0+ELmbY91TFSH/SLktnbY99yAABAAfPYTwV79+6VJFWrVk0+Pll/wxoZGemwrzP9+fn5qUqVKvnuDygpklIseuGHzXrxxy26lJrusM3by6QxPRpq5F0NWC0dAAA48Nh7JC5cuPata9myZbPdJ2Nbxr7O9FemTBn72JD89JcXqampSk1Ntf+elHRt4TeLxeL0OBdnZPRVkH26EvW6Vn7qjT2SqGGztupYYuaprSPKBuhf/ZqoeUQZ/v2Kel2Fel3L0+s1m83uLAdADjw2iGSs6eHr65vtPn5+1wbEJicnZ7uPq/rLi/Hjx+vNN9/M1L5o0aJMUwYXhMWLFxd4n65Eva6Vm3qthrTomJf+OGaSoczBvVUFm+6rcUknt63RyW0FWeX/FOfntyigXteiXtfKqLdXr15urgTAzXhsEPH395ckpaWlZbtPxtWFgICAQu8vL0aOHKmhQ4faf09KSlJERIS6deumkJCQAjuPxWLR4sWL1bVrV4/4toh6XSu39R69cFUvz45X7LHETNuC/X301j0N1KOp62aOK+7Pr7tRr2tRr2t5Wr1ASeexQcSZ26ScuX3rxv4SExNlGEaWt2flpr+88PPzs191uZ7ZbHbJG6qr+nUV6nUtZ+qdu/m4Xp8bn2ksiCS1qlFWHz7QTFXLFs76IMXx+S1KqNe1qNe1PK1eoKTy2CBSp04dSddmu0pPT89ywPqBAwcc9nWmv9TUVJ04cULh4eH56g8oTpJSLBozN15zt5zItM3by6QXb6+jZ2+rLW8GpAMAACd57KxZzZs3l9lsVkpKimJjYzNtt1gs2rBhgySpTZs2OfZXrVo1hYWFSZJWr16d5T4Z7c70BxQXmw6f190frcwyhFQLDdSsp2/R87fXIYQAAIBc8dggEhISoi5dukiSpk2blmn7rFmzlJSUpHLlyqlTp0459mcymdSnT59s+1uzZo127dols9msnj175q94wAOkW22avGSP7v/8bx27kHmChnujw/XbkFsVXc01tyoCAIDizWODiCSNHj1aJpNJX3zxhWbOnGlvj4uLsw/6HjFihMNMWJMnT1aNGjXUv3//TP0NHz5cvr6+WrRokSZOnCjDMCRJhw8f1sCBAyVJgwcPtl85AYqrjBXSJy/ZK6vNcNgW7O+jfz/YXJPub6Zgf+7BBgAAeePRQSQmJkbjxo2TzWbTQw89pFq1aikqKkrR0dE6ffq0unfvrmHDhjkck5iYqMOHD+vUqVOZ+qtZs6amTp0qLy8vjRgxQhEREYqOjladOnW0e/dutWjRQhMnTsyylvLly9t/nn/+eUnSX3/95dD+ww8/FPyTABSweVuyXyG9VY2y+u8L7dUzKutFPwEAAJzlsYPVM4wePVpRUVH68MMPtWnTJp06dUpNmjTR448/rueee07e3t656m/AgAGqXbu2xo8frzVr1mjHjh2KjIzUgw8+qFdeecU+ze+NEhISMrVZLBaH9oy1SoCi6FJKusb9sl1zNh/PtI0B6QAAoKB5fBCRpB49eqhHjx5O7Tt27FiNHTv2pvu0a9dOCxYsyFUNGbdxAZ7o4CVp4idrsxwLEhEaoI/6N2csCAAAKFDFIogAyJt0q00fL9+v/8R7y6asB6S/2bMRY0EAAECBI4gAJZDFatO8LSf0yfJ9OnDuiiTH262C/X30Tp8mjAUBAAAuQxABSpC0dJt+jj2mT1bs09Hzma+ASIW/QjoAACiZCCJACZBiserHDUf12Z/7dfJi1pMmeHuZ9MLtdfRsp1ry8fboCfUAAIAHIIgAxdjVtHTNWHdEn/91QGcvpWa7X9UgQ5P+0VqtIysUYnUAAKAkI4gAxdClFIu++/uwvlh5UOevpGW7X7OIMnq2Y01d3bdBzSPKFF6BAACgxCOIAMXIxasWfbXmoL5afUgXky3Z7te6Rqiev722bq1dXunp6Vq4vxCLBAAAEEEEKBbOX0nTtFUH9O2aw7qUmp7tfrfWLq/nO9dWm8hyhVgdAABAZgQRwIOduZSiL1Ye1Pd/H9bVNGu2+91Wr4Ke61xHLaqzKCEAACgaCCKABzp5MVmf/3lAM9cfUWq6Ldv97mhUSc/dVkdNqpYuxOoAAAByRhABPMjR81f16Z/7NXvjMaVZsw4gJpPUvUllPde5tuqHhRRyhQAAAM4hiABF1KUUi44nJuv4hWQdT0zWliOJmh93Quk2I8v9vb1M6hVVRc/eVlu1K5Yq5GoBAAByhyACuIFhGEq4kmYPGRn/e8z++1UlpWQ/6Px6Pl4m3deiqp7pVEvVywW5uHIAAICCQRABXMBqSMcTk3X6UpJD0Lj+v282tsMZvt5eeqBVhJ7uVEvhZQIKqHIAAIDCQRABCtDv8Sc1ecle7TnlLdvfK11yDn+zlx5qXV1PdYxUpRB/l5wDAADA1QgiQAH5Zs0hvTF/+//9ZiqwfisE+ym8TIDCywaoYeUQPdAqQuVL+RVY/wAAAO5AEAEKwLdrrw8hzvPxMimstL89aFT9v/8NLxOo8LIBqlzaX/5mbxdUDAAA4F4EESCfvlt7SGPmZR1C/M1e/xcyAhVeJkBVywbYQ0d4mQBVCvGXt1fBXT0BAADwFAQRIB+++/uwXs8ihNxV1ao3Hr5dlUoHymQiaAAAANyIIALk0fd/H9brc+MztQ/tUlvVr+xSuSBfQggAAEA2vNxdAOCJpq87rNeyCCHDutbVMx0j3VARAACAZyGIALk0Y90RjZ6TxZWQrnX1/O113FARAACA5yGIALkwY90RjZqzLVP7S13qagghBAAAwGkEEcBJP6zPOoS82KWOXuhCCAEAAMgNggjghB83HNGrv2QOIS/cXkcvdqnrhooAAAA8G0EEyMFPG45mGUKG3F5HL3UlhAAAAOQFQQS4iZ82HtUrv2yVYTi2D+lcWy9xOxYAAECeEUSAbMzaeFSv/Jw5hDzfubZe6lqXNUIAAADygSACZGH2pmMakUUIee622hpKCAEAAMg3gghwg9mbjmn47LhMIeTZTrU0rBshBAAAoCAQRIDr/JxNCHmmUy0Nv6MeIQQAAKCAEESA//NL7DG9nEUIebpjLY0ghAAAABQoH3cXAGTFMAxtP5GkuZuPa/3BBCVd9Nbf6TvUuGoZNagcovphwQr0Lbh/vnM2H9OwWZlDyFMdI/XKnYQQAACAgkYQQZFy7MJVzdtyQnM3H9feM5ev22LSoQ3HpA3Hrv1mkmqWD1LDyiFqUDlEDauEqFHlEFUI9st1aJi7+biG/ZRFCOkQqVfvrE8IAQAAcAGCCNzu4lWLFsaf1JzNx7X+4HmnjjEM6cDZKzpw9op+3XrS3l4uyFcNq4So4f+FkwaVQxRZPkg+3lnfhThvy3EN/WmLbDeEkCc7ROrVuwghAAAArkIQgVukplu1fNdZzd18XMt2nVGa1VYg/SZcSdPKvee0cu85e5uvj5fqhwWrQdi1cNKwyrVbu5btOqOXfswcQp5oX1MjCSEAAAAuRRBBobHZDG08fEFzNh/Xb1tPKCklPcdjwssEqGfTMB0/tE+20uHadfqyDpy9nCk83Exauk1bj13U1mMXHdpNJmW6HWvwrTU16u4GhBAAAAAXI4jA5faduaS5m09o7pbjOnYhOcf9SweY1b1pZfVpHq4W1crKak3XwoV7dffdTWU2m5WcZtWe05e042SSdpxI0s6T136upFlzVdeNIWTQrTU1ujshBAAAoDAQROASZy6laEHcSc3dfFzbjl/McX9fby/d3qCiejcPV6d6FeTn423fZr0hXwT4eisqooyiIsrY22w2Q0fOX9WO/wslO04kacfJJJ28mOJUvY/H1NBrhBAAAIBCQxBBgbmSmq5FO05pzuYTWrX3rFO3T7WpGao+zcN1V+PKKh1ozvO5vbxMqlE+SDXKB+nuJpXt7ReupF0LJteFk31nLiv9uuIGcyUEAACg0BFEkC+GcW3cxw/rj2rhtpNKtuR8e1SdiqXUJzpcPaOqqGrZQJfWVzbIV+1ql1e72uXtbanpVu09fVmHEq6oatlANbvuygoAAAAKB0EEeXLucqp+iT2mHzYc1YGzV3Lcv2Kwn3o1q6LezcPVsHKIW68++Pl4q3F4aTUOL+22GgAAAEo6ggicZrUZWrn3rH7ccFSLd5x2uL0pK0G+3rqjcZj6NA9Xu1rl5e3FrU8AAAC4hiCCHB27cFU/bTym2RuP6kQOg7+9vUzqUKe8ejcPV9eGlRToyz8xAAAAZManRGQpNd2qJTvO6IcNR7Rq37lMU93eKLJCkB5oGaF7o6uqQrBf4RQJAAAAj0UQgYO9py/pxw1H9cvm4zp/Je2m+/qbvdS9SRX1bx2hltXLMusUAAAAnEYQga6mpevXrSf144aj2nT4Qo77NwkvrQdaRahnsyoK8c/7lLsAAAAouQgiJZRhSFuPXdTszSe1IO6ELqem33T/YH8f9WkervtbRjDbFAAAAPKNIFLCJF5N08+bjmraVm+d+Htdjvu3qRmq/q0jdFfjyvI3e+e4PwAAAOAMgkgJYRiGXvl5q+ZuOaG0dJuk7MdzlC/lp/taVNX9LasqskKpwisSAAAAJQZBpIQwmUxKS7f9XwjJzMsk3Vavou5vFaHO9SvK7O1VyBUCAACgJCGIlCAPtKqmuVtOOLRFhAbogZYRuq9FhMJK+7upMgAAAJQ0BJESpG1kqGqUC9TxxGQ1KWPVC/e0Uvu6leTFiucAAAAoZASREsRkMunfDzZXWLBZa1csUbta5QghAAAAcAuCSAnTtGoZWSwWd5cBAACAEo4RyQAAAAAKHUEEAAAAQKEjiAAAAAAodAQRAAAAAIWuWASRhQsXqkuXLgoNDVVQUJCio6P18ccfy2bLevG+nKxdu1a9evVShQoVFBAQoIYNG2rcuHFKSUm56XE7d+7UP/7xD1WuXFn+/v6qVauWXn75ZSUmJuapDgAAAKC48vggMmHCBHXv3l1Lly5V2bJlVbt2bcXFxWnIkCHq06dPrsPI9OnT1b59e82fP19+fn5q0KCB9u3bpzFjxqhDhw66evVqlsctX75cLVq00IwZM2S1WtWoUSOdOnVK//rXv9SiRQudPn26IB4uAAAAUCx4dBBZu3atRo0aJS8vL82YMUP79+9XXFycYmNjValSJc2fP1+TJk1yur9Dhw5p0KBBslqtev/993X06FHFxsZq7969qlevnjZs2KARI0ZkOu7SpUt64IEHlJycrCFDhuj48ePatGmTjhw5opiYGB04cECDBg0qyIcOAAAAeDSPDiJvv/22DMPQ4MGD9eCDD9rbo6Ki7AFkwoQJTq+bMXHiRKWmpqpbt24aPny4TKZri/1Vr15dX375pSRpypQpma5ufPbZZzp79qwaNGigSZMmyWw2S5LKlSunGTNmyMfHR7/99ptiY2Pz/ZgBAACA4sBjg0hSUpKWLFkiSVlebejXr59CQkKUkJCg5cuX59ifYRiaM2dOtv21a9dO9evXl8Vi0bx58xy2/fLLL5Kkxx57TN7e3g7bqlWrpi5dukiSZs+e7cQjAwAAAIo/jw0imzdvVlpamvz9/RUdHZ1pu9lsVqtWrSRJ69aty7G/I0eO6OTJk5KkmJiYLPfJaL++v/T0dG3atCnXxwEAAAAlmccGkb1790q6dsXBx8cny30iIyMd9nWmPz8/P1WpUsXp/g4dOmS/9Stje37qAAAAAEqCrD/Be4ALFy5IksqWLZvtPhnbMvZ1pr8yZcrYx4Y409/1/51dLc7WkZqaqtTUVPvvFy9elCSdP3/e6XEuzrBYLLp69aoSEhLs41mKMup1Lep1Lep1Lep1LU+v12w2Kzg4ONu/6wDcy2ODSMaaHr6+vtnu4+fnJ0lKTk52WX/Xry2S3bHO1jF+/Hi9+eabmdpr1qx50+MAAEDWLl68qJCQEHeXASALHhtE/P39JUlpaWnZ7pNxdSEgIMBl/WUcl3Hs9b/nto6RI0dq6NCh9t9tNpvOnz+vcuXKFei3OUlJSYqIiNDRo0c94s2Zel2Lel2Lel2Lel2rONQbHBzs5qoAZMdjg4gztzs5c/vWjf0lJibKMIwsP/hn1d/1/33hwgVVrlw5z3X4+fnZr55kKFOmTI6151VISIhH/GHJQL2uRb2uRb2uRb2uRb0AXMFjB6vXqVNH0rXZrtLT07Pc58CBAw77OtNfamqqTpw44XR/NWrUsN83m7E9P3UAAAAAJYHHBpHmzZvLbDYrJSUly4UCLRaLNmzYIElq06ZNjv1Vq1ZNYWFhkqTVq1dnuU9G+/X9+fj42KcPzs1xAAAAQEnmsUEkJCTEvlDgtGnTMm2fNWuWkpKSVK5cOXXq1CnH/kwmk/r06ZNtf2vWrNGuXbtkNpvVs2dPh2333nuvJOnrr7+W1Wp12HbkyBH7wot9+/bN+YEVAj8/P73xxhuZbgMrqqjXtajXtajXtajXtagXgEsZHmzVqlWGyWQyvLy8jBkzZtjbt2zZYlSqVMmQZLz33nsOx3z44YdG9erVjQceeCBTfwcOHDB8fX0NScb7779v2Gw2wzAM49ChQ0a9evUMScYzzzyT6biLFy8a5cuXNyQZQ4YMMdLS0gzDMIxz584ZMTExhiTjrrvuKsiHDgAAAHg0k2EYhluTUD698847eu211yRdWziwVKlSio+Pl81mU/fu3TVv3jx5e3vb9x87dqzefPNNdezYUStWrMjU37fffqvHH39cNptN4eHhqlixouLj42WxWNSiRQv9+eefCgoKynTc0qVL1aNHD6WkpKhChQqqVq2adu7cqatXr6pGjRpau3at/dYvAAAAoKTz2FuzMowePVoLFixQ586dlZCQoH379qlJkyaaPHlyphDijAEDBmjlypXq0aOHkpOTtWPHDkVGRmrs2LFatWpVliFEkm6//XZt3LhR/fv3l8lk0rZt21SpUiUNHTpUsbGxhBAAAADgOh5/RQQAAACA5/H4KyIAAAAAPA9BpAQ4ePCgpk6dqieeeEJRUVHy8fGRyWTS22+/7e7SsjR37lw99dRTatGihSpXrixfX1+VKVNG7dq100cffaS0tDR3l+jgsccek8lkuulPSkqKu8u0O3ToUI71Zvz8+eef7i5XknTq1Cm99NJLqlOnjvz9/VW+fHndeeed+uOPP9xST15eU6dOndK3336r5557Tq1bt5afn59MJpMGDx5cJOtdvny5hgwZoltuuUXh4eHy8/NTcHCwWrRooXHjxunSpUtFruaxY8fm+G96165dRaZeZ1+H33zzTZGoV5IuXryoMWPGqHHjxgoMDFSZMmXUoUMHzZw5s8BrzGAYhlatWqXhw4erbdu2KlOmjHx9fVWlShX17dtXy5cvz/I4d77mADjHY1dWh/M++ugjffTRR+4uw2kffPCBVq9eLT8/P1WpUkVRUVE6efKk1q5dq7Vr1+q7777TkiVLXLrqfF7UqVNHFStWzHKbl1fRyfz+/v6KiYnJdvvJkyd14MAB+fv7q1mzZoVXWDa2bdumrl276vTp0/Lz81Pjxo118eJF/fHHH/rjjz80fvx4vfrqq4VaU15eUz/88INeeuklF1V0c3mpd9q0aZo+fbp8fHxUpUoVNW3aVGfPntXmzZsVGxurr776SitWrFC1atWKTM0ZIiIisq0rMDAwP2VlKy/13ux1eOHCBe3YsUOS1LZt23zVlpW81Hv8+HHddttt2rt3r7y9vdW4cWNZLBatWrVKK1eu1F9//aVPP/20wGtdtmyZfbp+Ly8v1a5dW0FBQdq7d69++eUX/fLLL3rttdc0btw4h+Pc+ZoD4ByCSAlQvnx59ejRQ61bt1arVq30xRdf6Oeff3Z3WdkaPHiw3n77bcXExNhXrZekv//+W/369dOmTZs0evRo/b//9//cWGVmo0aN0mOPPebuMnIUFhamVatWZbv94Ycf1oEDB9SzZ0+VLl26ECvLLD09Xffdd59Onz6tTp066aefflKFChUkXftw0rt3b40aNUrt2rVThw4dCq2uvLymQkJC1LVrV7Vu3VqtW7fWkiVL9PHHHxfZevv06aOHH35YHTt2VEBAgL19x44devDBB7V161Y988wz+u2334pMzRkGDhyosWPHuqSu7OSl3pu9Dl977TXt2LFDrVu3Vr169Qq63DzV+8gjj2jv3r1q1KiRfv31V9WoUUOSFBcXp7vvvlufffaZ2rVrp0ceeaRAazUMQ7Vr19bQoUPVv39/lS1bVpKUlpamsWPHavz48Xr77bfVpk0b9ejRw36cO19zAJzkzrmD4R6PPvqoIckYN26cu0vJtZ9++smQZFSpUsXdpdhlPJ9fffWVu0vJt0uXLhlBQUGGJGPBggXuLseYO3euIcnw8/MzDh06lGn7hAkTDElG586d3VDd/+TlNfXGG28YkoxBgwa5sLKs5fc9YP369YYkw9vb20hOTi7g6rLmTM0Zz+kbb7xRKDXdTH6eY5vNZtSoUcOQZHz88ccuqC6znOrdsmWLIcmQZKxduzbT9h9++MGQZERGRhZ4bRcvXjQsFku22++66y5DktGzZ8+b9uPO1xyArBWd+0UAJ9SvX1+SdPXqVTdXUjz98ssvunLliipUqKA777zT3eVo9erVkqRWrVqpevXqmbb37dtXkrRixQqdOXOmUGsryTJeh1arVampqW6upvhZuXKlDh06JLPZrP79+7u7HEn/ey1WrVo1y1vF+vTpIy8vLx04cECbNm0q0HOHhITIxyf7Gzi6du0qSdqzZ0+BnheA63FrFjzK2rVrJUnR0dFuriSz2bNna+7cuUpKSlLFihUVExOjAQMGuP32ptz4/vvvJUn9+/e/6R/+wnLhwgVJUnh4eJbbM9ptNps2bNig7t27F1ptJVnG6zAyMrJI/vtevny5tm/froSEBIWGhqp169YaMGCAx6znlPE6vPPOO1W+fHk3V3NNTq9FX19flS9fXmfOnNHff/+tFi1aFFptGZOBXH8LIQDP4P5PGkAOrFarTp48qfnz5+vVV19VUFCQxo8f7+6yMrnxXvkff/xRb7zxhmbMmFEkri7k5OTJk1q6dKkkFfg93nmV8SH3+PHjWW6/vn337t0EERcyDEOnT5/W0qVLNXz4cPn4+GjSpEnuLitLf/31l8PvP//8s8aOHatPPvmkyI/jSk1N1axZsyQVndehlPNrMS0tTefOnZN07bVYWAzDsD9fNxv8D6Bo4tYsFFmTJ0+WyWSSj4+PIiIi9M9//lO33367/v77b7Vu3drd5dnVqlVL7777ruLi4pSUlKRLly5p0aJFatOmjS5cuKDevXtr48aN7i4zR9OnT5fNZlO9evXUqlUrd5cjSfY6Nm7cqKNHj2ba/ssvv9j/O+MbWxSsuXPnymQyycvLS5UrV9bDDz+sunXrasWKFerVq5e7y3NQuXJljRo1Shs2bFBCQoKuXr2q1atX66677lJycrIGDhyoBQsWuLvMm1qwYIESExNVunRp3XPPPe4uxy7jtXjs2DGtX78+0/a5c+fKZrNJKtzX4tSpU7V582b5+vrqxRdfLLTzAigYBBEUWeHh4YqJiVHr1q1VqVIlSdduuZg5c6asVqubq/uf119/XSNHjlTTpk0VHBysUqVKqWvXrvrrr7/UunVrpaam6pVXXnF3mTnKuB2kKH0L26tXL1WpUkUpKSl66KGHdPLkSfu23377Te+884799+TkZHeUWOyVK1dOMTExatu2rcLDw2UymbR+/Xp9++23Re45f+qpp/TOO++oZcuWCg0NVUBAgNq1a6fffvtNffr0kWEYeumll2QYhrtLzVbG67Bfv37y9/d3czX/06ZNG/vtVo899pjDeIx169Y5TJNbWP8uYmNj9cILL0iS3n77bdWqVatQzgug4BBEUGT169dPq1at0rp163Tq1Cn9/fffqlGjht59910999xz7i4vR76+vvZ57VesWFGkv7Hftm2b4uLiZDKZ9PDDD7u7HDt/f3/9+OOPCg4O1qpVq1StWjU1btxY4eHh6tGjh30xNUkqVaqUm6stntq3b69Vq1Zp7dq1OnbsmLZv3662bdtqypQpuvfee91dnlNMJpMmTJggSdq/f7+2bt3q5oqylpCQoIULF0qSBgwY4OZqMps+fbrCwsK0c+dONWjQQPXq1VPNmjXVtm1bXb161X4FpzBeiwcPHlSPHj3sX1K8/PLLLj8ngIJHEIHHaNOmjRYuXCg/Pz9NmTJFhw8fdndJObrlllskXRtMfeDAATdXk73vvvtOktShQ4csZ6dyp1tvvVWxsbEaOHCgwsLC7N/EPv3009q4caP96pinDET2dA0aNNCCBQtUqVIl/f777zddC6MoqVu3rkJDQyVJ+/btc3M1Wfvxxx9lsVhUo0YN3Xrrre4uJ5N69epp8+bNeuGFF1SjRg0dOnRIV65c0T/+8Q/FxsYqJCREkutfi6dOnVLXrl118uRJde/eXV9//bVMJpNLzwnANRisDo9SpUoVNWvWTOvWrVNcXFyR+9B8o+sXZExPT3djJdmz2WyaOXOmpKJ1W9b1ateurWnTpmVqT09PV1xcnCQV6iw9JV1QUJA6deqkH3/8UbGxsUXyQ3NWMl6PRfW1mHFb1sMPP1xkP1iHhYVp8uTJmjx5cqZtGWPhXPlaPH/+vLp27ar9+/erY8eOmjVrlsP7LADPwhUReJyMDxFF9cPE9bZv327/76pVq7qxkuwtX75cx44dk7+/v+677z53l5Mrf/zxhy5fvqwqVaoUySmdizNPeh1K0rlz5+xrzRTF1+L+/fvt0yIXpdsjnbV9+3bt3r1b/v7+6tKli0vOcfnyZd19992Kj49Xq1attGDBAqbsBTwcQQQe5dChQ/ZvwKOiotxcTc7+9a9/Sbq2AFx28++7W8ZtWT179iySa0JkJy0tTWPGjJEkPfPMM/L29nZzRSXHxYsXtXz5cklSs2bN3FuMkyZNmiTDMFS6dOkiMyvc9TJeh61bt1a9evXcXE3uGIahkSNHSpL+8Y9/qGzZsgV+jtTUVPXq1Uvr1q1To0aN9Pvvvys4OLjAzwOgcBFEUKRs2rRJb7zxRpbjKX7//XfdddddSk9P1913310kZkhZvHixRo4cqYMHDzq0X7x4UUOGDLHf8pTxgbmoSU5Otk+BW1Rvy1q4cKHWrVvn0Hb06FH17t1bsbGxatiwoYYPH+6m6oqnEydO6MUXX3S4opfh77//1p133qnz58+rSZMm6tixoxsqzGz79u169tlnM9WckpKid999V++9954k6ZVXXpGvr687Sryp6dOnSyq6r0NJWrVqlZYuXeow61hCQoIef/xx+7ihjEkBCpLValX//v21bNky1apVS4sXL7aP9wHg4QwUe6tWrTLKlStn//Hz8zMkGYGBgQ7tR44ccXepxvLlyw1JhiQjLCzMaNmypdG0aVOjTJky9vZWrVoZZ8+edXephmEYxpw5c+x1hYeHG61atTKaNWtm+Pr6GpIMk8lkvPHGG+4uM1szZswwJBkVKlQwLBaLu8vJ0gsvvGBIMsqWLWs0b97caNCggWEymQxJRsOGDY1jx44Vek15eU0dOXLEYVtAQIAhyfDz83NoX7VqldvrPXjwoP3fdWhoqBEdHW00b97cKF++vL29Vq1axr59+wq81rzWvHnzZnttFSpUMFq0aGG0aNHCCAwMtLcPGjTIsNlsRaLe661Zs8aQZJjN5kJ7b8tLvR9++KEhyQgODjaaNm1qNGnSxPDx8bG//23bts0ltWa8T0ky6tSpY8TExGT5c9999zkc587XHADnMFi9BLBYLEpISMjUfvXqVV29etX+e1FYmyMqKkofffSRli5dqu3bt2vXrl1KS0tTuXLldMstt+j+++/Xww8/LB+fovFPt0WLFho9erTWrl2rffv2KT4+XoZhKDw8XO3bt9ezzz6rNm3auLvMbGXcDtK/f/8i85zeqHfv3jp58qTWr1+vnTt3ys/PT61atdIDDzygf/7zn/Lz8yv0mvLymrJarVkek5qaqtTUVIe+C1pu6w0LC9Pnn3+upUuXasuWLdq/f7+uXLmismXLqnPnzurdu7cGDx7s0vvzc1tzjRo1NG7cOK1Zs0a7du3S7t27lZaWpooVK+ruu+/W4MGDdccddxSZeq+X8Tq88847Vb58eZfVeL281NupUycNGDBAa9eu1f79+2UymdSwYUPde++9eumll+yzZhW0618fe/fu1d69e7Pc78bJS9z5mgPgHJNhFOGVnQAAAAAUS4wRAQAAAFDoCCIAAAAACh1BBAAAAEChI4gAAAAAKHQEEQAAAACFjiACAAAAoNARRAAAAAAUOoIIAAAAgEJHEAEAAABQ6AgiAAAAAAodQQQAsmEymRx+fv311xyPSU9Pt+9fo0YN1xcJAICHIogAgJNeffVV2Ww2d5cBAECxQBABACdt375d33zzjbvLAACgWCCIAEAO/P395eV17e1yzJgxSklJcXNFAAB4PoIIAOSgXLlyGjBggCTp2LFj+ve//+3migAA8HwmwzAMdxcBAEWRyWSSJIWHh2vt2rWqW7euUlJSVKZMGR04cEBly5bNdEx6errMZrMkqXr16jp06FBhlgwAgMfgiggAOCEiIkLPP/+8JCkxMVHvvvuumysCAMCzcUUEALJx/RWRY8eO6cKFC6pVq5YuXLggPz8/7dmzR9WqVXM4hisiAAA4hysiAOCksmXLauTIkZKk1NRUjRkzxs0VAQDguQgiAJALzz//vCIiIiRJ3333nbZt2+bmigAA8EwEEQDIBX9/f7311luSJJvNpldffdXNFQEA4JkIIgCQSwMGDFDjxo0lSQsXLtSff/7p5ooAAPA8BBEAyCUvLy9NmDDB/vuIESPcWA0AAJ6JIAIAedC9e3d17NhRkrR+/XrNnj3bzRUBAOBZCCIAkEfvvfee/b9HjRql9PR0N1YDAIBnIYgAQB61adNGffv2lSTt3btXU6dOdXNFAAB4DoIIAOTDu+++Kx8fH0nSW2+9pStXrri5IgAAPANBBADyoW7duho8eLAk6dSpU/rXv/7l5ooAAPAMJsMwDHcXAQBFkclkkiSFh4fr2LFj2e536tQp1a5dW1euXFFQUJD9qkj16tV16NChwigVAACPwxURAMinsLAwDR06VJK4NQsAACcRRACgAAwfPlwVKlRwdxkAAHgMgggAFIDg4GC9/vrr7i4DAACPwRgRAAAAAIWOKyIAAAAACh1BBAAAAEChI4gAAAAAKHQEEQAAAACFjiACAAAAoNARRAAAAAAUOoIIAAAAgEJHEAEAAABQ6AgiAAAAAAodQQQAAABAoSOIAAAAACh0BBEAAAAAhY4gAgAAAKDQEUQAAAAAFDqCCAAAAIBC9/8BuWgtqheVaDAAAAAASUVORK5CYII=",
- "text/plain": [
- "