diff --git a/README.md b/README.md
index a1af37a..05de8e6 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-### A collection of useful scripts, tutorials, and other Python-related things
+
A collection of useful scripts, tutorials, and other Python-related things
@@ -12,8 +12,8 @@
- [// Plotting and Visualization](#-plotting-and-visualization)
- [// Benchmarks](#-benchmarks)
- [// Python and "Data Science"](#-python-and-data-science)
-- [// Other](#-other)
- [// Useful scripts and snippets](#-useful-scripts-and-snippets)
+- [// Other](#-other)
- [// Links](#-links)
@@ -21,8 +21,7 @@
-###// Python tips and tutorials
-[[back to top](#a-collection-of-useful-scripts-tutorials-and-other-python-related-things)]
+Python tips and tutorials [back to top]
- A collection of not so obvious Python stuff you should know! [[IPython nb](http://nbviewer.ipython.org/github/rasbt/python_reference/blob/master/tutorials/not_so_obvious_python_stuff.ipynb?create=1)]
@@ -59,8 +58,7 @@
-###// Python and the web
-[[back to top](#a-collection-of-useful-scripts-tutorials-and-other-python-related-things)]
+Python and the web [back to top]
- Creating internal links in IPython Notebooks and Markdown docs [[IPython nb](http://nbviewer.ipython.org/github/rasbt/python_reference/blob/master/tutorials/table_of_contents_ipython.ipynb)]
@@ -68,26 +66,23 @@
-###// Algorithms
-[[back to top](#a-collection-of-useful-scripts-tutorials-and-other-python-related-things)]
+Algorithms and Data Structures [back to top]
-*The algorithms category has been moved to a separate GitHub repository [rasbt/algorithms_in_ipython_notebooks](https://github.com/rasbt/algorithms_in_ipython_notebooks)*
+*This category has been moved to a separate GitHub repository [rasbt/algorithms_in_ipython_notebooks](https://github.com/rasbt/algorithms_in_ipython_notebooks)*
-
-- Sorting Algorithms [[IPython nb](http://nbviewer.ipython.org/github/rasbt/algorithms_in_ipython_notebooks/blob/master/ipython_nbs/sorting/sorting_algorithms.ipynb?create=1)]
+- Sorting Algorithms [[Collection of IPython Notebooks](https://github.com/rasbt/algorithms_in_ipython_notebooks/tree/master/ipython_nbs/sorting)
- Linear regression via the least squares fit method [[IPython nb](http://nbviewer.ipython.org/github/rasbt/algorithms_in_ipython_notebooks/blob/master/ipython_nbs/statistics/linregr_least_squares_fit.ipynb?create=1)]
- Dixon's Q test to identify outliers for small sample sizes [[IPython nb](http://nbviewer.ipython.org/github/rasbt/algorithms_in_ipython_notebooks/blob/master/ipython_nbs/statistics/dixon_q_test.ipynb?create=1)]
-- Sequential Selection Algorithms [[IPython nb](http://nbviewer.ipython.org/github/rasbt/python_reference/blob/master/tutorials/sorting_csvs.ipynb)]
-
- Counting points inside a hypercube [[IPython nb](http://nbviewer.ipython.org/github/rasbt/algorithms_in_ipython_notebooks/blob/master/ipython_nbs/geometry/points_in_hybercube.ipynb)]
+- Singly Linked List [[ IPython nbviewer ](http://nbviewer.ipython.org/github/rasbt/algorithms_in_ipython_notebooks/blob/master/ipython_nbs/data-structures/singly-linked-list.ipynb)]
+
-###// Plotting and Visualization
-[[back to top](#a-collection-of-useful-scripts-tutorials-and-other-python-related-things)]
+Plotting and Visualization [back to top]
*The matplotlib-gallery in IPython notebooks has been moved to a separate GitHub repository [matplotlib-gallery](https://github.com/rasbt/matplotlib-gallery)*
@@ -100,8 +95,7 @@
-###// Benchmarks
-[[back to top](#a-collection-of-useful-scripts-tutorials-and-other-python-related-things)]
+Benchmarks [back to top]
- Simple tricks to speed up the sum calculation in pandas [[IPython nb](http://nbviewer.ipython.org/github/rasbt/python_reference/blob/master/benchmarks/pandas_sum_tricks.ipynb)]
@@ -126,8 +120,7 @@
-###// Python and "Data Science"
-[[back to top](#a-collection-of-useful-scripts-tutorials-and-other-python-related-things)]
+Python and "Data Science" [back to top]
*The "data science"-related posts have been moved to a separate GitHub repository [pattern_classification](https://github.com/rasbt/pattern_classification)*
@@ -146,8 +139,7 @@
-###// Useful scripts and snippets
-[[back to top](#a-collection-of-useful-scripts-tutorials-and-other-python-related-things)]
+Useful scripts and snippets [back to top]
- [watermark](https://github.com/rasbt/watermark) - An IPython magic extension for printing date and time stamps, version numbers, and hardware information.
@@ -161,15 +153,14 @@
-###// Other
+Other [back to top]
- [Python book reviews](./other/python_book_reviews.md)
- [Happy Mother's Day Plot](./other/happy_mothers_day.ipynb)
-###// Links
-[[back to top](#a-collection-of-useful-scripts-tutorials-and-other-python-related-things)]
+Links [back to top]
@@ -195,16 +186,18 @@
**// Resources for learning Python**
-- [Learn Python The Hard Way](http://learnpythonthehardway.org/book/) - The popular and probably most recommended resource for learning Python.
-
- [Dive Into Python](http://www.diveintopython.net) / [Dive Into Python 3](http://getpython3.com/diveintopython3/) - A free Python book for experienced programmers.
- [The Hitchhiker’s Guide to Python](http://docs.python-guide.org/en/latest/) - A free best-practice handbook for both novices and experts.
- [Think Python - How to Think Like a Computer Scientist](http://www.greenteapress.com/thinkpython/) - An introduction for beginners starting with basic concepts of programming.
+- [A Byte of Python](https://python.swaroopch.com/) - a free book on programming using the Python language.
+
- [Python Patterns](http://matthiaseisen.com/pp/) - A directory of proven, reusable solutions to common programming problems.
+- [Intro to Computer Science - Build a Search Engine & a Social Network](https://www.udacity.com/course/intro-to-computer-science--cs101) - A great, free course for learning Python if you haven't programmed before.
+
**// My favorite Python projects and packages**
diff --git a/tutorials/installing_scientific_packages.md b/tutorials/installing_scientific_packages.md
index 0439c71..918d293 100644
--- a/tutorials/installing_scientific_packages.md
+++ b/tutorials/installing_scientific_packages.md
@@ -278,7 +278,7 @@ print its path:
Finally, we can set an `alias` in our `.bash_profile` or `.bash_rc` file to
-conviniently run IPython from the console. E.g.,
+conveniently run IPython from the console. E.g.,
diff --git a/tutorials/key_differences_between_python_2_and_3.ipynb b/tutorials/key_differences_between_python_2_and_3.ipynb
index 0f74195..68feac3 100644
--- a/tutorials/key_differences_between_python_2_and_3.ipynb
+++ b/tutorials/key_differences_between_python_2_and_3.ipynb
@@ -6,7 +6,7 @@
"source": [
"[Sebastian Raschka](http://sebastianraschka.com) \n",
"\n",
- "last updated 05/27/2014\n",
+ "last updated 07/02/2016\n",
"\n",
"- [Open in IPython nbviewer](http://nbviewer.ipython.org/github/rasbt/python_reference/blob/master/tutorials/key_differences_between_python_2_and_3.ipynb?create=1) \n",
"\n",
@@ -87,6 +87,8 @@
"\n",
"- [Returning iterable objects instead of lists](#Returning-iterable-objects-instead-of-lists)\n",
"\n",
+ "- [Banker's Rounding](#Banker's-Rounding)\n",
+ "\n",
"- [More articles about Python 2 and Python 3](#More-articles-about-Python-2-and-Python-3)"
]
},
@@ -2038,6 +2040,177 @@
"
"
]
},
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Banker's Rounding"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "[[back to the section-overview](#Sections)]"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Python 3 adopted the now standard way of rounding decimals when it results in a tie (.5) at the last significant digits. Now, in Python 3, decimals are rounded to the nearest even number. Although it's an inconvenience for code portability, it's supposedly a better way of rounding compared to rounding up as it avoids the bias towards large numbers. For more information, see the excellent Wikipedia articles and paragraphs:\n",
+ "- [https://en.wikipedia.org/wiki/Rounding#Round_half_to_even](https://en.wikipedia.org/wiki/Rounding#Round_half_to_even)\n",
+ "- [https://en.wikipedia.org/wiki/IEEE_floating_point#Roundings_to_nearest](https://en.wikipedia.org/wiki/IEEE_floating_point#Roundings_to_nearest)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "#### Python 2"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 5,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Python 2.7.12\n"
+ ]
+ }
+ ],
+ "source": [
+ "print 'Python', python_version()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 6,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "16.0"
+ ]
+ },
+ "execution_count": 6,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "round(15.5)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 7,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "17.0"
+ ]
+ },
+ "execution_count": 7,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "round(16.5)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "#### Python 3"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 4,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Python 3.5.1\n"
+ ]
+ }
+ ],
+ "source": [
+ "print('Python', python_version())"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 5,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "16"
+ ]
+ },
+ "execution_count": 5,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "round(15.5)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 6,
+ "metadata": {
+ "collapsed": false
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "16"
+ ]
+ },
+ "execution_count": 6,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "round(16.5)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "
\n",
+ "
"
+ ]
+ },
{
"cell_type": "markdown",
"metadata": {},
@@ -2095,22 +2268,23 @@
}
],
"metadata": {
+ "anaconda-cloud": {},
"kernelspec": {
- "display_name": "Python 3",
+ "display_name": "Python 2",
"language": "python",
- "name": "python3"
+ "name": "python2"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
- "version": 3
+ "version": 2
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
- "pygments_lexer": "ipython3",
- "version": "3.5.0"
+ "pygments_lexer": "ipython2",
+ "version": "2.7.12"
}
},
"nbformat": 4,
diff --git a/tutorials/multiprocessing_intro.ipynb b/tutorials/multiprocessing_intro.ipynb
index a3b0916..b3566c9 100644
--- a/tutorials/multiprocessing_intro.ipynb
+++ b/tutorials/multiprocessing_intro.ipynb
@@ -1,1151 +1,1105 @@
{
- "metadata": {
- "name": "",
- "signature": "sha256:a96ed2f762cf56d93a4e5345428c7db5ec576916158ce54446dfdf837ec7e505"
- },
- "nbformat": 3,
- "nbformat_minor": 0,
- "worksheets": [
+ "cells": [
{
- "cells": [
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "[Sebastian Raschka](http://sebastianraschka.com) \n",
- "\n",
- "- [Open in IPython nbviewer](http://nbviewer.ipython.org/github/rasbt/python_reference/blob/master/tutorials/multiprocessing_intro.ipynb?create=1) \n",
- "\n",
- "- [Link to this IPython notebook on Github](https://github.com/rasbt/python_reference/blob/master/tutorials/multiprocessing_intro.ipynb) \n",
- "\n",
- "- [Link to the GitHub Repository python_reference](https://github.com/rasbt/python_reference)\n"
- ]
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "import time\n",
- "print('Last updated: %s' %time.strftime('%d/%m/%Y'))"
- ],
- "language": "python",
- "metadata": {},
- "outputs": [
- {
- "output_type": "stream",
- "stream": "stdout",
- "text": [
- "Last updated: 20/06/2014\n"
- ]
- }
- ],
- "prompt_number": 1
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "
\n",
- "I would be happy to hear your comments and suggestions. \n",
- "Please feel free to drop me a note via\n",
- "[twitter](https://twitter.com/rasbt), [email](mailto:bluewoodtree@gmail.com), or [google+](https://plus.google.com/+SebastianRaschka).\n",
- "
"
- ]
- },
- {
- "cell_type": "heading",
- "level": 1,
- "metadata": {},
- "source": [
- "Parallel processing via the `multiprocessing` module"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "CPUs with multiple cores have become the standard in the recent development of modern computer architectures and we can not only find them in supercomputer facilities but also in our desktop machines at home, and our laptops; even Apple's iPhone 5S got a 1.3 Ghz Dual-core processor in 2013.\n",
- "\n",
- "However, the default Python interpreter was designed with simplicity in mind and has a thread-safe mechanism, the so-called \"GIL\" (Global Interpreter Lock). In order to prevent conflicts between threads, it executes only one statement at a time (so-called serial processing, or single-threading).\n",
- "\n",
- "In this introduction to Python's `multiprocessing` module, we will see how we can spawn multiple subprocesses to avoid some of the GIL's disadvantages."
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "
\n",
- "
"
- ]
- },
- {
- "cell_type": "heading",
- "level": 2,
- "metadata": {},
- "source": [
- "Sections"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "- [An introduction to parallel programming using Python's `multiprocessing` module](#An-introduction-to-parallel-programming-using-Python's-`multiprocessing`-module)\n",
- " - [Multi-Threading vs. Multi-Processing](#Multi-Threading-vs.-Multi-Processing)\n",
- "- [Introduction to the `multiprocessing` module](#Introduction-to-the-multiprocessing-module)\n",
- " - [The `Process` class](#The-Process-class)\n",
- " - [How to retrieve results in a particular order](#How-to-retrieve-results-in-a-particular-order)\n",
- " - [The `Pool` class](#The-Pool-class)\n",
- "- [Kernel density estimation as benchmarking function](#Kernel-density-estimation-as-benchmarking-function)\n",
- " - [The Parzen-window method in a nutshell](#The-Parzen-window-method-in-a-nutshell)\n",
- " - [Sample data and `timeit` benchmarks](#Sample-data-and-timeit-benchmarks)\n",
- " - [Benchmarking functions](#Benchmarking-functions)\n",
- " - [Preparing the plotting of the results](#Preparing-the-plotting-of-the-results)\n",
- "- [Results](#Results)\n",
- "- [Conclusion](#Conclusion)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "
\n",
- "
"
- ]
- },
- {
- "cell_type": "heading",
- "level": 3,
- "metadata": {},
- "source": [
- "\n",
- "Multi-Threading vs. Multi-Processing\n"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "Depending on the application, two common approaches in parallel programming are either to run code via threads or multiple processes, respectively. If we submit \"jobs\" to different threads, those jobs can be pictured as \"sub-tasks\" of a single process and those threads will usually have access to the same memory areas (i.e., shared memory). This approach can easily lead to conflicts in case of improper synchronization, for example, if processes are writing to the same memory location at the same time. \n",
- "\n",
- "A safer approach (although it comes with an additional overhead due to the communication overhead between separate processes) is to submit multiple processes to completely separate memory locations (i.e., distributed memory): Every process will run completely independent from each other.\n",
- "\n",
- "Here, we will take a look at Python's [`multiprocessing`](https://docs.python.org/dev/library/multiprocessing.html) module and how we can use it to submit multiple processes that can run independently from each other in order to make best use of our CPU cores."
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- ""
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "
\n",
- "
"
- ]
- },
- {
- "cell_type": "heading",
- "level": 1,
- "metadata": {},
- "source": [
- "Introduction to the `multiprocessing` module"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "[[back to top](#Sections)]"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "The [multiprocessing](https://docs.python.org/dev/library/multiprocessing.html) module in Python's Standard Library has a lot of powerful features. If you want to read about all the nitty-gritty tips, tricks, and details, I would recommend to use the [official documentation](https://docs.python.org/dev/library/multiprocessing.html) as an entry point. \n",
- "\n",
- "In the following sections, I want to provide a brief overview of different approaches to show how the `multiprocessing` module can be used for parallel programming."
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "
\n",
- "
"
- ]
- },
- {
- "cell_type": "heading",
- "level": 3,
- "metadata": {},
- "source": [
- "The `Process` class"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "[[back to top](#Sections)]"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "The most basic approach is probably to use the `Process` class from the `multiprocessing` module. \n",
- "Here, we will use a simple queue function to compute the cubes for the 6 numbers 1, 2, 3, 4, 5, and 6 in 6 parallel processes."
- ]
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "import multiprocessing as mp\n",
- "import random\n",
- "import string\n",
- "\n",
- "random.seed(123)\n",
- "\n",
- "# Define an output queue\n",
- "output = mp.Queue()\n",
- "\n",
- "# define a example function\n",
- "def rand_string(length, output):\n",
- " \"\"\" Generates a random string of numbers, lower- and uppercase chars. \"\"\"\n",
- " rand_str = ''.join(random.choice(\n",
- " string.ascii_lowercase \n",
- " + string.ascii_uppercase \n",
- " + string.digits)\n",
- " for i in range(length))\n",
- " output.put(rand_str)\n",
- "\n",
- "# Setup a list of processes that we want to run\n",
- "processes = [mp.Process(target=rand_string, args=(5, output)) for x in range(4)]\n",
- "\n",
- "# Run processes\n",
- "for p in processes:\n",
- " p.start()\n",
- "\n",
- "# Exit the completed processes\n",
- "for p in processes:\n",
- " p.join()\n",
- "\n",
- "# Get process results from the output queue\n",
- "results = [output.get() for p in processes]\n",
- "\n",
- "print(results)"
- ],
- "language": "python",
- "metadata": {},
- "outputs": [
- {
- "output_type": "stream",
- "stream": "stdout",
- "text": [
- "['BJWNs', 'GOK0H', '7CTRJ', 'THDF3']\n"
- ]
- }
- ],
- "prompt_number": 2
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "
\n",
- "
"
- ]
- },
- {
- "cell_type": "heading",
- "level": 3,
- "metadata": {},
- "source": [
- "How to retrieve results in a particular order "
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "[[back to top](#Sections)]"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "The order of the obtained results does not necessarily have to match the order of the processes (in the `processes` list). Since we eventually use the `.get()` method to retrieve the results from the `Queue` sequentially, the order in which the processes finished determines the order of our results. \n",
- "E.g., if the second process has finished just before the first process, the order of the strings in the `results` list could have also been\n",
- "`['PQpqM', 'yzQfA', 'SHZYV', 'PSNkD']` instead of `['yzQfA', 'PQpqM', 'SHZYV', 'PSNkD']`\n",
- "\n",
- "If our application required us to retrieve results in a particular order, one possibility would be to refer to the processes' `._identity` attribute. In this case, we could also simply use the values from our `range` object as position argument. The modified code would be:"
- ]
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "# Define an output queue\n",
- "output = mp.Queue()\n",
- "\n",
- "# define a example function\n",
- "def rand_string(length, pos, output):\n",
- " \"\"\" Generates a random string of numbers, lower- and uppercase chars. \"\"\"\n",
- " rand_str = ''.join(random.choice(\n",
- " string.ascii_lowercase \n",
- " + string.ascii_uppercase \n",
- " + string.digits)\n",
- " for i in range(length))\n",
- " output.put((pos, rand_str))\n",
- "\n",
- "# Setup a list of processes that we want to run\n",
- "processes = [mp.Process(target=rand_string, args=(5, x, output)) for x in range(4)]\n",
- "\n",
- "# Run processes\n",
- "for p in processes:\n",
- " p.start()\n",
- "\n",
- "# Exit the completed processes\n",
- "for p in processes:\n",
- " p.join()\n",
- "\n",
- "# Get process results from the output queue\n",
- "results = [output.get() for p in processes]\n",
- "\n",
- "print(results)"
- ],
- "language": "python",
- "metadata": {},
- "outputs": [
- {
- "output_type": "stream",
- "stream": "stdout",
- "text": [
- "[(0, 'h5hoV'), (1, 'fvdmN'), (2, 'rxGX4'), (3, '8hDJj')]\n"
- ]
- }
- ],
- "prompt_number": 3
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "And the retrieved results would be tuples, for example, `[(0, 'KAQo6'), (1, '5lUya'), (2, 'nj6Q0'), (3, 'QQvLr')]` \n",
- "or `[(1, '5lUya'), (3, 'QQvLr'), (0, 'KAQo6'), (2, 'nj6Q0')]`\n",
- "\n",
- "To make sure that we retrieved the results in order, we could simply sort the results and optionally get rid of the position argument:"
- ]
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "results.sort()\n",
- "results = [r[1] for r in results]\n",
- "print(results)"
- ],
- "language": "python",
- "metadata": {},
- "outputs": [
- {
- "output_type": "stream",
- "stream": "stdout",
- "text": [
- "['h5hoV', 'fvdmN', 'rxGX4', '8hDJj']\n"
- ]
- }
- ],
- "prompt_number": 4
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "**A simpler way to to maintain an ordered list of results is to use the `Pool.apply` and `Pool.map` functions which we will discuss in the next section.**"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "
\n",
- "
"
- ]
- },
- {
- "cell_type": "heading",
- "level": 3,
- "metadata": {},
- "source": [
- "The `Pool` class"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "[[back to top](#Sections)]"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "Another and more convenient approach for simple parallel processing tasks is provided by the `Pool` class. \n",
- "\n",
- "There are four methods that are particularly interesing:\n",
- "\n",
- " - Pool.apply\n",
- " \n",
- " - Pool.map\n",
- " \n",
- " - Pool.apply_async\n",
- " \n",
- " - Pool.map_async\n",
- " \n",
- "The `Pool.apply` and `Pool.map` methods are basically equivalents to Python's in-built [`apply`](https://docs.python.org/2/library/functions.html#apply) and [`map`](https://docs.python.org/2/library/functions.html#map) functions."
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "Before we come to the `async` variants of the `Pool` methods, let us take a look at a simple example using `Pool.apply` and `Pool.map`. Here, we will set the number of processes to 4, which means that the `Pool` class will only allow 4 processes running at the same time."
- ]
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "def cube(x):\n",
- " return x**3"
- ],
- "language": "python",
- "metadata": {},
- "outputs": [],
- "prompt_number": 5
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "pool = mp.Pool(processes=4)\n",
- "results = [pool.apply(cube, args=(x,)) for x in range(1,7)]\n",
- "print(results)"
- ],
- "language": "python",
- "metadata": {},
- "outputs": [
- {
- "output_type": "stream",
- "stream": "stdout",
- "text": [
- "[1, 8, 27, 64, 125, 216]\n"
- ]
- }
- ],
- "prompt_number": 6
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "pool = mp.Pool(processes=4)\n",
- "results = pool.map(cube, range(1,7))\n",
- "print(results)"
- ],
- "language": "python",
- "metadata": {},
- "outputs": [
- {
- "output_type": "stream",
- "stream": "stdout",
- "text": [
- "[1, 8, 27, 64, 125, 216]\n"
- ]
- }
- ],
- "prompt_number": 7
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "The `Pool.map` and `Pool.apply` will lock the main program until all a process is finished, which is quite useful if we want to obtain resuls in a particular order for certain applications. \n",
- "In contrast, the `async` variants will submit all processes at once and retrieve the results as soon as they are finished. \n",
- "One more difference is that we need to use the `get` method after the `apply_async()` call in order to obtain the `return` values of the finished processes."
- ]
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "pool = mp.Pool(processes=4)\n",
- "results = [pool.apply_async(cube, args=(x,)) for x in range(1,7)]\n",
- "output = [p.get() for p in results]\n",
- "print(output)"
- ],
- "language": "python",
- "metadata": {},
- "outputs": [
- {
- "output_type": "stream",
- "stream": "stdout",
- "text": [
- "[1, 8, 27, 64, 125, 216]\n"
- ]
- }
- ],
- "prompt_number": 8
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "
\n",
- "
"
- ]
- },
- {
- "cell_type": "heading",
- "level": 1,
- "metadata": {},
- "source": [
- "Kernel density estimation as benchmarking function"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "[[back to top](#Sections)]"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "In the following approach, I want to do a simple comparison of a serial vs. multiprocessing approach where I will use a slightly more complex function than the `cube` example, which he have been using above. \n",
- "\n",
- "Here, I define a function for performing a Kernel density estimation for probability density functions using the Parzen-window technique. \n",
- "I don't want to go into much detail about the theory of this technique, since we are mostly interested to see how `multiprocessing` can be used for performance improvements, but you are welcome to read my more detailed article about the [Parzen-window method here](http://sebastianraschka.com/Articles/2014_parzen_density_est.html). "
- ]
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "import numpy as np\n",
- "\n",
- "def parzen_estimation(x_samples, point_x, h):\n",
- " \"\"\"\n",
- " Implementation of a hypercube kernel for Parzen-window estimation.\n",
- "\n",
- " Keyword arguments:\n",
- " x_sample:training sample, 'd x 1'-dimensional numpy array\n",
- " x: point x for density estimation, 'd x 1'-dimensional numpy array\n",
- " h: window width\n",
- " \n",
- " Returns the predicted pdf as float.\n",
- "\n",
- " \"\"\"\n",
- " k_n = 0\n",
- " for row in x_samples:\n",
- " x_i = (point_x - row[:,np.newaxis]) / (h)\n",
- " for row in x_i:\n",
- " if np.abs(row) > (1/2):\n",
- " break\n",
- " else: # \"completion-else\"*\n",
- " k_n += 1\n",
- " return (k_n / len(x_samples)) / (h**point_x.shape[1])"
- ],
- "language": "python",
- "metadata": {},
- "outputs": [],
- "prompt_number": 9
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "
\n",
- "**A quick note about the \"completion else**\n",
- "\n",
- "Sometimes I receive comments about whether I used this for-else combination intentionally or if it happened by mistake. That is a legitimate question, since this \"completion-else\" is rarely used (that's what I call it, I am not aware if there is an \"official\" name for this, if so, please let me know). \n",
- "I have a more detailed explanation [here](http://sebastianraschka.com/Articles/2014_deep_python.html#else_clauses) in one of my blog-posts, but in a nutshell: In contrast to a conditional else (in combination with if-statements), the \"completion else\" is only executed if the preceding code block (here the `for`-loop) has finished.\n",
- "
"
- ]
- },
- {
- "cell_type": "heading",
- "level": 3,
- "metadata": {},
- "source": [
- "The Parzen-window method in a nutshell"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "[[back to top](#Sections)]"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "So what this function does in a nutshell: It counts points in a defined region (the so-called window), and divides the number of those points inside by the number of total points to estimate the probability of a single point being in a certain region.\n",
- "\n",
- "Below is a simple example where our window is represented by a hypercube centered at the origin, and we want to get an estimate of the probability for a point being in the center of the plot based on the hypercube."
- ]
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "%matplotlib inline"
- ],
- "language": "python",
- "metadata": {},
- "outputs": [],
- "prompt_number": 10
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "from mpl_toolkits.mplot3d import Axes3D\n",
- "import matplotlib.pyplot as plt\n",
- "import numpy as np\n",
- "from itertools import product, combinations\n",
- "fig = plt.figure(figsize=(7,7))\n",
- "ax = fig.gca(projection='3d')\n",
- "ax.set_aspect(\"equal\")\n",
- "\n",
- "# Plot Points\n",
- "\n",
- "# samples within the cube\n",
- "X_inside = np.array([[0,0,0],[0.2,0.2,0.2],[0.1, -0.1, -0.3]])\n",
- "\n",
- "X_outside = np.array([[-1.2,0.3,-0.3],[0.8,-0.82,-0.9],[1, 0.6, -0.7],\n",
- " [0.8,0.7,0.2],[0.7,-0.8,-0.45],[-0.3, 0.6, 0.9],\n",
- " [0.7,-0.6,-0.8]])\n",
- "\n",
- "for row in X_inside:\n",
- " ax.scatter(row[0], row[1], row[2], color=\"r\", s=50, marker='^')\n",
- "\n",
- "for row in X_outside: \n",
- " ax.scatter(row[0], row[1], row[2], color=\"k\", s=50)\n",
- "\n",
- "# Plot Cube\n",
- "h = [-0.5, 0.5]\n",
- "for s, e in combinations(np.array(list(product(h,h,h))), 2):\n",
- " if np.sum(np.abs(s-e)) == h[1]-h[0]:\n",
- " ax.plot3D(*zip(s,e), color=\"g\")\n",
- " \n",
- "ax.set_xlim(-1.5, 1.5)\n",
- "ax.set_ylim(-1.5, 1.5)\n",
- "ax.set_zlim(-1.5, 1.5)\n",
- "\n",
- "plt.show()"
- ],
- "language": "python",
- "metadata": {},
- "outputs": [
- {
- "metadata": {},
- "output_type": "display_data",
- "png": "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\n",
- "text": [
- ""
- ]
- }
- ],
- "prompt_number": 11
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "point_x = np.array([[0],[0],[0]])\n",
- "X_all = np.vstack((X_inside,X_outside))\n",
- "\n",
- "print('p(x) =', parzen_estimation(X_all, point_x, h=1))"
- ],
- "language": "python",
- "metadata": {},
- "outputs": [
- {
- "output_type": "stream",
- "stream": "stdout",
- "text": [
- "p(x) = 0.3\n"
- ]
- }
- ],
- "prompt_number": 12
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "
\n",
- "
"
- ]
- },
- {
- "cell_type": "heading",
- "level": 2,
- "metadata": {},
- "source": [
- "Sample data and `timeit` benchmarks"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "[[back to top](#Sections)]"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "In the section below, we will create a random dataset from a bivariate Gaussian distribution with a mean vector centered at the origin and a identity matrix as covariance matrix. "
- ]
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "import numpy as np\n",
- "\n",
- "np.random.seed(123)\n",
- "\n",
- "# Generate random 2D-patterns\n",
- "mu_vec = np.array([0,0])\n",
- "cov_mat = np.array([[1,0],[0,1]])\n",
- "x_2Dgauss = np.random.multivariate_normal(mu_vec, cov_mat, 10000)"
- ],
- "language": "python",
- "metadata": {},
- "outputs": [],
- "prompt_number": 13
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "The expected probability of a point at the center of the distribution is ~ 0.15915 as we can see below. \n",
- "And our goal is here to use the Parzen-window approach to predict this density based on the sample data set that we have created above. \n",
- "\n",
- "\n",
- "In order to make a \"good\" prediction via the Parzen-window technique, it is - among other things - crucial to select an appropriate window with. Here, we will use multiple processes to predict the density at the center of the bivariate Gaussian distribution using different window widths."
- ]
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "from scipy.stats import multivariate_normal\n",
- "var = multivariate_normal(mean=[0,0], cov=[[1,0],[0,1]])\n",
- "print('actual probability density:', var.pdf([0,0]))"
- ],
- "language": "python",
- "metadata": {},
- "outputs": [
- {
- "output_type": "stream",
- "stream": "stdout",
- "text": [
- "actual probability density: 0.159154943092\n"
- ]
- }
- ],
- "prompt_number": 14
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "
\n",
- "
"
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "[Sebastian Raschka](http://sebastianraschka.com) \n",
+ "\n",
+ "- [Open in IPython nbviewer](http://nbviewer.ipython.org/github/rasbt/python_reference/blob/master/tutorials/multiprocessing_intro.ipynb?create=1) \n",
+ "\n",
+ "- [Link to this IPython notebook on Github](https://github.com/rasbt/python_reference/blob/master/tutorials/multiprocessing_intro.ipynb) \n",
+ "\n",
+ "- [Link to the GitHub Repository python_reference](https://github.com/rasbt/python_reference)\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Last updated: 20/06/2014\n"
]
- },
- {
- "cell_type": "heading",
- "level": 2,
- "metadata": {},
- "source": [
- "Benchmarking functions"
+ }
+ ],
+ "source": [
+ "import time\n",
+ "print('Last updated: %s' %time.strftime('%d/%m/%Y'))"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "
\n",
+ "I would be happy to hear your comments and suggestions. \n",
+ "Please feel free to drop me a note via\n",
+ "[twitter](https://twitter.com/rasbt), [email](mailto:bluewoodtree@gmail.com), or [google+](https://plus.google.com/+SebastianRaschka).\n",
+ "
"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Parallel processing via the `multiprocessing` module"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "CPUs with multiple cores have become the standard in the recent development of modern computer architectures and we can not only find them in supercomputer facilities but also in our desktop machines at home, and our laptops; even Apple's iPhone 5S got a 1.3 Ghz Dual-core processor in 2013.\n",
+ "\n",
+ "However, the default Python interpreter was designed with simplicity in mind and has a thread-safe mechanism, the so-called \"GIL\" (Global Interpreter Lock). In order to prevent conflicts between threads, it executes only one statement at a time (so-called serial processing, or single-threading).\n",
+ "\n",
+ "In this introduction to Python's `multiprocessing` module, we will see how we can spawn multiple subprocesses to avoid some of the GIL's disadvantages."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "
\n",
+ "
"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Sections"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "- [An introduction to parallel programming using Python's `multiprocessing` module](#An-introduction-to-parallel-programming-using-Python's-`multiprocessing`-module)\n",
+ " - [Multi-Threading vs. Multi-Processing](#Multi-Threading-vs.-Multi-Processing)\n",
+ "- [Introduction to the `multiprocessing` module](#Introduction-to-the-multiprocessing-module)\n",
+ " - [The `Process` class](#The-Process-class)\n",
+ " - [How to retrieve results in a particular order](#How-to-retrieve-results-in-a-particular-order)\n",
+ " - [The `Pool` class](#The-Pool-class)\n",
+ "- [Kernel density estimation as benchmarking function](#Kernel-density-estimation-as-benchmarking-function)\n",
+ " - [The Parzen-window method in a nutshell](#The-Parzen-window-method-in-a-nutshell)\n",
+ " - [Sample data and `timeit` benchmarks](#Sample-data-and-timeit-benchmarks)\n",
+ " - [Benchmarking functions](#Benchmarking-functions)\n",
+ " - [Preparing the plotting of the results](#Preparing-the-plotting-of-the-results)\n",
+ "- [Results](#Results)\n",
+ "- [Conclusion](#Conclusion)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "
\n",
+ "
"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Multi-Threading vs. Multi-Processing"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Depending on the application, two common approaches in parallel programming are either to run code via threads or multiple processes, respectively. If we submit \"jobs\" to different threads, those jobs can be pictured as \"sub-tasks\" of a single process and those threads will usually have access to the same memory areas (i.e., shared memory). This approach can easily lead to conflicts in case of improper synchronization, for example, if processes are writing to the same memory location at the same time. \n",
+ "\n",
+ "A safer approach (although it comes with an additional overhead due to the communication overhead between separate processes) is to submit multiple processes to completely separate memory locations (i.e., distributed memory): Every process will run completely independent from each other.\n",
+ "\n",
+ "Here, we will take a look at Python's [`multiprocessing`](https://docs.python.org/dev/library/multiprocessing.html) module and how we can use it to submit multiple processes that can run independently from each other in order to make best use of our CPU cores."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ ""
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "
\n",
+ "
"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Introduction to the `multiprocessing` module"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "[[back to top](#Sections)]"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "The [multiprocessing](https://docs.python.org/dev/library/multiprocessing.html) module in Python's Standard Library has a lot of powerful features. If you want to read about all the nitty-gritty tips, tricks, and details, I would recommend to use the [official documentation](https://docs.python.org/dev/library/multiprocessing.html) as an entry point. \n",
+ "\n",
+ "In the following sections, I want to provide a brief overview of different approaches to show how the `multiprocessing` module can be used for parallel programming."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "
\n",
+ "
"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### The `Process` class"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "[[back to top](#Sections)]"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "The most basic approach is probably to use the `Process` class from the `multiprocessing` module. \n",
+ "Here, we will use a simple queue function to generate four random strings in parallel."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "['BJWNs', 'GOK0H', '7CTRJ', 'THDF3']\n"
]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "[[back to top](#Sections)]"
+ }
+ ],
+ "source": [
+ "import multiprocessing as mp\n",
+ "import random\n",
+ "import string\n",
+ "\n",
+ "random.seed(123)\n",
+ "\n",
+ "# Define an output queue\n",
+ "output = mp.Queue()\n",
+ "\n",
+ "# define a example function\n",
+ "def rand_string(length, output):\n",
+ " \"\"\" Generates a random string of numbers, lower- and uppercase chars. \"\"\"\n",
+ " rand_str = ''.join(random.choice(\n",
+ " string.ascii_lowercase \n",
+ " + string.ascii_uppercase \n",
+ " + string.digits)\n",
+ " for i in range(length))\n",
+ " output.put(rand_str)\n",
+ "\n",
+ "# Setup a list of processes that we want to run\n",
+ "processes = [mp.Process(target=rand_string, args=(5, output)) for x in range(4)]\n",
+ "\n",
+ "# Run processes\n",
+ "for p in processes:\n",
+ " p.start()\n",
+ "\n",
+ "# Exit the completed processes\n",
+ "for p in processes:\n",
+ " p.join()\n",
+ "\n",
+ "# Get process results from the output queue\n",
+ "results = [output.get() for p in processes]\n",
+ "\n",
+ "print(results)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "
\n",
+ "
"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### How to retrieve results in a particular order "
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "[[back to top](#Sections)]"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "The order of the obtained results does not necessarily have to match the order of the processes (in the `processes` list). Since we eventually use the `.get()` method to retrieve the results from the `Queue` sequentially, the order in which the processes finished determines the order of our results. \n",
+ "E.g., if the second process has finished just before the first process, the order of the strings in the `results` list could have also been\n",
+ "`['PQpqM', 'yzQfA', 'SHZYV', 'PSNkD']` instead of `['yzQfA', 'PQpqM', 'SHZYV', 'PSNkD']`\n",
+ "\n",
+ "If our application required us to retrieve results in a particular order, one possibility would be to refer to the processes' `._identity` attribute. In this case, we could also simply use the values from our `range` object as position argument. The modified code would be:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "[(0, 'h5hoV'), (1, 'fvdmN'), (2, 'rxGX4'), (3, '8hDJj')]\n"
]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "Below, we will set up benchmarking functions for our serial and multiprocessing approach that we can pass to our `timeit` benchmark function. \n",
- "We will be using the `Pool.apply_async` function to take advantage of firing up processes simultaneously: Here, we don't care about the order in which the results for the different window widths are computed, we just need to associate each result with the input window width. \n",
- "Thus we add a little tweak to our Parzen-density-estimation function by returning a tuple of 2 values: window width and the estimated density, which will allow us to to sort our list of results later."
+ }
+ ],
+ "source": [
+ "# Define an output queue\n",
+ "output = mp.Queue()\n",
+ "\n",
+ "# define a example function\n",
+ "def rand_string(length, pos, output):\n",
+ " \"\"\" Generates a random string of numbers, lower- and uppercase chars. \"\"\"\n",
+ " rand_str = ''.join(random.choice(\n",
+ " string.ascii_lowercase \n",
+ " + string.ascii_uppercase \n",
+ " + string.digits)\n",
+ " for i in range(length))\n",
+ " output.put((pos, rand_str))\n",
+ "\n",
+ "# Setup a list of processes that we want to run\n",
+ "processes = [mp.Process(target=rand_string, args=(5, x, output)) for x in range(4)]\n",
+ "\n",
+ "# Run processes\n",
+ "for p in processes:\n",
+ " p.start()\n",
+ "\n",
+ "# Exit the completed processes\n",
+ "for p in processes:\n",
+ " p.join()\n",
+ "\n",
+ "# Get process results from the output queue\n",
+ "results = [output.get() for p in processes]\n",
+ "\n",
+ "print(results)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "And the retrieved results would be tuples, for example, `[(0, 'KAQo6'), (1, '5lUya'), (2, 'nj6Q0'), (3, 'QQvLr')]` \n",
+ "or `[(1, '5lUya'), (3, 'QQvLr'), (0, 'KAQo6'), (2, 'nj6Q0')]`\n",
+ "\n",
+ "To make sure that we retrieved the results in order, we could simply sort the results and optionally get rid of the position argument:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 4,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "['h5hoV', 'fvdmN', 'rxGX4', '8hDJj']\n"
]
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "def parzen_estimation(x_samples, point_x, h):\n",
- " k_n = 0\n",
- " for row in x_samples:\n",
- " x_i = (point_x - row[:,np.newaxis]) / (h)\n",
- " for row in x_i:\n",
- " if np.abs(row) > (1/2):\n",
- " break\n",
- " else: # \"completion-else\"*\n",
- " k_n += 1\n",
- " return (h, (k_n / len(x_samples)) / (h**point_x.shape[1]))"
- ],
- "language": "python",
- "metadata": {},
- "outputs": [],
- "prompt_number": 15
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "def serial(samples, x, widths):\n",
- " return [parzen_estimation(samples, x, w) for w in widths]\n",
- "\n",
- "def multiprocess(processes, samples, x, widths):\n",
- " pool = mp.Pool(processes=processes)\n",
- " results = [pool.apply_async(parzen_estimation, args=(samples, x, w)) for w in widths]\n",
- " results = [p.get() for p in results]\n",
- " results.sort() # to sort the results by input window width\n",
- " return results"
- ],
- "language": "python",
- "metadata": {},
- "outputs": [],
- "prompt_number": 16
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "Just to get an idea what the results would look like (i.e., the predicted densities for different window widths):"
+ }
+ ],
+ "source": [
+ "results.sort()\n",
+ "results = [r[1] for r in results]\n",
+ "print(results)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "**A simpler way to maintain an ordered list of results is to use the `Pool.apply` and `Pool.map` functions which we will discuss in the next section.**"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "
\n",
+ "
"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### The `Pool` class"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "[[back to top](#Sections)]"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Another and more convenient approach for simple parallel processing tasks is provided by the `Pool` class. \n",
+ "\n",
+ "There are four methods that are particularly interesting:\n",
+ "\n",
+ " - Pool.apply\n",
+ " \n",
+ " - Pool.map\n",
+ " \n",
+ " - Pool.apply_async\n",
+ " \n",
+ " - Pool.map_async\n",
+ " \n",
+ "The `Pool.apply` and `Pool.map` methods are basically equivalents to Python's in-built [`apply`](https://docs.python.org/2/library/functions.html#apply) and [`map`](https://docs.python.org/2/library/functions.html#map) functions."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Before we come to the `async` variants of the `Pool` methods, let us take a look at a simple example using `Pool.apply` and `Pool.map`. Here, we will set the number of processes to 4, which means that the `Pool` class will only allow 4 processes running at the same time."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 5,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def cube(x):\n",
+ " return x**3"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 6,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "[1, 8, 27, 64, 125, 216]\n"
]
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "widths = np.arange(0.1, 1.3, 0.1)\n",
- "point_x = np.array([[0],[0]])\n",
- "results = []\n",
- "\n",
- "results = multiprocess(4, x_2Dgauss, point_x, widths)\n",
- "\n",
- "for r in results:\n",
- " print('h = %s, p(x) = %s' %(r[0], r[1]))"
- ],
- "language": "python",
- "metadata": {},
- "outputs": [
- {
- "output_type": "stream",
- "stream": "stdout",
- "text": [
- "h = 0.1, p(x) = 0.016\n",
- "h = 0.2, p(x) = 0.0305\n",
- "h = 0.3, p(x) = 0.045\n",
- "h = 0.4, p(x) = 0.06175\n",
- "h = 0.5, p(x) = 0.078\n",
- "h = 0.6, p(x) = 0.0911666666667\n",
- "h = 0.7, p(x) = 0.106\n",
- "h = 0.8, p(x) = 0.117375\n",
- "h = 0.9, p(x) = 0.132666666667\n",
- "h = 1.0, p(x) = 0.1445\n",
- "h = 1.1, p(x) = 0.157090909091\n",
- "h = 1.2, p(x) = 0.1685\n"
- ]
- }
- ],
- "prompt_number": 17
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "Based on the results, we can say that the best window-width would be h=1.1, since the estimated result is close to the actual result ~0.15915. \n",
- "Thus, for the benchmark, let us create 100 evenly spaced window width in the range of 1.0 to 1.2."
+ }
+ ],
+ "source": [
+ "pool = mp.Pool(processes=4)\n",
+ "results = [pool.apply(cube, args=(x,)) for x in range(1,7)]\n",
+ "print(results)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 7,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "[1, 8, 27, 64, 125, 216]\n"
]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "
\n",
- "
"
+ }
+ ],
+ "source": [
+ "pool = mp.Pool(processes=4)\n",
+ "results = pool.map(cube, range(1,7))\n",
+ "print(results)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "The `Pool.map` and `Pool.apply` will lock the main program until all processes are finished, which is quite useful if we want to obtain results in a particular order for certain applications. \n",
+ "In contrast, the `async` variants will submit all processes at once and retrieve the results as soon as they are finished. \n",
+ "One more difference is that we need to use the `get` method after the `apply_async()` call in order to obtain the `return` values of the finished processes."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 8,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "[1, 8, 27, 64, 125, 216]\n"
]
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "widths = np.linspace(1.0, 1.2, 100)"
- ],
- "language": "python",
- "metadata": {},
- "outputs": [],
- "prompt_number": 18
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "import timeit\n",
- "\n",
- "mu_vec = np.array([0,0])\n",
- "cov_mat = np.array([[1,0],[0,1]])\n",
- "n = 10000\n",
- "\n",
- "x_2Dgauss = np.random.multivariate_normal(mu_vec, cov_mat, n)\n",
- "\n",
- "benchmarks = []\n",
- "\n",
- "benchmarks.append(timeit.Timer('serial(x_2Dgauss, point_x, widths)', \n",
- " 'from __main__ import serial, x_2Dgauss, point_x, widths').timeit(number=1))\n",
- "\n",
- "benchmarks.append(timeit.Timer('multiprocess(2, x_2Dgauss, point_x, widths)', \n",
- " 'from __main__ import multiprocess, x_2Dgauss, point_x, widths').timeit(number=1))\n",
- "\n",
- "benchmarks.append(timeit.Timer('multiprocess(3, x_2Dgauss, point_x, widths)', \n",
- " 'from __main__ import multiprocess, x_2Dgauss, point_x, widths').timeit(number=1))\n",
- "\n",
- "benchmarks.append(timeit.Timer('multiprocess(4, x_2Dgauss, point_x, widths)', \n",
- " 'from __main__ import multiprocess, x_2Dgauss, point_x, widths').timeit(number=1))\n",
- "\n",
- "benchmarks.append(timeit.Timer('multiprocess(6, x_2Dgauss, point_x, widths)', \n",
- " 'from __main__ import multiprocess, x_2Dgauss, point_x, widths').timeit(number=1))"
- ],
- "language": "python",
- "metadata": {},
- "outputs": [],
- "prompt_number": 19
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "
\n",
- "
"
+ }
+ ],
+ "source": [
+ "pool = mp.Pool(processes=4)\n",
+ "results = [pool.apply_async(cube, args=(x,)) for x in range(1,7)]\n",
+ "output = [p.get() for p in results]\n",
+ "print(output)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "
\n",
+ "
"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Kernel density estimation as benchmarking function"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "[[back to top](#Sections)]"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "In the following approach, I want to do a simple comparison of a serial vs. multiprocessing approach where I will use a slightly more complex function than the `cube` example, which he have been using above. \n",
+ "\n",
+ "Here, I define a function for performing a Kernel density estimation for probability density functions using the Parzen-window technique. \n",
+ "I don't want to go into much detail about the theory of this technique, since we are mostly interested to see how `multiprocessing` can be used for performance improvements, but you are welcome to read my more detailed article about the [Parzen-window method here](http://sebastianraschka.com/Articles/2014_parzen_density_est.html). "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 9,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import numpy as np\n",
+ "\n",
+ "def parzen_estimation(x_samples, point_x, h):\n",
+ " \"\"\"\n",
+ " Implementation of a hypercube kernel for Parzen-window estimation.\n",
+ "\n",
+ " Keyword arguments:\n",
+ " x_sample:training sample, 'd x 1'-dimensional numpy array\n",
+ " x: point x for density estimation, 'd x 1'-dimensional numpy array\n",
+ " h: window width\n",
+ " \n",
+ " Returns the predicted pdf as float.\n",
+ "\n",
+ " \"\"\"\n",
+ " k_n = 0\n",
+ " for row in x_samples:\n",
+ " x_i = (point_x - row[:,np.newaxis]) / (h)\n",
+ " for row in x_i:\n",
+ " if np.abs(row) > (1/2):\n",
+ " break\n",
+ " else: # \"completion-else\"*\n",
+ " k_n += 1\n",
+ " return (k_n / len(x_samples)) / (h**point_x.shape[1])"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "
\n",
+ "**A quick note about the \"completion else**\n",
+ "\n",
+ "Sometimes I receive comments about whether I used this for-else combination intentionally or if it happened by mistake. That is a legitimate question, since this \"completion-else\" is rarely used (that's what I call it, I am not aware if there is an \"official\" name for this, if so, please let me know). \n",
+ "I have a more detailed explanation [here](http://sebastianraschka.com/Articles/2014_deep_python.html#else_clauses) in one of my blog-posts, but in a nutshell: In contrast to a conditional else (in combination with if-statements), the \"completion else\" is only executed if the preceding code block (here the `for`-loop) has finished.\n",
+ "
"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### The Parzen-window method in a nutshell"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "[[back to top](#Sections)]"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "So what this function does in a nutshell: It counts points in a defined region (the so-called window), and divides the number of those points inside by the number of total points to estimate the probability of a single point being in a certain region.\n",
+ "\n",
+ "Below is a simple example where our window is represented by a hypercube centered at the origin, and we want to get an estimate of the probability for a point being in the center of the plot based on the hypercube."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 10,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "%matplotlib inline"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 11,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "image/png": "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\n",
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "from mpl_toolkits.mplot3d import Axes3D\n",
+ "import matplotlib.pyplot as plt\n",
+ "import numpy as np\n",
+ "from itertools import product, combinations\n",
+ "fig = plt.figure(figsize=(7,7))\n",
+ "ax = fig.gca(projection='3d')\n",
+ "ax.set_aspect(\"equal\")\n",
+ "\n",
+ "# Plot Points\n",
+ "\n",
+ "# samples within the cube\n",
+ "X_inside = np.array([[0,0,0],[0.2,0.2,0.2],[0.1, -0.1, -0.3]])\n",
+ "\n",
+ "X_outside = np.array([[-1.2,0.3,-0.3],[0.8,-0.82,-0.9],[1, 0.6, -0.7],\n",
+ " [0.8,0.7,0.2],[0.7,-0.8,-0.45],[-0.3, 0.6, 0.9],\n",
+ " [0.7,-0.6,-0.8]])\n",
+ "\n",
+ "for row in X_inside:\n",
+ " ax.scatter(row[0], row[1], row[2], color=\"r\", s=50, marker='^')\n",
+ "\n",
+ "for row in X_outside: \n",
+ " ax.scatter(row[0], row[1], row[2], color=\"k\", s=50)\n",
+ "\n",
+ "# Plot Cube\n",
+ "h = [-0.5, 0.5]\n",
+ "for s, e in combinations(np.array(list(product(h,h,h))), 2):\n",
+ " if np.sum(np.abs(s-e)) == h[1]-h[0]:\n",
+ " ax.plot3D(*zip(s,e), color=\"g\")\n",
+ " \n",
+ "ax.set_xlim(-1.5, 1.5)\n",
+ "ax.set_ylim(-1.5, 1.5)\n",
+ "ax.set_zlim(-1.5, 1.5)\n",
+ "\n",
+ "plt.show()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 12,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "p(x) = 0.3\n"
]
- },
- {
- "cell_type": "heading",
- "level": 2,
- "metadata": {},
- "source": [
- "Preparing the plotting of the results"
+ }
+ ],
+ "source": [
+ "point_x = np.array([[0],[0],[0]])\n",
+ "X_all = np.vstack((X_inside,X_outside))\n",
+ "\n",
+ "print('p(x) =', parzen_estimation(X_all, point_x, h=1))"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "
\n",
+ "
"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Sample data and `timeit` benchmarks"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "[[back to top](#Sections)]"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "In the section below, we will create a random dataset from a bivariate Gaussian distribution with a mean vector centered at the origin and a identity matrix as covariance matrix. "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 13,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import numpy as np\n",
+ "\n",
+ "np.random.seed(123)\n",
+ "\n",
+ "# Generate random 2D-patterns\n",
+ "mu_vec = np.array([0,0])\n",
+ "cov_mat = np.array([[1,0],[0,1]])\n",
+ "x_2Dgauss = np.random.multivariate_normal(mu_vec, cov_mat, 10000)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "The expected probability of a point at the center of the distribution is ~ 0.15915 as we can see below. \n",
+ "And our goal is here to use the Parzen-window approach to predict this density based on the sample data set that we have created above. \n",
+ "\n",
+ "\n",
+ "In order to make a \"good\" prediction via the Parzen-window technique, it is - among other things - crucial to select an appropriate window with. Here, we will use multiple processes to predict the density at the center of the bivariate Gaussian distribution using different window widths."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 14,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "actual probability density: 0.159154943092\n"
]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "[[back to top](#Sections)]"
+ }
+ ],
+ "source": [
+ "from scipy.stats import multivariate_normal\n",
+ "var = multivariate_normal(mean=[0,0], cov=[[1,0],[0,1]])\n",
+ "print('actual probability density:', var.pdf([0,0]))"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "
\n",
+ "
"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Benchmarking functions"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "[[back to top](#Sections)]"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Below, we will set up benchmarking functions for our serial and multiprocessing approach that we can pass to our `timeit` benchmark function. \n",
+ "We will be using the `Pool.apply_async` function to take advantage of firing up processes simultaneously: Here, we don't care about the order in which the results for the different window widths are computed, we just need to associate each result with the input window width. \n",
+ "Thus we add a little tweak to our Parzen-density-estimation function by returning a tuple of 2 values: window width and the estimated density, which will allow us to sort our list of results later."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 15,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def parzen_estimation(x_samples, point_x, h):\n",
+ " k_n = 0\n",
+ " for row in x_samples:\n",
+ " x_i = (point_x - row[:,np.newaxis]) / (h)\n",
+ " for row in x_i:\n",
+ " if np.abs(row) > (1/2):\n",
+ " break\n",
+ " else: # \"completion-else\"*\n",
+ " k_n += 1\n",
+ " return (h, (k_n / len(x_samples)) / (h**point_x.shape[1]))"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 16,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def serial(samples, x, widths):\n",
+ " return [parzen_estimation(samples, x, w) for w in widths]\n",
+ "\n",
+ "def multiprocess(processes, samples, x, widths):\n",
+ " pool = mp.Pool(processes=processes)\n",
+ " results = [pool.apply_async(parzen_estimation, args=(samples, x, w)) for w in widths]\n",
+ " results = [p.get() for p in results]\n",
+ " results.sort() # to sort the results by input window width\n",
+ " return results"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Just to get an idea what the results would look like (i.e., the predicted densities for different window widths):"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 17,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "h = 0.1, p(x) = 0.016\n",
+ "h = 0.2, p(x) = 0.0305\n",
+ "h = 0.3, p(x) = 0.045\n",
+ "h = 0.4, p(x) = 0.06175\n",
+ "h = 0.5, p(x) = 0.078\n",
+ "h = 0.6, p(x) = 0.0911666666667\n",
+ "h = 0.7, p(x) = 0.106\n",
+ "h = 0.8, p(x) = 0.117375\n",
+ "h = 0.9, p(x) = 0.132666666667\n",
+ "h = 1.0, p(x) = 0.1445\n",
+ "h = 1.1, p(x) = 0.157090909091\n",
+ "h = 1.2, p(x) = 0.1685\n"
]
- },
+ }
+ ],
+ "source": [
+ "widths = np.arange(0.1, 1.3, 0.1)\n",
+ "point_x = np.array([[0],[0]])\n",
+ "results = []\n",
+ "\n",
+ "results = multiprocess(4, x_2Dgauss, point_x, widths)\n",
+ "\n",
+ "for r in results:\n",
+ " print('h = %s, p(x) = %s' %(r[0], r[1]))"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Based on the results, we can say that the best window-width would be h=1.1, since the estimated result is close to the actual result ~0.15915. \n",
+ "Thus, for the benchmark, let us create 100 evenly spaced window width in the range of 1.0 to 1.2."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "
\n",
+ "
"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 18,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "widths = np.linspace(1.0, 1.2, 100)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 19,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import timeit\n",
+ "\n",
+ "mu_vec = np.array([0,0])\n",
+ "cov_mat = np.array([[1,0],[0,1]])\n",
+ "n = 10000\n",
+ "\n",
+ "x_2Dgauss = np.random.multivariate_normal(mu_vec, cov_mat, n)\n",
+ "\n",
+ "benchmarks = []\n",
+ "\n",
+ "benchmarks.append(timeit.Timer('serial(x_2Dgauss, point_x, widths)', \n",
+ " 'from __main__ import serial, x_2Dgauss, point_x, widths').timeit(number=1))\n",
+ "\n",
+ "benchmarks.append(timeit.Timer('multiprocess(2, x_2Dgauss, point_x, widths)', \n",
+ " 'from __main__ import multiprocess, x_2Dgauss, point_x, widths').timeit(number=1))\n",
+ "\n",
+ "benchmarks.append(timeit.Timer('multiprocess(3, x_2Dgauss, point_x, widths)', \n",
+ " 'from __main__ import multiprocess, x_2Dgauss, point_x, widths').timeit(number=1))\n",
+ "\n",
+ "benchmarks.append(timeit.Timer('multiprocess(4, x_2Dgauss, point_x, widths)', \n",
+ " 'from __main__ import multiprocess, x_2Dgauss, point_x, widths').timeit(number=1))\n",
+ "\n",
+ "benchmarks.append(timeit.Timer('multiprocess(6, x_2Dgauss, point_x, widths)', \n",
+ " 'from __main__ import multiprocess, x_2Dgauss, point_x, widths').timeit(number=1))"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "
\n",
+ "
"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Preparing the plotting of the results"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "[[back to top](#Sections)]"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 20,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import platform\n",
+ "\n",
+ "def print_sysinfo():\n",
+ " \n",
+ " print('\\nPython version :', platform.python_version())\n",
+ " print('compiler :', platform.python_compiler())\n",
+ " \n",
+ " print('\\nsystem :', platform.system())\n",
+ " print('release :', platform.release())\n",
+ " print('machine :', platform.machine())\n",
+ " print('processor :', platform.processor())\n",
+ " print('CPU count :', mp.cpu_count())\n",
+ " print('interpreter:', platform.architecture()[0])\n",
+ " print('\\n\\n')"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 25,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from matplotlib import pyplot as plt\n",
+ "import numpy as np\n",
+ "\n",
+ "def plot_results():\n",
+ " bar_labels = ['serial', '2', '3', '4', '6']\n",
+ "\n",
+ " fig = plt.figure(figsize=(10,8))\n",
+ "\n",
+ " # plot bars\n",
+ " y_pos = np.arange(len(benchmarks))\n",
+ " plt.yticks(y_pos, bar_labels, fontsize=16)\n",
+ " bars = plt.barh(y_pos, benchmarks,\n",
+ " align='center', alpha=0.4, color='g')\n",
+ "\n",
+ " # annotation and labels\n",
+ " \n",
+ " for ba,be in zip(bars, benchmarks):\n",
+ " plt.text(ba.get_width() + 2, ba.get_y() + ba.get_height()/2,\n",
+ " '{0:.2%}'.format(benchmarks[0]/be), \n",
+ " ha='center', va='bottom', fontsize=12)\n",
+ " \n",
+ " plt.xlabel('time in seconds for n=%s' %n, fontsize=14)\n",
+ " plt.ylabel('number of processes', fontsize=14)\n",
+ " t = plt.title('Serial vs. Multiprocessing via Parzen-window estimation', fontsize=18)\n",
+ " plt.ylim([-1,len(benchmarks)+0.5])\n",
+ " plt.xlim([0,max(benchmarks)*1.1])\n",
+ " plt.vlines(benchmarks[0], -1, len(benchmarks)+0.5, linestyles='dashed')\n",
+ " plt.grid()\n",
+ "\n",
+ " plt.show()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "
\n",
+ "
"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Results"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "[[back to top](#Sections)]"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 26,
+ "metadata": {},
+ "outputs": [
{
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "import platform\n",
- "\n",
- "def print_sysinfo():\n",
- " \n",
- " print('\\nPython version :', platform.python_version())\n",
- " print('compiler :', platform.python_compiler())\n",
- " \n",
- " print('\\nsystem :', platform.system())\n",
- " print('release :', platform.release())\n",
- " print('machine :', platform.machine())\n",
- " print('processor :', platform.processor())\n",
- " print('CPU count :', mp.cpu_count())\n",
- " print('interpreter:', platform.architecture()[0])\n",
- " print('\\n\\n')"
- ],
- "language": "python",
+ "data": {
+ "image/png": "iVBORw0KGgoAAAANSUhEUgAAAowAAAIACAYAAAAIQT11AAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAIABJREFUeJzs3Xl8TPf6B/DPmZBVElmERGWzFQlFaFGyqL3V2kKqIfYt\nqm7Rlt4mVAVxKbXUls3FrSD2NRJL0LpibWuXxBZLbElIJJHz+8NvzjVmJjPImJP4vF8vr3bOnDnn\nmfPM8uScZ75fQRRFEUREREREWiiMHQARERERyRsLRiIiIiIqEQtGIiIiIioRC0YiIiIiKhELRiIi\nIiIqEQtGIiIiIioRC0YCAOzduxcKhQKxsbGvvA2FQoEBAwaUYlRln5+fHzw8PPRePyQkBAoF35a6\npKenQ6FQYPLkycYORW/u7u7w9/c3dhikhaE+v8ria7U0lYXnHxMTA4VCgX379hk7FFnjN5MMXb58\nGUOHDsW7774LKysr2Nvbo379+ggJCcHevXsNtl9BECAIwmtvQ+6URZlCoUBqaqrGdebMmSOt8zpF\nNKB+TGJiYjB37lyt65aFYygXZelYGSq37u7u0mtVoVDAzMwMHh4eGDJkCK5du1bq+yvPDPl6Kkuv\n1ZeVnp6O8PBwnDx5Uus6xn7+e/fuxeTJk/Hw4UO1+5TvTWPHKHcVjB0AqTp69Ch8fX1hZmaGfv36\noUGDBsjLy8P58+exa9cu2NjYwM/Pr9T36+vri7y8PFSo8Pa8JMzNzREdHY2mTZuq3RcdHQ1zc3Pk\n5+eX+odITEwMMjIyMGbMGLX7li5disWLF5fq/sojd3d35Ofnw8TExNih6O38+fMG+0KqUaMGIiIi\nAAA5OTlITk5GVFQUtm3bhlOnTsHBwcEg+y1PytrrSU7S09MxZcoUeHp6olGjRir3yeW9unfvXkyZ\nMgUDBgyAra2tyn3BwcEICgpCxYoVjRRd2fD2VAdlxOTJk5Gfn4/ff/8d3t7eavffunWrVPeXk5MD\na2trCIIAU1PTUt223HXr1g2rV6/G7NmzVZ77f//7X/z555/4/PPPsWrVKoPsW1vhYKiCPTc3F5Uq\nVTLIto2lrL1eDfllZGtri88//1y6PWzYMDg5OWH+/PmIjo7GuHHjXnsfys+K8qqsvZ7kSNvEcXI6\ntppiVCgUsopRrnhJWmYuXLgABwcHjcUiAFStWlVtWWJiItq3bw87OztYWFigUaNGGs9SKXuojh8/\njg4dOqBy5crSX4OaehhFUcRPP/2ENm3awNnZGWZmZnBzc8PIkSNx7969V3p+Z86cgUKhwNdff63x\n/qCgIJiZmeHu3bsAgKtXr2LgwIFwc3ODubk5qlatilatWiEuLu6V9v+8AQMG4P79+9iwYYPK8ujo\naDg5OeHjjz9We4yy12X//v1q9+nTr+ju7o79+/dLfT3Kf8rtaephVC7LyspCv3794OjoiEqVKuGj\njz7C8ePHVdZ9vl/ot99+Q9OmTWFpaYnRo0dL6yxbtgxNmjSBpaUlKleujA4dOuDgwYMa401OTkaX\nLl3g4OAACwsL1KxZE4MHD5byo/Tbb7/hww8/hI2NDaysrPDBBx9g3bp1atvbunUrfH19UaVKFVha\nWsLNzQ09evTAhQsXpHX0ybmmvqjnl23ZsgXNmjWDhYUFXFxcMGHCBDx9+lQtnnXr1qFRo0awsLCA\nm5sbpkyZgsTERL1aEb755hsoFAqcPn1a7b6HDx/CwsIC3bp1k5Zp6mHctWsXevfuDU9PT1haWsLO\nzg4dOnTQ+Pp6We3btwcAXLp06aX3pXwtp6WloWfPnrC3t5fOyrx4Cfz5fy8+v6NHj6Jbt26oUqUK\nzM3N8e6772LatGlquVDuLzMzE0FBQbC3t4eVlRU6duyo8tooyYABA2BhYYEnT55Iyw4fPgyFQgEH\nBweVQmH79u1QKBSIj4+XlmnqYVQuO3z4MHx9fVGpUiU4OjpiyJAhePTokVoMKSkpaNWqFSwtLVGt\nWjWMHj0aubm5GuN99OgRvvvuO9SsWRPm5uZwdnZG//79ceXKFWmdJ0+ewMLCAiEhISqPHTZsGBQK\nBb766iuV5b1794atrS2Ki4t1Hq8LFy4gODhY+mz38PDAhAkT8PjxY5X1dL0fY2JiEBAQAOBZDl58\nLeh6r65duxbvvfceLC0tUatWLSxbtgwAkJGRgZ49e8LBwQE2NjYIDg5WO5Znz57FyJEj0aBBA+mz\nx8fHB8uXL1dZLyQkBFOmTAEAeHh4SDEql2n7XM/KysKoUaNQo0YNmJmZwdXVFaGhoWrff8rHJycn\nY9asWVJO69atWyrfVXLBM4wyU6tWLWzbtg0JCQkqXzbaLFmyBMOHD0fLli3x/fffw8rKCrt27cKI\nESNw6dIlzJw5U1pXEARcuXIFbdu2RWBgIHr16qX2Bnz+zNeTJ08wa9Ys9OzZE926dYOVlRWOHDmC\n5cuXIyUlBampqS991qRevXpo1qwZVq1ahcjISJXiKDs7Gxs3bkTnzp3h4OCAoqIitGvXDjdu3MCo\nUaNQp04dPHz4ECdPnkRKSgr69ev3Uvt+8Xk2btwY7733HqKiohAYGAjg2WWp1atXY9CgQa90RkjX\nJce5c+fiu+++Q1ZWFn7++Wdpeb169XRuo2PHjnBwcMDkyZORmZmJ+fPnw9fXF4cPH0aDBg1U1t2w\nYQOuXLmCkSNHYuTIkbCxsQHwrMiJjIzE+++/j4iICGRnZ2PJkiXw9/fHxo0b0alTJ2kbixcvxogR\nI1CjRg2MGjUKbm5uyMjIwJYtW3D9+nXpMuf333+PadOmoVOnTpg6dSoUCgXWr1+PXr16Yf78+Rg5\nciQAYN++fejatSsaNmyIiRMnonLlyrh+/Tr27NmDS5cuoXbt2i+dc03Hatu2bVi4cCFGjBiBwYMH\nY8OGDZg1axbs7Ozw3XffSev99ttvCAoKQu3atREeHg4TExPExsZi8+bNeuUyJCQEkZGRiIuLQ2Rk\npMp9a9aswZMnT1S+6DX1SMXGxuLBgwcICQnBO++8g2vXrmHZsmVo27YtkpOT8eGHH5YYQ0mUhZaj\no+NL70sQBOTm5sLX1xcffvghIiIicPv2bQDPXsMvFku///475s+fj2rVqknLtm7diu7du6NOnToY\nN24c7O3tcejQIfzwww84ceIE1qxZo7K/R48eoU2bNmjRogUiIiJw+fJlzJ07F59++in+/PNPnT8G\na9u2LWJjY3Hw4EGpgNmzZw8UCgUePHiA48ePo0mTJgCApKQkjQWuppyfOHECn3zyCQYOHIgvvvgC\nycnJWL58ORQKhcof5n/88Qc++ugj2Nra4ttvv4WtrS3+85//aPxjrLCwEB06dMChQ4fQq1cvjB8/\nHufPn8eiRYuwa9cuHD16FNWrV4eZmRlatWqF5ORklccrn1dSUpK0TBRF7N27F23atNF5rFJTUxEQ\nEAB7e3uMGDEC1atXx4kTJzBv3jwcPHgQ+/btQ4UKFfR6P/r6+mLixImYNm0ahg0bhtatWwNQP7mh\n6dhu2bIFv/76K0aNGgV7e3ssW7YMQ4cOhYmJCcLCwtCuXTtERETgyJEjiIqKgrm5OZYuXSo9ft++\nfThw4AC6du0KDw8PPHr0CGvWrMGQIUNw584dfPvttwCA4cOHIycnBwkJCfj555+l90TDhg21HqOH\nDx+iZcuWuHTpEgYNGoQmTZrg2LFjWLRoEZKSknDkyBG1qzYTJ05Efn4+RowYAVNTUyxatAghISGo\nVasWWrZsWWJOygSRZOXw4cOiqampKAiCWLt2bXHAgAHiokWLxDNnzqite+PGDdHMzEzs27ev2n1j\nxowRTUxMxMuXL0vL3NzcREEQxOXLl6utn5ycLAqCIMbGxqosz8/PV1t3+fLloiAI4po1a1SWC4Ig\nDhgwQOdzXLBggSgIgrht2zaV5cuWLRMFQRATEhJEURTFkydPioIgiJGRkTq3+TL69+8vCoIgZmVl\nib/88otoYmIiXrt2TRRFUVy5cqUoCIL4119/ifHx8WrHJDo6WhQEQdy3b5/adn19fUUPD49XWvZi\nbJqW9ejRQ2V5amqqqFAoxI4dO0rL0tLSREEQRFNTU/Hs2bMq6589e1YUBEFs3bq1WFhYKC2/ceOG\nWLlyZdHd3V18+vSpKIqiePXqVdHU1FRs0KCB+PDhQ7U4i4uLpRgEQRAnTZqkts5nn30m2tjYiLm5\nuaIoiuLYsWNFQRDEO3fuaHzuoqh/zpXPc/LkyWrLKlWqJGZkZKis7+XlJTo7O0u3CwsLRRcXF7Fa\ntWrigwcPpOW5ubmip6enxveCJs2aNRNdXFyk46b04YcfilWqVFE5zm5ubqK/v7/Keo8ePVLb5q1b\nt0RHR0exc+fOOvev3G69evXErKws8c6dO+Lly5fFqKgo0dbWVjQ1NRX/+uuvl96Xr6+vKAiC+M9/\n/lPn/tPS0kQnJyexdu3a4r1790RRFMW8vDyxatWqoq+vr9qxmTNnjigIgrh37161/b2Y98jISFEQ\nBHHnzp0647h27Zraa9Hf31/89NNPRRsbG3HmzJnS8iZNmogNGzZUebymzy9BEEQTExPxyJEjKsu7\ndOkiVqxYUeWYtmjRQjQzMxMvXLggLSsoKBCbN2+u9lpdsmSJKAiC+M0336hsd+vWraIgCGJwcLC0\nbOrUqaIgCNJ2MzIypHUEQRBv3boliqIonjp1ShQEQZw9e7bOY9WwYUOxXr160ntTKSEhQRQEQYyJ\niRFFUf/3o7bvD1HU/V69cuWKtPzOnTuiubm5KAiCOGfOHJXtdO/eXTQ1NVU55ppe08XFxaKfn59o\na2ur8v4LCwsTBUFQ+2wQRc2f6xMnThQFQRAXLVqksq7y++v594by8U2aNFHZ5/Xr10UzMzMxKChI\nbZ9lES9Jy8wHH3yA1NRU9O/fH9nZ2YiJicHIkSNRv359+Pr6Ii0tTVp37dq1KCgowMCBA5GVlaXy\n7+OPP0ZxcTESExNVtu/g4PBSQ0eYmZkBAJ4+fYoHDx4gKytL+qv8yJEjr/Qcg4KCYGpqqnaqPi4u\nDg4ODtKlYOUlsKSkJNy5c+eV9lUSQRDw+eefo2LFitLlx+joaDRv3hz169cv9f29rgkTJqjcbtKk\nCdq1a4fExES1y0hdunRB3bp1VZZt3LhR2s7zvZLOzs4YMGAAMjIycOLECQBAfHw8CgsLERYWJp2d\nfJ7ybMHKlSshCAL69eun9hr85JNPkJOTg8OHDwMAKleuDODZ67aoqEjjcyyNnH/22WdwdXVVWebn\n54ebN29Kxyk1NRWZmZkICQlRaYC3srLC8OHD9d5X//79kZmZid27d0vL0tLScOjQIQQFBensSbW0\ntJT+Pzc3F3fv3oVCoUDz5s3xxx9/6B3H2bNnUaVKFTg5OaFmzZoYNGgQnJycsHHjRum1/LL7EgRB\nZ+/jw4cP8fHHH6OoqAhbt26FnZ0dAGD37t24ffs2QkJCcO/ePZXXhfIs9q5du1S2ZWJigi+//FJl\nmfKz5uLFizqPQfXq1VGnTh3prJuyF7xjx47w9fXFnj17AAAPHjzAyZMnpbOQurRo0QLNmjVTi6uo\nqAjp6ekAgNu3b+P333/Hp59+ilq1aknrVaxYEWPHjlXbZkJCAkxMTFTOeANA586d0ahRI+m9Cjw7\ncwpAel5JSUmoUKECwsPDIQiCdPZR+V9dz+v06dM4ffo0goKCkJeXp5Ib5eV0ZW4M/Rn82WefoUaN\nGtJtR0dH1KlTBxUqVMCoUaNU1v3www9RWFgoHXNA9TWdn5+Pu3fv4u7du2jXrh2ys7Nx7ty5V44t\nISEBTk5OGDp0qMryYcOGoUqVKkhISFB7zMiRI1Xe8y4uLqhTp45er9+ygAWjDHl5eSE6Oho3b95E\neno6YmNj0bp1axw4cACffvopCgsLATzrBwSAjz76CE5OTir/2rdvD0EQpMtISjVr1nypX2quWbMG\n77//PiwtLWFvby99IQHA/fv3X+n52dnZ4eOPP8bGjRuRk5MD4FlPS0pKCvr06SO94dzc3DBp0iTs\n2rULzs7O8PHxwTfffIOjR4++0n41sbe3R9euXaVfLicnJ8t2LMnnL1s/v+zp06fIyMhQWV6nTh21\ndZV/bLx4+RqAVFRcvnwZwP8uZzZu3LjEmM6cOQNRFPHuu++qvQYHDx4MQRCkH2qFhoaicePGGDly\nJBwcHNClSxf88ssvyMrKkrZXGjn39PRUW6a8fK7svVQeixeLakDzsdNG0x8/cXFxEEVRr5aJS5cu\noU+fPrCzs4ONjY1U9G3fvh0PHjzQOw4PDw8kJiYiMTER+/fvx8WLF3H+/Hl07NjxlfdVpUoVjX8s\nKBUVFaFXr164ePEi1q1bh9q1a0v3KT+bBg4cqPa6qFevnsbPJhcXF7UfHryYt+LiYty8eVPlX3Z2\ntrS+v78/jh49itzcXBw6dAj5+fkICAiAv78/UlJSUFhYiL1796K4uFjvglGf15PyffPuu++qravp\nfZuWlgYXFxe1X+sCz96fOTk50vvCx8cH1tbWUkGYlJQEHx8feHp6wtvbWyqEk5KS4ODgoPYr5Rcp\ncxMWFqaWm6pVq+Lx48dSbgz9Gazp2NrZ2cHZ2VmtJUj5x8jz/dO5ubkYN24cXF1dYWlpKb2mv//+\newCv/h0FPMtR3bp11S7vm5iYoHbt2ionb0p6Pvb29mo932UVexhlztXVFcHBwQgODkbr1q1x8OBB\n/Pe//0XLli2lJu4VK1bA2dlZ4+Nf/BHG83+R6bJ+/Xr06dMH77//PubNm4caNWrA3NwcRUVF6Nix\no16N1dr069cP69evx5o1azBo0CCsWLECoiiif//+Kuv9+OOPGDhwILZu3YoDBw5g2bJliIyMxIQJ\nEzB9+vRX3v/zBg4ciE6dOmHIkCEwMzNDUFCQ1nVLKra1nTUzhpfJ8+sQRRGCIGDHjh1ah81QFqP2\n9vb473//iwMHDmD37t3Yv38/xo4di7CwMGzbtg0ffPABgNfPeUnDd4hafsX5quzt7dG5c2ds2LAB\njx49gpWVFVasWIH69etrHK7pebm5uWjTpg3y8vIwduxYeHt7w9raGgqFAtOmTVPrWyuJlZVViQXQ\nq+xL12to5MiRSExMxPLly9WG+lIe51mzZuG9997T+HgXFxeV2/rk7cqVK2pfyiEhIYiKigLw7Gzc\n4sWLsX//fhw6dEg665iXl4evv/4av//+O5KSkmBiYgJfX98Sn9/LxGVIFSpUQOvWrVUKRmVvrL+/\nPzZt2gRRFLFv3z589NFHOrenjHncuHEqf1A8T1mcAYb9DNZ2bPU95p9//jm2bt2KYcOGoU2bNnBw\ncICJiQm2bt2KOXPmvNZ31KvQFvebeJ28CSwYy5DmzZvj4MGDuH79OoD/nQlxcHDQ+6/ll7FixQpY\nWFggOTkZ5ubm0vKzZ8++9rY7d+4MR0dHrFixQioY69WrBx8fH7V1PTw8EBoaitDQUDx58gQdOnTA\nzJkzMW7cOKl5+XW0b98e77zzDhITE9G3b98Sz6rY29sDgMZfiaelpUmX8EvyqmPx/f3333j//ffV\nllWoUAFubm46H688M/znn3+q/SHx999/A/jfX8jKM2/Hjx9XucT2ojp16mDnzp2oUaOGxrMrL1Io\nFPD19ZW+rE+fPo2mTZti6tSp2LJli7SeoXPu7u4OQPNr+WUvY/Xv3x8bNmzAmjVrUKdOHVy+fBkz\nZszQ+bg9e/YgMzMT0dHRan8oTZw48aVieNP7ioyMxLJly/DNN99oPCOv/GyytLQs1c8mZ2dntTab\n5wtPPz8/CIKAPXv24PDhw9K+GzZsCEdHR+zZswfJyclo0qRJie/zl6V8PynP3j1P+d56nqenJ3bu\n3ImHDx+qnWX8+++/YWtrq/I6DwgIwLZt2xAfH48bN25Il6nbtm2LuXPnYt26dXj48KFex1qZG4VC\noXdudL0fjTHg9YMHD7Blyxb0798fCxcuVLnvxXYH4OU/dz09PXH27Fk8ffpUpRAsKirC+fPnNZ5N\nLO94SVpmdu/erXH4j7y8POzatQuCIEhnbAIDA2FmZoawsDDk5+erPebhw4coKCh45ViUb5Ln4xFF\nEVOnTn3lbSpVqFABn3/+OQ4cOIBVq1bh4sWLal9k2dnZ0uV3JTMzM6kwef5yw9mzZ6XLQi9LEAQs\nWLAA4eHh+Oabb0pcV/lh+3zPGgCsXr0amZmZeu2vUqVKJQ5LpO2D7flfvAPAsWPHkJiYiLZt2+p1\nRrFr164QBAGRkZEqZ0OVhYS7u7t0Cbpnz54wNTXF5MmTpbYBTYKDgwE8Kzo0/TX//Lihmi7L1K1b\nF+bm5lIuXybnr6NZs2ZwdnZGTEyMyuXY3Nxc/Prrry+1rS5dusDR0RFxcXGIi4uDQqHAF198ofNx\nyvfXi8dt165dr9wf/Cb2lZCQgG+//Rbdu3eXBgt/UYcOHeDk5ITp06drzFleXp7W4WZKYmZmhoCA\nAJV/z/+h4ujoCG9vb2zZsgVHjx6VCiJBEODv74/4+Hj8/fffpf4HdtWqVfHBBx9g48aNKsMAFRQU\nYM6cOWrrd+vWDcXFxWpn6LZv344TJ06ga9euKsuV8YaHh8Pc3BytWrUCALRp0wYmJiYIDw9XWa8k\njRs3hpeXF3799VeNl1WLiope+v2o/LXwm7z0amJiAkEQ1F7TmZmZWLZsmdrn6MvG2K1bN9y5c0ca\n5kdp6dKlyMrK0msUk/KGZxhlZuzYsbh37x66du0KLy8vWFpa4urVq1i1ahUuXLiA/v37Sz1o1atX\nx6JFizB48GDUq1cPwcHBcHV1xZ07d3D69Gls3LgRZ86cUfsBgL569eqF9evXIyAgAMHBwSgsLMSG\nDRuQl5dXKs+1f//+mDdvHkaMGAETExO1L9mkpCQMHToUPXv2RJ06dVCpUiWkpqZi+fLl+OCDD1R6\npurXrw83NzeNH4D6+OSTT/DJJ5/oXK9u3br46KOPsHjxYoiiiEaNGuHEiRPYsGEDatWqpfbhCqhf\njmjRogW2bt2K0NBQtGjRAiYmJmjbti2qVKmicX2lK1euoEOHDvjkk0+kYXWsrKzUhnTRpk6dOhg/\nfjxmzpyJNm3aIDAwEDk5OViyZAkeP36M1atXSx+y1atXx88//4xRo0bB29sb/fr1g6urK65fv45N\nmzYhKioKjRo1go+PD8LDwxEeHo733nsPvXr1grOzMzIzM5Gamort27dL4+INHjwY169fR/v27eHq\n6oq8vDz89ttvePTokdTv9zI5fx0mJiaYNWsW+vbti+bNm2PQoEEwMTFBTEwMHBwckJ6ervcZiQoV\nKiAoKAjz589Hamoq2rVrp7VF5HmtW7dGtWrV8PXXXyM9PV0a2uTf//43vL29NY7v+KpeZV+aXod3\n797FF198AUtLS3To0AH//ve/Ve6vVq0aPvroI1haWiIuLg6fffYZ6tati4EDB6JmzZp48OABzp49\ni4SEBGzYsAFt2rQpcX+vIiAgAD///DMEQVApoAICAqRxFw1xRWb27Nnw8/NDq1atMGrUKGlYHU0n\nAEJCQhAbG4sZM2YgPT0drVu3xsWLF7Fw4UJUq1YN06ZNU1n/vffeg52dHc6cOQN/f3+p19PGxgY+\nPj74448/4OLiorEnV5MVK1YgICAADRs2xMCBA1G/fn08fvwYFy9eREJCAqZPn45+/frp/X5s0KAB\nrK2tsXDhQlhaWsLW1hZVq1Z95bnT9XktWFtbo3379vj3v/8NCwsL+Pj4ICMjA0uWLIGnp6dan2WL\nFi0APBta7PPPP4e5uTm8vb019nQDz34cGB8fj1GjRuHYsWN47733cPz4cURFReHdd99V+xHi6z6f\nMuGN/iabdNq1a5c4atQosVGjRqKjo6NYoUIF0dHRUQwICBCjo6M1PubgwYNit27dRCcnJ9HU1FR0\ncXERAwICxNmzZ6sMi+Pu7q42rIdScnKyqFAo1IZFWLp0qVi/fn3R3NxcdHZ2FocNGybeu3dP6xAU\n+gyr8zxvb29RoVCI7du3V7svLS1NHD58uFivXj3RxsZGtLKyEuvXry+GhYWJ2dnZavvWNlTNi0JC\nQkSFQiHevXu3xPXi4+M1HpObN2+KvXr1Em1sbMRKlSqJnTt3Fs+ePSv6+fmpxaBp2ePHj8VBgwaJ\nVatWFU1MTESFQiEN56CM7Xn9+/cXFQqFmJWVJQYHB4sODg6ipaWl2LZtW/HYsWMq62oawuJFS5cu\nFRs3biyam5uLNjY2Yvv27cWUlBSN6+7atUts166daGtrK5qbm4s1a9YUhw4dqnbstm7dKnbo0EG0\nt7cXzczMRFdXV7Fz587i4sWLpXXWr18vdu3aVXznnXdEMzMzsUqVKqKfn5+4fv16lfj1yXlJQ3Vo\neu7h4eGiQqFQG1IjPj5ebNiwoRRzWFiYNLRIfHy81mP4IuXwQgqFQly1apXGdTS9/06dOiV27NhR\ntLOzE62trUV/f38xJSVF4+tAG3d3d9Hb21vnei+zL02vW1H83zFWKBSiIAhq/158fn/++af4xRdf\niNWrVxdNTU3FqlWriq1atRKnTp0qDcGjz/5Kej2/aPPmzaIgCGKtWrVUll+4cEEUBEE0MzMT8/Ly\n1B73Mp9p0dHRKu9bpf3794stW7YUzc3NxWrVqomhoaHin3/+qfE5PHr0SPzuu+9ET09P6dj069dP\nZZiZ5/Xo0UNUKBTi1KlTVZZPmjRJVCgU4hdffKH9oGiQkZEhDh8+XHR3dxdNTU1FBwcH0cfHR5w4\ncaI0zNjLfAZv27ZNbNKkiTQsjvK18LLvVW2vBU3HPCsrSxw8eLDo4uIimpubiw0bNhSXLVsmxsTE\naMzPzJkzRU9PT7FixYqiQqGQ9q8tn3fu3BFHjhwpvvPOO2LFihXFGjVqiKGhoWqff9oeX9LzKYsE\nUSwvpS9R+RQSEoK4uLg33sD9tvrXv/6F8ePH4/fff0fz5s2NHQ4RkSywh5GoDDBGU3l5V1hYqHa5\nMDc3FwsWLICjo6M0KwgREbGHkahM4IWA0nfp0iV06tQJQUFBcHd3R2ZmJmJjY5GRkYFFixbpHHSb\niOhtwk9xJtkgAAAgAElEQVREIpnTNAcxvT4nJye0aNECK1euxO3bt1GhQgU0bNgQM2fORM+ePY0d\nHhGRrLCHUYf33nsPJ0+eNHYYRERERDr5+vpi7969pb5dFow6CILAy4EyoxzGheSFeZEn5kWemBd5\nUX7Xl4e8GKpu4Y9eqMx5fvJ5kg/mRZ6YF3liXuSJedGOBSMRERG91cLCwowdguyxYKQyJyQkxNgh\nkAbMizwxL/LEvMiL8jI086Idexh1YA8jERERlRXsYST6f4b49Re9PuZFnpgXeWJe5Il50Y4FIxER\nERGViJekdeAlaSIiIioreEmaiIiIyADK+tiLbwILRipz2GMiT8yLPDEv8sS8yMvkyZMBMC8lYcFI\nRERERCViD6MO7GEkIiIq38rTdz17GImIiIjIKFgwUpnDHhN5Yl7kiXmRJ+ZFnpgX7VgwEhER0VuN\nc0nrxh5GHcpTXwMRERGVb+xhJCIiIiKjYMFIZQ57TOSJeZEn5kWemBd5Yl60Y8FIRERERCViD6MO\n7GEkIiKisoI9jEREREQGwLmkdWPBSGUOe0zkiXmRJ+ZFnpgXeeFc0rqxYCQiIiKiErGHUQf2MBIR\nEZVv5em7nj2MRERERGQULBipzGGPiTwxL/LEvMgT8yJPzIt2LBiJiIjorca5pHVjD6MO5amvgYiI\niMo39jASERERkVGwYKQyhz0m8sS8yBPzIk/MizwxL9qxYCQiIiKiErGHUQf2MBIREVFZwR5GIiIi\nIgPgXNK6sWCkMoc9JvLEvMgT8yJPzIu8cC5p3VgwEhEREVGJ2MOoA3sYiYiIyrfy9F3PHkYiIiIi\nMgoWjFTmsMdEnpgXeWJe5Il5kSfmRTsWjERERPRW41zSurGHUYfy1NdARERE5Rt7GImIiIjIKFgw\nUpnDHhN5Yl7kiXmRJ+ZFnpgX7VgwEhEREVGJ2MOoA3sYiYiIqKxgDyMRERGRAXAuad1YMFKZwx4T\neWJe5Il5kSfmRV44l7RuLBiJiIiIqETsYdSBPYxERETlW3n6rmcPIxEREREZBQtGKnPYYyJPzIs8\nMS/yxLzIE/OiHQtGIiIieqtxLmnd2MOoQ3nqayAiIqLyjT2MRERERGQULBipzGGPiTwxL/LEvMgT\n8yJPzIt2LBiJiIiIqETsYdSBPYxERERUVrCHkYiIiMgAOJe0biwYqcxhj4k8MS/yxLzIE/MiL5xL\nWjcWjERERERUIvYw6sAeRiIiovKtPH3Xs4eRiIiIiIyCBSOVOewxkSfmRZ6YF3liXuSJedGOBSMR\nERG91TiXtG7sYdShPPU1EBERUfnGHkYiIiIiMgoWjFTmsMdEnpgXeWJe5Il5kSfmRTsWjERERERU\nIvYw6sAeRiIiIior2MNIREREZACcS1o3FoxU5rDHRJ6YF3liXuSJeZEXziWtGwtGIiIiIioRexh1\nYA8jERFR+VaevuvZw0hERERERsGCkcoc9pjIE/MiT8yLPDEv8sS8aMeCkYiIiN5qnEtaN/Yw6iAI\nAibOmGjsMIjKBCcbJ4wZPsbYYRARvbUM1cNYodS3WA65tXUzdghEZULGngxjh0BERAbAS9JU5pw7\nes7YIZAGzIs8sSdLnpgXeWJetGPBSEREREQlYg+jDoIgYPHRxcYOg6hMyNiTgZ8m/GTsMIiI3loc\nh5GIiIjIADiXtG4sGKnMYa+cPDEv8sSeLHliXuSFc0nrxoKRiIiIiErEHkYd2MNIpD/2MBJRWcS5\npHXjGUYiIiIiKhELRipz2CsnT8yLPLEnS56YF3liXrRjwUhERERvNc4lrRt7GHVgDyOR/tjDSERk\nXOxhJCIiIiKjYMFIZQ575eSJeZEn9mTJE/MiT8yLdiwYiYiIiKhELBipzKnrU9fYIZAGr5uXgoIC\nDBo0CO7u7rCxsUHjxo2xY8cOtfWmTJkChUKBpKQkaVlRURFGjx4NZ2dnODg4oGvXrrhx44bG/fz+\n++9o164dHBwc4OTkhMDAQNy8eVOvbRUVFaFPnz6ws7NDp06dkJOTIz1u2rRpmDNnzmsdA0Pw8/Mz\ndgikAfMiT8yLdiwYiUgWioqK4Orqiv379yM7OxtTp05FYGAgMjIypHUuXbqEtWvXwsXFReWxCxcu\nxIEDB3Dq1CncuHEDdnZ2GD16tMb9PHjwAMOHD0dGRgYyMjJgbW2NAQMG6LWt9evXw8TEBHfv3oWt\nrS2WLFkCAEhLS8PmzZsxZsyY0j4sRPQGcC5p3VgwUpnDXjl5et28WFpaIiwsDK6urgCALl26wMPD\nA8eOHZPWCQ0NxYwZM1CxYkWVx/7111/o0KEDqlSpAjMzMwQGBuKvv/7SuJ+OHTuiR48eqFSpEiws\nLDBq1CgcPHhQr22lp6fD19cXCoUCfn5+uHz5MgDgyy+/xOzZs6FQyO8jlT1Z8sS8yAvnktZNfp9u\nREQAbt26hfPnz6NBgwYAgPj4eJibm6NTp05q67Zv3x7bt29HZmYmHj9+jJUrV6Jz58567Wf//v3w\n8vLSa1teXl5ISkrCkydPkJycDC8vLyQkJMDJyQktWrQohWdNRCRPFYwdANHLYg+jPNX1qYuMPRm6\nV9RDYWEh+vbti5CQENSpUwc5OTmYNGkSEhMTNa7fo0cPbNq0CdWrV4eJiQkaNmyIBQsW6NzPqVOn\n8OOPP2LTpk16batz5844cOAAmjdvjhYtWqB3795o27YtEhMTMWnSJKSkpMDLyws///yz2llQY2FP\nljwxL/LEvGj31p5h3LZtG9q0aQNra2vY2tqiWbNmSE5ONnZYRG+94uJiBAcHw9zcHPPnzwfwrL8o\nODhYulwNQGVg2nHjxiEnJwf37t3Do0eP0K1bN41nIp938eJFdO7cGfPmzUOrVq303lZERAROnjyJ\nX3/9FRERERgxYgT++OMPpKamYt++fSgoKEBUVFRpHQ4iIll4KwvGxYsX47PPPkOzZs2wYcMGxMfH\nIzAwEHl5ecYOjfTAHkZ5Ko28iKKIQYMG4c6dO1i3bh1MTEwAAElJSZg3bx6cnZ3h7OyMq1evIjAw\nEJGRkQCAHTt2YMCAAahcuTJMTU0RGhqKI0eO4N69exr3k5GRgXbt2uGHH35A3759Ve7Td1unT5/G\n4cOHMWTIEJw+fRpNmzYFAPj4+ODUqVOvfSxKC3uy5Il5kSfmRbu37pJ0eno6vvrqK8yaNQtffvml\ntLx9+/ZGjIqIAGDEiBE4e/YsEhMTYWZmJi3fs2cPioqKADwrKps1a4Y5c+ZIZ/4aNmyI2NhY+Pr6\nwsLCAgsXLkT16tVhb2+vto/r168jICAAoaGhGDp0qNr9+mxLFEWMHj0av/zyCwRBgKenJ+bPn4+C\nggLs27cPPj4+pX1oiMiAOJe0bm/dGcaoqChUqFABw4cPN3Yo9IrYwyhPr5uXjIwMLFmyBCdPnkS1\natVgbW0Na2trrF69Gvb29nBycoKTkxOqVq0KExMT2NnZwdLSEgAwZ84cKBQK1KxZE05OTtixYwcS\nEhKkbXt5eWH16tUAgGXLliEtLQ3h4eHSPmxsbKR1dW0LAGJiYuDt7Y3GjRsDALp37w4XFxc4OTnh\n/v37GgtRY2FPljwxL/KiHFaHedFOEA0xQ7WMBQQEIDs7G6Ghofjxxx9x5coVuLu7Y+zYsRg5cqTa\n+oIgYPHRxUaIlKjsydiTgZ8m/GTsMIiI3lqCIMAQpd1bd4bxxo0buHDhAiZMmICJEydi9+7daNeu\nHUJDQzFv3jxjh0d6YA+jPDEv8sSeLHliXuSJedHurethLC4uRk5ODmJjY/HZZ58BeHYKOj09HRER\nESp9jUrRYdFwdHEEAFhUskCNujWky2/KL0nefnO3r567Kqt4ePt/tzMuZWDv3r3SZR3lhy9vG+/2\niRMnZBUPb/O2nG+XxfeL8v/T09NhSG/dJekWLVrgyJEjyM7OhpWVlbR8zpw5+Prrr5GZmYmqVatK\ny3lJmkh/vCRNRGRcvCRdSho0aGCQA0lERERlE+eS1u2tKxi7d+8O4NlYa8/bsWMHatSooXJ2keSJ\nvXLyxLzI0/OXrUg+mBd54VzSur11PYydO3eGv78/hg0bhqysLHh4eCA+Ph67d+9GTEyMscMjIiIi\nkp23rocRAHJycvDdd99h7dq1uH//PurVq4dvv/0Wffr0UVuXPYxE+mMPIxGVRYbq+zMGQz2Xt+4M\nIwBYW1tj/vz50jy1RERERKTdW9fDSGUfe+XkiXmRJ/ZkyRPzIk/Mi3YsGImIiOitxrmkdXsrexhf\nBnsYifTHHkYiIuPiOIxEREREZBQsGKnMYa+cPDEv8sSeLHliXuSJedGOBSMRERERlYg9jDqwh5FI\nf+xhJCIyLvYwEhERERkA55LWjQUjlTnslZMn5kWe2JMlT8yLvHAuad1YMBIRERFRidjDqAN7GIn0\nxx5GIiqLOJe0bjzDSEREREQlYsFIZQ575eSJeZEn9mTJE/MiT8yLdiwYiYiI6K3GuaR1Yw+jDuxh\nJNIfexiJiIyLPYxEREREZBQsGKnMYa+cPDEv8sSeLHliXuSJedGOBSMRERERlYg9jDqwh5FIf+xh\nJCIyLvYwEhERERkA55LWjQUjlTnslZMn5kWe2JMlT8yLvHAuad0qGDuAsiBjT4axQ6Dn3Lp0C+YP\nzY0dBr3g1qVbaNa4mbHDICIiA2APow7laX5JIiIiUleevuvZw0hERERERsGCkcoc9pjIE/MiT8yL\nPDEv8sS8aMeCkYiIiN5qnEtaN/Yw6lCe+hqIiIiofGMPIxEREREZBQtGKnPYYyJPzIs8MS/yxLzI\nE/OiHQtGIiIiIioRexh1YA8jERERlRXsYSQiIiIyAM4lrRsLRipz2GMiT8yLPDEv8sS8yAvnktaN\nBSMRERERlYg9jDqwh5GIiKh8K0/f9YZ6LhVKfYvl0KSZk4wdAlGZ4GTjhDHDxxg7DCIiKmUsGPXg\n1tbN2CHQc84dPYe6PnWNHQa94NzRc7j98Laxw6AX7N27F35+fsYOg17AvMgT86IdexiJiIjorca5\npHVjD6MOgiBg8dHFxg6DqEzI2JOBnyb8ZOwwiIjeWhyHkYiIiIiMggUjlTnnjp4zdgikAfMiTxxX\nTp6YF3liXrRjwUhEREREJdKrh/Hp06cAABMTEwBAZmYmtm7dinr16qFVq1aGjdDI2MNIpD/2MBIR\nGZdRexi7dOmC+fPnAwByc3PRrFkzjB8/Hr6+voiNjS31oIiIiIjeFM4lrZteBWNqair8/f0BAOvX\nr4e1tTVu376NZcuW4V//+pdBAyR6EXvl5Il5kSf2ZMkT8yIvnEtaN70KxtzcXNjZ2QEAdu3ahW7d\nuqFixYrw9/fHxYsXDRogERERERmXXgVjjRo1kJKSgtzcXOzcuRPt2rUDANy7dw+WlpYGDZDoRZzl\nRZ6YF3nirBXyxLzIE/OinV5TA3799dfo168frKys4ObmhjZt2gAA9u/fj4YNGxo0QCIiIiIyLr3O\nMA4bNgyHDx9GVFQUDh48KP1aumbNmvjxxx8NGiDRi9grJ0/MizyxJ0uemBd5Yl600+sMIwD4+PjA\nx8dHZdnHH39c6gERERERvUmcS1o3vc4wiqKIBQsWoEGDBrCwsMDly5cBANOnT8eaNWsMGiDRi9gr\nJ0/MizyxJ0uemBd5UQ6rw7xop1fBOHfuXEydOhVDhgxRWe7i4iKNz0hE9DoKCgowaNAguLu7w8bG\nBo0bN8aOHTvU1psyZQoUCgX27NkjLevUqROsra2lf2ZmZlr7q1euXKmyrpWVFRQKBY4fPw7g2RdH\nxYoVpfttbGyQnp4OACgqKkKfPn1gZ2eHTp06IScnR9rutGnTMGfOnFI8IkRE8qFXwbho0SIsXboU\nX331FSpU+N9V7CZNmuDPP/80WHBEmrBXTp5eNy9FRUVwdXXF/v37kZ2djalTpyIwMBAZGRnSOpcu\nXcLatWvh4uICQRCk5du3b0dOTo70r2XLlggMDNS4n759+6qsu3DhQtSsWRONGzcG8GyWhKCgIOn+\n7OxsuLu7A3g2Dq2JiQnu3r0LW1tbLFmyBACQlpaGzZs3Y8yYMa91DAyBPVnyxLzIE/OinV4F45Ur\nV+Dt7a22vGLFisjLyyv1oIjo7WNpaYmwsDC4uroCeDbDlIeHB44dOyatExoaihkzZqBixYpat5Oe\nno4DBw6gX79+eu03JiZGZV1RFLVOq5Weng5fX18oFAr4+flJ7TlffvklZs+eDYVCr49UIqIyR69P\nNw8PD6Smpqot3759O+rXr1/qQRGVhL1y8lTaebl16xbOnz+PBg0aAADi4+Nhbm6OTp06lfi4uLg4\ntGnTRio8S5KRkaFWXAqCgM2bN8PBwQFeXl749ddfpfu8vLyQlJSEJ0+eIDk5GV5eXkhISICTkxNa\ntGjxis/UsNiTJU/MizwxL9rpVTCOHz8eoaGhWLlyJYqLi3Ho0CGEh4dj4sSJGD9+vKFjNKiOHTtC\noVDgn//8p7FDIaL/V1hYiL59+yIkJAR16tRBTk4OJk2ahLlz5+p8bFxcHEJCQvTaj7K4dHNzk5YF\nBgbi7NmzyMrKwtKlSzFlyhT85z//AQB07twZHh4eaN68Oezs7NC7d29MmTIFM2fOxKRJk+Dr64tR\no0ahsLDwlZ43ERkH55LWTa+CccCAAZg8eTK+++475OXloV+/fli2bBl++eUX9OnTx9AxGszq1atx\n6tQpAFDphyJ5Yw+jPJVWXoqLixEcHAxzc3PpR3Xh4eEIDg5WOWuo6bJxSkoKbt26hZ49e+q1r7i4\nOPTv319lWb169VCtWjUIgoAWLVpgzJgxWLt2rXR/REQETp48iV9//RUREREYMWIE/vjjD6SmpmLf\nvn0oKChAVFTUqzx1g2BPljwxL/LCuaR107vhZsiQIbhy5Qpu3bqFzMxMXLt2DYMGDTJkbAZ1//59\n/OMf/+CvGolkRBRFDBo0CHfu3MG6deukSQKSkpIwb948ODs7w9nZGVevXkVgYCAiIyNVHh8bG4se\nPXroNWXpwYMHkZmZqXdx+aLTp0/j8OHDGDJkCE6fPo2mTZsCeDZmrfIPUSKi8kKvgvHp06d4+vQp\nAKBKlSooLi7GsmXLcPDgQYMGZ0jffPMNvL290bt3b2OHQi+JPYzyVBp5GTFiBM6ePYtNmzbBzMxM\nWr5nzx789ddfOHnyJE6cOAEXFxcsWbIEI0eOlNbJy8tDfHy83pejY2Nj0bNnT1hZWaks37hxI+7f\nvw9RFHHkyBHMmzcPn376qco6oihi9OjR+OWXXyAIAjw9PZGSkoKCggLs27cPNWvWfPWDUMrYkyVP\nzIs8MS/a6VUwdunSRbo0lJubi2bNmmH8+PHw9fVFbGysQQM0hJSUFKxYsQILFiwwdihE9P8yMjKw\nZMkSnDx5EtWqVZPGQVy9ejXs7e3h5OQEJycnVK1aFSYmJrCzs1Mp9jZs2AA7OzuNH/heXl5YvXq1\ndDs/Px/x8fFql6MB4LfffkPt2rVhY2OD/v3747vvvkNwcLDKOjExMfD29paG4unevTtcXFzg5OSE\n+/fvY+jQoaV0VIiI5EEQtY0f8ZwqVapgz549aNiwIeLi4hAREYFTp05h5cqVmD17dpm6/FJQUIDG\njRujR48emDJlCgBAoVDg+++/l24/TxAELD66+E2HSSU4d/QczzLK0Lmj52D+0Bw/TfjJ2KHQc/bu\n3cuzJjLEvMiLIAgQRbFc5EX5XEqbXmcYc3NzYWdnBwDYtWsXunXrhooVK8Lf3x8XL14s9aAMaebM\nmXjy5AkmTZpk7FCIiIhIBjiXtG4VdK8C1KhRAykpKfjkk0+wc+dOaf7oe/fu6dVcLhdXrlzBTz/9\nhOXLlyMvL09l0PH8/Hw8fPgQ1tbWaoPvRodFw9HFEQBgUckCNerWkM5wKX8Zyttv9raSXOLh7bqo\n61MX+5fsV/kLXfmLQ9427m0lucTD237w8/OTVTxv++3w8PAy+35R/r9yClND0euS9OLFixEaGgor\nKyu4ubnh2LFjMDExwdy5c7Fx40YkJSUZNMjSsnfvXgQEBJS4zokTJ1TmoOUlaSL9ZezJ4CVpIiIj\nMuol6WHDhuHw4cOIiorCwYMHpaEuatasiR9//LHUgzKUxo0bY+/evSr/kpOTAQDBwcHYu3evrH7d\nSJpxHEZ5Yl7k6cWzJiQPzIs8MS/a6XVJGng2tpiPj490u7CwEB9//LFBgjIUW1tbtGnTRuN9bm5u\nWu8jIiIiepvpdYZx7ty5WLdunXR74MCBMDc3R506dXDuHM8q0JvFX0jLE/MiT8p+J5IX5kWemBft\n9CoY582bB0fHZz/62L9/P+Lj47Fq1So0btwYX3/9tUEDfBOKi4s1DqlDRERE5R/nktZNr4Lxxo0b\n8PT0BABs3rwZPXv2RO/evREeHo7Dhw8bNECiF7FXTp6YF3liT5Y8MS/ywrmkddOrYLSxscGtW7cA\nALt370bbtm0BABUqVEB+fr7hoiMiIiIio9PrRy/t27fHkCFD0KRJE1y8eBGdOnUCAPz999/w8PAw\naIBEL2KvnDzV9amLjD0Zxg6DXsCeLHliXuSJedFOrzOM8+fPx4cffoisrCysXbsWDg4OAIDU1FR8\n/vnnBg2QiIiIiIxLr4G732YcuFt+OJe0PHEuaXnaWw7mxi2PmBd54VzSuul1hhEAbt68icjISIwY\nMQJZWVkAgJSUFKSlpZV6UERERERvCueS1k2vM4ypqakICAiAp6cn/vzzT5w7dw6enp4ICwvDhQsX\nsGrVqjcRq1HwDCOR/jg1IBGRcRn1DOPXX3+NMWPG4Pjx4zA3N5eWd+zYESkpKaUeFBERERHJh14F\n47FjxxASEqK2vFq1atJwO0RvCsf7kyfmRZ44rpw8MS/yxLxop1fBaGFhgXv37qktP3fuHJycnEo9\nKCIiIiKSD70Kxk8//RSTJ09WGaQ7LS0NEyZMQI8ePQwWHJEm/IW0PDEv8lTWf/FZXjEv8sS8aKdX\nwRgZGYn79++jSpUqePz4MT788EPUqlULlStXxtSpUw0dIxEREZHBcC5p3fQqGG1tbXHgwAFs3LgR\n06dPx5gxY7Bz507s378flSpVMnSMRCrYKydPzIs8sSdLnpgXeeFc0rrpNTUg8Oxn2gEBAQgICDBk\nPEREREQkM3qdYQwJCcGcOXPUls+ePRuDBw8u9aCISsJeOXliXuSJPVnyxLzIE/OinV4F444dO+Dv\n76+2PCAgAFu3bi31oIiIiIhIPvQqGB88eKCxV9HS0lLjcDtEhsReOXliXuSJPVnyxLzIE/OinV4F\nY+3atbFlyxa15du2bUOtWrVKPSgiIiKiN4VzSeum11zSsbGxGD58OMaOHYu2bdsCABITE/Hzzz9j\nwYIFGDhwoMEDNRbOJU2kP84lTURkXIaaS1qvX0n3798f+fn5+PHHHzF9+nQAQPXq1TFnzpxyXSwS\nERERkZ6XpAFg2LBhuHbtGm7evImbN2/i6tWrGD58uCFjI9KIvXLyxLzIE3uy5Il5kSfmRTu9x2EE\ngMuXL+Pvv/+GIAioV68ePD09DRUXEREREcmEXj2M2dnZGDhwINavXw+F4tlJyeLiYvTo0QNRUVGw\ntrY2eKDGIggCJs6YaOwwiMoEJxsnjBk+xthhEBG9tQzVw6hXwThgwAAcOnQIS5YsQYsWLQAAhw4d\nwrBhw9CqVStERUWVemByYagDT0RERPIQHh5ebuaTNlTdolcP46ZNm7B06VL4+vrC1NQUpqam8PPz\nw9KlS7Fhw4ZSD4qoJOwxkSfmRZ6YF3liXuSFc0nrplfBmJeXBwcHB7Xl9vb2yM/PL/WgiIiIiEg+\n9Lok/dFHH8HGxgYrVqyAlZUVACA3Nxf9+vVDdnY2EhMTDR6osfCSNBERUflWnr7rjdrDePr0aXTo\n0AGPHz9Go0aNIIoiTp8+DUtLS+zcuRNeXl6lHphclKcXEREREakrT9/1Ru1h9Pb2xoULFxAZGYmm\nTZvCx8cHkZGRuHjxYrkuFkme2GMiT8yLPDEv8sS8yBPzop3OcRgLCgrg6uqKPXv2YMiQIW8iJiIi\nIqI3hnNJ66bXJel33nkHu3btQv369d9ETLJSnk5TExERUflm1EvSo0ePRkREBAoLC0s9ACIiIiKS\nN70KxpSUFGzcuBHvvPMO2rZti08++UT617VrV0PHSKSCPSbyxLzIE/MiT8yLPDEv2uk1l7SDgwO6\nd++u8T5BEEo1ICIiIiKSF716GN9m7GEkIiKissJQdYteZxiVLl26hDNnzgAA6tWrh5o1a5Z6QHI0\naeYkY4dAVG442ThhzPAxxg6DiEhSnuaSNhS9zjDevXsXAwcOxObNm6FQPGt7LC4uxscff4zo6GiN\n0waWF4IgYPHRxcYOg55z7ug51PWpa+ww6AX65iVjTwZ+mvDTG4iIgGc9WX5+fsYOg17AvMiL8qxc\neciLUX8lPXjwYFy6dAkHDhxAXl4e8vLycODAAaSlpWHw4MGlHhQRERERyYdeZxgtLS2RmJiIli1b\nqiw/fPgw2rZti8ePHxssQGPjGUai0sUzjEQkN+Xp9wpGPcPo6OgIKysrteWWlpZwdHQs9aCIiIiI\nSD70Khh/+OEHjB07FteuXZOWXbt2Df/4xz/www8/GCw4Ik3OHT1n7BBIA+ZFnjiunDwxL/LEvGin\n16+k586di/T0dLi7u6N69eoAgOvXr8PCwgK3b9/G3LlzATw7DXrq1CnDRUtERERUyjiXtG56FYw9\nevTQa2McxJveBP5CWp6YF3kq67/4LK+YF3lRDqnDvGinV8HIsYmIiIiI3l569TASyQl75eSJeZEn\n9mTJE/MiT8yLdiwYiYiIiKhEnEtaB47DSFS6OA4jEZHhGHUcRiIiIqLyir/V0E1rwWhiYoLbt28D\nAAYOHIjs7Ow3FhRRSdgrJ0/MizyxJ0uemBd5mTx5MgDmpSRaC0YLCwvk5OQAAGJiYpCfn//GgiIi\nIp2VIUAAACAASURBVCIi+dA6rE7Lli3RrVs3NGnSBAAwZswYWFhYqKwjiiIEQUBUVJRhoyR6Dsf7\nkyfmRZ44rpw8MS/yxLxop7VgjIuLw6xZs3Dx4kUAwN27d2FqaqoyOLeyYCQiIiKi8kvrJelq1aph\n1qxZ2LBhA1xdXbFq1Sps2bIFmzdvlv4pbxO9SeyVk6c3kZeCggIMGjQI7u7usLGxQePGjbFjxw4A\nwN9//w0fHx/Y29ujcuXKaNWqFVJSUqTHdurUCdbW1tI/MzMzNGzYUON+Vq5cqbKulZUVFAoFjh8/\nLq1z7NgxtGnTBtbW1qhWrRrmzZsHACgqKkKfPn1gZ2eHTp06Sa09ADBt2jTMmTPHEIdGK/ZkyRPz\nIk/Mi3Z6/Uo6PT0djo6Oho6FiKhERUVFcHV1xf79+5GdnY2pU6ciMDAQGRkZqF69OuLj43H37l3c\nv38fffr0Qc+ePaXHbt++HTk5OdK/li1bIjAwUON++vbtq7LuwoULUbNmTTRu3BgAkJWVhU6dOmHE\niBG4d+8eLl26hPbt2wMA1q9fDxMTE9y9exe2trZYsmQJACAtLQ2bN2/GmDFjDHyUiOhlcS5p3fQe\nVmfLli1o3bo1HBwc4OjoCF9fX2zdutWQsRFpxF45eXoTebG0tERYWBhcXV0BAF26dIGHhweOHTsG\nW1tbeHh4QBAEPH36FAqFAs7Ozhq3k56ejgMHDqBfv3567TcmJkZl3dmzZ6Njx44ICgpCxYoVYWVl\nhXfffVfatq+vLxQKBfz8/HD58mUAwJdffonZs2dDoXizo5mxJ0uemBd54VzSuun1ybVs2TJ0794d\ntWrVwowZMzB9+nR4eHigW7duWL58uaFjJCLS6NatWzh//jwaNGggLatcuTIsLCwwc+ZMrF27VuPj\n4uLi0KZNG6nwLElGRoZacfnHH3/Azs4OrVq1QtWqVdG1a1dcvXoVAODl5YWkpCQ8efIEycnJ8PLy\nQkJCApycnNCiRYvXfMZERMahV8E4Y8YMzJ49G9HR0Rg8eDAGDx6MmJgY/Otf/8KMGTMMHWOp2rlz\nJwICAuDs7Axzc3PUqFEDvXv3xpkzZ4wdGumJPYzy9KbzUlhYiL59+yIkJAR16tSRlj948AAPHz5E\nnz590KtXL40zHsTFxSEkJESv/SiLSzc3N2nZ1atXERsbi3nz5uHKlSvw8PBAUFAQAKBz587w8PBA\n8+bNYWdnh969e2PKlCmYOXMmJk2aBF9fX4waNQqFhYWvdwD0xJ4seWJe5Il50U6vgvHKlSvo2LGj\n2vKOHTsiPT29tGMyqPv376NZs2ZYsGABdu/ejYiICPz111/44IMPpDMERCRvxcXFCA4Ohrm5OebP\nn692v6WlJaZPn47z58/j9OnTKvelpKTg1q1bKv2NJYmLi0P//v3Vtt+9e3c0bdoUZmZmCAsLw6FD\nh6QfuERERODkyZP49ddfERERgREjRuCPP/5Aamoq9u3bh4KCAg5HRkRlil4FY40aNbBr1y615bt3\n71b5q7ss6NOnD2bMmIHu3bujdevW+OKLL7B+/Xrk5ORovXxF8sIeRnl6U3kRRRGDBg3CnTt3sG7d\nOpiYmGhc7+nTpyguLoalpaXK8tjYWPTo0UNtuSYHDx5EZmamWnGp7dfVLzp9+jQOHz6MIUOG4PTp\n02jatCkAwMfHB6dOndJrG6+LPVnyxLzIE/OinV4F4/jx4/HVV19h8ODBiI6ORnR0NAYNGoSvvvoK\n48aNM3SMBmdvbw8AWr94iEg+RowYgbNnz2LTpk0wMzOTlicmJuLEiRN4+vQpsrOz8Y9//AN169ZF\nrVq1pHXy8vIQHx+v9+Xo2NhY9OzZE1ZWVirLBwwYgISEBJw8eRKFhYX48ccf0bp1a1hbW0vriKKI\n0aNH45dffoEgCPD09ERKSgoKCgqwb98+1KxZ8/UOBBGVGs4lrZteBeOwYcPw22+/4cyZMxg3bhzG\njRuHc+fOIT4+HsOGDTN0jAbx9OlTFBQU4MKFCxg2bBiqVq2KPn36GDss0gN7GOXpTeQlIyMDS5Ys\nwcmTJ1GtWjVpnMRVq1bhwYMHCAoKQuXKlVG3bl3cuXMHmzZtUnn8hg0bYGdnp/EsgpeXF1avXi3d\nzs/PR3x8vNrlaADw9/fHtGnT0KVLF1StWhWXL1/GqlWrVNaJiYmBt7e3NBRP9+7d4eLiAicnJ9y/\nfx9Dhw4thSOiG3uy5Il5kRfOJa2bIGrqCH8L+Pj44NixYwAANzc3bN26FfXr11dbTxAELD66+E2H\nRyU4d/QcL0vL0P+1d+dxVdX5/8Bf5yoICAgpICAi4C6oCDlp7pOm5JKaS5qKmlt9zZrMMiaFHFya\nEXNpcsnUxiWdxFzGVFJBEZVwRQwtA1FUFGUUcGH7/P7oxx2vcBeNy/3ce1/Px4NHnOWe8768vfHm\nc97nfAzNy+X9lxE9I7oaIiLg91+AvMwmH+ZFLoqiQAhhEXkpfy9VflxrLRjT09ORn5+PS5cu4R//\n+AdycnKQmJhYoSeTBSNR1WLBSESyMVaRZQosGI3o7t27aNSoEYYPH44vv/xSY5uiKHjhlRdQz+v3\nmW7sHe3h08xHPZJSfhmOy1zmsmHLOSdzsH7FegD/u/xT/hc9l7nMZS6bYllRFBw8eFCaeJ5mufz7\n8qfWrFu3jgWjMZXPQfvk3eAcYZQPL0nLiZek5RRvAZfYLBHzIhdektaveueoklROTg7S09N51yIR\nEZEV4lzS+ukdYSwqKkLnzp3xzTffoFkz8x/VGThwIEJCQhAUFARnZ2dcvHgRixYtws2bN5GcnKzx\nCA6AI4xEVY0jjERExmOsEcaa+nawtbVFRkYGFEWp8pObQocOHbBlyxYsXLgQRUVF8PHxQffu3TFz\n5kyD5pUlIiIisjYGXZIePXo0Vq1aZexYqsWMGTOQkpKCvLw8FBYWIj09HV9++SWLRTPC5zDKiXmR\n0+ON8SQP5kVOzIt2ekcYAeD+/ftYv3494uLiEBISop71QAgBRVGwZMkSowZJRERERKZj0F3ST94x\nVH55urxgLL8V3RKxh5GoarGHkYjIeEzWwwhwiJaIiIgsV2RkJOeT1uOpHquTm5uL48eP4+HDh8aK\nh0gv9srJiXmRE//glxPzIhfOJa2fQQVjfn4+hgwZAnd3d3Ts2BHXrl0DAEyePJkVOREREZGFM6hg\n/PDDD5GdnY2TJ0/C3t5evb5v376IjY01WnBEleEsL3JiXuRk7rNWWCrmRU7Mi3YG9TDu2LEDsbGx\naNu2rcbzGJs3b47ffvvNaMERERERkekZNMKYl5eHunXrVlifn5+PGjVqVHlQRLqwV05OzIuc2JMl\nJ+ZFTsyLdgYVjKGhodixY0eF9StXrkTHjh2rPCgiIiKi6sK5pPUz6DmMSUlJePnllzFs2DCsX78e\nEyZMwLlz55CcnIxDhw4hJCSkOmI1CT6Hkahq8TmMRETGY6znMBo0wtixY0ckJSWhqKgIAQEB2L9/\nP7y9vXHs2DGLLhaJiIiI6CmewxgUFIRvvvkGaWlpOH/+PNavX4+goCBjxkZUKfbKyYl5kRN7suTE\nvMiJedHOoLukAeDBgwfYuHEjfv75ZwBAixYtMGLECI3H7BARERGR5TGoh/HkyZPo27cvHjx4gKCg\nIAghkJaWhlq1amHXrl0WfVmaPYxEVYs9jERExmPSHsaJEyeiU6dOuHr1Kg4dOoTDhw/jypUr6NKl\nCyZNmlTlQRERERFVF85ap59BBWNaWhpmz56N2rVrq9fVrl0bs2bNwrlz54wWHFFl2CsnJ+ZFTuzJ\nkhPzIhfOJa2fQQVjs2bN1PNHP+769eto1ozTgRERERFZMq09jHfu3FF/f/ToUUyfPh2zZs1Chw4d\n1Ouio6Mxf/589O3bt3qiNQH2MBJVLfYwEpFsjNX3ZwrGei9a75KuV69ehXUjR46ssG7AgAEoLS2t\n2qiIiIiISBpaC8YDBw5UZxxEBruQcgHNQtkKIRvmRU7x8fHo1q2bqcOgJzAvcmJetNNaMPIHRkRE\nRNaAc0nrZ9BzGAHg0aNHSEtLw82bN1FWVqaxLSwszCjByUBRFHy84GNTh0FkMdyd3TFt8jRTh0FE\nZJGM1cNoUMF44MABjBw5Ejk5OZVuf7KAtCSW1AhLREREls2kD+6eMmUKXnnlFWRkZKCwsBD379/X\n+CKqTnxOlpyYFzkxL3JiXuTEvGhn0FzS165dw8cffwxfX19jx0NEREREkjHokvTQoUPRv39/vPHG\nG9URk1R4SZqIiIjMhUl7GPPy8vD666+jefPmCAoKgo2Njcb20aNHV3lgsmDBSEREZNkiIyMtZj5p\nkxaMW7ZsQXh4OB4+fAgHBwcoiqKxPT8/v8oDkwULRvnwOVlyYl7kxLzIiXmRS/nvekvIi0lvepk+\nfTreeust5Ofno6CgAPn5+RpfRERERGS5DBphdHZ2xqlTpxAQEFAdMUmFI4xERESWzZJ+15t0hHHQ\noEGIi4ur8pMTERERkfwMeqxOQEAAIiIicPjwYbRu3brCTS9/+ctfjBIcUWUsocfEEjEvcmJe5MS8\nyIl50c6ggnH16tVwcnLCkSNHkJSUVGE7C0YiIiIyV5xLWj+D55K2VpbU10BERESWzaQ9jERERERk\nvQy6JD116tQKz1583JIlS6osIBlFfBZh6hDoMZcvXYZvAKeplA3zIqfqyIu7szumTZ5m1HNYGvbK\nyYl50c6ggjE1NVWjYCwqKkJ6ejpKS0sRHBxstOBk4ftn/hKUycM6D+EbypzIhnmRU3Xk5fL+y0Y9\nPhGZnkEFY3x8fIV1Dx8+xLhx49ClS5eqjolIp2ahzUwdAlWCeZET8yInjmLJiXnR7pl7GO3s7BAR\nEYHo6OiqjIeIiIioWlnKPNLG9IduesnNzeXUgFTtLqRcMHUIVAnmRU7Mi5wqu3JHphMVFQWAedHF\noEvSCxcu1OhhFELg2rVr2LBhA8LCwowWHBERERGZnkHPYWzUqJFGwahSqeDm5oYePXpg5syZcHJy\nMmqQpqQoClakrDB1GERE0rq8/zKiZ7A9icyXJT1z2VjvxaARxszMzCo/MRERERGZBz64m8wOe7Lk\nxLzIiXmRE3vl5MS8aGfQCKMQAps3b8b+/ftx8+ZNlJWVqbcpioIdO3YYLUAiIiIiY+Jc0voZ1MP4\nwQcf4PPPP0f37t3h6emp0c+oKArWrFlj1CBNiT2MRES6sYeRSB4m7WH85ptvsHHjRgwZMqTKAyAi\nIiIiuRnUw1hWVmYVUwCSeWBPlpyYFzkxL3Jir5ycmBftDCoYJ0yYgPXr1xs7FiIiIiKSkEGXpO/e\nvYsNGzYgLi4OrVu3ho2NDYDfb4ZRFAVLliwxapBEj+PcuHJiXuTEvMiJcxbLiXnRzqCCMS0tDW3b\ntgUApKenq9eXF4xERERE5ioyMpLzSeth0CXp+Ph49dfBgwfVX+XLRNWJPVlyYl7kJHteli1bhtDQ\nUNjZ2WHs2LHq9ZmZmVCpVHByclJ/RUf/707sRYsWISAgAM7OzvDw8MDYsWORn5+v9Tz379/HW2+9\nBTc3N7i4uKBr164a20+ePIkuXbrAyckJ9evXV185KykpwfDhw+Hq6oo+ffponGPu3LlYtGjRM71v\n9srJhXNJ68cHdxMRkcl4e3vjk08+wbhx4yrdfu/ePeTn5yM/Px8RERHq9QMGDEBKSgru3buH9PR0\nZGVlaRSUT5o4cSL++9//Ij09HXl5efj888/V23Jzc9GnTx9MmTIFd+7cwaVLl9CrVy8AQGxsLGrU\nqIHbt2+jTp06WLlyJQAgIyMDO3fuxLRp06rix0AkPYMuSRPJhD1ZcmJe5CR7XgYOHAgASElJwdWr\nVytsLysrQ40aNSqs9/f319hHpVLB09Oz0nOkp6dj586dyM7OhqOjIwBoPPkjJiYGvXv3xuuvvw4A\nsLGxQfPmzQH8PtLZtWtXqFQqdOvWDampqQCAd955BzExMVCpnm3chb1ycmJetOMIIxERmZy2Bw37\n+vrCx8cH48aNw+3btzW2bdy4EXXq1IGbmxvc3Ny0jvYlJyfD19cXs2bNgpubG1q3bo3Y2Fj19uPH\nj8PV1RUvvvgiPDw80L9/f1y5cgUAEBgYiAMHDuDRo0c4ePAgAgMDsW3bNri7u6NDhw5V9O6J5MeC\nkcyO7D1Z1op5kZO55OXJGyjd3NyQkpKCrKwsnDhxAvn5+Rg5cqTGPiNGjMDdu3dx8eJF/Pzzz1r7\nCa9evYpz587BxcUF169fx7JlyzBmzBhcuPD7z+bKlStYt24dlixZgqysLPj5+alHG8PCwuDn54f2\n7dvD1dUVw4YNw6efforPPvsMERER6Nq1K95++20UFxc/1ftlr5ycmBftrK5g/O677/Dqq6+iYcOG\ncHBwQPPmzfHxxx+joKDA1KEREVmtJ0cYa9eujXbt2kGlUsHd3R3Lli3Dvn37UFhYWOG1jRs3xkcf\nfYRvvvmm0mPb29vDxsYGf/3rX1GzZk106dIF3bt3x969ewEADg4OGDRoEEJCQlCrVi3Mnj0bSUlJ\n6htc5s2bhzNnzmD58uWYN28epkyZguPHj+PEiRNISEhAUVERvv766yr+iVB14lzS+lldwbhw4ULY\n2Nhg/vz52LNnD6ZMmYIvv/wSPXv2NMrci1T1ZO/JslbMi5zMJS+GPqKtrKys0vXFxcVwcHCodFvr\n1q0BVCxKy89Zvl2f1NRUHD16FBMmTEBqaipCQkIAAKGhoTh79qxBxyjHXjm5lD9Sh3nRzuoKxl27\nduHf//43RowYgS5dumDatGlYsmQJjh8/zqFoIqJqVlpaiocPH6KkpASlpaV49OgRSkpKkJycjAsX\nLqCsrAy3b9/GO++8g+7du8PJyQkA8NVXX+HWrVsAgPPnz2P+/PkYPHhwpefo2rUrGjZsiHnz5qGk\npARHjhxBfHw8Xn75ZQDA2LFjsW3bNpw5cwbFxcWYM2cOOnfurD4X8HuxOXXqVCxduhSKosDf3x+J\niYkoKipCQkICAgICjPyTIjItqysY69atW2FdaGgoAODatWvVHQ49A3PpybI2zIucZM/LnDlz4ODg\ngAULFmD9+vWwt7fH3Llz8dtvv6FPnz5wdnZGUFAQ7O3tsWnTJvXrkpKSEBQUBCcnJwwcOBCjR4/G\ne++9p94eGBio3r9mzZrYvn07du/eDRcXF0yaNAn/+te/0LRpUwBA9+7dMXfuXLzyyivw8PDAb7/9\nho0bN2rEuXbtWgQFBanvrh40aBC8vLzg7u6OvLw8TJw48aneNwco5MS8aKcIXofF8uXL8dZbbyEl\nJQXt2rXT2KYoClakrDBRZFSZCykXzOYymzVhXuRUHXm5vP8yomdofwYiVRQfH8/LnxKyhLwoimKU\nFjurLxizs7MRHByM4OBgdQP041gwEhHpxoKRSB7GKhit+sHdBQUFGDBgAGxtbbFmzRqt+62ZvQb1\nvOoBAOwd7eHTzEf9F3v55R4uc5nLXLbWZTvYAfjf5bzyERouc9lcliMjI9XrZYjnaZbLv8/MzIQx\nWe0I44MHDxAWFobU1FQkJCSgVatWle7HEUb58NKnnJgXOfGStJws4dKnJSkflbOEvHCEsQoVFxfj\ntddew8mTJxEXF6e1WCQiIiIiKywYy8rKMHLkSMTHx2PXrl1o3769qUOip8RRLDkxL3JiXuRk7qNY\nlop50c7qCsa3334b3333HSIiImBvb49jx46pt/n4+MDb29uE0RERERHJx+qew7hnzx4oioLo6Gh0\n7NhR42v16tWmDo8MIPtz5awV8yIn5kVOfN6fnJgX7axuhDEjI8PUIRAREZFEOJe0flZ7l7SheJc0\nEZFuvEuaSB7Gukva6i5JExEREdHTYcFIZoc9WXJiXuTEvMiJvXJyYl60Y8FIRERERDqxh1EP9jAS\nEenGHkYiebCHkYiIiMgIIiMjTR2C9FgwktlhT5acmBc5MS9yYq+cXKKiogAwL7qwYCQiIiIindjD\nqAd7GImIdGMPI5k7Y/X9mQJ7GImIiIjIJFgwktlhT5acmBc5MS9yYq+cnJgX7VgwEhERkVXjXNL6\nsYdRD/YwEhHpxh5GInmwh5GIiIiITIIFI5kd9mTJiXmRE/MiJ/bKyYl50Y4FIxERERHpxB5GPdjD\nSESkG3sYieTBHkYiIiIiI+Bc0vqxYCSzw54sOTEvcmJe5MReOblwLmn9WDASERERkU7sYdSDPYxE\nRLqxh5HMHeeS1q9mlR/RAl3ef9nUIRARScvd2d3UIRCRkXGEUQ9L+qvDUsTHx6Nbt26mDoOewLzI\niXmRE/Mil/Lf9ZaQF94lTURERGQEnEtaP44w6sERRiIiIjIXHGEkIiIiIpNgwUhmh8/JkhPzIifm\nRU7Mi5yYF+1YMBIRERGRTuxh1IM9jERERGQu2MNIREREZAScS1o/FoxkdthjIifmRU7Mi5yYF7lw\nLmn9WDASERERkU7sYdSDPYxERESWzZJ+17OHkYiIiIhMggUjmR32mMiJeZET8yIn5kVOzIt2NU0d\ngDmI+CzC1CHQYy5fuoy45DhTh0FPYF7kxLzIiXmRy8uvvGzqEKTHHkY9FEXBipQVpg6DiIiIjOTy\n/suInhFt6jCqBHsYiYiIiMgkWDCS2bmQcsHUIVAlmBc5MS9yYl7kxB5G7VgwEhEREZFOLBjJ7DQL\nbWbqEKgSzIucmBc5MS9y6tatm6lDkBYLRiIiIrJqh+IOmToE6bFgJLPD3h85MS9yYl7kxLzIJfHH\nRADsYdSFBSMRERER6cTnMOrB5zASERFZtkmhkziXtB4cYSQiIiIinVgwktlh74+cmBc5MS9yYl7k\nxB5G7VgwEhERkVXr9FInU4cgPfYw6sEeRiIiIsvGuaT14wgjEREREenEgpHMDnt/5MS8yIl5kRPz\nIif2MGrHgpGIiIjoGS1btgyhoaGws7PD2LFjNbbt378fzZs3R+3atdGjRw9kZWVpbP/www9Rr149\n1KtXDx999JHO8xh6LAAaxyopKcHw4cPh6uqKPn36ID8/X71t7ty5WLRokUHvkwUjmR3OwSon5kVO\nzIucmBc5Pctc0t7e3vjkk08wbtw4jfW5ubkYPHgwoqOjkZeXh9DQUAwbNky9fcWKFdi+fTvOnj2L\ns2fPYufOnVixovJ7Jp7mWAA0jhUbG4saNWrg9u3bqFOnDlauXAkAyMjIwM6dOzFt2jSD3icLRiIi\nIrJqf2Qu6YEDB2LAgAGoW7euxvrY2FgEBgZi8ODBsLW1RWRkJM6cOYOLFy8CANatW4fp06fDy8sL\nXl5emD59OtauXVvpOZ7mWAA0jpWZmYmuXbtCpVKhW7du+O233wAA77zzDmJiYqBSGVYKsmAks8Pe\nHzkxL3JiXuTEvMilKuaSfvLO5LS0NLRp00a97ODggMaNGyMtLQ0AcP78eY3trVu3Vm970h85VmBg\nIA4cOIBHjx7h4MGDCAwMxLZt2+Du7o4OHToY/P4sqmAMDw+Hn5/fU78uPj4eKpUKhw49+18YRERE\nZL0URdFYLiwshLOzs8Y6Z2dndQ9hQUEB6tSpo7GtoKCg0mP/kWOFhYXBz88P7du3h6urK4YNG4ZP\nP/0Un332GSIiItC1a1e8/fbbKC4u1vn+LKpgnDVrFr7//ntTh0FGxt4fOTEvcmJe5MS8yOlZehjL\nPTnC6OjoiHv37mmsu3v3LpycnCrdfvfuXTg6OlZ67D96rHnz5uHMmTNYvnw55s2bhylTpuD48eM4\nceIEEhISUFRUhK+//lrn+7OIgvHRo0cAAH9/f40hWSIiIqLq8OQIY6tWrXDmzBn1cmFhIS5duoRW\nrVqpt58+fVq9/cyZMwgMDKz02FV1rNTUVBw9ehQTJkxAamoqQkJCAAChoaHqG2a0qdaC8eLFixg4\ncCA8PDxgb28PX19fDB06FKWlpQCAW7duYfLkyWjQoAHs7OzQokULrFq1SuMYa9euhUqlwuHDhzFk\nyBC4urqqr8FXdkl69uzZaNeuHerUqQM3Nzf8+c9/xvHjx6vnDZNRsPdHTsyLnJgXOTEvcnqWHsbS\n0lI8fPgQJSUlKC0txaNHj1BaWoqBAwfi3LlziI2NxcOHDxEVFYW2bduiadOmAIDRo0cjJiYG165d\nQ3Z2NmJiYhAeHl7pOZ7mWAAqPZYQAlOnTsXSpUuhKAr8/f2RmJiIoqIiJCQkICAgQOf7rNaC8ZVX\nXsH169exfPly7Nu3D/Pnz4ednR2EELh37x46deqEPXv2ICoqCrt370a/fv0wZcoULFu2rMKxRo4c\niYCAAGzduhXz589Xr3+yws/Ozsa7776LHTt2YN26dXB3d0eXLl1w7tw5o79fIiIikt8fmUt6zpw5\ncHBwwIIFC7B+/XrY29sjOjoa9erVw9atWxEREYHnnnsOKSkp+Pbbb9WvmzRpEvr164egoCC0bt0a\n/fr1w8SJE9XbAwMDsWnTJgB4qmMBqHAs4PcBt6CgIAQHBwMABg0aBC8vL7i7uyMvL6/C/k+qtrmk\nc3Nz4e7ujh07dqBv374Vts+ZMwdz587FuXPnNKrciRMnYtu2bcjJyYFKpcLatWsxbtw4vPfee1i4\ncKHGMcLDw5GQkICMjIxKYygtLYUQAoGBgejduzc+//xzAL//RdGjRw/Ex8ejS5cuGq/hXNJERESW\njXNJ61dtI4z16tWDv78/PvzwQ3z11Vf45ZdfNLbv2bMHL7zwAho1aoSSkhL1V69evXD79m2cP39e\nY/+BAwcadN4ff/wR3bt3R7169WBjYwNbW1tcvHhR/ewiIiIiItKtZnWeLC4uDpGRkZg5cyZu374N\nPz8/fPDBB5g8eTJu3ryJS5cuwcbGpsLrFEXB7du3NdZ5enrqPd/JkycRFhaGPn364Ouvv4anpydU\nKhXefPNNPHz40OC418xeg3pev0+3Y+9oD59mPuo73Mr7ULhcfctXLlzBSyNfkiYeLv++/HhPgnjy\nNAAAFuBJREFUlgzxcJmfF5mX+XmRbzk+Ph6nT5/Gu+++q14G/nfntKzL5d9nZmbCmKrtkvSTzpw5\ng2XLlmH16tXYvXs3oqKiULNmTSxevLjS/Zs2bQpHR0f1Jelff/0V/v7+Gvs8eUk6IiICixcvxt27\nd1GjRg31fr6+vggICMCBAwcA8JK0ubmQcoGPpJAQ8yIn5kVOzItcyi9Jx8fH/6FH68jAWJekq3WE\n8XFt2rTBwoULsXr1aqSlpaF3795YunQpfHx84ObmViXnuH//foUpbw4cOIArV67ovRuI5MX/ycqJ\neZET8yIn5kVO5l4sGlO1FYxnz57FtGnTMHz4cAQEBKC0tBRr166FjY0NevTogYCAAGzevBmdO3fG\ne++9h6ZNm6KwsBDp6elITEx8pgdy9+nTB4sXL0Z4eDjCw8Nx8eJF/O1vf4O3t7dRqm8iIiIyP4fi\nDgEzTB2F3KrtphdPT0/4+voiJiYGAwYMwIgRI3Djxg3s2rULwcHBcHZ2RlJSEsLCwrBgwQL07t0b\n48ePx86dO9GjRw+NYz356JzH1z++rVevXliyZAmOHDmCfv36Ye3atfjXv/6Fxo0bVziGtmOSfB7v\n/SF5MC9yYl7kxLzIpSrmkrZ0JuthNBfsYZQPe3/kxLzIiXmRE/Mil0mhkyCEYA+jruOyYNSNBSMR\nEZFlKy8YLYHZP4eRiIiIiMwTC0YyO+z9kRPzIifmRU7Mi5zYw6gdC0YiIiKyan9kLmlrwR5GPdjD\nSEREZNk4l7R+HGEkIiIiIp1YMJLZYe+PnJgXOTEvcmJe5MQeRu1YMBIRERGRTiwYyezwYbdyYl7k\nxLzIiXmRk7k/tNuYWDASERGRVTsUd8jUIUiPBSOZHfb+yIl5kRPzIifmRS6cS1o/FoxEREREpBOf\nw6gHn8NIRERk2TiXtH4cYSQiIiIinVgwktlh74+cmBc5MS9yYl7kxB5G7VgwEhERkVXjXNL6sYdR\nD/YwEhERWTbOJa0fRxiJiIiISCcWjGR22PsjJ+ZFTsyLnJgXObGHUTsWjERERESkEwtGMjucg1VO\nzIucmBc5MS9y4lzS2rFgJCIiIqvGuaT1q2nqAMzB5f2XTR0CPebypcvwDfA1dRj0BOZFTsyLnJgX\nuTw+lzRHGSvHx+roYazb0+nZ8QMtJ+ZFTsyLnJgXuZT/rreEvBirbmHBqAcLRiIiIstmSb/r+RxG\nIiIiIjIJFoxkdvicLDkxL3JiXuTEvMiJedGOBSMRERFZtdmzZ5s6BOmxh1EPS+prICIiIsvGHkYi\nIiIiMgkWjGR22GMiJ+ZFTsyLnJgXOTEv2rFgJCIiIiKd2MOoB3sYiYiIyFywh5GIiIjICCIjI00d\ngvRYMJLZYY+JnJgXOTEvcmJe5BIVFQWAedGFBSMRERER6cQeRj3Yw0hERGTZLOl3PXsYiYiIiMgk\nWDCS2WGPiZyYFzkxL3JiXuTEvGjHgpGIiIisGueS1o89jHpYUl8DERERWTb2MBIRERGRSbBgJLPD\nHhM5MS9yYl7kxLzIiXnRjgUjEREREenEHkY92MNIRERE5oI9jERERERGwLmk9WPBSGaHPSZyYl7k\nxLzIiXmRC+eS1o8FIxERERHpxB5GPdjDSEREZNks6Xc9exiJiIiIyCRYMJLZYY+JnJgXOTEvcmJe\n5MS8aMeCkYiIiKwa55LWjz2MelhSXwMRERFZNvYwEhEREZFJsGAks8MeEzkxL3JiXuTEvMiJedGO\nBSMRERER6cQeRj3Yw0hERETmgj2MREREREbAuaT1Y8FIZoc9JnJiXuTEvMiJeZEL55LWjwUjmZ3T\np0+bOgSqBPMiJ+ZFTsyLnJgX7Vgwktn573//a+oQqBLMi5yYFzkxL3JiXrRjwUhEREREOrFgJLOT\nmZlp6hCoEsyLnJgXOTEvcmJetONjdfRo27Ytzpw5Y+owiIiIiPTq2rWrUW7eYcFIRERERDrxkjQR\nERER6cSCkYiIiIh0YsFIRERERDqxYNRiz549aN68OZo0aYIFCxaYOhz6/xo1aoTWrVsjODgY7du3\nN3U4VmvcuHHw8PBAUFCQet2dO3fQs2dPNG3aFL169eLzzEygsrxERkaiQYMGCA4ORnBwMPbs2WPC\nCK3PlStX0L17d7Rq1QqBgYFYsmQJAH5eTE1bXvh50Y43vVSitLQUzZo1w48//ghvb288//zz2LRp\nE1q0aGHq0Kyen58fTpw4geeee87UoVi1w4cPw9HREaNHj0ZqaioAYMaMGahXrx5mzJiBBQsWIC8v\nD/PnzzdxpNalsrxERUXByckJf/nLX0wcnXW6ceMGbty4gbZt26KgoAAhISH4/vvvsWbNGn5eTEhb\nXrZs2cLPixYcYaxEcnIyGjdujEaNGsHGxgbDhw/H9u3bTR0W/X/8G8f0OnfuDFdXV411O3bswJgx\nYwAAY8aMwffff2+K0KxaZXkB+Jkxpfr166Nt27YAAEdHR7Ro0QLZ2dn8vJiYtrwA/Lxow4KxEtnZ\n2fDx8VEvN2jQQP0PiUxLURS89NJLCA0NxapVq0wdDj0mJycHHh4eAAAPDw/k5OSYOCIqt3TpUrRp\n0wbjx4/npU8TyszMxKlTp/CnP/2JnxeJlOflhRdeAMDPizYsGCuhKIqpQyAtjhw5glOnTuGHH37A\nF198gcOHD5s6JKqEoij8HEliypQpyMjIwOnTp+Hp6Yn333/f1CFZpYKCAgwePBiLFy+Gk5OTxjZ+\nXkynoKAAr732GhYvXgxHR0d+XnRgwVgJb29vXLlyRb185coVNGjQwIQRUTlPT08AgJubGwYOHIjk\n5GQTR0TlPDw8cOPGDQDA9evX4e7ubuKICADc3d3VBcmbb77Jz4wJFBcXY/DgwRg1ahReffVVAPy8\nyKA8L2+88YY6L/y8aMeCsRKhoaH45ZdfkJmZiaKiImzevBn9+/c3dVhW7/79+8jPzwcAFBYWYt++\nfRp3g5Jp9e/fH+vWrQMArFu3Tv0/YDKt69evq7/ftm0bPzPVTAiB8ePHo2XLlnj33XfV6/l5MS1t\neeHnRTveJa3FDz/8gHfffRelpaUYP348Zs6caeqQrF5GRgYGDhwIACgpKcHIkSOZFxN5/fXXkZCQ\ngNzcXHh4eODTTz/FgAEDMHToUGRlZaFRo0bYsmULXFxcTB2qVXkyL1FRUYiPj8fp06ehKAr8/Pyw\nYsUKde8cGV9iYiK6dOmC1q1bqy87z5s3D+3bt+fnxYQqy8vcuXOxadMmfl60YMFIRERERDrxkjQR\nERER6cSCkYiIiIh0YsFIRERERDqxYCQiIiIinVgwEhEREZFOLBiJiIiISCcWjERWKjMzEyqVCidP\nnqz2c69du7bC9GjWIjc3FyqVCocOHXrmY2zfvh1NmjSBjY0Nxo0bV4XRERFVjgUjkRXo1q0bpk6d\nqrGuYcOGuHHjBtq0aVPt8QwfPhwZGRnVfl5LMX78eAwZMgRZWVlYvHixqcPRa+XKlejevTtcXFyg\nUqmQlZVVYZ+8vDyMGjUKLi4ucHFxwejRo3H37l2NfbKystCvXz84OjrCzc0N06ZNQ3FxscY+qamp\n6Nq1KxwcHNCgQQPMmTOnwrkSEhIQEhICe3t7BAQEYMWKFVX7hoksEAtGIiulUqng7u6OGjVqVPu5\n7ezsUK9evWo/ryXIy8vDnTt30KtXL3h6ej7zSG1RUVEVR6bdgwcP0Lt3b0RFRWndZ8SIETh9+jT2\n7t2LPXv24OTJkxg1apR6e2lpKV555RUUFhYiMTERmzZtwnfffYf3339fvc+9e/fQs2dPeHp6IiUl\nBYsXL8bf//53xMTEqPfJyMhAWFgYOnXqhNOnT2PmzJmYOnUqYmNjjfPmiSyFICKLNmbMGKEoisbX\n5cuXRUZGhlAURZw4cUIIIcTBgweFoijihx9+EMHBwcLe3l507txZXL16Vezfv18EBQUJR0dH0a9f\nP3Hnzh2Nc3z99deiRYsWws7OTjRt2lQsWrRIlJWVaY1pzZo1wtHRUb08e/ZsERgYKDZt2iT8/f2F\nk5OTePXVV0Vubq7O9xYVFSV8fX1FrVq1RP369cXo0aM1ti9YsEAEBAQIe3t7ERQUJNavX6+xPTs7\nW4wYMULUrVtXODg4iLZt24qDBw+qty9fvlwEBAQIW1tb0bhxY7Fq1SqN1yuKIlauXClee+01Ubt2\nbeHv71/hHMnJyaJdu3bCzs5OBAcHi127dglFUURCQoIQQoiioiIxdepU4eXlJWrVqiV8fHzERx99\nVOn7Lc/R41/lx9m6dasIDAxUHyM6Olrjtb6+viIyMlKMHTtWuLi4iKFDh1Z6jjFjxoi+ffuKzz//\nXHh7ewtXV1cxduxYcf/+fS1ZMNxPP/2k/vf3uPPnzwtFUURSUpJ6XWJiolAURVy8eFEIIcTu3buF\nSqUSV69eVe+zfv16YWdnJ/Lz84UQQvzzn/8UderUEQ8fPlTv87e//U14e3url2fMmCGaNm2qcf43\n33xTdOjQ4Q+/PyJLxoKRyMLdvXtXdOzYUYwfP17k5OSInJwcUVpaqrVg/NOf/iQSExPF2bNnRWBg\noOjYsaPo3r27SE5OFikpKcLPz09MmzZNffyVK1cKT09PsXXrVpGZmSl27twp6tevL5YtW6Y1psoK\nRkdHRzFo0CCRmpoqjh49Knx9fcWkSZO0HuO7774Tzs7OYvfu3eLKlSsiJSVFfPHFF+rtH3/8sWje\nvLnYu3evyMzMFBs3bhS1a9cW//nPf4QQQhQUFIjGjRuLTp06icTERJGRkSG2b9+uLhhjY2OFjY2N\n+OKLL8Qvv/wili5dKmxsbMTOnTvV51AURTRo0EBs2LBBXLp0ScycOVPY2tqKrKwsIYQQ+fn5ws3N\nTQwdOlSkpaWJvXv3iubNm2sUev/4xz+Ej4+POHz4sLhy5YpISkoSa9eurfQ9FxUVqYurbdu2iZyc\nHFFUVCRSUlJEjRo1RGRkpPjll1/Ehg0bhKOjo1i6dKn6tb6+vsLZ2Vn8/e9/F5cuXRK//vprpecY\nM2aMqFOnjpg4caJIT08X+/btEy4uLmLevHnqfaKjo4Wjo6POr8TExArH1lYwrl69Wjg5OWmsKysr\nE46OjuqfxSeffCICAwM19rl586ZQFEXEx8cLIYQYNWqU6Nu3r8Y+ycnJQlEUkZmZKYQQonPnzuL/\n/u//NPbZsmWLsLGxESUlJZX+TIiIBSORVejWrZuYOnWqxjptBeO+ffvU+yxbtkwoiiJOnTqlXhcZ\nGanxi9vHx6fCqNqiRYtEy5YttcZTWcFoZ2cn7t27p14XHR0tGjdurPUYCxcuFM2aNRPFxcUVthUU\nFAh7e/sKRcu0adNEWFiYEOL3QtfJyUncvn270uOXF9mPCw8PF506dVIvK4oiPv74Y/VySUmJcHBw\nEBs2bBBCCLFixQrh4uIiCgsL1fusX79eo2B85513xJ///Get7/NJt27d0ni9EEKMGDGiwjEiIyNF\ngwYN1Mu+vr6if//+eo8/ZswY0bBhQ40R4gkTJoiXXnpJvXznzh1x6dIlnV8PHjyocGxtBWN0dLTw\n9/evsL+/v7+YP3++OoYn32NZWZmoWbOm+Pbbb4UQQvTs2bNCzi5fviwURRHHjh0TQgjRtGlTMWfO\nHI19EhIShKIo4saNG3p/PkTWqqapL4kTkVxat26t/t7d3R0AEBQUpLHu5s2bAIBbt27h6tWrmDhx\nIiZPnqzep6Sk5KnP6+vrq9GP5+npqT5PZYYOHYolS5bAz88PL7/8Mnr37o3+/fvD1tYW58+fx8OH\nD/Hyyy9DURT1a4qLi+Hn5wcAOHXqFNq0aYPnnnuu0uOnp6fjzTff1Fj34osvYseOHRrrHv951ahR\nA25ubuq4f/75Z7Rp0wYODg7qfV544QWN14eHh6Nnz55o2rQpevXqhbCwMPTp00cjbn3S09PRt2/f\nCrFGRUWhoKAAjo6OUBQFoaGhBh2vZcuWGuf39PTE8ePH1cuurq5wdXU1OL6qIoTQuf1pfmZE9HRY\nMBKRBhsbG/X35b+AH78xRlEUlJWVAYD6vytWrEDHjh2r7LxPnqcyDRo0wIULF7B//378+OOPeP/9\n9xEVFYXjx4+rX7dr1y40bNhQ63n0FSCVebIo0Re3vnMEBwcjMzMTe/fuxf79+zFmzBi0adMGcXFx\nT1UAaTvP48eoXbu2QceqWVPzV8OT72nu3LmYN2+ezmPs2bMHL774okHnq1+/Pm7duqWxTgiBmzdv\non79+up9kpKSNPbJzc1FaWmpxj43btzQ2CcnJ0e9Tdc+NWvW5I1YRDrwLmkiK2Bra/tMo376eHh4\nwMvLC7/++iv8/f0rfBlbrVq1EBYWhpiYGPz0009IS0tDUlISWrVqhVq1aiEzM7NCTD4+PgCAdu3a\n4ezZs7h9+3alx27RogUSExM11iUmJqJVq1YGx9eyZUukpqbi/v376nXHjh2rsJ+joyMGDx6Mf/7z\nn/jPf/6DAwcO4NKlSwafp0WLFjhy5EiFWH18fAwuEh+nr1CdMmUKzpw5o/MrJCTE4PN16NABBQUF\nOHr0qHrd0aNHUVhYqP5DpGPHjvj555+RnZ2t3icuLg61atVSn6tDhw44fPgwHj16pLGPt7c3fH19\n1fvExcVpnD8uLg7PP/+8SZ4YQGQuOMJIZAUaNWqE5ORkXL58GbVr10bdunWr7NhRUVGYOnUqXFxc\n0KdPHxQXF+PkyZO4du0aPvrooyo7z5PWrl2L0tJStG/fHo6Ojti8eTNsbW3RpEkTODo6Yvr06Zg+\nfTqEEOjcuTMKCgpw7Ngx1KhRAxMmTMCIESMwf/58DBgwAPPnz4eXlxfOnTsHZ2dndOvWDR988AGG\nDBmCkJAQ9OzZE3v27MHGjRuxbds2g2McMWIEIiIiMG7cOMyaNQvZ2dmIjo7W2CcmJgZeXl5o06YN\nbGxssGHDBtSpUwcNGjQw+Dzvv/8+nn/+eURFReH111/HTz/9hJiYGL2jgNroGxV92kvSN27cwI0b\nN3Dx4kUAQFpaGu7cuQNfX1+4urqiRYsW6N27NyZNmoSVK1dCCIFJkyahX79+aNKkCQCgV69eaNWq\nFUaPHo2FCxciNzcXM2bMwMSJE+Ho6Ajg9593VFQUwsPD8de//hUXLlzAggULEBkZqY5l8uTJWLZs\nGd577z1MnDgRR44cwbp16/Dtt98+5U+JyMqYrn2SiKrLxYsXRYcOHYSDg4NQqVTqx+qoVCqNm15U\nKpXGTSD//ve/hUql0jjW8uXLhZubm8a6TZs2qR8d4+rqKjp37iw2b96sNZ41a9Zo3BUbGRkpgoKC\ndO7zpO+//1506NBBuLi4iNq1a4v27dur74Aut3TpUtGyZUtRq1Yt4ebmJnr16iV+/PFH9farV6+K\nYcOGCRcXF+Hg4CDatWuncTPJ8uXLRePGjYWNjY1o0qSJ+OqrrzSOryiK2Lp1q8a6Ro0aiYULF6qX\njx8/Ltq1aydq1aol2rZtK3bu3ClUKpX6PKtWrRLt2rUTTk5OwtnZWXTr1k0cPXpU6/u+deuWxuvL\nxcbGiqCgIGFraysaNmwo5s6dqzMubcLDw0W/fv001lWWn6cxe/Zs9WOAVCqV+r/r1q1T75OXlyfe\neOMN4ezsLJydncWoUaPE3bt3NY6TlZUl+vbtKxwcHETdunXFtGnTRFFRkcY+qampokuXLsLOzk54\neXmJTz/9tEI8CQkJ6pz4+/uLFStWPPN7I7IWihDP0MRDRERERFaDPYxEREREpBMLRiIiIiLSiQUj\nEREREenEgpGIiIiIdGLBSEREREQ6sWAkIiIiIp1YMBIRERGRTiwYiYiIiEin/wdr/eoLv1plMAAA\nAABJRU5ErkJggg==\n",
+ "text/plain": [
+ ""
+ ]
+ },
"metadata": {},
- "outputs": [],
- "prompt_number": 20
+ "output_type": "display_data"
},
{
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "from matplotlib import pyplot as plt\n",
- "import numpy as np\n",
- "\n",
- "def plot_results():\n",
- " bar_labels = ['serial', '2', '3', '4', '6']\n",
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
"\n",
- " fig = plt.figure(figsize=(10,8))\n",
+ "Python version : 3.4.1\n",
+ "compiler : GCC 4.2.1 (Apple Inc. build 5577)\n",
"\n",
- " # plot bars\n",
- " y_pos = np.arange(len(benchmarks))\n",
- " plt.yticks(y_pos, bar_labels, fontsize=16)\n",
- " bars = plt.barh(y_pos, benchmarks,\n",
- " align='center', alpha=0.4, color='g')\n",
+ "system : Darwin\n",
+ "release : 13.2.0\n",
+ "machine : x86_64\n",
+ "processor : i386\n",
+ "CPU count : 4\n",
+ "interpreter: 64bit\n",
"\n",
- " # annotation and labels\n",
- " \n",
- " for ba,be in zip(bars, benchmarks):\n",
- " plt.text(ba.get_width() + 2, ba.get_y() + ba.get_height()/2,\n",
- " '{0:.2%}'.format(benchmarks[0]/be), \n",
- " ha='center', va='bottom', fontsize=12)\n",
- " \n",
- " plt.xlabel('time in seconds for n=%s' %n, fontsize=14)\n",
- " plt.ylabel('number of processes', fontsize=14)\n",
- " t = plt.title('Serial vs. Multiprocessing via Parzen-window estimation', fontsize=18)\n",
- " plt.ylim([-1,len(benchmarks)+0.5])\n",
- " plt.xlim([0,max(benchmarks)*1.1])\n",
- " plt.vlines(benchmarks[0], -1, len(benchmarks)+0.5, linestyles='dashed')\n",
- " plt.grid()\n",
- "\n",
- " plt.show()"
- ],
- "language": "python",
- "metadata": {},
- "outputs": [],
- "prompt_number": 25
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "
\n",
- "
"
- ]
- },
- {
- "cell_type": "heading",
- "level": 1,
- "metadata": {},
- "source": [
- "Results"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "[[back to top](#Sections)]"
- ]
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [
- "plot_results()\n",
- "print_sysinfo()"
- ],
- "language": "python",
- "metadata": {},
- "outputs": [
- {
- "metadata": {},
- "output_type": "display_data",
- "png": "iVBORw0KGgoAAAANSUhEUgAAAowAAAIACAYAAAAIQT11AAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAIABJREFUeJzs3Xl8TPf6B/DPmZBVElmERGWzFQlFaFGyqL3V2kKqIfYt\nqm7Rlt4mVAVxKbXUls3FrSD2NRJL0LpibWuXxBZLbElIJJHz+8NvzjVmJjPImJP4vF8vr3bOnDnn\nmfPM8uScZ75fQRRFEUREREREWiiMHQARERERyRsLRiIiIiIqEQtGIiIiIioRC0YiIiIiKhELRiIi\nIiIqEQtGIiIiIioRC0YCAOzduxcKhQKxsbGvvA2FQoEBAwaUYlRln5+fHzw8PPRePyQkBAoF35a6\npKenQ6FQYPLkycYORW/u7u7w9/c3dhikhaE+v8ria7U0lYXnHxMTA4VCgX379hk7FFnjN5MMXb58\nGUOHDsW7774LKysr2Nvbo379+ggJCcHevXsNtl9BECAIwmtvQ+6URZlCoUBqaqrGdebMmSOt8zpF\nNKB+TGJiYjB37lyt65aFYygXZelYGSq37u7u0mtVoVDAzMwMHh4eGDJkCK5du1bq+yvPDPl6Kkuv\n1ZeVnp6O8PBwnDx5Uus6xn7+e/fuxeTJk/Hw4UO1+5TvTWPHKHcVjB0AqTp69Ch8fX1hZmaGfv36\noUGDBsjLy8P58+exa9cu2NjYwM/Pr9T36+vri7y8PFSo8Pa8JMzNzREdHY2mTZuq3RcdHQ1zc3Pk\n5+eX+odITEwMMjIyMGbMGLX7li5disWLF5fq/sojd3d35Ofnw8TExNih6O38+fMG+0KqUaMGIiIi\nAAA5OTlITk5GVFQUtm3bhlOnTsHBwcEg+y1PytrrSU7S09MxZcoUeHp6olGjRir3yeW9unfvXkyZ\nMgUDBgyAra2tyn3BwcEICgpCxYoVjRRd2fD2VAdlxOTJk5Gfn4/ff/8d3t7eavffunWrVPeXk5MD\na2trCIIAU1PTUt223HXr1g2rV6/G7NmzVZ77f//7X/z555/4/PPPsWrVKoPsW1vhYKiCPTc3F5Uq\nVTLIto2lrL1eDfllZGtri88//1y6PWzYMDg5OWH+/PmIjo7GuHHjXnsfys+K8qqsvZ7kSNvEcXI6\ntppiVCgUsopRrnhJWmYuXLgABwcHjcUiAFStWlVtWWJiItq3bw87OztYWFigUaNGGs9SKXuojh8/\njg4dOqBy5crSX4OaehhFUcRPP/2ENm3awNnZGWZmZnBzc8PIkSNx7969V3p+Z86cgUKhwNdff63x\n/qCgIJiZmeHu3bsAgKtXr2LgwIFwc3ODubk5qlatilatWiEuLu6V9v+8AQMG4P79+9iwYYPK8ujo\naDg5OeHjjz9We4yy12X//v1q9+nTr+ju7o79+/dLfT3Kf8rtaephVC7LyspCv3794OjoiEqVKuGj\njz7C8ePHVdZ9vl/ot99+Q9OmTWFpaYnRo0dL6yxbtgxNmjSBpaUlKleujA4dOuDgwYMa401OTkaX\nLl3g4OAACwsL1KxZE4MHD5byo/Tbb7/hww8/hI2NDaysrPDBBx9g3bp1atvbunUrfH19UaVKFVha\nWsLNzQ09evTAhQsXpHX0ybmmvqjnl23ZsgXNmjWDhYUFXFxcMGHCBDx9+lQtnnXr1qFRo0awsLCA\nm5sbpkyZgsTERL1aEb755hsoFAqcPn1a7b6HDx/CwsIC3bp1k5Zp6mHctWsXevfuDU9PT1haWsLO\nzg4dOnTQ+Pp6We3btwcAXLp06aX3pXwtp6WloWfPnrC3t5fOyrx4Cfz5fy8+v6NHj6Jbt26oUqUK\nzM3N8e6772LatGlquVDuLzMzE0FBQbC3t4eVlRU6duyo8tooyYABA2BhYYEnT55Iyw4fPgyFQgEH\nBweVQmH79u1QKBSIj4+XlmnqYVQuO3z4MHx9fVGpUiU4OjpiyJAhePTokVoMKSkpaNWqFSwtLVGt\nWjWMHj0aubm5GuN99OgRvvvuO9SsWRPm5uZwdnZG//79ceXKFWmdJ0+ewMLCAiEhISqPHTZsGBQK\nBb766iuV5b1794atrS2Ki4t1Hq8LFy4gODhY+mz38PDAhAkT8PjxY5X1dL0fY2JiEBAQAOBZDl58\nLeh6r65duxbvvfceLC0tUatWLSxbtgwAkJGRgZ49e8LBwQE2NjYIDg5WO5Znz57FyJEj0aBBA+mz\nx8fHB8uXL1dZLyQkBFOmTAEAeHh4SDEql2n7XM/KysKoUaNQo0YNmJmZwdXVFaGhoWrff8rHJycn\nY9asWVJO69atWyrfVXLBM4wyU6tWLWzbtg0JCQkqXzbaLFmyBMOHD0fLli3x/fffw8rKCrt27cKI\nESNw6dIlzJw5U1pXEARcuXIFbdu2RWBgIHr16qX2Bnz+zNeTJ08wa9Ys9OzZE926dYOVlRWOHDmC\n5cuXIyUlBampqS991qRevXpo1qwZVq1ahcjISJXiKDs7Gxs3bkTnzp3h4OCAoqIitGvXDjdu3MCo\nUaNQp04dPHz4ECdPnkRKSgr69ev3Uvt+8Xk2btwY7733HqKiohAYGAjg2WWp1atXY9CgQa90RkjX\nJce5c+fiu+++Q1ZWFn7++Wdpeb169XRuo2PHjnBwcMDkyZORmZmJ+fPnw9fXF4cPH0aDBg1U1t2w\nYQOuXLmCkSNHYuTIkbCxsQHwrMiJjIzE+++/j4iICGRnZ2PJkiXw9/fHxo0b0alTJ2kbixcvxogR\nI1CjRg2MGjUKbm5uyMjIwJYtW3D9+nXpMuf333+PadOmoVOnTpg6dSoUCgXWr1+PXr16Yf78+Rg5\nciQAYN++fejatSsaNmyIiRMnonLlyrh+/Tr27NmDS5cuoXbt2i+dc03Hatu2bVi4cCFGjBiBwYMH\nY8OGDZg1axbs7Ozw3XffSev99ttvCAoKQu3atREeHg4TExPExsZi8+bNeuUyJCQEkZGRiIuLQ2Rk\npMp9a9aswZMnT1S+6DX1SMXGxuLBgwcICQnBO++8g2vXrmHZsmVo27YtkpOT8eGHH5YYQ0mUhZaj\no+NL70sQBOTm5sLX1xcffvghIiIicPv2bQDPXsMvFku///475s+fj2rVqknLtm7diu7du6NOnToY\nN24c7O3tcejQIfzwww84ceIE1qxZo7K/R48eoU2bNmjRogUiIiJw+fJlzJ07F59++in+/PNPnT8G\na9u2LWJjY3Hw4EGpgNmzZw8UCgUePHiA48ePo0mTJgCApKQkjQWuppyfOHECn3zyCQYOHIgvvvgC\nycnJWL58ORQKhcof5n/88Qc++ugj2Nra4ttvv4WtrS3+85//aPxjrLCwEB06dMChQ4fQq1cvjB8/\nHufPn8eiRYuwa9cuHD16FNWrV4eZmRlatWqF5ORklccrn1dSUpK0TBRF7N27F23atNF5rFJTUxEQ\nEAB7e3uMGDEC1atXx4kTJzBv3jwcPHgQ+/btQ4UKFfR6P/r6+mLixImYNm0ahg0bhtatWwNQP7mh\n6dhu2bIFv/76K0aNGgV7e3ssW7YMQ4cOhYmJCcLCwtCuXTtERETgyJEjiIqKgrm5OZYuXSo9ft++\nfThw4AC6du0KDw8PPHr0CGvWrMGQIUNw584dfPvttwCA4cOHIycnBwkJCfj555+l90TDhg21HqOH\nDx+iZcuWuHTpEgYNGoQmTZrg2LFjWLRoEZKSknDkyBG1qzYTJ05Efn4+RowYAVNTUyxatAghISGo\nVasWWrZsWWJOygSRZOXw4cOiqampKAiCWLt2bXHAgAHiokWLxDNnzqite+PGDdHMzEzs27ev2n1j\nxowRTUxMxMuXL0vL3NzcREEQxOXLl6utn5ycLAqCIMbGxqosz8/PV1t3+fLloiAI4po1a1SWC4Ig\nDhgwQOdzXLBggSgIgrht2zaV5cuWLRMFQRATEhJEURTFkydPioIgiJGRkTq3+TL69+8vCoIgZmVl\nib/88otoYmIiXrt2TRRFUVy5cqUoCIL4119/ifHx8WrHJDo6WhQEQdy3b5/adn19fUUPD49XWvZi\nbJqW9ejRQ2V5amqqqFAoxI4dO0rL0tLSREEQRFNTU/Hs2bMq6589e1YUBEFs3bq1WFhYKC2/ceOG\nWLlyZdHd3V18+vSpKIqiePXqVdHU1FRs0KCB+PDhQ7U4i4uLpRgEQRAnTZqkts5nn30m2tjYiLm5\nuaIoiuLYsWNFQRDEO3fuaHzuoqh/zpXPc/LkyWrLKlWqJGZkZKis7+XlJTo7O0u3CwsLRRcXF7Fa\ntWrigwcPpOW5ubmip6enxveCJs2aNRNdXFyk46b04YcfilWqVFE5zm5ubqK/v7/Keo8ePVLb5q1b\nt0RHR0exc+fOOvev3G69evXErKws8c6dO+Lly5fFqKgo0dbWVjQ1NRX/+uuvl96Xr6+vKAiC+M9/\n/lPn/tPS0kQnJyexdu3a4r1790RRFMW8vDyxatWqoq+vr9qxmTNnjigIgrh37161/b2Y98jISFEQ\nBHHnzp0647h27Zraa9Hf31/89NNPRRsbG3HmzJnS8iZNmogNGzZUebymzy9BEEQTExPxyJEjKsu7\ndOkiVqxYUeWYtmjRQjQzMxMvXLggLSsoKBCbN2+u9lpdsmSJKAiC+M0336hsd+vWraIgCGJwcLC0\nbOrUqaIgCNJ2MzIypHUEQRBv3boliqIonjp1ShQEQZw9e7bOY9WwYUOxXr160ntTKSEhQRQEQYyJ\niRFFUf/3o7bvD1HU/V69cuWKtPzOnTuiubm5KAiCOGfOHJXtdO/eXTQ1NVU55ppe08XFxaKfn59o\na2ur8v4LCwsTBUFQ+2wQRc2f6xMnThQFQRAXLVqksq7y++v594by8U2aNFHZ5/Xr10UzMzMxKChI\nbZ9lES9Jy8wHH3yA1NRU9O/fH9nZ2YiJicHIkSNRv359+Pr6Ii0tTVp37dq1KCgowMCBA5GVlaXy\n7+OPP0ZxcTESExNVtu/g4PBSQ0eYmZkBAJ4+fYoHDx4gKytL+qv8yJEjr/Qcg4KCYGpqqnaqPi4u\nDg4ODtKlYOUlsKSkJNy5c+eV9lUSQRDw+eefo2LFitLlx+joaDRv3hz169cv9f29rgkTJqjcbtKk\nCdq1a4fExES1y0hdunRB3bp1VZZt3LhR2s7zvZLOzs4YMGAAMjIycOLECQBAfHw8CgsLERYWJp2d\nfJ7ybMHKlSshCAL69eun9hr85JNPkJOTg8OHDwMAKleuDODZ67aoqEjjcyyNnH/22WdwdXVVWebn\n54ebN29Kxyk1NRWZmZkICQlRaYC3srLC8OHD9d5X//79kZmZid27d0vL0tLScOjQIQQFBensSbW0\ntJT+Pzc3F3fv3oVCoUDz5s3xxx9/6B3H2bNnUaVKFTg5OaFmzZoYNGgQnJycsHHjRum1/LL7EgRB\nZ+/jw4cP8fHHH6OoqAhbt26FnZ0dAGD37t24ffs2QkJCcO/ePZXXhfIs9q5du1S2ZWJigi+//FJl\nmfKz5uLFizqPQfXq1VGnTh3prJuyF7xjx47w9fXFnj17AAAPHjzAyZMnpbOQurRo0QLNmjVTi6uo\nqAjp6ekAgNu3b+P333/Hp59+ilq1aknrVaxYEWPHjlXbZkJCAkxMTFTOeANA586d0ahRI+m9Cjw7\ncwpAel5JSUmoUKECwsPDIQiCdPZR+V9dz+v06dM4ffo0goKCkJeXp5Ib5eV0ZW4M/Rn82WefoUaN\nGtJtR0dH1KlTBxUqVMCoUaNU1v3www9RWFgoHXNA9TWdn5+Pu3fv4u7du2jXrh2ys7Nx7ty5V44t\nISEBTk5OGDp0qMryYcOGoUqVKkhISFB7zMiRI1Xe8y4uLqhTp45er9+ygAWjDHl5eSE6Oho3b95E\neno6YmNj0bp1axw4cACffvopCgsLATzrBwSAjz76CE5OTir/2rdvD0EQpMtISjVr1nypX2quWbMG\n77//PiwtLWFvby99IQHA/fv3X+n52dnZ4eOPP8bGjRuRk5MD4FlPS0pKCvr06SO94dzc3DBp0iTs\n2rULzs7O8PHxwTfffIOjR4++0n41sbe3R9euXaVfLicnJ8t2LMnnL1s/v+zp06fIyMhQWV6nTh21\ndZV/bLx4+RqAVFRcvnwZwP8uZzZu3LjEmM6cOQNRFPHuu++qvQYHDx4MQRCkH2qFhoaicePGGDly\nJBwcHNClSxf88ssvyMrKkrZXGjn39PRUW6a8fK7svVQeixeLakDzsdNG0x8/cXFxEEVRr5aJS5cu\noU+fPrCzs4ONjY1U9G3fvh0PHjzQOw4PDw8kJiYiMTER+/fvx8WLF3H+/Hl07NjxlfdVpUoVjX8s\nKBUVFaFXr164ePEi1q1bh9q1a0v3KT+bBg4cqPa6qFevnsbPJhcXF7UfHryYt+LiYty8eVPlX3Z2\ntrS+v78/jh49itzcXBw6dAj5+fkICAiAv78/UlJSUFhYiL1796K4uFjvglGf15PyffPuu++qravp\nfZuWlgYXFxe1X+sCz96fOTk50vvCx8cH1tbWUkGYlJQEHx8feHp6wtvbWyqEk5KS4ODgoPYr5Rcp\ncxMWFqaWm6pVq+Lx48dSbgz9Gazp2NrZ2cHZ2VmtJUj5x8jz/dO5ubkYN24cXF1dYWlpKb2mv//+\newCv/h0FPMtR3bp11S7vm5iYoHbt2ionb0p6Pvb29mo932UVexhlztXVFcHBwQgODkbr1q1x8OBB\n/Pe//0XLli2lJu4VK1bA2dlZ4+Nf/BHG83+R6bJ+/Xr06dMH77//PubNm4caNWrA3NwcRUVF6Nix\no16N1dr069cP69evx5o1azBo0CCsWLECoiiif//+Kuv9+OOPGDhwILZu3YoDBw5g2bJliIyMxIQJ\nEzB9+vRX3v/zBg4ciE6dOmHIkCEwMzNDUFCQ1nVLKra1nTUzhpfJ8+sQRRGCIGDHjh1ah81QFqP2\n9vb473//iwMHDmD37t3Yv38/xo4di7CwMGzbtg0ffPABgNfPeUnDd4hafsX5quzt7dG5c2ds2LAB\njx49gpWVFVasWIH69etrHK7pebm5uWjTpg3y8vIwduxYeHt7w9raGgqFAtOmTVPrWyuJlZVViQXQ\nq+xL12to5MiRSExMxPLly9WG+lIe51mzZuG9997T+HgXFxeV2/rk7cqVK2pfyiEhIYiKigLw7Gzc\n4sWLsX//fhw6dEg665iXl4evv/4av//+O5KSkmBiYgJfX98Sn9/LxGVIFSpUQOvWrVUKRmVvrL+/\nPzZt2gRRFLFv3z589NFHOrenjHncuHEqf1A8T1mcAYb9DNZ2bPU95p9//jm2bt2KYcOGoU2bNnBw\ncICJiQm2bt2KOXPmvNZ31KvQFvebeJ28CSwYy5DmzZvj4MGDuH79OoD/nQlxcHDQ+6/ll7FixQpY\nWFggOTkZ5ubm0vKzZ8++9rY7d+4MR0dHrFixQioY69WrBx8fH7V1PTw8EBoaitDQUDx58gQdOnTA\nzJkzMW7cOKl5+XW0b98e77zzDhITE9G3b98Sz6rY29sDgMZfiaelpUmX8EvyqmPx/f3333j//ffV\nllWoUAFubm46H688M/znn3+q/SHx999/A/jfX8jKM2/Hjx9XucT2ojp16mDnzp2oUaOGxrMrL1Io\nFPD19ZW+rE+fPo2mTZti6tSp2LJli7SeoXPu7u4OQPNr+WUvY/Xv3x8bNmzAmjVrUKdOHVy+fBkz\nZszQ+bg9e/YgMzMT0dHRan8oTZw48aVieNP7ioyMxLJly/DNN99oPCOv/GyytLQs1c8mZ2dntTab\n5wtPPz8/CIKAPXv24PDhw9K+GzZsCEdHR+zZswfJyclo0qRJie/zl6V8PynP3j1P+d56nqenJ3bu\n3ImHDx+qnWX8+++/YWtrq/I6DwgIwLZt2xAfH48bN25Il6nbtm2LuXPnYt26dXj48KFex1qZG4VC\noXdudL0fjTHg9YMHD7Blyxb0798fCxcuVLnvxXYH4OU/dz09PXH27Fk8ffpUpRAsKirC+fPnNZ5N\nLO94SVpmdu/erXH4j7y8POzatQuCIEhnbAIDA2FmZoawsDDk5+erPebhw4coKCh45ViUb5Ln4xFF\nEVOnTn3lbSpVqFABn3/+OQ4cOIBVq1bh4sWLal9k2dnZ0uV3JTMzM6kwef5yw9mzZ6XLQi9LEAQs\nWLAA4eHh+Oabb0pcV/lh+3zPGgCsXr0amZmZeu2vUqVKJQ5LpO2D7flfvAPAsWPHkJiYiLZt2+p1\nRrFr164QBAGRkZEqZ0OVhYS7u7t0Cbpnz54wNTXF5MmTpbYBTYKDgwE8Kzo0/TX//Lihmi7L1K1b\nF+bm5lIuXybnr6NZs2ZwdnZGTEyMyuXY3Nxc/Prrry+1rS5dusDR0RFxcXGIi4uDQqHAF198ofNx\nyvfXi8dt165dr9wf/Cb2lZCQgG+//Rbdu3eXBgt/UYcOHeDk5ITp06drzFleXp7W4WZKYmZmhoCA\nAJV/z/+h4ujoCG9vb2zZsgVHjx6VCiJBEODv74/4+Hj8/fffpf4HdtWqVfHBBx9g48aNKsMAFRQU\nYM6cOWrrd+vWDcXFxWpn6LZv344TJ06ga9euKsuV8YaHh8Pc3BytWrUCALRp0wYmJiYIDw9XWa8k\njRs3hpeXF3799VeNl1WLiope+v2o/LXwm7z0amJiAkEQ1F7TmZmZWLZsmdrn6MvG2K1bN9y5c0ca\n5kdp6dKlyMrK0msUk/KGZxhlZuzYsbh37x66du0KLy8vWFpa4urVq1i1ahUuXLiA/v37Sz1o1atX\nx6JFizB48GDUq1cPwcHBcHV1xZ07d3D69Gls3LgRZ86cUfsBgL569eqF9evXIyAgAMHBwSgsLMSG\nDRuQl5dXKs+1f//+mDdvHkaMGAETExO1L9mkpCQMHToUPXv2RJ06dVCpUiWkpqZi+fLl+OCDD1R6\npurXrw83NzeNH4D6+OSTT/DJJ5/oXK9u3br46KOPsHjxYoiiiEaNGuHEiRPYsGEDatWqpfbhCqhf\njmjRogW2bt2K0NBQtGjRAiYmJmjbti2qVKmicX2lK1euoEOHDvjkk0+kYXWsrKzUhnTRpk6dOhg/\nfjxmzpyJNm3aIDAwEDk5OViyZAkeP36M1atXSx+y1atXx88//4xRo0bB29sb/fr1g6urK65fv45N\nmzYhKioKjRo1go+PD8LDwxEeHo733nsPvXr1grOzMzIzM5Gamort27dL4+INHjwY169fR/v27eHq\n6oq8vDz89ttvePTokdTv9zI5fx0mJiaYNWsW+vbti+bNm2PQoEEwMTFBTEwMHBwckJ6ervcZiQoV\nKiAoKAjz589Hamoq2rVrp7VF5HmtW7dGtWrV8PXXXyM9PV0a2uTf//43vL29NY7v+KpeZV+aXod3\n797FF198AUtLS3To0AH//ve/Ve6vVq0aPvroI1haWiIuLg6fffYZ6tati4EDB6JmzZp48OABzp49\ni4SEBGzYsAFt2rQpcX+vIiAgAD///DMEQVApoAICAqRxFw1xRWb27Nnw8/NDq1atMGrUKGlYHU0n\nAEJCQhAbG4sZM2YgPT0drVu3xsWLF7Fw4UJUq1YN06ZNU1n/vffeg52dHc6cOQN/f3+p19PGxgY+\nPj74448/4OLiorEnV5MVK1YgICAADRs2xMCBA1G/fn08fvwYFy9eREJCAqZPn45+/frp/X5s0KAB\nrK2tsXDhQlhaWsLW1hZVq1Z95bnT9XktWFtbo3379vj3v/8NCwsL+Pj4ICMjA0uWLIGnp6dan2WL\nFi0APBta7PPPP4e5uTm8vb019nQDz34cGB8fj1GjRuHYsWN47733cPz4cURFReHdd99V+xHi6z6f\nMuGN/iabdNq1a5c4atQosVGjRqKjo6NYoUIF0dHRUQwICBCjo6M1PubgwYNit27dRCcnJ9HU1FR0\ncXERAwICxNmzZ6sMi+Pu7q42rIdScnKyqFAo1IZFWLp0qVi/fn3R3NxcdHZ2FocNGybeu3dP6xAU\n+gyr8zxvb29RoVCI7du3V7svLS1NHD58uFivXj3RxsZGtLKyEuvXry+GhYWJ2dnZavvWNlTNi0JC\nQkSFQiHevXu3xPXi4+M1HpObN2+KvXr1Em1sbMRKlSqJnTt3Fs+ePSv6+fmpxaBp2ePHj8VBgwaJ\nVatWFU1MTESFQiEN56CM7Xn9+/cXFQqFmJWVJQYHB4sODg6ipaWl2LZtW/HYsWMq62oawuJFS5cu\nFRs3biyam5uLNjY2Yvv27cWUlBSN6+7atUts166daGtrK5qbm4s1a9YUhw4dqnbstm7dKnbo0EG0\nt7cXzczMRFdXV7Fz587i4sWLpXXWr18vdu3aVXznnXdEMzMzsUqVKqKfn5+4fv16lfj1yXlJQ3Vo\neu7h4eGiQqFQG1IjPj5ebNiwoRRzWFiYNLRIfHy81mP4IuXwQgqFQly1apXGdTS9/06dOiV27NhR\ntLOzE62trUV/f38xJSVF4+tAG3d3d9Hb21vnei+zL02vW1H83zFWKBSiIAhq/158fn/++af4xRdf\niNWrVxdNTU3FqlWriq1atRKnTp0qDcGjz/5Kej2/aPPmzaIgCGKtWrVUll+4cEEUBEE0MzMT8/Ly\n1B73Mp9p0dHRKu9bpf3794stW7YUzc3NxWrVqomhoaHin3/+qfE5PHr0SPzuu+9ET09P6dj069dP\nZZiZ5/Xo0UNUKBTi1KlTVZZPmjRJVCgU4hdffKH9oGiQkZEhDh8+XHR3dxdNTU1FBwcH0cfHR5w4\ncaI0zNjLfAZv27ZNbNKkiTQsjvK18LLvVW2vBU3HPCsrSxw8eLDo4uIimpubiw0bNhSXLVsmxsTE\naMzPzJkzRU9PT7FixYqiQqGQ9q8tn3fu3BFHjhwpvvPOO2LFihXFGjVqiKGhoWqff9oeX9LzKYsE\nUSwvpS9R+RQSEoK4uLg33sD9tvrXv/6F8ePH4/fff0fz5s2NHQ4RkSywh5GoDDBGU3l5V1hYqHa5\nMDc3FwsWLICjo6M0KwgREbGHkahM4IWA0nfp0iV06tQJQUFBcHd3R2ZmJmJjY5GRkYFFixbpHHSb\niOhtwk9xJtkgAAAgAElEQVREIpnTNAcxvT4nJye0aNECK1euxO3bt1GhQgU0bNgQM2fORM+ePY0d\nHhGRrLCHUYf33nsPJ0+eNHYYRERERDr5+vpi7969pb5dFow6CILAy4EyoxzGheSFeZEn5kWemBd5\nUX7Xl4e8GKpu4Y9eqMx5fvJ5kg/mRZ6YF3liXuSJedGOBSMRERG91cLCwowdguyxYKQyJyQkxNgh\nkAbMizwxL/LEvMiL8jI086Idexh1YA8jERERlRXsYST6f4b49Re9PuZFnpgXeWJe5Il50Y4FIxER\nERGViJekdeAlaSIiIioreEmaiIiIyADK+tiLbwILRipz2GMiT8yLPDEv8sS8yMvkyZMBMC8lYcFI\nRERERCViD6MO7GEkIiIq38rTdz17GImIiIjIKFgwUpnDHhN5Yl7kiXmRJ+ZFnpgX7VgwEhER0VuN\nc0nrxh5GHcpTXwMRERGVb+xhJCIiIiKjYMFIZQ57TOSJeZEn5kWemBd5Yl60Y8FIRERERCViD6MO\n7GEkIiKisoI9jEREREQGwLmkdWPBSGUOe0zkiXmRJ+ZFnpgXeeFc0rqxYCQiIiKiErGHUQf2MBIR\nEZVv5em7nj2MRERERGQULBipzGGPiTwxL/LEvMgT8yJPzIt2LBiJiIjorca5pHVjD6MO5amvgYiI\niMo39jASERERkVGwYKQyhz0m8sS8yBPzIk/MizwxL9qxYCQiIiKiErGHUQf2MBIREVFZwR5GIiIi\nIgPgXNK6sWCkMoc9JvLEvMgT8yJPzIu8cC5p3VgwEhEREVGJ2MOoA3sYiYiIyrfy9F3PHkYiIiIi\nMgoWjFTmsMdEnpgXeWJe5Il5kSfmRTsWjERERPRW41zSurGHUYfy1NdARERE5Rt7GImIiIjIKFgw\nUpnDHhN5Yl7kiXmRJ+ZFnpgX7VgwEhEREVGJ2MOoA3sYiYiIqKxgDyMRERGRAXAuad1YMFKZwx4T\neWJe5Il5kSfmRV44l7RuLBiJiIiIqETsYdSBPYxERETlW3n6rmcPIxEREREZBQtGKnPYYyJPzIs8\nMS/yxLzIE/OiHQtGIiIieqtxLmnd2MOoQ3nqayAiIqLyjT2MRERERGQULBipzGGPiTwxL/LEvMgT\n8yJPzIt2LBiJiIiIqETsYdSBPYxERERUVrCHkYiIiMgAOJe0biwYqcxhj4k8MS/yxLzIE/MiL5xL\nWjcWjERERERUIvYw6sAeRiIiovKtPH3Xs4eRiIiIiIyCBSOVOewxkSfmRZ6YF3liXuSJedGOBSMR\nERG91TiXtG7sYdShPPU1EBERUfnGHkYiIiIiMgoWjFTmsMdEnpgXeWJe5Il5kSfmRTsWjERERERU\nIvYw6sAeRiIiIior2MNIREREZACcS1o3FoxU5rDHRJ6YF3liXuSJeZEXziWtGwtGIiIiIioRexh1\nYA8jERFR+VaevuvZw0hERERERsGCkcoc9pjIE/MiT8yLPDEv8sS8aMeCkYiIiN5qnEtaN/Yw6iAI\nAibOmGjsMIjKBCcbJ4wZPsbYYRARvbUM1cNYodS3WA65tXUzdghEZULGngxjh0BERAbAS9JU5pw7\nes7YIZAGzIs8sSdLnpgXeWJetGPBSEREREQlYg+jDoIgYPHRxcYOg6hMyNiTgZ8m/GTsMIiI3loc\nh5GIiIjIADiXtG4sGKnMYa+cPDEv8sSeLHliXuSFc0nrxoKRiIiIiErEHkYd2MNIpD/2MBJRWcS5\npHXjGUYiIiIiKhELRipz2CsnT8yLPLEnS56YF3liXrRjwUhERERvNc4lrRt7GHVgDyOR/tjDSERk\nXOxhJCIiIiKjYMFIZQ575eSJeZEn9mTJE/MiT8yLdiwYiYiIiKhELBipzKnrU9fYIZAGr5uXgoIC\nDBo0CO7u7rCxsUHjxo2xY8cOtfWmTJkChUKBpKQkaVlRURFGjx4NZ2dnODg4oGvXrrhx44bG/fz+\n++9o164dHBwc4OTkhMDAQNy8eVOvbRUVFaFPnz6ws7NDp06dkJOTIz1u2rRpmDNnzmsdA0Pw8/Mz\ndgikAfMiT8yLdiwYiUgWioqK4Orqiv379yM7OxtTp05FYGAgMjIypHUuXbqEtWvXwsXFReWxCxcu\nxIEDB3Dq1CncuHEDdnZ2GD16tMb9PHjwAMOHD0dGRgYyMjJgbW2NAQMG6LWt9evXw8TEBHfv3oWt\nrS2WLFkCAEhLS8PmzZsxZsyY0j4sRPQGcC5p3VgwUpnDXjl5et28WFpaIiwsDK6urgCALl26wMPD\nA8eOHZPWCQ0NxYwZM1CxYkWVx/7111/o0KEDqlSpAjMzMwQGBuKvv/7SuJ+OHTuiR48eqFSpEiws\nLDBq1CgcPHhQr22lp6fD19cXCoUCfn5+uHz5MgDgyy+/xOzZs6FQyO8jlT1Z8sS8yAvnktZNfp9u\nREQAbt26hfPnz6NBgwYAgPj4eJibm6NTp05q67Zv3x7bt29HZmYmHj9+jJUrV6Jz58567Wf//v3w\n8vLSa1teXl5ISkrCkydPkJycDC8vLyQkJMDJyQktWrQohWdNRCRPFYwdANHLYg+jPNX1qYuMPRm6\nV9RDYWEh+vbti5CQENSpUwc5OTmYNGkSEhMTNa7fo0cPbNq0CdWrV4eJiQkaNmyIBQsW6NzPqVOn\n8OOPP2LTpk16batz5844cOAAmjdvjhYtWqB3795o27YtEhMTMWnSJKSkpMDLyws///yz2llQY2FP\nljwxL/LEvGj31p5h3LZtG9q0aQNra2vY2tqiWbNmSE5ONnZYRG+94uJiBAcHw9zcHPPnzwfwrL8o\nODhYulwNQGVg2nHjxiEnJwf37t3Do0eP0K1bN41nIp938eJFdO7cGfPmzUOrVq303lZERAROnjyJ\nX3/9FRERERgxYgT++OMPpKamYt++fSgoKEBUVFRpHQ4iIll4KwvGxYsX47PPPkOzZs2wYcMGxMfH\nIzAwEHl5ecYOjfTAHkZ5Ko28iKKIQYMG4c6dO1i3bh1MTEwAAElJSZg3bx6cnZ3h7OyMq1evIjAw\nEJGRkQCAHTt2YMCAAahcuTJMTU0RGhqKI0eO4N69exr3k5GRgXbt2uGHH35A3759Ve7Td1unT5/G\n4cOHMWTIEJw+fRpNmzYFAPj4+ODUqVOvfSxKC3uy5Il5kSfmRbu37pJ0eno6vvrqK8yaNQtffvml\ntLx9+/ZGjIqIAGDEiBE4e/YsEhMTYWZmJi3fs2cPioqKADwrKps1a4Y5c+ZIZ/4aNmyI2NhY+Pr6\nwsLCAgsXLkT16tVhb2+vto/r168jICAAoaGhGDp0qNr9+mxLFEWMHj0av/zyCwRBgKenJ+bPn4+C\nggLs27cPPj4+pX1oiMiAOJe0bm/dGcaoqChUqFABw4cPN3Yo9IrYwyhPr5uXjIwMLFmyBCdPnkS1\natVgbW0Na2trrF69Gvb29nBycoKTkxOqVq0KExMT2NnZwdLSEgAwZ84cKBQK1KxZE05OTtixYwcS\nEhKkbXt5eWH16tUAgGXLliEtLQ3h4eHSPmxsbKR1dW0LAGJiYuDt7Y3GjRsDALp37w4XFxc4OTnh\n/v37GgtRY2FPljwxL/KiHFaHedFOEA0xQ7WMBQQEIDs7G6Ghofjxxx9x5coVuLu7Y+zYsRg5cqTa\n+oIgYPHRxUaIlKjsydiTgZ8m/GTsMIiI3lqCIMAQpd1bd4bxxo0buHDhAiZMmICJEydi9+7daNeu\nHUJDQzFv3jxjh0d6YA+jPDEv8sSeLHliXuSJedHurethLC4uRk5ODmJjY/HZZ58BeHYKOj09HRER\nESp9jUrRYdFwdHEEAFhUskCNujWky2/KL0nefnO3r567Kqt4ePt/tzMuZWDv3r3SZR3lhy9vG+/2\niRMnZBUPb/O2nG+XxfeL8v/T09NhSG/dJekWLVrgyJEjyM7OhpWVlbR8zpw5+Prrr5GZmYmqVatK\ny3lJmkh/vCRNRGRcvCRdSho0aGCQA0lERERlE+eS1u2tKxi7d+8O4NlYa8/bsWMHatSooXJ2keSJ\nvXLyxLzI0/OXrUg+mBd54VzSur11PYydO3eGv78/hg0bhqysLHh4eCA+Ph67d+9GTEyMscMjIiIi\nkp23rocRAHJycvDdd99h7dq1uH//PurVq4dvv/0Wffr0UVuXPYxE+mMPIxGVRYbq+zMGQz2Xt+4M\nIwBYW1tj/vz50jy1RERERKTdW9fDSGUfe+XkiXmRJ/ZkyRPzIk/Mi3YsGImIiOitxrmkdXsrexhf\nBnsYifTHHkYiIuPiOIxEREREZBQsGKnMYa+cPDEv8sSeLHliXuSJedGOBSMRERERlYg9jDqwh5FI\nf+xhJCIyLvYwEhERERkA55LWjQUjlTnslZMn5kWe2JMlT8yLvHAuad1YMBIRERFRidjDqAN7GIn0\nxx5GIiqLOJe0bjzDSEREREQlYsFIZQ575eSJeZEn9mTJE/MiT8yLdiwYiYiI6K3GuaR1Yw+jDuxh\nJNIfexiJiIyLPYxEREREZBQsGKnMYa+cPDEv8sSeLHliXuSJedGOBSMRERERlYg9jDqwh5FIf+xh\nJCIyLvYwEhERERkA55LWjQUjlTnslZMn5kWe2JMlT8yLvHAuad0qGDuAsiBjT4axQ6Dn3Lp0C+YP\nzY0dBr3g1qVbaNa4mbHDICIiA2APow7laX5JIiIiUleevuvZw0hERERERsGCkcoc9pjIE/MiT8yL\nPDEv8sS8aMeCkYiIiN5qnEtaN/Yw6lCe+hqIiIiofGMPIxEREREZBQtGKnPYYyJPzIs8MS/yxLzI\nE/OiHQtGIiIiIioRexh1YA8jERERlRXsYSQiIiIyAM4lrRsLRipz2GMiT8yLPDEv8sS8yAvnktaN\nBSMRERERlYg9jDqwh5GIiKh8K0/f9YZ6LhVKfYvl0KSZk4wdAlGZ4GTjhDHDxxg7DCIiKmUsGPXg\n1tbN2CHQc84dPYe6PnWNHQa94NzRc7j98Laxw6AX7N27F35+fsYOg17AvMgT86IdexiJiIjorca5\npHVjD6MOgiBg8dHFxg6DqEzI2JOBnyb8ZOwwiIjeWhyHkYiIiIiMggUjlTnnjp4zdgikAfMiTxxX\nTp6YF3liXrRjwUhEREREJdKrh/Hp06cAABMTEwBAZmYmtm7dinr16qFVq1aGjdDI2MNIpD/2MBIR\nGZdRexi7dOmC+fPnAwByc3PRrFkzjB8/Hr6+voiNjS31oIiIiIjeFM4lrZteBWNqair8/f0BAOvX\nr4e1tTVu376NZcuW4V//+pdBAyR6EXvl5Il5kSf2ZMkT8yIvnEtaN70KxtzcXNjZ2QEAdu3ahW7d\nuqFixYrw9/fHxYsXDRogERERERmXXgVjjRo1kJKSgtzcXOzcuRPt2rUDANy7dw+WlpYGDZDoRZzl\nRZ6YF3nirBXyxLzIE/OinV5TA3799dfo168frKys4ObmhjZt2gAA9u/fj4YNGxo0QCIiIiIyLr3O\nMA4bNgyHDx9GVFQUDh48KP1aumbNmvjxxx8NGiDRi9grJ0/MizyxJ0uemBd5Yl600+sMIwD4+PjA\nx8dHZdnHH39c6gERERERvUmcS1o3vc4wiqKIBQsWoEGDBrCwsMDly5cBANOnT8eaNWsMGiDRi9gr\nJ0/MizyxJ0uemBd5UQ6rw7xop1fBOHfuXEydOhVDhgxRWe7i4iKNz0hE9DoKCgowaNAguLu7w8bG\nBo0bN8aOHTvU1psyZQoUCgX27NkjLevUqROsra2lf2ZmZlr7q1euXKmyrpWVFRQKBY4fPw7g2RdH\nxYoVpfttbGyQnp4OACgqKkKfPn1gZ2eHTp06IScnR9rutGnTMGfOnFI8IkRE8qFXwbho0SIsXboU\nX331FSpU+N9V7CZNmuDPP/80WHBEmrBXTp5eNy9FRUVwdXXF/v37kZ2djalTpyIwMBAZGRnSOpcu\nXcLatWvh4uICQRCk5du3b0dOTo70r2XLlggMDNS4n759+6qsu3DhQtSsWRONGzcG8GyWhKCgIOn+\n7OxsuLu7A3g2Dq2JiQnu3r0LW1tbLFmyBACQlpaGzZs3Y8yYMa91DAyBPVnyxLzIE/OinV4F45Ur\nV+Dt7a22vGLFisjLyyv1oIjo7WNpaYmwsDC4uroCeDbDlIeHB44dOyatExoaihkzZqBixYpat5Oe\nno4DBw6gX79+eu03JiZGZV1RFLVOq5Weng5fX18oFAr4+flJ7TlffvklZs+eDYVCr49UIqIyR69P\nNw8PD6Smpqot3759O+rXr1/qQRGVhL1y8lTaebl16xbOnz+PBg0aAADi4+Nhbm6OTp06lfi4uLg4\ntGnTRio8S5KRkaFWXAqCgM2bN8PBwQFeXl749ddfpfu8vLyQlJSEJ0+eIDk5GV5eXkhISICTkxNa\ntGjxis/UsNiTJU/MizwxL9rpVTCOHz8eoaGhWLlyJYqLi3Ho0CGEh4dj4sSJGD9+vKFjNKiOHTtC\noVDgn//8p7FDIaL/V1hYiL59+yIkJAR16tRBTk4OJk2ahLlz5+p8bFxcHEJCQvTaj7K4dHNzk5YF\nBgbi7NmzyMrKwtKlSzFlyhT85z//AQB07twZHh4eaN68Oezs7NC7d29MmTIFM2fOxKRJk+Dr64tR\no0ahsLDwlZ43ERkH55LWTa+CccCAAZg8eTK+++475OXloV+/fli2bBl++eUX9OnTx9AxGszq1atx\n6tQpAFDphyJ5Yw+jPJVWXoqLixEcHAxzc3PpR3Xh4eEIDg5WOWuo6bJxSkoKbt26hZ49e+q1r7i4\nOPTv319lWb169VCtWjUIgoAWLVpgzJgxWLt2rXR/REQETp48iV9//RUREREYMWIE/vjjD6SmpmLf\nvn0oKChAVFTUqzx1g2BPljwxL/LCuaR107vhZsiQIbhy5Qpu3bqFzMxMXLt2DYMGDTJkbAZ1//59\n/OMf/+CvGolkRBRFDBo0CHfu3MG6deukSQKSkpIwb948ODs7w9nZGVevXkVgYCAiIyNVHh8bG4se\nPXroNWXpwYMHkZmZqXdx+aLTp0/j8OHDGDJkCE6fPo2mTZsCeDZmrfIPUSKi8kKvgvHp06d4+vQp\nAKBKlSooLi7GsmXLcPDgQYMGZ0jffPMNvL290bt3b2OHQi+JPYzyVBp5GTFiBM6ePYtNmzbBzMxM\nWr5nzx789ddfOHnyJE6cOAEXFxcsWbIEI0eOlNbJy8tDfHy83pejY2Nj0bNnT1hZWaks37hxI+7f\nvw9RFHHkyBHMmzcPn376qco6oihi9OjR+OWXXyAIAjw9PZGSkoKCggLs27cPNWvWfPWDUMrYkyVP\nzIs8MS/a6VUwdunSRbo0lJubi2bNmmH8+PHw9fVFbGysQQM0hJSUFKxYsQILFiwwdihE9P8yMjKw\nZMkSnDx5EtWqVZPGQVy9ejXs7e3h5OQEJycnVK1aFSYmJrCzs1Mp9jZs2AA7OzuNH/heXl5YvXq1\ndDs/Px/x8fFql6MB4LfffkPt2rVhY2OD/v3747vvvkNwcLDKOjExMfD29paG4unevTtcXFzg5OSE\n+/fvY+jQoaV0VIiI5EEQtY0f8ZwqVapgz549aNiwIeLi4hAREYFTp05h5cqVmD17dpm6/FJQUIDG\njRujR48emDJlCgBAoVDg+++/l24/TxAELD66+E2HSSU4d/QczzLK0Lmj52D+0Bw/TfjJ2KHQc/bu\n3cuzJjLEvMiLIAgQRbFc5EX5XEqbXmcYc3NzYWdnBwDYtWsXunXrhooVK8Lf3x8XL14s9aAMaebM\nmXjy5AkmTZpk7FCIiIhIBjiXtG4VdK8C1KhRAykpKfjkk0+wc+dOaf7oe/fu6dVcLhdXrlzBTz/9\nhOXLlyMvL09l0PH8/Hw8fPgQ1tbWaoPvRodFw9HFEQBgUckCNerWkM5wKX8Zyttv9raSXOLh7bqo\n61MX+5fsV/kLXfmLQ9427m0lucTD237w8/OTVTxv++3w8PAy+35R/r9yClND0euS9OLFixEaGgor\nKyu4ubnh2LFjMDExwdy5c7Fx40YkJSUZNMjSsnfvXgQEBJS4zokTJ1TmoOUlaSL9ZezJ4CVpIiIj\nMuol6WHDhuHw4cOIiorCwYMHpaEuatasiR9//LHUgzKUxo0bY+/evSr/kpOTAQDBwcHYu3evrH7d\nSJpxHEZ5Yl7k6cWzJiQPzIs8MS/a6XVJGng2tpiPj490u7CwEB9//LFBgjIUW1tbtGnTRuN9bm5u\nWu8jIiIiepvpdYZx7ty5WLdunXR74MCBMDc3R506dXDuHM8q0JvFX0jLE/MiT8p+J5IX5kWemBft\n9CoY582bB0fHZz/62L9/P+Lj47Fq1So0btwYX3/9tUEDfBOKi4s1DqlDRERE5R/nktZNr4Lxxo0b\n8PT0BABs3rwZPXv2RO/evREeHo7Dhw8bNECiF7FXTp6YF3liT5Y8MS/ywrmkddOrYLSxscGtW7cA\nALt370bbtm0BABUqVEB+fr7hoiMiIiIio9PrRy/t27fHkCFD0KRJE1y8eBGdOnUCAPz999/w8PAw\naIBEL2KvnDzV9amLjD0Zxg6DXsCeLHliXuSJedFOrzOM8+fPx4cffoisrCysXbsWDg4OAIDU1FR8\n/vnnBg2QiIiIiIxLr4G732YcuFt+OJe0PHEuaXnaWw7mxi2PmBd54VzSuul1hhEAbt68icjISIwY\nMQJZWVkAgJSUFKSlpZV6UERERERvCueS1k2vM4ypqakICAiAp6cn/vzzT5w7dw6enp4ICwvDhQsX\nsGrVqjcRq1HwDCOR/jg1IBGRcRn1DOPXX3+NMWPG4Pjx4zA3N5eWd+zYESkpKaUeFBERERHJh14F\n47FjxxASEqK2vFq1atJwO0RvCsf7kyfmRZ44rpw8MS/yxLxop1fBaGFhgXv37qktP3fuHJycnEo9\nKCIiIiKSD70Kxk8//RSTJ09WGaQ7LS0NEyZMQI8ePQwWHJEm/IW0PDEv8lTWf/FZXjEv8sS8aKdX\nwRgZGYn79++jSpUqePz4MT788EPUqlULlStXxtSpUw0dIxEREZHBcC5p3fQqGG1tbXHgwAFs3LgR\n06dPx5gxY7Bz507s378flSpVMnSMRCrYKydPzIs8sSdLnpgXeeFc0rrpNTUg8Oxn2gEBAQgICDBk\nPEREREQkM3qdYQwJCcGcOXPUls+ePRuDBw8u9aCISsJeOXliXuSJPVnyxLzIE/OinV4F444dO+Dv\n76+2PCAgAFu3bi31oIiIiIhIPvQqGB88eKCxV9HS0lLjcDtEhsReOXliXuSJPVnyxLzIE/OinV4F\nY+3atbFlyxa15du2bUOtWrVKPSgiIiKiN4VzSeum11zSsbGxGD58OMaOHYu2bdsCABITE/Hzzz9j\nwYIFGDhwoMEDNRbOJU2kP84lTURkXIaaS1qvX0n3798f+fn5+PHHHzF9+nQAQPXq1TFnzpxyXSwS\nERERkZ6XpAFg2LBhuHbtGm7evImbN2/i6tWrGD58uCFjI9KIvXLyxLzIE3uy5Il5kSfmRTu9x2EE\ngMuXL+Pvv/+GIAioV68ePD09DRUXEREREcmEXj2M2dnZGDhwINavXw+F4tlJyeLiYvTo0QNRUVGw\ntrY2eKDGIggCJs6YaOwwiMoEJxsnjBk+xthhEBG9tQzVw6hXwThgwAAcOnQIS5YsQYsWLQAAhw4d\nwrBhw9CqVStERUWVemByYagDT0RERPIQHh5ebuaTNlTdolcP46ZNm7B06VL4+vrC1NQUpqam8PPz\nw9KlS7Fhw4ZSD4qoJOwxkSfmRZ6YF3liXuSFc0nrplfBmJeXBwcHB7Xl9vb2yM/PL/WgiIiIiEg+\n9Lok/dFHH8HGxgYrVqyAlZUVACA3Nxf9+vVDdnY2EhMTDR6osfCSNBERUflWnr7rjdrDePr0aXTo\n0AGPHz9Go0aNIIoiTp8+DUtLS+zcuRNeXl6lHphclKcXEREREakrT9/1Ru1h9Pb2xoULFxAZGYmm\nTZvCx8cHkZGRuHjxYrkuFkme2GMiT8yLPDEv8sS8yBPzop3OcRgLCgrg6uqKPXv2YMiQIW8iJiIi\nIqI3hnNJ66bXJel33nkHu3btQv369d9ETLJSnk5TExERUflm1EvSo0ePRkREBAoLC0s9ACIiIiKS\nN70KxpSUFGzcuBHvvPMO2rZti08++UT617VrV0PHSKSCPSbyxLzIE/MiT8yLPDEv2uk1l7SDgwO6\nd++u8T5BEEo1ICIiIiKSF716GN9m7GEkIiKissJQdYteZxiVLl26hDNnzgAA6tWrh5o1a5Z6QHI0\naeYkY4dAVG442ThhzPAxxg6DiEhSnuaSNhS9zjDevXsXAwcOxObNm6FQPGt7LC4uxscff4zo6GiN\n0waWF4IgYPHRxcYOg55z7ug51PWpa+ww6AX65iVjTwZ+mvDTG4iIgGc9WX5+fsYOg17AvMiL8qxc\neciLUX8lPXjwYFy6dAkHDhxAXl4e8vLycODAAaSlpWHw4MGlHhQRERERyYdeZxgtLS2RmJiIli1b\nqiw/fPgw2rZti8ePHxssQGPjGUai0sUzjEQkN+Xp9wpGPcPo6OgIKysrteWWlpZwdHQs9aCIiIiI\nSD70Khh/+OEHjB07FteuXZOWXbt2Df/4xz/www8/GCw4Ik3OHT1n7BBIA+ZFnjiunDwxL/LEvGin\n16+k586di/T0dLi7u6N69eoAgOvXr8PCwgK3b9/G3LlzATw7DXrq1CnDRUtERERUyjiXtG56FYw9\nevTQa2McxJveBP5CWp6YF3kq67/4LK+YF3lRDqnDvGinV8HIsYmIiIiI3l569TASyQl75eSJeZEn\n9mTJE/MiT8yLdiwYiYiIiKhEnEtaB47DSFS6OA4jEZHhGHUcRiIiIqLyir/V0E1rwWhiYoLbt28D\nAAYOHIjs7Ow3FhRRSdgrJ0/MizyxJ0uemBd5mTx5MgDmpSRaC0YLCwvk5OQAAGJiYpCfn//GgiIi\nIp2VIUAAACAASURBVCIi+dA6rE7Lli3RrVs3NGnSBAAwZswYWFhYqKwjiiIEQUBUVJRhoyR6Dsf7\nkyfmRZ44rpw8MS/yxLxop7VgjIuLw6xZs3Dx4kUAwN27d2FqaqoyOLeyYCQiIiKi8kvrJelq1aph\n1qxZ2LBhA1xdXbFq1Sps2bIFmzdvlv4pbxO9SeyVk6c3kZeCggIMGjQI7u7usLGxQePGjbFjxw4A\nwN9//w0fHx/Y29ujcuXKaNWqFVJSUqTHdurUCdbW1tI/MzMzNGzYUON+Vq5cqbKulZUVFAoFjh8/\nLq1z7NgxtGnTBtbW1qhWrRrmzZsHACgqKkKfPn1gZ2eHTp06Sa09ADBt2jTMmTPHEIdGK/ZkyRPz\nIk/Mi3Z6/Uo6PT0djo6Oho6FiKhERUVFcHV1xf79+5GdnY2pU6ciMDAQGRkZqF69OuLj43H37l3c\nv38fffr0Qc+ePaXHbt++HTk5OdK/li1bIjAwUON++vbtq7LuwoULUbNmTTRu3BgAkJWVhU6dOmHE\niBG4d+8eLl26hPbt2wMA1q9fDxMTE9y9exe2trZYsmQJACAtLQ2bN2/GmDFjDHyUiOhlcS5p3fQe\nVmfLli1o3bo1HBwc4OjoCF9fX2zdutWQsRFpxF45eXoTebG0tERYWBhcXV0BAF26dIGHhweOHTsG\nW1tbeHh4QBAEPH36FAqFAs7Ozhq3k56ejgMHDqBfv3567TcmJkZl3dmzZ6Njx44ICgpCxYoVYWVl\nhXfffVfatq+vLxQKBfz8/HD58mUAwJdffonZs2dDoXizo5mxJ0uemBd54VzSuun1ybVs2TJ0794d\ntWrVwowZMzB9+nR4eHigW7duWL58uaFjJCLS6NatWzh//jwaNGggLatcuTIsLCwwc+ZMrF27VuPj\n4uLi0KZNG6nwLElGRoZacfnHH3/Azs4OrVq1QtWqVdG1a1dcvXoVAODl5YWkpCQ8efIEycnJ8PLy\nQkJCApycnNCiRYvXfMZERMahV8E4Y8YMzJ49G9HR0Rg8eDAGDx6MmJgY/Otf/8KMGTMMHWOp2rlz\nJwICAuDs7Axzc3PUqFEDvXv3xpkzZ4wdGumJPYzy9KbzUlhYiL59+yIkJAR16tSRlj948AAPHz5E\nnz590KtXL40zHsTFxSEkJESv/SiLSzc3N2nZ1atXERsbi3nz5uHKlSvw8PBAUFAQAKBz587w8PBA\n8+bNYWdnh969e2PKlCmYOXMmJk2aBF9fX4waNQqFhYWvdwD0xJ4seWJe5Il50U6vgvHKlSvo2LGj\n2vKOHTsiPT29tGMyqPv376NZs2ZYsGABdu/ejYiICPz111/44IMPpDMERCRvxcXFCA4Ohrm5OebP\nn692v6WlJaZPn47z58/j9OnTKvelpKTg1q1bKv2NJYmLi0P//v3Vtt+9e3c0bdoUZmZmCAsLw6FD\nh6QfuERERODkyZP49ddfERERgREjRuCPP/5Aamoq9u3bh4KCAg5HRkRlil4FY40aNbBr1y615bt3\n71b5q7ss6NOnD2bMmIHu3bujdevW+OKLL7B+/Xrk5ORovXxF8sIeRnl6U3kRRRGDBg3CnTt3sG7d\nOpiYmGhc7+nTpyguLoalpaXK8tjYWPTo0UNtuSYHDx5EZmamWnGp7dfVLzp9+jQOHz6MIUOG4PTp\n02jatCkAwMfHB6dOndJrG6+LPVnyxLzIE/OinV4F4/jx4/HVV19h8ODBiI6ORnR0NAYNGoSvvvoK\n48aNM3SMBmdvbw8AWr94iEg+RowYgbNnz2LTpk0wMzOTlicmJuLEiRN4+vQpsrOz8Y9//AN169ZF\nrVq1pHXy8vIQHx+v9+Xo2NhY9OzZE1ZWVirLBwwYgISEBJw8eRKFhYX48ccf0bp1a1hbW0vriKKI\n0aNH45dffoEgCPD09ERKSgoKCgqwb98+1KxZ8/UOBBGVGs4lrZteBeOwYcPw22+/4cyZMxg3bhzG\njRuHc+fOIT4+HsOGDTN0jAbx9OlTFBQU4MKFCxg2bBiqVq2KPn36GDss0gN7GOXpTeQlIyMDS5Ys\nwcmTJ1GtWjVpnMRVq1bhwYMHCAoKQuXKlVG3bl3cuXMHmzZtUnn8hg0bYGdnp/EsgpeXF1avXi3d\nzs/PR3x8vNrlaADw9/fHtGnT0KVLF1StWhWXL1/GqlWrVNaJiYmBt7e3NBRP9+7d4eLiAicnJ9y/\nfx9Dhw4thSOiG3uy5Il5kRfOJa2bIGrqCH8L+Pj44NixYwAANzc3bN26FfXr11dbTxAELD66+E2H\nRyU4d/QcL0vL0P+1d+dxVdX5/8Bf5yoICAgpICAi4C6oCDlp7pOm5JKaS5qKmlt9zZrMMiaFHFya\nEXNpcsnUxiWdxFzGVFJBEZVwRQwtA1FUFGUUcGH7/P7oxx2vcBeNy/3ce1/Px4NHnOWe8768vfHm\nc97nfAzNy+X9lxE9I7oaIiLg91+AvMwmH+ZFLoqiQAhhEXkpfy9VflxrLRjT09ORn5+PS5cu4R//\n+AdycnKQmJhYoSeTBSNR1WLBSESyMVaRZQosGI3o7t27aNSoEYYPH44vv/xSY5uiKHjhlRdQz+v3\nmW7sHe3h08xHPZJSfhmOy1zmsmHLOSdzsH7FegD/u/xT/hc9l7nMZS6bYllRFBw8eFCaeJ5mufz7\n8qfWrFu3jgWjMZXPQfvk3eAcYZQPL0nLiZek5RRvAZfYLBHzIhdektaveueoklROTg7S09N51yIR\nEZEV4lzS+ukdYSwqKkLnzp3xzTffoFkz8x/VGThwIEJCQhAUFARnZ2dcvHgRixYtws2bN5GcnKzx\nCA6AI4xEVY0jjERExmOsEcaa+nawtbVFRkYGFEWp8pObQocOHbBlyxYsXLgQRUVF8PHxQffu3TFz\n5kyD5pUlIiIisjYGXZIePXo0Vq1aZexYqsWMGTOQkpKCvLw8FBYWIj09HV9++SWLRTPC5zDKiXmR\n0+ON8SQP5kVOzIt2ekcYAeD+/ftYv3494uLiEBISop71QAgBRVGwZMkSowZJRERERKZj0F3ST94x\nVH55urxgLL8V3RKxh5GoarGHkYjIeEzWwwhwiJaIiIgsV2RkJOeT1uOpHquTm5uL48eP4+HDh8aK\nh0gv9srJiXmRE//glxPzIhfOJa2fQQVjfn4+hgwZAnd3d3Ts2BHXrl0DAEyePJkVOREREZGFM6hg\n/PDDD5GdnY2TJ0/C3t5evb5v376IjY01WnBEleEsL3JiXuRk7rNWWCrmRU7Mi3YG9TDu2LEDsbGx\naNu2rcbzGJs3b47ffvvNaMERERERkekZNMKYl5eHunXrVlifn5+PGjVqVHlQRLqwV05OzIuc2JMl\nJ+ZFTsyLdgYVjKGhodixY0eF9StXrkTHjh2rPCgiIiKi6sK5pPUz6DmMSUlJePnllzFs2DCsX78e\nEyZMwLlz55CcnIxDhw4hJCSkOmI1CT6Hkahq8TmMRETGY6znMBo0wtixY0ckJSWhqKgIAQEB2L9/\nP7y9vXHs2DGLLhaJiIiI6CmewxgUFIRvvvkGaWlpOH/+PNavX4+goCBjxkZUKfbKyYl5kRN7suTE\nvMiJedHOoLukAeDBgwfYuHEjfv75ZwBAixYtMGLECI3H7BARERGR5TGoh/HkyZPo27cvHjx4gKCg\nIAghkJaWhlq1amHXrl0WfVmaPYxEVYs9jERExmPSHsaJEyeiU6dOuHr1Kg4dOoTDhw/jypUr6NKl\nCyZNmlTlQRERERFVF85ap59BBWNaWhpmz56N2rVrq9fVrl0bs2bNwrlz54wWHFFl2CsnJ+ZFTuzJ\nkhPzIhfOJa2fQQVjs2bN1PNHP+769eto1ozTgRERERFZMq09jHfu3FF/f/ToUUyfPh2zZs1Chw4d\n1Ouio6Mxf/589O3bt3qiNQH2MBJVLfYwEpFsjNX3ZwrGei9a75KuV69ehXUjR46ssG7AgAEoLS2t\n2qiIiIiISBpaC8YDBw5UZxxEBruQcgHNQtkKIRvmRU7x8fHo1q2bqcOgJzAvcmJetNNaMPIHRkRE\nRNaAc0nrZ9BzGAHg0aNHSEtLw82bN1FWVqaxLSwszCjByUBRFHy84GNTh0FkMdyd3TFt8jRTh0FE\nZJGM1cNoUMF44MABjBw5Ejk5OZVuf7KAtCSW1AhLREREls2kD+6eMmUKXnnlFWRkZKCwsBD379/X\n+CKqTnxOlpyYFzkxL3JiXuTEvGhn0FzS165dw8cffwxfX19jx0NEREREkjHokvTQoUPRv39/vPHG\nG9URk1R4SZqIiIjMhUl7GPPy8vD666+jefPmCAoKgo2Njcb20aNHV3lgsmDBSEREZNkiIyMtZj5p\nkxaMW7ZsQXh4OB4+fAgHBwcoiqKxPT8/v8oDkwULRvnwOVlyYl7kxLzIiXmRS/nvekvIi0lvepk+\nfTreeust5Ofno6CgAPn5+RpfRERERGS5DBphdHZ2xqlTpxAQEFAdMUmFI4xERESWzZJ+15t0hHHQ\noEGIi4ur8pMTERERkfwMeqxOQEAAIiIicPjwYbRu3brCTS9/+ctfjBIcUWUsocfEEjEvcmJe5MS8\nyIl50c6ggnH16tVwcnLCkSNHkJSUVGE7C0YiIiIyV5xLWj+D55K2VpbU10BERESWzaQ9jERERERk\nvQy6JD116tQKz1583JIlS6osIBlFfBZh6hDoMZcvXYZvAKeplA3zIqfqyIu7szumTZ5m1HNYGvbK\nyYl50c6ggjE1NVWjYCwqKkJ6ejpKS0sRHBxstOBk4ftn/hKUycM6D+EbypzIhnmRU3Xk5fL+y0Y9\nPhGZnkEFY3x8fIV1Dx8+xLhx49ClS5eqjolIp2ahzUwdAlWCeZET8yInjmLJiXnR7pl7GO3s7BAR\nEYHo6OiqjIeIiIioWlnKPNLG9IduesnNzeXUgFTtLqRcMHUIVAnmRU7Mi5wqu3JHphMVFQWAedHF\noEvSCxcu1OhhFELg2rVr2LBhA8LCwowWHBERERGZnkHPYWzUqJFGwahSqeDm5oYePXpg5syZcHJy\nMmqQpqQoClakrDB1GERE0rq8/zKiZ7A9icyXJT1z2VjvxaARxszMzCo/MRERERGZBz64m8wOe7Lk\nxLzIiXmRE3vl5MS8aGfQCKMQAps3b8b+/ftx8+ZNlJWVqbcpioIdO3YYLUAiIiIiY+Jc0voZ1MP4\nwQcf4PPPP0f37t3h6emp0c+oKArWrFlj1CBNiT2MRES6sYeRSB4m7WH85ptvsHHjRgwZMqTKAyAi\nIiIiuRnUw1hWVmYVUwCSeWBPlpyYFzkxL3Jir5ycmBftDCoYJ0yYgPXr1xs7FiIiIiKSkEGXpO/e\nvYsNGzYgLi4OrVu3ho2NDYDfb4ZRFAVLliwxapBEj+PcuHJiXuTEvMiJcxbLiXnRzqCCMS0tDW3b\ntgUApKenq9eXF4xERERE5ioyMpLzSeth0CXp+Ph49dfBgwfVX+XLRNWJPVlyYl7kJHteli1bhtDQ\nUNjZ2WHs2LHq9ZmZmVCpVHByclJ/RUf/707sRYsWISAgAM7OzvDw8MDYsWORn5+v9Tz379/HW2+9\nBTc3N7i4uKBr164a20+ePIkuXbrAyckJ9evXV185KykpwfDhw+Hq6oo+ffponGPu3LlYtGjRM71v\n9srJhXNJ68cHdxMRkcl4e3vjk08+wbhx4yrdfu/ePeTn5yM/Px8RERHq9QMGDEBKSgru3buH9PR0\nZGVlaRSUT5o4cSL++9//Ij09HXl5efj888/V23Jzc9GnTx9MmTIFd+7cwaVLl9CrVy8AQGxsLGrU\nqIHbt2+jTp06WLlyJQAgIyMDO3fuxLRp06rix0AkPYMuSRPJhD1ZcmJe5CR7XgYOHAgASElJwdWr\nVytsLysrQ40aNSqs9/f319hHpVLB09Oz0nOkp6dj586dyM7OhqOjIwBoPPkjJiYGvXv3xuuvvw4A\nsLGxQfPmzQH8PtLZtWtXqFQqdOvWDampqQCAd955BzExMVCpnm3chb1ycmJetOMIIxERmZy2Bw37\n+vrCx8cH48aNw+3btzW2bdy4EXXq1IGbmxvc3Ny0jvYlJyfD19cXs2bNgpubG1q3bo3Y2Fj19uPH\nj8PV1RUvvvgiPDw80L9/f1y5cgUAEBgYiAMHDuDRo0c4ePAgAgMDsW3bNri7u6NDhw5V9O6J5MeC\nkcyO7D1Z1op5kZO55OXJGyjd3NyQkpKCrKwsnDhxAvn5+Rg5cqTGPiNGjMDdu3dx8eJF/Pzzz1r7\nCa9evYpz587BxcUF169fx7JlyzBmzBhcuPD7z+bKlStYt24dlixZgqysLPj5+alHG8PCwuDn54f2\n7dvD1dUVw4YNw6efforPPvsMERER6Nq1K95++20UFxc/1ftlr5ycmBftrK5g/O677/Dqq6+iYcOG\ncHBwQPPmzfHxxx+joKDA1KEREVmtJ0cYa9eujXbt2kGlUsHd3R3Lli3Dvn37UFhYWOG1jRs3xkcf\nfYRvvvmm0mPb29vDxsYGf/3rX1GzZk106dIF3bt3x969ewEADg4OGDRoEEJCQlCrVi3Mnj0bSUlJ\n6htc5s2bhzNnzmD58uWYN28epkyZguPHj+PEiRNISEhAUVERvv766yr+iVB14lzS+lldwbhw4ULY\n2Nhg/vz52LNnD6ZMmYIvv/wSPXv2NMrci1T1ZO/JslbMi5zMJS+GPqKtrKys0vXFxcVwcHCodFvr\n1q0BVCxKy89Zvl2f1NRUHD16FBMmTEBqaipCQkIAAKGhoTh79qxBxyjHXjm5lD9Sh3nRzuoKxl27\nduHf//43RowYgS5dumDatGlYsmQJjh8/zqFoIqJqVlpaiocPH6KkpASlpaV49OgRSkpKkJycjAsX\nLqCsrAy3b9/GO++8g+7du8PJyQkA8NVXX+HWrVsAgPPnz2P+/PkYPHhwpefo2rUrGjZsiHnz5qGk\npARHjhxBfHw8Xn75ZQDA2LFjsW3bNpw5cwbFxcWYM2cOOnfurD4X8HuxOXXqVCxduhSKosDf3x+J\niYkoKipCQkICAgICjPyTIjItqysY69atW2FdaGgoAODatWvVHQ49A3PpybI2zIucZM/LnDlz4ODg\ngAULFmD9+vWwt7fH3Llz8dtvv6FPnz5wdnZGUFAQ7O3tsWnTJvXrkpKSEBQUBCcnJwwcOBCjR4/G\ne++9p94eGBio3r9mzZrYvn07du/eDRcXF0yaNAn/+te/0LRpUwBA9+7dMXfuXLzyyivw8PDAb7/9\nho0bN2rEuXbtWgQFBanvrh40aBC8vLzg7u6OvLw8TJw48aneNwco5MS8aKcIXofF8uXL8dZbbyEl\nJQXt2rXT2KYoClakrDBRZFSZCykXzOYymzVhXuRUHXm5vP8yomdofwYiVRQfH8/LnxKyhLwoimKU\nFjurLxizs7MRHByM4OBgdQP041gwEhHpxoKRSB7GKhit+sHdBQUFGDBgAGxtbbFmzRqt+62ZvQb1\nvOoBAOwd7eHTzEf9F3v55R4uc5nLXLbWZTvYAfjf5bzyERouc9lcliMjI9XrZYjnaZbLv8/MzIQx\nWe0I44MHDxAWFobU1FQkJCSgVatWle7HEUb58NKnnJgXOfGStJws4dKnJSkflbOEvHCEsQoVFxfj\ntddew8mTJxEXF6e1WCQiIiIiKywYy8rKMHLkSMTHx2PXrl1o3769qUOip8RRLDkxL3JiXuRk7qNY\nlop50c7qCsa3334b3333HSIiImBvb49jx46pt/n4+MDb29uE0RERERHJx+qew7hnzx4oioLo6Gh0\n7NhR42v16tWmDo8MIPtz5awV8yIn5kVOfN6fnJgX7axuhDEjI8PUIRAREZFEOJe0flZ7l7SheJc0\nEZFuvEuaSB7Gukva6i5JExEREdHTYcFIZoc9WXJiXuTEvMiJvXJyYl60Y8FIRERERDqxh1EP9jAS\nEenGHkYiebCHkYiIiMgIIiMjTR2C9FgwktlhT5acmBc5MS9yYq+cXKKiogAwL7qwYCQiIiIindjD\nqAd7GImIdGMPI5k7Y/X9mQJ7GImIiIjIJFgwktlhT5acmBc5MS9yYq+cnJgX7VgwEhERkVXjXNL6\nsYdRD/YwEhHpxh5GInmwh5GIiIiITIIFI5kd9mTJiXmRE/MiJ/bKyYl50Y4FIxERERHpxB5GPdjD\nSESkG3sYieTBHkYiIiIiI+Bc0vqxYCSzw54sOTEvcmJe5MReOblwLmn9WDASERERkU7sYdSDPYxE\nRLqxh5HMHeeS1q9mlR/RAl3ef9nUIRARScvd2d3UIRCRkXGEUQ9L+qvDUsTHx6Nbt26mDoOewLzI\niXmRE/Mil/Lf9ZaQF94lTURERGQEnEtaP44w6sERRiIiIjIXHGEkIiIiIpNgwUhmh8/JkhPzIifm\nRU7Mi5yYF+1YMBIRERGRTuxh1IM9jERERGQu2MNIREREZAScS1o/FoxkdthjIifmRU7Mi5yYF7lw\nLmn9WDASERERkU7sYdSDPYxERESWzZJ+17OHkYiIiIhMggUjmR32mMiJeZET8yIn5kVOzIt2NU0d\ngDmI+CzC1CHQYy5fuoy45DhTh0FPYF7kxLzIiXmRy8uvvGzqEKTHHkY9FEXBipQVpg6DiIiIjOTy\n/suInhFt6jCqBHsYiYiIiMgkWDCS2bmQcsHUIVAlmBc5MS9yYl7kxB5G7VgwEhEREZFOLBjJ7DQL\nbWbqEKgSzIucmBc5MS9y6tatm6lDkBYLRiIiIrJqh+IOmToE6bFgJLPD3h85MS9yYl7kxLzIJfHH\nRADsYdSFBSMRERER6cTnMOrB5zASERFZtkmhkziXtB4cYSQiIiIinVgwktlh74+cmBc5MS9yYl7k\nxB5G7VgwEhERkVXr9FInU4cgPfYw6sEeRiIiIsvGuaT14wgjEREREenEgpHMDnt/5MS8yIl5kRPz\nIif2MGrHgpGIiIjoGS1btgyhoaGws7PD2LFjNbbt378fzZs3R+3atdGjRw9kZWVpbP/www9Rr149\n1KtXDx999JHO8xh6LAAaxyopKcHw4cPh6uqKPn36ID8/X71t7ty5WLRokUHvkwUjmR3OwSon5kVO\nzIucmBc5Pctc0t7e3vjkk08wbtw4jfW5ubkYPHgwoqOjkZeXh9DQUAwbNky9fcWKFdi+fTvOnj2L\ns2fPYufOnVixovJ7Jp7mWAA0jhUbG4saNWrg9u3bqFOnDlauXAkAyMjIwM6dOzFt2jSD3icLRiIi\nIrJqf2Qu6YEDB2LAgAGoW7euxvrY2FgEBgZi8ODBsLW1RWRkJM6cOYOLFy8CANatW4fp06fDy8sL\nXl5emD59OtauXVvpOZ7mWAA0jpWZmYmuXbtCpVKhW7du+O233wAA77zzDmJiYqBSGVYKsmAks8Pe\nHzkxL3JiXuTEvMilKuaSfvLO5LS0NLRp00a97ODggMaNGyMtLQ0AcP78eY3trVu3Vm970h85VmBg\nIA4cOIBHjx7h4MGDCAwMxLZt2+Du7o4OHToY/P4sqmAMDw+Hn5/fU78uPj4eKpUKhw49+18YRERE\nZL0URdFYLiwshLOzs8Y6Z2dndQ9hQUEB6tSpo7GtoKCg0mP/kWOFhYXBz88P7du3h6urK4YNG4ZP\nP/0Un332GSIiItC1a1e8/fbbKC4u1vn+LKpgnDVrFr7//ntTh0FGxt4fOTEvcmJe5MS8yOlZehjL\nPTnC6OjoiHv37mmsu3v3LpycnCrdfvfuXTg6OlZ67D96rHnz5uHMmTNYvnw55s2bhylTpuD48eM4\nceIEEhISUFRUhK+//lrn+7OIgvHRo0cAAH9/f40hWSIiIqLq8OQIY6tWrXDmzBn1cmFhIS5duoRW\nrVqpt58+fVq9/cyZMwgMDKz02FV1rNTUVBw9ehQTJkxAamoqQkJCAAChoaHqG2a0qdaC8eLFixg4\ncCA8PDxgb28PX19fDB06FKWlpQCAW7duYfLkyWjQoAHs7OzQokULrFq1SuMYa9euhUqlwuHDhzFk\nyBC4urqqr8FXdkl69uzZaNeuHerUqQM3Nzf8+c9/xvHjx6vnDZNRsPdHTsyLnJgXOTEvcnqWHsbS\n0lI8fPgQJSUlKC0txaNHj1BaWoqBAwfi3LlziI2NxcOHDxEVFYW2bduiadOmAIDRo0cjJiYG165d\nQ3Z2NmJiYhAeHl7pOZ7mWAAqPZYQAlOnTsXSpUuhKAr8/f2RmJiIoqIiJCQkICAgQOf7rNaC8ZVX\nXsH169exfPly7Nu3D/Pnz4ednR2EELh37x46deqEPXv2ICoqCrt370a/fv0wZcoULFu2rMKxRo4c\niYCAAGzduhXz589Xr3+yws/Ozsa7776LHTt2YN26dXB3d0eXLl1w7tw5o79fIiIikt8fmUt6zpw5\ncHBwwIIFC7B+/XrY29sjOjoa9erVw9atWxEREYHnnnsOKSkp+Pbbb9WvmzRpEvr164egoCC0bt0a\n/fr1w8SJE9XbAwMDsWnTJgB4qmMBqHAs4PcBt6CgIAQHBwMABg0aBC8vL7i7uyMvL6/C/k+qtrmk\nc3Nz4e7ujh07dqBv374Vts+ZMwdz587FuXPnNKrciRMnYtu2bcjJyYFKpcLatWsxbtw4vPfee1i4\ncKHGMcLDw5GQkICMjIxKYygtLYUQAoGBgejduzc+//xzAL//RdGjRw/Ex8ejS5cuGq/hXNJERESW\njXNJ61dtI4z16tWDv78/PvzwQ3z11Vf45ZdfNLbv2bMHL7zwAho1aoSSkhL1V69evXD79m2cP39e\nY/+BAwcadN4ff/wR3bt3R7169WBjYwNbW1tcvHhR/ewiIiIiItKtZnWeLC4uDpGRkZg5cyZu374N\nPz8/fPDBB5g8eTJu3ryJS5cuwcbGpsLrFEXB7du3NdZ5enrqPd/JkycRFhaGPn364Ouvv4anpydU\nKhXefPNNPHz40OC418xeg3pev0+3Y+9oD59mPuo73Mr7ULhcfctXLlzBSyNfkiYeLv++/HhPgnjy\nNAAAFuBJREFUlgzxcJmfF5mX+XmRbzk+Ph6nT5/Gu+++q14G/nfntKzL5d9nZmbCmKrtkvSTzpw5\ng2XLlmH16tXYvXs3oqKiULNmTSxevLjS/Zs2bQpHR0f1Jelff/0V/v7+Gvs8eUk6IiICixcvxt27\nd1GjRg31fr6+vggICMCBAwcA8JK0ubmQcoGPpJAQ8yIn5kVOzItcyi9Jx8fH/6FH68jAWJekq3WE\n8XFt2rTBwoULsXr1aqSlpaF3795YunQpfHx84ObmViXnuH//foUpbw4cOIArV67ovRuI5MX/ycqJ\neZET8yIn5kVO5l4sGlO1FYxnz57FtGnTMHz4cAQEBKC0tBRr166FjY0NevTogYCAAGzevBmdO3fG\ne++9h6ZNm6KwsBDp6elITEx8pgdy9+nTB4sXL0Z4eDjCw8Nx8eJF/O1vf4O3t7dRqm8iIiIyP4fi\nDgEzTB2F3KrtphdPT0/4+voiJiYGAwYMwIgRI3Djxg3s2rULwcHBcHZ2RlJSEsLCwrBgwQL07t0b\n48ePx86dO9GjRw+NYz356JzH1z++rVevXliyZAmOHDmCfv36Ye3atfjXv/6Fxo0bVziGtmOSfB7v\n/SF5MC9yYl7kxLzIpSrmkrZ0JuthNBfsYZQPe3/kxLzIiXmRE/Mil0mhkyCEYA+jruOyYNSNBSMR\nEZFlKy8YLYHZP4eRiIiIiMwTC0YyO+z9kRPzIifmRU7Mi5zYw6gdC0YiIiKyan9kLmlrwR5GPdjD\nSEREZNk4l7R+HGEkIiIiIp1YMJLZYe+PnJgXOTEvcmJe5MQeRu1YMBIRERGRTiwYyezwYbdyYl7k\nxLzIiXmRk7k/tNuYWDASERGRVTsUd8jUIUiPBSOZHfb+yIl5kRPzIifmRS6cS1o/FoxEREREpBOf\nw6gHn8NIRERk2TiXtH4cYSQiIiIinVgwktlh74+cmBc5MS9yYl7kxB5G7VgwEhERkVXjXNL6sYdR\nD/YwEhERWTbOJa0fRxiJiIiISCcWjGR22PsjJ+ZFTsyLnJgXObGHUTsWjERERESkEwtGMjucg1VO\nzIucmBc5MS9y4lzS2rFgJCIiIqvGuaT1q2nqAMzB5f2XTR0CPebypcvwDfA1dRj0BOZFTsyLnJgX\nuTw+lzRHGSvHx+roYazb0+nZ8QMtJ+ZFTsyLnJgXuZT/rreEvBirbmHBqAcLRiIiIstmSb/r+RxG\nIiIiIjIJFoxkdvicLDkxL3JiXuTEvMiJedGOBSMRERFZtdmzZ5s6BOmxh1EPS+prICIiIsvGHkYi\nIiIiMgkWjGR22GMiJ+ZFTsyLnJgXOTEv2rFgJCIiIiKd2MOoB3sYiYiIyFywh5GIiIjICCIjI00d\ngvRYMJLZYY+JnJgXOTEvcmJe5BIVFQWAedGFBSMRERER6cQeRj3Yw0hERGTZLOl3PXsYiYiIiMgk\nWDCS2WGPiZyYFzkxL3JiXuTEvGjHgpGIiIisGueS1o89jHpYUl8DERERWTb2MBIRERGRSbBgJLPD\nHhM5MS9yYl7kxLzIiXnRjgUjEREREenEHkY92MNIRERE5oI9jERERERGwLmk9WPBSGaHPSZyYl7k\nxLzIiXmRC+eS1o8FIxERERHpxB5GPdjDSEREZNks6Xc9exiJiIiIyCRYMJLZYY+JnJgXOTEvcmJe\n5MS8aMeCkYiIiKwa55LWjz2MelhSXwMRERFZNvYwEhEREZFJsGAks8MeEzkxL3JiXuTEvMiJedGO\nBSMRERER6cQeRj3Yw0hERETmgj2MREREREbAuaT1Y8FIZoc9JnJiXuTEvMiJeZEL55LWjwUjmZ3T\np0+bOgSqBPMiJ+ZFTsyLnJgX7Vgwktn573//a+oQqBLMi5yYFzkxL3JiXrRjwUhEREREOrFgJLOT\nmZlp6hCoEsyLnJgXOTEvcmJetONjdfRo27Ytzpw5Y+owiIiIiPTq2rWrUW7eYcFIRERERDrxkjQR\nERER6cSCkYiIiIh0YsFIRERERDqxYNRiz549aN68OZo0aYIFCxaYOhz6/xo1aoTWrVsjODgY7du3\nN3U4VmvcuHHw8PBAUFCQet2dO3fQs2dPNG3aFL169eLzzEygsrxERkaiQYMGCA4ORnBwMPbs2WPC\nCK3PlStX0L17d7Rq1QqBgYFYsmQJAH5eTE1bXvh50Y43vVSitLQUzZo1w48//ghvb288//zz2LRp\nE1q0aGHq0Kyen58fTpw4geeee87UoVi1w4cPw9HREaNHj0ZqaioAYMaMGahXrx5mzJiBBQsWIC8v\nD/PnzzdxpNalsrxERUXByckJf/nLX0wcnXW6ceMGbty4gbZt26KgoAAhISH4/vvvsWbNGn5eTEhb\nXrZs2cLPixYcYaxEcnIyGjdujEaNGsHGxgbDhw/H9u3bTR0W/X/8G8f0OnfuDFdXV411O3bswJgx\nYwAAY8aMwffff2+K0KxaZXkB+Jkxpfr166Nt27YAAEdHR7Ro0QLZ2dn8vJiYtrwA/Lxow4KxEtnZ\n2fDx8VEvN2jQQP0PiUxLURS89NJLCA0NxapVq0wdDj0mJycHHh4eAAAPDw/k5OSYOCIqt3TpUrRp\n0wbjx4/npU8TyszMxKlTp/CnP/2JnxeJlOflhRdeAMDPizYsGCuhKIqpQyAtjhw5glOnTuGHH37A\nF198gcOHD5s6JKqEoij8HEliypQpyMjIwOnTp+Hp6Yn333/f1CFZpYKCAgwePBiLFy+Gk5OTxjZ+\nXkynoKAAr732GhYvXgxHR0d+XnRgwVgJb29vXLlyRb185coVNGjQwIQRUTlPT08AgJubGwYOHIjk\n5GQTR0TlPDw8cOPGDQDA9evX4e7ubuKICADc3d3VBcmbb77Jz4wJFBcXY/DgwRg1ahReffVVAPy8\nyKA8L2+88YY6L/y8aMeCsRKhoaH45ZdfkJmZiaKiImzevBn9+/c3dVhW7/79+8jPzwcAFBYWYt++\nfRp3g5Jp9e/fH+vWrQMArFu3Tv0/YDKt69evq7/ftm0bPzPVTAiB8ePHo2XLlnj33XfV6/l5MS1t\neeHnRTveJa3FDz/8gHfffRelpaUYP348Zs6caeqQrF5GRgYGDhwIACgpKcHIkSOZFxN5/fXXkZCQ\ngNzcXHh4eODTTz/FgAEDMHToUGRlZaFRo0bYsmULXFxcTB2qVXkyL1FRUYiPj8fp06ehKAr8/Pyw\nYsUKde8cGV9iYiK6dOmC1q1bqy87z5s3D+3bt+fnxYQqy8vcuXOxadMmfl60YMFIRERERDrxkjQR\nERER6cSCkYiIiIh0YsFIRERERDqxYCQiIiIinVgwEhEREZFOLBiJiIiISCcWjERWKjMzEyqVCidP\nnqz2c69du7bC9GjWIjc3FyqVCocOHXrmY2zfvh1NmjSBjY0Nxo0bV4XRERFVjgUjkRXo1q0bpk6d\nqrGuYcOGuHHjBtq0aVPt8QwfPhwZGRnVfl5LMX78eAwZMgRZWVlYvHixqcPRa+XKlejevTtcXFyg\nUqmQlZVVYZ+8vDyMGjUKLi4ucHFxwejRo3H37l2NfbKystCvXz84OjrCzc0N06ZNQ3FxscY+qamp\n6Nq1KxwcHNCgQQPMmTOnwrkSEhIQEhICe3t7BAQEYMWKFVX7hoksEAtGIiulUqng7u6OGjVqVPu5\n7ezsUK9evWo/ryXIy8vDnTt30KtXL3h6ej7zSG1RUVEVR6bdgwcP0Lt3b0RFRWndZ8SIETh9+jT2\n7t2LPXv24OTJkxg1apR6e2lpKV555RUUFhYiMTERmzZtwnfffYf3339fvc+9e/fQs2dPeHp6IiUl\nBYsXL8bf//53xMTEqPfJyMhAWFgYOnXqhNOnT2PmzJmYOnUqYmNjjfPmiSyFICKLNmbMGKEoisbX\n5cuXRUZGhlAURZw4cUIIIcTBgweFoijihx9+EMHBwcLe3l507txZXL16Vezfv18EBQUJR0dH0a9f\nP3Hnzh2Nc3z99deiRYsWws7OTjRt2lQsWrRIlJWVaY1pzZo1wtHRUb08e/ZsERgYKDZt2iT8/f2F\nk5OTePXVV0Vubq7O9xYVFSV8fX1FrVq1RP369cXo0aM1ti9YsEAEBAQIe3t7ERQUJNavX6+xPTs7\nW4wYMULUrVtXODg4iLZt24qDBw+qty9fvlwEBAQIW1tb0bhxY7Fq1SqN1yuKIlauXClee+01Ubt2\nbeHv71/hHMnJyaJdu3bCzs5OBAcHi127dglFUURCQoIQQoiioiIxdepU4eXlJWrVqiV8fHzERx99\nVOn7Lc/R41/lx9m6dasIDAxUHyM6Olrjtb6+viIyMlKMHTtWuLi4iKFDh1Z6jjFjxoi+ffuKzz//\nXHh7ewtXV1cxduxYcf/+fS1ZMNxPP/2k/vf3uPPnzwtFUURSUpJ6XWJiolAURVy8eFEIIcTu3buF\nSqUSV69eVe+zfv16YWdnJ/Lz84UQQvzzn/8UderUEQ8fPlTv87e//U14e3url2fMmCGaNm2qcf43\n33xTdOjQ4Q+/PyJLxoKRyMLdvXtXdOzYUYwfP17k5OSInJwcUVpaqrVg/NOf/iQSExPF2bNnRWBg\noOjYsaPo3r27SE5OFikpKcLPz09MmzZNffyVK1cKT09PsXXrVpGZmSl27twp6tevL5YtW6Y1psoK\nRkdHRzFo0CCRmpoqjh49Knx9fcWkSZO0HuO7774Tzs7OYvfu3eLKlSsiJSVFfPHFF+rtH3/8sWje\nvLnYu3evyMzMFBs3bhS1a9cW//nPf4QQQhQUFIjGjRuLTp06icTERJGRkSG2b9+uLhhjY2OFjY2N\n+OKLL8Qvv/wili5dKmxsbMTOnTvV51AURTRo0EBs2LBBXLp0ScycOVPY2tqKrKwsIYQQ+fn5ws3N\nTQwdOlSkpaWJvXv3iubNm2sUev/4xz+Ej4+POHz4sLhy5YpISkoSa9eurfQ9FxUVqYurbdu2iZyc\nHFFUVCRSUlJEjRo1RGRkpPjll1/Ehg0bhKOjo1i6dKn6tb6+vsLZ2Vn8/e9/F5cuXRK//vprpecY\nM2aMqFOnjpg4caJIT08X+/btEy4uLmLevHnqfaKjo4Wjo6POr8TExArH1lYwrl69Wjg5OWmsKysr\nE46OjuqfxSeffCICAwM19rl586ZQFEXEx8cLIYQYNWqU6Nu3r8Y+ycnJQlEUkZmZKYQQonPnzuL/\n/u//NPbZsmWLsLGxESUlJZX+TIiIBSORVejWrZuYOnWqxjptBeO+ffvU+yxbtkwoiiJOnTqlXhcZ\nGanxi9vHx6fCqNqiRYtEy5YttcZTWcFoZ2cn7t27p14XHR0tGjdurPUYCxcuFM2aNRPFxcUVthUU\nFAh7e/sKRcu0adNEWFiYEOL3QtfJyUncvn270uOXF9mPCw8PF506dVIvK4oiPv74Y/VySUmJcHBw\nEBs2bBBCCLFixQrh4uIiCgsL1fusX79eo2B85513xJ///Get7/NJt27d0ni9EEKMGDGiwjEiIyNF\ngwYN1Mu+vr6if//+eo8/ZswY0bBhQ40R4gkTJoiXXnpJvXznzh1x6dIlnV8PHjyocGxtBWN0dLTw\n9/evsL+/v7+YP3++OoYn32NZWZmoWbOm+Pbbb4UQQvTs2bNCzi5fviwURRHHjh0TQgjRtGlTMWfO\nHI19EhIShKIo4saNG3p/PkTWqqapL4kTkVxat26t/t7d3R0AEBQUpLHu5s2bAIBbt27h6tWrmDhx\nIiZPnqzep6Sk5KnP6+vrq9GP5+npqT5PZYYOHYolS5bAz88PL7/8Mnr37o3+/fvD1tYW58+fx8OH\nD/Hyyy9DURT1a4qLi+Hn5wcAOHXqFNq0aYPnnnuu0uOnp6fjzTff1Fj34osvYseOHRrrHv951ahR\nA25ubuq4f/75Z7Rp0wYODg7qfV544QWN14eHh6Nnz55o2rQpevXqhbCwMPTp00cjbn3S09PRt2/f\nCrFGRUWhoKAAjo6OUBQFoaGhBh2vZcuWGuf39PTE8ePH1cuurq5wdXU1OL6qIoTQuf1pfmZE9HRY\nMBKRBhsbG/X35b+AH78xRlEUlJWVAYD6vytWrEDHjh2r7LxPnqcyDRo0wIULF7B//378+OOPeP/9\n9xEVFYXjx4+rX7dr1y40bNhQ63n0FSCVebIo0Re3vnMEBwcjMzMTe/fuxf79+zFmzBi0adMGcXFx\nT1UAaTvP48eoXbu2QceqWVPzV8OT72nu3LmYN2+ezmPs2bMHL774okHnq1+/Pm7duqWxTgiBmzdv\non79+up9kpKSNPbJzc1FaWmpxj43btzQ2CcnJ0e9Tdc+NWvW5I1YRDrwLmkiK2Bra/tMo376eHh4\nwMvLC7/++iv8/f0rfBlbrVq1EBYWhpiYGPz0009IS0tDUlISWrVqhVq1aiEzM7NCTD4+PgCAdu3a\n4ezZs7h9+3alx27RogUSExM11iUmJqJVq1YGx9eyZUukpqbi/v376nXHjh2rsJ+joyMGDx6Mf/7z\nn/jPf/6DAwcO4NKlSwafp0WLFjhy5EiFWH18fAwuEh+nr1CdMmUKzpw5o/MrJCTE4PN16NABBQUF\nOHr0qHrd0aNHUVhYqP5DpGPHjvj555+RnZ2t3icuLg61atVSn6tDhw44fPgwHj16pLGPt7c3fH19\n1fvExcVpnD8uLg7PP/+8SZ4YQGQuOMJIZAUaNWqE5ORkXL58GbVr10bdunWr7NhRUVGYOnUqXFxc\n0KdPHxQXF+PkyZO4du0aPvrooyo7z5PWrl2L0tJStG/fHo6Ojti8eTNsbW3RpEkTODo6Yvr06Zg+\nfTqEEOjcuTMKCgpw7Ngx1KhRAxMmTMCIESMwf/58DBgwAPPnz4eXlxfOnTsHZ2dndOvWDR988AGG\nDBmCkJAQ9OzZE3v27MHGjRuxbds2g2McMWIEIiIiMG7cOMyaNQvZ2dmIjo7W2CcmJgZeXl5o06YN\nbGxssGHDBtSpUwcNGjQw+Dzvv/8+nn/+eURFReH111/HTz/9hJiYGL2jgNroGxV92kvSN27cwI0b\nN3Dx4kUAQFpaGu7cuQNfX1+4urqiRYsW6N27NyZNmoSVK1dCCIFJkyahX79+aNKkCQCgV69eaNWq\nFUaPHo2FCxciNzcXM2bMwMSJE+Ho6Ajg9593VFQUwsPD8de//hUXLlzAggULEBkZqY5l8uTJWLZs\nGd577z1MnDgRR44cwbp16/Dtt98+5U+JyMqYrn2SiKrLxYsXRYcOHYSDg4NQqVTqx+qoVCqNm15U\nKpXGTSD//ve/hUql0jjW8uXLhZubm8a6TZs2qR8d4+rqKjp37iw2b96sNZ41a9Zo3BUbGRkpgoKC\ndO7zpO+//1506NBBuLi4iNq1a4v27dur74Aut3TpUtGyZUtRq1Yt4ebmJnr16iV+/PFH9farV6+K\nYcOGCRcXF+Hg4CDatWuncTPJ8uXLRePGjYWNjY1o0qSJ+OqrrzSOryiK2Lp1q8a6Ro0aiYULF6qX\njx8/Ltq1aydq1aol2rZtK3bu3ClUKpX6PKtWrRLt2rUTTk5OwtnZWXTr1k0cPXpU6/u+deuWxuvL\nxcbGiqCgIGFraysaNmwo5s6dqzMubcLDw0W/fv001lWWn6cxe/Zs9WOAVCqV+r/r1q1T75OXlyfe\neOMN4ezsLJydncWoUaPE3bt3NY6TlZUl+vbtKxwcHETdunXFtGnTRFFRkcY+qampokuXLsLOzk54\neXmJTz/9tEI8CQkJ6pz4+/uLFStWPPN7I7IWihDP0MRDRERERFaDPYxEREREpBMLRiIiIiLSiQUj\nEREREenEgpGIiIiIdGLBSEREREQ6sWAkIiIiIp1YMBIRERGRTiwYiYiIiEin/wdr/eoLv1plMAAA\nAABJRU5ErkJggg==\n",
- "text": [
- ""
- ]
- },
- {
- "output_type": "stream",
- "stream": "stdout",
- "text": [
- "\n",
- "Python version : 3.4.1\n",
- "compiler : GCC 4.2.1 (Apple Inc. build 5577)\n",
- "\n",
- "system : Darwin\n",
- "release : 13.2.0\n",
- "machine : x86_64\n",
- "processor : i386\n",
- "CPU count : 4\n",
- "interpreter: 64bit\n",
- "\n",
- "\n",
- "\n"
- ]
- }
- ],
- "prompt_number": 26
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "
\n",
- "
"
- ]
- },
- {
- "cell_type": "heading",
- "level": 1,
- "metadata": {},
- "source": [
- "Conclusion"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "[[back to top](#Sections)]"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "We can see that we could speed up the density estimations for our Parzen-window function if we submitted them in parallel. However, on my particular machine, the submission of 6 parallel 6 processes doesn't lead to a further performance improvement, which makes sense for a 4-core CPU. \n",
- "We also notice that there was a significant performance increase when we were using 3 instead of only 2 processes in parallel. However, the performance increase was less significant when we moved up to 4 parallel processes, respectively. \n",
- "This can be attributed to the fact that in this case, the CPU consists of only 4 cores, and system processes, such as the operating system, are also running in the background. Thus, the fourth core simply does not have enough capacity left to further increase the performance of the fourth process to a large extend. And we also have to keep in mind that every additional process comes with an additional overhead for inter-process communication. \n",
"\n",
- "Also, an improvement due to parallel processing only makes sense if our tasks are \"CPU-bound\" where the majority of the task is spent in the CPU in contrast to I/O bound tasks, i.e., tasks that are processing data from a disk. "
+ "\n"
]
- },
- {
- "cell_type": "code",
- "collapsed": false,
- "input": [],
- "language": "python",
- "metadata": {},
- "outputs": []
}
],
- "metadata": {}
+ "source": [
+ "plot_results()\n",
+ "print_sysinfo()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "
\n",
+ "
"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Conclusion"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "[[back to top](#Sections)]"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "We can see that we could speed up the density estimations for our Parzen-window function if we submitted them in parallel. However, on my particular machine, the submission of 6 parallel 6 processes doesn't lead to a further performance improvement, which makes sense for a 4-core CPU. \n",
+ "We also notice that there was a significant performance increase when we were using 3 instead of only 2 processes in parallel. However, the performance increase was less significant when we moved up to 4 parallel processes, respectively. \n",
+ "This can be attributed to the fact that in this case, the CPU consists of only 4 cores, and system processes, such as the operating system, are also running in the background. Thus, the fourth core simply does not have enough capacity left to further increase the performance of the fourth process to a large extend. And we also have to keep in mind that every additional process comes with an additional overhead for inter-process communication. \n",
+ "\n",
+ "Also, an improvement due to parallel processing only makes sense if our tasks are \"CPU-bound\" where the majority of the task is spent in the CPU in contrast to I/O bound tasks, i.e., tasks that are processing data from a disk. "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": []
}
- ]
-}
\ No newline at end of file
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3",
+ "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.6.3"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 1
+}
diff --git a/tutorials/not_so_obvious_python_stuff.ipynb b/tutorials/not_so_obvious_python_stuff.ipynb
index 15569ba..cf683b0 100644
--- a/tutorials/not_so_obvious_python_stuff.ipynb
+++ b/tutorials/not_so_obvious_python_stuff.ipynb
@@ -14,9 +14,7 @@
{
"cell_type": "code",
"execution_count": 1,
- "metadata": {
- "collapsed": false
- },
+ "metadata": {},
"outputs": [],
"source": [
"%load_ext watermark"
@@ -25,18 +23,16 @@
{
"cell_type": "code",
"execution_count": 2,
- "metadata": {
- "collapsed": false
- },
+ "metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
- "Last updated: 16/07/2014 \n",
+ "last updated: 2018-06-09 \n",
"\n",
- "CPython 3.4.1\n",
- "IPython 2.0.0\n"
+ "CPython 3.6.4\n",
+ "IPython 6.2.1\n"
]
}
],
@@ -57,7 +53,6 @@
"cell_type": "markdown",
"metadata": {},
"source": [
- "
\n",
"
"
]
},
@@ -186,10 +181,8 @@
},
{
"cell_type": "code",
- "execution_count": 2,
- "metadata": {
- "collapsed": false
- },
+ "execution_count": 3,
+ "metadata": {},
"outputs": [
{
"name": "stdout",
@@ -230,10 +223,8 @@
},
{
"cell_type": "code",
- "execution_count": 3,
- "metadata": {
- "collapsed": false
- },
+ "execution_count": 4,
+ "metadata": {},
"outputs": [
{
"name": "stdout",
@@ -295,25 +286,23 @@
"cell_type": "markdown",
"metadata": {},
"source": [
- "Python `list`s are mutable objects as we all know. So, if we are using the `+=` operator on `list`s, we extend the `list` by directly modifying the object directly. \n",
+ "Python `list`s are mutable objects as we all know. So, if we are using the `+=` operator on `list`s, we extend the `list` by directly modifying the object. \n",
"\n",
- "However, if we use the assigment via `my_list = my_list + ...`, we create a new list object, which can be demonstrated by the following code:"
+ "However, if we use the assignment via `my_list = my_list + ...`, we create a new list object, which can be demonstrated by the following code:"
]
},
{
"cell_type": "code",
- "execution_count": 6,
- "metadata": {
- "collapsed": false
- },
+ "execution_count": 5,
+ "metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
- "ID: 4366496544\n",
- "ID (+=): 4366496544\n",
- "ID (list = list + ...): 4366495472\n"
+ "ID: 4486856904\n",
+ "ID (+=): 4486856904\n",
+ "ID (list = list + ...): 4486959368\n"
]
}
],
@@ -338,22 +327,20 @@
{
"cell_type": "code",
"execution_count": 6,
- "metadata": {
- "collapsed": false
- },
+ "metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[] \n",
- "ID (initial): 140704077653128 \n",
+ "ID (initial): 4486857224 \n",
"\n",
"[1] \n",
- "ID (append): 140704077653128 \n",
+ "ID (append): 4486857224 \n",
"\n",
"[1, 2] \n",
- "ID (extend): 140704077653128\n"
+ "ID (extend): 4486857224\n"
]
}
],
@@ -390,7 +377,9 @@
"cell_type": "markdown",
"metadata": {},
"source": [
- "\"It often comes as a big surprise for programmers to find (sometimes by way of a hard-to-reproduce bug) that, unlike any other time value, midnight (i.e. `datetime.time(0,0,0)`) is False. A long discussion on the python-ideas mailing list shows that, while surprising, that behavior is desirable—at least in some quarters.\" \n",
+ "\"It often comes as a big surprise for programmers to find (sometimes by way of a hard-to-reproduce bug) that, unlike any other time value, midnight (i.e. `datetime.time(0,0,0)`) is False. A long discussion on the python-ideas mailing list shows that, while surprising, that behavior is desirable — at least in some quarters.\" \n",
+ "\n",
+ "Please note that Python version <= 3.4.5 evaluated the first statement `bool(datetime.time(0,0,0))` as `False`, which was regarded counter-intuitive, since \"12am\" refers to \"midnight.\"\n",
"\n",
"(Original source: [http://lwn.net/SubscriberLink/590299/bf73fe823974acea/](http://lwn.net/SubscriberLink/590299/bf73fe823974acea/))"
]
@@ -404,24 +393,25 @@
},
{
"cell_type": "code",
- "execution_count": 8,
- "metadata": {
- "collapsed": false
- },
+ "execution_count": 3,
+ "metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
- "\"datetime.time(0,0,0)\" (Midnight) -> False\n",
+ "Current python version: 3.6.4\n",
+ "\"datetime.time(0,0,0)\" (Midnight) -> True\n",
"\"datetime.time(1,0,0)\" (1 am) -> True\n"
]
}
],
"source": [
+ "from platform import python_version\n",
"import datetime\n",
"\n",
- "print('\"datetime.time(0,0,0)\" (Midnight) ->', bool(datetime.time(0,0,0)))\n",
+ "print(\"Current python version: \", python_version())\n",
+ "print('\"datetime.time(0,0,0)\" (Midnight) ->', bool(datetime.time(0,0,0))) # Python version <= 3.4.5 evaluates this statement to False\n",
"\n",
"print('\"datetime.time(1,0,0)\" (1 am) ->', bool(datetime.time(1,0,0)))"
]
@@ -460,10 +450,8 @@
},
{
"cell_type": "code",
- "execution_count": 9,
- "metadata": {
- "collapsed": false
- },
+ "execution_count": 8,
+ "metadata": {},
"outputs": [
{
"name": "stdout",
@@ -489,7 +477,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
- "(*I received a comment that this is in fact a CPython artefact and **must not necessarily be true** in all implementations of Python!*)\n",
+ "(*I received a comment that this is in fact a CPython artifact and **must not necessarily be true** in all implementations of Python!*)\n",
"\n",
"So the take home message is: always use \"==\" for equality, \"is\" for identity!\n",
"\n",
@@ -505,10 +493,8 @@
},
{
"cell_type": "code",
- "execution_count": 11,
- "metadata": {
- "collapsed": false
- },
+ "execution_count": 9,
+ "metadata": {},
"outputs": [
{
"name": "stdout",
@@ -537,10 +523,8 @@
},
{
"cell_type": "code",
- "execution_count": 6,
- "metadata": {
- "collapsed": false
- },
+ "execution_count": 10,
+ "metadata": {},
"outputs": [
{
"name": "stdout",
@@ -567,10 +551,8 @@
},
{
"cell_type": "code",
- "execution_count": 12,
- "metadata": {
- "collapsed": false
- },
+ "execution_count": 11,
+ "metadata": {},
"outputs": [
{
"name": "stdout",
@@ -621,23 +603,25 @@
},
{
"cell_type": "code",
- "execution_count": 1,
- "metadata": {
- "collapsed": false
- },
+ "execution_count": 12,
+ "metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"IDs:\n",
- "list1: 4346366472\n",
- "list2: 4346366472\n",
- "list3: 4346366408\n",
- "list4: 4346366536\n",
+ "list1: 4486860424\n",
+ "list2: 4486860424\n",
+ "list3: 4486818632\n",
+ "list4: 4486818568\n",
+ "\n",
+ "list1: [3, 2]\n",
"\n",
"list1: [3, 2]\n",
- "list1: [3, 2]\n"
+ "list2: [3, 2]\n",
+ "list3: [4, 2]\n",
+ "list4: [1, 4]\n"
]
}
],
@@ -655,7 +639,10 @@
"\n",
"list3[0] = 4\n",
"list4[1] = 4\n",
- "print('list1:', list1)"
+ "print('\\nlist1:', list1)\n",
+ "print('list2:', list2)\n",
+ "print('list3:', list3)\n",
+ "print('list4:', list4)"
]
},
{
@@ -674,22 +661,23 @@
},
{
"cell_type": "code",
- "execution_count": 25,
- "metadata": {
- "collapsed": false
- },
+ "execution_count": 13,
+ "metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"IDs:\n",
- "list1: 4377956296\n",
- "list2: 4377961752\n",
- "list3: 4377954928\n",
+ "list1: 4486818824\n",
+ "list2: 4486886024\n",
+ "list3: 4486888200\n",
"\n",
"list1: [[3], [2]]\n",
- "list1: [[3], [2]]\n"
+ "\n",
+ "list1: [[3], [2]]\n",
+ "list2: [[3], [2]]\n",
+ "list3: [[5], [2]]\n"
]
}
],
@@ -707,7 +695,9 @@
"print('list1:', list1)\n",
"\n",
"list3[0][0] = 5\n",
- "print('list1:', list1)"
+ "print('\\nlist1:', list1)\n",
+ "print('list2:', list2)\n",
+ "print('list3:', list3)"
]
},
{
@@ -751,10 +741,8 @@
},
{
"cell_type": "code",
- "execution_count": 9,
- "metadata": {
- "collapsed": false
- },
+ "execution_count": 14,
+ "metadata": {},
"outputs": [
{
"name": "stdout",
@@ -803,10 +791,8 @@
},
{
"cell_type": "code",
- "execution_count": 1,
- "metadata": {
- "collapsed": false
- },
+ "execution_count": 15,
+ "metadata": {},
"outputs": [
{
"name": "stdout",
@@ -838,17 +824,15 @@
},
{
"cell_type": "code",
- "execution_count": 10,
- "metadata": {
- "collapsed": false
- },
+ "execution_count": 16,
+ "metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
- "1397764090.456688\n",
- "1397764090.456688\n"
+ "1528560045.3962939\n",
+ "1528560045.3962939\n"
]
}
],
@@ -891,15 +875,13 @@
"cell_type": "markdown",
"metadata": {},
"source": [
- "Be aware of what is happening when combining \"`in`\" checks with generators, since they won't evaluate from the beginning once a position is \"consumed\"."
+ "Be aware of what is happening when combining `in` checks with generators, since they won't evaluate from the beginning once a position is \"consumed\"."
]
},
{
"cell_type": "code",
- "execution_count": 9,
- "metadata": {
- "collapsed": false
- },
+ "execution_count": 17,
+ "metadata": {},
"outputs": [
{
"name": "stdout",
@@ -922,15 +904,13 @@
"cell_type": "markdown",
"metadata": {},
"source": [
- "Although this defeats the purpose of an generator (in most cases), we can convert a generator into a list to circumvent the problem. "
+ "Although this defeats the purpose of a generator (in most cases), we can convert a generator into a list to circumvent the problem. "
]
},
{
"cell_type": "code",
- "execution_count": 27,
- "metadata": {
- "collapsed": false
- },
+ "execution_count": 18,
+ "metadata": {},
"outputs": [
{
"name": "stdout",
@@ -986,10 +966,8 @@
},
{
"cell_type": "code",
- "execution_count": 28,
- "metadata": {
- "collapsed": false
- },
+ "execution_count": 19,
+ "metadata": {},
"outputs": [
{
"name": "stdout",
@@ -1036,19 +1014,17 @@
"cell_type": "markdown",
"metadata": {},
"source": [
- "Remember the section about the [\"consuming generators\"](consuming_generators)? This example is somewhat related, but the result might still come unexpected. \n",
+ "Remember the section about the [consuming generators](#consuming_generator)? This example is somewhat related, but the result might still come as unexpected. \n",
"\n",
"(Original source: [http://openhome.cc/eGossip/Blog/UnderstandingLambdaClosure3.html](http://openhome.cc/eGossip/Blog/UnderstandingLambdaClosure3.html))\n",
"\n",
- "In the first example below, we call a `lambda` function in a list comprehension, and the value `i` will be dereferenced every time we call `lambda` within the scope of the list comprehension. Since the list comprehension has already been constructed and evaluated when we for-loop through the list, the closure-variable will be set to the last value 4."
+ "In the first example below, we call a `lambda` function in a list comprehension, and the value `i` will be dereferenced every time we call `lambda` within the scope. Since the list comprehension has already been constructed and evaluated when we `for-loop` through the list, the closure-variable will be set to the last value 4."
]
},
{
"cell_type": "code",
- "execution_count": 7,
- "metadata": {
- "collapsed": false
- },
+ "execution_count": 20,
+ "metadata": {},
"outputs": [
{
"name": "stdout",
@@ -1077,10 +1053,8 @@
},
{
"cell_type": "code",
- "execution_count": 9,
- "metadata": {
- "collapsed": false
- },
+ "execution_count": 21,
+ "metadata": {},
"outputs": [
{
"name": "stdout",
@@ -1109,10 +1083,8 @@
},
{
"cell_type": "code",
- "execution_count": 10,
- "metadata": {
- "collapsed": false
- },
+ "execution_count": 22,
+ "metadata": {},
"outputs": [
{
"name": "stdout",
@@ -1173,10 +1145,8 @@
},
{
"cell_type": "code",
- "execution_count": 31,
- "metadata": {
- "collapsed": false
- },
+ "execution_count": 23,
+ "metadata": {},
"outputs": [
{
"name": "stdout",
@@ -1189,10 +1159,13 @@
],
"source": [
"x = 0\n",
+ "\n",
+ "\n",
"def in_func():\n",
" x = 1\n",
" print('in_func:', x)\n",
- " \n",
+ "\n",
+ "\n",
"in_func()\n",
"print('global:', x)"
]
@@ -1206,10 +1179,8 @@
},
{
"cell_type": "code",
- "execution_count": 34,
- "metadata": {
- "collapsed": false
- },
+ "execution_count": 24,
+ "metadata": {},
"outputs": [
{
"name": "stdout",
@@ -1222,11 +1193,14 @@
],
"source": [
"x = 0\n",
+ "\n",
+ "\n",
"def in_func():\n",
" global x\n",
" x = 1\n",
" print('in_func:', x)\n",
- " \n",
+ "\n",
+ "\n",
"in_func()\n",
"print('global:', x)"
]
@@ -1242,10 +1216,8 @@
},
{
"cell_type": "code",
- "execution_count": 36,
- "metadata": {
- "collapsed": false
- },
+ "execution_count": 25,
+ "metadata": {},
"outputs": [
{
"name": "stdout",
@@ -1259,13 +1231,16 @@
],
"source": [
"def outer():\n",
- " x = 1\n",
- " print('outer before:', x)\n",
- " def inner():\n",
- " x = 2\n",
- " print(\"inner:\", x)\n",
- " inner()\n",
- " print(\"outer after:\", x)\n",
+ " x = 1\n",
+ " print('outer before:', x)\n",
+ "\n",
+ " def inner():\n",
+ " x = 2\n",
+ " print(\"inner:\", x)\n",
+ " inner()\n",
+ " print(\"outer after:\", x)\n",
+ "\n",
+ "\n",
"outer()"
]
},
@@ -1278,10 +1253,8 @@
},
{
"cell_type": "code",
- "execution_count": 35,
- "metadata": {
- "collapsed": false
- },
+ "execution_count": 26,
+ "metadata": {},
"outputs": [
{
"name": "stdout",
@@ -1295,14 +1268,17 @@
],
"source": [
"def outer():\n",
- " x = 1\n",
- " print('outer before:', x)\n",
- " def inner():\n",
- " nonlocal x\n",
- " x = 2\n",
- " print(\"inner:\", x)\n",
- " inner()\n",
- " print(\"outer after:\", x)\n",
+ " x = 1\n",
+ " print('outer before:', x)\n",
+ "\n",
+ " def inner():\n",
+ " nonlocal x\n",
+ " x = 2\n",
+ " print(\"inner:\", x)\n",
+ " inner()\n",
+ " print(\"outer after:\", x)\n",
+ "\n",
+ "\n",
"outer()"
]
},
@@ -1340,18 +1316,17 @@
},
{
"cell_type": "code",
- "execution_count": 41,
- "metadata": {
- "collapsed": false
- },
+ "execution_count": 27,
+ "metadata": {},
"outputs": [
{
"ename": "TypeError",
"evalue": "'tuple' object does not support item assignment",
"output_type": "error",
"traceback": [
- "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m\n\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)",
- "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[0mtup\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m(\u001b[0m\u001b[0;36m1\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 2\u001b[0;31m \u001b[0mtup\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m+=\u001b[0m \u001b[0;36m1\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
+ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
+ "\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)",
+ "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[0mtup\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m(\u001b[0m\u001b[0;36m1\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 2\u001b[0;31m \u001b[0mtup\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m+=\u001b[0m \u001b[0;36m1\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
"\u001b[0;31mTypeError\u001b[0m: 'tuple' object does not support item assignment"
]
}
@@ -1365,15 +1340,13 @@
"cell_type": "markdown",
"metadata": {},
"source": [
- "#### But what if we put a mutable object into the immutable tuple? Well, modification works, but we **also** get a `TypeError` at the same time."
+ "### But what if we put a mutable object into the immutable tuple? Well, modification works, but we **also** get a `TypeError` at the same time."
]
},
{
"cell_type": "code",
- "execution_count": 42,
- "metadata": {
- "collapsed": false
- },
+ "execution_count": 28,
+ "metadata": {},
"outputs": [
{
"name": "stdout",
@@ -1387,8 +1360,9 @@
"evalue": "'tuple' object does not support item assignment",
"output_type": "error",
"traceback": [
- "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m\n\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)",
- "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[0mtup\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m(\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 2\u001b[0m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'tup before: '\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mtup\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 3\u001b[0;31m \u001b[0mtup\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m+=\u001b[0m \u001b[0;34m[\u001b[0m\u001b[0;36m1\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
+ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
+ "\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)",
+ "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[0mtup\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m(\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 2\u001b[0m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'tup before: '\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mtup\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 3\u001b[0;31m \u001b[0mtup\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m+=\u001b[0m \u001b[0;34m[\u001b[0m\u001b[0;36m1\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
"\u001b[0;31mTypeError\u001b[0m: 'tuple' object does not support item assignment"
]
}
@@ -1401,19 +1375,9 @@
},
{
"cell_type": "code",
- "execution_count": 43,
- "metadata": {
- "collapsed": false
- },
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "tup after: ([1],)\n"
- ]
- }
- ],
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
"source": [
"print('tup after: ', tup)"
]
@@ -1429,10 +1393,8 @@
},
{
"cell_type": "code",
- "execution_count": 44,
- "metadata": {
- "collapsed": false
- },
+ "execution_count": 29,
+ "metadata": {},
"outputs": [
{
"name": "stdout",
@@ -1452,10 +1414,8 @@
},
{
"cell_type": "code",
- "execution_count": 5,
- "metadata": {
- "collapsed": false
- },
+ "execution_count": 30,
+ "metadata": {},
"outputs": [
{
"name": "stdout",
@@ -1488,15 +1448,13 @@
"cell_type": "markdown",
"metadata": {},
"source": [
- "#### One more note about the `immutable` status of tuples. Tuples are famous for being immutable. However, how comes that this code works?"
+ "### One more note about the `immutable` status of tuples. Tuples are famous for being immutable. However, how comes that this code works?"
]
},
{
"cell_type": "code",
- "execution_count": 6,
- "metadata": {
- "collapsed": false
- },
+ "execution_count": 31,
+ "metadata": {},
"outputs": [
{
"name": "stdout",
@@ -1517,23 +1475,21 @@
"cell_type": "markdown",
"metadata": {},
"source": [
- "What happens \"behind\" the curtains is that the tuple is not modified, but every time a new object is generated, which will inherit the old \"name tag\":"
+ "What happens \"behind\" the curtains is that the tuple is not modified, but a new object is generated every time, which will inherit the old \"name tag\":"
]
},
{
"cell_type": "code",
- "execution_count": 8,
- "metadata": {
- "collapsed": false
- },
+ "execution_count": 32,
+ "metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
- "4337381840\n",
- "4357415496\n",
- "4357289952\n"
+ "4486707912\n",
+ "4485211784\n",
+ "4486955152\n"
]
}
],
@@ -1580,14 +1536,13 @@
},
{
"cell_type": "code",
- "execution_count": 11,
- "metadata": {
- "collapsed": false
- },
+ "execution_count": 33,
+ "metadata": {},
"outputs": [],
"source": [
"import timeit\n",
"\n",
+ "\n",
"def plainlist(n=100000):\n",
" my_list = []\n",
" for i in range(n):\n",
@@ -1595,14 +1550,17 @@
" my_list.append(i)\n",
" return my_list\n",
"\n",
+ "\n",
"def listcompr(n=100000):\n",
" my_list = [i for i in range(n) if i % 5 == 0]\n",
" return my_list\n",
"\n",
+ "\n",
"def generator(n=100000):\n",
" my_gen = (i for i in range(n) if i % 5 == 0)\n",
" return my_gen\n",
"\n",
+ "\n",
"def generator_yield(n=100000):\n",
" for i in range(n):\n",
" if i % 5 == 0:\n",
@@ -1613,27 +1571,25 @@
"cell_type": "markdown",
"metadata": {},
"source": [
- "#### To be fair to the list, let us exhaust the generators:"
+ "### To be fair to the list, let us exhaust the generators:"
]
},
{
"cell_type": "code",
- "execution_count": 13,
- "metadata": {
- "collapsed": false
- },
+ "execution_count": 34,
+ "metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
- "plain_list: 10 loops, best of 3: 22.4 ms per loop\n",
+ "plain_list: 10.8 ms ± 793 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)\n",
"\n",
- "listcompr: 10 loops, best of 3: 20.8 ms per loop\n",
+ "listcompr: 10 ms ± 830 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)\n",
"\n",
- "generator: 10 loops, best of 3: 22 ms per loop\n",
+ "generator: 11.4 ms ± 1 ms per loop (mean ± std. dev. of 7 runs, 100 loops each)\n",
"\n",
- "generator_yield: 10 loops, best of 3: 21.9 ms per loop\n"
+ "generator_yield: 12.3 ms ± 1.82 ms per loop (mean ± std. dev. of 7 runs, 100 loops each)\n"
]
}
],
@@ -1642,25 +1598,29 @@
" for i in plain_list():\n",
" pass\n",
"\n",
+ "\n",
"def test_listcompr(listcompr):\n",
" for i in listcompr():\n",
" pass\n",
"\n",
+ "\n",
"def test_generator(generator):\n",
" for i in generator():\n",
" pass\n",
"\n",
+ "\n",
"def test_generator_yield(generator_yield):\n",
" for i in generator_yield():\n",
" pass\n",
"\n",
- "print('plain_list: ', end = '')\n",
+ "\n",
+ "print('plain_list: ', end='')\n",
"%timeit test_plainlist(plainlist)\n",
- "print('\\nlistcompr: ', end = '')\n",
+ "print('\\nlistcompr: ', end='')\n",
"%timeit test_listcompr(listcompr)\n",
- "print('\\ngenerator: ', end = '')\n",
+ "print('\\ngenerator: ', end='')\n",
"%timeit test_generator(generator)\n",
- "print('\\ngenerator_yield: ', end = '')\n",
+ "print('\\ngenerator_yield: ', end='')\n",
"%timeit test_generator_yield(generator_yield)"
]
},
@@ -1693,21 +1653,19 @@
"metadata": {},
"source": [
"Who has not stumbled across this quote \"we are all consenting adults here\" in the Python community, yet? Unlike in other languages like C++ (sorry, there are many more, but that's one I am most familiar with), we can't really protect class methods from being used outside the class (i.e., by the API user). \n",
- "All we can do is to indicate methods as private to make clear that they are better not used outside the class, but it is really up to the class user, since \"we are all consenting adults here\"! \n",
+ "All we can do is indicate methods as private to make clear that they are not to be used outside the class, but it really is up to the class user, since \"we are all consenting adults here\"! \n",
"So, when we want to mark a class method as private, we can put a single underscore in front of it. \n",
"If we additionally want to avoid name clashes with other classes that might use the same method names, we can prefix the name with a double-underscore to invoke the name mangling.\n",
"\n",
- "This doesn't prevent the class user to access this class member though, but he has to know the trick and also knows that it his own risk...\n",
+ "This doesn't prevent the class users to access this class member though, but they have to know the trick and also know that it is at their own risk...\n",
"\n",
"Let the following example illustrate what I mean:"
]
},
{
"cell_type": "code",
- "execution_count": 11,
- "metadata": {
- "collapsed": false
- },
+ "execution_count": 35,
+ "metadata": {},
"outputs": [
{
"name": "stdout",
@@ -1723,11 +1681,14 @@
"class my_class():\n",
" def public_method(self):\n",
" print('Hello public world!')\n",
+ "\n",
" def __private_method(self):\n",
" print('Hello private world!')\n",
+ "\n",
" def call_private_method_in_class(self):\n",
" self.__private_method()\n",
- " \n",
+ "\n",
+ "\n",
"my_instance = my_class()\n",
"\n",
"my_instance.public_method()\n",
@@ -1768,10 +1729,8 @@
},
{
"cell_type": "code",
- "execution_count": 3,
- "metadata": {
- "collapsed": false
- },
+ "execution_count": 36,
+ "metadata": {},
"outputs": [
{
"name": "stdout",
@@ -1791,10 +1750,8 @@
},
{
"cell_type": "code",
- "execution_count": 4,
- "metadata": {
- "collapsed": false
- },
+ "execution_count": 37,
+ "metadata": {},
"outputs": [
{
"name": "stdout",
@@ -1818,15 +1775,13 @@
"source": [
"
\n",
"
\n",
- "**The solution** is that we are iterating through the list index by index, and if we remove one of the items in-between, we inevitably mess around with the indexing, look at the following example, and it will become clear:"
+ "**The solution** is that we are iterating through the list index by index, and if we remove one of the items in-between, we inevitably mess around with the indexing. Look at the following example and it will become clear:"
]
},
{
"cell_type": "code",
- "execution_count": 7,
- "metadata": {
- "collapsed": false
- },
+ "execution_count": 38,
+ "metadata": {},
"outputs": [
{
"name": "stdout",
@@ -1880,10 +1835,8 @@
},
{
"cell_type": "code",
- "execution_count": 14,
- "metadata": {
- "collapsed": false
- },
+ "execution_count": 39,
+ "metadata": {},
"outputs": [
{
"name": "stdout",
@@ -1939,23 +1892,22 @@
"cell_type": "markdown",
"metadata": {},
"source": [
- "As we have all encountered it 1 (x10000) time(s) in our live, the infamous `IndexError`:"
+ "As we have all encountered it 1 (x10000) time(s) in our lives, the infamous `IndexError`:"
]
},
{
"cell_type": "code",
- "execution_count": 15,
- "metadata": {
- "collapsed": false
- },
+ "execution_count": 40,
+ "metadata": {},
"outputs": [
{
"ename": "IndexError",
"evalue": "list index out of range",
"output_type": "error",
"traceback": [
- "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m\n\u001b[0;31mIndexError\u001b[0m Traceback (most recent call last)",
- "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[0mmy_list\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m[\u001b[0m\u001b[0;36m1\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m2\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m3\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m4\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m5\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 2\u001b[0;31m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mmy_list\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m5\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
+ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
+ "\u001b[0;31mIndexError\u001b[0m Traceback (most recent call last)",
+ "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[0mmy_list\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m[\u001b[0m\u001b[0;36m1\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m2\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m3\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m4\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m5\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 2\u001b[0;31m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mmy_list\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m5\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
"\u001b[0;31mIndexError\u001b[0m: list index out of range"
]
}
@@ -1969,24 +1921,14 @@
"cell_type": "markdown",
"metadata": {},
"source": [
- "But suprisingly, it is not raised when we are doing list slicing, which can be a really pain for debugging:"
+ "But suprisingly, it is not raised when we are doing list slicing, which can be a real pain when debugging:"
]
},
{
"cell_type": "code",
- "execution_count": 16,
- "metadata": {
- "collapsed": false
- },
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "[]\n"
- ]
- }
- ],
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
"source": [
"my_list = [1, 2, 3, 4, 5]\n",
"print(my_list[5:])"
@@ -2025,23 +1967,14 @@
},
{
"cell_type": "code",
- "execution_count": 37,
- "metadata": {
- "collapsed": false
- },
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "global\n"
- ]
- }
- ],
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
"source": [
"def my_func():\n",
" print(var)\n",
"\n",
+ "\n",
"var = 'global'\n",
"my_func()"
]
@@ -2055,23 +1988,14 @@
},
{
"cell_type": "code",
- "execution_count": 38,
- "metadata": {
- "collapsed": false
- },
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "global\n"
- ]
- }
- ],
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
"source": [
"def my_func():\n",
" var = 'locally changed'\n",
"\n",
+ "\n",
"var = 'global'\n",
"my_func()\n",
"print(var)"
@@ -2086,28 +2010,15 @@
},
{
"cell_type": "code",
- "execution_count": 40,
- "metadata": {
- "collapsed": false
- },
- "outputs": [
- {
- "ename": "UnboundLocalError",
- "evalue": "local variable 'var' referenced before assignment",
- "output_type": "error",
- "traceback": [
- "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m\n\u001b[0;31mUnboundLocalError\u001b[0m Traceback (most recent call last)",
- "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[1;32m 4\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 5\u001b[0m \u001b[0mvar\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m'global'\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 6\u001b[0;31m \u001b[0mmy_func\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
- "\u001b[0;32m\u001b[0m in \u001b[0;36mmy_func\u001b[0;34m()\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mmy_func\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 2\u001b[0;31m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mvar\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;31m# want to access global variable\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 3\u001b[0m \u001b[0mvar\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m'locally changed'\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 4\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 5\u001b[0m \u001b[0mvar\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m'global'\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
- "\u001b[0;31mUnboundLocalError\u001b[0m: local variable 'var' referenced before assignment"
- ]
- }
- ],
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
"source": [
"def my_func():\n",
- " print(var) # want to access global variable\n",
- " var = 'locally changed' # but Python thinks we forgot to define the local variable!\n",
- " \n",
+ " print(var) # want to access global variable\n",
+ " var = 'locally changed' # but Python thinks we forgot to define the local variable!\n",
+ "\n",
+ "\n",
"var = 'global'\n",
"my_func()"
]
@@ -2121,25 +2032,15 @@
},
{
"cell_type": "code",
- "execution_count": 43,
- "metadata": {
- "collapsed": false
- },
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "global\n",
- "locally changed\n"
- ]
- }
- ],
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
"source": [
"def my_func():\n",
" global var\n",
- " print(var) # want to access global variable\n",
- " var = 'locally changed' # changes the gobal variable\n",
+ " print(var) # want to access global variable\n",
+ " var = 'locally changed' # changes the gobal variable\n",
+ "\n",
"\n",
"var = 'global'\n",
"\n",
@@ -2175,25 +2076,14 @@
"cell_type": "markdown",
"metadata": {},
"source": [
- "Let's assume a scenario where we want to duplicate sub`list`s of values stored in another list. If we want to create independent sub`list` object, using the arithmetic multiplication operator could lead to rather unexpected (or undesired) results:"
+ "Let's assume a scenario where we want to duplicate sub`list`s of values stored in another list. If we want to create an independent sub`list` object, using the arithmetic multiplication operator could lead to rather unexpected (or undesired) results:"
]
},
{
"cell_type": "code",
- "execution_count": 24,
- "metadata": {
- "collapsed": false
- },
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "initially ---> [[1, 2, 3], [1, 2, 3]]\n",
- "after my_list1[1][0] = 'a' ---> [['a', 2, 3], ['a', 2, 3]]\n"
- ]
- }
- ],
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
"source": [
"my_list1 = [[1, 2, 3]] * 2\n",
"\n",
@@ -2215,20 +2105,9 @@
},
{
"cell_type": "code",
- "execution_count": 25,
- "metadata": {
- "collapsed": false
- },
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "initially: ---> [[1, 2, 3], [1, 2, 3]]\n",
- "after my_list2[1][0] = 'a': ---> [[1, 2, 3], ['a', 2, 3]]\n"
- ]
- }
- ],
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
"source": [
"my_list2 = [[1, 2, 3] for i in range(2)]\n",
"\n",
@@ -2249,22 +2128,11 @@
},
{
"cell_type": "code",
- "execution_count": 26,
- "metadata": {
- "collapsed": false
- },
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "id my_list1: 4350764680, id my_list2: 4350766472\n",
- "id my_list1: 4350764680, id my_list2: 4350766664\n"
- ]
- }
- ],
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
"source": [
- "for a,b in zip(my_list1, my_list2):\n",
+ "for a, b in zip(my_list1, my_list2):\n",
" print('id my_list1: {}, id my_list2: {}'.format(id(a), id(b)))"
]
},
@@ -2336,11 +2204,7 @@
"- [Handling exceptions](#handling_exceptions)\n",
"- [next() function and .next() method](#next_next)\n",
"- [Loop variables and leaking into the global scope](#loop_leak)\n",
- "- [Comparing unorderable types](#compare_unorder)\n",
- "\n",
- "
\n",
- "
\n",
- "\n"
+ "- [Comparing unorderable types](#compare_unorder)"
]
},
{
@@ -2371,9 +2235,9 @@
"metadata": {},
"source": [
"\n",
- "####- Python 2: \n",
+ "#### Python 2: \n",
"We have ASCII `str()` types, separate `unicode()`, but no `byte` type\n",
- "####- Python 3: \n",
+ "#### Python 3: \n",
"Now, we finally have Unicode (utf-8) `str`ings, and 2 byte classes: `byte` and `bytearray`s"
]
},
@@ -2381,12 +2245,12 @@
"cell_type": "code",
"execution_count": null,
"metadata": {
- "collapsed": false
+ "code_folding": []
},
"outputs": [],
"source": [
"#############\n",
- "# Python 2\n",
+ "# Python 2 #\n",
"#############\n",
"\n",
">>> type(unicode('is like a python3 str()'))\n",
@@ -2454,9 +2318,7 @@
{
"cell_type": "code",
"execution_count": null,
- "metadata": {
- "collapsed": false
- },
+ "metadata": {},
"outputs": [],
"source": [
"# Python 2\n",
@@ -2485,9 +2347,7 @@
{
"cell_type": "code",
"execution_count": null,
- "metadata": {
- "collapsed": false
- },
+ "metadata": {},
"outputs": [],
"source": [
"# Python 2\n",
@@ -2533,9 +2393,7 @@
{
"cell_type": "code",
"execution_count": null,
- "metadata": {
- "collapsed": false
- },
+ "metadata": {},
"outputs": [],
"source": [
"# Python 2\n",
@@ -2563,16 +2421,14 @@
"cell_type": "markdown",
"metadata": {},
"source": [
- "\n",
- "
\n",
- "
"
+ ""
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
- "###`xrange()` "
+ "### `xrange()`"
]
},
{
@@ -2594,23 +2450,21 @@
{
"cell_type": "code",
"execution_count": null,
- "metadata": {
- "collapsed": false
- },
+ "metadata": {},
"outputs": [],
"source": [
"# Python 2\n",
- "> python -m timeit 'for i in range(1000000):' ' pass'\n",
+ ">>> python -m timeit 'for i in range(1000000):' ' pass'\n",
"10 loops, best of 3: 66 msec per loop\n",
"\n",
" > python -m timeit 'for i in xrange(1000000):' ' pass'\n",
"10 loops, best of 3: 27.8 msec per loop\n",
"\n",
"# Python 3\n",
- "> python3 -m timeit 'for i in range(1000000):' ' pass'\n",
+ ">>> python3 -m timeit 'for i in range(1000000):' ' pass'\n",
"10 loops, best of 3: 51.1 msec per loop\n",
"\n",
- "> python3 -m timeit 'for i in xrange(1000000):' ' pass'\n",
+ ">>> python3 -m timeit 'for i in xrange(1000000):' ' pass'\n",
"Traceback (most recent call last):\n",
" File \"/Library/Frameworks/Python.framework/Versions/3.4/lib/python3.4/timeit.py\", line 292, in main\n",
" x = t.timeit(number)\n",
@@ -2656,9 +2510,7 @@
{
"cell_type": "code",
"execution_count": null,
- "metadata": {
- "collapsed": false
- },
+ "metadata": {},
"outputs": [],
"source": [
"# Python 2\n",
@@ -2719,9 +2571,7 @@
{
"cell_type": "code",
"execution_count": null,
- "metadata": {
- "collapsed": false
- },
+ "metadata": {},
"outputs": [],
"source": [
"# Python 2\n",
@@ -2742,12 +2592,8 @@
]
},
{
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "collapsed": false
- },
- "outputs": [],
+ "cell_type": "markdown",
+ "metadata": {},
"source": [
"\n",
"
\n",
@@ -2774,15 +2620,13 @@
"source": [
"\n",
"\n",
- "Where you can use both function and method in Python 2.7.5, the `next()` function is all that remain in Python 3!"
+ "Where you can use both function and method in Python 2.7.5, the `next()` function is all that remains in Python 3!"
]
},
{
"cell_type": "code",
"execution_count": null,
- "metadata": {
- "collapsed": false
- },
+ "metadata": {},
"outputs": [],
"source": [
"# Python 2\n",
@@ -2831,59 +2675,48 @@
"source": [
"This goes back to a change that was made in Python 3.x and is described in [What’s New In Python 3.0](https://docs.python.org/3/whatsnew/3.0.html) as follows:\n",
"\n",
- "\"List comprehensions no longer support the syntactic form `[... for var in item1, item2, ...]`. Use `[... for var in (item1, item2, ...)]` instead. Also note that list comprehensions have different semantics: they are closer to syntactic sugar for a generator expression inside a `list()` constructor, and in particular the loop control variables are no longer leaked into the surrounding scope.\""
+ "*\"List comprehensions no longer support the syntactic form `[... for var in item1, item2, ...]`. Use `[... for var in (item1, item2, ...)]` instead. Also note that list comprehensions have different semantics: they are closer to syntactic sugar for a generator expression inside a `list()` constructor, and in particular the loop control variables are no longer leaked into the surrounding scope.\"*"
]
},
{
"cell_type": "code",
- "execution_count": 4,
- "metadata": {
- "collapsed": false
- },
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "This code cell was executed in Python 3.3.5\n",
- "[0, 1, 2, 3, 4]\n",
- "1 -> i in global\n"
- ]
- }
- ],
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
"source": [
- "from platform import python_version\n",
- "print('This code cell was executed in Python', python_version())\n",
- "\n",
- "i = 1\n",
- "print([i for i in range(5)])\n",
- "print(i, '-> i in global')"
+ ">>> from platform import python_version\n",
+ ">>> print 'This code cell was executed in Python', python_version()\n",
+ "'This code cell was executed in Python 2.7.6'\n",
+ ">>> i = 1\n",
+ ">>> print [i for i in range(5)]\n",
+ "'[0, 1, 2, 3, 4]'\n",
+ ">>> print i, '-> i in global'\n",
+ "'4 -> i in global'"
]
},
{
"cell_type": "code",
- "execution_count": 9,
- "metadata": {
- "collapsed": false
- },
+ "execution_count": 61,
+ "metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
- "This code cell was executed in Python 2.7.6\n",
+ "This code cell was executed in Python 3.6.4\n",
"[0, 1, 2, 3, 4]\n",
- "4 -> i in global\n"
+ "1 -> i in global\n"
]
}
],
"source": [
+ "%%python3\n",
"from platform import python_version\n",
- "print 'This code cell was executed in Python', python_version()\n",
+ "print('This code cell was executed in Python', python_version())\n",
"\n",
"i = 1\n",
- "print [i for i in range(5)]\n",
- "print i, '-> i in global' "
+ "print([i for i in range(5)])\n",
+ "print(i, '-> i in global')"
]
},
{
@@ -2899,7 +2732,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
- "#### Python 3.x prevents us from comparing unorderable types"
+ "### Python 3.x prevents us from comparing unorderable types"
]
},
{
@@ -2911,53 +2744,50 @@
},
{
"cell_type": "code",
- "execution_count": 8,
- "metadata": {
- "collapsed": false
- },
+ "execution_count": 101,
+ "metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
- "This code cell was executed in Python 2.7.6\n",
- "False\n",
- "True\n",
- "False\n"
+ "Couldn't find program: 'python2'\n"
]
}
],
"source": [
- "from platform import python_version\n",
- "print 'This code cell was executed in Python', python_version()\n",
- "\n",
- "print [1, 2] > 'foo'\n",
- "print (1, 2) > 'foo'\n",
- "print [1, 2] > (1, 2)"
+ ">>> from platform import python_version\n",
+ ">>> print 'This code cell was executed in Python', python_version()\n",
+ "'This code cell was executed in Python 2.7.6'\n",
+ ">>> print [1, 2] > 'foo'\n",
+ "'False'\n",
+ ">>> print (1, 2) > 'foo'\n",
+ "'True'\n",
+ ">>> print [1, 2] > (1, 2)\n",
+ "'False'"
]
},
{
"cell_type": "code",
- "execution_count": 3,
- "metadata": {
- "collapsed": false
- },
+ "execution_count": 67,
+ "metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
- "This code cell was executed in Python 3.3.5\n"
+ "This code cell was executed in Python 3.6.4\n"
]
},
{
"ename": "TypeError",
- "evalue": "unorderable types: list() > str()",
+ "evalue": "'>' not supported between instances of 'list' and 'str'",
"output_type": "error",
"traceback": [
- "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m\n\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)",
- "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[1;32m 2\u001b[0m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'This code cell was executed in Python'\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mpython_version\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 3\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 4\u001b[0;31m \u001b[0;34m[\u001b[0m\u001b[0;36m1\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m2\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m>\u001b[0m \u001b[0;34m'foo'\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 5\u001b[0m \u001b[0;34m(\u001b[0m\u001b[0;36m1\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m2\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m>\u001b[0m \u001b[0;34m'foo'\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 6\u001b[0m \u001b[0;34m[\u001b[0m\u001b[0;36m1\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m2\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m>\u001b[0m \u001b[0;34m(\u001b[0m\u001b[0;36m1\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m2\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
- "\u001b[0;31mTypeError\u001b[0m: unorderable types: list() > str()"
+ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
+ "\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)",
+ "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[1;32m 2\u001b[0m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'This code cell was executed in Python'\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mpython_version\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 3\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 4\u001b[0;31m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m1\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m2\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m>\u001b[0m \u001b[0;34m'foo'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 5\u001b[0m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m1\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m2\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m>\u001b[0m \u001b[0;34m'foo'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 6\u001b[0m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m1\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m2\u001b[0m\u001b[0;34m]\u001b[0m \u001b[0;34m>\u001b[0m \u001b[0;34m(\u001b[0m\u001b[0;36m1\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m2\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
+ "\u001b[0;31mTypeError\u001b[0m: '>' not supported between instances of 'list' and 'str'"
]
}
],
@@ -3003,10 +2833,8 @@
},
{
"cell_type": "code",
- "execution_count": 8,
- "metadata": {
- "collapsed": false
- },
+ "execution_count": 14,
+ "metadata": {},
"outputs": [],
"source": [
"def foo1(x: 'insert x here', y: 'insert x^2 here'):\n",
@@ -3023,10 +2851,8 @@
},
{
"cell_type": "code",
- "execution_count": 10,
- "metadata": {
- "collapsed": false
- },
+ "execution_count": 15,
+ "metadata": {},
"outputs": [],
"source": [
"def foo2(x, y) -> 'Hi!':\n",
@@ -3050,10 +2876,8 @@
},
{
"cell_type": "code",
- "execution_count": 9,
- "metadata": {
- "collapsed": false
- },
+ "execution_count": 17,
+ "metadata": {},
"outputs": [
{
"name": "stdout",
@@ -3069,10 +2893,8 @@
},
{
"cell_type": "code",
- "execution_count": 11,
- "metadata": {
- "collapsed": false
- },
+ "execution_count": 16,
+ "metadata": {},
"outputs": [
{
"name": "stdout",
@@ -3099,61 +2921,56 @@
},
{
"cell_type": "code",
- "execution_count": null,
- "metadata": {
- "collapsed": false
- },
+ "execution_count": 18,
+ "metadata": {},
"outputs": [],
"source": [
"def is_palindrome(a):\n",
" \"\"\"\n",
" Case-and punctuation insensitive check if a string is a palindrom.\n",
- " \n",
+ "\n",
" Keyword arguments:\n",
" a (str): The string to be checked if it is a palindrome.\n",
- " \n",
+ "\n",
" Returns `True` if input string is a palindrome, else False.\n",
- " \n",
+ "\n",
" \"\"\"\n",
" stripped_str = [l for l in my_str.lower() if l.isalpha()]\n",
- " return stripped_str == stripped_str[::-1]\n",
- " "
+ " return stripped_str == stripped_str[::-1]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
- "However, function annotations can be useful to indicate that work is still in progress in some cases. But they are optional and I see them very very rarely.\n",
+ "However, function annotations can be useful to indicate that work is still in progress in some cases. But they are optional and I see them very, very rarely.\n",
"\n",
"As it is stated in [PEP3107](http://legacy.python.org/dev/peps/pep-3107/#fundamentals-of-function-annotations):\n",
"\n",
- "1. Function annotations, both for parameters and return values, are completely optional.\n",
+ "1. *Function annotations, both for parameters and return values, are completely optional.*\n",
"\n",
- "2. Function annotations are nothing more than a way of associating arbitrary Python expressions with various parts of a function at compile-time.\n"
+ "2. *Function annotations are nothing more than a way of associating arbitrary Python expressions with various parts of a function at compile-time.*\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
- "The nice thing about function annotations is their `__annotations__` attribute, which is dictionary of all the parameters and/or the `return` value you annotated."
+ "The nice thing about function annotations is their `__annotations__` attribute, which is a dictionary of all the parameters and/or the `return` value you annotated."
]
},
{
"cell_type": "code",
- "execution_count": 17,
- "metadata": {
- "collapsed": false
- },
+ "execution_count": 19,
+ "metadata": {},
"outputs": [
{
"data": {
"text/plain": [
- "{'y': 'insert x^2 here', 'x': 'insert x here'}"
+ "{'x': 'insert x here', 'y': 'insert x^2 here'}"
]
},
- "execution_count": 17,
+ "execution_count": 19,
"metadata": {},
"output_type": "execute_result"
}
@@ -3164,10 +2981,8 @@
},
{
"cell_type": "code",
- "execution_count": 18,
- "metadata": {
- "collapsed": false
- },
+ "execution_count": 20,
+ "metadata": {},
"outputs": [
{
"data": {
@@ -3175,7 +2990,7 @@
"{'return': 'Hi!'}"
]
},
- "execution_count": 18,
+ "execution_count": 20,
"metadata": {},
"output_type": "execute_result"
}
@@ -3207,7 +3022,6 @@
"cell_type": "markdown",
"metadata": {},
"source": [
- "
\n",
"
\n",
""
]
@@ -3219,6 +3033,13 @@
"## Abortive statements in `finally` blocks"
]
},
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "[[back to top](#sections)]"
+ ]
+ },
{
"cell_type": "markdown",
"metadata": {},
@@ -3228,22 +3049,9 @@
},
{
"cell_type": "code",
- "execution_count": 24,
- "metadata": {
- "collapsed": false
- },
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "in try:\n",
- "do some stuff\n",
- "an error occurred\n",
- "always execute finally\n"
- ]
- }
- ],
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
"source": [
"def try_finally1():\n",
" try:\n",
@@ -3256,7 +3064,8 @@
" print('no error occurred')\n",
" finally:\n",
" print('always execute finally')\n",
- " \n",
+ "\n",
+ "\n",
"try_finally1()"
]
},
@@ -3272,9 +3081,7 @@
{
"cell_type": "code",
"execution_count": 21,
- "metadata": {
- "collapsed": false
- },
+ "metadata": {},
"outputs": [
{
"name": "stdout",
@@ -3294,7 +3101,8 @@
" finally:\n",
" print(\"do some stuff in finally block\")\n",
" return \"always execute finally\"\n",
- " \n",
+ "\n",
+ "\n",
"print(try_finally2())"
]
},
@@ -3319,7 +3127,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
- "#Assigning types to variables as values"
+ "## Assigning types to variables as values"
]
},
{
@@ -3338,10 +3146,8 @@
},
{
"cell_type": "code",
- "execution_count": 1,
- "metadata": {
- "collapsed": false
- },
+ "execution_count": 22,
+ "metadata": {},
"outputs": [
{
"data": {
@@ -3349,7 +3155,7 @@
"'123'"
]
},
- "execution_count": 1,
+ "execution_count": 22,
"metadata": {},
"output_type": "execute_result"
}
@@ -3361,20 +3167,18 @@
},
{
"cell_type": "code",
- "execution_count": 4,
- "metadata": {
- "collapsed": false
- },
+ "execution_count": 23,
+ "metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
- "0 \n",
- "1 \n",
- "2.0 \n",
- "3 \n",
- "4 \n"
+ "0.0 \n",
+ "1.0 \n",
+ "2 \n",
+ "3.0 \n",
+ "4.0 \n"
]
}
],
@@ -3400,7 +3204,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
- "# Only the first clause of generators is evaluated immediately"
+ "## Only the first clause of generators is evaluated immediately"
]
},
{
@@ -3420,18 +3224,17 @@
},
{
"cell_type": "code",
- "execution_count": 18,
- "metadata": {
- "collapsed": false
- },
+ "execution_count": 24,
+ "metadata": {},
"outputs": [
{
"ename": "ZeroDivisionError",
"evalue": "division by zero",
"output_type": "error",
"traceback": [
- "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m\n\u001b[0;31mZeroDivisionError\u001b[0m Traceback (most recent call last)",
- "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mgen_fails\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m(\u001b[0m\u001b[0mi\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mi\u001b[0m \u001b[0;32min\u001b[0m \u001b[0;36m1\u001b[0m\u001b[0;34m/\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
+ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
+ "\u001b[0;31mZeroDivisionError\u001b[0m Traceback (most recent call last)",
+ "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mgen_fails\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m(\u001b[0m\u001b[0mi\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mi\u001b[0m \u001b[0;32min\u001b[0m \u001b[0;36m1\u001b[0m\u001b[0;34m/\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
"\u001b[0;31mZeroDivisionError\u001b[0m: division by zero"
]
}
@@ -3449,10 +3252,8 @@
},
{
"cell_type": "code",
- "execution_count": 19,
- "metadata": {
- "collapsed": false
- },
+ "execution_count": 25,
+ "metadata": {},
"outputs": [],
"source": [
"gen_succeeds = (i for i in range(5) for j in 1/0)"
@@ -3460,28 +3261,27 @@
},
{
"cell_type": "code",
- "execution_count": 20,
- "metadata": {
- "collapsed": false
- },
+ "execution_count": 26,
+ "metadata": {},
"outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "But obviously fails when we iterate ...\n"
+ ]
+ },
{
"ename": "ZeroDivisionError",
"evalue": "division by zero",
"output_type": "error",
"traceback": [
- "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m\n\u001b[0;31mZeroDivisionError\u001b[0m Traceback (most recent call last)",
- "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'But obviously fails when we iterate ...'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 2\u001b[0;31m \u001b[0;32mfor\u001b[0m \u001b[0mi\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mgen_succeeds\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 3\u001b[0m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mi\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
- "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m(.0)\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mgen_succeeds\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m(\u001b[0m\u001b[0mi\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mi\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mrange\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m5\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mj\u001b[0m \u001b[0;32min\u001b[0m \u001b[0;36m1\u001b[0m\u001b[0;34m/\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
+ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
+ "\u001b[0;31mZeroDivisionError\u001b[0m Traceback (most recent call last)",
+ "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'But obviously fails when we iterate ...'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 2\u001b[0;31m \u001b[0;32mfor\u001b[0m \u001b[0mi\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mgen_succeeds\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 3\u001b[0m \u001b[0mprint\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mi\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
+ "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m(.0)\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mgen_succeeds\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m(\u001b[0m\u001b[0mi\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mi\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mrange\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m5\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mj\u001b[0m \u001b[0;32min\u001b[0m \u001b[0;36m1\u001b[0m\u001b[0;34m/\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
"\u001b[0;31mZeroDivisionError\u001b[0m: division by zero"
]
- },
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "But obviously fails when we iterate ...\n"
- ]
}
],
"source": [
@@ -3503,7 +3303,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
- "##Keyword argument unpacking syntax - `*args` and `**kwargs`"
+ "## Keyword argument unpacking syntax - `*args` and `**kwargs`"
]
},
{
@@ -3517,22 +3317,20 @@
"cell_type": "markdown",
"metadata": {},
"source": [
- "Python has a very convenient \"keyword argument unpacking syntax\" (often also referred to as \"splat\"-operators). This is particularly useful, if we want to define a function that can take a arbitrary number of input arguments."
+ "Python has a very convenient \"keyword argument unpacking syntax\" (often referred to as \"splat\"-operators). This is particularly useful, if we want to define a function that can take a arbitrary number of input arguments."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
- "#### Single-asterisk (*args)"
+ "### Single-asterisk (*args)"
]
},
{
"cell_type": "code",
- "execution_count": 55,
- "metadata": {
- "collapsed": false
- },
+ "execution_count": 27,
+ "metadata": {},
"outputs": [
{
"name": "stdout",
@@ -3550,6 +3348,7 @@
" print('args contents:', args)\n",
" print('1st argument:', args[0])\n",
"\n",
+ "\n",
"a_func(0, 1, 'a', 'b', 'c')"
]
},
@@ -3557,22 +3356,20 @@
"cell_type": "markdown",
"metadata": {},
"source": [
- "#### Double-asterisk (**kwargs)"
+ "### Double-asterisk (**kwargs)"
]
},
{
"cell_type": "code",
- "execution_count": 56,
- "metadata": {
- "collapsed": false
- },
+ "execution_count": 28,
+ "metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"type of kwargs: \n",
- "kwargs contents: {'d': 4, 'a': 1, 'c': 3, 'b': 2}\n",
+ "kwargs contents: {'a': 1, 'b': 2, 'c': 3, 'd': 4}\n",
"value of argument a: 1\n"
]
}
@@ -3582,7 +3379,8 @@
" print('type of kwargs:', type(kwargs))\n",
" print('kwargs contents: ', kwargs)\n",
" print('value of argument a:', kwargs['a'])\n",
- " \n",
+ "\n",
+ "\n",
"b_func(a=1, b=2, c=3, d=4)"
]
},
@@ -3590,16 +3388,14 @@
"cell_type": "markdown",
"metadata": {},
"source": [
- "#### (Partially) unpacking of iterables\n",
+ "### (Partially) unpacking of iterables\n",
"Another useful application of the \"unpacking\"-operator is the unpacking of lists and other other iterables."
]
},
{
"cell_type": "code",
- "execution_count": 57,
- "metadata": {
- "collapsed": false
- },
+ "execution_count": 29,
+ "metadata": {},
"outputs": [
{
"name": "stdout",
@@ -3654,10 +3450,8 @@
},
{
"cell_type": "code",
- "execution_count": 53,
- "metadata": {
- "collapsed": false
- },
+ "execution_count": 30,
+ "metadata": {},
"outputs": [
{
"name": "stdout",
@@ -3673,10 +3467,12 @@
" def __new__(clss, *args, **kwargs):\n",
" print('excecuted __new__')\n",
" return None\n",
+ "\n",
" def __init__(self, an_arg):\n",
" print('excecuted __init__')\n",
" self.an_arg = an_arg\n",
- " \n",
+ "\n",
+ "\n",
"a_object = a_class(1)\n",
"print('Type of a_object:', type(a_object))"
]
@@ -3691,10 +3487,8 @@
},
{
"cell_type": "code",
- "execution_count": 54,
- "metadata": {
- "collapsed": false
- },
+ "execution_count": 31,
+ "metadata": {},
"outputs": [
{
"name": "stdout",
@@ -3713,10 +3507,12 @@
" print('excecuted __new__')\n",
" inst = super(a_class, cls).__new__(cls)\n",
" return inst\n",
+ "\n",
" def __init__(self, an_arg):\n",
" print('excecuted __init__')\n",
" self.an_arg = an_arg\n",
- " \n",
+ "\n",
+ "\n",
"a_object = a_class(1)\n",
"print('Type of a_object:', type(a_object))\n",
"print('a_object.an_arg: ', a_object.an_arg)"
@@ -3724,10 +3520,8 @@
},
{
"cell_type": "code",
- "execution_count": 5,
- "metadata": {
- "collapsed": false
- },
+ "execution_count": 32,
+ "metadata": {},
"outputs": [
{
"name": "stdout",
@@ -3750,10 +3544,8 @@
},
{
"cell_type": "code",
- "execution_count": 6,
- "metadata": {
- "collapsed": false
- },
+ "execution_count": 33,
+ "metadata": {},
"outputs": [
{
"name": "stdout",
@@ -3799,7 +3591,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
- "I would claim that the conditional \"else\" is every programmer's daily bread and butter. However, there is a second flavor of \"else\"-clauses in Python, which I will call \"completion else\" (for reason that will become clear later). \n",
+ "I would claim that the conditional `else` is every programmer's daily bread and butter. However, there is a second flavor of `else`-clauses in Python, which I will call \"completion else\" (for reason that will become clear later). \n",
"But first, let us take a look at our \"traditional\" conditional else that we all are familiar with. \n"
]
},
@@ -3807,15 +3599,13 @@
"cell_type": "markdown",
"metadata": {},
"source": [
- "###Conditional else:"
+ "### Conditional else:"
]
},
{
"cell_type": "code",
- "execution_count": 3,
- "metadata": {
- "collapsed": false
- },
+ "execution_count": 34,
+ "metadata": {},
"outputs": [
{
"name": "stdout",
@@ -3837,10 +3627,8 @@
},
{
"cell_type": "code",
- "execution_count": 4,
- "metadata": {
- "collapsed": false
- },
+ "execution_count": 35,
+ "metadata": {},
"outputs": [
{
"name": "stdout",
@@ -3866,7 +3654,7 @@
"source": [
"Why am I showing those simple examples? I think they are good to highlight some of the key points: It is **either** the code under the `if` clause that is executed, **or** the code under the `else` block, but not both. \n",
"If the condition of the `if` clause evaluates to `True`, the `if`-block is exectured, and if it evaluated to `False`, it is the `else` block. \n",
- "\n",
+ "
\n",
"### Completion else\n",
"**In contrast** to the **either...or*** situation that we know from the conditional `else`, the completion `else` is executed if a code block finished. \n",
"To show you an example, let us use `else` for error-handling:"
@@ -3881,10 +3669,8 @@
},
{
"cell_type": "code",
- "execution_count": 5,
- "metadata": {
- "collapsed": false
- },
+ "execution_count": 36,
+ "metadata": {},
"outputs": [
{
"name": "stdout",
@@ -3906,10 +3692,8 @@
},
{
"cell_type": "code",
- "execution_count": 6,
- "metadata": {
- "collapsed": false
- },
+ "execution_count": 37,
+ "metadata": {},
"outputs": [
{
"name": "stdout",
@@ -3932,7 +3716,6 @@
"cell_type": "markdown",
"metadata": {},
"source": [
- "
\n",
"In the code above, we can see that the code under the **`else`-clause is only executed if the `try-block` was executed without encountering an error, i.e., if the `try`-block is \"complete\".** \n",
"The same rule applies to the \"completion\" `else` in while- and for-loops, which you can confirm in the following samples below."
]
@@ -3946,10 +3729,8 @@
},
{
"cell_type": "code",
- "execution_count": 7,
- "metadata": {
- "collapsed": false
- },
+ "execution_count": 38,
+ "metadata": {},
"outputs": [
{
"name": "stdout",
@@ -3972,10 +3753,8 @@
},
{
"cell_type": "code",
- "execution_count": 8,
- "metadata": {
- "collapsed": false
- },
+ "execution_count": 39,
+ "metadata": {},
"outputs": [
{
"name": "stdout",
@@ -4004,10 +3783,8 @@
},
{
"cell_type": "code",
- "execution_count": 9,
- "metadata": {
- "collapsed": false
- },
+ "execution_count": 40,
+ "metadata": {},
"outputs": [
{
"name": "stdout",
@@ -4028,10 +3805,8 @@
},
{
"cell_type": "code",
- "execution_count": 10,
- "metadata": {
- "collapsed": false
- },
+ "execution_count": 41,
+ "metadata": {},
"outputs": [
{
"name": "stdout",
@@ -4090,10 +3865,8 @@
},
{
"cell_type": "code",
- "execution_count": 34,
- "metadata": {
- "collapsed": false
- },
+ "execution_count": 42,
+ "metadata": {},
"outputs": [
{
"name": "stdout",
@@ -4125,20 +3898,18 @@
},
{
"cell_type": "code",
- "execution_count": 38,
- "metadata": {
- "collapsed": false
- },
+ "execution_count": 43,
+ "metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
" 3 0 LOAD_CONST 1 ('Hello')\n",
- " 3 STORE_FAST 0 (s)\n",
+ " 2 STORE_FAST 0 (s)\n",
"\n",
- " 4 6 LOAD_FAST 0 (s)\n",
- " 9 RETURN_VALUE\n"
+ " 4 4 LOAD_FAST 0 (s)\n",
+ " 6 RETURN_VALUE\n"
]
}
],
@@ -4152,20 +3923,18 @@
},
{
"cell_type": "code",
- "execution_count": 39,
- "metadata": {
- "collapsed": false
- },
+ "execution_count": 44,
+ "metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
" 2 0 LOAD_CONST 3 ('Hello')\n",
- " 3 STORE_FAST 0 (s)\n",
+ " 2 STORE_FAST 0 (s)\n",
"\n",
- " 3 6 LOAD_FAST 0 (s)\n",
- " 9 RETURN_VALUE\n"
+ " 3 4 LOAD_FAST 0 (s)\n",
+ " 6 RETURN_VALUE\n"
]
}
],
@@ -4178,25 +3947,23 @@
},
{
"cell_type": "code",
- "execution_count": 40,
- "metadata": {
- "collapsed": false
- },
+ "execution_count": 45,
+ "metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
" 2 0 LOAD_CONST 1 ('Hell')\n",
- " 3 STORE_FAST 0 (s)\n",
+ " 2 STORE_FAST 0 (s)\n",
"\n",
- " 3 6 LOAD_FAST 0 (s)\n",
- " 9 LOAD_CONST 2 ('o')\n",
- " 12 BINARY_ADD\n",
- " 13 STORE_FAST 0 (s)\n",
+ " 3 4 LOAD_FAST 0 (s)\n",
+ " 6 LOAD_CONST 2 ('o')\n",
+ " 8 BINARY_ADD\n",
+ " 10 STORE_FAST 0 (s)\n",
"\n",
- " 4 16 LOAD_FAST 0 (s)\n",
- " 19 RETURN_VALUE\n"
+ " 4 12 LOAD_FAST 0 (s)\n",
+ " 14 RETURN_VALUE\n"
]
}
],
@@ -4215,15 +3982,13 @@
"
\n",
"It looks like that `'Hello'` and `'Hell'` + `'o'` are both evaluated and stored as `'Hello'` at compile-time, whereas the third version \n",
"`s = 'Hell'` \n",
- "`s = s + 'o'` seems to be not interned. Let us quickly confirm the behavior with the following code:"
+ "`s = s + 'o'` seems to not be interned. Let us quickly confirm the behavior with the following code:"
]
},
{
"cell_type": "code",
- "execution_count": 42,
- "metadata": {
- "collapsed": false
- },
+ "execution_count": 46,
+ "metadata": {},
"outputs": [
{
"name": "stdout",
@@ -4248,10 +4013,8 @@
},
{
"cell_type": "code",
- "execution_count": 45,
- "metadata": {
- "collapsed": false
- },
+ "execution_count": 47,
+ "metadata": {},
"outputs": [
{
"name": "stdout",
@@ -4296,6 +4059,12 @@
"cell_type": "markdown",
"metadata": {},
"source": [
+ "#### 06/09/2018\n",
+ "- pep8 spacing\n",
+ "- fixed minor typos\n",
+ "- fixed minor markdown formatting\n",
+ "- fixed broken page jumps\n",
+ "\n",
"#### 07/16/2014\n",
"- slight change of wording in the [lambda-closure section](#lambda_closure)\n",
"\n",
@@ -4322,9 +4091,7 @@
{
"cell_type": "code",
"execution_count": null,
- "metadata": {
- "collapsed": false
- },
+ "metadata": {},
"outputs": [],
"source": []
}
@@ -4345,9 +4112,40 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
- "version": "3.5.0"
+ "version": "3.6.4"
+ },
+ "latex_envs": {
+ "LaTeX_envs_menu_present": true,
+ "autoclose": false,
+ "autocomplete": true,
+ "bibliofile": "biblio.bib",
+ "cite_by": "apalike",
+ "current_citInitial": 1,
+ "eqLabelWithNumbers": true,
+ "eqNumInitial": 1,
+ "hotkeys": {
+ "equation": "Ctrl-E",
+ "itemize": "Ctrl-I"
+ },
+ "labels_anchors": false,
+ "latex_user_defs": false,
+ "report_style_numbering": false,
+ "user_envs_cfg": false
+ },
+ "toc": {
+ "base_numbering": 1,
+ "nav_menu": {},
+ "number_sections": true,
+ "sideBar": true,
+ "skip_h1_title": false,
+ "title_cell": "Table of Contents",
+ "title_sidebar": "Contents",
+ "toc_cell": false,
+ "toc_position": {},
+ "toc_section_display": true,
+ "toc_window_display": false
}
},
"nbformat": 4,
- "nbformat_minor": 0
+ "nbformat_minor": 1
}
diff --git a/tutorials/sqlite3_howto/README.md b/tutorials/sqlite3_howto/README.md
index e5cccec..c596dfc 100644
--- a/tutorials/sqlite3_howto/README.md
+++ b/tutorials/sqlite3_howto/README.md
@@ -123,7 +123,7 @@ there is more information about PRIMARY KEYs further down in this section).
conn.close()
-Download the script: [create_new_db.py](https://raw.github.com/rasbt/python_reference/master/tutorials/code/create_new_db.py)
+Download the script: [create_new_db.py](https://github.com/rasbt/python_reference/blob/master/tutorials/sqlite3_howto/code/create_new_db.py)
* * *
@@ -207,7 +207,7 @@ Let's have a look at some code:
conn.close()
-Download the script: [add_new_column.py](https://raw.github.com/rasbt/python_reference/master/tutorials/code/add_new_column.py)
+Download the script: [add_new_column.py](https://github.com/rasbt/python_reference/blob/master/tutorials/sqlite3_howto/code/add_new_column.py)
@@ -270,8 +270,7 @@ But let us first have a look at the example code:
conn.close()
-Download the script: [update_or_insert_records.py](https://raw.github.com/rasb
-t/python_sqlite_code/master/code/update_or_insert_records.py)
+Download the script: [update_or_insert_records.py](code/update_or_insert_records.py)

@@ -335,8 +334,7 @@ drop the index, which is also shown in the code below.
conn.close()
-Download the script: [create_unique_index.py](https://raw.github.com/rasbt/pyt
-hon_sqlite_code/master/code/create_unique_index.py)
+Download the script: [create_unique_index.py](code/create_unique_index.py)

@@ -401,8 +399,7 @@ row entries for all or some columns if they match certain criteria.
conn.close()
-Download the script: [selecting_entries.py](https://raw.github.com/rasbt/pytho
-n_sqlite_code/master/code/selecting_entries.py)
+Download the script: [selecting_entries.py](code/selecting_entries.py)

@@ -542,8 +539,7 @@ that have been added xxx days ago.
conn.close()
-Download the script: [date_time_ops.py](https://raw.github.com/rasbt/python_sq
-lite_code/master/code/date_time_ops.py)
+Download the script: [date_time_ops.py](code/date_time_ops.py)
@@ -590,7 +586,7 @@ syntax applies to simple dates or simple times only, too.
#### Update Mar 16, 2014:
-If'd we are interested to calulate the hours between two `DATETIME()`
+If'd we are interested to calculate the hours between two `DATETIME()`
timestamps, we can could use the handy `STRFTIME()` function like this
@@ -645,8 +641,7 @@ column names):
conn.close()
-Download the script: [get_columnnames.py](https://raw.github.com/rasbt/python_
-sqlite_code/master/code/get_columnnames.py)
+Download the script: [get_columnnames.py](code/get_columnnames.py)

@@ -682,53 +677,58 @@ convenient script to print a nice overview of SQLite database tables:
import sqlite3
-
+
+
def connect(sqlite_file):
""" Make connection to an SQLite database file """
conn = sqlite3.connect(sqlite_file)
c = conn.cursor()
return conn, c
-
+
+
def close(conn):
""" Commit changes and close connection to the database """
# conn.commit()
conn.close()
-
+
+
def total_rows(cursor, table_name, print_out=False):
""" Returns the total number of rows in the database """
- c.execute('SELECT COUNT(*) FROM {}'.format(table_name))
- count = c.fetchall()
+ cursor.execute('SELECT COUNT(*) FROM {}'.format(table_name))
+ count = cursor.fetchall()
if print_out:
print('\nTotal rows: {}'.format(count[0][0]))
return count[0][0]
-
+
+
def table_col_info(cursor, table_name, print_out=False):
- """
- Returns a list of tuples with column informations:
- (id, name, type, notnull, default_value, primary_key)
-
+ """ Returns a list of tuples with column informations:
+ (id, name, type, notnull, default_value, primary_key)
"""
- c.execute('PRAGMA TABLE_INFO({})'.format(table_name))
- info = c.fetchall()
-
+ cursor.execute('PRAGMA TABLE_INFO({})'.format(table_name))
+ info = cursor.fetchall()
+
if print_out:
print("\nColumn Info:\nID, Name, Type, NotNull, DefaultVal, PrimaryKey")
for col in info:
print(col)
return info
-
+
+
def values_in_col(cursor, table_name, print_out=True):
- """ Returns a dictionary with columns as keys and the number of not-null
- entries as associated values.
+ """ Returns a dictionary with columns as keys
+ and the number of not-null entries as associated values.
"""
- c.execute('PRAGMA TABLE_INFO({})'.format(table_name))
- info = c.fetchall()
+ cursor.execute('PRAGMA TABLE_INFO({})'.format(table_name))
+ info = cursor.fetchall()
col_dict = dict()
for col in info:
col_dict[col[1]] = 0
for col in col_dict:
- c.execute('SELECT ({0}) FROM {1} WHERE {0} IS NOT NULL'.format(col, table_name))
- # In my case this approach resulted in a better performance than using COUNT
+ c.execute('SELECT ({0}) FROM {1} '
+ 'WHERE {0} IS NOT NULL'.format(col, table_name))
+ # In my case this approach resulted in a
+ # better performance than using COUNT
number_rows = len(c.fetchall())
col_dict[col] = number_rows
if print_out:
@@ -736,23 +736,22 @@ convenient script to print a nice overview of SQLite database tables:
for i in col_dict.items():
print('{}: {}'.format(i[0], i[1]))
return col_dict
-
-
+
+
if __name__ == '__main__':
-
+
sqlite_file = 'my_first_db.sqlite'
table_name = 'my_table_3'
-
+
conn, c = connect(sqlite_file)
total_rows(c, table_name, print_out=True)
table_col_info(c, table_name, print_out=True)
- values_in_col(c, table_name, print_out=True) # slow on large data bases
-
+ # next line might be slow on large databases
+ values_in_col(c, table_name, print_out=True)
+
close(conn)
-
-Download the script: [print_db_info.py](https://raw.github.com/rasbt/python_sq
-lite_code/master/code/print_db_info.py)
+Download the script: [print_db_info.py](code/print_db_info.py)

diff --git a/tutorials/sqlite3_howto/code/print_db_info.py b/tutorials/sqlite3_howto/code/print_db_info.py
index 22b72a8..285a635 100644
--- a/tutorials/sqlite3_howto/code/print_db_info.py
+++ b/tutorials/sqlite3_howto/code/print_db_info.py
@@ -22,52 +22,57 @@
import sqlite3
+
def connect(sqlite_file):
""" Make connection to an SQLite database file """
conn = sqlite3.connect(sqlite_file)
c = conn.cursor()
return conn, c
+
def close(conn):
""" Commit changes and close connection to the database """
- #conn.commit()
+ # conn.commit()
conn.close()
+
def total_rows(cursor, table_name, print_out=False):
""" Returns the total number of rows in the database """
- c.execute('SELECT COUNT(*) FROM {}'.format(table_name))
- count = c.fetchall()
+ cursor.execute('SELECT COUNT(*) FROM {}'.format(table_name))
+ count = cursor.fetchall()
if print_out:
print('\nTotal rows: {}'.format(count[0][0]))
return count[0][0]
+
def table_col_info(cursor, table_name, print_out=False):
- """
- Returns a list of tuples with column informations:
- (id, name, type, notnull, default_value, primary_key)
-
+ """ Returns a list of tuples with column informations:
+ (id, name, type, notnull, default_value, primary_key)
"""
- c.execute('PRAGMA TABLE_INFO({})'.format(table_name))
- info = c.fetchall()
-
+ cursor.execute('PRAGMA TABLE_INFO({})'.format(table_name))
+ info = cursor.fetchall()
+
if print_out:
print("\nColumn Info:\nID, Name, Type, NotNull, DefaultVal, PrimaryKey")
for col in info:
print(col)
return info
+
def values_in_col(cursor, table_name, print_out=True):
- """ Returns a dictionary with columns as keys and the number of not-null
- entries as associated values.
+ """ Returns a dictionary with columns as keys
+ and the number of not-null entries as associated values.
"""
- c.execute('PRAGMA TABLE_INFO({})'.format(table_name))
- info = c.fetchall()
+ cursor.execute('PRAGMA TABLE_INFO({})'.format(table_name))
+ info = cursor.fetchall()
col_dict = dict()
for col in info:
col_dict[col[1]] = 0
for col in col_dict:
- c.execute('SELECT ({0}) FROM {1} WHERE {0} IS NOT NULL'.format(col, table_name))
- # In my case this approach resulted in a better performance than using COUNT
+ c.execute('SELECT ({0}) FROM {1} '
+ 'WHERE {0} IS NOT NULL'.format(col, table_name))
+ # In my case this approach resulted in a
+ # better performance than using COUNT
number_rows = len(c.fetchall())
col_dict[col] = number_rows
if print_out:
@@ -85,7 +90,7 @@ def values_in_col(cursor, table_name, print_out=True):
conn, c = connect(sqlite_file)
total_rows(c, table_name, print_out=True)
table_col_info(c, table_name, print_out=True)
- values_in_col(c, table_name, print_out=True) # slow on large data bases
-
- close(conn)
+ # next line might be slow on large databases
+ values_in_col(c, table_name, print_out=True)
+ close(conn)
diff --git a/tutorials/sqlite3_howto/code/update_or_insert_records.py b/tutorials/sqlite3_howto/code/update_or_insert_records.py
index 37292a5..ee461ec 100644
--- a/tutorials/sqlite3_howto/code/update_or_insert_records.py
+++ b/tutorials/sqlite3_howto/code/update_or_insert_records.py
@@ -1,6 +1,6 @@
# Sebastian Raschka, 2014
# Update records or insert them if they don't exist.
-# Note that this is a workaround to accomodate for missing
+# Note that this is a workaround to accommodate for missing
# SQL features in SQLite.
import sqlite3
diff --git a/useful_scripts/conc_gzip_files.py b/useful_scripts/conc_gzip_files.py
index da849c9..b8d9b33 100644
--- a/useful_scripts/conc_gzip_files.py
+++ b/useful_scripts/conc_gzip_files.py
@@ -13,7 +13,7 @@ def conc_gzip_files(in_dir, out_file, append=False, print_progress=True):
Keyword arguments:
in_dir (str): Path of the directory with the gzip-files
out_file (str): Path to the resulting file
- append (bool): If true, it appends contents to an exisiting file,
+ append (bool): If true, it appends contents to an existing file,
else creates a new output file.
print_progress (bool): prints progress bar if true.
diff --git a/useful_scripts/principal_eigenvector.py b/useful_scripts/principal_eigenvector.py
new file mode 100644
index 0000000..913cf62
--- /dev/null
+++ b/useful_scripts/principal_eigenvector.py
@@ -0,0 +1,20 @@
+# Select a principal eigenvector via NumPy
+# to be used as a template (copy & paste) script
+
+import numpy as np
+
+# set A to be your matrix
+A = np.array([[1, 2, 3],
+ [4, 5, 6],
+ [7, 8, 9]])
+
+
+eig_vals, eig_vecs = np.linalg.eig(A)
+idx = np.absolute(eig_vals).argsort()[::-1] # decreasing order
+sorted_eig_vals = eig_vals[idx]
+sorted_eig_vecs = eig_vecs[:, idx]
+
+principal_eig_vec = sorted_eig_vecs[:, 0] # eigvec with largest eigval
+
+normalized_pr_eig_vec = np.real(principal_eig_vec / np.sum(principal_eig_vec))
+print(normalized_pr_eig_vec) # eigvec that sums up to one