##// END OF EJS Templates
remove ipython_parallel
Min RK -
Show More
@@ -145,7 +145,7 b" have['zmq'] = test_for('zmq.pyzmq_version_info', min_zmq, callback=lambda x: x()"
145 # Test suite definitions
145 # Test suite definitions
146 #-----------------------------------------------------------------------------
146 #-----------------------------------------------------------------------------
147
147
148 test_group_names = ['parallel', 'config', 'core',
148 test_group_names = ['core',
149 'extensions', 'lib', 'terminal', 'testing', 'utils',
149 'extensions', 'lib', 'terminal', 'testing', 'utils',
150 'qt', 'html', 'nbconvert'
150 'qt', 'html', 'nbconvert'
151 ]
151 ]
@@ -171,8 +171,6 b' class TestSection(object):'
171 return self.enabled and all(have[p] for p in self.dependencies)
171 return self.enabled and all(have[p] for p in self.dependencies)
172
172
173 shims = {
173 shims = {
174 'parallel': 'ipython_parallel',
175 'config': 'traitlets',
176 'html': 'jupyter_notebook',
174 'html': 'jupyter_notebook',
177 }
175 }
178
176
@@ -219,13 +217,6 b" if sys.platform == 'win32':"
219 if (not have['pexpect']) or (not have['zmq']):
217 if (not have['pexpect']) or (not have['zmq']):
220 test_sections['terminal'].exclude('console')
218 test_sections['terminal'].exclude('console')
221
219
222 # parallel
223 sec = test_sections['parallel']
224 sec.requires('zmq')
225 if not have['pymongo']:
226 sec.exclude('controller.mongodb')
227 sec.exclude('tests.test_mongodb')
228
229 # extensions:
220 # extensions:
230 sec = test_sections['extensions']
221 sec = test_sections['extensions']
231 # This is deprecated in favour of rpy2
222 # This is deprecated in favour of rpy2
@@ -451,7 +451,6 b' def prepare_controllers(options):'
451 py_testgroups = py_test_group_names
451 py_testgroups = py_test_group_names
452 if not options.all:
452 if not options.all:
453 js_testgroups = []
453 js_testgroups = []
454 test_sections['parallel'].enabled = False
455 else:
454 else:
456 js_testgroups = all_js_groups()
455 js_testgroups = all_js_groups()
457
456
@@ -558,8 +557,7 b' def run_iptestall(options):'
558 is passed, one process is used per CPU core. Default 1 (i.e. sequential)
557 is passed, one process is used per CPU core. Default 1 (i.e. sequential)
559
558
560 inc_slow : bool
559 inc_slow : bool
561 Include slow tests, like IPython.parallel. By default, these tests aren't
560 Include slow tests. By default, these tests aren't run.
562 run.
563
561
564 slimerjs : bool
562 slimerjs : bool
565 Use slimerjs if it's installed instead of phantomjs for casperjs tests.
563 Use slimerjs if it's installed instead of phantomjs for casperjs tests.
@@ -4,22 +4,4 b''
4 Using IPython for parallel computing
4 Using IPython for parallel computing
5 ====================================
5 ====================================
6
6
7 .. toctree::
7 IPython.parallel has moved to `ipython_parallel <https://github.com/ipython/ipython_parallel>`_.
8 :maxdepth: 2
9
10 parallel_intro
11 parallel_process
12 parallel_multiengine
13 magics
14 parallel_task
15 asyncresult
16 parallel_mpi
17 parallel_db
18 parallel_security
19 parallel_winhpc
20 parallel_demos
21 dag_dependencies
22 parallel_details
23 parallel_transition
24
25
@@ -7,3 +7,4 b''
7 -e git+https://github.com/jupyter/jupyter_nbformat.git#egg=jupyter_nbformat
7 -e git+https://github.com/jupyter/jupyter_nbformat.git#egg=jupyter_nbformat
8 -e git+https://github.com/jupyter/jupyter_client.git#egg=jupyter_client
8 -e git+https://github.com/jupyter/jupyter_client.git#egg=jupyter_client
9 -e git+https://github.com/ipython/ipython_kernel.git#egg=ipython_kernel
9 -e git+https://github.com/ipython/ipython_kernel.git#egg=ipython_kernel
10 -e git+https://github.com/ipython/ipython_parallel.git#egg=ipython_parallel
@@ -80,9 +80,6 b' def install():'
80 programs = [
80 programs = [
81 'ipython',
81 'ipython',
82 'iptest',
82 'iptest',
83 'ipcontroller',
84 'ipengine',
85 'ipcluster',
86 ]
83 ]
87 programs = [suffix(p) for p in programs]
84 programs = [suffix(p) for p in programs]
88 scripts = pjoin(sys.prefix, 'scripts')
85 scripts = pjoin(sys.prefix, 'scripts')
@@ -105,10 +102,6 b' def install():'
105 arguments(scripts, 'ipython'), iconpath)
102 arguments(scripts, 'ipython'), iconpath)
106 mkshortcut(python, 'IPython (pylab mode)', ip_start_menu,
103 mkshortcut(python, 'IPython (pylab mode)', ip_start_menu,
107 arguments(scripts, 'ipython', '--pylab'), iconpath)
104 arguments(scripts, 'ipython', '--pylab'), iconpath)
108 mkshortcut(python, 'IPython Controller', ip_start_menu,
109 arguments(scripts, 'ipcontroller'), iconpath)
110 mkshortcut(python, 'IPython Engine', ip_start_menu,
111 arguments(scripts, 'ipengine'), iconpath)
112 mkshortcut(pythonw, 'IPython Qt Console', ip_start_menu,
105 mkshortcut(pythonw, 'IPython Qt Console', ip_start_menu,
113 arguments(scripts, 'ipython', 'qtconsole'), iconpath)
106 arguments(scripts, 'ipython', 'qtconsole'), iconpath)
114
107
@@ -145,28 +145,9 b" if len(sys.argv) >= 2 and sys.argv[1] in ('sdist','bdist_rpm'):"
145 # List of things to be updated. Each entry is a triplet of args for
145 # List of things to be updated. Each entry is a triplet of args for
146 # target_update()
146 # target_update()
147 to_update = [
147 to_update = [
148 # FIXME - Disabled for now: we need to redo an automatic way
149 # of generating the magic info inside the rst.
150 #('docs/magic.tex',
151 #['IPython/Magic.py'],
152 #"cd doc && ./update_magic.sh" ),
153
154 ('docs/man/ipcluster.1.gz',
155 ['docs/man/ipcluster.1'],
156 'cd docs/man && gzip -9c ipcluster.1 > ipcluster.1.gz'),
157
158 ('docs/man/ipcontroller.1.gz',
159 ['docs/man/ipcontroller.1'],
160 'cd docs/man && gzip -9c ipcontroller.1 > ipcontroller.1.gz'),
161
162 ('docs/man/ipengine.1.gz',
163 ['docs/man/ipengine.1'],
164 'cd docs/man && gzip -9c ipengine.1 > ipengine.1.gz'),
165
166 ('docs/man/ipython.1.gz',
148 ('docs/man/ipython.1.gz',
167 ['docs/man/ipython.1'],
149 ['docs/man/ipython.1'],
168 'cd docs/man && gzip -9c ipython.1 > ipython.1.gz'),
150 'cd docs/man && gzip -9c ipython.1 > ipython.1.gz'),
169
170 ]
151 ]
171
152
172
153
@@ -266,7 +247,7 b' setuptools_extra_args = {}'
266 pyzmq = 'pyzmq>=13'
247 pyzmq = 'pyzmq>=13'
267
248
268 extras_require = dict(
249 extras_require = dict(
269 parallel = [pyzmq],
250 parallel = ['ipython_parallel'],
270 qtconsole = [pyzmq, 'pygments'],
251 qtconsole = [pyzmq, 'pygments'],
271 doc = ['Sphinx>=1.1', 'numpydoc'],
252 doc = ['Sphinx>=1.1', 'numpydoc'],
272 test = ['nose>=0.10.1', 'requests'],
253 test = ['nose>=0.10.1', 'requests'],
@@ -375,9 +375,6 b' def find_entry_points():'
375 """
375 """
376 ep = [
376 ep = [
377 'ipython%s = IPython:start_ipython',
377 'ipython%s = IPython:start_ipython',
378 'ipcontroller%s = IPython.parallel.apps.ipcontrollerapp:launch_new_instance',
379 'ipengine%s = IPython.parallel.apps.ipengineapp:launch_new_instance',
380 'ipcluster%s = IPython.parallel.apps.ipclusterapp:launch_new_instance',
381 'iptest%s = IPython.testing.iptestcontroller:main',
378 'iptest%s = IPython.testing.iptestcontroller:main',
382 ]
379 ]
383 suffix = str(sys.version_info[0])
380 suffix = str(sys.version_info[0])
This diff has been collapsed as it changes many lines, (2671 lines changed) Show them Hide them
@@ -1,2671 +0,0 b''
1 {
2 "cells": [
3 {
4 "cell_type": "markdown",
5 "metadata": {},
6 "source": [
7 "# IPython's Data Publication API"
8 ]
9 },
10 {
11 "cell_type": "markdown",
12 "metadata": {},
13 "source": [
14 "IPython has an API that allows IPython Engines to publish data back to the Client. This Notebook shows how this API works."
15 ]
16 },
17 {
18 "cell_type": "markdown",
19 "metadata": {},
20 "source": [
21 "## Setup"
22 ]
23 },
24 {
25 "cell_type": "markdown",
26 "metadata": {},
27 "source": [
28 "We begin by enabling matplotlib plotting and creating a `Client` object to work with an IPython cluster."
29 ]
30 },
31 {
32 "cell_type": "code",
33 "execution_count": 1,
34 "metadata": {
35 "collapsed": false
36 },
37 "outputs": [],
38 "source": [
39 "%matplotlib inline"
40 ]
41 },
42 {
43 "cell_type": "code",
44 "execution_count": 2,
45 "metadata": {
46 "collapsed": false
47 },
48 "outputs": [],
49 "source": [
50 "from IPython.parallel import Client"
51 ]
52 },
53 {
54 "cell_type": "code",
55 "execution_count": 3,
56 "metadata": {
57 "collapsed": false
58 },
59 "outputs": [
60 {
61 "data": {
62 "text/plain": [
63 "<DirectView [0, 1, 2, 3,...]>"
64 ]
65 },
66 "execution_count": 3,
67 "metadata": {},
68 "output_type": "execute_result"
69 }
70 ],
71 "source": [
72 "c = Client()\n",
73 "dv = c[:]\n",
74 "dv.block = False\n",
75 "dv"
76 ]
77 },
78 {
79 "cell_type": "markdown",
80 "metadata": {},
81 "source": [
82 "## Simple publication"
83 ]
84 },
85 {
86 "cell_type": "markdown",
87 "metadata": {},
88 "source": [
89 "Here is a simple Python function we are going to run on the Engines. This function uses `publish_data` to publish a simple Python dictionary when it is run."
90 ]
91 },
92 {
93 "cell_type": "code",
94 "execution_count": 4,
95 "metadata": {
96 "collapsed": false
97 },
98 "outputs": [],
99 "source": [
100 "def publish_it():\n",
101 " from IPython.kernel.zmq.datapub import publish_data\n",
102 " publish_data(dict(a='hi'))"
103 ]
104 },
105 {
106 "cell_type": "markdown",
107 "metadata": {},
108 "source": [
109 "We run the function on the Engines using `apply_async` and save the returned `AsyncResult` object:"
110 ]
111 },
112 {
113 "cell_type": "code",
114 "execution_count": 5,
115 "metadata": {
116 "collapsed": false
117 },
118 "outputs": [],
119 "source": [
120 "ar = dv.apply_async(publish_it)"
121 ]
122 },
123 {
124 "cell_type": "markdown",
125 "metadata": {},
126 "source": [
127 "The published data from each engine is then available under the `.data` attribute of the `AsyncResult` object."
128 ]
129 },
130 {
131 "cell_type": "code",
132 "execution_count": 6,
133 "metadata": {
134 "collapsed": false
135 },
136 "outputs": [
137 {
138 "data": {
139 "text/plain": [
140 "[{'a': 'hi'},\n",
141 " {'a': 'hi'},\n",
142 " {'a': 'hi'},\n",
143 " {'a': 'hi'},\n",
144 " {'a': 'hi'},\n",
145 " {'a': 'hi'},\n",
146 " {'a': 'hi'},\n",
147 " {'a': 'hi'}]"
148 ]
149 },
150 "execution_count": 6,
151 "metadata": {},
152 "output_type": "execute_result"
153 }
154 ],
155 "source": [
156 "ar.data"
157 ]
158 },
159 {
160 "cell_type": "markdown",
161 "metadata": {},
162 "source": [
163 "Each time `publish_data` is called, the `.data` attribute is updated with the most recently published data."
164 ]
165 },
166 {
167 "cell_type": "markdown",
168 "metadata": {},
169 "source": [
170 "## Simulation loop"
171 ]
172 },
173 {
174 "cell_type": "markdown",
175 "metadata": {},
176 "source": [
177 "In many cases, the Engines will be running a simulation loop and we will want to publish data at each time step of the simulation. To show how this works, we create a mock simulation function that iterates over a loop and publishes a NumPy array and loop variable at each time step. By inserting a call to `time.sleep(1)`, we ensure that new data will be published every second."
178 ]
179 },
180 {
181 "cell_type": "code",
182 "execution_count": 7,
183 "metadata": {
184 "collapsed": false
185 },
186 "outputs": [],
187 "source": [
188 "def simulation_loop():\n",
189 " from IPython.kernel.zmq.datapub import publish_data\n",
190 " import time\n",
191 " import numpy as np\n",
192 " for i in range(10):\n",
193 " publish_data(dict(a=np.random.rand(20), i=i))\n",
194 " time.sleep(1)"
195 ]
196 },
197 {
198 "cell_type": "markdown",
199 "metadata": {},
200 "source": [
201 "Again, we run the `simulation_loop` function in parallel using `apply_async` and save the returned `AsyncResult` object."
202 ]
203 },
204 {
205 "cell_type": "code",
206 "execution_count": 8,
207 "metadata": {
208 "collapsed": false
209 },
210 "outputs": [],
211 "source": [
212 "ar = dv.apply_async(simulation_loop)"
213 ]
214 },
215 {
216 "cell_type": "markdown",
217 "metadata": {},
218 "source": [
219 "New data will be published by the Engines every second. Anytime we access `ar.data`, we will get the most recently published data."
220 ]
221 },
222 {
223 "cell_type": "code",
224 "execution_count": 9,
225 "metadata": {
226 "collapsed": false
227 },
228 "outputs": [],
229 "source": [
230 "import matplotlib.pyplot as plt"
231 ]
232 },
233 {
234 "cell_type": "code",
235 "execution_count": 10,
236 "metadata": {
237 "collapsed": false
238 },
239 "outputs": [
240 {
241 "data": {
242 "text/plain": [
243 "<matplotlib.legend.Legend at 0x10c41e910>"
244 ]
245 },
246 "execution_count": 10,
247 "metadata": {},
248 "output_type": "execute_result"
249 },
250 {
251 "data": {
252 "image/png": [
253 "iVBORw0KGgoAAAANSUhEUgAAAlYAAAGKCAYAAADOsQ/WAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\n",
254 "AAALEgAACxIB0t1+/AAAIABJREFUeJzsnXlcU2f2/z83O9kg7ERWUURRXKsoota1Ivp1q9Pa1u07\n",
255 "ju2320ztMoodbR3tjLXz69hlpqvVVrtpW5XaWrV1wbVaUVlUXABlTSAEEhKy3Of3R0gkkEBCEhB6\n",
256 "36+XL8m9z73Pc2+2k3M+5xyKEELAwMDAwMDAwMDgMayuXgADAwMDAwMDQ0+BMawYGBgYGBgYGLwE\n",
257 "Y1gxMDAwMDAwMHgJxrBiYGBgYGBgYPASjGHFwMDAwMDAwOAlGMOKgYGBgYGBgcFLMIYVA8PvnAkT\n",
258 "JuDw4cPtjouNjUVJSYnb5+/oca4yYcIEHD161GfnZ2BgYHAHxrBiYGiHJUuWgMVi2f6JRCIMGzYM\n",
259 "GzduRF1dXVcvz2MoigJFUS6Ns5KTk4OkpCSYTCa3jvMFrq7fFzz66KN47733umTuexmj0Yh//OMf\n",
260 "iImJgUAgwODBg/H999939bIYGDoFxrBiYGgHiqKwZs0a0DQNmqZRVlaG1157DUeOHEFycjLy8/O7\n",
261 "eomdzpAhQ5CXlwcOh9PVS+lSPvvsM6xYsaKrl3HP8cwzz+Cbb77BN998g+rqaqxfvx7Lli1DVlZW\n",
262 "Vy+NgcHnMIYVA4MLNG9Q4O/vj2nTpuGnn37C3Llz8cADD0Cj0bh8riNHjuD+++/3xTIZOoCrz8e6\n",
263 "devwyiuvdMKKPMfX4de2KCsrw/vvv4/du3dj+PDhEIlEmDVrFl5//XW89NJLXbImBobOhDGsGBg8\n",
264 "YPPmzZBIJHjzzTe7eikMDDYoikJXdSsrKipCQEAAoqKi7LYPGzYMV69e7ZI1MTB0JoxhxcDgASwW\n",
265 "C4sXL8bu3btt2z755BMMHToUQqEQcrkc69evt+2LjY3FxIkTcfToUbBYLPTu3RsAcPv2bTz22GMI\n",
266 "Dw+HUCjE+PHjUVBQ4HTeoqIiREVFIT8/H+np6RCLxQgNDcUTTzwBtVptG+fIy3LkyBHExcXZbWto\n",
267 "aMBLL72EyMhICIVCjBs3DmfOnGlz/ubnuHbtGqZMmQKJRILo6Gj8+9//thtfXFyMmTNnQiKRoE+f\n",
268 "Pvjggw9anfPTTz9Fv379IBAIkJSUZHdPAaCxsRFr1qyxW2N2drbTNVrpyPPR8lpZLBZeffVVvPLK\n",
269 "K2CxWFi2bBkAi/5u27ZtAIDCwkKwWCycOnUK9913H4RCISZNmoTS0lLs3bsXiYmJ8Pf3x4wZM1BW\n",
270 "VmY3R2FhIaZPnw6RSITQ0FD85S9/QUNDg9Nr0mq1WLp0KYKDgyGTybBixQo0NjZi3bp1YLFYKC4u\n",
271 "RlxcHFgsFo4dOwYAMBgMyMzMhFwuh1AoxP33349Lly7Z3afHHnsMe/bswfDhw+Hn54fevXvjjTfe\n",
272 "AE3TtnF79+5FSEgI7ty543Bt0dHRqK2tbbX/woUL8Pf3d3pNDAw9BcawYmDwkIEDB+LGjRsALF/+\n",
273 "R48exRtvvAGFQoGjR49i9+7d+PbbbwFYvqR/+eUXjB8/HjRN4+bNmwCAc+fOIS4uDmfPnkV1dTXm\n",
274 "z5+Phx56qM151Wo15s6di8WLF6O0tBSnT5+GwWDAiBEjbMaVK6JuQgiefvppNDY24vjx4ygrK8Mf\n",
275 "//hHTJ8+HXv27HHpHjzyyCMYN24cqqqq8Omnn+LTTz+1CfsJIVixYgVWrFiBqqoqbNmyBc899xwu\n",
276 "X75sO37z5s34+OOP8cUXX6C2thbbt2/Hhg0b8MUXX9jGzJgxAxcuXMBPP/2EqqoqrF69GitWrGjT\n",
277 "C2IwGDr0fDQnNjYWNE1j7dq1WLduHWiaxscff2y7v9Z7zOVyAQAvvPACPv74Y5SVlaF3796YO3cu\n",
278 "/vnPf+LgwYMoKiqCXC7Hs88+azt/UVERJk+ejEWLFqGiogKXL18GRVGYO3eu0+tat24dampqcPXq\n",
279 "VeTk5CA3Nxc5OTm29cXExKCoqAg0TWPcuHEAgHnz5kGlUuHUqVNQKpV47rnnkJ6ejlu3btnOe+zY\n",
280 "Maxfvx5btmyBUqnEV199hV27dmHBggW2MUFBQejfvz/8/Pwcri0yMhKLFy/GggULcPnyZWi1Whw4\n",
281 "cAAvvvgipk2b5vSaGBh6DISBgaFNlixZQtasWeN0//fff08kEonT/W+99RZ55plnbI9/+eUXMmHC\n",
282 "hHbnDQ4OJiqVyuG+W7duEYqiyIEDB1rtGzNmDNm4cSMhhJB169aRdevW2e3/5ZdfSGxsrO3x+PHj\n",
283 "ycMPP9zqPO+99x5JSEiwPY6NjSXFxcW2+ZufIzAwkBw8eNDhWmNjY8l//vMfu20PPfQQefvttwkh\n",
284 "hNTU1JDQ0FCi0Wjsxhw/fpwMGjSIEELIrl27SExMDNHr9XZj8vPzCY/HI0ePHnU4tyM6+nw4updL\n",
285 "liwhn3zyCSHk7nPy008/2faXlJQQiqLIyZMn7bYFBwfbHj/00ENk69atrebr27cvOX/+vMO1zJ07\n",
286 "l2RmZjpda/PnihBCfvzxR4fXuH79evL0008TQgjZunUrCQgIIJWVlXZjqqqqiL+/v901tEdjYyNZ\n",
287 "vXo1CQ0NJX5+fqRPnz6ExWKR3377zeVzMDB0VxiPFQODh1y+fBnx8fG2xydOnMCDDz6I2NhYCIVC\n",
288 "PPPMM6ivr2/zHDqdDuvWrcPo0aMhk8nA4XBQU1PTZjmHgIAATJ06tdX22bNn4/jx4y6vn6IoPPzw\n",
289 "ww7PU1hYCIVC0e45XnrpJcyaNQuzZ8/Gv//9b1RWVtrtHz9+vN3j+Ph4VFdXAwDOnz8PhUIBiURi\n",
290 "V9Zi3LhxNm/UkSNH8MADD4DP59udp3///khOTm5zbR15Pjxh9OjRtr979eoFwKIvar7Neu0AcPz4\n",
291 "cSxbtszu2lksFm7cuOE0HPz0009jy5YtGDt2LNatW2fn/XPE8ePHbeHO5v/+9re/2c0xceJEhIaG\n",
292 "2h0bEhKC1NRUt15TPB4PGzZsQGVlJRoaGtC7d2889NBDGDp0qMvnYGDorjCGFQODB5hMJmzbtg3z\n",
293 "588HYKnvlJ6ejqlTp+L8+fNoaGjABx980K6QeNGiRTh58iT++9//QqFQwGQyQS6Xt3mMszAfIcS2\n",
294 "j6KoVrWmHGl32ltfe7z44ou4fPkypkyZgr179yIpKckuxCQSiezGc7lc25wURSEoKMhWzqL5v8bG\n",
295 "RtuYjtDR58MThEKh7W8Wy/IR29wgtG5r/njfvn2trt1sNuORRx5xOMeECRNQVFSEJ554AoWFhRg9\n",
296 "erQtvOkIFouFuXPnOrzHBw8etI1zdl+av6bcJSsrCydOnMDrr7/eoeMZGLobjGHFwOACzr5Unnvu\n",
297 "OTQ0NODPf/4zAODgwYOYOHEili9fjqCgIACwEwhbz9VcDAwAP/zwAzZv3ozBgweDw+Gguroa5eXl\n",
298 "ba5JpVLZfSla2bt3L9LS0gAAMpmsVdq9I8H3l19+2Wrbnj17kJCQgJCQkDbXAVgMzPj4eDz55JM4\n",
299 "fPgwhg8f3uYXfXNGjBgBvV6PAwcO2G0vLy+HUqkEYDEkDhw4YDO0rFy5cqXV/W1OR58PR7g6zl3G\n",
300 "jx+Pzz77rNX2ixcvOj3GZDIhMDAQjzzyCHbs2IHMzEyb7svRWsePH49Dhw618j7m5eXZjTty5Aiq\n",
301 "qqrsxiiVSpw6dcr2mnIHo9GI559/HqtXr273hwIDQ0+BMawYGNqBEGL3S16lUuHAgQOYPHky9u3b\n",
302 "hx9++MHmkUlISMDJkydx7tw5aLVa7Nq1C/v377f78goPD8fVq1ehUChsRk/fvn3x4Ycfoq6uDsXF\n",
303 "xfjTn/6EgICANr/IxWIxnn32WXz55ZdQqVS4efMmli9fjsrKSvzf//0fACAtLQ3fffcdDh8+DJ1O\n",
304 "h88//9xOEG69vhMnTuAvf/kLbt26BZVKhe3bt+Ovf/0rNm3a1O79UalUiI+Px+7du9HY2IjffvsN\n",
305 "ly9fRt++fV26v/7+/vj73/+OJUuWICsrCxqNBmfPnsX06dPx4YcfAgDmzp2LPn36YN68ecjPz0d9\n",
306 "fT1+/PFHzJs3z2YwOaKjz4cjwsPDcfbsWeh0OpSWlrp0ba6wceNG/PDDD8jMzERlZSUqKirwzDPP\n",
307 "4JFHHnH6/Kenp2PNmjXQaDS4c+cODh48iISEBLu1Hjt2DPX19VCpVJg0aRLuv/9+zJw5Ezk5OdBq\n",
308 "tdi9ezcmTJiAU6dO2Y4Ti8VIT0/HiRMnoNFocO7cOcyaNQuTJ09GSkoKAEtodezYsTajty3efvtt\n",
309 "m3HFwPC7oavEXQwM3YUlS5YQiqJs//z8/MjQoUPJxo0bSV1dXavxL7/8MgkJCSESiYQ88sgjZPfu\n",
310 "3WT27Nl2Y5YtW0b4fD4ZNmwYIYSQy5cvk9GjRxOBQED69etHvv32WzJ27FiSk5PjcE23bt0ikZGR\n",
311 "5PLly2TKlClEKBSSoKAgsnz5clJbW2s39q233iIxMTFELBaThQsXkr179xK5XG7bP2HCBLJv3z7y\n",
312 "3HPPkbCwMCIQCEhqaio5deqU3Xlaitfj4uJs+7777juSnJxM+Hw+iYuLI2+++abD46ysW7eOvPLK\n",
313 "K3bbduzYQRITEwmPxyN9+/a1OwchhOj1evLXv/6VyOVywufzyZgxY8jRo0fJhAkT2hSvd+T5cIRK\n",
314 "pSIjR44kfD6fPPvss4QQy2tj27ZttnvCYrGI2Wy2O47FYrU6V8tt169fJ+np6UQkEpHAwECyaNEi\n",
315 "UlFR4XQteXl5ZPLkyUQoFJLg4GDy+OOP2wn79+3bR4KDg0lgYCDJzs4mhBBiMBhIZmYmkcvlRCAQ\n",
316 "kJSUFLvkh08++YQ8+uij5JtvviGDBw8mPB6PxMTEkE2bNhGapm3jvvvuOxISEkJKSkqcro8QQpRK\n",
317 "JZHJZOTbb79tcxwDQ0+DIqSLqsgxMDB0mKKiIqSlpeH27dtdvRSGHsInn3yCw4cP49NPP+3qpTAw\n",
318 "dGvabfRlMpmwc+dOFBQU4LXXXmu1//jx4zhw4ABYLBbi4uKwdOlSnyyUgYGBgcF3dFUjawaGnka7\n",
319 "GqvPP/8cAwcOdLivqqoKhw8fxvr16/Hqq69CIpHg559/9voiGRgYGBh8CxO8YGDwDu0aVo899phd\n",
320 "DZbm5OTkYNy4cbZfOpMnT8b58+e9u0IGBgaHMB4GBm/SvIo8AwNDx/EoK1Cj0UAqldoeS6XSNgsa\n",
321 "MjAweIfY2Ng2M9gYGNxl8eLF2L59e1cvg4Gh29OuxqotJBKJnSFVV1dnZ2g54vDhw55MycDAwMDA\n",
322 "wMDQqUyaNMnlsR4ZVkOGDMG7776LCRMmgMVi4dChQxg+fHi7xzkLLXZ3zDQBm8W40r1B45cfoeLX\n",
323 "k4jZ/FFXL4Whm7Bv3z7MnDmzq5fB0A1gXisM7vDbb7+5Nb5DocAdO3agrq4OISEhuP/++/Hyyy/j\n",
324 "b3/7G9RqNSZOnNiRU3Z76vQm/GHHZdTqjF29lB4BXVEKnr516xUGBgYGBoZ7GZc9Vs1LLTTvXzVu\n",
325 "3DiMGzfOu6vqhlQ3GFHXaMZXl6rwp1G9uno53R5SWQa+TtvVy2BgYGBgYHALpqWNl1DrTYgK4OPA\n",
326 "tWpUaxmvlScQQkBXloJj0IOYzV29HAYGBgYGBpdhDCsvUas3IVbmh2kJQdiZU9HVy+nWkNoagMeH\n",
327 "iScAqavt6uUwdIAvPzyLqnImQ5iBgeH3B2NYeQm1zoQAAQcLkkNx5KYKFfWNXb2kbgupLAMrvBcQ\n",
328 "EGgxshi6FYQQVNxRQ63Sdeq8zZsQ9yRoowm3P93T1cvoUfTU1wrDvYFHWYEMd1HrTfAXcBDgx8XM\n",
329 "/sHYcaECK8fFdPWyuiV0ZSlYYXL4+QlB1Ixh1d3Q1DXCaDCjQWPo1Hn79evXqfN1FvW5hch7cRMi\n",
330 "5k4BRyTs6uX0CLrytUIIQVVVFcxmM1OQtYuxdhuQSqUQi8VeOy9jWHkJtd6E6AABAGD+oFAs/boA\n",
331 "t2v1iGraxuA6dGUZWGFy0BQLRK3q6uUwuIlKaUk6aNB2rmHVU6nNyQcIQX3+DcjuG9TVy2HwkKqq\n",
332 "KkgkEgiFjJF8L0AIQU1NDRobGxEUFOSVczKhQC9RqzfB389ip4r5HMwdGIJPf2O0Vh2BVJaCCusF\n",
333 "igkFdkuqFVpQLAoNGiYc7g3qcgrAFvqhLvdaVy+lR1CrM6JQ2XWlXMxmM2NU3UNQFIWgoCA0Nnrv\n",
334 "84oxrLxEbZPGysrspBBcLK/HzZrO1Zn0BOiKUrDCe4HylzGGVTdEpdQiTC5lPFZeovZCPiLmTkF9\n",
335 "bmFXL6VH8PMNFTJ/vAGtoWsyjpnw372JN58XxrDyElaNlRU/LhsLksOw/Xx5F66q+2EptWAJBVIB\n",
336 "gaCZUGC3o0ahQWSsrNM1Vj0RU70W+juVkM97oMd5rDT6OuQW/4p9Zz/FyYIDnTavQmOEwUzjMyai\n",
337 "wOAjGI2Vl1DrTQjws7+dM/sHY/flKlyp0iIxVNRFK+teELUK4HBBiSRgBQSC1FZ39ZIY3KRGocWQ\n",
338 "UdG4Vajs6qV0e9SXrkCS1AfS5ARort0CbTSBxe1+H9sqjQK3Kq+gqPKq5f+qq9Do1IgJTUCgJBQn\n",
339 "8n/AmP7TOmUtCq0Bi4dHYGdOJaYnBtm0sQydh16vx4svvog333wTLJbv/DvXr1/HCy+8AJPJBL1e\n",
340 "jyVLltgVOPcV3e8deg9ipgnqG02Q8u1vJ4/DwsKh4dh2vhyvTe/TRavrXpAmbxUAUP6BjHi9m2HN\n",
341 "BgyP9EdDJ5YcMZtosDk9zwGvzimA/5D+4IiE8OsVBu31Ykj6x3f1spxCCEGVurSVEUXTZsSFJSI2\n",
342 "LBFj+k/DwvHPIEwWCRbFQkNjPZ54dzoIIZ0SJlNoDegbHIKHBofhvdN38Pdp8Ux4rpMRCATYsmWL\n",
343 "T+egaRqPPfYY3nzzTYwaNQo6nQ5TpkxBnz59MGrUKJ/OzRhWXqC+0QQRj+2wAfO0hEB8dakSl8o1\n",
344 "SI7wXjpnT4WutOirAIAKYDRW3uJy0RlEBveGTBzi03lUSi38g4QQivlo1JtAm2mw2L43eLa+mY0F\n",
345 "f7wP0gA/n88FoNOMAHVOAcKmjwcASJISUJdbeM8YVmbahLKa4rsGVOVVFFVdgR9PjNiwfogLS8SU\n",
346 "ofMRF5aIQHGo0/sl5EvA5wqg0ioR6OPXJ2AJBYaIeOgXIsT+K0qcuV2HlGh/n8/L0LmcPXsWMTEx\n",
347 "NiPKz88Pzz33HL788kvGsOoOqPX2wvXmcNksPDo0HNvOl2HzjL7ML6N2oCtKwQpr6rUosGTOEF0D\n",
348 "KD8mi8YTth7ahJEJ9+OhcU/5dJ4apRaBwSKwWBT4flzoGowQSfg+ndPQaEJtTQPqavWdYlhp9fV4\n",
349 "+bMlWLfwQ0iFMp/Opb6Qj4RVjwMApIP6oj73GvDgAz6d0xEGUyNuK280M6Ku4LbyBmTikCZPVD/M\n",
350 "Hr0UsaH9OnRPImQxqKgp8blhZaYJVDojvr+iwLL7euHxlEi8c+o2hvWSgNcJPwC6G9euXcP69euh\n",
351 "0WgAAGPGjMELL7wAAJgxYwZmzZqFgwcPor6+Hv7+/vjggw/g728xUo8dO4ZNmzaBpmkAwFNPPYXV\n",
352 "q1cjJycHADB48GBcvHgRRUVFWLx4MaZOnYrz589DoVBgwYIFePrppwEAjY2N+Mc//oELFy6Aw+FA\n",
353 "IpFg/fr1iIyMhMlkwp///GesXr0acrncbu0XL15EcnKy3bYhQ4bgP//5j+9uWBOMYeUFWgrXWzKp\n",
354 "TyC+vFSJ86X1GBEp7cSVdT9IZRlYw8cAsGRpUP4yEHUNY1h5QE19FSpr7+D01cP4Q9qTPjXuaxRa\n",
355 "BIZY9IRCMQ8NWoPPDStr3SxtJ4Uej+d9j7KaIpy68hOmDfuDz+ZpVNTArGmAMC4SACBNSsDNtz71\n",
356 "2XzNKSy7jOvluTZDqkJ1G+GyKJsRNXbAA4gJTYAfzzva0XBZFCpUJRgQPdwr53NGdYMRfA4L3+Yq\n",
357 "sCA5DPdFSRFdIMC3uQr8YXCYT+dui6kfXui0uX7641CXxmk0GqxevRrvvPMOwsIs92bjxo3Yvn07\n",
358 "Fi1aBDabjezsbHz99degKArr1q3Dhx9+iJUrV6KgoADPP/88du3ahejoaJhMJrzwwgt2nz3Wv1ks\n",
359 "FvLy8rBy5UpkZmZCp9MhNTUV8+bNg1wux/r165GSkoK1a9cCsBh7jz/+OLKyssDhcPD22287XL9W\n",
360 "q21V9FMsFkOtVrt9z9yFMay8QPMaVo5gsygsHhaBT86VY3gvCeO1agO6shTcsLu/PCj/QJBaFRAe\n",
361 "2YWr6t7klvyK4X3G4VZFAW4rryM6pK/P5qpRahGXEAwAEIp4nZIZqGqqSdQZhhUhBIcvfotZIxfj\n",
362 "eN5+nxpW6pwCSAf3t31eSAb2RV1eoc/DkDk3T+K/P7yCEX0nIKHXYEwdtgBRwfHgcXxnIIfLolGu\n",
363 "KvHZ+a0oNAbwOSzUN5pxsLAGcwaGYsWoSDy79yom9w1EkJDr8zU4wlVjpzM5c+YMrly5guXLl9u2\n",
364 "6fV6DB16d60rVqywvRbT0tKwZ4+l9dKuXbuwfPlyREdHAwA4HA7Wrl2LI0eOOJxLLpdj1qxZACwh\n",
365 "u2HDhqGkpARyuRzfffcdLl26hPfff982vqqqCnq9HgKB88QDsViM6mr75CeNRmPzqPkSxrDyAmpd\n",
366 "2x4rABgbF4CdOZU4WaxGamxAJ62se0EIAV1xV7wOwFJyobYG7C5cV3cnr/gsBsWMRIi/HGev/exb\n",
367 "w0qhxfAxllZOIjEfDVrfGzuqai3YbAqaTjCsrpVdgtFswIK0J/DUfzNQVlMEeWCsT+ZS5xTAf2h/\n",
368 "22N+SCDYAj70dyrgFxXhkzkB4GjuXswb80dMGfqgz+ZoSYQsCicKfvT5PAqtEWyKwvR+QcgqUGJ2\n",
369 "Ugh6+fMxvV8QPv61DC+MZ9qQWaEoCsnJydi5c6fTMRKJxPY3n8+3hf30er2tXYyVlo+dnQewiNut\n",
370 "5wKAbdu2QSZzL8ScnJyMN998025bTk4OBg3yffcCJqjsBWrb0FhZYVEUloyIwLbz5TDTzl9gv2dI\n",
371 "XS3AYYMS3w2XsgJkTL9ADyCEILf4VwyMGYmRCRNx+uph381Fk9ahwE7yWEVEBXSKx+rni99g0uC5\n",
372 "4LC5GNN/GrLzfvDZXOoL+fAf0t9um3SgRcDuKzT6Oly8dQqjE6f6bA5HRATGoFx12+fzVGkNAAjG\n",
373 "9Q4Am0XhYrlFO/TwkHD8VlqPgiqtz9fQXUhJSUFhYSEOHToEwJJlt2HDBly8eLHdYxcuXIiPPvoI\n",
374 "d+7cAQAYjUa8+uqrbRpXznjwwQeRmZkJk8kEwCJK37RpU7vHjRw5EkVFRTh9+jQAoKGhAf/617+w\n",
375 "YMECt9fgLoxh5QXa01hZGRUlhYDDwrFbTAkBR5DKZsL1JpiSC55R0RReCZdFo698ELT6OpTVFPlk\n",
376 "rvo6PfgCDvgCSzhFKOJ1SvX1GqUWkXGB0NbrfTqPRl+HXwuPYPzADABAWlI6jufv79CXRXsQQmyl\n",
377 "FpojGdjXp4VCT185iEGxKRD7dW6WXFhAFCpr74AmdPuDPcBSHJQg0I+LjP7B2JdvqbUm5LHxv/fJ\n",
378 "8e6pO6B98Hx2R4RCIb766its3boVM2bMwLRp02A2m516fCiKsoUFk5KSsHnzZjz55JPIyMhARkYG\n",
379 "UlNTHWqsWv7dkszMTERFRWH69OmYMWMG3njjDcyfPx+AxWB76qmnUFZW1uo4FouFTz/9FK+99hoy\n",
380 "MjIwa9YsrFixAikpKR26H+7AhAK9gFpnQlJY+yJOiqKwdIQcW07cxrg4mcPyDL9nLBXXWxhWAYGg\n",
381 "r+V10Yq6P7nFZzEw5j7Lhx4ojEyYiDNXf8ac0cu8PldzbxVg8ViV3/atUJQQApVSi7SpfXG9oNKn\n",
382 "c2Xn7ceQ3qmQCmVQ602IDe0HPkeAq6U5SIz0rkZGV1IGFp8HQbh9lpx0YALKdvkuZHYsNwuzU7z/\n",
383 "2mgPAc8PYoEU1XWVCPH3XZhToTVAZ6QRKORiUp9AfHKuHEqtAcEiHib2kWFfgQIHC2swLcE7zXi7\n",
384 "O3FxcdixY4fDfXv37rV7nJqaitTUVNvjtLQ0pKWl2R4rlUq8++67tscXLlgE+9HR0cjOzrY7V3NB\n",
385 "OofDwapVq7Bq1apWa+ByuU7F6wDQt29fm+6rM2E8Vl6g1kWPFQAMkYsRLOLiYCET3mqJpUegfcqs\n",
386 "RbzO3KuOklv8K5JiRtoej0qYiDPXfBMOVDWVWrBiEa/7NjynazACAILDJNDW+W4uq2h90uA5MNEE\n",
387 "j36RhwYjbfFa5e33+nyOvFWApeSCrzxW5TXFqFSXIjnO97/oHREhi7F5WH1FpcYAUICQy4KIx8aE\n",
388 "eBl+uGoROLMoCv83OhJbz5V1WR/BnkJ+fj7Wrl1rC9/RNI3Nmzdj+vTpXbyyzoExrLyAJRToWjYJ\n",
389 "1aS12nGhAgazb93e3Q1SWQYqrIVhxWisOgxNaOSVnMPAZoZVYuRQqJrKL3ibagceK1+HAlVKLWTB\n",
390 "IghFPDTqTTCbfPOeKiy7DKPZgAFRI1DTYESjiUZ5XSNSB0zHmauHYTB516hTX8iH/9ABrbb7Rcth\n",
391 "qtPAUON9T+CxvO+R2v8BcNhdkxkXHhjlc8NKoTFAJuDYQk8Z/YOx/0o1TE26134hItwXKcWOC0wf\n",
392 "QU/o06cPeDwepk6dioyMDEyZMgVisRgrV67s6qV1Coxh5QXaKhDqiKQwMWJkAvx4lemD1xzakcYq\n",
393 "IIjRWHWQ4sqrkApldkUXWSw2RvSdgLPXfvb6fCplC8NKxPe5eF2lbEBgsAgUi4JQzIPWRx6ywxd3\n",
394 "Y9LguaAoCoomY7GsvhHB0nDEhPbFhRvZ7ZzBPZx5rCgWC5KkPqjP866AnSY0jud9b9OPdQURsmif\n",
395 "CtgNZhpagxnBoruGY+9AP0RIeDhdfNdQXXafHAcLa3C71reavZ4Mj8dDZmYmfv75Z2RlZeHw4cNY\n",
396 "s2aNT/sC3kv8Pq7Sh9CEoE5vglTgXkGAxcMj8HlOJfQ++oXd3bCUWnBgWEn8QerVIDTjmneX3JJf\n",
397 "7bxVVkYlTMQZH2QH1ihahAI7zWNlKR4rlvB9khnYUrSu1FrCj2V1lmtLS5rh1XAgbTKh7nIh/Acn\n",
398 "OtxvbW04YtMBAAAgAElEQVTjTfJLzkMkkCImNMGr53WHcFkUylXFPju/UmuEmM9BkIhntz2jfzD2\n",
399 "FdxtGC7z4+IPg0Px39OlPlsLQ8+GMaw8RNNohoDLBtfNdgh9g4UYECrC3nyFj1bWzahXAyw2KIl9\n",
400 "ZXqKwwElkoDU+b5abk/DIlxvbVgNiB6Byto7UNZ5L9xhaDRB12CApFlLGS6PDUIIjAaT1+Zpiapa\n",
401 "C1mQxZgTSfg+qWWVnbcfg+PG2Fq1KLVGCDgslDdpukYmTEReyTnU62q9Mp+2sBj88GBw/SUO9/tC\n",
402 "Z3UsNwvjkrrOWwVY29r4zmOl0Brgx2Uh0M8+1Dk2LgC3anR2Hqr/GRCCivpGnClhPncY3IcxrDzE\n",
403 "HeF6Sx4bHo6vL1UxQklYewTKHe6jAgIZnZWbmMxGXCu95LBFCIfNxbD4NPx67RevzadSahEQJASL\n",
404 "ZZ9CLRTx0dDk4fEFKmWDzWMl8oHHyipanzxkrm2bQmvAwHARypoMKyFfjCG9x+D0lYNemVOdU4CA\n",
405 "oa3DgFakSQmov+w9w0pvaMD560cxdkDXCotDA3pBWVcOM+0bQ1yhMYLHphAktP+85rFZeKBfEL6/\n",
406 "ctdrxWWz8HhKJP5zupTRwjK4DWNYeYi7+qrmxMr8MCJSgm9zq7y8qu6HpdSCE8PKX8ZkBrpJYdll\n",
407 "RATGQCxw3JtyVMIkr2YHWkotiFtttxQJ9Y3uidDE4rFqCj+KpQKvZwY2F61bUWqNSA4X2wwrAF7N\n",
408 "DnRUGLQ54n5xaCgphVnnnWs9c/UwEiOHwl8U6JXzdRQeh48AURAU6nKfnF+hNYBFUQh00LYmPTEI\n",
409 "Bwtr7KQZ90VJERXAx3e5TFSBwT0Yw8pD1DoTAtroE9gejw6NwHd5CtTpfRcu6Q7QlWWgwns53Gfr\n",
410 "F8jgMs7CgFYGxY7CbcV11GqUTse4Q02LUgtWfNkv0FKQlAse3/L+80UosLlo3YpSa0T/MBFq9SYY\n",
411 "mr6Ik2NTUFl7BxVeEF9bWtm0zgi0wuJxIYqPQf2VGx7PBQBHc7MwrgtF680Jl0X7LDNQoTXCRBPI\n",
412 "/FobVuESPgaEiXDkhv3nzOOjeuGrS5WobvCd15Wh58EYVh7iatV1Z/Ty52NsbAC+vvz79lo5qrpu\n",
413 "hQkFuo+ljc19TvdzOTwM6Z2KXwu9Ew5sWRzUii8F7LXVDXbGnLdDgS1F61YUWgPCxDyEiXmoqLdc\n",
414 "G4fNxej+U5Gd75nXyqxvhKawCJKktvs5SgcmoN4LAnaFugy3ldcxLD6t/cGdQESg75oxKzQGGEy0\n",
415 "Q48VAMzsH4x9+Qq7Svq9/AV4oKmPIAODqzCGlYd4orGysnBoOPZfUUKl+/3+KmpXY8WEAl1Gb2hA\n",
416 "cdU19Os1uM1xo/pNxhkvlV2oUToxrHxYJLSmSddlxduG1Yn8H+xE6wBgpglUOhOChFzIpfwW4UBL\n",
417 "dqAnLW7q8woh7hMDtoDf5jhvCdiP5X2P0YlTwOXw2h/cCVg8Vr4RsCu0BmgN5lYaKyvDe0lRbzDj\n",
418 "qqLBbvvCpj6CV5g+gl5Dr9fjmWeesWu07CtKS0sxZcoUvPTSSz6fywpjWHmIpx4rAAgV8zCpTyC+\n",
419 "yPFtS457FUKIpYaV01AgUyTUHQpu/4be4QPA5/q1OW5wXApulOejrsGzMCuhSauq61Z86bGyCNfv\n",
420 "zin2YiiQEIJDOd/YidYBoFZngoRvyQKOkPBR1my+3mH9wWZxUFh2ucPzOisM2hJJkuceK0IIjud9\n",
421 "f8+EAYGmkgs1vim5UKUxQm+iIXXyec1mUchIDEZWgX14XMhjY9l9crzD9BH0GgKBAFu2bPF5XauT\n",
422 "J09izpw5SEx0XLrEVzCGlYeo9Z5prKw8NDgMh67XoMrHBRXvSTR1lv/FjoXWrIBA0IzGymUs9auc\n",
423 "hwGt8Ll+SI5LwfnrRz2ar06th8DvrtapOb4sEmox5u56rIRiHnRaA2ja8y8/i2i90U60Dli8HtYC\n",
424 "k738eXYeK4qimkTs33d4XmeFQVsiSeqD+oIbIOaOZxRfK70IFsVGfHhSh8/hbSxtbbzvsdIbzWg0\n",
425 "0wgQcMBqo+Hv1IRAnChWt9K8TuojAwXgENOKrFsRGBiIzz//HGPGjOnUeRnDykPUOs89VgAQKOQi\n",
426 "vV8Qdub8/lopWJsvO+twzoQC3aM94XpzLNmBnoUDnemrAB97rJoyAgkh0L+9AfSZI+ALONB5YT5H\n",
427 "onXAIlwPaSowGSGxDwUCwNgB03H66iGYzB0L69deaFu4boUrFYMXEgjtzY63Jjqal4XxAzOcvu+6\n",
428 "ghD/CKg0ig7fP2dUaY0IEHAQKGq7XU+AHxcp0VL8VGjfFcPaR/Dj31kfwWvXruGxxx7DnDlzMGfO\n",
429 "HLz++uu2fTNmzMB7772H+fPnY9q0aViwYAHU6rt1v44dO4aMjAykp6cjPT0d+/fvx5AhQ2z7Bw+2\n",
430 "SBWKioowfvx4bNiwAXPnzkVaWhreeust27jGxka88sormD17NubPn4+lS5fizh3L695kMuGpp55C\n",
431 "WZljDVxiYiLi4+O9ek9cwXOL4HeONzRWVh5MDsOyr/OxIDkMcmnbGouehKX5suMwINCUFdjDQoEn\n",
432 "D1+HLFiI/oMd68o6Sl2DCgp1GeIj2v9yBoCh8an44MDfodXXQyRwXJCyPWoUGoelFgBruQXvG1Zm\n",
433 "M426Wj38A4Wg7xTBfOUS6DtFmKwVQFsaD1FibIfPbRWtPzLhz632KRvueqwsGiv7awvxlyMyuDcu\n",
434 "3MzGfX3vd2teo7oejRVKiPrGuDTeqrMSuzi+OQajHmeuHsampV+4fawv4bC5CJKGobL2DnoFxXnt\n",
435 "vAqtAWIe26lwvTkz+4dg09FizB0YaufdSgy19BHceaECy0c5/7zqCJtX/+jV87XF8xsfcGmcRqPB\n",
436 "6tWr8c477yAsLAwAsHHjRmzfvh2LFi0Cm81GdnY2vv76a1AUhXXr1uHDDz/EypUrUVBQgOeffx67\n",
437 "du1CdHQ0TCYTXnjhBTsj3vo3i8VCXl4eVq5ciczMTOh0OqSmpmLevHmQy+VYv349UlJSsHbtWgAW\n",
438 "Y+/xxx9HVlYWOBwO3n77bS/fIc9hDCsPqdUbvWZYSQUc/E9SCD77rRwvToj1yjm7A3Rlaavmy3b4\n",
439 "CQGaBtHrQAna1g11F67nVyKsl7/XDau8knNIjBwKNsu116QfT4QB0SNw/sYxjEua0aE5LT0C7Q2r\n",
440 "d07expyBoZCKfOOxqlPpIJbwweGwYDiXDc7o+8FbsAz6VzZD8P9Wwrjo/8AZM7FD3hhHonUrCq0R\n",
441 "wU0eqzAJDwqtAWaagN2sMGraAEtNK3cNK/XFK5AO6gsWx7XnzlYodM4Ut+YBgHPXj6J3+AAEScLc\n",
442 "PtbXhMssmYFeNaw0RvA5LAQ5KLXQkv6hQgg4LFworcfwSHt5wtIRcvxpdwGmJwYh0l/gtfW5aux0\n",
443 "JmfOnMGVK1ewfPly2za9Xo+hQ4faHq9YscL2HktLS8OePXsAALt27cLy5csRHR0NAOBwOFi7di2O\n",
444 "HDnicC65XI5Zs2YBAPz8/DBs2DCUlJRALpfju+++w6VLl/D+++/bxldVVUGv10Mg8N5z4E0Yw8oD\n",
445 "CCGo05s7XCDUEXMHhmLpV/koVukQI+sZRkR7kMoysAeNcLqfoihbkVBnta66E4ZGExQV9WC52QbJ\n",
446 "FdwJA1oZ1W8Szlw93GHDqlqhRZ8B9l/QJ4vVSI6QYEy0FDqtAYQmoFjeCzlZegRawo+mX0+Av/gp\n",
447 "UFweyofOBJ+fhuh922E6cxT8pc+CJQty+bxW0fqSyc873K/UGtE70PK+5LEt7VGqNAZENPMwj+o3\n",
448 "GZ/+8iY0+jqnBVod4aq+yopkUF8Uf/C1y+ObczQ3q0sbLrdFhCwaFTXeLbmg0BrAYTkuDtoSiqIw\n",
449 "c4Clf2BLwypQyMUfBofhv6dL8fdpnR9i6kwoikJycjJ27tzpdIxEctfLzefzbVl+er2+VXZsW9my\n",
450 "zc8DWMTtzTMGt23bBpms9Q+dexVGY+UBWoMZPDYFHsd7t1HEY2N+cii2//b70Vq1VWrBiqWWVc8Q\n",
451 "sJffViM4TAJlpQa0l9tlWAyr9oXrzRkePw75JeegM3QsnVzVotSCiSaobjBCoTWAzWaBx+dA5+VS\n",
452 "IjVNrWzoyjIQdQ1YfS0GiUjCh0oYAb/174AV3Ru6zMdhzD7kcgkEZ6J1K8pm4nUArUouAIBIIMHg\n",
453 "uBScuXLIrWuqa6cwaEukSQmoy73mdnmHGo0C18su476+E9w6rrPwRckFRVNbJVcMKwCYGC/D5QqN\n",
454 "w2Si2UkhKKvr+X0EU1JSUFhYiEOHLK9jmqaxYcMGXLx4sd1jFy5ciI8++simhTIajXj11Vc7VIrk\n",
455 "wQcfRGZmJkwmS0LB2bNnsWnTJrfO4UkJlI7AGFYe4I1SC46YNSAEeZUaXFc2tD+4B0BXlrWpsQJ6\n",
456 "VpHQshIVYhOCIZbwoar23nOsUJdDb2hAVHAft44TCSToFzkEF26ccHvORr0Jep0JEuldl7xSawBN\n",
457 "YPtS8oWAvbap+bLp3Amwh48BxWIDsJRc0NY3guLywJ+/BIIXNsL4/VfQ/+tvoFXV7ZwVOHzxG4ei\n",
458 "dSsKrRHBwrs1nyKkvFaGFQCMTUrH8Xz3sgNr22ll0xJ+eDAoUGiscK96fnbefozsN6ndchxdRYQs\n",
459 "yutFQhUaA4w0DZkfB2aaRqOp7U4Xflw2JsbLsP9K63tr7SP439OlMPbgPoJCoRBfffUVtm7dihkz\n",
460 "ZmDatGkwm80YNGiQw/EURdneN0lJSdi8eTOefPJJZGRkICMjA6mpqQ41Vi3/bklmZiaioqIwffp0\n",
461 "zJgxA2+88Qbmz58PwGKwtSVed7S2zoAJBXqAN4XrzRFwWHh4cDi2nS/H+h7ubib1dQBtBiT+bY5j\n",
462 "+QeC7iGZgWUltRg8Mgq11Q2oKq9DUKhj4be75BafRVLMfR36ABmVMBFnrh3GmP5T3TqupqnkQfMw\n",
463 "X5XG4h1QNP0vFPGg0xiAULeX1ca8DYjvHwbTwWzw5j5m2y6S8FF8464BxY7rC7/178CwZyd0mY+D\n",
464 "9/CfwBk72eE90urr8WvhL3hkwrMO56QJQbXW6MBj1dpoHBI3Bu/98CqqaksRGtB++FpfoQDdaIBf\n",
465 "tOuaO4qiIBloEbALIkJcOoYQgmO5Wfjfqatdnqez8UVbG4XWCApAkJCLnbl5uKqsxt8nTmjzmIz+\n",
466 "wXhp/3U8MjQc3BZh+5FRUuzL5+PbPAUWJN97OjVvERcXhx07djjct3fvXrvHqampSE1NtT1OS0tD\n",
467 "Wtrdiv5KpRLvvvuu7fGFCxcAANHR0cjOzrY7V3NBOofDwapVq7Bq1apWa+ByuS6J1x9++GE8/PDD\n",
468 "7Y7zFozHygPUehP8vVDDyhHTE4NwS6VDfmXPrvZLN7Wyac8Y6CmNmAlNUFZSC3l0AEIjJFCU13vt\n",
469 "3LnFZzEw2r0woJXhfcbj0q3TaDTq3DpOpdBC1qLUQpXGgAgJD1XaZh4rL1dfVym1COTqQZfdBrv/\n",
470 "3QrzjqqvUxwu+PMWQ/DiazD+sAv6N14GXdPaE5Gdv9+paB0A6vQm+HFZ4DcL/cslfJQ7KErKYXMx\n",
471 "OnEqjrvY4saqr3LXKHa3tc3NygIYzAYkRg5pf3AXESwNR52u1u3XojMIIVBoDahvNCFQyMUP12+g\n",
472 "rL79912MzA+RAQKcLHYc8ns8pRe+usj0EXREfn4+1q5dawvf0TSNzZs3Y/r06V28ss6BMaw8QK0z\n",
473 "eVW43hwem4VHh0bgk/M9u0eVK2FAoOdorKoVGviJeBCK+QiJkKLKS4YVIQR5JefcFq5bkQpliI8Y\n",
474 "gIu3Trt1XI1Cg6AWGYGVGgMGhouhsIUC+V4NBRoNZui0BggKz4MzdBQozl0PkriNtjbs2D7we/Vt\n",
475 "sHsnQLfmCRiPHbBpL6yi9UmD5zo8FrDPCLQi9+ej1EEoEEBTsVDXWty013jZGe62tjmWm4VxSTPu\n",
476 "qdpVLWGx2Aj1l6OytuM1uppjrTul1ptgoBtxqbIKVVrXQvAz+7euxG6F6SPonD59+oDH42Hq1KnI\n",
477 "yMjAlClTIBaLsXLlyq5eWqfAGFYe4CuNlZXJfQNRpTEip8x7Xo17DdJeqYUmekqR0LKSWvSKDgAA\n",
478 "i8eqwjvP7Z3qm+Bx+C6FnZwxKmEizlx1T3Bdo9RC1qz6OWDRs/QLEaK+0QyDmfZ6Lavamgb4y/xA\n",
479 "nz8BzoixdvusHitnxgzF4YI3dxEEL70G44/fQv/GGtA1SptoPSnaeXaqpTiovfg5QsJDRV2jw1Yn\n",
480 "fSIGAgBuVOS1e03qC+5lBFqRJCWgzkWPlclsxMmCAx3O/uxMwmXRKPdSZqBCa0SgHxciHhtHi4uR\n",
481 "Fh0FRYNrhtWYGH/crtWjWOXYe8b0EXQMj8dDZmYmfv75Z2RlZeHw4cNYs2aNz1vY3Cv8Pq7SR/hK\n",
482 "Y2WFw6KwaFg4tp4r6/Sshs6CrnDRY9VDioSWFtdCHmMJNUn8BTAZzV5pHNyRbMCW3Nf3fuTcPAGj\n",
483 "yXUjyFJ1vbXHKlzCR6CQC6XWaGnE7EWPlUqpRagUMN+8Cvag4Xb7OFw2OFw29O1kIbJjmrxX8YnQ\n",
484 "rXkCRd/8B5OS57TpyVG0yAgELCJnEY+NGgfhoLstbtoOBxJCUHexY4aVqHckDIoaGOs07Y797UY2\n",
485 "IoN7e2R8dxYRXtRZKbQG+As4kAm5OHTzFub3T0Sj2Qydsf0QHpfNwgP9gpx6rYQ8NpaOiGD6CDLY\n",
486 "wRhWHlDrpXY2bTEhXgadkcbZ23U+naersGisXPFY9QyNVVmJCvImjxVFUQiVS73itcot/rXDYUAr\n",
487 "AeJgRIX0weWiMy6Np2mC2uoGu359AFClNSBUzEWomAuF1gChmAetFzVWKqUWMbqbYA8cDorfukCg\n",
488 "WMKH1kl4rjkUhwPenMdA/vI3RF2+hgk/54KuUTgdr3QQCgSACAclF6yMHTAdp6781GaLloZbd8AW\n",
489 "C8EPCWx3za2ugc2GpH886vOutzvWEga8N2tXtSRCFo1yL5VcUGiMEPJYEPMI8hRKpEZHIVQkdNlr\n",
490 "lZ4YjJ9vqKAzOm5lM7mv5Xlj+ggyWGEMKw+wNGB2rS5KR2FRFBYPj8An58t75C8iS9V1FzxWUhlI\n",
491 "vRqE7r59uhq0BjRoDHZZgCHhElSVe2Y0m2kTCm6fR1IHhevNsfQOPOzS2LpaHfxEPHB5d39cEEJQ\n",
492 "pTEiTMxDiIiHKo3R642Ya5QNCKnMBee+sQ73i6R8aNzwAp6oz8fB9BHgJw5GQ+YTMB790aGHWKE1\n",
493 "tAoFAs4zAwEgLCASEbIYXLx1yun87hYGbYnEBZ1VXYMK+bfPYVS/SR2epzMJD4zyqseKx2ah3lSD\n",
494 "1KhICDgchAiFULioswoV8zAoXIyfbzjWeLIoCk/+DvsIMjiHMaw8QK33nXi9OWNi/MGigOyiWp/P\n",
495 "1ZkQTR1gMoOSBrQ7luJwAKHYUp6hm1JeUouIKH+wmpUmCI2QepwZeLOiAMHScPiL3Pd4tOS+hPtx\n",
496 "/voxl5rgOmq+rNabwGNT8OOyESrmoUpj8Hodq/rKGgjLroIzZJTD/SKJ64YVIQSHL36DiUPngzfn\n",
497 "Ufit2gTjwb3Qv74adHWV3Vhli1ILVhwVCW2OJRzovKaV+kJ+h4TrVqRJCahvx7A6UfAjhsWnQcj3\n",
498 "TmkPX2Nta+MNFFojWBRQ1lCFKb0tbXJCRSJUaV3XRc3sH4x9+UqnkozEUBFG9JJiZ87vp7Azg3MY\n",
499 "w8oDfC1et0JRFJYMl2P7+QqY6Z7jtbJkBMpdzlBidfOSC6UltZBH26fyh0RIPM4M9EYY0EqwNBzh\n",
500 "sijkl5xrd6wjw6pKa0So2BIuCxXzoNAYLBorL3qshCW5oPoMACUUOdwvlghc1q0Vll2GwXRXtM6O\n",
501 "7g2/dVvA7jcIDWv+D8Zf7mb1OcoKBAC5lIfyNgyrlMQpuHjrNLR6x8+zpx4r6cD2BezHcrMw7h5t\n",
502 "YeMImTgEeoMWDY3ta8fao0pjgN5kQrlGhfExlt51wULXQ4EAMLSXBHoTjYIq58csu0+OA1ercUet\n",
503 "93jNDN0bxrDqIIQQi3jdR3WsWjIiUgIpn41fnLijuyN0ZRlYLoQBrVABQd3asCorvquvshIUKoa6\n",
504 "pgFGJ/oNV7DUr/KOYQU09Q689nO742oUGgQGtzCs6g0IazKsQkQWjRVfwIHZZIbJg2u0otcZEaW+\n",
505 "Al7KOKdjHNWycoajSusUhwPe/yyE3+rXYTycBf2mVTArKqFsMxTofD6xQIpBsSNx1kGIlTaaUJ93\n",
506 "Hf7JiS6t1+H5E3tDe6MYdKNj47VEUQi1tqbDNc66AhbFQlhAFCq9oLNSaI24U69EQmAoRLwmo18k\n",
507 "dLnkgmU9FDL6B2FfgXMNXqCQiwWDw/De6VKP18zQvWEMqw6iM9JgURQEXuwT2BYURWHJiAh8+ls5\n",
508 "TD3Ea0UqXCu1YIUKkHXbzECziUZlWR0iouwNKw6HhYBgIaorO/bL3GDU40Z5HhKjhrY/2EVGJkzE\n",
509 "ucIjoNvRs6mUDa08VpUaA0JEdz1WVRojKIryWi0rVbkKUboicIaNdjpG7GIo0Fpp3Zmgmx0VZ/Fe\n",
510 "JSaj4eUnMaX8rMP3e4SEj7J6Q5uZu86yAzVXb0IQGQaOxLH3zRXYfnwIYyKhuVbkcP+x3CykJaWD\n",
511 "1dT2p7vgDQE7IQRKrQEl9RUYGxVt2x4qEqHKDY8VAEztG4QzJXVQ6523w5mTFILSukacvd2z+wh6\n",
512 "il6vxzPPPGPXaNkX3Lx5E/Pnz0dGRgamT5+Of/3rXz6dzwpjWHWQztJXNSc5QoIIKR8HrrXf86w7\n",
513 "YK267iqWkgvd02NXVV6HgEAh+A5eM6ERHc8MvFZ2CdEhfbyqnQkLiIRMEoordy60Oa5aoWlVakGh\n",
514 "NSBMYvHqhIot1dcJIV4ruaC7cA4NAb3A8nfe6V4kdS0r0FppvS1tmtV7Vf3U3zGl7Az0//wraGWl\n",
515 "3RipgAMWBdQ1OjdEh8Sl4rbyBhTqcrvt6pwCBHigr7KtwYmA3UybkJ3/Y7cKA1oJl0WhXFXs0Tks\n",
516 "mj8WlI01mNQ71rY9RCiE0g2NFWB5nsfE+OPAVeefv5Y+gr16fB9BTxEIBNiyZYvP61o9+uij+NOf\n",
517 "/oSsrCxkZWXhwoUL2L17t0/nBBjDqsN0lr6qJUuGR2DnhQoYTN3/Tetq1XUr3blIqLWNjSM8yQy0\n",
518 "1K/yXhjQiqV3oPNwoF5nhNFghljKt9teqTHYNFYiHhsULJWvvVUklHP5NLTxw9oc40oo0Cpab6vS\n",
519 "enMqpRHYmf4i2ElD0fDykzD+Yu99ipDw29RZcTk8jOo3Gdn5P9htV18ogNQDfZUVZ4VCL906jWBp\n",
520 "OHoFxXk8R2cTERiDCg89VgqtEX5cAj4lRmzA3R8B7oYCrWT0D0bWFWWbGdojo/whl/LxXZ7zsCFD\n",
521 "5/D1119j6lRL/1M2m41x48bhypUrPp+3xxpWam0NTuQ7Tpv2Bp1Rw8oRiaEi9AkSIstB1/XuBu1u\n",
522 "KLAbi9dLS2ohj3FsWHmSGWgRrntfOzOq3yScvfYzaOLYgFcptQgMFrVKPKjSGBDaTOAdIuahylok\n",
523 "1EPDipjNkBRfBAY7DwMCd0OBbb33raL1AdHDnY5pjlJrRJDED7yZD0G45g0Yvv0M5usFtv1yqfPW\n",
524 "Nlas2YHN16XOKUDAEO94rBxlBh7Ly8J4N71VKp0RH5ztep1QuMzzkgsKrQE6sx5idiCEvLuh0BCh\n",
525 "yC3xupV+IUJI+Gycu9P2+/XxlF748mKlw8Kx3Ylr167hsccew5w5czBnzhy8/vrrtn0zZszAe++9\n",
526 "h/nz52PatGlYsGAB1Oq7IdBjx44hIyMD6enpSE9Px/79+zFkyN0elYMHW3p8FhUVYfz48diwYQPm\n",
527 "zp2LtLQ0vPXWW7ZxjY2NeOWVVzB79mzMnz8fS5cuxZ07lnZHJpMJTz31FMrKHLcV6tXr7g93g8GA\n",
528 "nTt32jWK9hWdbxn4mDvKm/j+3A6cvXoYeqMOg2JHOW2q6gm1ehMCOkm43pLFIyKw6ofrmN4vCH7c\n",
529 "7qWbsEK09YDJCKqNkE5LqIBA0N0wFEgIQVmxCuOmJTjcb80MJIS41cNNq6/HHeVN9JUne2upNuSB\n",
530 "sRAJpCgsu4x+vQa32u8oIxCApYaV5K5hFSpqXnLBsyKh5iuXUM+TQdo7qs1xPD4HFAUYGs0OQ6/A\n",
531 "XdE6i3Ltt2XzquusXjHgzXoYhj074bdyPYD2MwMBIEGeDDNtws3KAsSHD4C5QY+Gm7chGRDv0hra\n",
532 "QpKUgLq8QhCaBtUUXtHo63Dx1in875RVbp3rqqIBP16txvKRXVuh3RttbcrrGlGtU6OXJMRuu5TP\n",
533 "g8FsRoPRCCHX9VqEFEVhZv8Q7CtQYGSU1Om4SH8BpjX1EXx+fEy75/0xfIzLa/CUBypOujROo9Fg\n",
534 "9erVeOeddxAWFgYA2LhxI7Zv345FixaBzWYjOzsbX3/9NSiKwrp16/Dhhx9i5cqVKCgowPPPP49d\n",
535 "u3YhOjoaJpMJL7zwgn2SSNPfLBYLeXl5WLlyJTIzM6HT6ZCamop58+ZBLpdj/fr1SElJwdq1awFY\n",
536 "jL3HH38cWVlZ4HA4ePvtt9u9FqPRiOXLl2PQoEGYMGGCm3fMfXqEYWVpQPsrsn79DLcqr2Dq0Afx\n",
537 "/5Z/i41fPwWFuswnhlVXaKys9A70Q3KEGHvyFHhoSHiXrMFT6MpysMJcL7UAAKyAQJDa7qcvq1fr\n",
538 "QdME/jI/h/uFIh54fDbqVDr4BwodjnFEwe3z6NsrGVxO6xIA3iClyWvlzLCStTCs9EYzdEaznSc3\n",
539 "VMxFlcaAcBEfDR5WXzf9mo0bgniMDm5f6G0NBzoyrKyi9YXjn3F5bqXWiIHhd0NJnHHTYNizA+ai\n",
540 "62DH9oFcym+3p6e1xU123n7Ehw9AXe41iPrFgsX3/PnjyaTgBkjQUFwGUVwkAOD0lYMYFJsCsZ+/\n",
541 "W+cqVTeivtGMOr0J0i76jAMAf2EgzLQZGp3a7WuwcrGiBhI+G+Ei+9cMRVEIaaq+HuPv3rknxMvw\n",
542 "4U1+As8AACAASURBVNlSVNQ3IlzCdzpu4ZBw/O+ufFyp0iIxtO3XrKvGTmdy5swZXLlyBcuXL7dt\n",
543 "0+v1GDr0bqLMihUrbJ/haWlp2LNnDwBg165dWL58OaKjLQkDHA4Ha9euxZEjRxzOJZfLMWvWLACA\n",
544 "n58fhg0bhpKSEsjlcnz33Xe4dOkS3n//fdv4qqoq6PV6CAStOy+0xGAwYOnSpQgICMC///1v925C\n",
545 "B+nWoUCT2YhjuVn467aF+OTQ6xiVMBFvrdiHeWOWQyqUIUQaAUVdefsn6gBdpbGysnBIOPYVOC9Y\n",
546 "d69DV9xxqeJ6cyj/QJDa7uexKiuuRa9oWZtGZEiE1O16Vr4KA1oZmTARZ68edvgaq1FoEdSy1ILW\n",
547 "iBARD6xm1xki4kGhNXpcJJTQNEy/ZuOOfyIELnQ7ELehs3JFtN6SlsVBKR4P3PQHYdizE4C1rU37\n",
548 "1zd2QDpOFhyAmTZBfSHfK2FAK5ZCoXd1VsdyszC+Ay1srHWYyr3Qw9ITKIpChCzKo0Kh15R1iPKX\n",
549 "IlDY+jVjEbC7Hw4UcFiY3DcQ+6+0/SNPxGNj2Qg53u2mfQQpikJycjL27t1r+/fTTz/hn//8p22M\n",
550 "RCKx/c3n821Zfnq9vtXnRlvfVc3PA1jE7c0zBrdt22a3jrNnz7pkVOn1ejz66KOIiorCO++849YP\n",
551 "eU/oloaVRl+HPae34un3ZuJ43n48PO4pvL7sK9yfPBs8zt1fECH+Ea2ycLyFuhNrWDkiViaAmSao\n",
552 "9GLhxc6EuClcBwAIRYDZCKJ33Gn+XqW0ROVUX2UlNELidmagr4TrVqKC+4DN5uJmZUGrfTXK1h6r\n",
553 "Ks3dGlZWvFUklL55FWa+CGx522FAK5bq660LNborWreiaGjdgJl7fzroa7kw3ylqt5aVlXBZFEID\n",
554 "euHSrdOWwqBDPReuW5EOSrBlBpbXFKNSXYrkuBS3z1Na1wgJn41yFwxFXxMeGN1hAbvRbIZCa0S0\n",
555 "NMChYeVu9fXmzEgMxo9Xq2FoJ/Nvct9AEKBb1h9MSUlBYWEhDh06BACgaRobNmzAxYsX2z124cKF\n",
556 "+Oijj2xaKKPRiFdffbVDjoAHH3wQmZmZMJksZS7Onj2LTZs2tXucTqfDI488gsGDB+Mf//iH2/N6\n",
557 "QrcyrCpVt7H10CY8+/7/oLS6CH+dvwWZf3gXQ3qnOrREQ6RyKH3kseoq8boViqIwKEKMS+WeVybu\n",
558 "ClxtvtwciqIsRUK7mc6qrYxAK6HhElSVuZ4ZqNIooNIqERfaz9PlOYWiKKT0m4QzV+0LW9JmGrU1\n",
559 "DZAFtTasQsT2X2DWUKDIQ4+V6dds1MUOgcyFMCDgPDPwenmuW6J1wGKMKTRGW30uK5TAD9wH5sK4\n",
560 "9wsE+nGgM9FocKFXXFrSDBzP329pZeNFj5Uk6W7JhWN53yO1/wPgsN3vZVqqbsSwXhKXDEVf40nJ\n",
561 "hXNl5eCyeBBweE49Vu7WsrISFSBAXKAfTrTTZoxFUVg6IgJfXKzsdtEFoVCIr776Clu3bsWMGTMw\n",
562 "bdo0mM1mDBo0yOF4iqJs38NJSUnYvHkznnzySWRkZCAjIwOpqakONVYt/25JZmYmoqKiMH36dMyY\n",
563 "MQNvvPEG5s+fD8BisDkTr//yyy84c+YMzp49i1mzZtn+NRfg+4puobG6WnoR3//6GQpu/4ZJg+fg\n",
564 "9WVfIVAc0u5xwf7huFx8xidr6kqNlZVB4WJcrtBgakJQl66jI9CVZeBOmO72cZR/U5FQN42yrsJo\n",
565 "MKG6SoswuXOhKwCEyKU4eqDtfm/NySs5hwFRw31e9HFkwiRs2bcKD497yvbhp67VQSThg8uzn9uZ\n",
566 "x6pKa7AUCO2gxooQAtO5bJQPW4TAYNc0aGIJHxoHhsGhnN1uidYBoMFIg6IAIbf1MdzJM6F9bjF4\n",
567 "laWQS3gor29EfFDbaxydOAVfHH0L8WouRH2i2xzrDtKBllAgTWgcy/0eL877f26fQ2+iodabMCRC\n",
568 "goKqjnlzvEmELAYXbmZ36NgDN24BRIxGE41AB9GFEJHrjZgdMbN/ML7JrcL98W2HlIfKLWGui+Ua\n",
569 "DJFL2hx7rxEXF4cdO3Y43Ld37167x6mpqXYZd2lpaUhLS7M9ViqVePfdd22PL1yw1MmLjo5Gdrb9\n",
570 "c9xckM7hcLBq1SqsWtU6CYPL5ToVr6enp9s8Zp3NPeuxMtMmnL56CC9/tgTvfP83JEWPwFsr9uGh\n",
571 "cU+5ZFQBQLA0wmceq67WWAFAcpNh1R0hlWVua6yA7qezqrhTh9AICTjtZG8GBAqh0xqg17mWnu3r\n",
572 "MKCVuLBEmGkzShTXbdtqFNpWrWwA+xpWVoKEXNQ0mMDz40Knbbs6uTPo27cAmkYZHYAADzxW7VVa\n",
573 "d4Y1I9DRr2rKTwTelFkw7PsSES6UXAAAiV8A+gjjUTkuCBTbe4axIDIMdGMjci79ArGfFDGhjrNQ\n",
574 "26KsrhEREj56+fO7XGMFNJVc6EBmoJmm8fPN2xDx2KjVmZ2HAjvosQKA0TH+KK834FZN29IEiqIw\n",
575 "OykE3+b+fupa5efnY+3atbbwHU3T2Lx5M6ZPd//HdHfknjOsdAYtfjj3Of7ywVzsP7cTM0cuwpt/\n",
576 "/AbThv0BAp7rGVMAEOIvh0Jd4RMXbO09YFhFywTQNJqh9EJF686ENGhBGvWgAlwXD1uhAgJBd6Na\n",
577 "VqUlrfsDOoLFohAc5prOihDSZFj5vvcbRVFNxUIP2ba1VWqhpWHFZbMgFbD/P3tvHt1Wfef9v672\n",
578 "XfIi2/KWfV9JAgkEN4WUUEgIO1PagTmdeZi2U9ofncAzD4ROaPPATCmdpdN2Di1tnzLDdIF2IE2h\n",
579 "LZBCCB2yQHY7++Jdlrxo33V/f1zLlqxdlmOn8D4n50TW1b1fW9K9n/t5vz/vN+5IDIVSTihHHEg2\n",
580 "xA7sQXHltQz2B6isKqywMpjSY20k0frVRYnWQRKuW3XZJ/eUG24j+v67zMFTsC5pkaeB09NLv6hn\n",
581 "giAIGBfN5a0Dvyy6eEygyxWiwawe1oxN/nklEWtT7Dn8kN2OWWOgzqiiPxDJXFiV4L6eDLlM4Ob5\n",
582 "Vexsy+8puH52Ja19vilBr14KzJ49G5VKxYYNG9i0aRM33HADBoOBLVu2TPbSLgmmTGHV77HzX299\n",
583 "my8/u5mTXYf40i1P8vXP/Iir5l5fMt2hVxsBEV+oNPPFbAhGYsRFEW0GauBSQiYILL4Mu1aJKJtS\n",
584 "JjRkl1leYPfF7MagY1FjMxZkFGof6iQWj1FfOX2cqysMV81dz76Toy7s2QurdCoQkrysShSwR/fv\n",
585 "QbZyLa4BP5Y8NFsCYztWo6L1O4s+/tiJwLEQDCaU121k1bHfFXzhtB0NYle56PfY829cBNRLpnOk\n",
586 "/xDXLiytM9DlCtJgVlOlU+IORQlOcsKDQWtGLpPj8hf3nX/97HmWWuux6lUM+CNZqcBS3NeTcfO8\n",
587 "at46N5hXW6dRyLhxbiU7Wj8cXSuVSsXWrVvZtWsXO3fu5M033+Txxx+f8AibqYJJ/y3P20/wnZ2P\n",
588 "879//CkisTBP3vc8D936DebUZxbIFQNBELCabTjLPBk4NKyvulSjm7mw1GbgaO/kayGKQbGO68m4\n",
589 "nPICxbgoCdebCiusJKPQ/AL2BA14qT5/s+sX4w956eo/D2SmAmNxkQF/5gLEaki2XCjujj3e24no\n",
590 "ceGvmo5Wr0rTdWXD2MKqFNF6AsnmoNmg/OQd1LX+D+7e/IWSKIr4PjjJldM/lhZxM16cbw7R6DMX\n",
591 "3ZVLoNMVotGkRi4TqDOo6J0CHRZbZXNRDuyiKPL6ufM0m6qp0ikJROIZ/bisuvFRgQBVeiXLbUbe\n",
592 "PJO/8Nu80MrrpweIxS8vEftHKB6TUljFxTgfnH2H7T/7HM/86m+ZVjOXb//1Dv5i/cPUWMrr9ltt\n",
593 "suFwZ7a7LxVTQbiewJK6y28yULR3Fz0RmIBwGZmEDjh9qDUKDKb8fitQuOXCpaIBE5AJMq6ae/3I\n",
594 "dOCAM71j1e+PYNIoUMrTTymjHSt10R2r6IF3ka9cy+BgkIoChesAGq2SaDROJCJ1EkoRrSfg9KVP\n",
595 "BI6FzFxB7Or1LD3yu7z7C/U4EONxPn7Vnbxz/NWyShUOiieYdbx03VaXW6ICQYrp6Z4SOqviLBeO\n",
596 "O5yo5HJEUYFBJadCq0jxVhNjMWLBECa1isiw+/p4cMvC6oI8BWsMKpbbjAwWqKP8CJcv8lYHO3bs\n",
597 "YP/+/QCsWLGC22+/PePzcrkcg8HA3/zN36DT5T4BPvzDu1EpNWy68s9ZM+8TJY0EFwqruR6nq7es\n",
598 "+5xsD6tkzKzU0u+PMBSIYCnAOHEqIG7vRj5vcUmvlQqry6Nj1Z0jHzATqmuN9Pd5icfiyDIUKCDd\n",
599 "lBxvP8B91/9tuZZZEK6aez3P7/oWNy2/n1g0hn6M43SfN0yNIfPnr8agpNsdYm4JlgvR/XtQ3f1Z\n",
600 "Bp2+NHuHXBAEAb1B6lopdbGindaT4fSFuWZafndu0+Y/4+qH/4rQwADqyuwdo6GDrZiXL2Be43JC\n",
601 "kQAX+k4yo3Z+SWtLhsPVTZevkzUH40R9fhT64jSpIHWsGszSjYDNpJ4aXlYVTfQMFG658Pq5c2yY\n",
602 "NQOHL8LMSg0VY86L3S/9Fseb77H8+9tLdl9PxjKbgVhc5Jjdx5Ikd/5MuG2xFae9vNejjzD1kPP2\n",
603 "ra2tjfPnz7N9+3a2b99Ob28vR48eTdnmnXfe4e///u954oknqKqq4syZM1n2Noq/2vB/+If7/5Nr\n",
604 "F940oUUVTEzHarI9rJIhlwksqtVz7DKiA+O9xXtYJTBit3AZoOviIPXNhccpqdQKjCYNA87s72V7\n",
605 "32kMGjNVxtpyLLFgzG9czqDXwdlz57OHL2fQV8Gw5YJ3mAosomMVH3AQt3chn79UMiQtcCIwgQQd\n",
606 "uKf1tZJE6wk4fBGq83SsABTVVg42rsS986Wc27kPtWFevgCZIKNl4c28c/zVktY1FruP/4ZrFmzA\n",
607 "PGcmntazRb/eG4oSjo1aE9SbVFNCbG2rmFZwx0oURX5/9jw3zJyJwxtGJghU6lLP1b5znTjf2ks8\n",
608 "GqVGpx+X5QJIRfymBdUFidgX1+pTumcf4U8TOQurgwcPsn79+pHH69ev54MPPkjZ5s477+QLX/gC\n",
609 "Dz30EE6nk6VL8wfCLmq+8pLpQybCfX0qWC0kY0mdgSOXkYBdtHch1DWW9FrBXIHoHkKM5zdinGx0\n",
610 "tw/RUMBEYDKs9bmjbS41DZiATCbnyjnXcbDtEJXW9LvybMJ1SMTaDIvXi+hYRQ+8i+KKqxEUCgad\n",
611 "/oI9rBIwGNV43EHePPzLkkTrCUhUYGE3gEeX34TyndekkPEsGDrYivkKyRi0ZdFoxM14IIoi7xz/\n",
612 "DR9bvCkt2qZQdLlDNJjUI+dmm7EwN/mJRjGxNqcHBgjHYiyyVuPwRRBFMW0iMNDZS9TtxfVB67CX\n",
613 "1fhvSm+YU8n+Dndemk8QhLx6vY9w+SNnYeXxeFIyfEwmEy6Xa+RxKBTi17/+Nf/0T//Ev/zLv2Cz\n",
614 "2di9e/fErbYETERe4FTSWMGoUejlADFQutUCgKBQglaP6CncpXwyEPCH8bpDVNcVZwhYU5d7MvBY\n",
615 "+/5L4l+VCVfNu56L7V2ZJwJ96VYLCSTc14s1CY3tl2wWAAb7fQV7WCWgN6k503W0ZNE6QCASIxKL\n",
616 "Y1QXKJqvr8c+eyWR372c8XkxHsd95CTm5VKUja1yGtWmOo5d3F/S+hI41XUYmSBnVt0iTItHo22K\n",
617 "QcJqIYF60xTxsqpsxj7UQVzMP6H4+rnz3DBzBjFROk+HYiJVYwqrYGcv5hWLcL61d1zu68kwqBVc\n",
618 "O8PC707m139apoiM5CNMHHIWVkajEbd79ALmdrsxmUYdpDs6Opg5c+bIz9atW5dGFU42JI3Vn3bH\n",
619 "ak61lm53CG9ofHe9lwLx3m5kNbZxdSxllvJPBvZ5w/SW8SLS0z6ErcmMTFbc71mTYzIwGotwsvMQ\n",
620 "i5pXlWOJRWNh00oiXiUKfXrXye7JTgWaNQqC0TgKjaJgKlB0DxG7cBr54hVEo3G87hDmCm1R69Ub\n",
621 "1Rzo+C3XL729JNE6jNKAhX5e600q3lvyScKvv4IYSO+E+M52oLSYUFWNdjJbFt3MO8d/U9L6Enj7\n",
622 "+E7WLd4keVktnlNaYTXcsUqg1qjC4Y1M+hSbVqVHo9Iz6M1vVfD6ufPcMGsG/b4IFo0CVyBKpTa9\n",
623 "Y9V03604/7CXGv34qcAEbllQzc4Tzrx/r4+oQCkc+ctf/nJK0PJE4LXXXmPDhg3ceuut3HHHHbz3\n",
624 "3nsTerwEcp5tVqxYwa5do/41u3btYuXK0Tu/uro6zp49SzgsnSwPHTpEU1NhIamXCkathUgsRCBc\n",
625 "Pg3SUGDqiNdBMmGcb9VzzD71dVZxezdCseHLYyAJ2Murs/rFETs/PlC+AryrgHzATLDaJCow04TR\n",
626 "mZ5j1FU0YdCWLrQdDxRyJSZZI+2eI2nP5dJYCYKAVa8iIBMKLqyiH/wPiqWrEFRqXAN+TGYN8iyC\n",
627 "/qzr1UQ469rPusW3FPW6ZDgLsFpIRr1JzSmZBcWSlUTe2Jn2vGtYX5WMq+dv4IOz7xAMl3aBD0eC\n",
628 "7D35JtcuuhkA48JZeE+dJx4p7karyxWi0Tw6waqSy6jQKeibAkHvtor8lgsXh1wMBIJcUVeHwyfl\n",
629 "VvYHIlQkaazikSihvn7qbl2P9/QFKkTK0rECmFOto1KrZH/H1O6mTwVoNBq+/e1vT6ivlSiKvPnm\n",
630 "m7zwwgu88sor/Pu//ztf+MIXRuqViUTO32r+/PlMnz6dxx9/nMcff5yamhqWLFnCCy+8gNvtxmAw\n",
631 "cOutt7J9+3a2bdtGV1cXGzdunPBFFwNBEKgy1pW1azUVXNfHYkmd/rKgA8USwpfHYiIE7G12H+93\n",
632 "ust2dy4FLxcuXE/AYFKDKGYMED52cfJoQIBYLI4Q1nK4+42Un4uiSJ8vu8YKwGpQ4oWCNVbRA3uQ\n",
633 "rxqmAUsQrgOcGvojtcoFJYvWoTh9FTAcaxNGufleIr/9JWIomPK8K0lflYBJV8H8xivYd2oXpWD/\n",
634 "6beYWbdwZKBBodehbajFd6a48OLOMVQgTB2dVV1FM70DuQXsr587z/oZ05EJglRYjZiDjr5/oV4H\n",
635 "amslCp2WyjXLUZ9pL4vGKoFNCyTrhY8w+RAEgWeeeQarVYrAq62txWKx0NFRuHVHqchbHdx6663c\n",
636 "euutKT/7zGc+M/L/1atXs3r16vKvrIywmutxuHtoss4uy/4kjdXUEiAutRn4wb7yTj9OBOL2LuRz\n",
637 "Fo1rH+XOCwxGYrS7QlTrlJx2+plfU/xFPBmxWJzeThe2puI7S4IgYB32sxrrf3Xs4j5uv/qvxrW2\n",
638 "8cA1EMBo0nJ84DSDXgcVw5mdnlAMmSCgz2HeWaNXMRgRCYeixKJx5Irs93Si30fsxDE0X3wMgMF+\n",
639 "f1EeViAVe+93vEZDfHzZZIVOBCZQZ5SE+tQvQDZ3EZE/vIrqk3eMPO861EbdLdenva5l0UZ2Hf5v\n",
640 "Pra4+Cia3cM0YDKMi+biPnYa44JZBe1DFEU6XcEUKhCgwSQVVqUp1MqHQgTsr587x0OrpRsPh1fy\n",
641 "Hjtu96WI1wOddjSNdQBUX7ca+/utOJbPKNs6182s4Af7uul2h6gf87e8HHHq1Cm2b9+O1yvdtF9z\n",
642 "zTU88sgjAGzcuJHNmzfz+uuv4/F4MJvN/OAHP8A8bF2xe/dunn766RG678EHH+Sxxx7j0KFDACxb\n",
643 "tozDhw9z4cIF/uIv/oINGzbw/vvv43A4uOeee/jSl74ESFruf/zHf+TgwYMoFAqMRiPbt2+nsbGR\n",
644 "aDTKQw89xGOPPUZ9fe4b9rfffhuVSsXMmTMn5G+VjKnVdpkgWE3lnQx0BaNTToA4z6rn4mCQQCSG\n",
645 "Nk/g72Qi3tuFsmXDuPYhWCoR+/vKtCI45fQzo0LD4joDBzrd4y6sHL0ezBVaNCX6itUM04Ez5o6G\n",
646 "jQfDAc7bTzCvYfm41jYeJIxBr6i+lv2n32LDFXcDkjN5TZ6uTs2w+7pWryLgD+c0TY0e2ot8/hIE\n",
647 "rfQ+DDp91NSbsm6fCWd6jhEVw6h945MmOH1hZlQWru1SyWVUaCX6rGbzpwn+01dRXr8JQaUiHo7g\n",
648 "bTuLaWl6OPLKWS089/unGPA6Cg6ZBxjwOjjTfYy/ve2bKT83LZmD59gpuPuTBe3HFYyikAlpDuU2\n",
649 "k5oez+RTgXUVzZzqzq5D6/Z46HB7WFVvA6TPZK1RjSsYpSLpXB3o7EXbIHX2qj++GvHZn9I3t6Zs\n",
650 "61QrZNwwp5LfnHDywFWFSx4+9fSlK11/9r/fL2g7r9fLY489xne/+11qa6W/2VNPPcXzzz/P/fff\n",
651 "j1wuZ8+ePbz44osIgsATTzzBc889x5YtW2hra+Phhx/mpZdeorm5mWg0yiOPPJKiVUz8XyaTcfz4\n",
652 "cbZs2cLWrVsJBAKsXbuWO++8k/r6erZv386aNWvYtm0bIBV7n//859m5cycKhYLvfOc7OX+P/fv3\n",
653 "89BDD2G32/n5z39+SRwJplZ1MEGwmm04yzQZGI7GicZEdJOcEzgWaoWMOdVaWvt8rGwo7iJ0KSHa\n",
654 "uwuKs3H0eBhweJm31Jb2nGCpJH7uZNnW1NrnZ2GtnlWNRp5/v5c/X5F+zGLQfbE0fVUC1joj50+l\n",
655 "0gknOg8ys24BGlVxAu5yYsDhpdJqoHnuen77/s9GCiu7N0yNMXdXx6pX0trno3rYyypnYXXg3ZFp\n",
656 "QIBBp595S4p7T948/Cs+sex2zvwmnrdDlgtOX4Sriuw8JhzLbTPmIJs2i+g7v0e5fhOetrNop9Vn\n",
657 "NO5UKTVcNfd63m39LbdcdV/Bx9pz/FWumrcetTL1c2FaNJdz3/nPgveTiQYEsJlUtPVNvnYzX6zN\n",
658 "G+fOc930aSjl0k2lwxdhVpUWvUqekgYQ7OxFO9yx0s1sQo+MSFRyX9cpy8NCbJxfzUO/PsVfrLCh\n",
659 "KvBzV2ixcymxd+9eTpw4wQMPPDDys2AwyBVXXDHy+HOf+9xIodLS0sIrr7wCwEsvvcQDDzxAc3Mz\n",
660 "AAqFgm3btvHWW29lPFZ9fT2bN28GQKvVsmLFCtrb26mvr+fll1/myJEjfP/73x/Zvq+vj2AwiEaT\n",
661 "P9Xiyiuv5N1336W1tZW//Mu/5MUXX5xwLfjUqg4mCNUmGw5XeWiyoWAU0xTJCRyLJXUGjk7heBsx\n",
662 "4EcM+BEsVXm3vXDGye7fncoo4i63xqrN7mNBjZ7FtQYuDAZwB8c3XSk5rhevr0pA6lilCmCPXdzH\n",
663 "4ubJ01fBaPjysulXc673BG6/RMf2ecPU5KHLRkxC83hZieEQsaMHUKy4evS4Tl9RVKAv6GH/qT+w\n",
664 "bslmdHoVviIsHsbCkSeAORPqTWq6XdIxVbd+hvCvf4YYjUrC9TH6qmRIZqGFTweKosjuYzv52KJ0\n",
665 "+tC4eA6e45m/P5nQ5U4VridQP0U0VrWWRvpc3cSz+NclbBYScHjDKGWylG4VSB2rBBUoCALW61ZT\n",
666 "ERdxlEnADtBgVjOnWsvu80Nl2+dkQBAEli5dyo4dO0b+/f73v+cb3/jGyDbJdkxqtXqE9gsGg2mf\n",
667 "vVyfxeT9gCRuT54Y/MlPfpKyjn379hVUVCVj4cKFrF+/nj/84Q9Fva4UfCgKK6u5Hqe7PDECU81q\n",
668 "IRlT3c8qbh+2WihgEsTjCuIaDGDvTp+wkVmqiA+Wp7ASRZHWPqmwUilkLKkz8EF3/ry+XOhuHyza\n",
669 "GDQZlVY97qEAkfDoRUTyr7r0xqDJSBRWKqWGZTPWcOD0WwD0eSM5hesgBTFLXla53ddjR99HPmMu\n",
670 "glHqEoVDUULBKMYC8xYB9rS+xtIZazDrK9PCmIuF0xcuSrwOUjGSoM/kcxYiq60n+u6bknB9zERg\n",
671 "MuY3XYE/5OFiX2HmnufsbYRjYeY3ptPDamslMrWaYGf+UGiQOlaZNEEJKrCceYalQK3UYtJaMp7H\n",
672 "HX4/pwcGuKZp1HS4zxdBEMhoDproWIFEBxpdvrJZLiRQqBP7VMaaNWs4ffo0b7whDavE43GefPJJ\n",
673 "Dh8+nPe1n/70p/nhD39IZ2cnAJFIhK9//eslfY7uvvtutm7dSjQq3fDu27ePp59+Ou/rDhw4wL33\n",
674 "3jsyBeh2u3nnnXcKMjEfLz4UhVW1uXwdq6lmDpqMhbV6TjsDhKMT6w1SKkR7F0JtYboDrzuIpUrH\n",
675 "qaPpJ1LBUr6OVY8njFImjFgFrGo0cWAc49IeV5BIOIalqvictgTkChmV1XqcdqnA8wSGsA92Mss2\n",
676 "PtH/eDHo9FE5PJ23et4n2HtKCmUuhAqs0StH3NdzdZCi+0enAUESrluqtAgF+oGJojjstC4JxsdT\n",
677 "WIWicQKReJruKB/qTaldHtVtnyG846e4DqVPBCZDJsi4duFNBXetpG7Vxqzd82KMQrtdIRozUIF6\n",
678 "lRyNQsZAYPI98uqyWC7sOneBluZmVMM0YDgaxx+OEY7F08xBxxZWVdeuRN/bj32wvN2l1U1mHL4w\n",
679 "Z5zlLdguJXQ6Hb/4xS/48Y9/zMaNG7nxxhuJxWIsWbIk4/aCIIx8FhctWsQzzzzDF7/4RTZt2sSm\n",
680 "TZtYu3ZtRo3V2P+PxdatW2lqauKmm25i48aNfOtb3+Kuu+4CpILtwQcfpLs7/fq+atUqrrnmGjZt\n",
681 "2sStt97Kpz71Kb7yla+wfPnE61SnZoVQZlj0VfhDXsKRICplce3DsZhKAcxjoVXKmVah4YTDx1Jb\n",
682 "cY7flwJxezeyAj2sPK4QK6+ZxoE9F2i5cW7qF09ngGhEcnBXj+/9bOvzsaBWzwd/vEgoFGXVikZ+\n",
683 "eqgXURRLonsTNOB4qWKrzYSj14OtycLx9gPMb1w+4bmaueD3hYnHRXTDBegVM9fy/d9uxxt0F0QF\n",
684 "apRy1AoZMrUiKxUoRqNED72H7p7Pjvxs0Omjsojw5TM9x4ad1iUTVYNRjbfEwsrpi1ClVxZt6ChZ\n",
685 "LoweUzZ/KRhM6HzH807ptSzayP/9+Rf49LovIZNlH0KJxiL8se13PHnf81m3SRiF1t70sbxrqSFJ\n",
686 "1wAAIABJREFUzjQRmEC9SUWPO5RWpFxq2Cqa6RnsYOmMq1N+/vq589y9aLQT6PBFqNQpGQxER3IP\n",
687 "QSq6g129aBpHczYVRj3VGg0XWk/DouzdxGIhlwlsnC91rR5qaS7bfi81ZsyYwQsvvJDxuR07dqQ8\n",
688 "Xrt2LWvXrh153NLSQktLy8hjp9PJ9773vZHHBw8eBKC5uZk9e/ak7CtZkK5QKHj00Ud59NFH09ag\n",
689 "VCpzite/9KUvjUwXXkp8KDpWMkFGlam2LNE2UymAOROW1hk4OkUDmYsJX/a6g8ycX4MgCGl0oCAI\n",
690 "kuVCGdzXW+0+Ftbo6e1y0X62nwazGo1SxrmBYP4XZ0DXxfHRgAlIDuxSx0ryr5oaNGCiYNSodCye\n",
691 "diXvn3k7Z05gMmoMKiIKeVYqMNZ2GFltA7LK0am4Yj2s3jz8qxSn9fF0rJz+4mlAkAqRXneI+DDt\n",
692 "IQgC4YVraZ6lRshjctpQNYMKfTXH2w/k3O6Ds3torJ5JjSX7jYppcWGZgXFRpNudWbwOU8nLqimt\n",
693 "YzUUDHKkr4+W5lExssMXpsaglDyskorBcP8gcq0mbXigoaGOzvOFZREWg0/Oq2L3+SF84amfa1pu\n",
694 "tLa2sm3bthH6Lh6P88wzz3DTTeOzP7lc8KEorACspvLorIamMBUIsMQ2dQXshbqux+MiPm8Ig1HN\n",
695 "3CV1melAc0VZ3Ndb+3wsrNUz6PRj73IhxkWJDuwsjQ6UOlbjL6ysdUYcwwJ2KXh5coXryTRgAlfN\n",
696 "Xc97J97EG4qluFtnQ41eRVAQsnasogf2pEwDSsf1F1xY+YIe9p3aleK0Pq7CqkgPqwS0Sjk6lZwB\n",
697 "/2gg75AziqDTEXv/j3lf37JoI+8cfzXnNtlE68kwLSks2sbpi6BXy7PatDSYp0hhVdmc5mX1hwsX\n",
698 "WdNQnzLR5/BJHlYDgSgVSYVVsMOeQgMm0Dx/Nj3O8hoOg6TvWtlo5PXT5d/3VMfs2bNRqVRs2LCB\n",
699 "TZs2ccMNN2AwGNiyZctkL+2S4ENTWJVLZzWVqUCARbV62hw+opOc75UJhbqu+70htDoVcoWMeYtr\n",
700 "OXm0N030KJQhLzAQidHpCjGrSsug04dMJmPA6Su5sIqEYzjtXmobxh85kzAJdbh68Ic8ZTO3LRWS\n",
701 "1UJqgbNiVgutHe9TpY0WRJcl3NcDGTRWYjxG7P0/oli1NuXng/2FTwTuaX2NZTOuTnFaHw8VWMpE\n",
702 "YAKSzmq0gHQdPkF0+ccJv/JCXgHv1Qs28P6ZtwmGAxmfd/sHae04wOp563PuR9tcT9TtJTzgyrnd\n",
703 "2CibsbAZp4aXVaZYmzfGTAOCNBFo1SvTXNeTJwKTMW3+LAYVMgKd5RlwSsYtwyL2yRb/X2qoVCq2\n",
704 "bt3Krl272LlzJ2+++SaPP/74hEbYTCV8OH5LoNpUVxYvK1dganesjGoFNqOa01NMNCkGA4g+L0JF\n",
705 "dd5tPa6gFO0C1NSbMtOBlspxC9hPOvzMqtQSC0WJx0Wmzamit9PFcpuBU04//iJb+L1dLqprDSjL\n",
706 "YNCq1alQa5R8cPwAi5pXlRwiXC5IVKAh5Wd6jZEG6xIM4rGC9lGjVzEUzxxrEz/ThmA0I6sbnewS\n",
707 "RZEBR2FU4FjR+sgax9WxKo0KhHQBu+tgK/qbNkIsRuzwvpyvteirmNOwlANn3sr4/Lutv2XFrBZ0\n",
708 "akPG5xMQZDKMi2bjOZ6bDuxyh2jM4RJuM6mmRMeq1tJIv9tONCZ1An3hMPu6ulk3fVrKdlJOoBRn\n",
709 "U6UbYw6aobCqMRjwVVtwvrW37GteUmdAAI5M4Wntj1B+fGgKK6u5vizu61PZbiGBpTYDR6YYHRi3\n",
710 "dyOrrS/IasHrDo2M1wuCkJEOlJkriY+TCmwbtlkYGo5MsTWa6el0oVHKWWDVc6inONuF7vYhGsbh\n",
711 "XzUWNTYjJ0+fLYt/VSgoFY+lYsDho8KaXuDYatYS9+fWAyVQY1DhjMTxe9PH96P7302jAQPDVJq2\n",
712 "ANH0WNF6AuOmAnXFU4EgFVY9w8cNOweJuDzoZ01DeetnCL+cv2uVy9Pq7eM7C46+SUTb5EKnK0h9\n",
713 "Fn0VpBeJkwWFXEmFwTpyHn/7YjsrbHWY1Klrd/giVOvSNVbBrsyFlVWnY0irmpDCShAENi6o4rUT\n",
714 "/WXf90eYupiUwmoy2qJWk6084vXLoLCain5WktVCYcJ1jyuIIYmayEQHCpbxa6xa+3wsqNVJOp4q\n",
715 "PXWNZno7JdpkVZORA51FFlYXB8uir0rAWmekt3uoLML1X//0ECcOl/b5j0XjuF1BLJXplJzWtAqv\n",
716 "63BW2ioZVoOSvkAUQSakeHSJopgSupxAQrheyITlG4deShGtJ6A3qPF7wyUVlQ5fuGQq0GZUjZiE\n",
717 "ug61YV42H0EmQ3HVtYg+L7HWQzlfv2rOOs50H2PIm+qF1O44jds3wOLmwj4TheisJHPQ7IWVRaMg\n",
718 "GhfxhqaC5UITPYNSuPTr586zYVZ67pvDG8aoUSCTCSm6sUBnL5qG2rTtTWoVMUGg+71DxKPl/x2v\n",
719 "C10k+MF7BCMfPhH7hxWTUliFHZdezGc123B+SDpWS+r0HLf7iE0hnZXUsSrQasEdxJhETWSiAwVL\n",
720 "1bg0VqIocqLPz8IavaTjqdJRW2/CafcQi8ZZ1Whif4e74JsAURSljlUZJgITUBiDaKK11Fga82+c\n",
721 "A/G4SHf7IB3nS/veDQ34MZo1KDLEcwyGNFgr53P4fH5RtlUv5QXqDKleVvGLZ0EmQ9aUqpUpJHy5\n",
722 "03mOp3/5EG2dh/j4ks1pz8sVMtRaJYEcbu/Z4BwWQZeCRKwNDBdWw8aggkyOavO9RF7OPMKegFqp\n",
723 "ZdWcj/Nu2+9Sfr772E4+tnhjTiuGZJgW5Z8M7HKFslotgNR1sRnVdE8FnVXlNHoHOwhGo7zb0cl1\n",
724 "Y2hAkDpWcgEqtLk9rBIQBIEag4HQzEZcB9vKvmbNsX3cNHCYP17MrXX7CH86mJTCynem/KOt+VBh\n",
725 "sOIODI7w86UgEosTjMQwqKduyDGARaukSqfk3ED+LsKlQry3C1ld4VYLyR2rTHSgNBVYenu92x1C\n",
726 "JReo1qsYdPqxVOtQqhRYqnQ4ej1Ms2iIiyIdrsIokMF+P0qVImcGXrHoi5xEH68ftyfWgMNLPCbS\n",
727 "dbG0QjRhtZAJfd4wS2asY9+pXXn3U6VT4gpG0epT3dejB/agWHVt2u+Zy2ph0OvgB797kq//7K9Z\n",
728 "1LyKb/3li5h0mWnYUgTskVgcTyhWcth6QrwuiiKug6lRNoqrryPutBM7mVub1rJoI++0jk4HxuJR\n",
729 "9rT+tmAaEMAwbwb+9i5igcy/fywuYveGseUorKTfR/KymmwkLBfebe9gkbWaSm1qRmIgEiMSiw+b\n",
730 "g6a+d8EshRVIdCBrlk0IHRh39DIjYGfX2fHbw3yEywOTU1iduzSFVXK3QS5TUGGw0j8OywXXcE5g\n",
731 "sYaBk4EldfopRQfG7d2Fu667QmkRJmPpQMFciThU+omqdbhbBdLkWcJKoK7BTG+XC0EQipoO7L5Y\n",
732 "HpuFZJxyvocsriLgH1+noKfDxeyFtfg8oZw5fdkwkMFqIYE+b5hr5l3PwXN7CEdzX3jlMoFKnQKF\n",
733 "Rpmyjtj+dJsFyGwOGgz7eendZ3nkR3+GVqXjn/7Xr9h45Z+jVGTvLJWis+r3R6jUKZAX6Pg+Fka1\n",
734 "HAFwB6MpHSsAQaFAdcunCL/yXzn3sbB5JR7/IB3OswAcOf8e1aY66iunF7wOmUqJfmYz3hPnMj7f\n",
735 "6wlTqVWiyuOvZZsiOquESejvM0wDAji8kkWGZA462rGKen3EQxGUVZm/o1a9jsiSOTj/UP7CSnTa\n",
736 "0fV3c6LHxVCg9Bv7j3D5YJIKq44JP0Y8LvLsN97C4xo1ehyvzupyoAETWGqbWjoryWqhUNf11I4V\n",
737 "pNOBgtmC6B5CjJcW35NwXBdFUepYDUfQ2Jos9HQM66yKKazGmQ84FvF4jLbOD6iuNeAc5/vY0zFE\n",
738 "wzQLtiYL3SV0rbJ1rOKiiNMXYaa1jmk1czl6If9FqUavQlSNmoTGu9sR/V5kM+elbSt5WEnvSywe\n",
739 "5c1Dv+IrP7idnoF2nrr/P/jz676CQWPKe8xSCqtSPawSEASBepOajpMdCAo5aps15XlFyw3EOy8Q\n",
740 "O5dd/yQTZKxd8MkRT6vdx3eyrohuVQK5om263MGc+qoEpoqAva6ime7+dnZfbGf9jAyFlS+M1aBk\n",
741 "wB9NEa5LVgu1Wbu/NXo9gXor3lPnCQ+WHmk1FqIocnFISbtlITeYApd9MHO5EAwG+fKXv5wStDyR\n",
742 "cLlcLFq0iL/7u7+7JMf7k6UCfZ4QXneIQ++NHstqto1rMnAoGC2ZGrjUWFwnGYXGp4B/ihgMIHo9\n",
743 "CJX5rRZEUcQ7RmMF6XSgoFSBVgve0k6CCcf1gC8siVyHp7/qGkz0dkmF1RX1Bo7bfYQKyF7sah+i\n",
744 "voyF1Xn7CSoMVmyNVfT1jO9E390xhK3JQsP0ipLowExWCwCD/igGtRyVQsZVc69n33B2YC5YDSqi\n",
745 "CtlIxyp64F0Uq9amTYuKcXE4J1DHB2fe4e9+fC/vtv2Wh+/8Z750y5M5HcfHohQq0OmLYB1nhIvN\n",
746 "pKJn3zHMyxekXdAFpQrlpnsIv5Jba9Wy6GbebX0Nb8DF4fP/w9XzNxS9jkS0TSZ0ZckIHIv6KeJl\n",
747 "ZTXb6AiqmW4xUWtIL/ZHzEHHTgR2ZjYHHdmvTocjFKJy9XL639lfvgX7PJzRzuVc1XI+pnKx68xH\n",
748 "dCCARqPh29/+9iXztfr7v/971q/P7ftWTkxKYeW/BFSgxxVEb1RzZH/HyARStcmGw126SahrisfZ\n",
749 "JMOqV6FXyWkfKi2apZyI9/Ugq7EVZLUQDESQK2QoVel/57F0oMxcSbwEAbs/HKPLPWwMOkYgXV1n\n",
750 "xDUQIByKYlArmFWpzWtdEQxEcA8GsNaVL5/x6MV9LJ52Jda60WibUhAORRnql9bWOK2CzgvF3TFL\n",
751 "XlLp5qAwHL48HGVz1dzref/MO3k1jDV6JUFBhn9YvD42dDkBrydEWNPLN/77QV54+1/59Lov89VP\n",
752 "PcusuuwhxtlQSseqzxceV8cKoMGkxnM4lQZMhvLjNxE/e4JYe2aaDqDJOhuj1sIPX/9Hlkxfg0Fb\n",
753 "vPlsrmibTlf2KJtk2KaIxkouUxDUzGd1XWY9nWPYe2wgEEnvWGWYCEygRq/D4fNRfd1VZaUD4w47\n",
754 "Tk0dHqWF6X47Xe5QQTdqH6F82L17N4IgcPXVV+ffuEyYlMIq0NE7IWOtyfC4gtQ3WahvttB6SCqm\n",
755 "rOZ6nK7xaaymsjnoWEwVPyuxt3CrBa8rhDGLC3QaHWipLMly4ZTTz6wqLUq5TBJIJ+l45HIZ1jrj\n",
756 "yDEKoQN7OoaoazQjy6NTKQaJGJsa22i0TSno7XRRYzMiV8ioazTj6PWkWB3kQ8AXRhCEjF5Sfb7R\n",
757 "wqrKWIutsjlvxp3VoMLLcKizs4+4oxf5/KUp2zhc3Xzv1a/Sqvgx1yzYwNOf/RkrZreULOLXm9T4\n",
758 "iiwKnONwXU/AZlITbzuF+YrMhZWgUqO86U4iO36acz8ti27mf078nnV5ImyywbhoNp62s4ix9Pe9\n",
759 "yx2ioYCBC6texVAwOulFQTQex4mNucbMBXyfN2EOmhrAnG0iMAGrTofD76f646txvrW3bJZAEXsv\n",
760 "gxhxR9XQdYGPzbAwFJh824pCcerUKe677z5uv/12br/9dr75zW+OPLdx40aeffZZ7rrrLm688Ubu\n",
761 "ueceXK7Rycfdu3ezadMmbr75Zm6++WZeffVVli9fPvL8smXLALhw4QLr1q3jySef5I477qClpYV/\n",
762 "+7d/G9kuFArxta99jdtuu4277rqLz372s3R2dgIQjUZ58MEH6e7O3DAJBAI89dRTfO1rX7ukNk+T\n",
763 "UiWoa6oItPegn9mUf+MS4XEFMFo0zF5Qwxs7Wlm6qnH8HavLSGMFkp/V/k43mxda8288gSjaaiFL\n",
764 "YZVMB9Y1mIcF7MUXVgkaEFJ1PAnUNZro6XDRNKOSK5tM/OMfLuTcX/fF8toshKMhznQfY+FtK1AK\n",
765 "OgacPmLROPIMdgf50N0xSlEqVXKsdQZ6u6TfrRD0jwlfTkafJzV8+aq569l78g2Wzch+Z1ijV/FB\n",
766 "HPzesDQNuOJqBLk0ZesNunn5f37EW0d3sKz2Ru6a8Q0+sXz8Hl6lUYFhFtcWHv6cCTa9gsi585iW\n",
767 "ZS6sAJTrb8H/t/cT7+lAZst8Ply74JMcOvdHls5YU9I6lCYDKmslvnOdGOak2hN0FdixkssEag0q\n",
768 "ej0hplVo824/UfigpxezSiAWzHyD7PBFaMlIBfZi/cQ1Wfdr1evp8/nRzWpGppDjPXUe47x0j6xi\n",
769 "4Wx3YFYpcIfkRIbauf7TFTjs6Wv3/vkN4z5WoTD85+sFbef1ennsscf47ne/S22t1O176qmneP75\n",
770 "57n//vuRy+Xs2bOHF198EUEQeOKJJ3juuefYsmULbW1tPPzww7z00ks0NzcTjUZ55JFHUs4jif/L\n",
771 "ZDKOHz/Oli1b2Lp1K4FAgLVr13LnnXdSX1/P9u3bWbNmDdu2bQOkYu/zn/88O3fuRKFQ8J3vfCfr\n",
772 "7/AP//APPPDAA5jN5nFPVxeDSakS9LOb8Z1rn+DCSrpAN82sRC6XceFMP1Zr3bi8rIaCUWZVTt5J\n",
773 "pVgsqTPwowPdiKJ4ST9UYxG3dyGfMbegbaU4m+x30PMW17Ljvw7RcuNcySS0hFibtj4fG+ZWAdJE\n",
774 "4NxFqXeytkYLZ070ATCrSosnFKPXE6LOmPkC1NU+yKpr04W0peJU1xEaq2eiU0vUosmiZcDhw2or\n",
775 "nmrsaR9i4RWjRW3DtAq6LgwWXFjltFrwhVP0OavnXs/j//kX/FX8UeSyzKcWq0GFMxofLqzeRbXx\n",
776 "biLRML8/+CKv7P0xq2av4+m//DlHdvejM4yPikugFCrQMU7xOkBln502gwlVRXaBvaDRotxwG+FX\n",
777 "form8/874zYWQzVb/+x741pLwig0ubAKR+MMBCIpxXEu2IxqetzhSS2sXj93jlU1hrQw5gQcvjA1\n",
778 "2ajAHB2rGr2OPp8PQRCo+vhqnH/YW5bCqq/XS62lEjGqwW2PsMAkJ0NdVXCxcymxd+9eTpw4wQMP\n",
779 "PDDys2AwyBVXXDHy+HOf+9zItaWlpYVXXnkFgJdeeokHHniA5uZmABQKBdu2beOtt97KeKz6+no2\n",
780 "b5Z86LRaLStWrKC9vZ36+npefvlljhw5wve///2R7fv6+ggGg2g02a8Vhw8f5sSJE3z9618HLq0x\n",
781 "+aQUVrqZzfjPdsAnJu4YniGJChQEgZVrp/H+uxe47f5lDPqcxOLRrCf+XJjqAcxjUWdUIRcEut0h\n",
782 "GnKErE40RHsXwpqPF7StN0fHClLpwEpzZdFeVqIo0tbn46FrpS/8YL/kYZWMukYze96QNCkyQWBV\n",
783 "o+TCvmlBemEVj8Xp6XBhaxp/8HICCRowAavNSF+vu+jCShRFejpdrN88qktqmF7BkX2FT+UO5rBa\n",
784 "sHvDrGgYXVONpYEqYy0nOg+yKIszeI1BiT0Uw+8JEu84y37FAD/90d00VE7nq596lqbqWQAMONtp\n",
785 "mF6eeCC9UY3PGyrqBqMcVCAnT2NvmIY/HEOnyu59p7zhVnxb7h/RIk4ERoxCbx/tjHR7QtQZVAVb\n",
786 "StSbVCOmp5OBuCjy+rnz/J+V09h39N2050VRxOGNYNYoCEbimNSpruu5qECjSkU0HscXiVD98dV0\n",
787 "/MfLzPj8veNes2MoRs1cPQG/Dq91JmJPOxXa/B3CqQBBEFi6dCn/9V/ZbUGMxtHvv1qtHpnyCwaD\n",
788 "aYVMrsImeT8giduTJwZ/8pOfUFFR3PngyJEjXLx4kXXr1gEwODhIMBjk+PHj7Ny5s6h9FYtJ0Vjp\n",
789 "ZzXhOzuxAna3K4jRIl2g5y+10dfjZqg/hElbwYDHUdI+p3oA81gIgjAcb+Ob1HXEe6WcwEKQHMCc\n",
790 "Ccl0YCkaqy53CI1SRpVeiSiKUk7gGK+kiiodoUBkZHItl87KYfdiMmtGpgrLgbGFVY3NhKMEAbt7\n",
791 "MIBMJqQUqg3TKuhuHyo44qU/V8dqDBUIsHreJ9h7Mvt0oEElJyyXEQxEOG6Ks+ODF/jrGx/n7+76\n",
792 "15GiCnKbgxYLpVKOQiEdsxBE4yKuYOq4filwHWolMHvWSGZgNgh6A8r1txD+9c/HdbxcMGaItimU\n",
793 "Bkxgsr2sjtr7MKrUrGyaOxJrkwxvOIZMgHBMxKJVjBTR8XCEcP8Q6rrsU8mCIFCj1+P0+alqWcXQ\n",
794 "/mNZTVWLgSOgomZaNZZKHW5LM/GOC1g04yzYLxHWrFnD6dOneeONNwCIx+M8+eSTHD58OO9rP/3p\n",
795 "T/PDH/5wRAsViUT4+te/XlLX6O6772br1q1Eh3XZ+/bt4+mnn877uvvuu4+9e/fy9ttv8/bbb/Po\n",
796 "o49y++23T3hRBZNVWM1swnd2Yr2sJCpQalkrlHKWr27m4B8vSgL2Er2sLoecwLGY7NxAMRRE9LoQ\n",
797 "qgrTeXndualAGJ0OxFx8XmCrXQpelo4VQqmSox7zngoygdqG0dzAFQ1GDnV7iMTShbtSPmD5gpf9\n",
798 "IQ+dznPMbRgVdJc6GdjdMYSt0ZLSpdHpVegNavrthX0mBrNYLQD0ZYh8WT33evaf+gNxMbPIuWfw\n",
799 "Igbv91DEAyhW3cCT9/9HWhZiPBbHPZQ5m7BU6I2FC9gH/FLXQ1GiOWgCrkNtyBfOpdud36ZA9ck7\n",
800 "iO7bTXygtJu+fEh0rJIvbFKUTeGd7HqTelInAxOmoFWmWnxBT1o+5YjVwhgaMNjTh7q2Cpki97nb\n",
801 "qpcE7EqTAeOi2Qy+lzvPMR9isTj9opHaOU2YK3V4tFbinRfQKCflsls0dDodv/jFL/jxj3/Mxo0b\n",
802 "ufHGG4nFYixZsiTj9oIgjJxrFi1axDPPPMMXv/hFNm3axKZNm1i7dm1GjdXY/4/F1q1baWpq4qab\n",
803 "bmLjxo1861vf4q677gKkgi2XeD3TGi8FJkdjNWvahLqvx6JxAv4w+iRNzLKrmvjRP79D1eJGHK5u\n",
804 "FjStKHq/rmAUi/byuNtIYInNwM8O2yft+PG+HgRrHUKB2WYed/apwAQSdKAjrMNcpMaqrW9UuC51\n",
805 "qzJfvBOBzDPnWbFolTRZNBy3+1hen9qy7m4fYtrsqqLWkAutHR8wu34xKkVSVuLwZGCxWrmeDhe2\n",
806 "5nSKsmF6BZ0XB/NSi9FoHI87iDmDrtAXjhEXRYxj4p1sldMwaC2c7jrCvMbRCaAhXz+/fPf7vHfy\n",
807 "DWp1GzDEAzRd/am00GQA11AAg1GdMZuwVBhMkoC9ugBLjHLQgPFQGO/J85gfm1tQl0cwmlGu+ySR\n",
808 "nb9Aff8Xx3XsTEh0a0K9TjTDZqWd7hDz8mQxJqPeqC6oSJwIiKLI6+fO8a+f3IBMkFFjacA+1MG0\n",
809 "mlHtpsObMAeNpLiu56MBE7DqJJ0VMDIdWH3d6pLXPNTpQCMG0VZbsFQG6RIMxDvPl7y/ycCMGTN4\n",
810 "4YXMXms7duxIebx27VrWrl078rilpYWWlpaRx06nk+99b1QrePDgQQCam5vZs2dPyr6SBekKhYJH\n",
811 "H32URx99NG0NSqUyp3g9Gffeey/33jt+ercQTErprGmoITLoIurzT8j+ve4geoMaWdIdp96oZvbC\n",
812 "WjSuuSW5r0fjIr5wLO1CMtXRZFYTisaxT5K5XzGO6wBeV26NFYzSgae7IkV3rNr6RjtWg/3Z6SZb\n",
813 "o5meztHR4Wx0oGQMWr6O1VgaEKTPLoKAt8huQXe7ZAw6Fg3TLAUZhQ71+zBbtMgz2EjYvWFq9KqM\n",
814 "hd7qeevZO2wWGooE+NUfn+ORH92DUqHin/7XL7lOmIFCKSMQyXz6yTSpOV4UI2B3DnshjQfu1jPo\n",
815 "ZzZjs5oK7vIob76LyLtvEi9h0jUfBEGQjEKPj9KBxVKBdUYVfb7wpIS7n3D2IyAwv0q6ibFVNKUJ\n",
816 "2BMdq0QcUQK5MgKTUTM8GQhQfd3qcftZ2U93Uy2XOsPmSh2usJx4x+VVWJWK1tZWtm3bNkLfxeNx\n",
817 "nnnmGW666aZJXtmlwaQUVoJMhm56I/5znROyf3eWi/PKa6YR6Kygb7B4Lyt3MIpRfXnkBCZDEASW\n",
818 "2AwcK5D6KTek8OXCCqtwKEosFk+j5jJh3uJaTp0YQAyHEcOFXbh84Rjd7jCzqqQOzKAzf8cqQZ1I\n",
819 "AvbUwsrrDhIJRctaBBy7uD+NGhMEoWg/q2gkhtPuoa4hfSItMRmYDwMOHxU5wpdrskyTrZ57PXtP\n",
820 "7uIPR17hKz+4gw7nGf7vn/8/7r9+C0athXkdB4ka9Ph9md+3cuqrEjAUUViVYyLQdagN8xULsJlU\n",
821 "dBVYWMkslSivuZ7Ia78c17GzwbR4Lp6jo0ahXa5gUYWVSiHDolHgKCFvcrx4/dx5PjFzxkghX1fR\n",
822 "TM/A2MJq2Bx0jNVCoCP3RGACCS8rAPPSeYScAwS6Su/293UMYNVJ3mGWSi0ud4R4MIg4wR6OUwGz\n",
823 "Z89GpVKxYcMGNm3axA033IDBYGDLli2TvbRLgkkjexOWCxMBT5JwPRk19SYMFiWDHcUXR5ebOWgy\n",
824 "ltRNnlGo5GFVoDmoWwpfLoTuStCB/RUzEQt0Xz/p8I0Yg4LUsbJkuYAbTFLH0z3sXD/fqsfhi9Dv\n",
825 "GxVAd7cPYWu2lI23H/I6GfT0MbM23feoxmakr7dwnVVfj4dKqyGjg72lSkcsFsc9FMjwylHktFrw\n",
826 "hqk1Zi4+GqtnoVXrefvYDr5y29P8f5v/kdoKyVpFDAWpaT/OkLFyJC9wLMaatpYD+iK8rMpBBboO\n",
827 "tmJevoCGIgXfyk33EHnrNURP+fLqEjAlCdj94Ri+SJyqIgX6k5UZ+Pq5c2xICl22VTTTO7ZjNWwO\n",
828 "OhhIzwksrGMlua8DCHI5VS1X0v/2vpLX3OcIUFMlrUOtUSKXywg3zoHw5KdhTDRUKhVbt25l165d\n",
829 "7Ny5kzfffJPHH3/8kkXYTDYmr7Ca2TxhmYEeVxBTFjppyeo6xN6moqcTLjerhWRMpoDc+ViQAAAg\n",
830 "AElEQVRdtHcjjCN8ORsSdOBZ/fyC6cC2Pv+Ivgpyd6wEQRjpWoFkkHhFvZEDXaMXvO728hqDHmvf\n",
831 "z4Kmlcgy6NGsNlNRAvaejiHqs1hACIJQUNdqIIfVQp9X8gvKtv+n7nuebfc+x5z6VKFr7OgBwk1z\n",
832 "cCtU2Qurfn/ZO1aXmgp0HZKibBKO5eECHctlVTUormoh/Lv/HtfxM8G4aC7u4WgbyXFdVXQH3maS\n",
833 "vKwuJc4ODuINR1hSWzPys7qKZnoHUwegJCowXWMV7MqdE5iANYkKBMYVbyOKIg4PWOtHv4OWSi0e\n",
834 "6yzE0J9+YfVhx+QVVrOa8J2bmMnA5InAsVi6bDZElUWH0V6OE4EJzKjU4ApG6fcXNm5eTsR7uwq3\n",
835 "WnAHMRYxpTRvcS1naSReoJdVq93HwmE3bTEu4hrIXlhBgg4czdZb1WjiQMdoYdV1caisE4GZaMAE\n",
836 "auqKowK7O4aoy6CvSqBhWv5A5lwdK3sOKhBApczceYzu34Owci1DMXHEzmIsJCqwvBorg1FTOBXo\n",
837 "Hx8VGPX4CHbaMcybiVwmUKNX0VuExlF1y6eIvLED0V9emxT9zEbCjgEibu+wvqp4b7t646X3snpj\n",
838 "mAZMLgLrKpvTLBckKjDhup4aZ6NpKI4KBKhet5r+d/ZnjALKB58nhBgXMTWMFoPmSh0eUwMEPyqs\n",
839 "/tQxaYWVblYz/gnysvIMBbIKoDVqLS7jId7bnTmUNBsuNw+rZMgEgUW1eo5d4q6VGA4heoYQqmvy\n",
840 "b8yw1UIRJ/uaehOCTIa9I3+wcFwUOeEYFa67XUE0OmVGqiwBW6OZ3iRd1ZWNJj7o9hCLi0QjMRy9\n",
841 "HuoaymMMKooixzMI1xOosOrxuIJEwoXpM3raXdTnKqyGJwNzrScfFZirsMq4z2iE6KF9mK5uYSgm\n",
842 "4vOmX6AjkRg+bxiTpbzu3sVRgePrWLmOnMC4aDYypfTZqjepiypGZDU2FMuvIvL6KyWvIRMEuRzj\n",
843 "gll4jp8Z7lgVb1Q5GV5Wvz8r2Swko0JfTSgSxB+SuriiKEodK4OK/iS7BTEeJ9jdhzZHAHMCCff1\n",
844 "BDQ2K+o6K65DbUWvua/bTXV8AJl1tKAzV+rwqCo/6lh9CDC5VODZ9gmxmc+msUpAVdtP1/khXIO5\n",
845 "NSbJuJw7VjA5dGDc3o1QXbjVgtcVxFjEyV4QBGZXRzjdkb8b0OkKoVPKR064QzkmAhOobTBh73aN\n",
846 "mGlW6ZVY9UpOOvzYu91U1+hR5nDULgZ9Q51EYxEaqjJH48jlMqqsBhwFvIded5BwKJqzG1djM+Ia\n",
847 "CGQ1zfR5QsjlQlbj0z5vpOjCKtZ6GFl9E+pqK3KtAk+GC/RQvx9LhTZlorccKJQKjMVFKcB3HOag\n",
848 "roOtmK8YdbuvN6mKLkZUm+8l8rv/RgwWfo4qBAmj0E5XMCWOqFBMhJfVhdNOdv48s+lkh8uN3edl\n",
849 "pS214yQIAraKZnqG6UBXMIpGIUMpE3AFolQMU4EhxwAKgx65Lv8Nm1GlIiaK+CKj34lSpwPt3W6q\n",
850 "/V3IqkcLOkulFndcCx8VVn/ymLTCSlVpRlDICTuLo+QKgXsou8YKwFpppXqmyKH30t17s+FyC2Ae\n",
851 "i6W28QvYs5k+ZoNYhHAditNYJTC3Sc3pfmXeAr2tb5QGhGEdT47CA0CrU6HTqxlwjP7dErYLE0ED\n",
852 "Lpp2ZU4hvNVmpK8AOrCnU4rYEXIUJ3K5DFuTme72zN2+Qac/qzFoJBbHHYwWLXyO7t+DYtW1AJhN\n",
853 "GrwZNFYTMREIoFLLEUVp8jQXhoJRDCo5qgwWE4Uioa9KoJRiRFbfjHzBMiK7yusSnTAKLdZqIQFJ\n",
854 "vB4u6w3xgT3nOXm0NyM1/Mb586yfMQN5BtFzXUUTvcOTgQmzWlcwikE9au4a7OxF05S/WwVSsWbV\n",
855 "6XAm66yG/ayKRV/HANWxAQT96HfIXKHD5Y3BZTZZ/hGKx6RK9PWzmssebRMJx4hEYmhzaCSqTTZU\n",
856 "9X0cPdCV90SbgCsQxXKZitcBZlfp6POGcQdLG/V95/ir/PPLmUNisyFu7y7YagFGpwKLQU2jBSEe\n",
857 "w96du+BoS3JcB+kCbilg8qyuKZUOXNVoYn+nm+72QerLKlzPTgMmUGi0TU8W/6qxyKWz6nd4s9KA\n",
858 "Tp9EtRSaMQcgxmPE3n8XxSrJQLDSrCXkz1BY9Zffwwqki6ahADrQ6ZNMJscD18G2lI5VqfSZ8tZP\n",
859 "E3n1pYLtRAqBafFc3MdOlUwF6lVyVHKBoRLPI2Mx6PTR1+Nhxpxqzg0HnycjYbOQCXUVzSNeVglz\n",
860 "0MFAhEptqr6qEOF6AtYxdGDF6mV42s4RGSpuSrOv2411jNOJpUrH0IAf1JOX2/oRLg0mvbDyl1nA\n",
861 "7nEF847sW831uKKdNM2s5PgHXQXt93KnAuUygQU1+pL8rKKxCL/Y8+9csJ8o6nXx3i6EYjpW7tw5\n",
862 "gZkgq6hiltjJqaO5vclakxzXoXATyrqGVAH7olo9HYMBOi+Ur7CKi/GcwvUEpI5VAYVVp6vwwirL\n",
863 "ZOCgs3TheibET7UiWKpGOpjVZhWiKKZpxiaqYwWF0YHODDE9xSDU10/M50c3ffSGItHlKRby5pnI\n",
864 "Zs4j8tZvS17PWBjmz8TZ40QUxZLPZzaTmm5XeYq9w/s6WLyykfnLbJw6nuoZZff6OD84xOqGzOcQ\n",
865 "W+Wo5YLDF6FGr6J/DI0b7CxsIjCBsQJ2uUZNxeql9L/zfsH7CAUj+ANRLNWpHV+jSY3fG/qosEIK\n",
866 "af7yl7+cErQ8EfjiF7/INddcw+bNm0f+HTt2bEKPCZNcWOlmNpXdcsHjCuTUVwFUm+pwuLtZuXY6\n",
867 "H/zxImIBTsL5fKzCoSi/+sn7E6IZKxdKpQPfPraTGnM9gz4nkWjhFwjRXrg5aCwaJxiIoDMUV1gJ\n",
868 "lkpm+k5y8lhv1r+9Lxyj1xNmZtWoIDqX63oybE2jlgsASrmM5RUaYoJQNoF1h+MMeo2RapMt53bW\n",
869 "OiNOuydngHI8Fqd3mArMh/pmC/ZuN9EMVgADjjxWC0V2daIH9qC4cjTuosagBpUCvy9V4zWhhZVJ\n",
870 "jdedW9/iGKeHletQG6blC1Ju7MbjWK667TNEfvNzxGh5JnrlWjX+BQupU5aem9ZgUtNdhiSHSDjG\n",
871 "8Q+6WHZVEzPnWek8P5DCILxx/jzrpjWjkmfWMSZbLmQ1By1wIjCBmjGWC1A8HdjX46FKG0NRk0pB\n",
872 "yuQyjGYtMeVHhZVGo+Hb3/72hPtaCYLAo48+yo4dO0b+LV68eEKPCVOgY1Vuk1BPAZEoVnM9DlcP\n",
873 "DdMsqDQKzp3KH3yaz8dqwOnj3ElH0bEjlxKlCNgj0TD//T/P8WctX6TKWIvDXVjYJUC8iDgbryeE\n",
874 "waguWrQsmCqoGjyLAFnpwBMOH3OqtSO6i5GQ34r8hVGNzYSzz5tSfMyWQ7jIAjAXjl3cx+Lm3DQg\n",
875 "gEarRKtXSXRCFjj7vBhNGjQFZFqq1AoqqvX0dbvSnuvPZw5aRMdKFEWi+/cgH9ZXAdQYVEQVcukO\n",
876 "PgkDObzFxotC3NedvvC4rBbG6qsAVHIZFdrSHMvlM+cha5hG9J3XS17TWAQWzqc6VLre0lYmAfvJ\n",
877 "oz3YmiyYK7RotEoaplVw/uToufiNc+e5YdbM7OuoaKZn4KI0EeiVJgIzFVbFdKxq9Dr6xthcJAqr\n",
878 "Qm+a+7rdWOUehOp0bZe5UktMPj5X/49QHCaj2TEFCqvyUoHZ4mySYTXZcLol6mjlNdN5/93cIvZY\n",
879 "XMQTimJSZy+sXAPS9I69K/0iNVUw16qjYyiEL1y4L8tbR1+hsXoWcxuWUmtpTDPlywYxHEJ0DSFU\n",
880 "FWa14HEFMRSprwIQVCoEjZa58yqy0oFj9VXuISlLUqHMP9GnVMmpqNaneEgZ/GHakREv0xe2EBow\n",
881 "AcnPKjsdKAUvF05RNkyz0HkhVcAeicTwe0KYsxSexVKB8QunQalE1jh95GdWvZKQTEgxCQ0GIkQj\n",
882 "sZTw9HKisMIqMj6rhYOtWJL0VQnYjOqCo23GQrX504R/82LJaxoLd2MzFkfpUS02Y/FTjplwaG8H\n",
883 "y9c0jzyes6iWU63SugYCAY47nKxtasz6eqPWgoiIJzA02rEKRKjSjUNjNUa8DqCfMw0A3+nChp36\n",
884 "ut1UR5wpVgsJWCp1RLl8JCWnTp3ivvvu4/bbb+f222/nm9/85shzGzdu5Nlnn+Wuu+7ixhtv5J57\n",
885 "7sHlGr3+7d69m02bNnHzzTdz88038+qrr7J8+Wgw+7JlywC4cOEC69at48knn+SOO+6gpaWFf/u3\n",
886 "fxvZLhQK8bWvfY3bbruNu+66i89+9rN0dkpxeNFolAcffJDu7uw3/N/61re46aabWL9+Pa+99lrZ\n",
887 "/ja5MKnvsG56I4GL3cSjUWSK8izFMxSkNkM+WjI0Kh1qpQa3f5B5S+rY/buTOHo8WG3GzPsMRdGr\n",
888 "5DnFuq4BP4JMwN7lZvbCwqZQLjVUchnzrDqO231c1ZT7bwQQjoZ4+b0f87e3SV+muoom7EOF5TuK\n",
889 "jl6E6lqELG38sSjWwyoZgrmC2Q1KfrOrl5Yb56ZRHG19Pm6eXz3yWKIBC++KSDqrUd3SUK+bmFnP\n",
890 "2f4Ac8YptI7GIpzoPMQXbn6ioO1r6k04etzMW5L5YiEFLxfurdU4rYLWwz3AqEB4yOnHXKFFlmUy\n",
891 "zuGNsG5m4YVVYhow+X2pMajwCkLKJFjCcb1cEUFjoTeqcdhza9TGQwWKojhCBY5Fw7AuaWXhsxwj\n",
892 "kM1bjOgaQPS4EYz5v7f5MGCppOlQZnuDQlBvUrOzzTmuNfR0ugj4wkyfM/q9nDW/hrdfO0k0EmPX\n",
893 "+QusbWpEk+O6IFkuTKN3sAOHT4ZVr2LQH2VJ3ai2KdhZWE5gAmPd1xPHSXStDHOn591HX4+HBZ6L\n",
894 "CNXpgcPmSi2xWPoN2cLvPVvwGseL1r/5XEHbeb1eHnvsMb773e9SWytd05566imef/557r//fuRy\n",
895 "OXv27OHFF19EEASeeOIJnnvuObZs2UJbWxsPP/wwL730Es3NzUSjUR555JGU73bi/zKZjOPHj7Nl\n",
896 "yxa2bt1KIBBg7dq13HnnndTX17N9+3bWrFnDtm3bAKnY+/znP8/OnTtRKBR85zvfyfo7fPWrXx1Z\n",
897 "e3t7O7fccgvTp09nwYL072g5MamFlVyrRl1TSbCzF9307HcmxcDjCjJnYf4uSUJnZbZVsnx1M+//\n",
898 "8QKfvHNJxm0LyQkcGvDTNKOS3jzTaZONBB1YSGG16/B/M71mHrNsiwCotTQWXFhJ4cvFWS0UOxGY\n",
899 "gMxSiVXpR0DA3u1OMe2UjEH9PLyusCibTKhrNNM9PD0XCkZwDQRYtHIaBzrd4y6szvYcp9bSgElX\n",
900 "mHWDtc7I0QPZ34OejiFWXjOt4OPXT6vgjR2tiHFxxJ5hwOnLarUAxXesogf2oPlc6kSpRasgIAi4\n",
901 "kzRPE+G4noxCxOsOX5jqLN5d+RBo70amUaOprU57zmZS01OiLkkQBGRNM4h1nEOxcHn+F+RBn0zD\n",
902 "nKPHEONxhBI0LvXj+F0SOLy3nWWrm1Kof71RjbXOyMWz/bx+7jy3zZubdz91FU10D7Qz4G+kSq9M\n",
903 "MQeNuDyIcRGlJfMNcybU6FPF6wlUf3w1nT/9NdP/+s9yvj4ajUsZpM4zKR5WCZgrdYSj6deIQoud\n",
904 "S4m9e/dy4sQJHnjggZGfBYNBrrjiipHHn/vc50YKpJaWFl55RTK1femll3jggQdobpY6kgqFgm3b\n",
905 "tvHWW29lPFZ9fT2bN28GQKvVsmLFCtrb26mvr+fll1/myJEjfP/73x/Zvq+vj2AwiEaT+5qRKKoA\n",
906 "mpubuf/++/nNb34z4YXVpCci6mY24ztbPjowV5xNMqzmepyuHgCWXtXE6eP2NL1HAoVMBLoGA8xb\n",
907 "Uoe9yzXlBexHCxCwhyNBXtn7/7jr2tEvfK2lEftggYVVEfoqGO5YlTD+DZKAHdcgc5fUpdGBnUMh\n",
908 "9Cr5iGEgFC5cT8CWlBnY0+GirsHMlc1mDvz/7L13mByFme39q87TeUJPz/REjTKKKIBAFsEiS8YE\n",
909 "wbJ4wYttbO/F1+wu3uuLha8wLP7Wtti715lkFmyMCcYk40RGYCQEQnGE4uTQPdM5p/r+qO6e7pnO\n",
910 "MwLZ5jyPnkfTVV1Vnarees95zxmYfhFdCQ0IkrGno0AYczgUw+cJ02AtXBRNhsGkQaVW4Byb0JU4\n",
911 "HX5qC+irkqKII1A4J3DK+oN9EA4jm5V7kZQJAqoaJePu3MKqbobDl7OhN6gJFKGwkqLI+DQ6VoVo\n",
912 "QJh+eLGsrYtk37Gqn5+GKIoMBeJYkmGCveXrJbNRW6MgEk9WJCnIRigY5fD+URavnHozPXeRlT17\n",
913 "B3lveISzOtrzPDsXzXXtHHOMYFBL3mPZOYHhwVFqWqwVdUAt2ly7hTTq163EtX0PiXAJKnnUh9ms\n",
914 "QSEjx8MqDXNtDfHYiZ2EmykIgsDSpUtzhN9//OMf+c53vpNZx2CYKFrVanVmyi8cDk+5Dha7LmZv\n",
915 "ByRxe/bE4EMPPZRzHDt27ChZVOWDUqkkUUVEUaX4yAsrXVfbjHlZiaKI1116KhAkLytHSmel1amY\n",
916 "t7iJ93fkL/DK8bDyOEO0zapDTIontYB9QaOOo84Q4RKhsC/ufoo5zYuZZV2QecxaARUoWS2UX1j5\n",
917 "vJGS2rhCEEx1iB4n8xdbp0wHTrZZgMo7VvVWPV5PmEg4zlCfG1u7mWXNeo6Mh6q+uKRRjn9VNoy1\n",
918 "NUTCcUJ5PKBGBqSirxCFVwgtnbl+Vs4iVguecJwapRxNGfo0SE0DrlqbtzOi1anwZHesxoOYT2TH\n",
919 "yljcx8objlOjlKFWVHdaLEQDwvQLK3l7F8n+41U/Pw1nUHqNjXM78O2rLNYrDUEQaDaoqhaw739v\n",
920 "kNkLGtHmGRKYc4qVV473strWjE5VunPYVNtOn8uFRSfZdziDExqr0MAImrbyaUDI774OoDQbMSzs\n",
921 "wrW9OIVqH/JiMcuQWfLLQUx1WuLxE39hnwmsWbOGw4cP8+KLLwKQTCa566672L27NI187bXX8sAD\n",
922 "D2S0ULFYjDvuuKOqpsNVV13F5s2bicelidEdO3bw3e9+t+Tz7HY7Z511VuYYBgcHefTRR7n66qsr\n",
923 "PoZK8dEXVnNmbjIwkjKtU5fhz2IxNePwTNyxrVzbwe7t/XlHz0t1rJKJJD5PCKNZg7XFVNKs8qOE\n",
924 "RiFjdl0NB+2FA17D0RDPbn+ITWtz29NWUwsOzxDJZOkTQ6Wu6/4yhg4KQTDXIrpdUnZgig5Mo9ue\n",
925 "K1wHqWNlrqBjJZfLsDQZGB30MNjrwtZhRq2QsciqY9dgaV+pQghHQxwb6WZB66mlV05BEASpa5VH\n",
926 "wC4FL1eeXSgZhU4I2ItZLYz6KrNaiL+zDcXqT+RdZjSqCWRRSq6xwvudCWhqlMRjkoFwPkj6qmlM\n",
927 "BO7qLtixShci1XazZe0z07Ea8IZpMaoxLpGMQquFzVRdoSgmRXZv72f5mra8y021NfTpQqw0lDf0\n",
928 "0lzbzrA3iEWvJBBNoJAJmaK/UuE65HdfT6Mc2wX7sI9GTRShIf9+NTVKBP4ynNe1Wi2PP/44Dz74\n",
929 "IBs2bODCCy8kkUiwZEl+yYwgCJnu4KJFi9i6dSs33XQTGzduZOPGjaxduzavxmry/ydj8+bNtLW1\n",
930 "cfHFF7NhwwbuvvtuNm3aBEgFWyHxemNjIzfffDOf/exnufTSS7npppvYunUrs2blN5ydSXzk4wm6\n",
931 "rnbsv39jRrbl80hRNuW0fi3GZvb2TPxIGqwGGqx6PtgzzKIVuZ2WUnE2Pm8EbWrKzNpiZHTQw5yF\n",
932 "5Z0YPgosSflZLbfl1x786f0nmd+6nI7GuTmPq5Qa9DUmxn12LKbinkuVuq5XOxUIEhWY7DmCIAgZ\n",
933 "OjCtszpgD7Bh4YTmJRFP4vcWnngrhKZWE8MDbmnqLiViT8fbfGJWdUahHwy+zyzrfDSqyro06Wib\n",
934 "9tn1OY8P97lZujr/BasYWjpreed1qRtSMnw5UL6+KukYITluRzYvv29MrUmD/4gjs1/JDf/EdawE\n",
935 "QUBnUEsTj3VT9zOdicBkPI537yGMS+fnXa5VydGq5DiDceqr2IestZPkUB9iMlF29mY+pKNsDIvm\n",
936 "MvCLZ6vejs1QnZdV79FxlCp5QQPbYCxGnzyAxVnepamptp3xQIJlWiXOrIxAqK6wggn39Q5z7k1K\n",
937 "w7mns++W/4At/7Pgc+1DXrpM7oIdKwCF8iPvZ5SNWbNm8cgjj+Rd9uyzud+ftWvXsnbthFfdunXr\n",
938 "WLduXebvsbExfvzjH2f+3rVrFyBpn7Zt25azrWxBukKh4NZbb+XWW2+dcgxKpbKoeP3KK6/kyiuv\n",
939 "LLj8ROEj/4S1s2eOCiwVvpyNBmMzjpTGKo2Vazt5963eKXeVnnBxKtDjDGYu1FJhdfJ2rKC4n1U4\n",
940 "GuT5HQ+z6cwb8y4vZzJQjEYR3c68Pi75kEyKBPySj1U1kKhAicrKpgMD0QR2f5Suuokiyu0KYjBq\n",
941 "kFdIlzW1mug7Mo7eoM5QGOl4m2q7EPt6K6MB08gXbSOKYqroq7xjVd+gIxyK4feG8XsjKFXygj5Y\n",
942 "lXhYxXe+iWLlmQUnQxvqaoinQqCD/ihyuaxg6PNMQVck1sYRiFatrwoc6kFjs6A0FRZKNxurt1wQ\n",
943 "arQIplrE0ep0UWlIhZUmE21TLar1snp/ex/L17QXvPl9o6+fJQ0Whg46yzJu1mkMiIp69KrYFA+r\n",
944 "cH9lE4FpNOp02PMI2E3LFxIZHSM8nN/3MJkUcYz4aAiNFD33yRUzE9x+MuPAgQNs2bIlQ98lk0m2\n",
945 "bt3KxRdPnZT8a8RHXljVtFiJOt3Eg9NPcS9XuA4p8bp3OOeiOGtuA/FogoFJMR+eUPGOlccVytwB\n",
946 "W20nNxUIUizLB44g0cRU2vMP7z3Goo7VtFnm5H2uJGAvPmwgOkYQ6hvLtloI+iNoapTIq9S2CKZa\n",
947 "km4nQA4deNAeYE69NmMMCpKVQDWTZ82t0udq65i4024zqZEJAr3u6tLq9/XuYFEZxqCTYWmaGm3j\n",
948 "Gg+iUsur8wKTCdg6zAz2uYvSgACjvvIjX9L6qkJortMixhKISbHigYJqUWwycGwaVGA+Y9DJaDGq\n",
949 "GS4xlVgMsrYuEr3TowMHPBFajWo0rVaSkQgRh7Oq7TQbK/ey8rpDDBx3sXBZ4W73n44e45KFc1Gp\n",
950 "FYyU6QmoVNmQJ92pwirLw2qwsjibNCxaLY48VKAgl1P/iVWMvZafDnSPB9HqVChdw3k9rNL4S+pY\n",
951 "VYs5c+agUqm44IIL2LhxI+effz56vZ5bbrnloz60DwUf+ScsyOVoO1oJHi9PFF0MPneobJ2OTmMA\n",
952 "BALhiSJIkAmsWNvBu2/25KxbSmPldgYxpboiRrOGZCJZMjrjo4ROJafNrOaQI/fkEYz4+e3OR7iy\n",
953 "QLcKwGou3bFKVhBlAynhepU0IIDMXI+YKqyy6UBJuJ5bRFV7ATfXa4lGEtQ3Tkz6CILAqlZDVdOB\n",
954 "/pCHEVc/c22VxyvUW/W4xgMksvSAw/3lBS8XQmsqN7CYcB2krk45Hauk20myvwf5osL6sSaTmoRM\n",
955 "RigUS2U3nvjCqphJ6FjKZLIauHcdyAlezofpGmvK2meR7J9eYTXolahAQRAwLJpXtYC9pQox/p53\n",
956 "Bli4vBmlKv+5NBKP80ZfP+tndTJ3kZXD+8szMRXldSRjoylz0OycwBFqWir3FMznvp5Gw7mnM/bK\n",
957 "jrzL7ENeGm3GjIdfISiqvIH8S4JKpWLz5s28/PLLPP/887z00kvcdtttJzzC5mTBSfEqdTNEB5YT\n",
958 "Z5MNi6kZhzeXDlx0qo3BHldObIiksSp8wvU4Q5hrpQu4IAh/sXTg79/9Fcs6z6ClvrC4rxwvq+TI\n",
959 "YGXC9WmYgwKg00MkjBiVNB9pOrB71M9C6/QmAtMQBAGZXJhyt7mq1cjO/soF7Pv7djKvZRkKeeUX\n",
960 "cqVSjqlWy7h94vPL1n5Vg/RkoNPhL1pYjfqjNBpKF1aJ995CsWw1grLwuhadKuW+HsF5gj2s0tAZ\n",
961 "1AWndqdjDuoto2M17cnAaVouJJIiI74ItpStiXHJ3KrpQItOhTscz9v1zrvveJK9OwdYfnphC4U/\n",
962 "Dwwyr76OBq1WcmHfP1oWzR4R9YRDfTiD8YzVQiIcIer2orbWl3j2VDQWEK+DJGAff30HYp6R/dFh\n",
963 "L43NBpJjo3k9rNIoJ/HhY/xl4yQprNoJzoCXVTlxNtmw5NFZKVUKlqxq5b23JuIL3KHiBqEe10TH\n",
964 "Cv4y6MDJgcyBsI/fvfsoVxTpVgFYa1sZcRf/rJKjQwgVdKymMxEIIMhkkv7EK1G4Eh0Iff2eqVYL\n",
965 "40HMVXglBXwRRJEp3Y7lNgPdjgDhApNmhXBwYBeL2ldVfBxpNDYbsGf5WQ33ubG1V66vSsPaYsLp\n",
966 "CDBu9xc1B7X7S3tYiaJI7K1XitKAIAm64wo5Y67wCQ1fzobeqCmosaqWCkyEIviP9GJYNLfoetP2\n",
967 "smqfNS3LBUcgirlGkbGTMCyah7fKjpVcJmDRqRgpU8B+5MAodRZdTsc3jUTvUSK/up8/HTvOBals\n",
968 "QKvNSDKRZHy0uOdeLJEkklDi9h7J0ViFh+xomixlyxGy0ZDHfT0Nja0RtaUez+6DU5Y5hr1YahUg\n",
969 "k+f1sErjb6Fj9beOk+IT1s5umxHLhUrE6zChs5qM5WvaObBriEg4TlIU8UbiGDWFf6BuZyhnysza\n",
970 "YixbH/BRYbFVT7c9QCIlEP3du79kxex1NNcVN+VLm4QWu5MURyvrWEkTgdPLhxPMdTl0oHVuA7Zg\n",
971 "BPMkEXa17t5DfW7qG/VTOpE6lZy5DVp2l2G6mo0Rdz+2+s6KjyMNS7Mhk18YiyYYd/hpbK4+7kSh\n",
972 "kEmB0yP+ghqrUCxBNJ4saZYb3/4a+L3IV55Zcr9yjYLR8SCu8eAJNQdNo5DGShRFKYBZW3nHyrv/\n",
973 "EPq5ncg1xb/DUmEVrXjYQRRFxoJBhEYbos+DWICmKoUBT4SWLMp9Oh0rAJuxfEvDtUAAACAASURB\n",
974 "VC+rybmA2Ujse4/wzjd5paeX81Kj8IIgMOeUiezAQhgPxjCqZdjdfThDMWqzPKyq0VdBcSoQ0nRg\n",
975 "rs5KFEVGh3xYlMGiE4FA1VrSj/GXg5PiE9Z1tRM4Mr3CShRF/Cm7hXLRYGzK8bJKw2iuoWNuPfve\n",
976 "HcAfSaBVylEWmCKLRuLEovGc4Fhri+mkpwKNGgWNehVHxoP4Qx7+8N7jXHHmF0o+T68xopAr8QZd\n",
977 "BddJVuphNU2NFUgC9nRhBRBr0GPx5br/xmIJgoEoRvPUAQdfJII7XFgXN9TnomN2PSMDU53107YL\n",
978 "lcDhHsJiLP89mozGZmNGwD465KHBapg2xdDUZiIcimEsEb5czM5EDPiI/uInqD//LwiK0kWKUqvE\n",
979 "4QriGQ+eUKuFNAoVVr5IAoVchlZV+Xvofb+7pL4KwKCWZ/ZVLqKJBLe/9gaX/PIxEATJdqHKrtWg\n",
980 "J0KraeI8pZvdQXholHiB7kwplNuBGxv14RoPFLSgSfYc5t1wjDaDgWbDRKenHJ2VIxDDalAz6h5g\n",
981 "PBClPu26PjBCTYXmoGkUEq+nkc/PKk0vawNjBT2s0jhRWZgf4+TByVFYzW4ncKx/WlEwoWAMhVJe\n",
982 "UBiZD4U6VgArz+zkvbd6cQZjxScCnSFMtdqcH4vRrEn5JZ28AnaYoAN/u/MRVs09B6u5vLxGq7kw\n",
983 "HSjGooiu8ZInl2z4vNV7WKUhmCcsFwCOxUVUclkOJesZT4UL5wnTvuP1bXz+2d8SLRB3MNjrZta8\n",
984 "BuQKGV5X7gTr6lYD7wyUr7MSRRGHd6ikF1gxWJokk1BRFBnqq85mYTJMZg1yhSzv+wNg98dKelhF\n",
985 "Hr0PxepPIJ9butAAyX3dNRakRqdCWUVRUyn0BewWxoLV66vcu0rrq0C6oNoqmKZzh8Pc+NxvGQ8G\n",
986 "kQsCY8GgZBRapYB9wDOhrwKQKRXo58/Cd+BoVdtrNkgduFJ4f3s/S1a1FrQ4SfQc4eX6Vs5rrMt5\n",
987 "vKWjloAvkqN3nQyHP4pVr0Gn1uPy2zNUYGhwtCqrBchyX4/mf221a5bjO3CUmGfiN28f9mK1GRDH\n",
988 "R0t2rD7GXz9OisJKWWdCECA27i69cgGUG2WTjQbTRKzNZNjazWj1ao502zEV87CapK+CLAH7Sa6z\n",
989 "WtKk5/2BYf6060muOOPzZT+vmJeVZLVgQSiSSj8Z09VYQcok1D2e+fugI0Tnwsac7EDXeH7h+rDP\n",
990 "z7a+fuprNHx/+ztTlsfjSezDPppaTSmj0Fyat6uuhnAswaCnvAumL+RGKVejVZef6TcZOoMamVzA\n",
991 "5wkz0u/GNg3hehoKlZx4PEGygCC5lIdV4uAeEnt2orr6c2Xv02DQEPCEPhThOkCNTkUkFCMx6TWO\n",
992 "VZB/OBnlWC2kYTOU1+U54nTxd0/+hmVWK9+/+EJm1Zrp83inZbkw6A3ndKwAjNOYDLSVYR8RjcQ5\n",
993 "uHu4oHGtGA4RH7fzmqWN9ZPefplMYM7CxqJdK0cghkWvwlrbTjwynOkKVjsRCKl0A23+MGYAeY0a\n",
994 "8+rFjL+xM/OYfchLY7MR0TFatn/f3zLC4TBf/epXc/IATyQeffRRPvOZz3wo+4KTpLASBCEVxlw9\n",
995 "HVjpRCCkxeuFDfdWru3g6LsDRYXrk/VVafwl0IFLmvQcOv4Up89fj8VUPi1VzMtKogHLF66Lopjq\n",
996 "WE1PYyUz1SG6pY6VLxLHEYiyenVrTnZgIauFX+zdx2UL5vEf532S5w8f5s8DgznL7UMe6i06VGoF\n",
997 "TVmBzGlItgtG3i3z87Z7BqfVrUojbRQ61O+muX36hZXfI/mJ2QuEPNv9USwFCisxFiX8wH+hvv5/\n",
998 "INSUr5WqNamJBqLUfgj6KpAu1jU6FUF/bjei2jibmMdHZGQM/bzOstYvJwrm9d4+/vGZZ/kfq1fy\n",
999 "r2ecjkwQaDca6fV4kE/DciHtup6N6RiFNpehsTrw/hBtXXUFz83JvqN0dy6kTiGn1T3VeLMUHehI\n",
1000 "WWSYDa3o5WMZ5iBUpTloGg26yuhA+7CPxmajNBFYxMPqY0jQaDR8//vf/1DsF+x2O/fddx8/+clP\n",
1001 "Tvi+0jgpCitIZwZWPxnoc1emrwIw1JiJJ6IEI/mFx3MXWQl5IxgjsbzLQepYmfPEY6SjbU5myEUf\n",
1002 "8tBrrFhQWSVfzHKhUquFcCiGXC5DpZ5eupJgrkX0SBqrg/Yg8xq0NLfkZge6xqbqeALRKE91H+S6\n",
1003 "pUuoq6nhrk+ew+aXX8nRWw32ujPGoE0tUwsrgFVtkgt7OXB4hmk0lV98FoKl2cBAj4tEQqw4oicf\n",
1004 "nI4ADVYDgz359XPFOlax536FzNaOYlX+XMBCaKyrQYwkPrSOFeSnA8eqtFrw7D6IccncsqfPpC5P\n",
1005 "fopJFEUe2r2Hb77yGj+46EI+PX9eZlm7yZTqWM0iOdCDWOGdfiyRZCwYo2lSuoFh8Vy8+6ssrAxq\n",
1006 "RvzRzADMZIhiKhewiMVCoucIR5vaWaTXkRyeek5p76pn3O4vKKtw+CXDWp3WhpqJwmw64nUo7L6e\n",
1007 "huXcNYy9uj1z01auh9XH+PDxzW9+k61bt2I0Vj/cUylOnsKqa3peVtV0rARBoMHYXFBnJZfLqJld\n",
1008 "j6K/sFDb7QzlzR1rsp38VOBzOx7G2riOfn9l3QJrbeHCShwdQqhQuD4tD6sUhCyT0HTwcrZZKOQP\n",
1009 "+f1190HOaGvFZpCiSNa2tXHh7C7+zyuvZU6aQ31ubKmOUFPKgT056WKywmZg77CfaJ4Q78lweKan\n",
1010 "r0qjsdnAQI+T5jbTjAhinWMB2mbVMdib//ueFq9PRnKwj+ifnkX92Zsq3mdTvQ7iyQ/FaiGNfAJ2\n",
1011 "yRy08o6Vpwxj0Gw0G/LH2kQTCf7Pq6/z9MEPePTKyzi1ObcoaDcZ6fN6EXQGBJ0e0ZFfwlAIwz7p\n",
1012 "9Skm6ecMp8wm8EEPyVi8ou0BqBUyTGoFY4H8N55DfW4S8STts+vyLgdI9hxhwFhHe30d4sjUc4pc\n",
1013 "IaNrvoUj3fa8z3cEolj0SlTqZuQJqbMlJhKERxxobNXntTaWELDr5nVCUiRwpI9wKEYwEMVcV1PS\n",
1014 "w+ovDYcOHeK6667j8ssv5/LLL+d73/teZtmGDRu455572LRpExdeeCFXX301Hs/ETefrr7/Oxo0b\n",
1015 "ueSSS7jkkkt44YUXWL58eWb5smXLAOjp6eHss8/mrrvu4oorrmDdunX84Ac/yKwXiUT41re+xWWX\n",
1016 "XcamTZu44YYbGBiQvivxeLxgCDPACy+8wBtvvMG//Mu/cNFFF/Hb3/52Rt+fQvjIQ5jT0HW1M/Sb\n",
1017 "P1b9fJ8njKXJUvHzLCYbY54R2i35PWjiNhPxg3bJxDKPwNrrDGLO0y0w1tYQjyUJ+CI5E4MnC9z+\n",
1018 "MV7Z+wyXnnMPe0b8XLao/PfOam5lpCAVOIhyefkxLT5PeNoTgZCaCkx1rA7YA1x2ivR65i+28uyj\n",
1019 "77PuwnkpD6uJIjieTPLwnr385wXn52zrn08/jWt+/RueOvgBVyyYz1Cfm3MuWQBI6fR6gxqn3U9D\n",
1020 "00QunFGjoKNWw75RPytait8Z2T1DtDV0Tfs1W5qNOB0BVi+YfuC3mJTCl+dusrJ7hzRIMrlYs/uj\n",
1021 "NOqVk56XJPyz/0J1+XXI6ir//dkatMhEEWOem5MThXyFVbXmoJ73u2m+7Lyy12/Jk7HnCoW4+Q9/\n",
1022 "wqRW84srLkOnnHocUsdKumjJ2meT7D9WUWd48kRgGgqdFk1LI4GjvRgWzC57e2k0p3RW1jymse+/\n",
1023 "3cey09uKFv3JniP0r+hkma2F5G/z36zNXSR9J/N1vhwBqWMlKJtIxKRiMzI6jspsLGl/UQyWEpYL\n",
1024 "giBk6ED5+vOwNBkQgv6SHlaFcMH9u6o+1krxxy8UTkPIht/v5xvf+AY/+tGPsFqlYvHb3/42Dz/8\n",
1025 "MNdffz1yuZxt27bxxBNPIAgCt99+O/fffz+33HIL3d3dfO1rX+PJJ5+kvb2deDzOv/3bv+V8F9L/\n",
1026 "l8lk7N+/n1tuuYXNmzcTCoVYu3YtV155JTabjTvvvJM1a9awZcsWQCr2vvzlL/P888+jUCgKhjDH\n",
1027 "YjHuuOMO/t//+3+cf/759Pf3c80111BfX8+aNWum8xaWxMlTWM2Znkmoz1O5eB1SYczewjorTwJs\n",
1028 "s+vZvb2ftefnFl9iUsTjCuUdT59wYPfQNQMXvpnGszse4qxFGzijq5NH9nyQ90JaCGZdA9F4mGDE\n",
1029 "P0WALVGBFZiDzoC+CtJBzG4SySQfOIIsSEXZpLMDB3tdRMKxnCLuxWPHadLpWWrN/XzUCgXfO389\n",
1030 "n336ORbUmBEEadIzjaY2ScCeXVhB2nbBV7KwcniGWDG7MsosH+rqtUQj8SnHUQ183jBqjYIGqx5B\n",
1031 "SKUJ5BShIq5QfIoOKf7a7yEeQ3nexqr2q05NAkY+xKiLfLE2VVOBuw6w4FtfLXv9Oq2CYDRBMJpA\n",
1032 "q5JzxOnkphf+wEVzZnPz6auRFfgNtpuM9HmkwG9Z2yySfcehAto1n3A9DcOieXj3Hq6qsEpPOS63\n",
1033 "5X4Hg/4Ixz5wsP7Swt08MRYlOdxPXyxBR3MLYiiIGApM0ejNmtfA73+9l1AwmhPSHYknCUYTmGsU\n",
1034 "xGggErGTSManNRGYRqNWy8Gx8aLrNJxzOgOP/RYWnkajzUhybKTqicByi50PE9u3b+fgwYPceOOE\n",
1035 "aXQ4HObUUyeO9Utf+lLmurFu3TqeeeYZAJ588kluvPFG2tulYlihULBlyxZeffXVvPuy2Wxceuml\n",
1036 "ANTU1LBixQr6+vqw2Ww8/fTT7Nmzh3vvvTezvt1uJxwOo9EUvubv3LmTBQsWcP750o1zW1sb//zP\n",
1037 "/8xjjz32t1NYaTtbCfYNIiYSVbnlVkMFQirWxpOfCgQpzmbdqS3sfm4/p53ThTLLKyjgj6DSKArq\n",
1038 "g6w2IyND3pOusHL6Hby273nu/twTmPUq1AoZ/Z4I7WUWpoIgZHRWs6wLMo+L8ZhktVDBycXvjUx7\n",
1039 "IhBAUKlApaZvaByTRpExBk3Tgft2DmKu0yKkqBBRFPnv3Xv4wqnL825vTl0dN61eyQ/+8DYXtbXn\n",
1040 "FJ1pndWSVbn2FKtbjdz9Rh9fPL14YSlRgdV7WKWRJiNnwsnZ6ZAyAgVBoKWzloFeV05hNR6IUVuj\n",
1041 "yKGSkm4n0SceRPO/v4Mgq84qweOWrCuGXCFaPySdlc6oxj6Jpq+GCgwPO0hGY9S0lU/rCoKQ6vJE\n",
1042 "GfDZ2fzKq3z9zDP4VJaeKh/MGg1yQcAVDmNo75JMWCvAgCdCV11+HZ5xyVx8+w7BVRdVtE0o7GW1\n",
1043 "d+cA8xY3oakpXKwmB3qgqYUBn5/2WjNCUwvJkUHks3LfC6VKQfvseo4ddLBoxcRvaywQpV6nRCYI\n",
1044 "uCMCNepaxrwjJKYxEZhGg05XlAoEqD9rFXv/9dtEP+WmbXY9oqMH4a9IuC4IAkuXLuWXv/xlwXUM\n",
1045 "homCWq1WZ6b8wuHwFPukYnZK2dsBSdyePTH40EMPUVtbW9Hxu1wu1Orcm4nsYzyROGk0VnKtBlV9\n",
1046 "LaGByrQDAMmkiN8XqcoLyWKcmheYDU84RnOzAWuLiYO7c9dzpzysCuFknQx85u0HOXfJpZj1DYDk\n",
1047 "Z7W3QufwfJOBomMEoa6hLGPINKotiPNBMNeyv985JXh5/mIrxw45coYM3h8ZxR0Kc25nR8Ht/f3i\n",
1048 "RVgiao6SOyWXbzIQYG6DFlcwht1f2NtH8rAaxmKcvsbKMeJDpVHgGq/O4DEbTseE/iwdyJyNfPqq\n",
1049 "6C9+guLsi5C3V09rusaCIJdhn4HXUC50k8TrgWgCEdAqKzsdelLGoJXq25oNKh7avZstr73Ojy6+\n",
1050 "qGRRlUa6a1VNZmAhKhAky4Vqo22ajWqGJ3lZJZNiirrLb7GQWa/nCGPtczGoVOiUSmRNrXkF7JB/\n",
1051 "OjBNAwK4QnHqjK0MO/sID4ygqdIcNI1S7usASrMRw/wuRo45sjpWfz2F1Zo1azh8+DAvvvgiAMlk\n",
1052 "krvuuovdu3eXfO61117LAw88kNFCpWm5arwqr7rqKjZv3kw8LukAd+zYwXe/+92SzzvttNP485//\n",
1053 "zMGDUvxQIBDg/vvv57LLLqv4GCrFSVNYQcootAo6MOCLUKNVVXXnLmmsChdW7nAcU42ClWd28O6b\n",
1054 "PTlfDGkisPA01sk4GTjmHWHbgd/xqdM+m3ksXyBzKVjNU72skiOVOa5DOs5mZgormamObnuQhZPy\n",
1055 "ARttRpIJEXWWbcZ/797DdcuWIC9CQQmCwCzRwOveIXYMTtDFjTYj4w4/8Un5gHKZwIoWI+8WmQ70\n",
1056 "BMapUWnRqKbfnRnuc1Nv0WeibaYD51ggE77c0lE7RcA+eSIwvms7ieOHUF3+D9Par2ssgEwlxzHJ\n",
1057 "dLVSfOfVHn769gAHRgMkS5y8J1OBY4EoDTplxQVSJf5VaUQTCfa7PuCN/h5+ecVlLGsqv7PSbjLR\n",
1058 "7/EgNLUgusYRw+W/Z4OT4myyYVg8F9/+Q1Vd9GwGNUOTaNXjhxzoDGqsLcVNaxM9hxloaqPdJFHn\n",
1059 "QnNrXgE7wOwFjfQdGycamRDZ2/2S1QKAMxijqbadEVfftCcCQaICC+UFZqP2nNPx+GM0WA1/dR5W\n",
1060 "Wq2Wxx9/nAcffJANGzZw4YUXkkgkWLJkSd71BUHI/IYWLVrE1q1buemmm9i4cSMbN25k7dq1eTVW\n",
1061 "k/8/GZs3b6atrY2LL76YDRs2cPfdd7Np0yZAKtgKidcbGhr42c9+xq233sqnPvUprrrqKq699lrO\n",
1062 "Pffcqt6PSlCSCnz22Wd55x3JNHHFihVcfvnlU9bx+/386Ec/4uabby7KeZaCrquN4LE++GRl/Od0\n",
1063 "uh4NxqaCGitRFPGGE5g0Chrm1CMCfUeddMyREtM9BTys0jCdhAL2p99+kPVLL8ekm5jUWdKk4+F3\n",
1064 "hyvSWVlrWzk20p3zWHJ0EFkF4csgaaxmQrwOkklotyfOFdbcwkoQBPRGNcGAdGfd5/HwztAw315f\n",
1065 "/AcWjcTxuUL8y/VncutLr/DU1ZswadQolXLqGnTYh32ZacE0VrUa2N7n5eIFDXm3afdML8omG8P9\n",
1066 "Hlo7a+k75iy9cgk4HQFmL5DE5w1NBvzeCMFAFG2qI5DtYSWGQ0Qe+gHqL/wrgmp632vXWACVVoXH\n",
1067 "U31KQTie5I3jbv5umZX/fKOPUCzBWbNqOavLzAKLdsp3erJ43RGIYdFWNxHY8cWry17fGQpx8+//\n",
1068 "iCDIuKTjtMwkarloM6YmA+VyZLZ2kgM9yOeULuzCsQS+SByLPn8nWW2pQ6ZWEx4YrTgGJu1llX3u\n",
1069 "eP/tvqIWC2kke44wcNYpdAjSccmaW0m8vyPvupoaJc1tZo4fHmP+YukY0+agIBVW53R2MOLqp2Fg\n",
1070 "BMv6Myp6HZOhV6lIptzXdarC3w1h+XI0zx9CoZARGxtFuWTltPZ7smHWrFk88sgjeZc9++yzOX+v\n",
1071 "XbuWtWsngtfXrVvHunXrMn+PjY3x4x//OPP3rl2SYL+9vZ1t27blbCtbkK5QKLj11lu59dZbpxyD\n",
1072 "UqksKF4HWLVqFb/5zW8KLj9RKNri6e7u5vjx49x5553ceeedjIyMsHfv3inrPfPMM3zxi1+cVlEF\n",
1073 "oJ1dXWagzx2qurAy6eoJRYNEYlPv/gLRBCq5gEouQxCETNcqDY8zmNdqIQ1BEGg8iWwXHJ5h3j74\n",
1074 "Jzaedl3O4zajmiRi0aR6URQJZ1kJpMOYc9YZHUSoQLgOqY5VAYqiUvhNFsZjMjrzFbuCZOIniiI/\n",
1075 "37OPq05ZkHf6KhvD/R4am42c09XB+lmd3P7a65m7+kJ04KpWI7uGfMQLePvMlNUCwFC/mzkLGxkb\n",
1076 "9U+xf6gUrrEAdRZpEEEmE7C1mxnqm0hCyO5YRX/9MPIFS1EsXjGtfYLkhq83qfGVcPAuhn53GJtR\n",
1077 "zXUrmrl/00Luumg2NUoZd7/ex3WP7eeetwfptgcyn51OLxXZ6fesGuG6KIp4dh8su2N1eNzJNb/+\n",
1078 "DatszfyvM8/CHig/LzCNdpORXo90LpG1zyqbDhz0RmkyqgsK46F6o1CDWoFcJuAJS50k93iQkQEP\n",
1079 "85cUL9DERILkQA/9Sg3tJqmzVYwKhKl0YNocNJEU8YTjdDV2MuzqI9w//Y5VKff1NEKGBjRjQ4RH\n",
1080 "HB97WGXhwIEDbNmyJUPfJZNJtm7dysUXX/wRH9mHg6KF1a5du1i/fn3m7/Xr1/Pee+/lrLNt2zb2\n",
1081 "7dvHvffem5kIqBbpzMBK4Z1Gx0omyKg3WBnLE23jCcdzcgIXLrcxPODBOSZx725nKK85aDZOJjrw\n",
1082 "N28/wHnLr8SozRUBCoJQkg58Z8DLV57+IGMG2JSPCqwwfDkWjZNIJIsKXCvBoZom5gp+5Hmy7vze\n",
1083 "CHK5jGM94zx/6DDXLl5ccnuDfa6MMegtZ5zOMZebpz+QLj5NrSZG8nyudVolTQYVB+359RkO78yY\n",
1084 "gwYDUYL+KE1tZnQGFa7x4nqQYohG4oSC0RyD3ZbOXJ2VPSBZLSSOHyL+5ouoP/OlaR1/Gq6xAPUN\n",
1085 "OsLBwia8pdDrCtNRO3HsnbU1XL+ymfuuXMC/XyAVWVtf7+W6x/Zz7/ZBDjtDqNQKQqkOpiNFBVaC\n",
1086 "4PEBFAYt6obCHk1pvNrTyw3PPsf/PG01N59+Gq1GTckomHzIZ7lQDgY9hScC05DowOqjbdKZgbt3\n",
1087 "9LN4ZUvJQPDkcD9CbT19gSAd5qzCamSgICU5Z2Ejxz9wEE/d4Dn8MRp1KtzhOAa1gpb6DoadvTMy\n",
1088 "FQgpy4USdKDDHqC+XsP4q9v/6jyspoM5c+agUqm44IIL2LhxI+effz56vZ5bbrnloz60DwVFCyuf\n",
1089 "z5ej1jcajTkGYMFgkLfeeovbb7+dr3/96/j9/oLjlOVAN7uNwLEqOlbTFEBbTLa8k4HuSYWVUiln\n",
1090 "2epW3nurF8ifEzgZTTbjSSFgH3UP8M6hV9i4Or8mplRhdcgRpM8d5s1eqYtRb7TiCY4TjU9cIJIj\n",
1091 "lVGBPq80cDBTae8fyOqYH5saixEOxUjEkyxY2sQLr3dzdkc7Vn1pQ8qhXjctKapPsmD4JFvfepte\n",
1092 "j0cqrPrzF8yS7UL+z3ym4mxGBqRjkMkEGpukaJtq4RwLUFuvy0xMQkrAnqWzGvVHaayRE3ng/6L6\n",
1093 "+y8iGKYf+hyNxAmHYjQ3GYiFplFYucN5u5SCINBZJxVZ91+5kH+/YDZqhYzvvdbLS41m7tsxyEF7\n",
1094 "AIe/8onAcoxBRVHkwV27uf211/nxJRfxqXmSXUujXoUrGCdaIJOxENLidQBZ2ywSfcfLet6gN0Jr\n",
1095 "CUsT4+J5ePdW78A+7IsQiyXY9+4Ay04rjwaUd86l1+OhI9WxEnR6BJU6Y/Q7GXqjhgarnr6jkg1C\n",
1096 "2hzUFYxRp1VgMdlw+e0kFaA0Vp/DmYZFpyvZsbIPeWld3I7zlTdBrkDQfnhGtyczVCoVmzdv5uWX\n",
1097 "X+b555/npZde4rbbbvtQImxOBhR9lQaDAa934gLh9XpzbOG7u7uZP39+ZqTx7LPPpru7e8p2ykVN\n",
1098 "axPRMReJYGV6C5+n8jibbFiMTXnd1z0p4Xo2lq9p5+DuYfzeMKFAtKTw2tpiOimowN/8+QEuOPUq\n",
1099 "9DX5L4hLm/XsKTIZ2OMKc/YsM4+9P4ooishlCskDLJW1KMZjiM6xisaNZyJ8ORsHYzXM903teLrG\n",
1100 "g9Q26Oha1IjzmJfrl+UXX2ZDTIoM97tzNFTz6uv5p1Ur+F9/ehlTvRafN0wkPLUgWNVq5J3+/J+5\n",
1101 "wzOMZQY6VsN9bmxt0mdpsRmwT6OwcjkC1FpyLwhNrSbswz5isQSiKGL3x7D8+XcIOgOKT5RviFl0\n",
1102 "vynD1vpaDYp4kkC0cnoMoNcVoqOEVUi6yPrsSokuvFiRhESS773Wy8tHXewc8HIwiy4shVLC9Wgi\n",
1103 "wW2vvMZzhw/z6BWXs9Q60cmQywQselVR6j0f6mtqiCYSeMIR5G1SZmA5xzvgidBS4nc2nWibtJfV\n",
1104 "ob0jNLWapsRG5UOy5zC0z6bf46XNNHFNKSZgh1w6MD0V6AzFqNMqUciVmNV1ROeW7iKWA0nAXrgT\n",
1105 "nEyKOEZ8zD5vJf6du5A1nFy2Oh/jo0PRwmrFihW8/PLLmb9ffvllVq6cEOcZDAb27duX8YV47733\n",
1106 "MoZg1UCQy9G2txDsKfzDygefJ1yVOWgaDQXCmN2h+JQAZr1RQ9d8Czvf7MFgrkGWh3bKhqmuhmgk\n",
1107 "TtBfvYZkuhh29vHukde5ZFXhTMB2s4ZANIEjkP9kf9wV4prlTUQTIu8NShfxbJ2VODaKUFtfsdXC\n",
1108 "TJiDAiSSIh8EBeaNH52yzD0WoLZey3sBO0qZjPpo6e7EuN1PjU6FVp97fJ9ZshizRs09u3bR2Gxg\n",
1109 "JE9n6hSrjiFfFFeeLsxMaayyg5cbmwzTmgx0OiQ6LhtKlZwGq56RAQ/eSAJb1IX421+hvuHmGesw\n",
1110 "ulOdMp1eTY2YLPjdK4XJVGApCIJAm1HD+joN929aSINWiVGj4Luv9XL9Ywe4b/sgHziKF1meXQcw\n",
1111 "F+hYjQdDfO6Z5/FHo/zi8k/TbJjaPWlJFSOVQBAE2k1G+r1eBKNZ6u6M5496yUa+8OXJ0HbYiHv8\n",
1112 "RJ2VyxbSXlbvl8gFzEay9whjtg6ManWO1lHW3FZcZ3WKlSPddvyROPFEEoNajjMYpy4lJ2iQ1RJs\n",
1113 "n5mukUVXXGPlGg+g1aswd9nQNehIKD/uVn0MCUULqwULFtDZ2cltt93GbbfdRmNjI0uWLOGRRx7B\n",
1114 "6/Uyb948lixZwje/+U22bNnCyMgIF11UuclcNrSz2yq2XJCowOpDaC0mW0GN1eTCCmDF2g72vzeI\n",
1115 "qYyTedqBfeQjpAOf+vP9XLTyGnSawlNIMkFgcZOefXnowGg8yagvSrtZzd8ts/Kr3dIdo9Xcyohb\n",
1116 "+qwqdVyHmZ0I7HOHqdUo0bumFsiucckW46E9++hY2MAHe0t7pQ32TdCA2RAEgX//5Dk82X0QfwN5\n",
1117 "dVYKmcBym553B3K7SEkxybh3ZNoeVmJSZGTAQ3ObdHyWZuO0OlbZVgvZTx2egwAAIABJREFUaO2U\n",
1118 "6MBRX4QbP3gK1SWbKp76LLrfVCdRq1ejSiRx+CunA8PxJOPBGLYKC/T0ZKAgCHgjCT6/2sYDmxby\n",
1119 "rQu6UMoFvvNqqsjaMbXISsbj+PYfwbhk/pTtHhof55pf/4bTW2383wvPR1tgQMKWJ9qmHHTk6KzK\n",
1120 "87Ma9EZoKfH+CDIZhkVz8B2oXGfVbFTTOxYk4Asza37pWCMxmSTRc4QBU31GX5WGrKmlaGFlqtNi\n",
1121 "MKrZf3gMi16FIAiMB6WOFUBtTIe/sTqz2skolRdoH/JibZa6bXXzWwh6q7sx+Bh/fShpt/DpT3+a\n",
1122 "T3/60zmPfeYzE52PSy+9NGNFPxPQdbUTONpb9vqJeJJQMDotO4NCHStPOJ5X1NrUYkKtVgBlWhPY\n",
1123 "JDqwq4yTzkxjcPw4u4+/xQ3nPV1y3TQdeO6k0NQ+d5hmoxqlXMY5s2t56N1huu0BmmonBOzJCsOX\n",
1124 "QdJYTQ5FrhYHRgMsbNJDKIQYiyIoJ7pSrvEAPgtE4nE+efY8nk9lBxbrvAz1ubF15Hf6tWi13HHO\n",
1125 "WWx56TX+KSTndKYaZKZ1Vudl0RJu/xg6jRGVcnrF5PhYAI1WmbFCMJo1xGMJgv7IlA5bOXA6AtSe\n",
1126 "NfVzaOmoZc87/Ri8B6mPeFBectW0jnsyXGMB2rvqqNGpkMcSjFbR1e1zh2kxqvMOLBSD3qDGORYg\n",
1127 "FEsQTXU+BEGgq66GrhRleMwZ5o3jLv7jlV7iSZGzu8ycNauWpvFh1LZGFIbc9+yVnl6++cqr3PqJ\n",
1128 "tWyYO6fo/psLOJaXQq7Oqotk/3FYUdhawBeJE0skqa0pHbKRjrap/8Sqio7JZlAz5Ilw/WltJTv4\n",
1129 "AKJ9GEGroy8ap92YG/8ka2ol9sG+os+fu8jK3g/GsOik35EzGKMtxViYvXLGjdXr9bJh0emKUoH2\n",
1130 "YR8Wm3T8eqsRx66jfCxd/xhwkhmEgpQZWMlkoN8bRqdXl/WDLgSLKb/7ujsUx6TJf8dZ36jHNVbe\n",
1131 "JNZHORn41Fv3c/HKa6dk+uWDJGCf+pp6XGFmpcTBCpnAVUsb+dX7ozlhzGKFwnWYWY3VAXuAU6x6\n",
1132 "BKMZ0ePOWeYaC/LH8V6uX7aEplR24ORIk8kY6nXl7VilcU5nB2e1tfOr8cN5KaNVLUbeHfRlpihh\n",
1133 "5oTrkr5q4tgEQaCxyq6VmBRxjQfyFrgtHWbGjo9g/e1/s+OTn0NQzGwClmssQG2DDoVChqCQYa/C\n",
1134 "JFSiASvvVqc7VpLVgmpKkS0IArPra/jHVTZ+dtVCbj+/C7lM4Nuv9PBPbznZfsGlmc9dFEUe2PU+\n",
1135 "d7z2Bj++5OKSRRXkTtJVgtzJwNKWC5IxqLos+ta4ZG5VlgtaQSSSEJmztLzvdrL3CLLOufR6vFM7\n",
1136 "ViWoQJAKqyP9now5qCsUoy5VOOocCZyqmXHxL0UFSh0riQVQKZJ4ehzEvJUZLX+Mv06cfIVVV1tF\n",
1137 "YcxeTzgnIDcb5Yph6/QWfCE3sXjuiW6y3UIOBIjFkgz3u/Mvz4JUWH34VGD/2FH29m7nopV/V9b6\n",
1138 "XXU1jAdjuCdpg467QnRm0Z4XzqvnoCNATNac07Gq2HXdG0Y/Q4VVtz3AwkYdgrkO0TMxVSSKIsdc\n",
1139 "Lg55XVw6f14mO7AYHRgMRAkGotQ3Fi9Gv3HuWhxCmCd2H5iyzGpQYdIoOJIV1SIJ16dvDjrc787Q\n",
1140 "gGlYmg04RiovrLyeMJoaZd68S61ezRr36xxvXYZ87oI8z54eXGMSFQigrFEy7q7cJLTXFapIX5VG\n",
1141 "dmFlKWG1kC6yblhl48GrFnLDyF52Ns/muCtMNJHgGy+/yu+OHOXRKy+bEuhdCDZDlR0rY1bHqn02\n",
1142 "iRKWC4Pe0sL1NIyL5uGrItrmwK4hzAoBT5lWaomeI8g759Dr8WQ8rNIQGpsQx0cR4/ECz5ZuaiNK\n",
1143 "OTUpba8zGM9Qgdq+AGOJ6RvmQnH3dVEUsQ95aUx1rHA5UM2eg3PbuzOy74/xl42TrrCSNFa9ZU/n\n",
1144 "FNJXecNx/u6RvRxylL57kcnk1OkbGfflZlEV0lgBeF1hTlnezLtvlaYtzXVaScBepTi3Wvz6zXvZ\n",
1145 "uPo6alTl0W1ymcAiq25K16rHGWJWlq2EWiHj8kUWXumRM+4dIZGMS4VVhR0rnyeMYQbE695wHGcw\n",
1146 "RmetRiqsssa1Q8EYezQerlm8CE2q4zJ/sZUP9o0U/I4N9UmFi1CiC1qjVPL3utn85zs76PdMLZxX\n",
1147 "tRrYmaWzcniGaJyRwsqTEa6n0dhswF6FgF0KX85fQCYO7qE1eJzXmj9JY4V2BKUQCkYRRZGa1AWx\n",
1148 "RqfCVVVhFc4p+suF3qDG741kTCbLhSAImHa9z0qznNePjXPDM88Rjsd5+LJLadKXP+LfZFBhD0Rz\n",
1149 "OprlIKdj1dyK6BhFjBYu0IplBE6Gfv4sgn2DJELlF3yiKLJ7ez/t9dqyO3DJnsPIOubQl2W1kIag\n",
1150 "VCHUNiA6Ct/4CIKAok5H3Cmd27M1VsojLvwxH9FY9U7+aehVKsSU+/pk+L0RBJmAzqBGFEWSjhGM\n",
1151 "nziDsVe3T3u/fwsIh8N89atfPaGByNu3b89IldL/Ojo62LEjv7v/TOKkK6xU9bWIIsTKnE4p5Lr+\n",
1152 "zoAXlVzGPdsHyyrS8umsCnWsRFHE7Qyy4sxOeg6N4SsRx5FxYP8Q6cBe+2G6B3ZxwanlR25Afj+r\n",
1153 "HleYWXW57/GnTrGwc9BPjbaLcdcg4ri9IquFRDxJOBSrShM0Gd32APMsWuQyAcFUm9Ox6hka51hN\n",
1154 "gL9fvCjzWGMJOnCod8IYtBSWt9u4yNTO1196mfikk8TqViPvZE0NzkScTTQSxzUepLEpdxDB0mSo\n",
1155 "ysvK6fDnFa6LsSjhB/4Lz3nX4fckpgQwTxdpGjBNURkM1bmv91Q4EZiGzqAm4I/g8EdpqKBoFEUR\n",
1156 "775DtLbqeWT3cc5obeXuC84rKFIvBJVChlmjqHgS0qLT4o/FCESjCAolsuZWkoOFb+4GypgITEOm\n",
1157 "UqLrasd/sPyA5/5jTmQKgS6rriwxviiKJHuPQscc+j3eTE5gznE0t5EsYrkAENco8A24SSaTEhWo\n",
1158 "VZIIhkn6gljMLVPMi6uBIAhYCriv24e9NDYbpO+v3wsKJfXnf4KxV7ZXlbn4twaNRsP3v//9E+pr\n",
1159 "dfrpp/Pss89m/v3kJz+hoaGBVasq0xBWg5OusBIEAV1XW9k6q0LmoNv7vHxutQ1fJM6f+0oXNBZT\n",
1160 "c46XlSiKmQDmyQgFY8hkAqbaGk451caut0ubmjZ9yHTgk2/ew6WnXY9GVZn+ZLKflS8Sxx+demHV\n",
1161 "qeRcsqCBSM1FjPfsRzDX5QjGS8Hvi0xbG5dGtz3AKangZaljNWFs+Xj3QZaq66nXTrwPpejAoQIT\n",
1162 "gfnQ1GriFL8RnVLJPe/mphIsadLT4wzhSwXHzoTVwsiAh8ZmA/JJgeP1jXrc48EpwdCl4HTk11fF\n",
1163 "nvsVMls7tevXo/SGZ7ywco4FqW2Y8DsyGzWEg7GSAcrZCMUSuEMxmqsYXFGq5CgUMsY84YKu66Io\n",
1164 "Yg8E2D44yGP7D/DdN//Ml596ljuuO5//OrAdlUzPF1asKBoVUww2o5rBCulAmSBkMgNBMgotprMa\n",
1165 "9IZLTgRmo9Jom3QuYLNxahhzPoiuMQDsKg1GtTpvQSpNBhY//3viIupYgoEhLwqZgEYhkxzXbY00\n",
1166 "17Yz7Ko8wSMfCrmv24e8NKYmApOOUWQWK/r5XSTjcYJVpId8jBOPH/7wh3zhC1/4UExKT7rCClIO\n",
1167 "7GVOBnrzeFjFkyLvDno5o93EF09v4f4dQwWz29KYLGAPxpLIBOkHOxkeZzATZbPijA72vtNPrISe\n",
1168 "y2ozfWiF1fHRgxwZ2st5y6+s+Llz6msY9kUyxUCPK0yHWZP34nHFYgtjiXkM9xyvKny5lLlquTiQ\n",
1169 "0lcByMx1JN2SM3MkHuf3wz1sbJ46tVeIDkzEk4wOeWlqLbOwajFiH/Ry17nn8Ni+A+wanijWVAoZ\n",
1170 "i5v07Er5fjm80zcHzaevAlAo5ZjrtYzbKxPP5rNaSA72Ef3Ts6g/exMakwYhKSKLzMykVRrpjlUa\n",
1171 "BqMaHSLuUGFtzWT0ucO0mDQVTwSmoTOocbpD6FSw127nuUOH+eGOndzyxxe58vFfs/q+n3Hl47/m\n",
1172 "Bzt2ss/uoF5bw4WCgn896mDbDf/AAouuqKluKUiWC9UI2LN1VoUtF0RRLMvDKhuVGIX6PGH6jjk5\n",
1173 "ZbmNljKnHJM9R5B1zqEvj3A9DUnAPlhwG6Io4gjEWDS3ngN7RjM0YHhQyghsrmtn2FX+ZHkxFHJf\n",
1174 "tw/5MvoqcUzKCBQEgYazT/urogMPHTrEddddx+WXX87ll1/O9773vcyyDRs2cM8997Bp0yYuvPBC\n",
1175 "rr766pxkltdff52NGzdyySWXcMkll/DCCy+wfPnyzPJly5YB0NPTw9lnn81dd93FFVdcwbp16/jB\n",
1176 "D36QWS8SifCtb32Lyy67jE2bNnHDDTcwMCB1JOPxOF/5ylcYGpo61Z8Np9PJc889x/XXXz8j70sp\n",
1177 "zOyYzwxBN7ujbAF7vo7VgVE/TQYV9Tol9Topu+357jEuW1TY7sBitLGv753M38X0VR5nKBNlY67X\n",
1178 "0tJRy/5dg0XN8awtRl7/wwdlvabp4sk37+XSNf+IWln5tJRSLmOBRcf+0QBr2k0cn6SvykZtjZL5\n",
1179 "tR622Q2cWaFwXZoInD4NmEiKHHIEM4WVYKpD9EgC0ucPH6FZqGGRbernnk0HWlsmTvD2YS/mei3q\n",
1180 "QkMLk6A3alCq5KgiAlvOOYuvv/Qyv77qSgypNIJVKTrwE51GnL5RGozTyzAb6vdwyrL8Xa/0ZGD2\n",
1181 "6ykFSWM1UeCIySThn/0XqiuuQ1ZnweEOE9arGe5zY64t7ahdLlzjQeaeMiH01upUGAUp7Dl9oSyF\n",
1182 "SvRV0USCfq+XHreHHrebHreHdzT9jAwf5jmHSKfZxCyzmU6ziXM62ulcbqbDZMKozv2OHnn5PZJd\n",
1183 "Hajkcla2GHl3wMtpbVPprHJgq9pyIUtn1dZFbPc7eddzheIo5TIMeQYTCsG4eB4jT79Y1rp7dw6w\n",
1184 "YFkzKrWC5jKLRKmwkqJsJlstpCE0tyJuf7XgNnyRBHIBlixp5oVn9lM3T/pNhQZG0LRYaapt5+jI\n",
1185 "1IGSalDIfX102Mu6i+YB6Y6VdAwN557O0BO/p+PzlVmTbP3G76d/sGXia98uz2vS7/fzjW98gx/9\n",
1186 "6EdYU8kB3/72t3n44Ye5/vrrkcvlbNu2jSeeeAJBELj99tu5//77ueWWW+ju7uZrX/saTz75JO3t\n",
1187 "7cTjcf7t3/4tZzo1/X+ZTMb+/fu55ZZb2Lx5M6FQiLVr13LllVdis9m48847WbNmDVu2bAGkYu/L\n",
1188 "X/4yzz//PAqFgh/+8IclX8t9993HNddcg0734Zi4fiSFVTAQzXjw5IN2dhsjz7xU1rZ87qlxNtv7\n",
1189 "vKxpn7i43HhaC19/4QjnzalFX+Ak02DKjbXxhOOYC3i/uF1BTFnhyyvXdvKnZ/azbHVbQcGzuU5L\n",
1190 "JBwv+dqni6MjBzg+0s3Nl/5/VW9jabOevcN+1rSb6M2jr8rG+V1yfuqYQ9AyQiX9J593ZqwWelxh\n",
1191 "6lPO2YCksXI7EUWR/35/D6ujdZjzUF3ZdGB2IVIJDZhGU4uJ4QE365d18kZfH//+xpt857xPArC6\n",
1192 "1cBju0cZ941irKlFqaj+sxdFKWZn/afyR6lUOhkYCceJhOM5Jq3x134P8RjK9RsBcPijyOq0DPa6\n",
1193 "Wbhs+sL7NCZ3rLR6NVpRxB6IsoDyTn6THdeTosioPyAVTh5PThE14vfTbNBniqfFFgsNgwLvuwX+\n",
1194 "8x9Op1Zb3ufi23+Ypk9LwfSrWo1857We8l/0JDQbVXQXCOsuhnaTkX12KRNT1t5Fok+KtplsqVCO\n",
1195 "MehkGBbNwdd9FDGRQJAXNtpMJJLseaefTf8o6VUa9SqcwRixRBKlvDARkug5jPIT5xXvWJUwCXUE\n",
1196 "Ylj0Klo6zIR8EdJOcaEBqWPVVNvOm90zU6g06qaahIZDMUKBKLWpa4A4NoLQ1ApA/brV7LvlP0hG\n",
1197 "osjU5f/Wyy12Pkxs376dgwcPcuONN2YeC4fDnHrqqZm/v/SlL2W+d+vWreOZZ54B4Mknn+TGG2/M\n",
1198 "JLEoFAq2bNlSMEvYZrNl/DBrampYsWIFfX192Gw2nn76afbs2cO9996bWd9utxMOh9FoSl9DgsEg\n",
1199 "P//5z3NSZE40PpLCyjUWKFpc6LraCBwprVuKRRPEYglqJm3r7X4PXz+7M/P3rLoazugw8ejuUW48\n",
1200 "LT8VM1m87gkVtlrwOENYWybutlpn1SJXyOg5Msasefm7YoIsLWD3MmteQ8nXVi2e3HYPl53xOVSK\n",
1201 "6rtBS5p03LdDei+OO0Osm1W40FjQZGOB98/8rm0xhQNzpsLnicwIFdidRQMCmanAbf0DKGQCxjGo\n",
1202 "LZBdNn+xlWcnmYUO9rqYs7Aym7+mNhMjA14WLrPx9TPPYNMTT/H8ocNsnDcXm1GNSi6we3Bk2lYL\n",
1203 "XncYQRAKFqSNzQaOduePODn8nXuxnHcm5pWLM485xwLUNWgzNwNJt5PoEw+i+d/fQZBJF9VRfxS9\n",
1204 "1cBgz/i0jj0bYlKUrBbqswsrVcXu673uMJcsqAfglj++yCs9vehVKmaZTXSazXSaTKxpbaHTZKLF\n",
1205 "aEA1qVB4qaeb7tFxTDXlC8+9ew8xb/M/ATCnoQZvOIHdH61Kg1YufTYZ7SYTLxw+Akg3EoJMhuh2\n",
1206 "ItTW56xXKQ0IUnixylJH4NgA+rkdBdc72m3HXKelITVEoZAJNOiUjPqjtBa5YUr2HEH2mS/T+95e\n",
1207 "llnn5l1HqG1ADAURQ0GEmqm/XUcgSqNOiUwuQ2MzIXNLxWl4YIT6dasw17Yx7Cx9/SgHFq2W7rHc\n",
1208 "775j2IelyTDxu3GMolwiFZiqOhP6uZ243tlTsdHqyQZBEFi6dCm//OUvC65jMEwM0ajV6syUXzgc\n",
1209 "niKzKCbqz94OSOL27InBhx56iNra/IbNpfDzn/+c9evX09j44WU5fiQaq1I6EG1XG8HeAcREcd2S\n",
1210 "NK6vyblTG/RECEQSzGnIpa+uX9nMHz4YZ6SAwLLeYMUdGCeRlDQe7iIeVh7XhMYKpC/gyrWdvPtm\n",
1211 "T9HjtdqMjA6duMnAw0N76XMc4dwlny69chHMt+jodYUJROIcL0G3WGtb2dj7Is+OqQjHyx+dnak4\n",
1212 "m257gIXWrMLKVIvocfHQ+7u5Zv4pKBVyNAUunJOnA0VRTDmuV96xGhmQPtcapZKt56/nP958i0Gv\n",
1213 "D0EQUi7snmkL19PGoIXMHi3NRhwjvrwnsMHHXsD55105j7kmWS1Ef/ETFOdcjLx9QpNm90dptBlx\n",
1214 "O4OE82QfVgO/L4Jao8ihW7Up9/VKpuR6XWE6zFIw8UvHe3jl+n/g9X+8jocuu5RvnXMWN5y6jHM7\n",
1215 "O5hVa55SVAGgUWASKFt8HvP4iDo9aGdJ3QmZIHCqzcC7efIiy0GzQc2wL1rxFFlHlsZKEISCAvZB\n",
1216 "T2XC9TSMS+biK6Gzen973xTpQylqU/S6EcNBhMZm+vJ4WKUhyGTIrC0FJwMd/iiW1M10wqJHGJW6\n",
1217 "tKGBETStTdQZGglG/ISilXcDJyOf+/potn8VqZzUhombsYZzT2fslRM/0n+isWbNGg4fPsyLL0rU\n",
1218 "cDKZ5K677mL37t0ln3vttdfywAMPZLRQsViMO+64o6qJyauuuorNmzcTT3mb7dixg+9+97tlPTce\n",
1219 "j/PTn/6Ur3zlKxXvdzr4SAorp6P4F16hrUFVZyY8VDxg1OcJTRGub+/3cFq7acrJsl6r5LJFFn72\n",
1220 "zlSHdQCFXIlJW4fTJ+2zmMbKnaWxSmPB0mZGh3y4xgu/thNtFPrEtp9y+RmfmxbdBJJP1dyGGrb3\n",
1221 "e1HJBcxF7uhrFFqWeOzMqVfxhw/K72r4PDNjDnpgdGIiEEBQazhsqOPwuJPVBmsO3TQZk6cDve4w\n",
1222 "yaSIqUIn76ZWI/ZhL8mEVFgutDTw+eXL+fqLkgXD6lYjB8dkNE5TuC4FLxfWT2l1KpQqOd5JflDh\n",
1223 "ITvhITuBw7mC3myrhfiu7SSOH0J1WW7f0e6P0mRU09RqYqivtBluOXClQrFzjl2vIhmNY/eXV1il\n",
1224 "JwKbDCoGvT6a9DpMmsqKiLhCjr6CE71v/xEMp8xGyJoqWtVqYOdgdTmNWpWcGoUMZ7B8wT6AVafD\n",
1225 "FQ4TikmFbiEB+4C3fA+rbBhT0TaFMG73MzbqZ+6i3M5uKTf5RO8R5B1zEKGg1UIasubCdGCaCgTw\n",
1226 "6dTEvWECvkiGCpQJMppqJ1IhpoPGPFOB9mEv1rRwPeVhJcsurM45/a9CwK7Vann88cd58MEH2bBh\n",
1227 "AxdeeCGJRIIlS5bkXV8QhMxN36JFi9i6dSs33XQTGzduZOPGjaxduzavxmry/ydj8+bNtLW1cfHF\n",
1228 "F7NhwwbuvvtuNm3aBEgFWzHx+lNPPcWiRYuYOzd/d/RE4aPpWJUorEDqWpWiA32e/Pqq0wuISTct\n",
1229 "aWTfiJ+DBXQNFlMzDo9UeBXysEokkgS8YYyTTEmTgojQqmTPO4W1AdaWEzcZeHBgF8POXs5ZMjO5\n",
1230 "jUua9Lzd58lE2RSCODZKQK1gXUeEJ/faS05fpiF1rKYnXveE47hCMdonFdePti7g7ztbCbjCRQsr\n",
1231 "yJ0OHOpz0dJeW1b8RzbUGiUGo4bxrO/VZ5cvRSWXcd97u1hm0zMa0mLUT48KLDQRmA3Jzyr3O+be\n",
1232 "uQ9VfS3+Qz05j6etFsRwiMh/fx/1DTcjqHI/k1F/jEa9itYOKZB5JuAaD075XDQaJclYEnuZU3K9\n",
1233 "rjCtZmkisCeP0WQ5CMllqBPld1m9+w5hXJR7gl7ZYuT9IV/FRp9p2Ezl2RRkQy6T0WowMOCVCjpZ\n",
1234 "2yySeRzYJSqw8psXQ4lom907+lm6qnWK5UezUVXUyyo9ETji9xe0WkhDaG5DHMk/GZht6jr+/7P3\n",
1235 "3nGWHWaZ5nNuzrluqHsrV+fuarVayZJsy5YDNgYMtoXBMJi8w8zujGFnBgYPzM7+ljFhfzPMGGYM\n",
1236 "GAcMOGAbjIxtbFkWSLJCt9ShOlVVd1euujnncPaPc2PdfKvklmZ5/5K6bqi64ZzvfN/7PW++jH3K\n",
1237 "yvLiDnl/GI1HGve4rZMHUlh14lhJqIXq6KrKsBJ0jc+z+e7jZDd3yQcObnx+pzQzM8Of//mf89Wv\n",
1238 "fpVvfvOb/MZv/EYdV/CVr3ylvtkH8NBDD7Vs89U8V48//jjf+MY3eOSRR1rGeS+/LHXQJycnefrp\n",
1239 "p1ue96Mf/SgPPvggIPmzfu3Xfo1vfvObfPWrX+Vzn/scs7NSV12pVPLRj36U8fHOx9bHHnuMz3zm\n",
1240 "MwfwSgynO9Sx6r+irJ+bJH2rd2GV2LMRmC6UuR5Mc7fX2PH2GqWcD97j4X92gYY6TB6CCanyjXdh\n",
1241 "WCXjOfRGddtB5bnNLT4dW2Lx/CblLiMxq00nGR8zB09g/8LTH+NHHvw5FPLhQIXddMpt4FogzXQP\n",
1242 "4zpIUTZpsx4tm7iNKp682f/kK1ZE0sk8+n2OAq8F0hwd07es2wfTaZ7S23if1dixM7JXzePAUcaA\n",
1243 "Nbl9koG9Jpkg8J8ffTN/cfkKS+EQBmGXWGn0wqpUqhDcTeH29t5A65QZGDu/yPj73k5qebXlc19D\n",
1244 "LRS++Cnkx06jOHl32+PV/EPeaStbqwdUWIXSLQwrkDyIGp2SWGIwYvZaTMKAAKzGYkxbhn/fUggo\n",
1245 "huB+Ja8sYzzZWljZ9UocOiU3Bkh46KRxo2ofYcyNzcC9HauKKLKdyOM1Dd+9rrGsOh0ji4USV1/e\n",
1246 "ZuG+ibafeYy9i8RBUAs1ydy+riyrYLpYHwVGskVmjzm5+dwSKrsFmUo6/rmtk+xE9o9c2EtfLxbL\n",
1247 "xMIZ7C7pHFNjWLX87goF9ofPEvrOa38cOKquXr3Kb/7mb9bHd5VKhd/7vd/jHe94xx3+zb43uiOF\n",
1248 "VSqR7wsy1M9Nku6DXEjGchgtjY7K+a0EJ1x6tMru2yyPztvIFSs8vdrudRozjxOKS2OhWBfzejzS\n",
1249 "uhFY05OraySUJZQWFctX/W0/B+nk4Ro/+HHg1fVzBBM7vP7E9x/YYx536Qmmiz2NqACV3S1Kdjv+\n",
1250 "2Cbvv8vF5y76+0IeM+kCKo0SRQdGGEjt9e+uxXl2rffoaa+/CuAvFq/wDqGAKZOUOiN9CqvmceD2\n",
1251 "WgzvPgqrms+qJpdBz2++8fX82299G0XxKmvJwSNP9iqwncDm0KFU9d43GfO0E9hj5xYZe8uDyNVq\n",
1252 "8rsSoLFSEYmFM5hS25Se/TbqD/xi22OVKyKRTBGHXolnwoJ/O9H1omEYRfZsBNakN6gpZosUBniO\n",
1253 "ZtTCWjzOdJ8TdSdFKyJifvAxXGJxGdPJw23/fo/PxPkRv9MSy2pU5EKVZeWdouLfRiw2LtiCqSIm\n",
1254 "tQJNj2NhN6ldDgSZrKMV49rFHXzTVkyW9k52PzO+lBEooRb6dRhl7j4eK4NUQEUyJY4ddxK5vo56\n",
1255 "vFHgeA6oY7WXvh72p+rB4dBgWO3V/yrjwFE1Pz+PSqXibW97G+9617t461vfisFg4Fd+5Vfu9K/2\n",
1256 "PdEdKawsVi3RUO8rPP3sBOmb/UaBrXE2ezELnSSXCfzi/V4+/uIWxT1jgOaOVSxX7OixikWybR4c\n",
1257 "URR5am2NN09PkffKuPRi9y+05LM6OAO7KIp84ZmP8Z4D7FYBaJVylHIBgd5FkujfQuby4o9ucve4\n",
1258 "EbVC4Ltrvf++brR8gEs7KT70t8t88tw2f/DsJn93PdT1ca76WzcCM8Uin79yjQ8Y1IixMNFwuiNq\n",
1259 "Ya+OnHRx7eIOkVAa5/jwJ2ioFlYdTq5vmZ3h3nE3m4UCi4HyyHEXg4wBodaxavwelXyBxNVlzGeO\n",
1260 "oz88RWp5FYBELItWr6Ly6d9H9f6fRzC2/93hTBGjRo5KLkOtUWB16PF3iQEaRrFQ54JXb1BhV8oI\n",
1261 "pvub5KXwZel7uBYbbRQYzpcRRCkmqJ8q+QLpW+sYjrTDZs/6jJxkZCYbAAAgAElEQVTfHM1n5RmB\n",
1262 "vg5Sx2otIX3PBJUKmdNDZbtx3NlK5IbeCKxJEARMC0dIXG7l7omiyIXn1jndhdfnNqnxJwsdL6zE\n",
1263 "TBoxFkbw+Fjv468CKQexstM+WaiIIqFMEYdeRb5UoVCuYDOqcWlLlIyN74c0CjygzcAmn1Vgp9W4\n",
1264 "3sywapbjkfsJP/Ui4iuYhfdqlkql4td//df59re/zeOPP84TTzzBhz/84e8J9fzVoDvyV9rGDH3H\n",
1265 "gbq5STJ9RoHJWOMEXa6IvLAxGKzvjNeIz6zhK1dbT9p7PVadOFbN1PWarofCqORyfvDIYa7KE4T8\n",
1266 "KaKhzj6ug+5YXVl/kWgqxMPHD7bFWq6IlMpiXzNxxb+NdmIef2wTQRB4/2k3n73o71lAdPJX3Qxn\n",
1267 "+fWv3+R3n1rjB447+MMfPsrvvHOeP395l693MMWXKyLLoQzHnI334is3ljjjcTNlk2JtYgN0rEAa\n",
1268 "B8rlMsbcxq5dtL6P4TESCaYodujE/vzCLBm5jnShNNJJFDoHL3eSxa4jky6Qz0nFSfzyDfSzkyj0\n",
1269 "OgyHpklXC6tIMI1VSCHojSgefkvHxwqmCriaMALeKcu+fVaVcoV4LNv2HQLJwG6TCQNtBjZnBK7G\n",
1270 "RutYBTNFNAYVqQE8TqmlVXSTXuTa9mLlpMvA7WgjumgY7Qe5sN4U/C2bnKWycbv+/8NkBHaS6dRh\n",
1271 "koutBvadjTiFfJnpeXvH+2gUMgxqOaEOhXFl/SayiRkEmXygjpWgNyKoVC2B6iAdl7UKGRqFjEim\n",
1272 "iE2rlIjnqgIJWeMz5bFOsHNAhZWzaTOwxV9F946VdsKN0mokcXnweKB/0v86ujOFlVPf18CunXCT\n",
1273 "D0S6Jq2LotjisVoKZbBqFbgHzA37+fvG+exFP4lc42A4ZmrkBXbjWMWj7R2rJ1fXeNP0FAsuJ5dD\n",
1274 "AU6cGe/atXJ5zewewFU/VLtVT3+M9z70C8hlB4sk24rnMWsUXA9me96usruFeeZEve3+0LSZTKHM\n",
1275 "xR5RH8l4I85mJ5HnI0+u8u+/vsK9EyY+/r5jPDpvQy4T8Jo1fOQd83z6/A5/v9RaXK1Gs9j1yjpV\n",
1276 "uiKKfOriZX769AKCxUohGEStUaIagDotCALHz4wzOWvre9tuUijl2McMbcZxgFQmwMPGdWLFME+s\n",
1277 "BEd6/O0NCbXQTzKZgMNlqINCY+cWsdwjsav0h6brHavw6g6m3euSYb2LWd+/h8/kndq/zyoey6I3\n",
1278 "qlF0GFHp9GpMMvoW8+lCmUS+jNuoIlMsEs/ncRuGH7OG0kWMJjXpAQqrxOISplOdN4tUChknXXpe\n",
1279 "3h6+a+WpbtIN28ls9lhBe2bg9ghw0GaZTh0hfqm1Y3Xh+XVO398dggwwblSz0+H1rI0BQeowdkMt\n",
1280 "NEvw+BD3jAODqcZGYCRbxFq9+NUXUoRL6nq0mFlvp1Quksrt/1jbPAr0N0XZQPeOFfzTOPD/z7pD\n",
1281 "HSt9X+SCTKFAO+khs9Z5zp6vFkQ1Fs7z63Hu7zMGbNaUVcvrZyz8xYVGtpvd5CaSDJApFBGha07g\n",
1282 "Xo/Vk6urPDI9hdtgQCGT4TxuZfGlbUodvCJWu45cpnAgBvbLq8+RzMZ48Ojb9v1Ye7UazXLIoeN6\n",
1283 "MN02Mq1JLJcRw35Mk0cplgukc0lkgsBjp1189kJnnxlIHjuFTskfPLvBv/ybG3jNaj7xvuO8+8QY\n",
1284 "qj3U5gmLho+8c55PnNvhiZXG1etezMJ3VtcwqlTc7XEjWGyUwqGBulU1ve7Nczz01v2t5Lp9ZnY2\n",
1285 "2seggdg2R2wWXj9t4wuLG5SHHA+kk3kKudLAf4+0GVgtrM4vYrlHWo82HJoivbSGKIqEvnsex9HZ\n",
1286 "nhmPgXSxvbBai448zgSIhjLYHJ3/Dp1BhQ6x7yhwPZZj0qxGJgisxxNMmExDByEXyxWS+TIms4b0\n",
1287 "AB2jxOIyxhPt/qqazvpMI40DTWqpwEzmhwvPHjcaCaYzFKqsv70G9s14vq8/spfMe0aBmXSBm9cC\n",
1288 "nDzbGxkyblKzHW9/PSury8im5qiIIpuJ/qNAqBnY9xRWTRuBkUypHn9U9AcxznhZXZamEIIg4LZO\n",
1289 "HMg4sEZfr1REQv7kno6Vv2PHCroXVvv5/vyTXjkd5PtyRwor+wCjQAD97GRX5EINtVC72n5uPcED\n",
1290 "Q2Z2/eTdbr61HKm34lUKNXqNifWwH7NG0fFKPhbJYmliWAXSaTYTSe52S1ctC04ntwsSmXflSntx\n",
1291 "IcgEnJ79jwNFUeTzT/9P3vvQLyCTDW9Q7afb0RzzDh3jJjXLoc5dKzEcQDBZkKk1uK0T+GPSQfDN\n",
1292 "c1Y24jmWOmxKpQtlLtyO8rkbEWSCwMffe4yfvNuDTtX9b5i0aPjIO+b44xe2ePKmVFztJa5/8uIl\n",
1293 "PnjXgsRSMduoxKJ9UQvNamawjCrJZ9VeWAUT2zgt4/zygycpltT80fn+gL1m7WzEcPvMPTsFzXKO\n",
1294 "NzYDO3WsSs99h2hegeOND/d8nMCeUaDRrEGpVhDpMuYeRNFQGou98/ui06tQlyt9O1ZrLWPAGFMD\n",
1295 "nKT3KpyRuh1Gk2agUWDyyhKmk90L73u8Js5tJoY+OAuCMNJmoEImw2M0sJmohTG3IhdGoa43S+Nz\n",
1296 "U8nmyAel79vi+U3mj7vQ9on+8ZjUbCfb379aRuBuKoVZ0xu1UJPM7WszsEuFVbVjlSlirxZW2c1d\n",
1297 "Js7Os9x0zD0w5EJ1FBgNp9EZVKg10nN2Ylg1y/rgGRKXliglW78vcrmcTIdg53/SnZEoioTDYdTq\n",
1298 "/WfX1nRHIm1sY3oioQxiRex5spCQC52/GMl4rg4HDaQKhNIFjjqHC1i0apW855STj7+4zX94dAYA\n",
1299 "h8nNRngTi6Z97JLPFSmXKy0ROt9ZXeOhCR/KKtn5lMvJJX+AH753hgsvbHC0Q2CuxLOKM31o9Gib\n",
1300 "W7tXSeXiPHCksz9mv7odyfKmOSvpQpnLuymOu9pf20rVuA7gskhAvln3MZRyGe895eSzF3f5jbdI\n",
1301 "Zt9CqcLfXgvxuYt+7k3l+cW3HeLMwuAk8imrlo983zy/+rUVZILA1UCGxxakA9piIMhWIslbZ6X3\n",
1302 "UDBbkaVjQ3WsDkJun5kX/qGdJxSM73By8l7MWiXzdh2fvXyNh6e8nBowYmF7I874EPmFY24ji+c2\n",
1303 "yW75qRSK6Kal90gz7oR8jvxn/gcx989hd/cuSPzJAvf5Wm/jq44D7WOjbThGwxlsXQpenWEw+vpq\n",
1304 "s3E9Hh8JtRCqruzrjYq+o0CxUpHgoCe6F1YTFumgvBHLMzlgMHRNNWL5sMevqarPatZqRbCNIRaL\n",
1305 "VOJRKkYLgXQBj3F0ULAgCJhOHSFxeQnHI/dz8fkN3vVjd/W937hJxTN7Nq7FfI5KYAeZb4p1f3Cg\n",
1306 "MSBIBvbi0mLLv+0dBdp0SkRRJLfp59Trj/H8x1+mXKogV8jwHBByoTYKDGwncHmavg8dGFbNUui0\n",
1307 "WO4+TviZ87i+7w31f3c6nexubrH5wgXKmSyGwzMo9Fp2t+JY7XrUGgViJk3Fv418ZvAu+o3NC8x5\n",
1308 "TrAeL2HVKjAUcmTXdzCdkjqtkUyRVKHMpEWDKIpc9Ac4bLe1FbliIU9l7SblycNEgmncvtEWekAy\n",
1309 "+xvNmraCPJYOkcom8Dlmib18Ffn8DLt5EYcg2RmGhTSPqtqFkMlkwjCCnaCb7khhpVIr0GgVJOK5\n",
1310 "ni+gbm6C2AuXO/5M2iyT7vv8epx7J0wtPKNB9SMnnfzMF65yZTfFCbeBMfM429FtzNr2oide3Qhs\n",
1311 "7mx8Z3WN7z/c+PAvuJz8t+df5N888ABPPH6tmsfW+sVzeU0du1nD6NrGSyxMP/CKdKtAOnnNWMeR\n",
1312 "CQJfvxHmR0+3X5WJ/m2EamHltjQ6VgDvOGLnLy/4WY1kWQpl+PRLO8zatPzOO+d54pMvMunpzBrr\n",
1313 "pWmblt96xzz/7u+WyZXE+gnsUxcv8RMLJ+vFrWA0Iy9ksVhfubDrTrI7DaQSeXLZYkuMTjC+xZhZ\n",
1314 "Arc+OGXFqDnCv/nmE3zxsfeiH+DKfWcjxn1vmBn49xhzGwkFUkTOXcZy9mT98yoIAnMLBjKTpyhm\n",
1315 "BQx9PDiBdAHnnpOzd9rK1lqMhXvbOUaDKBpKM3e0c56mTq9CLJQJ9MkLXIvmuGtc+vysxuLcOz58\n",
1316 "VFAoLWEk9EYVYX/v7nl2fRuFyYDK1v0EU4suOr+VGLqw8pikaJth1eyzEgShbmDfnTiOQ6fsGYY8\n",
1317 "iKTC6gZJ7xwanRLPACdYj7EdH1HZuI3M40NQqgYyrtckbQa2dqwC6QKzdum4H8kUOe7UUwzHkKmV\n",
1318 "WMZtWB161m9FmDnswG2b5OKtZwf8a7urRl8PdPRX9c4VrY0Dmwur+MtXWfrn/xH7G+7h2P/1r5Dr\n",
1319 "pM/Li0/uoj1hZnrGg5hOkf6tD6H/479pIf13UzqX5JOf/S0+8a//geejAS7sFvnZWSvPvvdDHFn8\n",
1320 "KgBRf5o/++4mH333DE+vb/CnKzf5wpn2Yrn0/FMUzz3FuuU460tZztx7dKDXqZO+/Ilr/MQvva4N\n",
1321 "z5HeCfPFZ/6Qj/zUn3PljT9F5Quf4MVQgR/zGXjhqVu8/xfuH/k5Xw26Y7uPdqehb2agfnaS9M3O\n",
1322 "VxzJWCPO5vmNxFD+qmapFTJ++p5xPlaFho6ZPARi2x2N67Foq78qWyzy4vYOD080TjInx8a4EQpT\n",
1323 "EURO3u3l0gvtHTeX17RvA/uNrYsc8fa/ghxFuWKZULqI16zmlFvPFX+qI1m6sruFzC1BL10WX0th\n",
1324 "pVbIOOM18KHHl/najTC/+sg0/+ltc0xZNSTj+ZFzAmdtWn7sLjflisS62k4meXp9g/cea3z5BZmM\n",
1325 "nEKPTXUw2XaDSlbnlLVesQfjO4xV42zu9ZkIpgTucrn44/Mvd3qYFlUqIv6t+FBXjSq1AoNJw9a5\n",
1326 "5foYEKB8/RJmbZEd8wlsY/qeo09RFNtGgVDdDNyHgT3ahWEFoKtyrAKp3mbuvQyrfrDJTqqNlAxG\n",
1327 "dd9RYDd+1V6d9Ro5N4LPql/GXjft3QyUV6Nt9jsGrMm0cJjE5RsdcwG7abyKj2h+/yprN5HVjOvx\n",
1328 "+ED+KgDB6UEM+xFLjQWjFjhopoRVp6xnBAIcOuFi+Yrkm/VYJw9kM7DesdrZmxHYeSOwWc25gWK5\n",
1329 "zM3/9mle+mf/liO/8S84+bv/rl5UAZhtOmJRaUQo6A0IeiNiaLALcH9sE7d1AkEQmLfrWAlnULsc\n",
1330 "lDM5inHpMzlhUbMRl8KRv3jtOu851rlgKq/dRD59iFi08/buoEon81TKYke0zrhtmp3IKsVslnIu\n",
1331 "T6giw21UMTFjxb+dGAiB8mrWHSusBjGw6+d7jwJNZg25ojSquqcLbX0QvXneSlkUeep2jDGzh3By\n",
1332 "twsctNVf9d3NLU6MjbVklOlVKrwmE0vhCKfu9XHl5e02GKrNrt+XgV0URW5sXeCI75UprNZiOXxm\n",
1333 "KS7EolXi0Ku4FWn3WVX828hc1cLK6sMfk96rGotqJZSlIor86iNTnHRLbdZ8roRMJgy0rddN0WyJ\n",
1334 "tx6y8ftPb/B7z7zEu48extg0H69URDKCDqOs90bjKyGJwN4orIqlAvFMBJtR6tLMO7QkcmUeO77A\n",
1335 "F65eI13sXfyF/SkMxvZWej85PUZ2bwawVgsrsVgg9/H/Smr+dVXYaO+xU6pQRgD0e7xv9jEDuWyR\n",
1336 "1ICE9GYVi2UyqUJHuCRIHatcuoAMsauZO10okyqU66b61REZVo2OVf+twMTiUhtxvZPOeI1c8acG\n",
1337 "Apw2a9w0In3d1GEzcOO2hFo4gIBz08nDxC7cYGc9xtEBx/YmjQKZIJBoev/Kq8vIpuYBJOr6gO+X\n",
1338 "oFQhWB2IocaCUTBVwFmDg1ZHgbWMQIBDJ5ysXAtQqYh18/p+TckGlQqxIkrhy02d9l4bgfX7Hpuj\n",
1339 "kssT+e4FXnzsXxN68jle940/xf39j7Td1mLTEW86zsomZqhsrg70O+5G13FZpQv8ObuWW5EsIqCf\n",
1340 "myBzS7rgNaoVaBQyViJJnt3Y5J2H5js+VmV1Gdn0PLFwpi0TdxgFdyWvcacLOJ3agEFjZmf1BmqH\n",
1341 "FX+qiNuoRqlS4PaZ2bwd6fCIrx3dwcKqv4Fd5bAiFksUIu2G4Bpq4cJ2isMOHYZ9nKhlgsAv3O/l\n",
1342 "4y9sY9Z7SKR3OwYPxyMZzNZGBf+d1TUemZ5qu90p5xiXAgGsdj1Oj7GNxL5fA/tudB2lXI3D1PtL\n",
1343 "PapuR3LMNEXZnHLrudQBn1DZ3aqPAl2WCbbCG3z4GxKL6l3HHPzRe47xrqMOvrjYQAykesBBB9W1\n",
1344 "QJqHpy382pt9fPP2Cift0y0/T8ay5NRG5KmDA7EOqr0E9nDSj9UwVsdhyASBs14jO3G4Z9zDl6/d\n",
1345 "6PZQQC14eXgPkcOhI5oB013HACg+/nlk45Mo73sD0XC2Hr7cTZ26VSB9dsenLGyNEMgcD2cwWbXI\n",
1346 "uozslSo5gkzArVN23Qxci+aYtGiQCQKxXI5SpYJdO/zBX/JYDVZYJReX2zICO8moVjBl1bDYZ7S4\n",
1347 "VwfVsZJGgbck1MIBdKx0sxPkQ1GOHbag7LFcsld7MwMrq8vIp6WT+LAw1+bNwHJFJJot1Q3rkUwR\n",
1348 "u1ZJbstfL6ysdj16g5rt9RhGrQVBkJHI7A8RIggCPrVeKlSacD6DdKwEQcDxyH28+Nj/gf3hs9z3\n",
1349 "V/8drbfzfSw2LbFIw9Qu8023sMl6aTe6gdviA6TPoUmtYCueRzfbGg03adHwxSu3edP0FKYOZm1R\n",
1350 "FOvRQ/FoO7NxGAV3k4z1sHyM22dY37yKymljN1nAVbUdTM/bWV15becs3rlR4AAdK0EQ0M1NdMwM\n",
1351 "lOCgWp7biPelrQ+i0x4jszYtl8J2MtlAR+p6PJqtV/AVUeQ7a+u8qUNhteByctkvxUEs3DfBpRfa\n",
1352 "kREur2lkivWNrYscfYW6VVDzVzVOVgseA5d3W08WYqWMGNpF5vSwm8zz8ZcyxDMx7vKo+Pj7jvGW\n",
1353 "QxKL6kdOOvnWcoRYVjpRJhM5jPs46NfAoEedOi4HN3jA6+WT58K8uNF4LaPhDKLB3AYX/F5ob2EV\n",
1354 "jG/jNLdmBN47YeLFzQQ/fddpPn3pUk/8gkRcH/7zrc/FKXonUei0iOUyxW99BfX7fxbDoSkSeaFv\n",
1355 "x2ovw6pZvikr2yOAQiPh7mPAmnQGFU6VjGCXzUCJuC4V5uvxONNm80jbnMF0AYdehVanpFgo9YzY\n",
1356 "SlxZrhuA++meEbALNp2STKFMdojcQgCvychOKkWxhlzwTlHZ3mAzlsV3AIVVuQJZm4tZ/XCd33Fj\n",
1357 "o1AUSyUqW+tS0VdFLUwMscXZ7LOKZIuY1HKUchnlikiiCnHObu6i8TYuMqVxoHQx6zkgArtP1KNz\n",
1358 "qFs+a4N0rADmPvRBHnj8j5j70AcR5N0LVPPejpVveoiO1QZua2Nce8ih5WY4i35uomXqM2FW8w+r\n",
1359 "u13HgGJUKmgEq4NYeH+jwFrHqpt89hk2dpdRO2zsJvO4q4XV1LyDteXuiRuvBd3RUWA/SCiAfm6K\n",
1360 "zJ7MQFEUSSZyGExqnl9PcP/k8OvWnfRz943z9ZUi2UK6zpdpVqyJYXUlGMSkUnX0dyy4XFyqFlbz\n",
1361 "R52EgynCe7pzrnHzyNE21zdfuTEgwGokx3RTC/iU28DibqolqkIMBxENZv7wfIh/8dc38Jp1eKwe\n",
1362 "7h8vtLCo7Holb5i18NdXpK5VMxx0FN2KZBnTq9AqZXz60mX+9/vP8B/fOsPvPLVWz2uLhtIIZhti\n",
1363 "/HtfWJmtWsqlSn1UFohvM7ansDrrNXJhO8mJsTHGdDq+dXu16+PtrA8WZbNXirVbZEzSAkb5yssI\n",
1364 "DhcyzwTaaS9ZtQFzn3DeQKrYtbAaNZC5F8OqJp1ejU0hI9BlM7A1fHk0fxU0RoGCIKAzqEl3KeQK\n",
1365 "oSjldLbu4emns1XswjCSCQLuKih0GKnkcpx6Pdsp6dgiaLQINgdb0ey+4KA1LS3uIp+eQlwbrjAZ\n",
1366 "NzcKq8rWGoLDiaDRDoVaqKm5sGreCIxlS5g0CuQyoWUUCI3CShTFA8sMdBTVCObWi+1eDKtm6aZ9\n",
1367 "mE/3N4AbzRrSyVw9j3Oowiq2gdva8PrO2XUshzPoZyfINEXDyeVFSmUl93RZ+KiNAStlkXSysXk/\n",
1368 "ivoVVl77DNvxdZROO6EmZp5z3EQmXSAZH95u8GrRHSus9EY15VKlr89IPzvR5rPKpgsolXLWkwU0\n",
1369 "Ctm+QHjNmrBoeGTOSlb7gyhoveqsVEQSsRzmqj/kydudx4AA8zYru6kUiXweuULGybM+Lr/Y2rWS\n",
1370 "MgNH7FhtXuCI9/RI9x1Et6PZujkYwKFXYVDLWY9KH/R0oczf/+NlrgtmBIE6i8pjnWwxsNf02IKL\n",
1371 "x6+FJH9MYnTjOtT4VTq+des2br2BBZeLEy4Dv/mWGT7y5Bovb0nhywqHg8od6FgJgtDStQrGtxkz\n",
1372 "tRZWFq0Sr1nNtUCan77rNH/68sWOPpBctkginmPMNfwacP7iJVAoSSfzlJ7+FoqHHq3+gjIKBguq\n",
1373 "eO8rwm6jQJBimcLB9NAG017G9Zp0BhUmoTt9/SCibMoVkViuAZfUG9VdPWOJK8uYTh4auCt2ZExH\n",
1374 "KFMknBlucWL0aBsTG03jwOLEPNF8uWtRPIwuPr+O7+G72jID+8ljbGw5VpqJ6/HEwKiFmgR3g77e\n",
1375 "AgfNFrFW7Rq5PYWVw2VAJhMIbCdwH5CB3ZCVUWj66NYZVn22AoeRXC7DYNKQiEtdK5l3ksruVot5\n",
1376 "v5v80Y26xwpg3q7lZiiLbg+yaCm6y5jW2vXzXFldQTY1TyKexWDSIB9xs7RcqhANpbH3OHZ57TPs\n",
1377 "ZnbJuN2YNYr6BblMJjAxZ2ftNTwOvGOFlSAIgxnY5ybaNgMTVYaVtA14MN2qmn7ijJu86iz+PSee\n",
1378 "VCKHRqusew2+U42x6SSFTMaxMQdXAlKXZuFeH1de2moZN1gdejLp4Q3siUyUeCbMhGNuqPsNqli2\n",
1379 "SKEs1g9gNZ1yGzi/leRLiwF++vNXKe9uMX9snl96na/uR3NZfPg7XB2Om9Tc7TXx1WshqWO1j0K4\n",
1380 "FrxcA4LWdNJt4D88OsNvPbnK2lYCjcuJGNuft2JUub0NA3swvs2YZbztNvdPmPnuWoI3TU8Rz+d4\n",
1381 "aXe37Ta7m3Fc4yZkQx7cRFEkfm6RMZeewHqI0oXnUD7wCCCNs9WUyN/ufbIJ9BgFKpRynB5jR8p8\n",
1382 "L0VDGaxd4KA16fQq9GJ3+noLHHSI1f1mRbJFzBoFiqrXy9DDZyUZ1wcbA4IU8n6Xx8j5IbtWnTAF\n",
1383 "g0jyWTWNnscP4xZyI6FnmhXYSZCI5Zh729mhC6tmM35lTfLrgDS6Hfb9knkakNB2OKjUQcpu7qKZ\n",
1384 "aBQ4giDUu1Ye28EUVvKkSELTVOCkEqBUImiHY4/1U/M4UFCpEezONkjqXmXyKXLFLFZ9AxE075A6\n",
1385 "VrppL+mbG4iiSLpY5IXt2xRL3ceR5aofLhbOtiWMDKNIKI3JokXZIbqqJq99hkAlTMLmqPurapqe\n",
1386 "t7O28todB97RqOmBkAtzU6T3jAKTVQP0c+sH469qlkWrRF28xDdWWg9yzRmB28kku+k0p93dr1YW\n",
1387 "XC4uBaRxoMWmwzluYqmJXSWrGtgDQ/qsbmxd4ND4wivIr8oxY9W0XdGcchv42PNbvLyd5LffOc9b\n",
1388 "zHn0E60sI2kzsPNB4P13ufjSlQCJeLYtgHkYXQukEWRZYtlcW2G74DHw4Uen2dpJENaZ78goEMA9\n",
1389 "0btjBVKm4jNrMWSCwAdPL/CJC5fabrMzonE9t+VHLJZxzzjYPX8F+eGTCCbpcSKhNCa1SGq5Nzix\n",
1390 "l8cKGvE2wygaTmPtNwo0qFBXOtPXU/kSmWKjG7MWGx0O6mi6cDD0yAsc1LjerLM+I+e3hvNZjbwZ\n",
1391 "aDa1GNi3bZN48vu/oLj4/AYL9/owHp0htxWglBqctu8xNYrE5o3AYRhWNQlWB2ImjZjNVEeBDeO6\n",
1392 "TaeklM5QzuVR2a0t9zt0wsXy1UCVvr6/wiqXLSIWKgTEhv+pEvQjcxz88lCbgX2i/zjQH93AZfG1\n",
1393 "HLPtOiUKmUBMqUWuUZMPhPnGyk3OeBzkyyKpLt1mqRA+VDWu72MjcKf3GBDApLMiq8COXlP3V9U0\n",
1394 "Ne9gdSWM2AHz81rQHS2sBulY6WZ9ZFY3EZsMvslYDpVexVY8z4kORPD9qFCqIC9tsZMSuLTTODhK\n",
1395 "qAXppPDU2jpvmJxA0QPctlAlsNd0+r6JNqbVKOPAV3oMuBrNMtPhC/XInJX/8cNH+b/fNseMTUtl\n",
1396 "d7NOXa9pL8uqWbM2LYfsOnaDmZE7VrFskWS+zNduXuMnT59C3uH1P+nUoytX+NhqiUL4zlzxuL1m\n",
1397 "/FtSvEkwLsXZ7NWsTYsowq1Ijh86cpgLu7vcjrZu2m1vxAcKXt4rKR/wJGMeE4GbO40xIBAJprHa\n",
1398 "tKSXVns+Rq9RINRAoYOfwHPZIsVCuWWrqpN0eom+3qmwat4IFEWRtRHjbJpHSlAdBXbrWF1ZHgi1\n",
1399 "0Kx7fCZe2kq2eBL7adykZnuAaJ292otc2NbY8UR7dzj6KZ8rcf3SDqfu8SFTKDAcnSV5ZWXg+9t1\n",
1400 "StKFMpl8gcr6rfpG4Hp8sIzAZgkyGTLXOJXdrdaOVVYa5eY2/Wi9rrYLQY/PTD5XRFW2sRvd2Bdy\n",
1401 "IbCdwOjQEsw2CisxtItwgGPAmqSO1Z7NwD6F1W5sE4+1nTM2b9exHMqgm5sgc3ODL127wXuOH2XC\n",
1402 "rGE91v5ZExMxxGwGwekhFn5lNwJrsqXVbMlE3HuOC2arFo1GUQ+Tf63pznasBiisFHodSrOR3Faj\n",
1403 "SEnGc0RFuNtr3DddeK9iuRJqtZ2T5mv80fPb9YOjFL5c81etdvVX1XTKOcYlf6D+hZ475iQazrR0\n",
1404 "6EYBhb7SG4G3I41RS7NUchlz9kbBVfFvI7jbC6teRtH3n3aRTubRGUbrWF0NpJmyqji3s8O7jx7p\n",
1405 "eJt4NIvRpOHn3noCMR7l6u5wq+8HIb1RjUotJ+iPkszFWw/fjpQAACAASURBVFr0NQmCwMPTFp5Z\n",
1406 "jaFVKvnRE8f51KVG10oUxapxffiObC0fcExfIZRVorj7dfWfRYJpxiYdpJZXu96/UKqQypex6roj\n",
1407 "TMYnLexsxKh0Cejeq2g4g9XRG0oKoDeoEQtlotlSG5R2tcm4Hsxk0CqVLfyyQSV1rBpFYzfkQjmT\n",
1408 "I7uxg+HQ9FCP7zSoMGnkrHTJ2Oykg0IubJdUeFJ+xNToAOKb1wN4p631JRPTnkDmfpIJAm6jmu3V\n",
1409 "LQSjGUEvnWCHRS3UH686DmyFgxaxaZVtG4E1CTKB+eMutpZTaJRaounRL7ICOwkcHiOBdKPgecU6\n",
1410 "VlYtsabNQHmVTdZLzQyrZs3XNgNnJ7i+fIv1RII3TE4wWQWF7lV5bQX51ByCIEgpI/uIBAvuJvp2\n",
1411 "rAAsERm7ZTpexE0dcrxmsQt3uGNlaNuW6yT93FQLciEZz7GWK49MW++lWK6EXudCW3oZQYAnb0pX\n",
1412 "5TXqerpQ4KVdPw9P9o708BgMCILATnVjRy6XSST2FxuFRy0zcFDli1nWg8vMeU6M8JcNpm4dq2aJ\n",
1413 "lTJicKdt1dhp9hJO+imVO/tjDtu0yCsiz49Y7Fzzp0mV4rzv+NGuUTCxqkH67lknMqWSj3ztKtcD\n",
1414 "o4cGjyq318yNmyvYja6uY9uHp808vSp1qX7s5Am+vnKTSPWqOBbOoFTJR9qgjJ1bxHL2JKaVF0go\n",
1415 "LZRkjdcqEkzjPuols7pJpYspVkIRKJH1KIK0OhVGi5bAgFeUsVB6oOxGnUFFNl3ArFG0GcDX9hjX\n",
1416 "97sRWFM3j1Xy+k0M81PIVINvsdV0j9dU31IdRE6DimimRGHAQrUmn8nIZjJZR3ZsJfL4jMqB+Ued\n",
1417 "tHItwKHjjW6M6dRhEpeXhnoMj0nF1u3Nur9qFNRCTYLbh7izSTBVaIwCs0WsOkXbRmCzDjf5rHb3\n",
1418 "kRkY2E4yOWklmG4cR15VHasmhlWz5mubgXOT/G3Qz7uPHEYplzNh0bAeay+sKqsNQn4smsGyj7y+\n",
1419 "4G5qoMLKuF0kVJK1jQLhte2zuqOFldmmJZXI92TIAG0sjngsy1KywL2+0Wnr3RTPljDrXYQSO/zi\n",
1420 "/V4+cW6bfKlSzwl8dnOLu1wuDKreWzeCILSNAxfu9XG1icRuc+jJpArksoNtEN3cucqEYx618pUJ\n",
1421 "qKyIYktcSDeJkRCCwYSgaf09lAoVFr2dcKLdiA3SAoDWqObzlwIjteYv76a4Ft7gx0+e7HobqTMi\n",
1422 "ncAVVjsfOqXnN/7+FkvB722avHvCzK2N2zjN3q63OebSE8+V2IrncOh0vH1ujr9cvALAzkZ8JH9V\n",
1423 "OZcndf0WpruOIn73W9gsrVl4kVAah8+KesxOdn2n42P0Qi00qxbIPIhqHat+0ulVZKp07b0sK6mw\n",
1424 "agpfHqH7AYOPApOLy0MZ15t11jdcvI1cJjBmULE7ZGagVqnEqtHgr570txJ5vC4L5fX2MPBBVCpV\n",
1425 "WFsOMXukkedoOnWE+KVhDexqtv3R+kbgKKiFmmRuH/ndLRL5MjbtHjhoj8LKN20lHs1i03vZ2Qdy\n",
1426 "wb+TwOezIgLpQnXbMehHNgBqYViZbVLHqnZ8FFxexEgIMde9+7mXYVVTbTNQNePjOwqRHzkmdfkn\n",
1427 "uxZWEmpBFMUqdX20jlUmJZ3T+4GgS5ks5jAki6qOhdXErI3t9RjFIflurwbd0cJKLpdhsWqJhnqf\n",
1428 "9HSzE6RXGh2rSCTDmEPfkY6+X8VzJexGD6H4Didceg47dHz5SpB4NTepG229kxacrYWV2abD5TWz\n",
1429 "tCiZ2CUDu3Fgn9UrGWMDkq9Gp5Rj7EOxr/i3kbk7Fwy9fFbJeA6HTSJvP78x3KiiVBFZCmV43aQN\n",
1430 "l6H7CToabmyeCRYbp3RFPvT6ST78jZss9/mcHaTcXjNbgXXGzN2jQGSCwINTZp5ZlbqWP3X6FJ9d\n",
1431 "vEquVGJ7I8b4CGPAxKUb6A9NIUT8iPEYY1NjBHak1zqbKVAuVdAb1egPT5PuMg7sZ1yvaRgDeyTU\n",
1432 "37gOUscqk8ozple1sazWmjAg++9Y9R8FJhaXMA3pr6ppwW1gJZwhXRj8pDC+h1g+qCbNJtbiCdKF\n",
1433 "MtlihbEJ38gdq41bYexOQ4sXznh0lszqJuXs4L+bNNrM1TtWa0NE2eyVzOMjFIhi1Srq246RTKke\n",
1434 "Z9ONMSaTy5g96kSes4xsYC8Wy8QjGRwuI2PVMGaoMqwGgIOChB4oDRhzpNWpEATqF9uCXI7MM0Fl\n",
1435 "u3thuJdhVZPbqCJbqvCsVokzlqovekyYNWx08FiVq6iFbKaIXC5rCZIfRsHdJE6Pqe/YvxCMYFeM\n",
1436 "ka9o6yPeZqk1Ssbcxn1lk94p3dHCCgaLttHPT5KpjgIrFZF8usg9s9ae9xlV8VwJm8GMTCYjlYvz\n",
1437 "s/d6+atLfuL5Elq9kqfWumMW9urUno4VwMJ9Pi6+sGccuD3YOPCVDF6G9iibbhKbomz2ymWd6FpY\n",
1438 "pRJ5jGYNP3raxWcv+ofqWi0FUxQrOX7m7oWet4uG0liqIyeZ2YoYi/C6KTP/6uEJPvyNm9wMf2+K\n",
1439 "K5fXTCixg93YO2Pt4WlLfRw4a7Wy4HLyNzeWqsT1EYzr1TFg6dlvo3jwzTjHzXUDaCSYrocvGw5N\n",
1440 "dfVZ9TOu1yQZ2GMDvY/RULov7R1Ao1ORz5UY0ykJphqd3ESuRK5UqXea1mKxkTYCoRFnU5POoCaX\n",
1441 "Kbb5xRKLyxiH3AisSaOUc3RMz8WdwbtW+/NZxaVulVmNfGqGyogdq5WrAeaPO1v+TaZWoZ+bJHn9\n",
1442 "5sCP4zaq2CnImzIC40MzrOrP7/ERiGfrJ19RFDvmBHbS4RMu8iEdO5HRCquQP4XVoUehkOHU6wlk\n",
1443 "0g2GlcPZ875iReTKy1v8yf/7D/zVn75YB3/2k8Wma/FZySa6R9tkC2lyhTQWQ2cf57xdy5fDYe59\n",
1444 "8Xp99D9uVhNMF1oyLcVMGjEWRuaZqBrXR5+KBHaSONz92Xv5YATZ2CxyMU423/lCe/qQ4zXJs7rz\n",
1445 "hZVTTzjUh2U1O1lHLqQSOYpygQdmXpnCKpYrYdYoGDOPE4rv4DWreXDcwLrTzOVgELtWh9c02Ajy\n",
1446 "lHOM66FQPXICYO6ok1gkQ6hqYh90M7BSKbO0dZHD3t6FxX60Gs22ENe7/i7+rbaNwJpcFh+73TpW\n",
1447 "CYlh9fppC7Fsicu7g3ufvnx1HYOmxDFH+wGkWdFQYxQoWGz1WJuHpi38ywcn+Pdfv9kxUPqgpdYo\n",
1448 "qKgTaLH1vN2Cx8BWIk+o2p356btO88mXLxIKpHCOD+9HiZ1fxHL2BKVnnkDx0KM4PUYC29LJPRpK\n",
1449 "1zMC9YemSXdBLvRiWDXLZNEgCNLCQC+Jokg0lKkXvL0kkwmotUrsSqGlY7UekzYCa1fBo44CyxWx\n",
1450 "vqrf/JxavaqFvi6Wy9JIdcTCCqrYhSHGgR7j8PR1aCAXtuI5vCa1FN67tYZYGW6EIlZEbl4PMH+s\n",
1451 "fcRlOnWE5BA+K08piV9jQ2aRPv+joBZqEvRGwjobYyqpgE/my6jkMtQKWUtOYCdNzdspRPRsh0fz\n",
1452 "WAW2Ezg90vdwTKcjmM4MxLBavxnmM3/4XS48t873/+gCao2Cbz9+baDnNNu0e3xW3cOYd6uoBZnQ\n",
1453 "+VTuNgrciIW5L5IitylZNBQyAbexFe9RWb+JbGIGQS6XFrX2YVwPVTtW/VQIREiPe9HJM2yFOxeO\n",
1454 "U69Rn9WdL6zG9ET6mIu1Ex7y/hDlXJ6VzTgFpbyvD2hUxXMlzFoFDpOHYELyoDzq1LKpUvD4jcHH\n",
1455 "gCClonuMRlYijVamXC7j1FlvHb3gqq7m99NG6BYmnQ2L3j7kXzS4bkdyzFi1VIol8oEwyRu3iHz3\n",
1456 "Av6/e4qNz/wNkWdfAqTwZZmrHSEA4LZM4O+y7p2M5zCaNMhlAo8tOPncxc5erL0SRZFn1sI8MtPb\n",
1457 "01Aqlkmn8nU6vmCxIcYbr/3rZyz80ut8/PuvrbD6PSiuSqo4ZHsX4Uq5jPsmTDy7JnUtz3rcaAQF\n",
1458 "UVelJ1yvk0RRJHZuEbNDiaA3IJ+cZcxjJLibRKyIhIONrpHh0DSpLsgFf9Xj1E+CIEjjwD6t+kyq\n",
1459 "gFwuoNUNRgPXGVSYZEILcqHZuF6uVNhMJEcyQsdyJfQqeUvsErSPA9M3N1A7bSiMo+Nc7vENF28z\n",
1460 "On1d6lhtxvP4zGoErR7BZEH0d/bQddPuVhyVWtExoNu0cIT4EJuBjuAqYZWRYrULuB6LD41aaFbY\n",
1461 "7sNRkYqNaLaITauQjlPBCGp394sthVLOkdnDBOJbVMThFgNAKqxc1Qscp14qrHptBIYCKb706fN8\n",
1462 "48uL3PfGWX78f3sA37SNdz52ms3VKBee798562xg71x4SAyr7otU/pwfn96FddLbwoOUkAsNn1W5\n",
1463 "iZAfi2SwWEcvrAK7SRwDGNfzwQiJMSc2baVrYeX2mYlHs2RSw38v7qR6m2m+B7KPGTj/9GrP28iU\n",
1464 "CrQTbjJrW1y8XcBobgdYHpTi2RIWjYIxs4dgXDowlZMFHjAo+IfbaX77HcOZWWsG9mNjjS//qXsn\n",
1465 "+MwfPMvr334Ym0NPOpUnly32nGkvbV0YCbNQzuQoROMUo3GK0QSFSIxiJCH9WyRGIZqQfhaJc+XN\n",
1466 "jzH1H/6Cb67dQmk2obSZUNksKK0mBLmc9U98iYee+BSifxuhS2HVy2OVSuSYnJOuYN9yyMafvbTL\n",
1467 "SijDfB/vzQvb25TKan7oeO9NzFg0i8miqZPKBbOVylbrleobZ61URJFf/foKv/2O+boZ+pVQphIm\n",
1468 "H+l/AfDwtIW/uRrkB4+PIQgCj5p8fCO7OvTz5Tb9iOUy8pWLyKrsKq1OhVqjIB7LEg2mOX5Get/0\n",
1469 "h6ZJr6whimLbdymYHqxjBQ2f1Ym7u5v0owOELzdLp1ehRyTQNApsjrLZTqawabVoFMMfvkJ7jOs1\n",
1470 "7S2sRuFX7dWMVUO+VGE7kWd8ACju6KNAqWPl1eS52yud0GSTs1Q2biHztG+LddPKtQDzxzqPt0yn\n",
1471 "DrP1ua8O/Fiy9RXs8qMEUgW8Zs2+PFYAYaOTibx08VHzV+V2AqiddmTK3p+DYycnUe7oCCf8PT2P\n",
1472 "nRTYSXLsLuk+YzodwUwGsRhv2whMJ/M8+8QKS1f83P/GWX7wx8+gUDSKd7VGwbt/8gx/+bHnsTsN\n",
1473 "TMx072RbbLqWIPdekNBuDCuQlpHO765ilc/Xo+HGHpXQK3sN7JXVZeRHTgEQi2TxTo02Zi+XK0SD\n",
1474 "aRzO/qPAQjBCwuzBZYTN8PWOt5HLZUzM2FhbCXPsrs7nnFej7nhhZRvTEwllECsiQo8YBt3sJJmb\n",
1475 "G9za1nJ8bPjstEEVr40CTR5C1Y5VLJLhhEPGt1eVlMvDnYgXnE4uBQL8KMfr/2a2anH7zCwt7nLi\n",
1476 "jLc6rkkwOde9G3V98wInp+6t/3+lWML/1SfJB6PVAilOMSIVSYWIVEgVonGoiCittQLJ3FIsaSc8\n",
1477 "mBaOorKZESwm4peKvPsrv4/OakLYA98sZ/M8ceztlLM5KoGdrh2rGn290wk7Ve1YgcTFes+pMT53\n",
1478 "0c+vPzrT8zX8k/OLqOVOJvsEgkor/Y0TuGCx10eBzXrTnI2KCL/6tZv89jvn+z7uKMoXsxQrWeID\n",
1479 "NOXO+kz87lNrJHJSsKw3riYpFLnk97PgGnzzKHb+Mtazxym9+DS6//yx+r+PeYwEd5KSx6pa4Kis\n",
1480 "JmRqFfndEBpPYwOsUo2TcXYwk3aSd9rKxT5X4dJ4dvDCSm9QoylXCDaNAtdiWe6bkDoHq/HRMgKh\n",
1481 "3bhek2HPZmDy8hKmETcCaxIEgbPVrtUPHh/re3u3UTLslyviUJE0kyYTG4kEE5p8/XlkE7OU12+h\n",
1482 "uO8NAz/OyrUAb//hzigX4/F50jdWqRRLfQsZkE7U476TbCcKeEzqkVELNYU0Vu5KSsfjmr+q10Zg\n",
1483 "s2aOjKH8Oxvr/ltDFVaVikjIn2TMXR0F6vVcDYaoFCP1jlWxUOLc06u89OwaJ+728rO//PquF8hW\n",
1484 "u553vm+Bv/3LC3zgn7+unuSxV2arlhuXG91Gwe5EzGURUwkEQ+truBtd59B4Z3vIi9vbGFQqclk1\n",
1485 "iilfSxjzpEXDi03d1MrqCsq3/wggMRtPnBmtiIkEq1E2qv7d9nwgTPTIYaZtcra2uy9bTM3bWf2n\n",
1486 "wmo4qdQKNFoFiXiu6wcNJOSC/+YWqZyPmaO9fTb7USxXrHusrm28DEgftNuuPIecFf7khW0++m5j\n",
1487 "T8ZPsxZcTj5zebH93++b4Nw/3ubEGS8ur5ndrd6F1Y2tC7znoZ+v/3/0uQvc+E9/gPMdb0RlNWE4\n",
1488 "PIPKZpaKJ6tZ+m+bGbl2sO7e7UgW1+pt9PbOVypyrRrdlI/0SxeQ6w1tqIWatCo9WpWOaDqEzdB6\n",
1489 "Mkkm8i1cpncecfC5i1clb0iX1dxb0ShLoSyv8xj6vubNqAWQOladCiuAR+dtlCsiv/p3K/zO988f\n",
1490 "WJB3TcH4DnaTm9hmlmKh3PNAo1HIOOM18tx6nLcdthPYSvDjbzjBJy5c4r+8/a0DP2fs3CLOWTNy\n",
1491 "1TwyW+O1d7qN7G7GiceyLT4nw+FpUsurLYVVNFPCoJKjUgzmEhhzGUgm8mTSBXRdirHogAyrmnQG\n",
1492 "FWK+RL5UIVsso1XKW0aBo0bZAAT3MKxq6tSxmvr59430HM066zXy1K3YQIWVSiHDolEQTBfaSNS9\n",
1493 "pFepMCiUbMZzeM3S/eSTMxSf/tbAjxELZ8imC3h8nV9XhU6LdsJDaun2QL6zyuoKnruM7CTz7KaE\n",
1494 "kVELNYVkWuxBaZQVrsFB17pvBDZLrVHgMHi5cuMqZw8/NPBzRkNp9AY1ao10mnTqdQQyGcSYHzw+\n",
1495 "Fs9v8vQ3l/FN2/iJX3rdQHiC6UMO7n/jLH/9Zy/xY794P6oOG9gWm45YuGFVEAShzrOSH20tonaj\n",
1496 "G7z+xDs7PtcXr93gvcePcm5VRazgRPj2s/WfTVjUfHFR6liJ+erFsk+yucQio6MWBiWugzQKjJzQ\n",
1497 "8DannYuXexRWhxw8/9Stjhfrr1bdcY8VDJoZOMlLoTxOuQzrKzi+iWVLWKoeq1rHKh7N8nIiwI+c\n",
1498 "GEcll/Gt5cEz6OZtVnaSSVKFVlPq7JExYpEsIX8S17ip52ZgKLFLvpjDY234u+IXruH+gTdz/P/5\n",
1499 "EPP/588y9TPvxfPut+J4432YF46g9blR6LQDfxBvR7JM93ldjScPkXn55a4bgTVJYcyt48ByuUI2\n",
1500 "U2hZ49ap5PzAMQefvxTY+xB1ffriZY7bJzjZIyW9JmkjsLljZaMS7+7/edthO//srId/93crbMUP\n",
1501 "doYfjG/jsnhxOA113EEv1bYDk/EcpWKZH7/7JM9vbbMRH9yjEzu3iLEUaomwARgbN7F81Y/RpEHR\n",
1502 "5NuSDOyrLbcNDDEGBGmlfXzSzPZ6rOtthu1Y6fQqslXKdjBVJJGTiqz6RmB8tCgbqI4CO/x9BpOa\n",
1503 "VKJ6ohHFakbg/jpWIKVDXNxJ1r1G/TTqONBnsiIiYlJL769sYnYo5MLKNT9zR509pwbGU4cHIrBX\n",
1504 "omHEYpHxMQvbify+x4AAoZIC26606SgtHyjI9jGuN2vGO8fNjcFjeaBqXB9vFAk183p6bZ3vPBfl\n",
1505 "8rktfugDZ3jX+08PVYjc/eAULq+Jr//V5Y5ZeEaLhnQy17JF2A0U6u/CsIrn8jy1usYPHD7EvF3H\n",
1506 "ltneAtmeMGvYjOepiCKVjdvIPD4EpYpSsUw2U+zLoOqmQTICayoEIgRFBcc848QyEXKFzhvbVrsO\n",
1507 "mUzom9LyatKrorAaJDNQPzvJZcGAUaxgtLwyhVWhXCFfqmBQyXGY3ATj24iiSCCa5noswoOTE/zC\n",
1508 "/V4+dX6H3ICrs0q5nKMOB5cDwZZ/l8tlnLrHx6UXN/tuBi5VY2yai6T4hWuY7jo22h/aQber4cu9\n",
1509 "ZDpxiMLSUleGVU2Sz6qVu5JO5tEb1Mj2HLjffWKMp1dj9a24ZkWyWb5+8yYK9BwbIBMyGs5ga+5Y\n",
1510 "GU2QSSGWugNYv++InQ+ccfNff+9LXP/C3/d9jkEVTOwwZhrH7TO3+CW66b4JE5d2UqyuRvBMWjCo\n",
1511 "1bzv+FE+fenyQM9XzubJ3ryFbHcVxb0Pt/zM6TZKGYF7TMmdkAuDMqya1c/AHhkgfLlZOoOKTLXA\n",
1512 "C6YLdX9V7fO/uo+O1V7UQk3NHav8rrSF1MsUPagsWiVes5prA9L/pcJq+M1Ah9aMUU39NRJcHin7\n",
1513 "LTPY83bCLOyV6dRhEpf6bwZW1laQT8/jNUtFomRcH72wypUqZMtg9K8ilssNj9WAo0CA44eOEYhv\n",
1514 "DgWbDOwkWjZzZSmRnXiSzMYmh9+4wPt/4b6RkCiCIPCWHzpBKpnnu99pR1jI5TL0Jg2JeBNywTdN\n",
1515 "eWO15Xa5QoZ0PoXV0N4N/eryMg9PTmDRaJi3a1lRGCkEo3UWmU4lx6SWE0gVqKyu1Inr8WgWk1nT\n",
1516 "dpweVMHdwQurVCRBtgJ2vRqPdZLtLnR8QRCkUObl18524KuksOrPslLN+Fh2jCPkSyNX0/2UqPqr\n",
1517 "BEHAqLVQqpQIhSPs6LLc7XGjVyo57tJz3Knni5e7d1n2asHl5LK//fan7vFx7cI2RouWdDJPPte5\n",
1518 "ALi+eYHDe/hV8YvXMJ8+Otwf2EODRNkYTx6qhi/3nnV3Ylkl4zkMHQy8Jo2Ctx6y8VcdXs/PLl7l\n",
1519 "0ZlZ1qJ5jnbYVNqrto6VTI5gNCMmundTAN551MGDNy9x9Q/+su9zDKpAbIsxswe3z8LOAIWVUa3g\n",
1520 "mFPPMzej9eDlD5w6yeNLy8Ry7ZTkvUpcus748TEUd93XtgZuselQquTY97yG+vkp0kutB7NAqjCw\n",
1521 "v6qmXqBQsSISD2eGGwXqJUio06AkkCqwFs22LBnsBw7abRTYHGuTuLKE8eShAxs7SNuBg2EXPCb1\n",
1522 "SJBQvdKAQt6IKBJk8p7bZM3KpAsEdpI9rQgA5gEzAytrUjSK9LcUWEt0Ry2Iosj6zTDLV/xdHy9U\n",
1523 "jViSW2yIwZ3qVmAtJ3AwD+L0+BxFZYS1IU7OgR0JG5BK5Pj6Fy/z1T97GUEmoCDL9P3H9vX5UChk\n",
1524 "/NAHznD5xc2Of7vFpiXewrJqRy74Y5s4Ld6OqIUvXrvBe45J54c5h47lSA7t1DiZ1cZxuRbGXF5b\n",
1525 "qYNc45HBsCjdNOgoUBRFAiUYq0Znee0zXTcDoRZv89rhWb0qCqtBwphvihoskTD5TLFlnHSQqhnX\n",
1526 "QaqSx0we1rZX2TLkW6CgP3PvOF9eDBDJDBZFs+CSDOx7VTOxL1/xM+buTmBf2hO8nA9FKCUz6GYG\n",
1527 "3/jpp9VIru8o0HTiELJUDMHZp7DqEMacSuTrxvW9es8pJ99cjpDINU4M+VKJzy5e4U1Th3EbVej7\n",
1528 "mCGLhTK5TBHTnqJbYln1J/ea1tfQLC1TCB0M5TeY2GHM7MXtMw3UsQJpHHghlK1fBTv1et48M83n\n",
1529 "r1zte9/YuUXG7CKKBx9t+5kgExhzG9vGcfpD020dq0CqgMs4nBfGM2EmsJPs2A1IxHNo9SqUqsHt\n",
1530 "nM0dq0C6yFpT+HKhXCaYyeA1jhZnFUoXcHTAPjTH2iQXl/dtXG/W2SFyA8dNKrZGKKxkqCmJrQW4\n",
1531 "bGKGynr/wurWjSBT8/a+eA/jiUMkr6wglnt3fcqry8im5vAYVewm86xF2wvhUrHM5XObfOq/P8MT\n",
1532 "f3uNb3xpsSsPLZgqMmZQSWHMO1uNUeAQHSun2UtWjHJjcXug24uiiH8rwdpKiE/+/jPoDGp+7pff\n",
1533 "gEuvJaw39GRYDSq9Uc0PfeAMf//lxTrEtyYJEtoYjcl9EiS0Gca7E13H04G4fi0YIp7P8YBPmixM\n",
1534 "WzXsJPJoZyZINxnYJywaNmI5aSOwjlrIvuJRNgDldIakxY6neluffZatcHeo7eS8nc3V6MCQ1Tut\n",
1535 "V0VhZRvTE+5TWL2wkeBoLIBOIxu5TdlPsSrDqqYx8zhrO+vcElK8capRWHlMat522M6nXxqME3Oq\n",
1536 "Gm3TiVB9+r4JLr2wIY0Dt9sPvpl8kp3oOjOuRncqfkHqVh3UFXW6UCaWK3XMa2qWym5BqxMo0Pt2\n",
1537 "7m4dqy5fuDG9igenzPzN1ca49PHlFY6NOUjl5Bx39j+I1bKt9npEpMKq95VOOZOjvLnD2pGTbP79\n",
1538 "M32faxAFqx0r25iBdDJPNtN/vHOfz8g6AvamK74Pnl7gM5evUOhzMkudO49SKCA/dbbjzx955xEO\n",
1539 "nWi9uteMOylnchRjjc/dKKNApUqBw2XoWEA2k/AHlU6vJpMqSLE2qUJLfuV6PMG40YBCNvyhSxRF\n",
1540 "Qpku5nWDmky6gFgRSSwu7Ru10KxjTh1b8TyxATJBx42jdawKRTnxYmvXXzY5S3m9Py195aq/K2ah\n",
1541 "WUqzEbXT1sJD6qTK6grymUNolXJ0KjlrsTSTJmmklk7meeZby/zR7z7F0uIuj7zjKB/8Vw9x94NT\n",
1542 "PP3NzmPGWrajzDNBZWeDcKaIVSMnt+1H4xusY6VUqLAYHFy9sUS5j9+tUq7wwlO3yeeKZNNFfvJf\n",
1543 "Psgb3n5YilhRyAnbh0M29JLbZ+bN7zrGX//ZS2Sa7BASy6rJwG6yICgUiNHGsawbw+pvl5b54aNH\n",
1544 "6ss+KrkMr1lDweNpydydtKhZj2aobK0jm5A2s2ORTM8lsl6qjQEHOS/lAxFSPl894cFrn+7ZsdLq\n",
1545 "VFgdOrY3ek8fXi16VRRWeqOacqnS8+Tz3EaceVkZjVDqepv9Kp5tdKwAHCY3Lwe2sSs1eIyt5ukf\n",
1546 "u8vFM6vxgUCT40YDoiiyk2ofd84eGSMRy6IzqDt2rJa3F5l1H0Mhb5wMEheuYz5Af9VaVOoI9Fvx\n",
1547 "FisV1BpI7vTuwHRiWaUSuZaNwL16bMHFV66GyBbLiKLIJy9c4oOnF7jqzwzkr4qEO2+eCeZWSGgn\n",
1548 "Ja+tSH6jB+5j7fGn+j7XIJI6VuPIZALuASGwlUQOiyhytekzdchu46jdxuNLy13vJ4oiqo1ryO5+\n",
1549 "CKEL22l80tq2tScIAvpDk6SaCOzBEUaBIAXebncYwi6V7gAAIABJREFUBw4aZdOsWsdqzKBs8VhB\n",
1550 "NcpmRL9OPFdCq5CI3XslV8hQqxVkM4Wqcf3gCiulXMZpj5GXtvqPAz0mNdvJwtAh5bFshUAm2nI/\n",
1551 "+cRMXwN7sVhm/WaE2aP9txZBIrD3GgeK6SRiMl5fcBk3qQmkimgzAl/7wiX+9L/8I9l0gR/9+ft4\n",
1552 "zwfvYfqQA0EQuOfhaTZuRTqOzYPVRQbB7aW4s0mpIqJKJJDrdSh0gxcB4/Yp5OYkm7c7Lx+J/x97\n",
1553 "bxrdVmKeaT73Yt8BYiMJgKQoUtRGSiWpVpWrbFelyk7idhxn69hx7KxnZrKMO0v35Ninpyc5Pelx\n",
1554 "lpn0JD2OJ4nt2G2nHTtud2I7Xqpc+14liZIoSpTEncRC7Pty7/y4AAiQWClKVdVn3j9VIkEQIIB7\n",
1555 "v/t97/e8ssyN+TCf+4/PM3d+naGAnR/+qZmmQsONRMS+v1vpR04Oc2h6kP/2pXP1os/uaKavw24D\n",
1556 "ezuG1eubm9zna/bBTroMxF0eso2QULue5VASweWtb3nfyiiwn43AYjhKyjtY3371OQ+wGun8Xh2b\n",
1557 "ePvE27wlCitBEDoa2NcSBTKFCk6HBV3h9mW9JfIKHLQmt22Yy+ks97Sg7Fp0an72pJfPvLzW9X4F\n",
1558 "QVB8VjsM7KBsVU2f9rMVTBNc231QmV87tysfMHFuDus++qtuxnK9ZQTGIshqHamrnblFFoMdSaqQ\n",
1559 "zm0/n1Qyj8XWfoQbsOuZGTLzzStbPLeyikoUuM/vYy6U4UgvHatIawhlJ+RCTckLV7FOTzHwrvvI\n",
1560 "vfQGlfytbQjmihkKpTw2owIB7HUcuL6S4JhZUw9lruljJ0/wN+cutD3Z5lY2GHBU0D/+vr4fq3nH\n",
1561 "ZmAwXeq7YwUwPGpndWn31eROBEYv0mhVyLLMgEbFRrJIWZJxViNoFIbV3o3rrRhWNZksOhLrUQqh\n",
1562 "KKaDnWG0/eq038JrPRRWJq0KvVokmuv9AlKSZTbTJVRima3cDl/Oyk1kqX2HZnlhC++wtWcqvrWL\n",
1563 "z6qyuIA4Mo4gikiSjDOd5ZGQjW996QJOj5lf+u2HePT9x3DuYBFqdWoeeGSCp755Zdf7PJze7liV\n",
1564 "1lZwGDTk10M9jwFrGnKMYPTmWnqagutJvvLXr/CDb17hofccYvK4tyXE01UusmXe2/uvk97x2CHU\n",
1565 "GhVP/pMCyrQNGInvGI0qPqvt4mMztox3xyiwWKlwbSvaBKQGOOg0smJt3gwcsetZSZbqY0BQRoF7\n",
1566 "pa73sxFYCEVJ2p31KcmgY4RIcoNypX1Xd3TC+bYxsL8lCitQkAvtCquXVxLcM2JDsjtRJ29f0nWj\n",
1567 "xwrAZR1ipaLhoUBrsu2PHnGxlizyWg+xFTMtAplrmr7bz+K1CKlkfpeBfX61mbguy7IyCtzHjtVi\n",
1568 "D6gFACm4Dg43yYvtuyegFJI7DezpROeOFcDPnPDy1dkQf/PGBX7+xAzRXJlsqYK/Q0FWU7sTuGB3\n",
1569 "InUrrC4qhdXU5DDpYT/R59/o+vs6KZxYx20bqrfEezWwb6zEeWDUxvNLCSoNa9j3+X1oRJFnlluP\n",
1570 "YJLf/R4qnRbVwf6L7UafVaZYoSLJWHT9RekA+EaUjtXO9fFYm4K3kwRBwGjSYRJkItkio40Zgbdg\n",
1571 "XG+3EViTyaIjen4e8+FxBFX/f4NOOuO38tpqsqdOVL/RNlvZEiaNyIjdzHIDnkMwWxGMZuRIe2P4\n",
1572 "wlyIgz2MAWvqthkoLS5QCRzitecW+as/fhrtcoyIU8sv/fZD3PPweMcC7vhpP/lcietzzcfJcBWR\n",
1573 "IQ76YXOVAaOa/Momhh6N6zUNOUaQjQmuXQ7V36fJeI5vfeUCX/vcaxw6PshHf+MsBw97CG+kWmZ1\n",
1574 "uvMZwvq9m7vbSRQFfvSnZ1he2OLCKyvYBgzEt7JN7xfRP4bUsBm4GVth0NHss726FSVgs+5ihk04\n",
1575 "DVzTO5pGgQ6DmookkQoohZUsyyRiWWx7DGDuZyOwEIkSN9vqhZVGrcVlHWIj1v6ifXjUQTScJt/D\n",
1576 "SP3N1lumsBpwm9qyrF5cTnJvwErRYEYI9pYvtxfFd4wCiyo7RQTOjLU2a2tUIr909zB/+dJa04mw\n",
1577 "lRSfVesDnNVuUFbsLfp6YC5AuVLi+sblJrJufi0IgoB+uPeDYTfdjOUZ66VjFVxHFRgjebH7yvXO\n",
1578 "cWAqUehqapx0GXGbVdzYkvmRyQnmghkOu009wVhjkWwTdb0m0e5ATnQprGbnsc4c4ojHxJXJY4S+\n",
1579 "82zX39dJ4YQyBqyphlzodlLdWI4zPeHEZdJwKbj9WRAEgY+dnOGz5863/Dn51acpBva2pWSeHKtv\n",
1580 "BobSRbxm7Z7ux2TRYTRp6+HiNbV7XbrJaNZSyZfRiAJD1u2T8eIthPmGq9tl7WS26KrG9f0bA9Y0\n",
1581 "bNWh14jciHbf8ByyavsqrNYSBXw2XT0zsFHiyAGk5damYKkWutwFs9Ao6/FDJC9ebdkFi0ezPHU+\n",
1582 "x+cWx1lfifMjP32C0r0u0gMWVKrupxpRFHj4vYd56tvzTT6oUG0UOOBCzKXxairKRmCfHatBR4BY\n",
1583 "bgODUcPiQoRn/vkqn/+Pz2OxG/jFf/UOTt47Uo/DCq2n8LQYaznTcbZUeweddpJOr+HHPnKKZ797\n",
1584 "jUgwjSDQVEQ0dqzyxRzpfJIBS3NxORsKMe3Z/XqODxiYL2uQCkWKMaX4FgQBfyHKmnscUPxvWp26\n",
1585 "JbS0myoViWg4g8vbK8Nqi6jWWPdYgTIOXIu0N7Cr1SK+UQfL19/648C3TGHVbjMwU6xwJZzhlM9C\n",
1586 "Hg3yynLH1vatKJ4vY2+II7gULWErr9RDfVvp7JgNs07Fd7tAQ4973MxFtii3eewn7glQLJbZbBgH\n",
1587 "LoWu4rEPY9Jvv1kT56/sq3FdlmUWozkO9NKx2lxDM36QSjrbdXuucTNQlmQyqTzmHrY5JVUEh2YY\n",
1588 "lSAyF8pwtAd/FSh5dK28Ad08VlKxRPraIpYjEzhNGjZnTrL5z8/27XFpVDixjqehsLLa9UgViXSH\n",
1589 "k2UuWySTLjLgMfPgmH3XOPA9EwdZTCS4HG4eJ8vlMobECtp3vmdPj7WRZbUX43qjfGPN2IVKWSKV\n",
1590 "7Jyo0E4KcqGITiVi129/JhWG1R47Vtnuo8Ds/PV93QhsVK/bgcN9IheUwkrPaDUzsFHiyEGkldYn\n",
1591 "q42VOEaTFnsfW2BalwO1xURuWdmuk2WZlZtRvv6F1/niX7yAmNziwz81xvt+5iTDI3ay5QyS1Hsh\n",
1592 "cuCQC5vDUA+ph+1RoCCKZByDjBaifW0E1jToGGEjtszkMS9f+/zrZNIFfv43zvLgD002FRO5bJF8\n",
1593 "rtjy7+KKRQjJt++0OeAy8d6fmOEfv3wes01PvBG54BtFWl9BlioE46t4bbtRC7PBENOe3X45o1aF\n",
1594 "21KNtrlZOy5X8CVWWTMohVg8msO2xzFgLJzBYtf3FGUDEN9KUhHFpkaG33WAta3Fjj83+jbBLrxl\n",
1595 "CqsBt5mtFiyr19aSHPOaMGhUpDMlDEKF/MZur9J+aOco8PmVEJbKIsVK+4OcIAj88j0KNDTXAT5n\n",
1596 "1ekYNJlYiLY+yY8fclGpSCw1VONXVnf7q5L7PAaM5soIgoDD0EP+V3ANcdCP5fgkycudKcaDDR2r\n",
1597 "bKaIVqduon63Ujib5ZWNBYYtRp5djHM5lOGIp/sHvZAvUyxUWnKylK3A9kVvev4mxhEfKqPSTfNN\n",
1598 "H6Qkqkhd6jzu7KRQYr2pYyUIAoMBe0ef1cZKgkG/FVEUODtq49nFeFNxp1Gp+LnpaT577kLTz5Ve\n",
1599 "e5FsuoLtoQd33mVPMoz5KAQjVHIFhWFl3vvV+E5QaDyWxWLTo+oxHqdRNQO7DBg0ys9nikUyxSIe\n",
1600 "095W3XsZBRZvLO7rRmCjTvstvNYDz2rYqusLubCWLOC36hixWnd3rAIHqLRBLnQKXe4k6/QhYufm\n",
1601 "ufT6Gn/75y/w3X+4xNiki1/+jXu5N/R9bFMT9dtGcwmyxf4uAh9+z2FeePI6hXxJGU/L1MfTcasX\n",
1602 "Xy68p8LKbRsino5w4j4fH/vNB3nPB6dbdtEVr5B114axLMs4IxuEy7dvgQqU4vLMg2OkE3miDR1g\n",
1603 "wWBEsNqRQ5sE4yu7/FUAs6Ew097Wr+mE00hhaLC+1SlvruGXM6zklOOMYlzf2xgwtJnCPdR7GkIw\n",
1604 "VcStbo6oGXYeYC3a3cD+dvBZvWUKK9uAgXRS4WA06qXlJPeNKFeoqXge+6C1icWxn2o0ryfyBa5E\n",
1605 "o3jkPFupzuPHwx4TM0PmlpDLRik+q9bjQFElcnh6iI2GaJD5tXNM+Xcb1/ffX9VbnqAcXEcc9GE9\n",
1606 "Nkmqi89KCWNWPsDpZL4ntsmXZi/xw5MTfPiuIb50bpOFrRxTvYBBq2TvVs+hZl5v14GqjQFrOuI1\n",
1607 "Ezt1itAtYBfCOworgKEuBPaNlXidXzXq0KNViVyLNJtXf+LoYZ5ZXmE9tX1yzn376yTFAVSGvbHd\n",
1608 "RLUa46iPzPWl+ihwr9rZseo3yqZRNUhovixRe1mXEglGbNaeczp3qtso0KQXkTc2sBw+uKf776YT\n",
1609 "QxauhDPku9C/h6tgzV5VywhURoHNHSvVyHjbUeDC5SATR/vzKWXTBdYPnuZrr+SYO7/Ogz80ycf+\n",
1610 "5wc5ee8Iqs0lRN9o02bqSiqOAE2Mum5yD1k4eNjDiz+4QThTxGPS1D/bYbMbdzpMfg+jQLVKg9M6\n",
1611 "SKIQZKDDcSW4I8qmrlQCJxUi2dwtdbR70ZkHxzBbdbzy7M3dPqvVm2zGVnYxrNLFIhupFBMOR8v7\n",
1612 "nHAaiDm9ZKsG9sriAgGbjpW4Mp6OR7N7N6734a8CCBUkvMbmi3llM7D9KBDA6TVTLkvEt27fEtt+\n",
1613 "6C1TWKlUInaHgVhk+w9WkWReXklyT8BKqVimXKpgGxtsWhndTyUaOFbPLC9zxDyAQ+8knOgOlfvY\n",
1614 "mSG+djHcMRNsuoOBHeCehw5QyJdJJwvIssz82vmmjpUsyyQuzGM9ub8bgb0Y12VJUoI6vcNKx6qL\n",
1615 "z8prD9TzAlPJQluGVU25Uon/cvkyHzkxzT0jViQZfNbuYFCAeKQ92VvQG0ClglzrD2LywjzW6an6\n",
1616 "v496TFwaP0roO890/b3tVEMtNGrQb+toYN9YideJ64Ig8OCYjed2bNlZdDo+cHiKL1xQQr3lXAbh\n",
1617 "xmU43Jpd1atMh0ZJX1siVAUx7lUOp5FSSSIZVwrCfsOXG2U0a4kllAN+pqAUIrcSZQPdtwJV0TCS\n",
1618 "faDevdxvmbQqJpxGLmx2TpnoNy9w22NlZSnR7OUTBv3KNm++uUjfCqcpFSt4fb11GcKbKb791Vn+\n",
1619 "6k+eoWJ3Mr38Ij/xsbsZn3LXOzuVxW2CN0BFklhLpZRCMdXfpu3ZRyeYfWWVlfUU7obXbM3oxhbf\n",
1620 "7CsnsFFD1XFgJ4WrxPWdkiJBLNVw80zp9hqoBUHgxD0BMqkCLz21XWzUDOytNgIvhcMcdrnQtFm8\n",
1621 "mHAZWLU66x0rafEao8MDLMeV1yYRzWHbK2phI4Wnj8IqIqsZ2mGx8Q2MsRlbQZLaX3gIgsBjHzjW\n",
1622 "88jxzVL/LrXbqFq0TY2FcTWSxWFQM2jRsRVOY7bpMQ+NNK2M7pfKkkymWKm3nJ9cXOKoxoFs8hJO\n",
1623 "dgeBDlp0+Kw65kJZZoZaBwbPeDx8+WJ7irZtwIhOr+b15xc5dK8RtajGZd0+eGRvrqK2GNG5BpBi\n",
1624 "WxQ+/SmlaNBqEbQ60Ooa/qsFrb7F97QI9a/rubme5ajHiJzLKrdp86GU41EEvQHBYMR6/BCLf9E5\n",
1625 "/mXA4iFdSJEv5kgl8m2p6zV9Y/4aJ7ze+onzl+/xsZrobvSFaseqg0G6BgkVjLtvk5y9yuC/2KaV\n",
1626 "H3QamPWM8s6ba+Q3w+gHe+P7NCocX8NtbYYIDvptBNcSyJK8e8QgyWysJPjhn9z2Dp0ds/Opp5f4\n",
1627 "2JnmAu3DM8f54H/5Kv/DmVPoX3mWrGTEeu+pvh9jo2rIhdDh0VvqWAmCgL8ab2O1G4hFMn2NBxpl\n",
1628 "NOmILGzhMRsJVxMOFuN7N67LsqzE2Rg7jDqXV8g7+z9Z96Mzfguvrqa4J9D+eVh1KiRZJpkvY9V3\n",
1629 "PkRXJJnNdJFhiw6NSnlfJQoF7Hrl8yaoVIjDAaTVRVQT253u65eVMWCnTrUsydy4Gua155bYCqW5\n",
1630 "674RfvG3HkJMJXju//ljZLl5lCMtXmvaTA1mMtj1evw2PevJQk/d55rMVj2nHhjl4vOLuKe2u2o3\n",
1631 "tU4eX3oGuVhC4+j/vTU0MMJGtPP5I7iR5PSDY7u+Loc3Ed1eXCYjoUwGs3bvn5VeNOA2M+Ayce7F\n",
1632 "ZdxeCwePeJTMwNdfYNO1ygNHHm+6/WwwzLS3/fFqwmnkP+ntPFA9f0qLCwz98E8Re6NEviwRj2aZ\n",
1633 "Gdhbmkd4M4Wrx8JKlmWiaj1nXM2312uNWI12JcC+xZizpoOH929x63bprVVYeUxsRbYN7C8tJ7i3\n",
1634 "YQxosekxjQTYeuaVff/dyXwZi06NKAiUKhWeW1nh17RHydqHCSd6I6yf9lt4fS3ZtrA65BxgNZkk\n",
1635 "UyxiavOhHB61c+XCBrIvxSHfiV3By7UxYOXyOZAqaB7/AJQKyIXC9n+LBeRSEeJR5KLydQqF6v8X\n",
1636 "t29bLHDD/5M88t1vk4nfhEIBRGFHgaYUY0gSQjV82XzoANnlNSq5QtsRlCiIuK1DhBKrpJNyx46V\n",
1637 "JMt87sIF/t3DD9W/dnfAyt2B3g6csa0sI+O7mTM11Q3sw83YDLlSIXV5oclTo1GJHHCbUd9/hvB3\n",
1638 "nyfwc+/v6THUlMmnqEgVLIbmzorRpEWn1yhB0TtOMNFIBr1Rg9G8/bc85DaSK0osx/KMNIRjD1ss\n",
1639 "vGMkwN9fvsJPP/s9Nm5mOHzmeF+PcadMk2MEv/UUQf/9t2ReB/CN2VlbinPkxDCxrSxT03srVIxm\n",
1640 "LYlkgcDwAKG0MhZbSiS43985ALyd0sUKalHA2OFKt3hjkYzNs6tg2E+d9lv5Dz9Y7HgbQRDqyIVu\n",
1641 "hVUwXWTAoEFb9bGN2BSfVa2wAhAD40grN5sKq4W5EPe/e2LX/YFSUJ17eYXXn1tEq1dz+uwYU8cH\n",
1642 "61452ehCEEWFJdWAPJCWFtA88qP1fy8lkozYrAr0dA/B0mceHOPF5xaxj21nGF4TB9CE19EHvHt6\n",
1643 "jQYdIyyF2nfbS6UKiWgWl2f3MVyKBBFdg3hMJsLZLONtRm77JfuAkVSywPs/dBdf+/zr/PQv3Y0j\n",
1644 "cIDSN77EpmptF3V9NhTi8YPjbe/PqldTHh4kc2MFSZKoLC6gOzDB8PVNVuP5KnW9/45VNlOkXKpg\n",
1645 "tffW6S0n0yQGXAy1WA7wOcdZ3brZsbB6O+itVVi5Tdyc3zamvbic5NcfUCroVCKP1abHdHCwa6TC\n",
1646 "XtTor3ptY5NRmw1ps8LwgQA3Yq/1dB+nfVb+8qU1Pnqm9fc1KhVTLicXw2Hu9bU+QUwe9bJyI8rl\n",
1647 "+XkOj+8wrp+/gu2EcnCUFuZQnbgH9V339vgMd6siyax+/gKH//2fYKxCGamUoVhELuSrRZjyX4oF\n",
1648 "BIcCnRO1GkwHR0lduY79rqNt778WbZNOOAmMtw94fXppGaNGw5nhvUVFxCIZZu5u/0EU7Q6keJSd\n",
1649 "p9TMwjK6QRcaa/NB9IjHROTkXVi+82zfhVXNX9XqoD8YUHxWOwurxjFg/TELAmer48ARR3Nx8tGT\n",
1650 "M/zaP36LH1tcIJFS9e012Snz5BjX/+zzJO8v10Gce5Vv1MGlN5TR+V4YVjUZzVrymSKTLgP/OKdc\n",
1651 "bC3GE/zMsfbvt06KtAlfblRm7hpF9zSFfBm94fas1E84DSTzleqiQPsidqg6PjvcBY67lijga1ja\n",
1652 "GLHZWEokmfFuFzxKZuD2OCmTKrAVShNoczGyuhTj1Wdv8t6fmME3at/1XhYEAevMFKmLV+uFlVwq\n",
1653 "Iq2v1KNRAJarHcZhi5ZLwc6RZa2k1amRDropXNpAfnQcSYZNWQuCiNm/N/r5oCPAS/Pfa/v9yGaK\n",
1654 "AZep5cKFHA4iDAfwYCSUuf0eH4tdTyaZxzNs5Z3vneLrf/sGP/vLp5FCG+TcCZyW5s7NbCjEb99/\n",
1655 "X8f7HPW7kPR6CnNzCDodos3BiD3OYiRDMV/uaXN7p2pg0F4L3UIoStLpbhmhVgtjPj3xUIuffPvo\n",
1656 "LeOxgu1RIChMnUimWD+wpBJKx8owMkxhM4xU6P8KqJPiuW1/1Q8Wl3jn2CiJaJbA4EhPHitQMsFW\n",
1657 "E3kSHYyaM57OPqshvx2NRkVoQWhJXK93rK5f2RMQslGbqQJ2vbp+FS8IAoJag2A0ITqciJ4hVIED\n",
1658 "qManUB2eQfQ2IASO92Bgt/sJxlZJJfMtN/Zq+uw5BQi61y5BLJJloAPdux1yITnb7K+q6YjXyMXR\n",
1659 "Q0RfeINKtrdxZE3hpAIHbaVBn60Jp1HT+vK2cb1RZ8fsPLu4m2Z+1O1mRC7x7dET2E5P33J3xXRw\n",
1660 "hOzNVZw6sWusUTd5hq3Et7Kkk3nyuVLXEXA7GU1ayvkyRzwmkoUKhXKFpUR8z6iFcJeNQFmWSV68\n",
1661 "hnpstB7GfDskCgKnfBZe7QIVHu6xy7OWVIzrNdU6Vk2/c/QglYZom+tXQoxNulC32dbcXE0wPuXG\n",
1662 "P+Zo+96yTh8iObvd+ZFWFxG9w0qHu6qlhAJzHdqDx6qmsN2IUJZYmAsRy5Ww6dWU9RYs7tZTgW4a\n",
1663 "cnQeBYbagEEBpMgmosuL22QkcgcKK5VKxGTVk4rnOXbKx8RRD//01cuUBwY4ovEiituXiqFMhny5\n",
1664 "gt/aeRw34TRQGBoi//qriFXiesCuZ2k9hc1h2GVT6EX9RNmAUlglrI56nE2jlMKqs4H97aC3VGHl\n",
1665 "dJuIRrLIVdP63QFr/UCfSuSx2A2IGjV6n5fsYvcomX5U61jJssyTi0s8MOhDlmV8ngCRHkeBGpXI\n",
1666 "zJCZN9bbr1TPeD3MdiisnB4T2WIKXWqcIdv21Z9ULpO8eA3rzBRysagcyA7c2lr4zWi+pyibVrIc\n",
1667 "P0TyUjcDu5/N+CrpDnDQy+Ewy8kE7+nQwu6kXLaIJMkYOpiS2wUxJ2evNm0E1nTEY+JSRsB28kjf\n",
1668 "Y+dwYgOPrXU3cihgY2Nld2G1sZpgqIXnZnrQTDBVJJjafYL90MocXzA6sZ0+1tfjayWVUY/odDCS\n",
1669 "v/WAU5VKZNBv49Ib69hbhGL3Kr1Rg1CqMObQ4zRquBFNIyA0jbj6kbIR2P49klvZQGUyYBh0ktlD\n",
1670 "CHI/Ou3rjl3o1cBeM67X1HIzsNqxqpnau2EWQutJvMOdC1jr9BSJC9vRNtLiQv1EXdNyIsGIzdY3\n",
1671 "PqJR4WyJU+8+yNPfmieSLuIwaijKOgyWvQ1bnBYvqXyCQql1xmtoPdnSuA7VjpV7EI/RRCjbfwdu\n",
1672 "L7IPGIhXMwMfes8UgiCwLuiYlJsvxGZDYaY97q4XWRNOI1Gnh/LVufqiwYhdx2YovSfjemQzxbVL\n",
1673 "m31tBG6FoqiQWy4m+Z3dWVZvB72lCiutTo3eoCaZyPNig78KIJXI1U/OpoMjTWj+/VC8yrC6HotT\n",
1674 "liQ86LENGBkwu0nlExTLvR0YTvusHSNuZrweLoTaF1aiSkR0B5GNca5e3L5dZmFJGVvZLEhLC4jD\n",
1675 "AQTdrW0v9boR2Eq9IBcGHYF6x6pdYfW587N8aPp4202WbopXo2w6HVAE2wByvPeOldukRasS0D10\n",
1676 "H6F/7o/CHkqste1YeYethDdTTVTpYqFMLJJteTBXiQL3j9p4fsd2YGX5BvdHNygUyiwcah231K8q\n",
1677 "I358sf3hw/lGHVx8dXXPY0CAZFGiohIwyDJuk4aLoS1G7bY9d+e6Maxqwcsmi47MbexYgeKzemM9\n",
1678 "1TGtYbhH+vpqotAU+dSqYyVY7QhaLfJWmGKhzOrNKAem2pucg2vJrtuCSsdqu7DauREI1Y6VzYrL\n",
1679 "pCFdqJAv9wd2lmWZcLrI9PFBbANGLr+6yoBBQy4Hek1nZEU7iaIKr83HZmy15fdDG61RC7IsVz1W\n",
1680 "HtymOzMKBLA5jPXCShQFfvRnTrCEjGvH4UwBg3Y3dU+4DKxYnBBcQTVaK6z0JKK5nkGx+VyJcy8t\n",
1681 "84W/eIG//+yr+A8McHimdxvH+lYaZxs2ZG0UeLtxFrdbb6nCCpTMwI3NJBc305zxbb/Ba+Z1ANPB\n",
1682 "wL5vBibyZewGdX0MmIzlsTuMiKKKAYuXrWT7vK1GnalCANu9MXwWC6WKxGa6/cp1wbCGwVXgfAN9\n",
1683 "OHFuDls1eLmyMId48NZZVou30rE6NkHq8nXkSvsDnNfuJxQNIgi0jEkIpjM8vbTMTxzZ+3PpJTJF\n",
1684 "aBFrI0uS0rFqQ9k+4jERmjlJ6LvP9UX63xln0yitTo3NYSCyud2tCK4l8QxZ2kI0z47aeXYHhb38\n",
1685 "3PdR3/MwDz83y1ey3UnevSg3NIyrQ6ZcP/KNOqrZjXsvrJZiedCqyWaVUOj5rfieNwIBIpli09r+\n",
1686 "TiUvXcNyfBKzRXdbR4EATqMGj1nDlTbZqEDV8N1bYeVrGLe26lhBLZD5BosLEYYC9rYesmKhTDKR\n",
1687 "x9llg88QGKKSzVMIK58rafEaqh2ohdVkioBV4Y55LVo2++xapQoV1CoRo1bFw++dYum1VRwagWws\n",
1688 "j6a898Jm0DHCZgvkgiTJRIJp3IMtispUAjQaBIMJt8lIOHtnCiv7gIFEA31db9BQHDdiDmZZX96u\n",
1689 "rpQom+4bzE6jhoTbizoTrXcYfTY9pVQBa4eLbFmSWVrY4p/+7jyf+dRTLN+IcvbRCX7ld9/JOx47\n",
1690 "1FcMzmaigEvV+rxhNtjQqnVE052ZkG91veUKqwG3iVcWE0y6jJirL5YsyyQTDYXV+AiZhf0vrGx6\n",
1691 "dZO/qhZG6bYOEukBuQBKC18lCizHW3tzBEHoGMgMsFW+jllnJ5MqEFpXDpKN/irp+hVUE7fOslqM\n",
1692 "9RZl00oamwWty0H2ZusrPwC3bZhMstDWX/XF2Yu879AkNv3e4JbQGyupFX09u7SO2mZG62zNRTri\n",
1693 "MXFFY0XrsJE4N9fz42kFB23UoN/GZkOsyfoTwU6SAAAgAElEQVRKa39VTad8Fq5vZYlXM8NkqUL5\n",
1694 "+e+TtY/ycEXkaizG1a1bj3iIeYcwbfbmJeym4RE7gsCeGVYAS/EcWoOGXFoJ4F2K791fBYrHqpN5\n",
1695 "vVZk34mOFShdq9fX2o8DnUYN2WKlY5pDsSIRzZWaTMAug4FCuUyy0PwcxIACCq1hFtopuJ7EPWiu\n",
1696 "Z+a1kyAIdZ+VXKko1oTRbbBqDbVgqIYBD1t1rPf5dw1nivUuo3vQgnbQimElRmI9gZjuHKnVSYOO\n",
1697 "QEuWVTSSwWTRoWuxiVnbCASUUWDmzowCbQNGEtHmIm5ZlyEglPnGfz5HKpFHkmUuhsIc76FjJQgC\n",
1698 "vhEHSBUEp1KI6dUiFklC0u/+fCRiOZ7//gKf+aOn+cG3rjAUsPOLv/UQ/+JfnuTAITfiHkb9oVwF\n",
1699 "r779+8vnHGdtqzOB/a2uroXVN77xDT75yU/yyU9+kn/4h39oeRtJkvjTP/1T/uRP/uSWH9CAy8T5\n",
1700 "SK5OWwclskQQqL/hTRMjZPd5FJjIlRHFMlejUe4ZHiIRy9Uzzty24Z4N7IIgKNEVHQ6aM14Ps23G\n",
1701 "gcVSnnBmESnqYvpuf71r1WRcX5hrWp3eiwpliWC62OTP6FfW45MkO0S/qFUaHLoAOuPuD1+mVOLv\n",
1702 "567wczPTe/79QE+dkVbm9eTsfMdMuCMeE3OhDJ7HH+w5lFmW5d4KqwZQ6MZynOEOTCOtWuRuv5UX\n",
1703 "lpSfqcxdQLANEL8Zwn3qGB+aPsbnzs/29Pg6adPhQbuyP75FnV7NoN/el+9ipxZjeUxmhb7uMWlY\n",
1704 "T6cYu6WOVefCKnVJCV++Y4VVFwO7KAgMWjob2DeSBbxmbdPCgSAIjNhsrOzKDBynsnyDG/NhDnYq\n",
1705 "rNaSeH29/Z2tM1MkZ+eRNlYQ7E4Ew/bncGkHc2zY0h/0FCCcLjV1GcsHXRQWo2ylRYiHO3bLO2l4\n",
1706 "YJSN6OKur4fWk22N63J4E8GtbEB6TEbCmewdGVfZBozEY81+sPnCJrpCnjOnvXz9C69zPRLFqtPh\n",
1707 "NPZ2kXzYLpNOVpDL238/U7lCulpMl0oV5s6v85W/foUv/Pnz5LJF3v/hu/j5Xz/LqQdGMXbo/Pai\n",
1708 "cEVksMNSi8859t93YTU3N8fNmzf5/d//fX7/93+fzc1NZmd3H8S/+tWvct99ndc8e9WA28RCvsK9\n",
1709 "I9tvcGUj0FD3VxjHA/seaxPPl7kRD3G/34dOrVaYHtWZs8s6RCTZOdamUYrPqn1hNd1hM/D65hx+\n",
1710 "9zjZhMzh6UHmZzfJp7Kk529iOT6JFI8iZzMI3r3xfGpajucZturQ9JA6306WY5NNm0GtNKANgHb3\n",
1711 "yeHrV+a5e3iIgG1vAMmaanE2nSRYbciZFHJDxtdO4vpOTbgMrCQK2N59f88+q0w+iSAImPXtn9OQ\n",
1712 "38bGquKZkmVZ6ViNdKaJK9uBSmFVfu57qM8+QvzVi9jPHOenjh3liZuLt3wFvWR1Iy2t7tvJ4md/\n",
1713 "9V4G/XsvhJZjeew2PdmM0rHayqUZvYWOVadRYDGaoJxMYxgZVkaByf42Qfei414zS7E8qUL7DeJu\n",
1714 "YcxryWbUQk0jNisryd2FVfHGAha7AWuHUPngegJvm+Jip6zT1cJqaaFpDAiwXGVY1TTUZ0wPQChT\n",
1715 "xN2QXRmTYHDETOjexxDsTuRw78fkRo15D3MzeGXX18Mbyli+lRo7ViatFoHbT1+Hqnl9a7uIK5by\n",
1716 "JHIxVMOjnBwTGHCZ+PK33uhpDFjTRH6TRElLbkWZwkiSjKpQ5kYsz/f+6yU+/Yc/4NLra0yf8fOr\n",
1717 "//qdPPK+oz2/J3rRlqhjaKD9xbDfNc5a5L/jwuqNN97gkUe2qdSPPPIIr7/+etNtXn75ZWw2GxMT\n",
1718 "rWFz/SqhUSNUJPwNZudUPNdkftZ5XVRyBUqJ7oGmPf/efJnZ0DrvHBtV/t1g5nNbh3ruWAHcNWzm\n",
1719 "UjBNsY1Zc9rj4VI4QqWFd2d+7RyH/Sdxec1kM0V8Yw7Of/cixjE/aqOhPgYUxFub4i7G8hwY2NsY\n",
1720 "sCbr9KGuYcUWlZeyutlPVpEkPn9+lp8/MXNLv1+WZWKRLPZuo0BRhWCxISe3TeDKRmD7wkqrEjng\n",
1721 "0LMZGKMYitYPQp3UrVsFykgjvpWjVCyTjOcRBKFrjuLdASuXgmnSqQzlV59Hdd/DSmF1ehq7Xs+P\n",
1722 "Hprki7MXuz6+dpJlmTU0qPRaCpv7E3C6123A2uNZiufxDBjJpJV8v3Qxu2ePVaZYQQaMmtafmdSl\n",
1723 "a1iOTSCI4h3rWGnVIse8Zt7o0Nnuthm4umMjsCbFZ7UDuTAcQIiGmJzsDLUM9WBcr8k2fYjkhast\n",
1724 "NwJrqIWaejXjN0pBZGwXw9FsiQlHibTLR9h9BGmjvQ2hkwKug2zGVndtBgbXW0fZQG0jcJsN5jbd\n",
1725 "mXGg3qBBEBTDOEAwsYbbNowYOIC8epPHfvw4V1Nx7OnePU7urWXWVQ4yN5bJZoq88MQCEvDy5RAm\n",
1726 "i46P/PoD/MTH7ubwzBBqzf5Hx0T1JnxD7S8mfT2EMb/V1fHsnEqlsFi2K3ir1Uqi4QO7vr7O+fPn\n",
1727 "eeyxx/btSvfCVg5Prkguu3110+ivAqXdrRjYW48D5XIJaW2Jytz5nn9vLFfkfGiTh0ZGkCSZVCJX\n",
1728 "J8m6bUM9xdrUZNapGXMY2kLxbHodHpOR67HdPoH51XNM+U4y6LMSXEty4p4AF2fDTfwq8Rb5VaCE\n",
1729 "Lx9w3NpWoeXYJMkum4E6HOTlZn/Tk4tLOAx67hrsLwB2p3KZIqIoYDB2b00Ltm0Du8Isuop1uv0o\n",
1730 "EOCo18TcVgH3ow/0NA4MJdZxWzsXViq1iMtrJrierAYvd990M2lVTA+aefG5N1BNHKaQLCCoVOj9\n",
1731 "yt/vIzPTfOXyFTLFvbHd4vkyBo0Ky6Ex0tcW93Qf+6lorowADNj0ZNMFBIoIggqjZm/Qzkg1fLnd\n",
1732 "3zl58RqWY8p7wWy9/eb1ms74LbzaobAasmo7+pLWE+07Vks7DewqNUmNg4mB9vdXLJRJxPM4W1DH\n",
1733 "W8k4HqC4Fae8cAVxrDm4uoZaqGkveYHhdLFpkzOaK2FKRJmobPJc5QiVPRZWGrUWv/MAS6HtY5cs\n",
1734 "y1XMRAeGlXsbxHunDOyCIFR9VkoRGIytMOgIVMOYF9FoVORdAqUbWW5c6c3wrV27ybrWzQt/9wJ/\n",
1735 "9cdPs74cxzJgxOC3c/+7Jzp2NG9VlUqFpNlKoAPgdbi6Gfh2VsfCymKxkGxoKSeTSazW7Tfe7Ows\n",
1736 "4XCYT33qU3zmM5/h6tWrfOELX7ilB/TSSpIJvYpow8aMwrBqLgJM4wGyV69TWbpO6fknKPz9Z8n9\n",
1737 "X/+OzO/+Aplffj+5P/235P7oE0hb3d9sFUkmko9x0OHAaTSQSuQxmLT1at1lHeo51qYmxWfVAbvQ\n",
1738 "YhwoyRJX1y8w5TuB12cjuJZkbNJFLldGmmogrt+ivwpuDbVQk37Yg1wuUwi1N0+LZSPJUvPf7rPn\n",
1739 "L/DRWwCC1hSNZLuOAWtqRC7k10MIoojO25nefMRjYi6Ywf3YWULfea7r7wgn1vHYOxdWsO2zakVc\n",
1740 "b6ezY3aevRFrGgPW/n4Bm5V7fcN87cp8l3tprVC6iMekwVTNDHyztRTLMeowYLRoyWaKhLNptIKB\n",
1741 "THFvnppIpoS7Q/GdunQV67TScdHqFJZdscOIbr902q+gWdpuEHfrWCULTZ39mlp1rLaCaWJ6D7Zs\n",
1742 "+/FZaCOFy2tG1aM9QBBFrMcnkJau11f3a6qhFmrymrWE0yXKHRATOxXOlOp0elmWiWXLqEMRJjwq\n",
1743 "iqKOhet7566NDx7lxuZ2bmsqkUelVjqWrSSHgwiu7QtBj/HOIRfsDkPdwL6xo7AqVircSMT56Afv\n",
1744 "4VtfvchWqP22eTSS4blvvE4hGqOk96DOxPiV332YwzNDDPusLCcKt903FtqMoysWMJraX9Q7TC7K\n",
1745 "lRLJ7N4XFN5sdfwEnTp1iieeeKL+7yeeeILTp0/X//3444/ze7/3e/zO7/wOv/Irv8KhQ4f48Ic/\n",
1746 "vOcHE8uVWEsUOOIyNBdW0QzmYoLSM9+l8OX/l9wff5LR4iyWf/y/KfynP6T86nMgCKjvfRj9r38S\n",
1747 "06e/jumPPotq+jSV+e4jklShTJkE7z5QGwNmm5geAxYPiewW5UrvM/VTvs4G9ukWm4FrkRuY9Vbs\n",
1748 "ZhfeYSvBtQSiKOBavcKq2o0sVajcuIpq/NY7Vjejecb2iFqoSRCErj4rqaBhq7BU//eFYIjNdJpH\n",
1749 "xw+0/ZleFe8Svtz0WBsgocnZeawzU10Lu5qB3fnw3cRfu0g51bn130vHCmqFVZL15URXf1VN9zrg\n",
1750 "nOiicuL+6hiwOR/wYydn+Nz5C5T7QEPUFEwX8Vi0mCdH3xIdq6VYnjGHHqNJRzZdZCmRxKo1Esrs\n",
1751 "zdPSfSPwGtZqx0oQhDs2DgzYdAgCrMRb/65uvqTVRL7lKDBgte5CLlybC6EaHUdead8JCK717q+q\n",
1752 "yXHYj4QKwbr9Pm5ELdSkUYkMGDX13Mde1LgVmCpU0KlFimtBDIFBHrrbwfObDip9srFqGh88wo3N\n",
1753 "7W1fBQza2l+1zbBqKKxMJsJ3cDOwZmAPxlbw2gMKPmN1katbUUZsVg6Ou3n4PYf4+t++Xh8bgtKF\n",
1754 "vPjaKl/69Et8+S9fwhBZRj02geadJ5GjEXR6DfFoFrfbhFoUiOZu7wXF6toW9mxnC48gCFWe1eJt\n",
1755 "fSy3Ux0Lq8OHDzM2NsYnPvEJPvGJT+DxeJienuaLX/xiUycL2Jfg0peuhbjLJjMZPYfh239L7v/4\n",
1756 "PTK/+SHir7yG7umvU5l9FUFvQPPQ4+Qf+kmu689g/MPPYPiNT6L74M+jue+dqAIHEKoBx6qp6Z4K\n",
1757 "q1iuRKYS4101f1UsVzeug7LdZjM6iaZ6Z2scdpsIporEcq1PBq1AoVeq/ioAp9dMIp4nF0thef1Z\n",
1758 "bqznyd9cRLA5ECy3ZiRM5svkShW8txi4C1VQYAefVSEjsZm9jiQrB8DPnb/Az81Mo75FjxhUGVa9\n",
1759 "dqzs25uBydnuY0AAt0mDShSISGoc98wQefKljrfvFGfTqCG/jbWlGOHNFIM9+llM557hoJjhja0y\n",
1760 "8deUjlWjZrxeBs1mvnu9/zgIpWOlVTpWV5e6/8Bt1lIsz6hDj9GsdKwWE3FcRjPhPk7KjaqNAlup\n",
1761 "kiuQXV7DfGis/rU7wbKC6gaxz8qrbTrbXrOWaLZEsbK7eMiVKmQKlZbPy2s2kSwUyDaYq69fDuKY\n",
1762 "PtqUGbhTwfXe/VU12QbN5KXm48hO1EJNQ1ZtRzN+oyRZZitTqtPyo7kSDqOa3OomBv8gB06NYy3F\n",
1763 "mlh//Whnxyq00d5fpTCstAiG7WON23jnWFa2qoEdYDO+wpAjgOBwIpdKXFharINBj5/2c2DKzT9+\n",
1764 "+Tyri1G+/dVZPv0ffsC1yyHufscYv/qv38lxbxHd5BTDx8aRlpUt4JqfOGDTsdIGE7RfWg+nGGhD\n",
1765 "vW/U2z3apuvZ7f3vfz9/8Ad/wB/8wR/w4z/+4wB86EMfahoJAng8Hj7+8Y/39EvlVILKlQuUvv+P\n",
1766 "FD7/5+T+998l82s/zXPf/gGnrjyJI75IsqJD8+j7MPzep8gOTeL++L9B/z/+L2h/7EOo734Qw8m7\n",
1767 "yHRgKIFSWEnz3VfRZ0MRVILAwWpaebyBYVWT2zbUM8sKFGr2yWFzW1bNlNPJSiLZtFlydfV8PR9Q\n",
1768 "pVK8OMtPX8Ax4iYwPsCV5+f3iV+VZ8xhuOVCGDoT2EulCqWShF6vIpYOs55K8fzKKh88cuvPAaob\n",
1769 "gb12rBo8Vt02Aus/Iwgc9Zi4HMrgeexBQt95puPtFTho923NAZeJQr7EgMuIRtub6bT83Pd5YMzB\n",
1770 "MwtbZK4ttTTef+zkCT57/kLf7fxQWhm5mCffGh6rxXi1sDJpyaaLLMYT+CzWvrodjYrsMEE3Kn3l\n",
1771 "BqbxEUTd9vfvVMcKqpaBNtgFlSjgNmtaRhqtJQoMW3WILT7DoiDgt1rryIVUIk88msN9ZoZKQ7TN\n",
1772 "TimFVX8LAkZdhWS4+WS8E7VQU68xPaBktxq1KnRVcG40W1Ko69XCShhwc1/8KV54YqGpQ9Or/K5x\n",
1773 "wsl18kWlYOmEWpAiQUR3sx/0TtLX7QNGErFqYRVbxusIIAgCYmCMCyvLTHu38RnvfO8UKpXAd/7h\n",
1774 "EgNuM7/w8XfwgZ87xcRRLyqVWF00mGDiSAAxlaKczRGPZrEPGBix69vyF/dLG/EcLrp3xd7uLKs3\n",
1775 "BRCa+a2PUvi7v6Zy8yqCexDNj/wUqk/+GbPe4zz4m7+G+hd+i9dMp1Gfuh/BM0QquTtrzjgeIHuj\n",
1776 "83q4OHoQKRJCTnemUz+/skLAsp2z1Miwqsm9F59Vh3gbrUrFpHOAy6HtGJEra+eY8m8HL3uHrYRe\n",
1777 "vIDt5BFm7g5w4Xphf4jrsRxjt2hcr8lyfJLkxdajwHQ1fNnrUMKYv3DhIh84PIVZe+udMlAYVvYe\n",
1778 "O1aifQCpCglVomy6d6xACWS+Esrg/qGzhJ94Eanc+qCgMKzWcNsGW36/UYIo4PXZOoJBGyVtrCJv\n",
1779 "hXjHfUd5cTmB4cgEqhZQ1XeNjZIsFHlto7819FC6iNesRTfkppLLU4rvD819L5JlWelY2fXo9Goq\n",
1780 "5QorsQQH7PbbMgpMXrqK9XjzRtudLKzuGrZwMZhpu0HcrhhZS7beCKxp1GZluTpVuH4lxIEpFyqn\n",
1781 "CwRhFywXoFQsk4hmcfVoXK9JlQqT2EhTTm+PxXaiFurPpQuXq1GNY0CAaLaMU68ivxFC7/MiiCJu\n",
1782 "p56DIwZe+kH/nQ21SkPANcFiSPEltouygSrDytX8ufbcoa1AqHasojmK5QKJTBSXVXkson+Mi1sx\n",
1783 "jjegFkSVyAc+cppf+Pg7uOehA7s8Y5Wb1xDHJhl1mkgMuIheXa5bXwJ2fdux9H4pmCnh0XW/oH+7\n",
1784 "s6zelMLK9OmvYfy3/yf6X/pXaN/7QdQzZ7hUMjA2YMCmV2MbMJBOFiiXKuQyRbRaFZodgY0aqxmV\n",
1785 "yUBho32+maBSoZo4TOXqpY6P543gGocc2x+cnR4rqEJC++hYAXVQaLvib8azPQ6MpkLki1mGB8bq\n",
1786 "3/f6rKRn57GdPMLYpIt8QSJi31tYcaMWY3nGbhG1UJNpYpT8RqjpwFpTOlHAYtXjtftZjCzxD1fm\n",
1787 "+fDM8Rb30r9qqIXeO1aKeb0Q2qKSK2AI9JZtdcStdKwMPi8Gn5f4q61Hy6lcHLVKi1HXGxRz5oyf\n",
1788 "wyd6ewyl576P+v534bEacJWybJ19oOXtREHgoydn+JtzvW/DQnUUaNYqDK6JUdLX3rxx4Fa2hFoU\n",
1789 "sBuULT6DSUs0keOQy36Lo8DWxXzq4jUsx5oLqzs1CgSw6NQccBiYDbY2HQ+1AWsqqIX2F0eNBvaF\n",
1790 "yyEmjngRBEEJZG7hswptpHB6zG2jlVpJlmXkpevIQyOkLi3Uv74TtVB/Ln1sBu6Eg0azJdzFDBqb\n",
1791 "pX5RIQz6uW+kwOyrq7vo5L2o5rPKZYvkc2XsjtYXaS07VndwFGi1Gcgk8wSja7isQ6hEpcudGRph\n",
1792 "o1RmwtEZoVGTnMsixyKIwyOoRIHi8BALb1ynXJYwmLTVwur2dqxCJYFBc/ftXt/bfDPwTSmsWo2g\n",
1793 "XlxOcl9AucpRqUTsDgOxrewu1EKjTBMjXTMDVVPHO/qswtks4UyaKed2OzUe3d2xcvURa1PToEWH\n",
1794 "QaPiRrT1m7Ux2mZ+7TyHfCea/jZen43KjZvYTh5BKOQ4kpnl4uqtb23cjOb2nBG4U6JajXnqAKm5\n",
1795 "3VeNqWQes02P1+HnO4ubPBDwM2zZO427UelkAa1O1TJ+opVqeYE1f1WvY9BJl5HleIF8WcL9WHsK\n",
1796 "ezixjqcLw6pRR04OEzgw0PV2siwrUNAHFJ7c1NI1roy2H2O+f+oQF4IhbrRAebRTMF3EUz3YmSZH\n",
1797 "39TNwJpxvSa1Qc2w1siwVU84s8fCKts+gDl5cXdepMmiv2MdK6iNA1tbBpSO1e7nvd4GDlqTEsac\n",
1798 "pJAvs74c48AhZQNWHBlHWtn9WQ2utUcNtJMcU5ZBjMeONgUyKxuBrUaBvbOswjvgoNFcCUcyhsG3\n",
1799 "XeCIQ36MsVVOPTDKM9/pjH1ppZrPKrSewjNkacteUzYCd3as7hx9XdlW1LO4usSgI1D/+rzVxaFS\n",
1800 "rucAe2n5BqJ/DKF6e+2on7Vra9gHlBD7Ebvuto8CtwQNgz1so7utQ6TzCXLFO9MV3G+9JbICZVnm\n",
1801 "pZUE9zbE2Ay4zURD6Sp1vU1hNT5C5npn86I4NU2lg8/q6aVl/BYXzurVUbFQplQs72qh9hNr06gz\n",
1802 "HbALjdE2Cr/qRNP3bToZIZVAO+KjcmOeIwM55i+GbmkVXJblusdqv2Q9dqilzyqdyGOx6nHZfDwf\n",
1803 "lm4ZCNqo+Fbv3SpAITXHo/WNwF6lVYuMOfRci2TxPPYg4TaFVagHOOheJF27DBot4tgEsiwz8tST\n",
1804 "vC4bkdoc0PVqNT9z/GjPMTe5UoViWcJWi4t6k31Wi1Xjek2yFvw6Mx6TllC6/1FgvlShUJaw6Haf\n",
1805 "fORKhdTcDSzHmlEBd3IUCHDG394y0G4UuJrI4+8wCqx1rBavhvGNOuohuWJgHGmpRWG1B3+VtHgN\n",
1806 "cWyiGm2zbQdQGFa7i7Qhi46NVLGnYmQnHHQrW8ISi6IPbBc44pAfaWONu98xxupilI2V/vALtY5V\n",
1807 "aCOJu81GINQYVs0dqztJXwdlHLi+EWoqrC4JGo7Ggj0Xd5XFa015jo7JUVIbibqf2GPWkipWyO4R\n",
1808 "a9L190syCbWOYU93C4QoqhhyjLL+Nt0MfFMKq505VstxJUiy8Up1wG1iK5IhFc9jaQMsM/UQbaM6\n",
1809 "eBhp5SZyoXUl/uTiEh69s35iScRy2Oy7jd0u6xDhPmJtajrts/J6m6vRgNVKvlwhlMns8lcBZC5f\n",
1810 "Qxr0EQnnkK5fwTp5gJFxJ3Pn++ucNSqcKaFTCfXnux9q57NKVT1WN7IGtHKWGW/3kNBe1UuUTaME\n",
1811 "vQEEgfTsXE/G9UbVeFbWmSnK6WzL91y/HateVXrue2gefBRBEMgtreFKx7AatMyH248h/uXxY/zz\n",
1812 "9etEehhVNI4BAcxv8mbgUlzxV9VUUEl4NSacJg1b2RKVPjhIoHSrXCZtyw5ldnENrdOOxtZ8Ur2T\n",
1813 "o0CAQy4jkWyJrRYesmGrtuX4bDVR6FJYKR2rhbkQE0cbujwjB9p0rBJ9bwRKiwuIoxNYp6dIXFA6\n",
1814 "Vq1QCzUZtSoMapFotvuF4U44aCxXxhDdwuBvKKwG/Uibq2i0as4+OskPvjnfVwfJ5zzAVmqTjdVo\n",
1815 "x27dToZVTXeKvg6KgT0STjQVVhcTSY4XM8hb7e0wjZIWF1A1EPJ9x8cpZ4p124soCPhtOlYSt6dr\n",
1816 "Fc4UMeWyGD3dO/WgvD6rb9PNwDelsPrVf/om8fz2i/fScpJ7dxCoBzwmoqEMqUSu4ygw26VjJej0\n",
1817 "iIEDVK7vzobKl8u8tLqGSe3AXk32TjRkBDbKafUSTQWRpP6q+ZkhM3PhDPkW5lRBEJj2uHl1bZmN\n",
1818 "6BLj3mZjeuLcHNpDEwTXklQW5hAPHmHmngAX9rhiDFXj+j75q2qyHj9E8tLuwiqdKGC26vjmzQiu\n",
1819 "4vl9bZsr/qreCytQkAu5K1ex9Whcr6m2GSgIQnU7cDcstJc4m34ll0uUX3oa9QPvBqiDQc+O2Xh2\n",
1820 "sf3V+YDBwHsnJvjSxc7eQoBgehvCCLzpLKsaHLSmFGUcog6tSsSqU7XFl7STshHYaQw4uevrJquO\n",
1821 "TJ/xK7ciZYO4dWd70KIjmC42FZTJfJmKJHe8OBo0m9nK5bh2NcTBww3mZt8o0uYacnn771gqVohH\n",
1822 "s7i8/Y3pK9WMQMvhcbKLq1TyhbaohZqGrbqONPmawpkSbnOzx0oTDjcXVkN+pI0VZFnm2CkfhUKJ\n",
1823 "a5eDPT9+lahm1H2I9dVo+yibFgyrmjx3dDPQQCpWwGvfLqxmQyGO22xIq715kaSl5uihgycmABXm\n",
1824 "hsaFshl4e977wXQRW3wLXR+F1duVZfWmFFaPHBjjf/rmt8lXN6x2jgGhOgoMpzt6rIzj3T1WUMMu\n",
1825 "7PZZvbS6xhG3i2xJwGZQDlLxaG4XagFAq9Zh0duIZfrLUjNpVUw4DcxutDanzng9PHPjCge8U2jU\n",
1826 "zQbbxPk5bHcdJrgar2cEjk04yeVKbK4mWt5fN92M5jmwhzFgPldic63177QcGSc9f3PXxlwqmWe9\n",
1827 "nCVZLOOQ10jl9k5K3qlYJIPd1fsoEACzDSGbwjge6H7bBtVAobIsVynsu7ELvcJB+1Hl/CuI/rH6\n",
1828 "Qb2WD3h2zM5zi4mOherPn5jm7y5dJtdlVBGudqxqMoz5KAQjVHJ3rrCoqbYR2Ni5jkp5LCifTbe5\n",
1829 "/3Fgp43AVsZ1AINBQ7FYply6PSORVjrjs/Bqi862Ti1i16ub/GVrVeJ6J5+gWhRx6wzITjVma0Mc\n",
1830 "mFaH4PYirW9fnIU3Uwy4zaj7MK5DbRQ4iajTYjo4QnruRlvUQk29sqx2bwWWINhcWAlmK6jUyMk4\n",
1831 "oijwzvce5ulvX+0LGnrAfYxMstw+xqcFw6qmO8uyMlLICAxVO1ahTIZ8uULAF0BaXez683KxiLSx\n",
1832 "iugfq3/N5B2gbLaRKW6/HreTZbWRyGMJB9E6ezPb+5wHWIv8/x2rnvXx++5lyGzm33zvCeK5Ite3\n",
1833 "cpwcan5jO90mopGsMgpsV1iNDpNfDyEVOx9s2xnYn1xa4l2joyRy5YZR4O6NwJpctv7CmGs67bN2\n",
1834 "8Fl5uRAM1vlVjUqcm2Po7AmSN1dAEBCcHgRRYOZuPxde2VvX6mY01zdxvVQs89XPvsZX/uqVlswY\n",
1835 "tdmEfshDZqF5hJRK5Plvywt85MQ0Q3Y/wfjesr1aKbbVf8eqLKuwjg/2HWDtMWsQUK64nGfPkLp4\n",
1836 "jWK0ucgMJ9Zx9xBn049Kz34PzdntEPQaGHTCaaAiKV65dhqz2zk5OMh/nW9PxYeacX27sBLVaoyj\n",
1837 "PjLX7/w4MJItoVOLWBs6McFSDl1FeabW7DoAACAASURBVL08Ji2hPg3snTYCk5eutcRuCKKAyawj\n",
1838 "s8ctxL3otN/K62vJlqPOnT6rtTbhyztllzToA7svolQjB5vGgXshrsupBHI2g1DNz7MeP0Rydr7t\n",
1839 "RmBNvbCsKpJMLFeuv275UoWyJFNaD6L3N5vIxaEAcjUzcGzShcNp7Asa6tUfBn267TakFN7tr6rp\n",
1840 "TtLXzTYNQsGIqwogng2Fmfa4UQXGWm557pS0ehNxyF+HZ4MyMSnanETXti0ut5NltR5O4chlEDW9\n",
1841 "2VD8rvG3bRjzm1JYiYLAv3/kXcTyef7XJ97gxJAF7Y43tlanRm9Qk4jlsLYprEStBv2wh+xS52JH\n",
1842 "degYlYU55Mr2Fagsyzy1uMxDYyOkCg2FVYuNwJrctmEie/FZ+dvH20x73KxkJSZ3GNcLkSjlVBbf\n",
1843 "mSn0oRsIB7YjWKZP+5mf3aSQ79/Evhjrr2NVqUh84z+fY8Bt4tDxQV5+qvUVhPV4s4G9UpHIZYq8\n",
1844 "FNnkxw5PKSyrfSqsZEkmEe2/sCpmK5h9vbWhGyUIAke8StdKZdAxcPYUkSde2H48skw4uYHb2hs+\n",
1845 "oRfJmTSVi6+hvuchAMrZHJmF5fpGY7dxICgxN589f4FKh5ibGsOqUaZDbw5yYSmWZ6TBX5Uvl4lU\n",
1846 "csgF5fG7zf1FokCXUeDs1ZYdK6gZ2G/vhlSjPGYtdoOG61u7qdQ7o226GddBeU9qkjKSY/chXgwc\n",
1847 "QFrePmHthbheWVTGgLWLFOvMFInZ+bYMq5rabTk2aitbwqpXoa5u6W1lywwYqtR1X3ORIw75kDa2\n",
1848 "C6mH3zPFC09e7xkaaqwMkxbW2n5farERWNOdhIQWVXH0shNRUJYwZoMhZrwexMBYTx2r2qJBoyoV\n",
1849 "iYreRLxh6jNyG5EL69EMTqn3z6/X7mcrGaRYvvPd81vVm7YVqFWp+LP3PM6VUAFJbN3NGXCbyGaK\n",
1850 "Ta3snTKNj5DtMg4UzFZElwdp6Xr9a3ORCHq1GrfBgkGz/SFu57GCWhhz/x2rCaeRaLZEpMXVtlmj\n",
1851 "QlXJoDGONn09ee4Ktpkp1BoVo2KYrHs7W89k0TFy0Mnc+f4eS1mSWUvkGekRDipLMt/+6iyCKPD4\n",
1852 "B47xwCMTXHhllVQLc6Pl+GRTtE0mVaCigQ8ePYxJo8FrVyCh+6FkIo/eqOmZWl5TNprBMNDn+LCq\n",
1853 "Ix4Tl4PKQdTz+DuafFaJzBZ6jQG9tr9Cr5PKLz+N6vhpBJPSyU2em8N85GCd4VMbB3bSqcFB7Ho9\n",
1854 "Ty62L5JCDaiFmsxvUhjzzjHgciKBzaonVzV1e6pBvv1IGSnt7lgVQlvI5TL64dYLFXfawA5w2mfh\n",
1855 "1Rbbgbs6VkmFut5JoY0UDnRsVXY/B3FkHGl5+1gYXNvLRmCzX8c6PbXdseo0CrR0Z1ntfM1iuRIe\n",
1856 "WXnd1TsWDRQD+3Zh5Bq0MHHEw0s/uE4vKia0JFgik2994Su32Ais6U6OAmO5dQTEesE4Gwox7fEg\n",
1857 "Do8oAOFK57F1ZcfrBcpEQSNITXaaYZuOzXSxr7DsXhVMFfFoeh/TqlUa3LZhNqLd7T5vNb2puAWT\n",
1858 "VotBZePZ1Sv8c4uMM4tNj0YjdoTWGQ8GyCx0/8OLU8ebsAtPLi7xrgOjxPPb3SpZkltS12ty24b6\n",
1859 "hoSCYk49Ndy6a7UUvoZTneF6ovlKNXFuDttJxczuKWwSNjSPmWbuDnDxtfZXWq20lsjjMmnR9+Cl\n",
1860 "kGWZJ795hWQsz/t+5iSiSsRi0zN9xseLT+4+aO2MttkMp4hR4EPTChDUaw/sW8cqvpXB0a+/Ckiv\n",
1861 "b6Ez7G0b8ojHxJVqMLj70QeIPPVyfQQdTm7g6SHKph+Vnvs+mgcfrf879mpzPuBRj4lottRxrCII\n",
1862 "Ah87eaIjMHTnKBDePOTC4g7j+mI8gWfAQjatPEePSds3y6qdxyo5q/Cr2vmUTJY7a2AHZRzYyjIw\n",
1863 "bNE2Gb7XumwEAlyfC3HU79kVxgxV5EJ1fFQuVYhtZXB7+yOuS4vXEEe3OyCWYxOk52+yFG+NWqg/\n",
1864 "lx5YVuFMCc8OOOhgNq5E2ex4vcShANJm83Hl7KMTzL661hM0NLSRwu7WcDO4e7kJOnes7iR9fTO+\n",
1865 "gtpYJhHNIckyF0NhjnvcCHqDkhsY6nyRLS0uoBpt7lglollsZjWsrtdH0FqViNukZT2x/+/9UEHG\n",
1866 "0+fx1+96e0bbvKmF1eVgmmGrjr/4kUf53556htc2mosWg0GDSt0ZfmY6OELmRveZumoHz+rJxaq/\n",
1867 "Kl+uG9cz6QJavbrOfNmpvcTa1HTab20JAby6ep4ph7UOCq0pcW4O68kjyOUS5uQ6K6Vmw9/I+ABb\n",
1868 "oXRfOVk3Y/me/VUvP3WD5RtbfOAjp5qo9/c8PM7Vi5tEI80HlBpyoWao/v6VG5is/x977xkkSZ6e\n",
1869 "9/0yy/vq6i7T3k2P7TG3M3u3t+bs3u4dSZAiDlIAojlAIkMUFCFBISok8INIhb5QIQkgGcEIMoIC\n",
1870 "CZIhARBBEQQPvD2Pu9vbWzO7Y7fHtLflvTeZ+pCV1WWybPdM7yn4fNrtnu6u7qrKfP/v+7y/x4Tf\n",
1871 "rhRA/lP0WMVH2Ais5vLkwml08mjcmfMTVrYTRUpVCZPXg215jvjP7gAQTh4MFL48qKRoCGl/G931\n",
1872 "FxsfS7YVVjpR4OV5F2/3GQe+vrhAJJ/nI42Ym3Yvi6qzQi7stDGsdlIpZsdd5HMK++g0R4Hph09x\n",
1873 "aGwEqrI5ny/LCpQN4vVYgVwbR2jKaWrc6GRZVuJs+nSs1j8OcevSNLvpzq6mMO5FLpeQUkoQuGfC\n",
1874 "ht4wGGRSVW1nvWW0pLdaMM1NcpBOa6IWVLnMemqSTKYHiy+S7YSDjmcSLcb1xu8SaB0FAtidZl54\n",
1875 "ZZ4ffbu3v1CqSURDWebmJ1sCmZsla1DXVT3PjlUouYfNqSMVz7OTTOEym/BYlEOIONPbZyVXq0j7\n",
1876 "24hzrakdyXgBj8/OeCzcglh4FqDQck0iIwn4hlycmh5f5PDn0Gd1poXVu7tpXppzcck7wf/6+pf4\n",
1877 "b7/1HbYSxzcKg1EPfVb0bcuzA24GriI9eYgsy4SyOQ7SGT41GSBVqOJuZlj1eOK9rimiIxZWL0w7\n",
1878 "+Ogw0wF2fHRwh0/PLbQUVrIsk7r7CNf1i0i7m8gTAQ4irTcUnV5kas7N/vbghO3teGEgf9W99/e4\n",
1879 "9/4+v/SrtzBbWm9KFquRm68u8PZ3WoGgZv8Egl5P8TBMuVbjvfV9zk9PND7vH5shmBwdE9GsZCyH\n",
1880 "ewg4KEDm4TqGyWlID/73apZJLzLvVkChAL43XyX8lgILjaSPThW1UP3p99F/5nMIeuVvL8uyYly/\n",
1881 "2RoH9MqCm7d3eo8DdaLIN64rXqt2RXMVxiz6xhhclW15jvzOftdcxGchWZbZbWNYbSdTzI+7MRh0\n",
1882 "lIpVZRQ4RF5guSqRr9RazPCqMg+e4uzir4KzGQWa9SKXvDbuHLYewKZdClizXJWIF6oYdSL2Loc/\n",
1883 "UK5jmVSRG+enCWVzlNvGREqA7xLS/jahHuHD3SQXcsiJGOJk63Zt5VOXcSJ0RS2oP7vdM9auTjho\n",
1884 "FWdKu7AS/VPIkWDHKOzFVxc42E5wuNv94BGP5XE4TZybudi1sJIiQU2GFTxf+nowscfYuI1koqBg\n",
1885 "FnzHI2xxprfPSjrcVRaf2jYbk/E8E3MTuKJh1iPHB+VZt/nUWVaRbBlXrYTFOz7U102PL7L/c7gZ\n",
1886 "eKaF1c/2UnxmVpnHvzo3y2+89Gn+i2/+yTHYUIByHwqsbWlusFHguA+MJuSjPX64s8Nrc7PoRbFl\n",
1887 "FJjs4a8CGHcEiGaCI72RfHYjLrOe9SZzqizLPN6/w5fP32InlSJfX40vHoZBljFP+6mtr2G8cIVU\n",
1888 "PE+l3Hqjm130sLfZGajaTdsDdKyePAzy9nfX+aVfu9XV23bz5Xn2txMd+AXn6gqZh095a2MTv87C\n",
1889 "fOC4yzZm91Io5Rpp8idRIprHMwQcFJTgZcvF85oBtIPqks/KWli5AKkUdiV8+fTgoLIsd2wD5rcP\n",
1890 "EI2GDuPujSk7u4kisXzvYuMvXrzAB4dH7KRany+tMSCAzmrG5B2n0Gcp5DQVyVUwt20E7iRTLLhc\n",
1891 "WG1G8tkyLrOefKWmyYTTUjRfYdxqQNQY96UfPMHRg2f2vOnrqrQWXSwGHfNjZh5F8hwMYFzfWAuz\n",
1892 "dNGHyaAnYLdzmOnslOvmFpF2N0fzV+1stESjqEpfmMeX799RnHL0ZllpoRZs8Tjmmc4CRzCalFSF\n",
1893 "SGtH1mDU8+pXVvjTf/+o6/U6fJjGO+Vk0X+RzeBax+dlWVY6Vl0KK5tRAc9my89+ezSY2MPvHycV\n",
1894 "z9f9VU1sspkFanvbXb9WqvPG2pWK5RkLuMBuY+vJ8TThWWwGBjNlPIUsRu9wy0M/ryyrMyusDlIl\n",
1895 "cqUa5yaOOyi/eOkif+HCeX79m98iX6koYy4ZCj3erKZJL7VcgUpamxPVLBW78MPtHb64oJjFU8Uq\n",
1896 "bsvxRqC7R0fHbLRgMVpJ5WKD/potujntaImuCKcOEASBac8s5zwe1iIKI0v1VwmCgLT+CP3KJca9\n",
1897 "dsJHrRfI2SUPe1uDFwpb8ULPKJvdjRjf+X8f8ot/9YWeHiaDUc9nv7TMj99qbbU7rqyQuv+Y3717\n",
1898 "j/O2MRxNhZkoiPhcU6cyDkzEhmdYpe89wXZtFTmX6Wv07CaVZwVgv6i01bOPNk81zkbaXodKGXHl\n",
1899 "SuNj7WNAVQadyKdnnfy0zzjQajDw5vIS39/abvl4JKddWAHYzj9fn5USZdP62txOJVlwu7DajeRz\n",
1900 "JURBwGszDhzGHG27QauqZnOUjiLYlue6fu1ZFVbd4m2uBezcD2YV1EK/MeBaiHOXlY6GSmBvl1hH\n",
1901 "LoQOUgRG2AhsN0IDJKa9jIf7d4T7sawi2VY4aKJQwRCNaHasAMTAdIfPCuDyp6Ypl2o8fagNDQ0f\n",
1902 "pvFPOpj0zJMpJMkWWg8ecjoJRpMmw0rV8xgHVmsVEtkI09MBkvEC90IRrjV3rGYXe3esttc7NgIB\n",
1903 "kokCbo8V0+IskbXjr1c2A0/3tR/MlHGmEgPDQVVNeeYJJveoSc+ve34aOrPC6r29FJ+ec3WcJn/9\n",
1904 "1k1Wxj38d9/+bgMOGo90NwgKgoB1aYb8gD6r7KMHfHB4xKtzShs72cyw6tOxAjXa5gQ+q6bT6OP9\n",
1905 "O1ycuYEgCFzzexvjwHSTcb22sYZ47hL+aSehNmNrYNpFIpobyGdVrNSI5ytdL8qhwzR//Ht3+YVf\n",
1906 "uTHQCfbqrRlS8QI768dFpvPqed7f2SNfqWCpitjbMBn+sZMb2KWaRDpZ7FkAayl9/zHO6xcR7E7l\n",
1907 "gjmCLvsVArssywiC0AhlPk3qevWn30P/yustJt3k7fsdY0BVg4wDAV6cmuT2YevrNpTpXljZV+bJ\n",
1908 "PUfkgkJcP369pEslitUaE1YrVpuJfL2Y8toMA7OsFON65++XWdvEfmERUd99nHYWo0CAhTEzpZrE\n",
1909 "QZt5+OqknXtHWfbTvY3rxTo8eOGcMnJRMwPbJc4uUt7ZIR7NMREYjrgubWt3QMJ2C+6tg74Hl34s\n",
1910 "K62OlRiKdDCsVLVvBjY+Lgp8/msXukJDw0fKGFQURKVrFWrtWvXyV6l6HvT1cOoAj8OHZ8JBMp5n\n",
1911 "PR7nkvfYZiEGppGjIeSy9t+0Vge5NkuWZZKxPC6PhbEVxaesdvZm67E2pzniDGZKOKPhoTtWJoOF\n",
1912 "MdsE4eRwi1pnrTMrrJQYm86TkiAI/J3Pv0ZVkniyH8U9bulZWIEaxjwYgf2dgwOu+rw4TMrFKVWs\n",
1913 "tuYE9rlhn8TAfi1g42k03wi5fHRwh/N1ftU1n4979UDm1N1HOG9cVCB86STi1Cz+aRehw9bCahif\n",
1914 "1XaiyKzbjE4jwT0Ry/Gvf/c2X/kLl5lbHmwGrtOJvPqVFX787WPDuvPKCv/WquMb166RTZdaOlZw\n",
1915 "Ogb2VLKAzW4aymxbK5bIbe1hv7iE4PKMPA70241IEg2fj+/NVwm99WNi6eCpMKzkWo3qOz9oGQNC\n",
1916 "944VwIszDh6Fc6T7MM1uTU1y+yjY4vEL58r4ujCenvdmoJZxfd6lxFwpHSulmBoGuRDtthH44ElP\n",
1917 "4zqA1WakmK8g1QZfDz8NCYKgCRRe9StbqfvJIlM9CqvNxxFmFz0NFMmcy8mOVsdqZoFoJMfYuBXD\n",
1918 "kMZ1LSYSwH6xwKQEuT4xY71YVuWaRKZUY6zJ2xnLV6kdhbt2rITJ2Q4Du6qFlQnGJqzcebf1/iDL\n",
1919 "MuHDTCPKRg1kblavjUBVzwMSGkzsERibxemykEkXmXc6sTb52AS9AdE/1ULTVyVLEtLOJrqm8GVQ\n",
1920 "CnBBALPFwNj5BcbjYYIZ5Tmxm/RY9OJQfsa+v0O2jP3wEJNvOI8VqOPAny8D+5kUVrlyjbVIjhem\n",
1921 "tU9KBp2O337zK9TyVe6ZUsTCvcd8tnODbQYKU7P8yOzk877jaj9VPDav9/NYgYJciI7YsTIbdFzw\n",
1922 "WrkXVH6fJ/t3uVgnrisE9vCxcf3GJWobj9AtXUAQdfinnIQ0ImUG9VltJ4osakT1ZNNF/tXvfMDL\n",
1923 "Xz7H+dXeF5F2XbgaQKpJjVZ7eMzB5oSTrwYCZOsBzM3yu2cIJk5mYE9E80OFLwNk1zaxLc6iM5sQ\n",
1924 "3GPIqdEKK0EQGl0rAM9LN4gcbWM12jAahqPZa6n28COEcR/i5EzjY9VcnvzGHq4uwdFmg44bUw7e\n",
1925 "3dNmwany2my4zWbW48e/ezhbxu/o1rFaIPdke/hfYkTtJIsstBnXF+oEb9VjBfXCasCOVbdRoGJc\n",
1926 "750XKepEzFbDc6Wvq7o108mzspv0TDtNbMWLzHQBJgMdocvdOlaC2ULUtYTPPVxRJZdLSOGjlmgU\n",
1927 "VTvJFEsBH+n7j3t+j0mHqesoMJar4LHqGwfAqiRTyOaRsrmuN2VxcrpBX9fS5796gZ/9cLOls59J\n",
1928 "FdHpRWwO5Rq1FLjcYWDvxbBS9TxGgaHEPoGxOXR6EdEsctUx0fFvuo0D5dAhgt2hxP80KRlXxoCC\n",
1929 "IGBbnmUyEeVp7Pj3OG0DezBdwn64j9EznJ8PYHp8gf3/UFj11+2DNFf8Niw9TkpmUY9Z0vGn+UN+\n",
1930 "cNS7G2VbmiU/QMdKBn46NslrpeOLVqpYwWXWU63UKOTKXeNzVI0KCVV1c1rxUGQKSWKZEHM+5eQ8\n",
1931 "53KSr1TYfbSB3mHFNOGhtv4Icfmi8nMDShu40mbmH9RntRVvHbWAcmr5w392m9VbM1z/9HD5eaBE\n",
1932 "f7z25nl+8u2nSDWJf/HgIV88SJC9v4XRpO/oKgVOYRSYjOWHZlil7z/GWS9MBPfoHSuo+6xCSmEl\n",
1933 "Gg3oXruAuzYadLRd1be/i76tW5W6s4bj8jlEk3YBBCostP9489bUJB80jQO7mdeh3rFa33kuG0+S\n",
1934 "uhHYBgdtFFZ2Y4Nl5bUNjlyI5CpMWDt/P8W43rtjBco48Cx8Vi9MO7l3lKXS1i27GrARyZW7wkGr\n",
1935 "VYmdp1GWLhwbm7t5rACiznm8+uG6LdJePRrF0Pp3rUkS+5kMy+eX+hZWEzYDqVKVksZ4TgsOOlXM\n",
1936 "YJ70do2iUlhW3UdFEwEHK5d9Lfy98GEa3+TxwX7Rf6mjsBqkY/U86OtHiV0C9fDlolFi2dxZnCib\n",
1937 "gZ3FR61LdzFVHwOCkrnrjIRaqP+nTWAPpkuMy5WOhYdBND3+88eyOpPCSsUs9JLa8fitL3yZf1fc\n",
1938 "5Wf73d841uXBRoEPwhGcRiPT28em62SdY5VKFnC4LYgao7JmjRpro+pWfevnycE9zk2uohOVbpkg\n",
1939 "CFz1eXnvzgNc1xV/lbSxhu6c8t96vYjHaycSHM1n1R5lU6nU+Df/4kNmFsd46QtLPb6ytxZWJrA5\n",
1940 "Tbzz3hZ/sr7OL1jshB9udfir4HRGgYlYbmiGVfrBE5zXlA6F4PIgp0ZDLkDrZiCA9KlZrJGTt8zl\n",
1941 "YoHqRz9D/5nPt3y81xhQ1Wdmndw5zFDsExp8a2qSD+o8K1mWCWdbQYzNMo450ZlNlILDhY6PonC2\n",
1942 "jNWga0EIbDeF+VrtppaO1aBBzFqjQKlSJftkC8el5S5fdayzMrC7zHpmXOZGZ1TVnNuMKApdAb97\n",
1943 "mzEm/PZGFwZgxuHgMJOhqhFrFBHGmCgN132XttdbwKCqgtkcHosZ39ULpO/15kfpRIGA3UhQ42/b\n",
1944 "jlpI5KtM5lOYp7sXOILHi5xNIxc744BUvfL6Cg9uH5CsQ0PDR5kWzIR/bIZ8KUs6f3xtGMRj9Vw6\n",
1945 "Vsk9/GNKFztKCb/QOXkQZxeRNDYDFT9c5yEi2ZSJa52fQheNstF0b5l1m9k9JQN7sVKjUJXwdLnW\n",
1946 "9NN/GAUOqPf20nxaw1/VrEzduH5jcZIvJ7z8zW9/l6cx7U6DbWmW3MZe39P1D7a3+cLMVAMUKssy\n",
1947 "6WINl1nfMyOwWSftWC16LGRLNW5vP+LCTGvw8jW/n7v7B7huXEKWJGobjxsdK6A+DhzNZ7UVL7BY\n",
1948 "Ry1INYl/93t3sbvMfOnPXupKnx5EgiDw2hvn+Z13PuJL8wvMX1ohvhns8FcBTDgDJLIRqrXRC5HE\n",
1949 "CHDQ9L2mjpVr7EQdq/MTVrYSRcr103Zp3oFhM0WtcLKLUPX22+jOryK6WkGwyQ86+VXtcpr1XPTa\n",
1950 "eF8DQNusW5NKx0qWZdKlGkadgNXY/QRpf06bge3dKoDtZLK1Y6V6rIagr0c14mxyGzuYp/zobf1f\n",
1951 "Q3an+UwM7FA/gLU9nw6TAtfsFjey/nGY5UuthYBJr2fCauUo02qnqFYlEiUDnmgrj66fum0E7tY9\n",
1952 "cc7V8woouEc+JSj5h1o+qw44aL7CRDbZ1V8FIIgiom8SKdT98G1zmLj5yjw/rkND2/ldioG91WfV\n",
1953 "i2Gl6nnQ14OJPSbH5siUSoTlAqZS5/W6W8dK2umyERg7tr2IRgOmST/hJ8c2jVm36dQ6VsFsmQmd\n",
1954 "NPRGoCq1sJLk5+t3PInOpLAas+gJOHqvDGeSRRxuCzqdyCWbh//q6gv8jW/+e0LZzhexweVAZzVT\n",
1955 "CvU+Xf9we4cvXr2OFAki5zLkysrNxagTScWPK/he8roCRNJHI49IREHghWkHHx5kuNhRWPl4VCwo\n",
1956 "hVVwH8Fmb7nR+qedBDUiL2aXevuskoUKNVlm3GpAlmW+/W8eUqtKfO3rVxH6dOgG0cS0gweWNLfk\n",
1957 "cRyrKySPEh3+KlCynzwO/4kK00R0ONSCVKmSebyJ44pycRHc40gnKKzMBh1zLlPDjxAvx/E6J4m9\n",
1958 "/cHI3xOg+vb3OsaAsiyT/PBh344VwCsL/SnsUw47BlFkJ5UinNXO0GuW7TllBm4nWsGgsiyznUwx\n",
1959 "5+r0WKn09X7vv0pNIl2qNVAqqtL3n+Jc7e2vUnVWHStQxoHt2IVksYrNqGM92tkhkSW57q/qzD6c\n",
1960 "dzk7COyxUAa324xuf32oxyVtP9XcCNxJKc+XcWIMvcNGYbf3e3zSob0Z2AEHLVRwpxJYZnuP5BQD\n",
1961 "e+9u+K0maKiCWmg93C83+az6MaxUqZDQZ6VqrUI8E8brmuJhJIrLYyWd6OzMCRN+5GwGOX98f5Rl\n",
1962 "WRkFanQYU4kC7ibPrePcLPZwsMHEO02WVTBTZlwqYxpyI1CVzezAarQRz2hjMz6JOpPC6jN9xoAA\n",
1963 "mVSh4XfyeO28YJrgl1cv8ze++SeaQDZbn3HgYSZDJJ/nxtQkuuWL1J58TLKZYZUoNGbOvWQ1OdCL\n",
1964 "ejKF0Vb2Aa5PWjjMj3FusvWGeWXcw6bdjP3qeWrrx2NAVcpmYBcDew+f1XY93FYQBH781hNi4Sx/\n",
1965 "4S/d6JnBOIy++XSdi95xjt6LYliYI5crY+/i3TmJgb1WlcimiwN1FlXlnm5jmQk0OhTiCczrqi76\n",
1966 "bDwK10cKyQPmV28SaQplHlZSMk5t/RH6Fz7b8vH81j46k7FrUHCzXl5w895eusOX0yxBELhZ91mF\n",
1967 "s2X8XZ4jVfaV+efSsWrfCIwVChh1Otxm5WPNHSuLQYdJL5LqswUZz1cZs+g7tmAzD5/g7LMRqOos\n",
1968 "C6vLfhuHmTLJphH/fqrEnNvMvaPOZZ7gQQqTWY9H49ChGNhbi7TQYRr/nAc5l0XO9e50qpKrVaSD\n",
1969 "nY5oFFC3OJVCxXn1fN9x4JTTqBnGHNZALdgTMU04aLPEyZmeBnY4hoZ+548eUipWO64jzZuBgzCs\n",
1970 "4HgU+Ky8iJHUEWMOH3qdgfvhMHOTblJxjcJKFDsI7HIsrGwMjnWa/tsbCbblOZbziUbRPmE1UKxK\n",
1971 "PaOHBlUwU2aslMM0JHW9WVNNoNDbB+m+W9BnrTMprF7qMwYEZRTobBRWNmLRHH/tUzf4VCDAb3zr\n",
1972 "O1TaWCm25TnyPdZ8f7i9w+fm5tCJYiM3MFUYnLrerJP6rMb1h1QMFzHoW9/Yxv0Q9nKFPalGbeNR\n",
1973 "yxgQwOu3k4zmqbR5afr5rFQw6Ps/3mJjLcwvfuNmYx37pJJlmd+9e4+//ukXWL7o5cP3D5G9AQx5\n",
1974 "7Yt14AQ+q2Qi3+hiDqpm4zqc3GMFtGwGRtJHLL/2RcJ1Cvsoqr7zA/S3XkYwtY7DBhkDqhq3Gphz\n",
1975 "m7lz2HuD9tZkgA8Oj3oa11XZnlNmYAdqIZli3n18+DKbDZRL1QaLyGvrH22jNQYESD94iqNHlE2z\n",
1976 "zoplBaAXBa5P2lu4dwepEqt+e2OruFnra2HOXdIuwBUDext1v05cF2cVAvsgkg53ESZ8CObOg81u\n",
1977 "Kt3oMDqvXuhrYJ9yam8GtsNB4/kKpmi05ygQukNC23X5U9Mgg2/S0dGtXwwcG9gH8VfBs6evBxO7\n",
1978 "BNyKv+p+KMzFOV/DJ9au9nFgNzBotSqRy5RaFrVsS3P4k9FGMoggCMy6TgcUGsqUcWdSGEccBYIS\n",
1979 "xrwf3aRck/i7P9gh38dPetY6k8Lqoq//KCedLB53rHw24pEcgiDwt157BaNO5G//8EctNzJrn8zA\n",
1980 "H2zv8IU6bV2sE9iTQzKsVE04R0cuABzF7uIwVnnS1tJP3VnjfE3gXiiEpNGx0ht0eLw2Im0E9n4+\n",
1981 "q61EEVO2xEfv7PD1X7uFRWNTgrufcwAAIABJREFUalT9bP+AmiTzyuwML3/5HHff3aM6No4Q0S48\n",
1982 "T2JgT47gr0rde9JaWNW3Ak9ywlQ3AyWpRjwTYm71BfR2K+l7vW8m3aSMAV/v+Hjydn/jerNeWXDx\n",
1983 "9k7vTqpiYFc6Vj5790w3UJALz7pj1dgIbEYtpJQoG1WCKGCxGRsJDL4Bwpgj+U7juizLZB48wdkj\n",
1984 "yqZZZ9mxgs6khoN0kc/OO3kQzFJr81l1GwNCl47VQQr/tBNxdpFajwDfZiljQO2idKdp2cB59Typ\n",
1985 "fsgFp4kDLY9VG1stUaiiiwxQWA0wCgQFGvrm11e5+cpCx+d8rmlK1SLJbHSgjcDG19mshJ+RgT2Y\n",
1986 "VBhWAPfDEW7MBpBlNA/RShjzduP/a10WDdLJAg6XBbHpgGpdnsUZCbHehFw4rTDmYLaEKx4deRQI\n",
1987 "dZ9VfIuf7qRY8lj6WonOWmdSWGlBKtulmtdBGQXG6ywrvSjyv7/xOhuJBP/w/duNf98rMzBXLnMn\n",
1988 "GOKVWaXy1y1fRNrdIJPJ4zLrFXbUgB4rUFhWJ/EJPd6/wxWvroNVk77ziNWxMe4fHSEd7SPOdW4u\n",
1989 "+add2jyrHj6rRwdpQvcO+Pqv3sLpHo5Y3k//7O49fvXGNQRBwOm2sHpzmrzOgtQWn6LqJGHMiVhu\n",
1990 "RNRC081APW332CDqp0mHkYok8yR0gMPixqA3Nijsw0o62EFOJdBdutbxuUE2Apv1yoKbn26nOm66\n",
1991 "zVp0uylVa2wlUn1HgaZJL7VCkUqyNyPrJApny9iN7RuBx8Z1VS0+qwEM7NFcuaOwKh6EEI3GgS/w\n",
1992 "Z11Y3aonNciyTKUmEclVWJmw4rUZW1bjk7E8hVyZyRm35vdp71jVqhLRcBbfpAPd3NLgHasuN+qa\n",
1993 "JHGQyTDbGAVeIHP/ac/DS8ChPIfNr9ViVaJYlRqHXYBYtoQcjWOe7D0OV+jr+wMdmALTLlauaOQO\n",
1994 "CoLCswqtDcSwUuW1PjuflcqwCmVzlGo1Zl1O3B6LZteqfRTYrRBONqEWVNmWZhEPDlmPnj5yIZgp\n",
1995 "Yw8ejQQHVTU9vsBBdItvPY7xtQujf5/npTMNYe6lTEoxrwOMe23Eo3nk+pvQajDwD//MV/njJ0/5\n",
1996 "Vx8rM3HbcndI6Nt7+9wI+LEZlRuJYLYoIaLbj3Gb9RTyFQRBwGzpfYJXdZJYG0mWeHxwl8+vzHWE\n",
1997 "raburHHz/DL39g8QZxcRjJ03Pv+0s4PADt19VnvbcfbTZX7l66uM++wjPeZuWo/HWYtE+XMrxxfb\n",
1998 "Fz+3SEk0ULjfGWoKEHDPEkqM1rEadiNQrtXIPFxvMSsLgqBsBp7AZyUIApd8Vu7sbuFzTQPg+4oS\n",
1999 "yjysKm9/D/3LX0IQW7fzqtkc+c29gY3WoIxXPFZ9x5p++2O/NTXJRiLWdxQoCAL2c/Nkn2G0zXai\n",
2000 "cyNQGQW2FglWu7EB6xwEuRBtM0EDZB4+7Rm83C6bw0QuW2pcd563Jp0mLAYdm/EiwYziPTLoRK5N\n",
2001 "2rkXPL52rK+FOHfJ13URZcbpZD+doVbf1IuGs7jGrBiMesTZJaQBO1a1LhtmwWyOMYsZcz0iyBSY\n",
2002 "AAFKR5Gu38uoE3Gb9S0FciSr/I7NW8rFowj6MWdPjhuA4HCCqBs5rkrVct1nNVzH6tnR11WG1YN6\n",
2003 "8LIgCLg8Vk2fldp9VIvLboR8rSaCecqHlMlSSGcb/qVZt5ndU4CEhjJlrHu7GL1j/f9xF02PL7EX\n",
2004 "3WQ9kuPl+eEho89bn8jCqlKuUq3UsFiVQsdo0mO26Ek3PckTViv/6M9+jX/w7vv8aGcX6/wUxYMQ\n",
2005 "UqXT1NYcuqxKd2EV2/aawrAaolsFasdqtMLqMLaNzezk5YVptuIFsnVzoFSukH28xQufvsFWLkd5\n",
2006 "6aLm12shF0DbZxUNZvi937uHw6Ln3NLpV/n//O59fnn1CqamzDW9XkTU6cjfX9M8Pfrc00TSR0jS\n",
2007 "8DPyRCyHe3zwjlVucx/juBuDu9XTd1JIKMBln41HRzt4XUqUjfvFVQoHIQoHg2+uyJKkuQ0IdTDo\n",
2008 "ld5gUC0NAgu9OTnJUS7et7ACJYz5WW4GtvurQBkFzrvaOlb1IGZQRoH9gpgjGgyr9P0nOAf0V4Hy\n",
2009 "WjYa9RQGyOJ8VlLHgc3hy9fquYGq1j/u7q8C5SDqMpsI1W/+4cM0/nrwskrslvu8H5VolI2uG4HN\n",
2010 "z5cgCDivDeazat4MbIeDyrKMHAxj7TMGVDWIgb2fVFDooB4reLYsK5VhdS8c4ZpfeY67dayE+ga5\n",
2011 "nE4iJePIlYomLkJZ1Gq93wmiiHVhhivlFJv1om32FDpW2VIVSZbRHRyeyLzusnqo1CRemdNhPKWl\n",
2012 "q2epT+QjTKeK2F3mlpOLx2snHmk1bS6Oufl7X32D3/zeD3iUTmEKeCnstLJMapLEn+7sNvxVqnQX\n",
2013 "ruLZf4zLbBjKXwVKXuCoHqvH+3e4MH0do17kit/G3foFMvPxOpaFaWwOO0u1Ck8mF7R/dsBBIpbr\n",
2014 "MLC3+6xSiQJ/+Lu3mX9pjpUBPG3DKprP8+3NTX559XLLxzPpEi6PhZrOwN77nZtBJoMFu8lBPBse\n",
2015 "+mcmonk8Q8TZNINBmyW4PEjJkxnYL/lt7MX3G+HLol6P90svEfnO4NuB0pMHCFYbOo0tq2HHgKpe\n",
2016 "XXDz9naq50jkmt9PtprqQBFo6Vn7rHYSymKFKkmW2UulmXO1FsPNQcw+m7FvELOWeT398GnfjMB2\n",
2017 "2Zwmcj0Cg5+1bs04+eAgzUG6xHTdGnE1YOdhKEdNksnnyoSPMn0zPudcLvbSyoEsdJDCX2c4CVYb\n",
2018 "gtONHO59PZNDBwgOF4KtM4ZsN3WMxlClGNj7bQa2sqwU1MJxMZwu1fBkElhnB8vhVMeBJ5ESbbM2\n",
2019 "EMNK1bOir1drFWLpED7XdL1jpRRWSsdKo7ASBHSzC0h7W0g76+jmlzUZhclYvgW1oMq2PMtSPsHT\n",
2020 "uvd3ymkikqs0mH2jKJgp47cZqGVyGMb6L611kyRDRZxkdeLZ2RJOU5/IwirTZFxXNe6zEdMYcbww\n",
2021 "GeBvf/41fv1PvkX+ylLHOPBuKIzPZmXK0XpB0J1fxRfexGUQSA2xEQgwcQKP1aODO418wJvTzobP\n",
2022 "KnX3Ea7rSpfqcjLMQ6v2i1Bv0OGZsBENdm7dqT6rfLbEv/qd93nxtUWqLisLY71jekbR7z/8mK8u\n",
2023 "L+OxtL5Bs6kiDpcF68VzfPgH2qMx/wjRNhU1cmgIj1j7RqCq00AunJ+wkswG8TiOL/q+N14bymdV\n",
2024 "+cl30b/aaVoHdSPw6tCPa2HMjE6ksd2jJafBhkSVWKG/z8y2Mv9MNwPbO1bBbJYxi7klZBZakQve\n",
2025 "AUeB7R2rzIMnQ41W4Ww3AwGuT9p5HMmzHsszUw9f9lgNuM16thMFNh9FmD833jeUfM55HG0TPDju\n",
2026 "WEG9a7XbexxY67JhBq2oBVXOq+dJ9VnmmGzbDFTgoK0bgb5sqq9xXZU4OTOQgb2XJpwBarUqUiTY\n",
2027 "l2GlymezPRPzejQdxG2fQKcz8CAcYdWnRBW5xqwkNUaBcOyzkrqAXKGVut4s29IcgWSk4d/TiwIB\n",
2028 "h5GDExwsgpkyXqOAcWKsayTRIProMIPJPAOV7hDYT5I+mYVVE2pBlWfC1tGxUvXG8hL/2Y3r/Pb1\n",
2029 "BYJtBvYfNm0DNktwOEma3XjjeyTjgzGsVNnNLiRJIlccjP/SrMcHdxvE9ZvTDj7YV8ypqTtruG5c\n",
2030 "QopHuJJLcD/XvQXrn3Zpg0IXPexuxPjD373NhWuTvPDyPFuJgmb48klUrFb5vQcf81eud9741aWD\n",
2031 "6VeuUlrf0jTUKyyr4S6AqVge51j/yKFmNRPXmyW4x088CrQYdFiEOGWOOwUTX/wMiffuUR3g9CqX\n",
2032 "y1Tf/wn6z36x83NDgEHbJQhC33FgLF9lwuzm9mH/ruuz7FhJssxunc2kqjnKplnN5vVxq4FUsdqV\n",
2033 "2VWTZJKFKh7rcWFVSaYpx9NYF6aHeoyKgf30MtOGldWoY2XCyk+2ko1RIMDV+jhQ9Vf1kxrGXKtJ\n",
2034 "RENZfE1wTHFuiVofA3u3aBRQUAvtz9lAo0CHkcNM8yiw1RcXL1QYyyQGL6xOoWMlCAJXPOepGXR9\n",
2035 "GVaqFPP66Xusgok9AmNz7CRTuMymxiHW7bFodqzgeLTbLSNQWdTqHAWC0rGyh0MtYcwnBYWGsmUm\n",
2036 "qJxoIxDgW49jrM6sNFhWn3R9Ygur9s7EuM9OPNL9xftXr1/jRbON/7kQp9zEuPrB9g5fXFjQ/JrH\n",
2037 "Y0u49h+T6lLBd5MgCEy4hh8HxrMRcsU0U+PK45kfM1OVZA7T5ePCav0R1ybGuRfuPirzTzs1NwMn\n",
2038 "/A6ioQzjPjuvvK68qbbjxZZRy2noj5885YrPy/JYpxkxmy7icJpwXT1PgDQ/eutxx1hqlDDmRGxI\n",
2039 "47osK54aDbPySc3rqnRSjGjp+Aald9hw31wl+sP3+n5t7c676OaXET3ejs/lN/fQWcyYJzs/N4jU\n",
2040 "cWA3hbNl5pzj3D7q//q1zE9RCkVPHNmjpWCmjNOkw9YUq7OdTLYwrFQ1d6x0osCYRd+gRLcrUajg\n",
2041 "NOvRNxXhmY/XcVxeHvrUbDvjjhXArWkHpZrMtOu4sLoWsHN3P83uRpyli/1fJ2oYcyycxek2Y2za\n",
2042 "wtTNLiLt9SusniLOa+cr7iQ7R4GW2Ulq+SKlSPf3WTvLKtIBB63iSMT7wkFVCafQsQK4ZJkhaxt8\n",
2043 "nf9Z0ddDyT0C7hnuN40BAZxuC9l0kZrGwUJBLmzVC+HOwiqfLaPXi5jMnTYA69Icwv4h4Uy5kTuq\n",
2044 "sKxGL6yCmRKeagHjCTYCU8UqHx5keO38ZfZjg22wnrU+uYVVe8fKayPWo7AC+I1Ll7Clc/zm935Q\n",
2045 "Pw2nSJVKjRZqs2RZ5p59HtPmxwPnBDbL6xp+M/BJ3V8lCsqfXRAEpWu1FSe/vY/j0jK19TUWl86R\n",
2046 "LZeJdmkv+6c6NwMlSeatf30fi9XIymU/giBQrkkcZUrMuk+P+SHJMv/87j2+cb0TDwDKc2d3mXGu\n",
2047 "riDv7lKtSKyvtRaJo7CsEtHhUAuFvSN0VrPmSUlweZBP6LGqSVUqlQS76dZiz/fGK30p7LIsU37r\n",
2048 "X6P//Fc1Pz8MGFRLF7xWMuVq1wtiKFvm8oSPDwboWIl6Pdb5aXIbpz8O3KknAjRrO5liwdWJDbDa\n",
2049 "jBSyxzfhXpuBXY3rQ44BQRkFniVyAeDmjBODTmjp5lybtLO/Gcc35RiISzdf71iF2saAoHSseiEX\n",
2050 "ZFmubwR2dqxqksR+E2pBlSAICoG9h89KzQtUD15acFBLPDZ4x8o/hRw5Qq6dDB45rxsjahj8e3it\n",
2051 "CsfqtOnrR4ldAmOzHYWVTi9ic5jIaGzsidMLSLubyJkUQqCzO5tKdLe92JZmyW/uMec2sVEfNc6d\n",
2052 "MIw5mCkzlk1jmhi9Y/Xdp3FemnOy7D/3cxPGfCaFVb7c+0XbHGejyuYwUatKDUigluzLc/ylP36H\n",
2053 "YDbLb73zLj/c3uHz83OIGga+fEXi6dgS8pMHZFOFoflO3hHCmJvHgKpuzjh490kQ+4VFRJOR2sYj\n",
2054 "9Ocuser1cT+sva7sDThIRHNU66cKWZb53r/9mEK+wo2X5hoG9v1UiYDDhHEIUnk//WR3D4Oo46Xp\n",
2055 "Kc3PZ9MlHE4zlrkpqpkcL3/Gz0/eeoLUtLLud88QGjLWJhHLD1VYpe897nojPY2twFg6hNPq4VG0\n",
2056 "9fXoe+NVIt/9ac+Le+32TyGfQ//ZL2h+flgwaLtEQeCVeTdv72h3rcLZMtf8XvbTGZLF/qdR2/ln\n",
2057 "g1zYSRaZbzvQ7KRSHQwrAKvd1OhYQW+WVXvnA0YzrkO9sDpD8zrAuXELv/XnVlr4fxM2I758Cc/C\n",
2058 "YDesWZeTvVSa0OGxcV2V4J9CTieRC9oH10Y0irvzZwWzOTxNqIVm9RsH2oxKPFGioGxGd3SscmX0\n",
2059 "kcjAhZVgNCmHpujJMuUCNRN7cnrgQslmNCI+A/q6yrC6F4pw1d/aHOhqYLfZERwuxLmlDoQLQDJW\n",
2060 "6DqdMXhcCKLARUO14bM66SgwmC3jTMZHDmCWZZlvPYnx1QvjjDv95EtZ8qXhLTjPW2dSWN3VyLpq\n",
2061 "VjrZ6bESBAGP19ZzHGie9CIm0vz9z32O729v849uf6TprwKlvVgZ8yLr9ARMuaFz80aJtVE3Apv1\n",
2062 "qSkHD1M17DcuK1lc20/RLV/kmt/HvZD2BUJv0DE2YSNSN7C/8/0NjvZT/Ed/+QXmz403eFZb8QKL\n",
2063 "p2xc/2d37/GNOhBUS5l0faNTFHFcOcdYKYHFZuTjj45Nh6p5fZgTXiKaG2oUmL7/BOe1Tn8V1Aur\n",
2064 "E44CI+lDAu4pSlWJaNMN3jI7idHnIfnhx5pfJ1erlH7vn2D85b+ueeGD0TcCm/VqD59VOFdh0mnm\n",
2065 "ut/PR8H+NyH7Mwpj3kkUWvxV0ErwbpbVpnCs1NdML+SCYlxvY1g9eDoUakGVzXn2o0BBELjgbT1U\n",
2066 "SJKMJ1sk7RzsQGg3GjEb9BzsJfFPt/59BVGHODXXQu1u+Vld/DrQiVpo1iDRNpMOI0fpErlyDUkG\n",
2067 "e9NYOBVOIBj06B2DH6hOw2dlTueImwViQ4T+Pgv6+lFiF49zmvV4nEsTEy2fc3t6GNhnF7v64RTj\n",
2068 "uvZrRhAErEtzLObijc3AGZeJg1QRaYRunCzLhDJlHOHQyKPAx5E8lZrM1YAdURCZ9iz8XPiszqSw\n",
2069 "aieON0uW5brHqrMgGO9TWAmiiHVpFsNBmH/8Z/8MlybG+eyMtlk1VQ9gLs9cYJ7hTzgTzkmiQ7Cs\n",
2070 "iuU8B/EtlgKteAK3xYCnkCG6eg1pfxth3IdgtdULqx4+q/o48KN3dvj4ziFf/8ZNTGZ9C89qO15g\n",
2071 "4RSN64+iMTYTCb52TttrAfWtwLrJ1nHlPJkHT3ntzfO8/d31RofNbnYiirqhgqwTsTzuoQqrx12j\n",
2072 "SwSnGzmb7svu6aVI6gifa1qJtwm3XlB9b75K+K0fa35d9Yf/HnHCh+7aLe3PZ3Pkt/ZHGls16+qk\n",
2073 "ncN0STP6JZRRcgJv1QOZ+8n2jAzs7aPAcq1GMJdj2tm50m8w6hBFgXJJec58diPhLnmB7RuBUqlM\n",
2074 "bnMX+8VOrEU/nTV9vZuO9pKYbUbW+mxHNmvO6SIWyrUY11WJ88tdQaG17Y2uG2a7PQor10BhzCYO\n",
2075 "M+X6RmAbHHQ/iH5yMH+VqtPYDJSjIUyB2UYg8yA6bfp6TaoSSwdJVM3MuZwdW7KuHgZ2/auvo//0\n",
2076 "a5qfS8V6b8DblmfxJ6ONjpXVqMNp1veNkNL8WcUqBp2AGI5gGhEO+q3HSrdKfV1Mjy/+XIwDz6Sw\n",
2077 "en+/e5u1VKwiCGAyd1LQPb5OllW7bEuz5Db3mHU5+T///J/reEGqUgOY0xNL+AvDvxGHjbVZP3rA\n",
2078 "gu8iRn2n32nh6RrrE9Mt+YBXfT4ehCNdTwr+aRcf/XSHd/90k//4125hq2cnNfOstjU8LCfRP797\n",
2079 "j/90dRWjTrvTUq3UKJdrDc+H8+oK6YdPmZ4fwzfp4M67x+M/ZTNwsHFguVSlVKzgcA72u8iy3HUj\n",
2080 "EEDQ6RBsDuTU6JTmcOoAr2uyXli1Fvu+N17V9FnJhTzlf/MvlW5Vl45f6qM1HKsriMbBUgC6SS8K\n",
2081 "vDTn4qdt48CaJBOv5+gphVX/17D9GYQx1ySZvWSxpWO1n04TsNu6vr6aIaFem7Hrxb59pJR5vIV1\n",
2082 "YQadeXivoeqxOm3/zEm1/nGYlct+7h9lB35sizo7Oqu2cVmcXey6GahEo3TvWLUzx1RZl2Ypx5I9\n",
2083 "I5FUSGhYg5RfPQpjHnAMqOo0DOxSJIh77mIjkHkQnTZ9PZoO4raN8yiWaPFXqXKPWbuGMRs++0V0\n",
2084 "F7U9sMl4oecB1bY0hy0UZC9ZbGzdzo44DgxmygQcRkqR2EhxNoVKjR9vJ/nKyvEYcWp8kf3oJ9/A\n",
2085 "fiaFVbUmtxB3m6UwrLS7LB6vNsuqWdblWfIb3cOYVakBzBH7HO7E8BXwsLE2jzTGgACVVIaZ+3e4\n",
2086 "V9RR23iE7pzCshq3WnCaTGwntW/+U3NuctkyX//GrY4TiMKzip0qaiGUzfH97W3+kyuXuv6bbLqE\n",
2087 "3WFqRGs46x0rgNfeOM97P9qkVFRO2MNsBiZiytZmt8iOdpVCUWRZxjzVfQ1dGQeObmBXO1aX/baO\n",
2088 "CBnXjUuUEyny262/X/mb/w+61Re63qTgdMaAqrTGgfFCBYdZh1EnctXnZT2eINfHG2JbniO/s49U\n",
2089 "7Uw1GFXBTBmXRY+1afSzk0qz4NbOu4M2SGiPIOb2UWDm4dOBg5fbZTDqEXUCpeLp/e6nofW1ENev\n",
2090 "T2LSi+wNaC72Vy1UHdqX/F6ZgcpGYJfCqsvoFpQJgnN1hfSD3gb2o3RJ0xdHOIxjbjA4qCpxcgb5\n",
2091 "BKNAWZaRoyF8yy8MVVh5T3kUGEzs4R+b5X44wlV/53VM6VgNn3eajOd7LmpZl2cpbe8TcJrYSSjF\n",
2092 "lLIZOHzXNpgtE3CYKEfiGEcwr/9oK8kVv43xJmzKzIQSxvxJ15kUVjdnHLy/r21A0zKuqxr39kYu\n",
2093 "ANiW58kNUFililXcZj2hmhN9pYCUiPV/4E1yWT0UKwWK5cFe3I/373QY10EZWV1w69lPlUhubSEu\n",
2094 "HxcuvcaB/iknf+M3v4h3snNsMrvoYWcjTqZUI+AYLhJFS1VJ4n/83vf5S1dXcZu7d40y6SL2pq6S\n",
2095 "/fwC+Z19aoUSEwEHiysTfPDjbeXxu2cIDRjGnBxyI1DFLHTrCsHJkQuResfq/ISVjVihhakkiCK+\n",
2096 "r7xCuKlrJSViVL77Rxh/6dd6ft+TbgQ264VpB0+jeVJNRUE4U26EL5v0ei57J7jTxcunStmuHKew\n",
2097 "M3rweLt2kgXm2xZGtpPJrjdpaIOE2oxEeowCWwje95/gGMFfpeqTNg6MRbJUyjX8006uTdq5ezSY\n",
2098 "mddWEEmatQtEcWYRaW8LWWpd4e8VjQIKw6odtdAsZ59x4JTDyGG6RCRbbolYKlRq2ONxHHPDdazE\n",
2099 "wMk6VnI6CSYzC3PX2Qxqx3Jp6bRHgSrD6n4orNmxcnm6d6y6qVKuUSz07vzblmbJbexxbtzSgAzP\n",
2100 "uU0n61iFRzOvq2PAZimjwO2hv9fz1pkUVi/OOLv6rNIaqAVVLo+FTLrY8OpoybY82zWMuVnJQlXJ\n",
2101 "CUwUkRYvUXv8YLAHX5cgCANH29SkKutHDzg/3dmeTd1ZY/zaBVa9Zu5KLsSZhcbnrvm7bwaCkmWm\n",
2102 "pcC0i0Qsz4LdoLkROax++2fvoRdFfv3WzZ7/Ltv23IkmI7alObKPlJPwy6+v8NHPdsllSkOFMQ/L\n",
2103 "sErfe4yryxhQ1UkhoZHUEV7XFFajjmmnseFJUKX4rI4p7OU//F0MX/ga4kT3LpoCBj29jpVJL3Jz\n",
2104 "xsk7TePAcK6Mr6mbM7DP6vzp+qy0UAs7Se2NQFXNkFCHSUdNksm1bRhLsjLqbIaDZh4+xTnCRqAq\n",
2105 "u8P8iSqsNurZgIIg1AOZe9sjVMnJKgeC9sFUcDgRLNaOjTppR+EhaR1SapLEgQZqoVn9DOxTLhNH\n",
2106 "mXInHDRfYTyTwDozXMdKGPciZ1LIxeG7OaD4q8QJPx67F4PeODCr8LTp66HkHi77NMFslnOeTn+S\n",
2107 "xWpAluWWbNh+SiULuNyWnp1/6+IM+Z19zrlNrNdBoXNjo44CS/hMIrViCb2rswHQS7vJIkfpEp+e\n",
2108 "bb0e+N0zJDJhypWzg/YOojMprD417eBBMKuZQdTNuA6g04m4xiwkYt1fwErFvdv3pJEqVnCb9aQS\n",
2109 "BQyXriE9vj/cL4ESfzCIz2o3/BSPw4fD0jnmUMGgLxiy3Jt9AaHJX3LV130zsJd0ehHzhI1ZafSM\n",
2110 "J1XfWt/gO5ub/G9f+TK6PnDFTLqIw9XqY3FePU/6oXJidY1ZuPypKX72ww18Q7CshkYtdImyaZbg\n",
2111 "Ghu5sKrWKqTycTwOpUjS8lmNv/YiqbtrVJJpantb1D58B+Mv/ErP75vf2EVns2AOjAYG1dKrC66W\n",
2112 "cWAoW8HnGL6wsq/MkztF5IJW+PJOKsXCgB0rQRDwamwGJgtVbEZdAzEiS5KCWrgy+jLAJwES2qz1\n",
2113 "tTDLl5QO0rWAfSCflSTJZKNFHle750iKc0sdBnZpe73rGFBBLVg0UQuq+iEX3GY95ZrEdqLQiloo\n",
2114 "VHEm45hnh/RYiTpE/xRSaLToEykSQvAqP3MpcJmNAceBPquV8Cl6rI4SuxR041z0TqDXuO4KgjB0\n",
2115 "10oxrve2huhtVgxjLhZq2UbHalRIaChTZrxWwuT19JweaOmtJzG+suJpgfwC6EQ9/rFZDuPPLmbr\n",
2116 "NHQmhZXDpGfBY+F+qPOkpcTZdH/y+40DDW4nOpOJUrj3aC9VrGIToFKRMF+9PnTHClTkQv+b0qMD\n",
2117 "7TEgHBdWN1LrfGSda7noXfZOsJFIUhzB21JymnH1YH4NoqexOP/Lj37C33/zjZ4jQFWZVOsoEMBx\n",
2118 "ZaXhswJ46QtLrN05wsI4wQFHgYlobsiNwCc4+nhqTuKxiqaDjNm96ETlhqK1GaizmvG8dIPID96l\n",
2119 "/Pv/BMOf/xUEm73n903eHi0fsJc+PevifjDbYMeFs8ejQIAbgQBrkSilPq+x094M3NYorLaTKU3q\n",
2120 "uiqlY9UECbV1bga2jwHzO4cY3A6MJwiA/SSNAnOZErFwlrklZbQScBjRiULfPLd4JIvdaULSQaIL\n",
2121 "u0zJDGz1WfXLCOxmXFdlW5mneBCmmu3SKRMEJh1Kx7e9Y2VNxAdmWLV8z8kZpKPRCis5epwRuOS/\n",
2122 "NPBmoNdmJXqKo8BQYp9w2cw1jTGgKveYdSifVTI+2Ga1bWkOXzzCZqxATZIZs+iRZEgO0R2DOhy0\n",
2123 "mMU45EZgVZL57tM4b17QNrz/l3/m7+Bza3MUPyk6M/L6izNOPtjr9AZoUdebpRDYe7e+FQN775t2\n",
2124 "slhFX67i9ljQLa4ghQ+Rc4O11FVNOCeJDIBceHJwtxG83KxSNE41ncO6ME1g+x6iXt9iEjTr9SyN\n",
2125 "uVmLRId6XABBg4FabPTiF+B1AAAgAElEQVQTVLpU4r/+1rf5H175LJe8E/2/gLp5ve25azevWu0m\n",
2126 "Xnh5ngc/jVOqFCiU+z/GYRhW5WiCajqLdb73G09wjSGN2LGKpA7xOo+/v1bHCsD35mtk/+SbSId7\n",
2127 "GF7/hb7fN3GKxnVVNqOOK34779VH7+Fs6yjQZjCw7PH0HDmDuhm4fSqPqSbJHKRaNwJzlQqpUomA\n",
2128 "vXvxabMfm9dBQS60d6wiuXKrcf3BU5wn6FZBvWN1xpBQVRuPwiycn2hw9xrjwD5sQJW4rkbbaEmc\n",
2129 "W6LW0bF6im5Re4zai2HV+J56PfYLi2Qernf9N5NOE5IMXntTxyqWQVcqYpwYfk1fDIxuYFc6VvXC\n",
2130 "KnBpYAP7adLXa1KVSOqQ7UyZqxqpIap6IRe0pBjXByislmeR9g4Zs+g5SJcQBGFoArsky4RzZVyp\n",
2131 "BCbvcBuB7+6mmHGZmOlSBywHLmM1DTdafN46s8Lq1oxD02eVTha6jgIBPL7eLCs4Hgf2UqpYRShU\n",
2132 "cHmsCHoDuqUL1J4+HOzB1zVIrI0sy103AtN3HuG6fhEEAWn9ES9MO7jdFq581efjfo/cwG4/c70q\n",
2133 "UUoXe5Lqu0mSZX7zez/g1bkZ/vyFwW9KmSaGlSrHlRUyH2+0kMhvvbrA7kYcj22yr8+qWKhQrUoN\n",
2134 "nEQ/pR/Ujet9xpbiCSChkdRhy4lpxmUiX6l1ZNdNfPklXNGHGL7+qwj6/viE09wIbNarCy7e3lLG\n",
2135 "gaFsuWUUCIONA20rC2TXd07lxnGUKeG2GLAYjsfeu6kUs05nT09g8ygQwGszEG6jr7czrNTXw0n0\n",
2136 "SYi1UbVe91c161pggMLqMI1/ytUIY9aSEm2z0fh/OZdRolH82izA3QE6VtB/HDjlNGE36lpeD+nd\n",
2137 "I2TvxNAjJFBZVsMlO6hSPFZKl2wxcImtAQ3sKn09cwr0dTXV4WEkprkRqMo9PuQosA9qQZW1YWC3\n",
2138 "sl4Hhc66TexpROh0UyxfwWHUIUfjQwcwf+txjDfPj54t+EnQmRVWKxNWksVqy8q0LMmNSJRu8njt\n",
2139 "xMN9WFbn5sht9imsClWquXJj9VS8sDr0OHCQWJtI6hBZlvG5ZzofQ30MKIcOEUwmbi17uX3Q2sXr\n",
2140 "BwrVUjxfRRAVntXB9vDjrn98+0NSxRL//cufHerrsunOUaDB5cA47ia/fdyaN5r0fObzSwgFV99x\n",
2141 "oOqvGvQCq9xIe/urQI21GW0UGG7rWAmCwEWvjbVQa8Gv336IYDSSkfvffKqZHIXtg5Ho4P302XkX\n",
2142 "HxxkKFVq9Y5Va5F3a2qS9/vwrIxjTnRmE6Wj3p2tQTSKcR1azeug5gW2F1ZtDKsHT0+0EQhgd34y\n",
2143 "Cqtyqcr+dpzF861djGuTdu4He/usQgdp/FN9OlaBGeRYpGH8ru1s1KNRtG8TvVALzVIM7E+7fn7K\n",
2144 "YepALeT3Q+iGhIOqUujro3qsgo2Olds2jtloIzygF9RnsxI5BQN7MLmH07VEuVZj2tG9M+Mas5JK\n",
2145 "DD4KTMXzuAfIxLUtz5Hf3OXchKWxlDPrHs5nFcooqIVSJI5xiI3AaK7Mx+Ecn1vsjl35edCZFVai\n",
2146 "GkDc1LXK58oYjToMRm1AIIBnwkY8mkeWul9EbEtz5HqMAouVGjKQTx7nJukuXKU2pIF9kFgbNR9Q\n",
2147 "qzBI3X2E8/pFahtriMuXuDFZN/U3re5f8/u4N2THSuFXmRWe1dZwXZkf7ezyBw/X+O03X+8KatSS\n",
2148 "VJPI58qanSXHqgIKbdb1z8yhK7l5stX9ggt11MJQG4GDdSgE18k6Vl5X67bSZb+NtaZOqlwuU/6D\n",
2149 "36Fw+TUi3/lp3++Z/OhjHFfPnxgMqiW3xcDyuIXvbSQQALup1Wz8QiDA3VCYSp/wWvspbQZq+asG\n",
2150 "GStZ7W0eK3snciHSxrBKPxwtfLlZinn97LeQttejTM66MVtaXyNTThM1WeYoo90tkSSZ8JE6Cuze\n",
2151 "sRL0esSpWaQDxRisgEG7F6W7qXRPT5wqJYy5e8fqkt/Gp+dav0/lMIhpesTCqg4JHba7qjKsxCa0\n",
2152 "xFLgEhsD+qx8VhvhU/BZBRN7SOY5rvp9PQ+Ubo9l4I6VLMmkEoW+5nU4Ri4sj1t5qm4GuobbDFRR\n",
2153 "C+XwcHDQ7zyN87lFN2bD4PeeT6LOrLACuNWGXejnrwIwmfWYLXrSPdqS1uXeo0AVDtr8QtOdu4S0\n",
2154 "s4E8RCvXbZ8gV0xTrnY/zWrlA4LyJlY7VtK6AgZ1mvXMuc183NT5WHS7SRVLxAuDn0yUjEALs4se\n",
2155 "9jYHLx52Uin+1vd/wP/xxut4bYNv4QHksmUsViM6jcBn55UVMm0p93q9yOrFizxY+7jnBXBo1MIA\n",
2156 "G4EAWKwgSSOtZUfSR3hdreORSz5rS8eq8p0/QpxbYuzrv0j4rR/3vcg/qzGgql+9Ock//eCICWtn\n",
2157 "4eYym5h1OliL9vby2U4pM3BXI3x5e4COldlqpFSsItUPHlr09Wj+eBRYisapFUqYZ0a7Qav6pJjX\n",
2158 "NzTGgFD3WQUcXceBiWgOq92I2WJgzulkp0vHCkCcPQaF9toIVFELM87+3Vj7xSVyW3vUitp/wyWP\n",
2159 "hf/8xVZPpBwMY5sdDrWgSnC4QBQUJtUQUhlWgvn4tTmUz8pmJXoKm4HBxB5ZPD39VQBOt4Vsqkit\n",
2160 "1n/7O5spYbIYMBi7b3CqssxNUQxGWHbo2IgVkGW53rEa/D0QzJTwO4yUonFMA8JBJVnWZFf9POpM\n",
2161 "C6ubMw4+OsxSrXefBimsoD4O7GFgty3MUNwPIlW0N52SBQUOmowf5yYJZgvi9DzSZu/Q0GaJgojH\n",
2162 "4SPWo2vVbSOweBgGWcY87ae2voauDga9OePkdlOxKQoCV3xe7g8xDlSjbFSe1SA+q3ylwn/zrW/z\n",
2163 "67du8sLk8Js4vZ47BbnQ2Zm6vrpKthph81H38VIimsM9IGqhks5SCsWwnZvr+28FQRgZuRBJHnR0\n",
2164 "rC54bazXQaFyNk353/0+pl/+aziurCBVqn1RBcpG4LMrrFYDds5PWCloIE5gMJ+VfWWe7CkgF3YS\n",
2165 "Bebd7RuByb7dD1EUMFkMFOpeNq/NQCxXodbUvVZGgUrHSg1eHsWn0yyTWY9Uk6mUj68n/RZoTltS\n",
2166 "TWLzcYRljcIKjseBWlKM68rftlfHCkCcW2wgF3ptBB5ls31RC6p0ZpPCs1sbPIpEF4niXhx980sx\n",
2167 "sA83DpQjwZZuFSjIhWE2A0+DZRVK7BEqGzXBoM3S6UVsDhOZAbxP/YjrzRINeiwzAUyhMAadQCir\n",
2168 "dJ8ShQrFHgzJlt8hWyZgV+Cgg44C7x1lMetFzk8MfpD+pOpMC6sxi4Eph7GxUaUwrPo/+eN9DOyi\n",
2169 "yYjJP0FhT/tGoVDXdaSTRVxNP2+UcWCvaJtsMU00dcSCr3MUkb77CNf1S1ApIx3uNi5gt6YdnT4r\n",
2170 "33DjwO24EmWj04tMz/f3WcmyzN/+4Y+4PDHBr6xeGfjnNCubLnb1xrUjF1RNemaRzCl+9O0nSF1G\n",
2171 "u4lYHs+Ab7TMg6c4Li+3sMB6aRTkQrlaIlNM4bG3niZtRh0Bh5GteJHyH/3f6D/9GuLUHIIg1Cns\n",
2172 "2qHMoLCWUrefbccK4PqknUypxp3Dzm3cm5ODGdhP2rGqSTL7qRJz7taR8U4qzYKrv6+i2cBu1IvY\n",
2173 "jDqSBaXgkWW5xbyePkGUTbMEQWhhWcUiWf7pb/+EaGgw4vlp6GA3idNtwdnl+thrMzB0mCIwpXSW\n",
2174 "PBYzVUki2RW5sIS0t4lcLChjsel5zX+nENcHR1j0Gwc2q1KTsMRieE5SWE3ODm1gl6LHDCtVi/6L\n",
2175 "bIUeIcn9u0KnNQo8jO+ykymz2qdjBQqBfZDNwEGN66rUzN2VcSVZQicKTDlN7KcG61oFM8dxNoOa\n",
2176 "19sDl3+edaaFFcCt2eNxYK84m2Z5JmzE+hnYe4wDU8UqLgHMFkOLn0t34crwBnZXd+TCk4O7LE9e\n",
2177 "afCOWh7DnTVcn7qktNsnZxFMyu99wWfjKFMm0cQMGcbAXpPk+qhF+X6zi/19Vv/i3n02E0n+p8+/\n",
2178 "NvKLOpMqYndpb+6Zp/1I5XIHW2zCGSBbTqA3yqzd6TRPy7JcZ1gN1rFK3388lJ9GMbAPF2UUTR8x\n",
2179 "7vAjip3F22Wfjc0nW1R+/BbGv/hXGh/3vvFqS7xNu3Ibe+gdNsz+wbAWoypVrPLaopu/95M9Sm2d\n",
2180 "q1tTk3wYDFLrAZW1ryyQPSFy4TBdwmM1tHgoksUiVUnCY+n/3tcysEfqhVa6VMOsFzHVUQSZE0bZ\n",
2181 "NKt5HHjvvX1MZj1rdwbPCj2p1j8OsXy5ewdj1mWiXJMIafisVNQCKEXinMvFXroHcmF3C2l3E3F6\n",
2182 "DqFLR2oQT1yznNcukBqwsEoUqrhSCawjMKxUiYFppCGRC3Ik1NGxclrHsFuchAYIjPeegnldkmrs\n",
2183 "Z3O4zWY8lv5NBteYheQALKtkPN/wEw8i27KyALY8buFpfTNwbogw5tY4m/6jvWypyrt7ab58bvjo\n",
2184 "m0+izr6wavJZ9Yqzada4b5DMwLmuhVWyWMVWkztao7rzq9TWP0aWBmt3Anid3SGhj/fvcLEHGNR5\n",
2185 "42I9ePk4H1AvClyftPNhU9fqqt/Hg3BkIDPmUabEmPV4lV0JZO5eWL1/cMg/+fAO/+CrbwzU1u+m\n",
2186 "XtucgiDgWD1Puq1rpRP1TDgDXHnFwdvfXafadrNXRz4WDV+QltL3H+O8NoC/qi7R5UEasmOlRtlo\n",
2187 "6ZLfhvet/wvjm38R0X18gRh/5QWyaxuUo9o/K3n7/jMdA6oKZcu8OONkZcLCv/yw9TU7YbUybrHy\n",
2188 "NN7972Ga9FIrlignunt0+kmTuJ5UiOuDFPUKy6rZwH4cxhxtY1ilTxhl0yy7w0QuXaJSqfHxRwd8\n",
2189 "9etXWbt71HOJ5rQky7ImZqFZgiBwNdAZbyPXjeu+qePuUs/NQNcYgl5P9cN3EHsa14csrPpE2zQr\n",
2190 "ni5gzaQwT/YehfWSOAIkVIoebwQ2a1AC+2nQ12OZMJJpnmv+wXyB7oE7Vv2p682yLiksyJV6FioM\n",
2191 "XlhVJSVWakysIdeq6Oz9C7rvbyS4NePAaR79HvRJ0pkXVpd8No7SZZKFCplkEWcPhpUqBRLa+wVs\n",
2192 "XZ4j3yUzMFWsYq5UOyp4welGcHuQdgdPz55wdY+1UTcC2yXLMqn6KFBaX0Ncvtjy+ZvTjpbCymu1\n",
2193 "YjUY2OnhjVC1FS+y2FQw+qddJOPaPqtgNsvf/M73+Luvf4lp58mAa5l0sQMO2izn6goZDZ+V3z0D\n",
2194 "tgwTfjt3320thJOx3HCohXuDoRZUjeKxiqQO8XUprFYLh/gPH2P42i+1fFw0GRn/3ItEvveO5tc9\n",
2195 "a+O6qlA96PbXPzvDW0/iDUaNqn4+K0EQsJ+bJ7c+us9qJ1lkwa0RZTPAdhlosayMDZZVpIm6Xs0X\n",
2196 "KOwHsa0sjPxYm2VzKqPAJw+C+KddnLvsw2DUcbA7nEF6FD388ACDSYc30Ps9qowDW8eTiVges9WI\n",
2197 "xXpccPb1Wc0uUX37uz03AneSgzGsVDmunCP7eKur77VZsZ0QFZcL0TD6TVYYYRSodKw6u2RL/osD\n",
2198 "+ay8tpMHMQcTu1RNM339VapcHgvJAZALyXhhyI7V7HHHKtbMsuo/CozmyoxZ9UgxBQ46yPX7/y+m\n",
2199 "dVVnXljpRYEbU3ZuH2TqBugB1kEdJmpVqacpW10Z1VKqUEVfqmqa+Yb1WXXrWFWqZbZCa6xMdUaU\n",
2200 "5LcP0NutmLyeeseqtbC6VTewN3eoBh0HbtdRC43fR6fNsyrXavzGW9/hL19b5eXZTsbWsMqmunus\n",
2201 "ABxXOjtWoBRWocQ+r71xnnf/dJNy6fjCG48OvhFYyxfJ7x1iv7A48GNWRoHDFVb/H3tvGudGQp17\n",
2202 "P1XaVdq3bvXu3tt2t+2xPTPMDgNmCFsgIYEAuSEsCRCSkJBAwsyFYUhIAr9AeAM3cCHLe0MgYRm2\n",
2203 "XGaBYRhmH894adu9u1u9amktVdq3qvuhVN1aqqSSVG23B/7frFZLslqqOnXOc54nSG+Kdqw4joP9\n",
2204 "B/+K7wyeQoyrPSF4Tt2C4EOP19wOlAorhaNsxAglcvCYNLAbNHjX9V347M9XK4TfJ7ydON3Az4oa\n",
2205 "bU9ntRJNi24EylnbByRGgQm+s1mur0rMLME0MtDWybkcYRR4/tk1HLmhFwRBYOKoV3SErSQhfxw/\n",
2206 "+9EcXvNmccuWcsSMQgMbu/oqgf46HSuAF7Bz0TDI/iHJ+/hkWi0IqI0GGHq9SMw3vmiNrWyiKENf\n",
2207 "VA+yowtcaKup6UO9jpWczUC3kR8FtmOi64+tIQ47Jjvk/f/ldqyaEa8Du5ZFHSYt8kW+AyW3YyXo\n",
2208 "q7LBCLQy9FWL2ynEs0Uc69rfburNcNULK4DfhHvORyOZyMJkaeywTRAEHO76AnZ+FCh+ZU1nCkAq\n",
2209 "t7MRWA5fWMnXWUlprC4HZuB1DMCgrdUH7dgsxCLg0qkaZ2OvRQe9hsRydPdDLLewWo6kMVDV8hXT\n",
2210 "Wf3140/AYzTiXcfER5XNwnespP92lsnKaBuBDlsvArF1uL1m9A87cfrxlZ2fxbaTssOX4zOL/Im0\n",
2211 "CR+oVsTrIYnCqnj2GXBMDMGjt1fYZQi473wJwo89V7NynmcSSK9uwXxIfPtKKXIFFvFsEY7SWPUV\n",
2212 "Iw6YdWp8+8LuZ+p4lxent7bqnhhMbWYGio0CV5oYK9V0rEy77uvlo0BGAWPQckxmHcLBBOhoGkNj\n",
2213 "/Elv4kgX5i/4UZTYtGyXXLaAH/zHWdzx6nG4PPVzJgGgz65HMlfc0ZwBJcf17srCSk7HCiQJsm9Q\n",
2214 "9OfNWC2UI3ccmFz3g+hsfQwIAIRWB8JiB7ctT5vKe1gFazRWAO/A7gvOg21QpFFaLVRtuq+vh9cQ\n",
2215 "zmsw4ZKnt+TF6/U7VrlsAflcUXZ6BQDoOl0oJlIoxJMYchqwGE6j26rHFpOtuBgTwx/n80hzoQh0\n",
2216 "MjYCH5gP49Soo27qwrXGviisTnRbML0ak/RBEsPZoLDSd3nA5guIz9Wu+MYyBRQSOdGZs2rsMNi5\n",
2217 "adlXHQ6zB3QqgkKx0qhQKh8QABihsFqahWpoTNTZ+Hh3pe3ClMxomxURV+tqndW3Z2ZxemMLf33n\n",
2218 "SxXZwOA43jG/2nW9HGqoH5nNAApVrfIOe8+O+/rNLx/BC0/6djQ0zXhYNTsGBFozCRUrrLhiEblv\n",
2219 "fAW6t7wbY50WzIrkBmpddpgmhhB58kzF7fSZS7DskTFoxetO5uGkNDsHL4Ig8Ee39OK/zgWwUWrv\n",
2220 "d5nN0KvVWI5Jj7eokX4k51sbBRZYDptMFr0iVgsDNnlOy6JBzInaUaASUTblUGYdgltxTB7vAVk6\n",
2221 "RlntBjjcFFYWm8/ybATHcXjo/ovoHrDj0DHxSJlqSILAZNV2YLnVgkA9jRXAe/qpDh4FoRU/EW8l\n",
2222 "EnDKtFooxzI5CuZ87cVVNdkNP7Rt6KsEmtkMFPOwEjDpLbAY7diK1k/zANoXsM9sh+A1amHUyDse\n",
2223 "GIwasCyLTJ2A5FhJX9XMsZ4gSV5ntbMZmIJeTcJu0MDfwNPNH8+WhOvhhh2rbIHFT5ei13yETTX7\n",
2224 "orDqMGvhIAANpW185xIOT30vK4IkMfgHb8f8X/1Tzc/oTAFpJiM6cyZcHYBKDS4gT/ioItWwUU6E\n",
2225 "44GK22fXz2JUxBgUAOhzM7zj+uJMhXC9nOuqbBcOul1YjESRLUhrFLIFFqFEria8slxnNR0M4rNP\n",
2226 "P4PPv+oUTFr573c90qk8NBoVNHXcckmNGqbRA4jPLFXcLowCAb6tPXHUi6cf5YvhaDgl28OKNwZt\n",
2227 "7kTayihQrLAqPPYgCIsNqqM34KCHwqWg+IHVc+pmhKrGgfwYcO/1VcEEfxVZjteiw1uOduJzj6/u\n",
2228 "XEicaGC70E7HapPOwkVpoFfvHnY4jsNqjEa/TL1ObcdKi2BpFMgHMPMnJMHDSim0ejUSTAaTJyvH\n",
2229 "5hNHu/ZkO/Dcs2sIBxN42WvFjw9STHWaMF0qrDiWQ2CzUrgO8COrVD6PhERnhfT2wPCRv5V8jlWa\n",
2230 "kf33KsfaIDNQgN0KwtiiOWg5pLcb7Ja8zUAxD6ty5AvY27NcWKbTOCQz9B7gL5CsjvqZgXST+ioB\n",
2231 "wXJhyGnA4na5gL1RYVWyWtiONtwIfGIlhjG3ER6TMuei/cK+KKwAYNSsRUZmtwooCdhFOgPl9L3j\n",
2232 "jUjMLCLy9NmK25lkHvlsASaR1ihBEFCNHUZxTn4gMx9ts3twZTkW8xvnMNYj4rheLIKZXoD1yDjY\n",
2233 "pdka4brA0S4zZoLJnbV4g0aDfpsVs9vS9gC+WAbdVh3UZOWViaCzmp0L4I8feBgfv/02DNqbT42X\n",
2234 "IiFzm5N3YK/UWXVYu7HNbO202W+8YwiXzmyCjqYRbSLOptmNQIBfVuDitGwdRjafRiqbgI3aPVhw\n",
2235 "mTRy3/n/oX3Le/jMQA+Fhe3UjultOZ5TtyL48BMV3dDYFfCvAnaF69X86iE30nkWD87zBeaJLi9O\n",
2236 "b0kb3hr6u5ANbqOYbt6JfCWWRn+VD1MolYJBo4FZJ29MYaR0FRoru0GNVK6IbIEtaay0YAsFJGYv\n",
2237 "Kzpe9a/RIAiixkdq9HAnludDFdrAdgls0Hji4QW89reO1r1YEWPKa97ZDIxFUtAb1DBWXbASBIFe\n",
2238 "qwVrdbpW9fDRNPqa2AgUMB8aQfziYkUguxhkMATrQPuFFeHtlW0SKuZhVY5cB3YPZUSoxc1AlmOx\n",
2239 "lVXjZI+4d5gUNnv9cSBvtSBfXyVADfMLYMMuIxbD8i0X/IncTseqkYfVj+bCuOtF1q0C9lFh1aMl\n",
2240 "sd2EVKGRxgrgHX9HPvwezH3iCzsns2yBhSZXgMWmB0GKt0b5wkq+gN1l8WKb3j0ZbUV80GuNcJpr\n",
2241 "r4ASCyvQdTihNhtRvDwP1aB4YUVpVRhyGCrclBvlBq5E0hiQECh2D9jxXz8/j9eOjuDlg/IF3nJo\n",
2242 "tBEoYJ4cBXOxchSg1ehhNth2On6UWYdjN/bh4e9egFpN1uSiicFmc0gsrMA8Li22FYNQqwGjCVxc\n",
2243 "3glmm/HDZfGCJHa/NvkffQuq8SmoBvluGaVVocOkxbLIgY4a6QepUe9sR3Isi9jzF69IYRVKihdW\n",
2244 "KpLAB2/tw1ef20Q4lcfJri6c3tyUHIWTajWM/T2S+sV6iOqrmhCuA7UdK5Ig4KI0CCVzfE6gUYPU\n",
2245 "0hp0nS6oTc3FMtVj5twWOHA1liBGSovuATsWLgUkfrM5spk8fvD1c7jzdQfhkNmtLeeAQw86U0A4\n",
2246 "lS8FL4u/t3y0TeMtYzHkhi9Xo7GaofM46ua4AoB2exuOA/LGn/UgO3vkjwJFPKzK4QurxpuBrjZG\n",
2247 "gZF4ABlVB455m/u/Wx2GugJ2OpKC1d58x8o4yFsWdVt0iGUKSGQL/GZgg8IqUNJYZUP1xeubTBYr\n",
2248 "0Qxu7G/+s7Tf2TeFlYnjECywiMu88rM5jIgzGRQaWOx733gKbC6HwA8fBcCPAZ3g6rZGyVYE7Mzu\n",
2249 "dpBUPiAgOK6Pg91YBWGzgzBLt9T5eJvdceCUx1M32mYlmqkRrgs8nvKDYkh84PoTjf47TROnMzDL\n",
2250 "WDqwSDiwd9p5AbvAiVsHENhg5AvX55Zh7O+Byti4uKuGbMIkNFgVZcPGIsg9eD+0b3pHxf0mOqid\n",
2251 "NIFyCILgzUIf5MeBycVVaCympkJKW0UQlIox5DTgV8ad+OJT6+izWngtVLxOZNRoa9E24oVVDANN\n",
2252 "nKQ1WhU4rjJexm3S4nIkDTVJwKhVgbm4ALNC/lUALwBPxrOgTOKZgQePKDMO5DgOD3z7AgZGXRif\n",
2253 "aq1jQxIEDnfw48DAJl0jXBfgBeytdaxW6easFsppJGAvsiyoaAQdw0oUVvJNQqU2AgUOdIzDF5xH\n",
2254 "ka1/fmpnFHg56EMORgw5mpsmNBoFxpp0XRfgLRfWoCIJHHAYsBRJN+xY5QosmGwBTqMGuQbmoA/N\n",
2255 "h3HnsB3aJiZV1wr75n+UYrLwuCicEYnbEEOlImG1GxAN1/8QEySJsbvfh/lP/RPYfAGxTAE2cKIb\n",
2256 "gQJkdz+4BANWpv7GbancDJyTyAcEAPqMELwsra8SON5txvMbZQL2BpuBfPhybXHxwOISfhxZhbmg\n",
2257 "Ri6j3MhCIMFk5XWsDg7xXjZVOjFPmc4KAHR6DW45NYruAXkHGH4M2JpQmbA6wMXkbQby4cu7+qrc\n",
2258 "/f8HmltfCdJTeRI86KFENwMBwPPKWxB8mC+srtQYcJ3O4JlVGlNe6c2ytx7txHIkjSd9dGkcWF9n\n",
2259 "1Yrlgk9kscJHM7KF6wBfnFaPAz2UBpcCyd0omwvzTTnwN+L8s2uYOtkDk0UvWlgNTbixtRZrO6j5\n",
2260 "hSd9YGJp3PEr4l1suUx5eaNQ/0btRqAAL2BvsWPVpNVCOZYGOqvIxjYKWi0M5va7jYTLA46hZQWt\n",
2261 "S3lYCRh1ZjhMHmyGV+o+jptq3ST0ufVlePRFqEWWmephcxhA1/GyijVpDipgPMCnl3Ach+GSzqq3\n",
2262 "VFhJdbQDCT6rU0USyIYi0LnFj+FFlsND85EX5RgQ2EeFVZxOY7zPitPr8vO3nO7GDuwA4LrjBhh6\n",
2263 "OrH+798HnS6AKhTrenoQJAnV6CGwMseBrqrCanb9rORGoGC1UKyjrxIYcRkRTuURTvLi3AM2G8Lp\n",
2264 "NKJp8S/RcpTPCCxnIRzBfY89js/ddUpWbmArxBt4WAmoTRR0XneNI36HbXczUODI9b24/S55mile\n",
2265 "uN6cvkqAsNllbwYGYxs75qDs5ioKz/4c2te/peZ+4x4jZiU+l/brjyC9soGMP3RFjEELLIe/fdSH\n",
2266 "3z7uRVedrqJWTeKPb+nDF55cx5S7A89tSPszUcP9TQvY+S5YtmaxwicjfLma6nGgx8Tnjbr3QLie\n",
2267 "yxYwe34Lkyd6KmJtytFo1Rga92BuWlqb1ojN1RiefvQyXveWo1Cr2zssT3pNOL8ZR3CTQUdXnY6V\n",
2268 "RKxNPQSrhd4mrRYELIfrbwaGLm8g7VDmZEuQKpAdXWADjb3GGnWsAHnjQI+x9VHgxVAYg5bmC6B6\n",
2269 "HSuW5RCPpSsycSi3eW0AACAASURBVOWitVtA6njbhGEnr7Oy6tXQqEhE0uIX6P44H77McRyyIemt\n",
2270 "wOc3GLgojeSE5Vqn4Tf4+9//Pu655x7cc889uP/++2t+/sQTT+CjH/0oPv7xj+PTn/40ki1W6wyd\n",
2271 "wfEhB06vMfKtDtyU7JT5sXvej6XP/gtiEQa6fLFhBc/rrOSNA8vF67HENhIZBt2uWg8YNpdHfO4y\n",
2272 "zJOjsjpWKpLAsa7drpWKJHHY48Z0MFRzXyZTQCbP7pxcAIDJZvGHDzyED9/8Eky4XbJyA1shwWTq\n",
2273 "Wi2UIzYOrB4FNkt8eqGNwkr+ZmB5xyr7n1+F9jW/CcJUe4Lps+lBZ4qIiaxAkxo1XC+9EaGHn7wi\n",
2274 "G4FfP+uHSavCaycabxpNeU24oc8CX1jfuGPVpOXCBp2Bm9Lu5PgJrJTibJqh2iTUTWmxsJ2Gi+IP\n",
2275 "6EqOAmfObaF30AGTRQ9TWRBzNRNHvbjUolloOpXDD79xFqfecKhuJ10uQw4DknQGaq0KRpN4Md1q\n",
2276 "x0qwWtC1GH9lLvnZcRKZlLGVTRTc7ZmDlkN0doNrMA6s52FVzmDnRMPNQA9Ftey+vsxkcNjTfF6o\n",
2277 "xWZAgs6ALda+p3E6A6NJB3WTSxACgtH2cMnLCgD66uisAiXhejGRAkGQUFPin+cfvcic1qupW1jN\n",
2278 "zMxgeXkZ9913H+677z74/X5MT+92cYrFIl544QV87GMfw8c//nFMTU3hxz/+cdMvolBgkU3nMeg1\n",
2279 "Q60i4IvKC3p0eBoL2AUsk6Nw3HoC6X//DlSZPGwNxHzNOLA7zR2IJIJg2SLmNs5htGuqQuAsEJ9Z\n",
2280 "grG/GyqCBRvy80Z8DTheZbsw1SHuZ7US5Y1BBa8SluPwFz/5KW7p68HrxvixSKPcwFbhtwLlbXWZ\n",
2281 "D4+AqYq26bD1wC8j5FQMtlBA/NJiy5lwvJeVzFFgyWqhODsN1rcEzSteL3o/kiAw7jZiRsJ2wX3q\n",
2282 "Fmx+6wGk17YUNbGsZjaYxPcvbeNPb+uT7WHzruu7MRfMIpFWS440qKE+pHzrNSPdeoiNAQssi/V4\n",
2283 "HL1N6nXELBcKLAc3pUF2KwSCIKBTINCa4zice3YNR67vAwDJjhUA9A85wcT4TdamnoPl8KNvTWP0\n",
2284 "cCdGDsrLh2uEiiQwriOhqXOM6zSZQGeySOel/Y/E4MOXW+tWAYDO5YDaZER6VbwIjfu2gE7lCive\n",
2285 "y6pBYVXHw6ocOQ7sgni9Ffd1f1aDG3qbXyxSq0kYTTrE6drzJt2k43o11CAfxtxv18PPZJEpsOi1\n",
2286 "Suusdjys6piDRtN5nNtM4PZB5TbT9xt1C6szZ87gzjvv3Pn3nXfeiRdeeGHn3yqVCh/4wAegLfkh\n",
2287 "ZTIZuFu42kjQGVBmPVQqEid7LHhuXV6L2uE2IRKU17ECgNGPvAeq7/4Q6u1ow44VeWAErH8DXKrx\n",
2288 "gVKj1sJssCGSCNXXVwljwMvzIPuHJJPjy7mu24IXNuJgS1/UKY+4zorPCNw9cX3p+RdAZ7L4s5te\n",
2289 "snNbvdzAdog327GarhwFdNh6EIitt3QwSi6tQud1Q92iJqOZvMAQvQm3xYvs178M7ZveAaKOD9iE\n",
2290 "R1zADgDul96A2PMXYJkaVSxypZpMgcXf/cyHP7ippyKYuBGUVoU/uLkHVtUAnlkTP/mpjHro3E6k\n",
2291 "ffI7NL5YrXB9K56Ay9i80WSNSaiJ79K6jJqdbpUSxrf+DQbZdB4Dw/yVdb3CilSRGJvsxMz55kTs\n",
2292 "zz2+jEwqj1tfqZwmDAA6ORYJnfRGLUkQ6LGYsc7Il14AvIdVK1YL5VgmxyTHgZmNANRdyhSYQGkz\n",
2293 "sFHHqoGHlcBAxxjWthdrzKDLoTSaltzXt+JxFDlgqqu1Cy2b04iYyCZyLJJqSbguYBziw5g1KhK9\n",
2294 "Nj1WIrzOSqpjtRtnIz0G/PFCBDf1W0FpW+uiXQvULazi8TjM5t38HovFAlqiffzYY49hfX0dN910\n",
2295 "U9MvIl7mg3SiahOuHg4Xhch2SnbCvKHXi+jNt8Bz5lHo9PXX+Am1BqrBMRQXGnuXALsC9rn1cxiT\n",
2296 "clwvC15WDckz/uswa2HRqXYSxgUBe3URslKmr3rMt4r/ujiDz77y5dCqdj+8UrmB7ZDNFMBxgE5m\n",
2297 "KjlvubBQ8fopvRlatQ50qvluWiuO6+WQdqesJYV0LolsPgPqwgWgWIT6JS+te/96hZXGZoH9hqN7\n",
2298 "mg/4lWc3MOoytnRVeFO/DZ0WNb57Sfp9oUabMwpdiWbQX+243kSUTTliQcwA4KK0JX2VMkWKIFoX\n",
2299 "bFnqjQIB4ODRLsyclbaqqGZ9JYLTj6/gNW8+IjtxQi66ZA6+BvY1jaJtxGjVaqEcy+SoaLwVABS2\n",
2300 "AjD0SIvIm0WOSWgjDysBg5aCy+LFRrh+3qGHan4z8OnVRVgQhUHXWhFktRtAR2ufMxZJtzVe5k1C\n",
2301 "eU3skNOAhe1UXZPQQCKHDrMWuZC4OSjHcXhg/sU9BgQaFFZmsxlMmcCRYRhYRESL3/72t+Hz+fD+\n",
2302 "97+/pRfB0GmYSwfdI14TZkJJZBrYKAD8yVxvUIMRaYFK4XvpKViWLyG52FgjQjbhZ+W2dmEjfBnr\n",
2303 "4SUMdYoXTeXC9erg5Xoc79mNt3FTFAwadY3wdLk0almlaXz0kUfx96deDjdV28VRWmeVYHjhutwO\n",
2304 "gc7jBEGSyG5V6sR4B/bmx4HtbAQCpY6VDPH6Nr2FTnMncv/1z9C+5d2iMUTljHuMmN9OSeZqjd/7\n",
2305 "AfT9zhtbes2NeG6NwVM+Gn9wU+vh2u886cValNwp6KsxjfQj2YTlAm+1UNkl9jURZVNO9VYgpVWB\n",
2306 "0qrgpjSlKJv2x6vZTB7zF/w4fHz3PazXsQKAzh4rOJaPkWlEKpHFD79xDnf92mSN6Wi7cByH+HYC\n",
2307 "61wpF1WCRtE2YqzSNPpa3AgUqLcZSARCsPS1bw4qIIwC6xW7jTysyhnsaGwU6m5BwH56wwevofXM\n",
2308 "SZvDiJjIhjzdojmoADXUh+Rl/rg84jJiKZyu62XFd6y0JeF67UXdpUAS4IBDHcp5zO1H6p4drrvu\n",
2309 "OjzyyCM7/37kkUdw/PjxnX8Xi0V86UtfAkVRePvb397yiyjvWBm1Koy6jDi3JW/E53DXj7apJlHU\n",
2310 "IHPbnZj/1Jca3rcZAbvL0omnZh9Gv3sUWk3tWKyYyiC1vA7TxCDYxRmQMjtWgIjOqmocyHEcViJp\n",
2311 "dJjV+MMHHsL7Th7HMa/4FZjSOqs4XT98uRqCICR1Vq0I2JnpubZW6+XaLQTpTdwRMYDs6oX60LGG\n",
2312 "9zfr1HBRGlGjUIDfjjL0KndlLsBkCvj7n6/iQ7f1w6Rrfcx4fY8HKW4Tn/7ZimhxSDURbZMvstiK\n",
2313 "Z9Fb9Tnx0TQGWjhJG01aJBOVo5aPvmwAvTZ9KXy5/Y7VpTOb6B9xVQTXUmYdEoz0RRxBEJg46sXM\n",
2314 "ufojUpbl8N//dR6HjnVjcEw5PZEAE01Do1FhpNuCC37pY2Of1Qof02THqsU4m3KEUaBYsaMJbcN2\n",
2315 "QLnCCiYLQBBAXPr/KWcjUICPtmmwGdiC+/qlUARD1tY7S1aHATERywXedb2NUeBAD9K+TXDFIt+x\n",
2316 "CqfgMWkRzxWRzFU2P9L5IjL5Imx6NR/A7K7tSj0wH8Yrx5yKjOr3M3ULq/HxcQwMDODuu+/G3Xff\n",
2317 "DY/Hg8nJSXzta18DwzA4c+YMnn32WTzzzDO49957ce+99+L73/9+0y8iHsvAUraGfaLHgtMydVbO\n",
2318 "JgTsAFBIZKF/7atAn7mE6On63SjV8EGwKwvg8o3n5W5rFy6tnpbUVzEX5kGNDYCIRwGSBOGUf0Cd\n",
2319 "8powF0ohXeriVftZBRN5GDQqfOapJ3HQ5cKbDx2UfCyldVYJJivLaqEcy+FRMNU6K3vzhRXHsmCm\n",
2320 "2/QsMlJAsdDQ6yYSWMb1CxFof/Ndsh/6YJ1x4F7AcRw+/8Qabh+04Vi3ufEv1EFNkpjwqFHkCrj/\n",
2321 "Yq2mj98MXJH1WBt0Fh0mLbQiG4GtjwIrO0cneixgE0nkQhFQg6136oCSaP25NRw52VtxO2XSIpPK\n",
2322 "i25fCUwc6cLseT/YOvKEZx5dQrHA4uaXKxe5U46/ZLMw1VkZyFxNsx2rQptWCwK6ThdAoKZrXUgk\n",
2323 "QRTycHnbXzwQIAgCpLen7jiwkYdVOXKibVyUsalRIMtxWInnMNnRevC0zWEUdV+n2xwFqgw6aN12\n",
2324 "pNf9GHIYsBrNgOWAXmtt18ofz6HDrANBEMiKmIOmckU8sULjFSP1Y25eDDS8pH3961+P17++cvvp\n",
2325 "rW99KwDgxIkT+OpXv9r2i4jTGRwY3f0yneix4L6f1J9jCzhcFEL+JgSYqTzcXXZ0/fm7MH/fF3H9\n",
2326 "d78oWT0TBiPIrl6wl+ehGqu/Fu+yeMGBk3Rcp0v6qmKpW9VMxW7QqDDmNuL8VgI39Fkx6fHgwaVn\n",
2327 "dn6+Ek1Dq87jcjSGr73x9XUfu1xnNazAFpLcOJtyLIdH4P/BTytu67D14Pzy0009TmplAxqbGVpn\n",
2328 "8+MkAYIgeMsFOlp3K8j5xJOIDPbD1cTWzoSHwgV/Aq89qHxXQoxHlqLwRTP4s9ubyxqT4mS3FxtM\n",
2329 "At84y+GmfluFDxY1MoDEog8cxzX8LK+ICNf525uLsxGotlsQiF9chHliCISqPVHs1loMxTyLvsHK\n",
2330 "EwCpIqE3apBK5iSXNRxuCiaLDqtLYQyM1BYIvsUwzj6zhre//yUg98hxOrDBoKPbCqvXhH98Urqg\n",
2331 "6LM0p7Hyt2m1IEAQxM44UN+1W0yk1/2IWx1wNrFsIQdBwC51DG+mY9XvGcP69mUUinmoVeI6XY+R\n",
2332 "wlZC/hRlJRaDlshjyN16oW111OYFZtJ5sCwLg7FxLFg9qME+JJfW4O7vhseshS+a4QXsdBbjnt2R\n",
2333 "XqA0BgRQirOpHAX+7HIUU14T7DJiyq519oVBaLwqxHfQoUcmX8QG3djJ2CHTJBQAckU+J9DjptD9\n",
2334 "plchT8cRfPDndX9Hru2CEHUyKlVYlfRV7NJsQ/8qMY73WHbMUw963FiIRJArhZn+fCWItXgIn7/r\n",
2335 "lKwNKyV1Vgla/kaggPnQKJhqLytb815WvJ6mdeG6QCOdFbsdRN+sD8wrTjX1uBMeCpckLBeUJpjI\n",
2336 "4Z+e3sCH7+iv8YpqleNdXlza3sRvHunAPzy+VjG60dotUOl1NV0HMXwiwvVsoYBQKoVuc/OdNQPF\n",
2337 "d46ql1biF+cV8a8698wapq7vFc0SbSRgB/iu1cy52u3ABJPB//3mefzKm6aa/s40Q2CDj7IZdRmx\n",
2338 "yWQlY8K8ZhO2U+md40gj2rVaKIePtqnsWseWN8HYHTBqlD0t1etYyfWwEtBrDeiw9WAttCh5H0+T\n",
2339 "7uvTwRDMiKLT1tv4zhIYjBoUiywyZd55vOO6se2xGzXYi1RJwD7iNGIpnBKNtvEnsjuFVS4YrulY\n",
2340 "/SKI1gX2T2FVJuAkCILfDpQhAnV6KERkescwmQKoIgub0whCpcLo3e/F/F/9r7p+PCqZuYEdth78\n",
2341 "jzs/BItRfAtrR7i+ONOUcF3geLcZL5TeD0qjQb/VgtntMPyJBH60sI5fPzSIbou8k5SSOqs4U1kU\n",
2342 "y4Ea7EEuFEGe2b2qa0VjxZxv3XG9nEYmoblv/Que69bB3tPcyLHPpkcsna8rIFYCluPw6Z/58MbD\n",
2343 "bgy72jeYFJj0eLAcjeHUqA2JbAEPLVS+RyaZm4G+aG04+BoTR7fZ3HR8B8B3XbU6NdJVBqzM9ELb\n",
2344 "UTaZdB6LM0Ecuk48q66RgB0Axqc6sXgpgHzZAg5bZPHf/3keR27oRf/w3p1cOI4rhS9boFGRmHBT\n",
2345 "uOgXPz6qSRJekwnrMh3YV2mmbeG6gGVyFPT5SgF7ZGULeZdLcf1N3cKKiYHQGxp6WJUz2Dle14G9\n",
2346 "WfH6+UAAquwqOuytj7AJgqgZB9KRdFv6KgHjUN9OcDa/GSguYPeXd6y2I9CV2S2sRNMIJvI42aNM\n",
2347 "Yb7fueqFVT5XQCFfrGlXnuiV52dFmXUo5FlZmqFoMgdNobhTCLjvvAk6twMb3/hvyd8hRw+huHAR\n",
2348 "HFv/qk6t0uBVx2vjTQAgzySQ3QrBOOAFu7YM8kDzB/8hpwFMtohAnP9/TnV04PmtLfzxgw/Dobfh\n",
2349 "FcNdDR5hFyV1VgmZAczlECoVzBNDiF/cveqzUk7kClmksvLHusz0fFsbgTuvx+oAKyFgL64sojj9\n",
2350 "PH5gpytyAuWgIgmMu/deZ3X/hRDyLIffmFLO/wcAtCoVDnncOB8M4k9u68NXnt1EJLVbzFAyMwPF\n",
2351 "wpd9sVhb3Y9qywUAYBToWF18YQODY24YJcZRcgork0WPzh4rLs/satOe+MkiSBWBG+8Yauv1NYKJ\n",
2352 "ZaBSkzsdsclSbqAUzeisfDEa/RaFCiuRzcD46hbYDuXH5kRnj6T7Ohfyg5DZrRIY7DyIpYC0zqpZ\n",
2353 "9/WzW1twqFIwaNvblKsWsMfa3AgU4C0XyjcDU+iz1npZ8UHvOj7OJhiB1rVbWD0wF8apUQdUIl3g\n",
2354 "FyNXvbBiSmPA6quU67rMmN5KIFdHKArwlbrDLU/A7g8lwWnVO54xBEFg9J73Y/EzX0UhJS5eJq12\n",
2355 "/sS7Jk/zJQZzfpY/4G+uguzoaurqaOd1EASuK+taTXrc+Pwzz8FtMCKTI9Bnk981UtLPKt7CKBDg\n",
2356 "HdjjF3dHAQRBlCwX5HWtOI5rKyOwHFIiL5DjOOS+8b/BvebXkSQKMBua13JNdOxtYbUSTeMb5wL4\n",
2357 "89v79+SgdaLLi9ObWxhyGnHXmBNffGr372Ma6UeigeVCrsjCn8ihu2ojcIWmW7JaEDBSWqTLdFZs\n",
2358 "Lo/k0irMY43TDKQQnNanrpceycgZBQL8OPBSaRx4eS6Eiy9s4NW/MQVyj08sgU26Ih9wymvCuS3p\n",
2359 "i5Vmom18ClgtCBh6vSimMsiGdr93qbUtqDtbF3BLQXZ0gQ1uiV4cs9sBkDL1VQK8A7t0x8pVGgXK\n",
2360 "8TPLFYtYisYwZGvffqC2Y5VSJCKJGtodBQ46DFiKpNFp0cKfyCFfdn4WOlYFOg6VTguVgf/O54ss\n",
2361 "frIYxStfpIHLYlz1wioey+x4WJVj0avRb9dLtrHLccosrLa3UyCMlVeitmMHYb/+CHxf/k/J3+Nt\n",
2362 "Fy42fHwpKoOXm9dXCRzvNuN0yXbhhu5uHPN24r0nXgK3qTaDrRG9BxxYbXMcWMgXkcsWJK/u62E5\n",
2363 "NFKjs+LDmOUVVpmNAEi1GnoFoksIm1N0FFicfh7sdhDho5PwWLtbGlFMeIx7Vljliyz+9lEffvdE\n",
2364 "/YDldjjR1YXnN/kC4W3HOrEUTuNJXwyAvI7VBs3rLrQqkY3ANk7SRlOl+3pifhnG3i6ojK1rl9ZX\n",
2365 "oiAA9AxIm6pSZh2STOPCauRQB9YuRxDcYvDAt6fx6t88IpnbpyS8cH23sBpzG7EWy9asxgvwJqHy\n",
2366 "R4HtmoMKEATBG4WW6azymwHoupW3ICF0ehAWG7jt2u1WLhQAIXMjUKDfPYLN8ApyBfHPAaXRQE2S\n",
2367 "stzXZ7fDcBtU6LaLj56boVrAHlNoFKjv6UQ2GEExnYVFr4ZFp8Z2Mg83pcVm6bvAcVxZnE0U2jJ9\n",
2368 "1VOrNAbs+j07Ru1Hrn5hRWdgtop3cOTaLjg88rys6EgKGpGD28hf/B5WvvyfyG2Ld3BUY4fByjQK\n",
2369 "FX3ec7OwHhkvBS83r68SON5twdnNOIosh16rBf/8utcgGC/iQAtZUL2D7QvYE/EsKIteVOTbCLOI\n",
2370 "+3IzYczM9DzMk8o4bIuJ1zm2iNzXvwzdm9+FYMK/s5zQLONuCvMhaaPQdvg/L/jhpjR7Kgg90uHB\n",
2371 "7PY2MoUCdGoSH7y1F194ch3JXBGmkQEkGlguiAnXgdY9rASMJl2Fl1VcgeDl86VuVb0CmrI0HgUC\n",
2372 "vHlx/4gT3/vaGZy4eQC9B67MirmwESigVZEYcxtxMSB+fOyzyOtY7VottGfjUU7NONAfgqlP+cIK\n",
2373 "kNZZsdv+pjtWWo0enfbeBgJ2ee7rF4JBeLRZdNr7mnoNYtgcBsTKOla8eL39USCpVsPQ50XKx79/\n",
2374 "wy4DFrfTpTBm/rsQz/KFu0mrQi4Uhq5sI/DBF3ngshj7pLASv8qUmxvocFMIy+gKJGIZGEWeizrQ\n",
2375 "A++vvgKLn/1X0d8TNgNbybIDAKbCcb31jpWT0sBp1GBhe/fLw4cvN3+V3tFtRSzcns4q3oK+SsA8\n",
2376 "Nojkog9sblez04yAnR8DKlRY2WpNQgs//zFgMEJ1/CaE6K2m9VUCFr0aTqMGKzKDxeVy0Z/AQ/Nh\n",
2377 "fPBW+QHLrWDUaDDidOBcIAAAmPKacbLHgq8+twmd141iJotcVPo7uhJN1ziuA617WO28riqNFXNh\n",
2378 "AZY2Aq1TyRwuz4UkResCckeBAMAWOWTTBZy8tflg3VbgheuVo0CAHwdK+VnJ7VgpZbVQDr8ZyBdW\n",
2379 "bC4PgmFg61FWJyhAdvaA3apNduA7Vs0/Z6NAZrdRnknodDAEig2j0976RqCA1WHcKayKRRZJJqOY\n",
2380 "q79guQAAw04jFqs2AwMJPiOQIAg+gLlkDhpM5DAbSuGWgdbH/tciV72wYmJpycJqxGVEJJVHKFn/\n",
2381 "5C9XY5VlMjBLdHeG/uR3sPWdB5FaqT2xE+5OgCDABZsLWAWA3HYUeToBg9PEb6B42/sCHe+2VLiw\n",
2382 "L0cyNRtXclCpSHT327Dehs4q0YKH1c7zG/Uw9HVVbJU1E2uj1EYgIBRW4Z1/c9kMct/6V+h+6/dA\n",
2383 "EARC9CY8LRZWADDuoTCr4DgwlSvi737mwwdu7r0injCCzkrgXdd34WkfjQuBJEzD/XXHgWLhy4lc\n",
2384 "DslcDh6RyCW5VAcxMxfa62BefGEDwxMe6Bu8n5RZL6tjtXgpgOAmDQ4c4nXc2pUkTmdAkARMVRc7\n",
2385 "k3WMQrstZmwlEsg3sFxQ0mpBwDo5uhPGnNkKIme1wWHeGxsKwtsDzr9RczvfsWq+SzbUOVHXgd1D\n",
2386 "ydsMnA4EQWZWFCmsLDYDEnQGbJEFE0uDsugVy6A0DvUidVkorAxYDPNhzEJhVbERGIzsBDA/NB/G\n",
2387 "HYN2xSxgrhWu+v+2XsdKRRK4rrtxKLPNYUScyaDQIF+wkMzBLjFz1rkcGHjPmzH/qS/X/IwgCNl+\n",
2388 "VtXQ52ZhmRoDu7wA1dB4w4y5RpzoMVeMR5ejaRxooWMFlPys2tBZxenmXdfLsRwaQbxsHNjUKPDC\n",
2389 "PKxTChVWFhu4OL0jbs0/8B2QIwd3uotBerPljhXAO7BfUrCw+tIzG5jymnDzFboKPOH17uisAMCk\n",
2390 "U+P9N/Xgsz9fhXZssK7lgi+awUDVKHC1FL5MttFpK+9YRZ4+i+TiKqxHWhuzcxy3MwZsBGXSIpnI\n",
2391 "1g1+pyMpPPTdi3jtW45h7HAnZkU8rfaCQMlxvbqDOeGhsBLN7CQ3lKNVqeChKGw2MLT0xZQTrgsY\n",
2392 "B3uRC8eQjzG8OajdAUebZpZSCCah5QgeVoSzecH8YOdBLAfqWC7IGAXGs1n4k0mkmHl02NpLCwAA\n",
2393 "tZqE0aRDnM4oZrUgQA32IrnEC9iHXUYsbqd493VaKKzKPKxCEeg8DrAchwfnI79wY0BgnxRWljpd\n",
2394 "j+pCQgyVioTVbkBUJISyHCKdh9stfZXc/3u/iegzZ0Gfqf3CkE3kBpazK1yfATnUur5K4HCnCZcj\n",
2395 "aSRzRaTzRUTTBXjNrY3jegedbemsEkxrG4EClsOVRqFOcweYVBS5fP0r/GwwDDaThb5HGT0GodYA\n",
2396 "BgpcnAHHxJD70beh+43f3fl5iN6E29J6fpmSm4FP+Wic2YzjvTe2fyCWyzFvJ84HQxVGkjcP2DBg\n",
2397 "1+Ox8ROSHatcgUVAbCOwTeE6sBvEnN2O4Nx7P4bJz30UGmtr+p+1yxGoNPymbCPUGhW02loPLYFC\n",
2398 "gcUPvn4WN9w+iK4+GyaOduFSg+xApajWVwno1CRGXAY+AFcEOZYLSgrXBQiShPnQMJgL88is+xGz\n",
2399 "2OE0KjdqLIfXWFV2w1vxsBLoc49gK7IqeayS42V1IbSNEbsFBo0eRp2p6dcghs1hRCySVsxqQYAa\n",
2400 "3A1jdho1UJEEDBoV1mJZsBxXGgUKHaswtG4Hzm7GYdKpMKKgt961wlUtrDiOK5mDSp+cj/dYcKYk\n",
2401 "2K6Hs4EDeyadB8dxcNd5LrXRgOE/fSfmPvnFGj1Vyx0rwXF9cRYqBQornZrEhIfC2c04fNEM+qy6\n",
2402 "ltfsO7otoNvws0rU6TbKgbdc2C2sSFIFp6UTQbr+iYiZ5h3XldQWkSWT0Nz9/w7NTS8D2clrbTiO\n",
2403 "4zVWtta3dvptekRSeTBtGoXG0nn8w+Or+LPb+2HUthfb0gwWnQ59VgsuhbYrbn//Tb14QufCwqa4\n",
2404 "+HmNzsJr1kEjthHY5kla2Ao8/7570f0bvwL3nS9p+bHOPsvnAsr9PNXbDPzZj2Zhthpw3U18rFBP\n",
2405 "vx3ZdKG52K0WERzXxZjsNEkG2/M6q/oCdh9No0/hwgrYDWROrG4harHDot+bwopwecAxNLjsbiHU\n",
2406 "ioeVgEatRZdzAL7QgujP5bivTweC6DdpFBGuC1gdBtDRFOg2w5erMQ71IrW0W5gOO43Yimdh1JDY\n",
2407 "Tub5UWBpMSwXikLnceKBuQju+gWyWCjnqhZW2UwBBEFAp5du/zqNGnhMWsw20FA53BTCdTYDY5EU\n",
2408 "MhoVbA1azd1veTWywW1sP1KZW0f2DIBjaLB1HLqr4TgO9NkZWKbGULw8q0jHChBc2ONYjqQx0MZV\n",
2409 "Ce9nZW9ZZxVnMjV6jmYQLBfKi1g540Cl/KvKIWwOFOemkX/qp9C+4W07tyczJbd7XevbUCqSwKjb\n",
2410 "2PAzXA+O4/C5x9fwihEHJjuVubpthhPeSp0VwH833zZixrcGrhO98PFF0+IZgW1uBAK8xioRTYIr\n",
2411 "FDH8Z+9s+XGS8Sx8C9s4eEz+qNdkERewz53fwuW5EF75a4d3ijSCJDB+xIuZs3vbteI4bmcUKMZU\n",
2412 "HaPQq9WxAgDr1CiY6Tkwvi0U3e62xsP1IEgVSI8XbGD379CKh1U59QKZ5ZiETgeDcGnS6GzDcb0a\n",
2413 "607Hqr3wZcbkHQAAIABJREFU5Wp0HieKmSzyMf5zMuwyYKmks1qLZSo1VqEwClYrnltn8LJhaeuS\n",
2414 "FzNXtbCS8rCq5kRZTp4UjQTs0XAKSbUKZl39K31SrcboX74Xc/d9AVzZ6IMgSahGD4Kdl+9nld0K\n",
2415 "ASwHHZkHQZlBWpX5kAk2FMvRTMv6KoF24m3q6ePkoHXZoaIMSK/tnrA7bD3wNxCwK+W4Xg5htSP3\n",
2416 "zX+B9tVvAmHePYGE6E24rd62u2MHPZTkKEYOD85H4I/n8PbjrY8k2+FkdxdOb9VqhV574wGoU0nc\n",
2417 "L1I4+GIZDIgUVr5Ye+agABB/9gyKRQ4H/7//CbKNTbULL2xg9HBn3Yu7asTc1yPbSfz4+5fwurcc\n",
2418 "rRHAHyxlB9bTZbVLgsmC4yD5fTzoobAUTiNTqDVcbtSx2gurBQFhMzC5ugXCu7dh5YS3B1zZOLAV\n",
2419 "D6tyhjqkNwPljAKng0EY2RA62sgIrMZmN4COpBSzWhAgCALUUC+SyyXLBacRC9v8ZqAvlkEgnkVH\n",
2420 "WQDzs0kSN/RaYNbtTQdyv3N1Cys6XVdfJSBHZ8V7WUmfuALBJIo6jawrIs9dt0JtMWHzWw9U3N7s\n",
2421 "OHBnDHi5PZuFagbsemQLLJ5epVvysCqn1UBmtsgilcyBalHfJcAL2Hfb6XLCmJnzc21nwlVD2Jwg\n",
2422 "DBQ0r3xDxe1BehMea/vmffxmYGuBzFtMFl99bhMfuaO/xmjzSnHc24kzW34U2coTs0qjwevPPoav\n",
2423 "nw/CX1VsiHlYcRwHH91enE3GH8KFD9wHg1EDlmr9ZM+x8kXr5fCF1e5IKZ8v4gf/cRY3v2JEVOPk\n",
2424 "9pqh06ux7ms/6UAKwWZB6gJAr1FhyGHAjEhx39+gY7UVV95qQYAaGUB6I4Dskg+aPTAHLYcXsO9u\n",
2425 "BrbiYVUO37GaFf1ZI/f1QCKJfJFFJrmu8CiQt1xQehQIlMKYBQG7c7djNe1PQK9RwaBRgWNZ5Laj\n",
2426 "eChY+IUUrQtc1cKKkdnxOOihsBbLICYhGAUAh4vvWEldFYbDSZAyHcIJgsDYPe/Hwt99BcX07slC\n",
2427 "1aSAnT47A+sxPnhZqTGg8PqO91jgj+dasloop1WdVTKRg8GobXud13x4BEyZzqqRl1UuyiAXoWEc\n",
2428 "VO4qDwDUN94B/fv+AoS2slAUOlbtMuGhMBtKNm0UWmQ5/N3PfHjzkY62xr7t4jAY4KGMmAuHa37W\n",
2429 "67XiVdokPvf4WsWJZEUkIzCayYAAAZu+tU4nWyjg3O//T/S949dgdpiQSrTuw7ayGIbOoEGnhC5J\n",
2430 "imovq5/+cAZOjwlH6hRoE0e79nQ7MLDJSOqrBKRyA3ssFmzE4zVFs8Aq0/6ygRSkRg3z2CC4cBSm\n",
2431 "vS6sqkxC29FYAUCvexiB2Doyudo4NMF9ncmKfz6ng0FMdngQiK0qYrUgYHMase2PgyTJhtYhzWIc\n",
2432 "2hWwd5q1SOVZOAxqnNmI74wB81EGhMGABFSY8l55ycJ+4eqPAmUUVhoViaNdvK5ICp1eDb1BDYYW\n",
2433 "39KgI2lom+iu2E9OwnpkHL5//ubObeSBUbBba+DS8kY69LlZWI6Mg23TGFSM67rNMOtUcLS5RdOq\n",
2434 "zirBtG4OWo7lUGWsRYe9B4GY9CgwfmEelsMjbdtWVKM6MALV+GTN7SFmS5GOlVWvht2g2fF9kcs3\n",
2435 "p4NQkwTecHhvxyRyON5Vq7MCANPIAG5ZnwGTKeDhBb77mS2w2E7m0F31/RY2AlsdrS78zZehMugx\n",
2436 "+Ee/LRrE3Aznn13DkQZO62KUjwIvntnA2nIEp95wqO7jjE95MX/Bj6LIKE4JqqNsxDgiYRSqV6vh\n",
2437 "MOjhT4gf13wxGn0Ke1iVY5kaA2u1wG5vPy+vHtWbgWwo0JKHlYBapUGPaxC+4Jzozz0UJTkOnA4G\n",
2438 "Mel2wx9dU1RjZTBqQKpIRTcCBXjLBf79IwgCw04DCiyHVJ5Fp6k0BtyOIG2x4JWjzj3Ty10LXOVR\n",
2439 "oHScTTVy4m0cbulomySdhrHJQmD0L38fy1/8jx1naUKjBXlgFMUFaf8SAY7jwJybgWXiAFj/Bsh+\n",
2440 "ZVPtb+yz4vdv7FFkM64VnVW8DXPQciyTlZuBbmsXthk/iqz4Bh2/EajsGLAeQXpDkY4VwOcGNuNn\n",
2441 "tRRO4dvTQXzotv59cZASE7ADpXHOwgo+eGsfvvLsJqLpPNZiGXgtOqirNlZ9NI2BFkXQwYefwNb9\n",
2442 "D2PqHz8GgiRLJqEtbrQyGawtRzBxpPm/rVBYbQcTePS/Z/G63zoGbQMtidVugNNjwvLCdt37tQov\n",
2443 "XK//vh70UFjYTiEnprOySOus9kq4LmCZHEPW5dozDysB3n19nfev4jhw24GWPKzKqRfI7KGk3den\n",
2444 "A0EMWvXQafQwtrEYUw1BELA5jIoK1wWowb6dMGYAGHIaEEjkYNSQO/qqxNY2wjoKp0avTITTfmUf\n",
2445 "FFbyTs68zioOtk6sjJSAnWU5ZBM5mJu096eG+9H56jtw+fP/tnMbPw5srLNKrWxARRmhSWyD7BkA\n",
2446 "oWk+qLjua9Oq8IoRZT68reisEnS2LQ8rAUNfF/J0HLkIf1DXqnWwGp3YZvyi99+LjcB6tBNnU81E\n",
2447 "Ew7suQKLv3nUh9+7oXvnoHW1Oe714vSWv0Y3YhrpR3LehxGXEadGHfjiU+u8cF1kMaVVD6v0mh8X\n",
2448 "PvjXOPJPn4DWyQvf+Y6VvHiZaqZPr2NssrNhQSSGyawDHU3jB/9xFrfdNQZ3p7wT415tByYY3m3b\n",
2449 "0mARyKhVod+ux4zIMbLeZuBeWS0IeO66FSuvfR3shj0WOputAEEAcRocHQVhMLbkYVXOUJ3NQCkB\n",
2450 "O8txuBjahkOVQqeCwnUBq8OwJ4WVcbAHyaXdcf+Iy4jLJZ1VZ2kadP7SOjQuB9wyZTcvVq6yxiot\n",
2451 "aysQADrNOph1KiyFa+fZAk6JwipOZ0Dq1LCbmv9jD3/ondj4xn/vbK7xAvbGOivm3AysR8ZRXJxR\n",
2452 "fAyoNK3orOJMexuBAgRJ8gL2S/J0Vsz0HCwKOa43gvewUkZjBQAHO+Q7sP/L6S302fS4cx+tK3vN\n",
2453 "Jpi0GixFYxW3U0P9SPnWwRYKeNt1Xixsp/DdiyH0iW4ExpruWLG5PM6+524ceP/bYD+5O641lExC\n",
2454 "m4VlOZx/br1p0boAZdYhwWTR2WPF4ePyx8Rjk51Ynt9Gtk0/s2qEMaCc7rVUbmCf1QofI96x8tHt\n",
2455 "+47VQ+d2YGn8CJx73LEiCAJkZzdY/zrfrWpDXyUw2HkQlyUc2KXc11diMdj0eqTTfkWF6wJjhztx\n",
2456 "YNSl+ONqrGaojHpkA3zXdchpwEI4jTcccuNYF39xMTu7ga6Bvcl7vJa4aoUVx3JIMM1FojQaBzrc\n",
2457 "JoSDtQcNOpICZ9DA2oL5nM7jRN/v/joW/paPulGNTIBdngeXr39Ap8/OwFIKXlZSuL4XtKKzStCZ\n",
2458 "tuJsyjEfHgUzXbYZaBcvrAqJJDIbQVAj/Yo8byPi6RjUKo1irfoBuwHbycZGoWc343j0chR/dHPz\n",
2459 "+p+95mRXV0W8DQCoDDroPC6kfZvQq0n88S19mAulRD2sfHTzVgtzn/hH6DqcGPj9N1fc3qrGank+\n",
2460 "BMqsk/R8aoRWp8btrxrDy1830dTfx2DUoveAHQuXAi09rxSBTQaeBmNAgSmJ3ECpjlWBZbEZT+yJ\n",
2461 "1UI5kVR+z0eBgKCz2gAbam8jUKDbeQDbzBbSudoLJimT0OlgCJMeXl/VoaC+SmDiaBd6D+zNKI4a\n",
2462 "6tvJDOy16hFO5XFDnxXdVh3W6QyyoQj6Bq+OJcx+4qoVVqlkDlqtCpomHKQbFVZOD4XIdu0HmY6m\n",
2463 "kdeqWyqsAODAe9+C8GOnwVyYB2Gg+C/nsrjj7s5z7jiu7/+OFdC8zipOt2cOWo7l8AjiF8sE7BJh\n",
2464 "zPGLizCND7blW9QMoTYzAqtRkQRGXUbMhaRtF5K5Ij7zmA8fvLVvz1yo2+G4t1PUz4oa6d/JDDza\n",
2465 "ZcaHbuvDdV2VJ2OW47BKM01lzvl/8FMEH3oCk5/7aE0R06rG6lxJtN4OJ289AI22+b/PxNEuxceB\n",
2466 "9RzXqzncacJcKIVcsVJnJeVltZdWCwIsxyGWKez9KBAA6e3lF5C22/OwElCrNOh1DWMlUCtglxoF\n",
2467 "Tgf4jUB/dHVPRoF7ibFMwK4iCRyw63emSA/OhTGIDAwdv9j6KuAqFlZ8lE1z8+0prwmLYT4nTwzK\n",
2468 "rEMhz9aMtOhICmmNCrYWv7hqE4XBD/4O5j75RQCNx4FcsQhmegGWXhdQLIJoY/PkStGszirBZBUZ\n",
2469 "BQKAueTALtAh4b7O66uunHA9RG/Co2BhBfA6q3q5gV94cg039Fpxfe/ebWG1w4nSZqCozmrBt/Pv\n",
2470 "U6NOmKr0S4FEEiatFpRGXmciubyOSx/5DI5++T5obLXvhxBr0wxMLI1NXwzjU1fnOzk07oF/na4x\n",
2471 "GG2HwCaDTpndN0qrQq9Vh/mq4r7XasE6U6th3UurBQEmU4BRQ9ZEH+0FwihQqY4VIAjYa3VWUu7r\n",
2472 "08EgJj0e+GPr6HQoPwrcS6ihXiTLBOwjLiOWwikUWQ4PL0TQkU9B5/7F9a8SuLqFVZMnZp2axKEO\n",
2473 "CmckbBcIghAVsMeiaTAk2XLHCgB63/Z6pH2b2H7suYYC9sSiDzqPA2RoDeTQ+L4b54jRjM6K47i2\n",
2474 "A5jLMY8dQGplfcczTEpjJWQEXimCCnesAL6wktJZPXY5iplgCu++XtnnVJJeiwUcOKwxlZ1jamRg\n",
2475 "p2MlRTNRNsV0Fmff9VEMf+idsB4V7/hSJl3To8Dp0+uYOOptqdukBBqtCsMTHsyeV8bTKhnPopBn\n",
2476 "YWnCz27Ka64ZB1IaDUxabc3oaq+tFgAgnCrAfgXGgABAdPaA828oprECgCGJzUC30YhgqvL9zBWL\n",
2477 "WIxEMe50IhBdu+Y6VtRg307HCuB1VovhNJ5dY9Bp0YGMxqDz/LKwuqYKK0DGOFCksKIjKURBwNZG\n",
2478 "YUVq+Kib+fu+AHLkEIrzF8FJGOoxpTFgcVF5/6q9ohmdVTqVh1rT3Bi3HqROC2qwD4m5ywB2C6vq\n",
2479 "rgh9/sp3rNwWZfUCEx5+FFjdGQin8vjHJ9fx4Tv6oddcuYDlZiEIQtR2wTQygOT8St3fXW0iymbm\n",
2480 "ns/CNNKP3t95g+R9DBSvsZJyt66GLbKYPr2OIyev7slMyXGgMAZs5uJNKjdQTGfl22OrBQA4vxXf\n",
2481 "8UHaa8jObrCBTbCBrbY8rMrhHdhrCysXZUQomar4fM5uh9FvsyKXZ6BWaUDp91a7pjTGod4Ky4Vh\n",
2482 "lxGL2yk8MBfGXaNO5EIRaN37Z+HmanHVCismJi/OppoTPRac3mAkD6Z8x6ryoEFH0ogAbecWdbzm\n",
2483 "DhAaDfyPnAZhsYJdXxG9H32mVFgtKeu4vtfI1VklmAxMVmX0VQLlDuxGnQk6jQGx5K7nTzGdRWp5\n",
2484 "DabxQUWftx5KxdmUYzNoYNWrKoxCOY7D3z/mw2smXBj37K1JohKc6PLi+a1KOwxqZACJRV/dImdF\n",
2485 "ZpTNxjcfQOSpszj0mQ/XLRjUahIajUr2lt3SXAgWmwEumfYIe0XfoANxJiuqB20W/yYDT5Mi/MOd\n",
2486 "/Di6UJUC0C+is1rd443As5txfP1sAO+5QdnvmRSETg/CYgUX2Gjbw0qgyzmASCKIVLZykkJpNNCo\n",
2487 "VBXu68IYMBBdU9Rx/Uph7O9Ges0PtsB/5wbsemwwWUz7E7it34xcJAat85eF1TXXseq16kCAwFpM\n",
2488 "XKPg8JgQLutY5bIFZLMFaA0aqMj2RnI7UTd/82WQwwclx4H0uVlYDo+AXVmEavDKja7aRa7OSsmN\n",
2489 "QIHqzEA+jHl3HJiYvQxqqB8qvbIFXT2UtFooZ9xNVWS2/XBmG3SmiN86tv+1eMCuzqocrd0ClV7H\n",
2490 "B49LwHtY1e9YJeaWMfvxz+PYV/4KalPjIrMZAfv5Z9dw5IarfzIjVSTGJjsVibgJbsjXVwmYdWp4\n",
2491 "zbU6K/GO1d55WK3GMvjrR1bwly8bQK9M2x0lIDt7QFhsbXtYCahINfo9o1gO1OYG8gL23e/6dCC4\n",
2492 "sxF4rY0BAUCl10HncSK9xl9YaVUkeqx63HLABlUiAbXVDFKz/5ZurjRXWbze/JeJIAic6DHjOYlx\n",
2493 "oMNNIVKmYaGjaRitelgVmuE7XnIUpvFBxIIZsCICdjaXR2L2MkwOHUhXBwjj/u9ACMjVWcUZZcxB\n",
2494 "y7FMjoK5ULkZGCzTWV1p4TrHcdhm/IprrADez2qmdFJbpzP4t+e38OE7+mtcyvcrg3Y74rkc/InK\n",
2495 "zrBptL7OaiVWX2NVSKZw5t0fxdg974N5Ql5SgVyTUDqSgn+dxujh/VG8ThztwqWzm7LHmFLwGYHN\n",
2496 "Fz5i48A+i6WiY7WXVgt0poD/+dAS3nl9F452XdkOIuntVUxfJSA1DvSUxoECvNWCB/7Y2p54WF0J\n",
2497 "jEO7YcwA8KYpD3590oNcMAKd+5cbgcDVLqxkxtlUU09nZXMYEWcyKOT5zUE6koLOrGtLX1XN6N3v\n",
2498 "xeX7n0Zh5nzNgTE+swRDfxeIjWWQ14i+SkCuzirRYrexHuaDw4hfWtrRrXVWbQbSV9AYFADoZBg6\n",
2499 "jR56rfIOxhMevmNVZDn87aM+/PZx7xW9Ym8XkiBwwtspOg5MShRW+WIRW4kEei3i3RWO43Dxzz8N\n",
2500 "27FD6Hnza2S/FqNMk9Dzz63j4LEuaPaJfk0Ifvavi5tyyiGVyCKXLcDaQi7cVKcJ01uVoyvecmH3\n",
2501 "uLpXVgu5Iot7H76MWw/Y8crRKy90Jrw9im0ECkhtBropCsGS5QKTzSKQSGDIYd8zD6srATW4G8YM\n",
2502 "AHcOO9Bn0yMbDP+ysCpxVQortsgimci27IN0tMuMS8EkMiKZVyoVCavdgGiY/zDT0TRIo7atjcBq\n",
2503 "zGODsN5xC4rJFLhQ5cmFOTcL65EJ3nH9GtJXCcjRWcUZ5TysBDQ2C7QOC1IrGwCAYe8knph5AIkM\n",
2504 "f6C/4lE2jHJRNtUMOviMrf/97AZMWhVeO6G8S/JeIzYONI30I1FmuVDOZjwBD2WEViVe2Kx/7fuI\n",
2505 "X1zAwU/9aVOvQ45JaLHAYvr51p3W9wKCIHDwaBdm2hgH+ptwXK9m0mvCxVJxL9Br5TtWwsWij1be\n",
2506 "aoHjOHz256uwGzR4x4mrYySpue0UtG/5PUUfs95moNCxuhgMYcLtgpokeQ+ra1BjBdRaLghkQ1Fo\n",
2507 "f7kRCOAqFVaJeBYGoxaqFn1LKK0Kw06jqIMwUJkZGGvDdb0ew3/+bsSCWaSffqLidsEYtLg0e811\n",
2508 "rAB5OqtW9XGN4P2s+HHg0cGbcHz4NvzD9z6CfDaDxNwyzAeVDbKux154WAmoSAIjLiMeXojgT2/r\n",
2509 "uybsOKoRK6zqdaxW6kTZMBfmMf+pL+HYV/4KKmNznys5GqvFmSCcbhOcblNTj73XTBzxYvb8Ftii\n",
2510 "+HZxI+QEL0th1avhMWmxGN4dU1l0OujUaoTTvOHjXgjX/+NsAGuxLP7sjqsXLE4YKJAuZYTrAl57\n",
2511 "H+hkZOdCUKDcfX06GMJkhwccx8EfXb9mR4HUYB9SS7UGzrlgGLpfbgQCuEqFlRIn5pM9ZslxoNNt\n",
2512 "2tkMpCNp5HRq2AzK+qTovW6oD06B/t73K26nz87AOt4PLroNsufKRK8oiRydVbNRRHKxHB6tELC/\n",
2513 "9Y4/AkEQ+NfvfRKGnk6oKeXHclLshYdVOa876MJH7hiA6xoNKx1zOhFMJhFJ72Z3mkYGkJCwXFih\n",
2514 "xYXreSaBs+++GxOf/CCo4ea/L3JMQs89u4ap6/ff2MXuomCxGeBbai4AXSCwQbccywOI5waWC9h9\n",
2515 "NKOoh9WjS1H8aG4b954ahF59VWNqFYckVRjoGMNyVdeq3H19OhjElMeDeDoGFamCSb8/TYAbYRzq\n",
2516 "rRgFCmS3I780By1x1QqrRknsjains3K4qZ3NQDqSQkqlUrxjBQCud/wPaOgtxGeXAADFVAbJ5TUY\n",
2517 "tTmoDoyCIPeHnqMZ5Ois4nQGpr3oWJVZLgD8ts0fvu5TOL/+LJZfoswGj1yUjrOp5vZBO07uU3d1\n",
2518 "OahIEkc7Oyp0VjqvG8VMFrlo7ffSJyJc5zgOF/7kr+G8/SS63vCKll6H0VRfYxXdTiLkj2Pk0P4Q\n",
2519 "rVczccSLmXOteVoFNxnZUTZiiOUGlkfbKNmxuhRI4gtPreMTp4b2PGz5aiGmsyp3XxesFrau4TEg\n",
2520 "ABi6O5ALR1FMZSpuzwYj0P5SYwXgqnas2jtRDjoNSOaK2GJqr1YdHhMioSQ4jgMdSyNOkoqK1wW0\n",
2521 "Eweho7RY+uTnAQDMxQWYRg+A8y1eU/5V1dTTWeWyBbAsB90evJ+WQ5UdKwAw6S14U+Qm/Mw9j9n1\n",
2522 "M4o/pxR7OQp8sVA9DiQIAqbhftFx4IrISXr1q99Ees2PiXv/qOXX0Ehjdf65NRy+rhvqfdohGZ/y\n",
2523 "YmkmiLxETJcUqWQO2UwBNkfrXdxJrwkXqnRWlR0rZawW/PEsPvGTy/jQbX0YbEFof60gthnopnj3\n",
2524 "9UAiiQLLostsumY9rAQIlQrGvm6kVirTMXKhCHSeXxZWwDU8CiQJQrJr5XDxGqsEk4VGqwZdYPek\n",
2525 "Y0WQKqgPToHYXEHkyTMV+qprxXFdjHo6qziTgdmq2xNdkL6nA2w2i2yo8rm15/34nYl343Pf+whC\n",
2526 "tDJRII3Y61HgiwExB3ZKwnKhumMVe+Eilj77bzj65ftA6lofh9bTWBUKLC68sLkvx4AClFkHb68V\n",
2527 "S7PBpn4vsMEbgxJtWHTYDRo4jGpcjuyOc4WOlVJWC8lcEfc8eBlvPtKJG/r21sH9ajPUeRCXA7Wj\n",
2528 "wGAyhfOlbhVBELyH1TVcWAGlcWCVziobDP8yzqbEVSmsmFi6JQ+rak70mHF6vTY3UKdXQ29QY305\n",
2529 "ApvDgFimAOseJaerJ6bQc9tBzH3iH0GfuQTrkfFrznG9mno6qwStXEZgNQRBwFzVteKKRcQvLuLG\n",
2530 "W9+I117/dnzm/j9BJpeu8yjtw3IswowfLsv+HB/tFw553PDRNOLZ3a6xSUTAnikUEEmn4TXx4vFc\n",
2531 "hMbZ99yNQ5/5MIz97Tlu1+tYLVz0w+M1w+7c315yE0eaj7gJbLanrxKoHgf2lzpWW/EEXMb2rBaK\n",
2532 "LIdP/mQZR7pM+NVD7rZf636nw96LRJoBk9qVURg1GmhVKjyxtoZJDy+Yv5Y9rAR4y4XKzcBcKPrL\n",
2533 "UWCJq6exUkCjc123Bee24siLbNU43CZcng/B6jCCThf2pGMFAKqxSRgK/Iqy/wePwNrnBKHTg7Rf\n",
2534 "u5V7PZ1VnMnuyUaggOXwCJiLu0ahycvr0Lrs0FjN+JUTb8WAZwz/6/9+DCzX2iaVHGKJbRj1Zug0\n",
2535 "L96xhRJoVSpMedw44w/s3EaJWC6s0jR6LGaoSBIcy2L6A/eh8zUvRcerbmv7Nej1GuSyBRRFrFfO\n",
2536 "PbO2rywWpBg51IG15aisAHSBwAajTGHlNWG6zCi0z2qFj6bbHgNyHIcvPLUOkgDee+P+7RgqCUmQ\n",
2537 "ONAxXuPA7jEa8ciyD4c9fHHpj66hw3ZtvyfUUC9SZQJ2tlBAnmagdby4u5JyuWZHgQC/Mtxr0+Ni\n",
2538 "oDZzy+GmsDK/DYvNgHh27wor8sAo2K01jP35O3m7fzZxTY8BBaR0VnsRZ1OO+fAomOndjhUzPQfL\n",
2539 "FO+4ThAE3nXqLxFNhPCdJ7+yZ69hL8KXX6wcr9JZiYUxl0fZLH/xa8jTDEY/+j5Fnp8gCRgobU1R\n",
2540 "Eg4mEA2nMDyh7Fr9XqDVqXFg1IW5aX/jO5do1XG9mqlOM6b9iZ1QcKtOB4IAzgeCbQnX778YwgV/\n",
2541 "An/5sgNtR4ldS/A6q0oBu5syYjuVwqTHXbJauLbF6wBgHOxFssx9PReOQWO3gpDwqftF46oUVpl0\n",
2542 "HkaTMgaTJyV0Vk43hXQqD71FB4NGtWdxIYRWC3JgGFaPHref/g645blregwoIKWzitPKm4OWYzk8\n",
2543 "gnhZx4qZnoPl8G6UjUatxZ/86qfx0/PfwzNzP9mT1xBi/l97dxrdVpneAfx/rzZr927Ju+UsdhLL\n",
2544 "xnHCpAOBEAgDhNLkcIbCadpSyoEOB0opJIUTlhkXkgOZk0PnTBgOTDsfIFDKMklZEghmCSWQzc5C\n",
2545 "7Bji2E5kW3Zsa7OtXf1wc2VJ3m1dybp5fl84iu3oPUa5enTf5/0/3chNT8xQ2FRXl2/Eka7RbSxl\n",
2546 "ST48ff1RJ4babXaU6vUYONSE9lf/G9Wv1sd1nphKLcdQTJ/VySMXsGx5wayz8hKtsiYfzU3T6x8c\n",
2547 "GfbCPexFRtbc40ey1DLoFFK0D3L/vxiGQbFOj4OdnbOOWviuw47/OdmL+nXlUMuvrDfa8U4G5qjV\n",
2548 "KNLpkKFUwjliA8Ow0Conn5k536nLo9PXvb0D1F8VISlXHY02DWycCh2ugX1sn1Xm5TBAViVHukD9\n",
2549 "VTzJ4ioEzp6GTK9F4KfUblznTdRn5XIIEw7KUy8owYjFCv8w10flONk6JnE9XZONxzfswOufvoB2\n",
2550 "69m4r6HXZqE7VtNkzs1Fa/8Ahn0+AAArlUJVUhj1abbDbkehRIoTv3oWVS9vhbIgvuNEuCyr0dep\n",
2551 "zxfAmcYumFekzl2BsoXZGOhzwT44df+g1eJArnFujeuRuDyr0WtosV436ztW5/qH8duDnXj2xjLk\n",
2552 "aVMzo20uuDtW0VuBOSoVqi5vA1ptF1Ny+HIseXYGQj4/vANcNIentx9yCgcNS0phFc835sU5KvQN\n",
2553 "edE/5Iv686xcrmE1kBb/1PVYksXLEDx7CiGvB0FLB9jShYI+XyJIJCwKSsb2WTkdHkEyrHisTMoF\n",
2554 "TTafQygUmnD4cpmhEv9w079hxwePwTbUH9c1CDnORmyUMhkqsrNwwjp6qk29qCTqZGC7zQbfa++i\n",
2555 "4K5bkXPDz+K+hthBzK2nemAo1EOfkTo9chIpi0XLDGiZRqZVvLYBebFBocV6PULAjMfZ9A/58Myn\n",
2556 "bXj4LwpRkTu/DwwIJS+9EG7vUNQ16baF5fgbcxUApHyGFY9hGG4Y83nurpX3Et2xipScwiqOA2cl\n",
2557 "LINQtXXEAAAQhUlEQVTafC2OWaK3A9VaBa5aVQyPVJhw0Kg1LFyKQNtZBM+1gC0oBiMXbqsskYrK\n",
2558 "xvZZOQXusQK4BHbHqVaMdHZBolZOONhzVcVNWL10PXb+eTN8/uk3/k6FMqxmpi7fiGOxfVYRhVVb\n",
2559 "txV5LjcWPH6fIM8fO4iZS1pPvTevyhojzpzoHjPYPZbVYp9TMGisKoMGp3qGws9bpNeBAVConX7U\n",
2560 "gtsXwDOfncP6ymysNl25dy4YhkGZoTIqgX1JTg5qDNxd2lTPsIqkNhWHIxc8vQNQZNOJQF5SCqt4\n",
2561 "nAiMVFekw5GYPiuGYbD29iVweAOChINGPZdKDdZQAN+BvZCUp/42IC+2gd3vD8Lr9kEl8BgWPoHd\n",
2562 "cWrsNmCsO695ADpVOv742fYp35CmizKsZmZ5TJ6VeuFoltX5z7+F2+fH6pe3gp3D0f3JREYu9PU4\n",
2563 "4bCNoHxx6h3vLyjOgM/jR1/P2NaGSPE6EcjL1cihlLHosHF9VsV6HYxazbSjFoKhELZ/2YHSDCX+\n",
2564 "ujq+27ypyDTBQGbg8olA0RRWo8OYPX0DkFM4aFjKbwUCwPICLY5bnFEJwjy7gBlWkSSLq+A//E1K\n",
2565 "Dl6eSG6+DvbB0T6rIYcbaq0ibr0dE9EtXQjnaa6w0psnL6xYhsVDt9WjrecH7Dv29pyfOxgMYMBp\n",
2566 "RRZlWE1brdGA07298Aa49HDNwhIMtXbA3d2Hr17YhRK9Dml52YI9f2RI6InDF1BVVwg2RZrWIzEs\n",
2567 "g4rqyZvY3SM+DA95kZEd3622yDyrmrw8/O6Wm6f9s3880gWXN4BHrylKyYHi8WYyVI4JCuX1DF6A\n",
2568 "McUzrHiq8tFhzNwAZiqseEnaCoxv70O2Wo5slQytl4bHfM0mYIZVJMniZUAoCIkITgTywn1W57k+\n",
2569 "K6dDuHDQSNqlC+BqaYO98cy4/VWx0uQqPLFxJ/Z8/yecPH9oTs894OqDVpkOuVQc27mJoJHLUZaR\n",
2570 "jtO9fQAAdXkJhjssaHrgaQTuWIPyAmEPAvB3rHxeP1pOdKOqLnUzgpZUG9FyshuhcT4kAqON6/E6\n",
2571 "/MMzGzU4dbmwkrAsKrOnVwh/0nIJ37bb8czaMshSsJgVgilvbOQCr8eW+hlWPO6O1eWtwL5B6rGK\n",
2572 "IIo7VgC3HThe7ILdnZjCiq0wgy0sBWMQ1zH9yNgFl13YcFCeVKOGwpCNgf87PuVWIC9Hn49//stt\n",
2573 "+P1Hz6B7oGPqH5iA0MOXxSpybqBEqYAiLxtSjQrDV5vHDF+ON5VajhGXBy0ne1BQkg5dnD+4JVK2\n",
2574 "QYs0pQwXO8Yfgm7tim9/Fc9s1OBkj2tG2+mNFif+62g36m82QZeAa2yqyNHnw+f3YsDVF/XnzhEb\n",
2575 "QqFgykct8NQmLiQ0FAzC09dPW4ERRFNYrSjU4ciF8QsroXusAIDVZ0C1/TXR3QqP7LNK1B0rgOuz\n",
2576 "kqXroDBOv1emsqgWv7zmn/DS+49h2DN5n8pEKBx0dri5gaMn2pbu2ALz759Dp8OB0jgM8p2MSqPA\n",
2577 "kMuLE4cvoDoFm9ZjVVYbJxxxY7U4BCmsDFoFZBIGF+xjh9qPp9PmxrYv2rF1bSkKE/BhK5UwDANT\n",
2578 "TAM7gMszAotF8x4h1aoh1arh6bnEDWCm5vWwpBRWSpUs7n/nkjw1Om1uONz+qD+3u30JuWMlVpF9\n",
2579 "VlxifmK2yHRLF0JbtXDGF6G1NRtRVXo1Xt77FILBwIyft9feReGgs1BrNKCxxwp/kBstk716BeQZ\n",
2580 "OrTbbOHUdaGo1HI4HW4MuzwoXZR6TeuxKqqNaD1thX+cMT3xblyPZDaMbgdOxu7245lPz+EfV+bD\n",
2581 "bJzbkGaxGi8o1Gq7KJoTgTxVeRGcLW3wu4YhyxDmdZmKklJYCVGxyyUszEYNjlui71TYRvyCB4SK\n",
2582 "WWSflcshfNQCr+CXt2LRlgdm9bOb1vwL/AEfdn/1HzP+WbpjNTsZSiWMWg1aLo3m94RCIW6cjcB3\n",
2583 "rGRyCWQyCapWFMW99ygZdOlKZOdpcL41eivJPeLDkMsTDj+ON7NRi5M9kxdW3kAQz33WhtVlGVi3\n",
2584 "iHpqJmIyLBnTwN4z2CmKcNBIalMxBg+fgDwrHQxLPXY8Uf0m6gqjYxdCoRDsbj/t/88R32fltAsb\n",
2585 "DhopLT8X+qtmd8JSKpHh0Tu248iPX+Kr0/87o5/ts9M4m9mqi4lduDQyArlEAn2a8Hc5K6uNMKdw\n",
2586 "03osbsRN9HZgb5cDOQatYMVj1eWTgRP1WYVCIew82IlMlQx/X0cfPiZjMlSgrac56nfZI6IMK57a\n",
2587 "VITB75qocT2GqAorfm4gP1DU5Q1AIWUhp9Mqc8L3WbkS2GM1V1plOp7YuBNvfvkyWi0np/1zfXYa\n",
2588 "ZzNbdflGHO0eLaw6bXbBG9d56zYsg1ornpOci5blof3HfnjcoxMluMR14bZb8nVcPl2XY/yw3d1N\n",
2589 "Vly0e/DEdSVgRdInJJQsrQGhUBADrtGJBGLKsOKpyotgO34GcopaiCKqisOoU0Alk+D8ADdvy+6m\n",
2590 "bcB44PushpweaFLozasw24QHb3kWO/dsRr/TOuX3B4J+DLj6KMNqlvgEdv6DTbtd+G1AsVKq5Cg2\n",
2591 "ZeLHH0Zft1zjunC/T4ZhuNiFcbYDvzg3gH1n+/Hrm0xIk4rqbUMQXAN7dJ+VmDKseGpTMUJeH2VY\n",
2592 "xRDdvxBuO5Drs7InKMNK7Pg+qzSVDJIUu6jWll+LW5bfjR3vPwaPb/IBtwPOXqSrsyGVxP9wxZUg\n",
2593 "V62GPk2BcwNcVEC7zZawO1ZiVFmTjzMRYaFWix0GgRrXebEDmQHgB6sLuw5Z8Jt1JmQKcPBIrLiB\n",
2594 "zFyflWvEjkAwIJqoBZ6qJB9gWSqsYqTWu+Q0rCjS4tjlPitbgjKsrgRFZZkJybASwu0r/xaFWSb8\n",
2595 "4ZPfTJrT02u3IEdP24BzUWcc3Q5st9kFPxEoZqaKHFgtdrgcbnjcPjgdHmTmCDvc2GzQ4EREn1WP\n",
2596 "04P6z8/jietKUJaZuvlgycAFhXKFFXcisFA0UQs8ViGHssgIOfVYRRFdYWU2aNB6aRjD3kDCwkGv\n",
2597 "BIuqDCmbaM0wDO7/xVb02bvw5+/+c8Lv67N3UzjoHNXlG3HkcgN7h90ueIaVmMlkEixYkoeWkz3o\n",
2598 "7XZyjesC94sW6hXwB0OwurxwefzYur8Nd1cbsLKIjtLPFL8VGAqF0D3YCYPItgF5mkWlSJtB3uCV\n",
2599 "QHSFVZpMgspcNZq6nQkLB70SpGeqUHN16l4Y5FIFHtuwAwea3sPRH78c93v67F3IpcJqTvgE9kAw\n",
2600 "iAt2B4r19IY8F5XVRjSf6ILVIkzieiyGYWA2aNBoceLfG9pxVb4WdyylN83ZyNDkQMJKccnRA+ug\n",
2601 "+DKseObfPY3cX1yb7GXMK6IrrAD+dKDzcoYV9QQQTqYmB4/91Ut4dV89Ovt+HPP1XhpnM2cFWi2k\n",
2602 "LIPvLV3IUKZBKaN/f3NRXJ4Fl8ODlhPdMCSgsAKAKqMGf/jeAgnD4MGfUfTIbPEJ7G09Z0SZYcWT\n",
2603 "pevASukGRiRRFlZ1hVocueCgrUAyRrlxKf5u7ePY8f6/wjEcPY+NmxNIPVZzwTAM6oxGvNfcQtuA\n",
2604 "ccCyDCrMBvRYHMjLT8zvc2WRDmajBk/dUAqJCAJXk4nfDuyxiS/DikxMlIVVcXoagqEQfrAOUWFF\n",
2605 "xrhmyS1YVXETdu7ZDH9gNCeI2wqkT+hztTzfiANt56lxPU4qa/IhlbHIzBW2cZ1n0CpQv64carkk\n",
2606 "Ic8nZqa8SrRZW0SZYUUmJsrCimEY1BXqYHV5oaccKzKOu1Y/BJVcgz8deAmhUAj+gA/24QFkanOT\n",
2607 "vbSUV5dvhC8YpAyrODEU6HHvo9dCQkHHKafMUIFWywn4/F7oVRRJcKUQ7b/UukKuH4Ga11PX2bNn\n",
2608 "Bfu7WYbFQ+vr0WJpwmdN76Lf0RNuNiVzY0pPR6YyLeEZVkK+XpJNn0FRB/GUqNdKhiYHaoUWhsxi\n",
2609 "0UUtkImJtrC6Kl8DlYylrcAU1traKujfr1Jo8MSG3+K9b1/DF6f2IkdHjevxwDAMnl9zPVbkJ7Zf\n",
2610 "TejXCxGPRL5WTIYlMKSnZlQNmZ0pq469e/fiyJEjAIDa2lps2LAh6usHDx7E/v37wbIsysrKcO+9\n",
2611 "9wqz0hnSKKTYffcyKFIsKZwkVl5GER5e/zxeeOchXLv01mQvRzSuKy1J9hIImReqSlfC5x9//iIR\n",
2612 "p0kLq+bmZpw/fx719fUAgFdeeQWnTp1CVVUVAKC3txeff/456uvrwTAM3n33XTQ0NOCGG24QfuXT\n",
2613 "oKLmSzINy0pW4Fe3/RppclWyl0IIEZmba+9K9hJIgk16O6exsRFr164NP167di2OHz8eftzU1ITV\n",
2614 "q1eH945vvPFGHDt2TKClEiKca5bcgroF1yV7GYQQQlLcpIWV0+mEVqsNP9bpdLDb7eHHLpcLOp0u\n",
2615 "6usOh0OAZRJCCCGEzH+TbgVqtdqoQsnhcEQVUlN9fSKRd70ImUhBQQG9Vsi00euFTBe9VoiQJi2s\n",
2616 "amtrsX///nBPVUNDA37+85+Hv15TU4Ndu3bh+uuvB8uyOHDgAJYvXz7pE0ZuLRJCCCGEiAkTCoVC\n",
2617 "k33Dnj17ok4Fbty4EW+++SZuv/126HQ6fP3119i/fz8kEglKSkpw3333JWThhBBCCCHzzZSFFSGE\n",
2618 "EEIImR4KeSKEEEIIiRMqrAghhBBC4oQKK0IIIYSQOEnYIL2pRuMQEumRRx5BVlZW+PHDDz+MzEya\n",
2619 "Dk8Av9+P3bt3o7m5Gdu2bQMwf0drkeQb7/Xy3HPPIRQKgWW5ewubNm2CyWRK5jLJPPHxxx/jm2++\n",
2620 "gVQqhdFoxP33349Dhw7N6PqSkMJqqtE4hMRSq9V49tlnk70MMg+99dZbWLZsGZqbmwHM/9FaJLli\n",
2621 "Xy8ANyj8ySefhEKhSOLKyHzjcrnQ2dmJ559/HgzD4I033sBHH32ExsbGGV1fErIVONVoHEJi+Xw+\n",
2622 "1NfXY/Pmzdi3b1+yl0PmkU2bNqG2tjb8mEZrkcnEvl4AQCKR4MUXX8SWLVvw9ttvJ2llZL7RaDR4\n",
2623 "8MEHw9cSj8eDUCg04+tLQu5YTTUah5BY27Ztg0wmg8/nw/bt21FRUYHS0tJkL4vMQy6XC8XFxeHH\n",
2624 "NFqLTGXLli2QyWQIBoPYtWsXjh49irq6umQvi8wjH3zwAZRKJYLB4IxH9yXkjtVsR9+QK5dMJgv/\n",
2625 "d8WKFejo6Ejyish8RdcXMlP89YVlWaxatQrt7e3JXRCZN4LBIF5//XXIZDLcc889s7q+JKSwqq2t\n",
2626 "RUNDQ/hxQ0PDlKNvyJXr4sWL+PDDDwFwjaeNjY1YsGBBkldF5quamhocPHgQwWAQAKY1WotcuQYH\n",
2627 "B/HOO+8A4N5EDx8+jEWLFiV5VWQ+cLvd2LlzJ8xmM9avXw8AqK6unvH1JSFbgRUVFTh79iy2bt0K\n",
2628 "gCu0qHGdTMRgMMBisWDr1q1gWRbr1q1DQUFBspdF5qmcnBysWbMGTz/9dHi01p133pnsZZF5KiMj\n",
2629 "A4FAAE899RSkUilWrlwJs9mc7GWReaChoQE//fQTXC4XPvnkEwDAmjVrZnx9oZE2hBBCCCFxQgGh\n",
2630 "hBBCCCFxQoUVIYQQQkicUGFFCCGEEBInVFgRQgghhMQJFVaEEEIIIXFChRUhhBBCSJxQYUUIIYQQ\n",
2631 "EidUWBFCCCGExAkVVoQQQgghcfL/u8ZDblhTSdUAAAAASUVORK5CYII=\n"
2632 ],
2633 "text/plain": [
2634 "<matplotlib.figure.Figure at 0x10c41e850>"
2635 ]
2636 },
2637 "metadata": {},
2638 "output_type": "display_data"
2639 }
2640 ],
2641 "source": [
2642 "data = ar.data\n",
2643 "for i, d in enumerate(data):\n",
2644 " plt.plot(d['a'], label='engine: '+str(i))\n",
2645 "plt.title('Data published at time step: ' + str(data[0]['i']))\n",
2646 "plt.legend()"
2647 ]
2648 }
2649 ],
2650 "metadata": {
2651 "kernelspec": {
2652 "display_name": "Python 3",
2653 "language": "python",
2654 "name": "python3"
2655 },
2656 "language_info": {
2657 "codemirror_mode": {
2658 "name": "ipython",
2659 "version": 3
2660 },
2661 "file_extension": ".py",
2662 "mimetype": "text/x-python",
2663 "name": "python",
2664 "nbconvert_exporter": "python",
2665 "pygments_lexer": "ipython3",
2666 "version": "3.4.2"
2667 }
2668 },
2669 "nbformat": 4,
2670 "nbformat_minor": 0
2671 }
@@ -1,352 +0,0 b''
1 {
2 "cells": [
3 {
4 "cell_type": "markdown",
5 "metadata": {},
6 "source": [
7 "<img src=\"../images/ipython_logo.png\">"
8 ]
9 },
10 {
11 "cell_type": "markdown",
12 "metadata": {},
13 "source": [
14 "Back to the main [Index](../Index.ipynb)"
15 ]
16 },
17 {
18 "cell_type": "markdown",
19 "metadata": {},
20 "source": [
21 "# Parallel Computing"
22 ]
23 },
24 {
25 "cell_type": "markdown",
26 "metadata": {},
27 "source": [
28 "IPython includes an architecture and library for interactive parallel computing. The enables Python functions, along with their arguments, to be run in parallel a multicore CPU, cluster or cloud using a simple Python API."
29 ]
30 },
31 {
32 "cell_type": "markdown",
33 "metadata": {},
34 "source": [
35 "## Tutorials"
36 ]
37 },
38 {
39 "cell_type": "markdown",
40 "metadata": {},
41 "source": [
42 "* [Data Publication API](Data Publication API.ipynb) "
43 ]
44 },
45 {
46 "cell_type": "markdown",
47 "metadata": {},
48 "source": [
49 "## Examples"
50 ]
51 },
52 {
53 "cell_type": "markdown",
54 "metadata": {},
55 "source": [
56 "* [Monitoring an MPI Simulation - 1](Monitoring an MPI Simulation - 1.ipynb)\n",
57 "* [Monitoring an MPI Simulation - 2](Monitoring an MPI Simulation - 2.ipynb)\n",
58 "* [Parallel Decorator and map](Parallel Decorator and map.ipynb)\n",
59 "* [Parallel Magics](Parallel Magics.ipynb)\n",
60 "* [Using Dill](Using Dill.ipynb)\n",
61 "* [Using MPI with IPython Parallel](Using MPI with IPython Parallel.ipynb)\n",
62 "* [Monte Carlo Options](Monte Carlo Options.ipynb)"
63 ]
64 },
65 {
66 "cell_type": "markdown",
67 "metadata": {},
68 "source": [
69 "## Non-notebook examples"
70 ]
71 },
72 {
73 "cell_type": "markdown",
74 "metadata": {},
75 "source": [
76 "This directory also contains examples that are regular Python (`.py`) files."
77 ]
78 },
79 {
80 "cell_type": "code",
81 "execution_count": 1,
82 "metadata": {
83 "collapsed": false
84 },
85 "outputs": [
86 {
87 "data": {
88 "text/html": [
89 "<a href='customresults.py' target='_blank'>customresults.py</a><br>"
90 ],
91 "text/plain": [
92 "/Users/bgranger/Documents/Computing/IPython/code/ipython/examples/Parallel Computing/customresults.py"
93 ]
94 },
95 "metadata": {},
96 "output_type": "display_data"
97 },
98 {
99 "data": {
100 "text/html": [
101 "<a href='dagdeps.py' target='_blank'>dagdeps.py</a><br>"
102 ],
103 "text/plain": [
104 "/Users/bgranger/Documents/Computing/IPython/code/ipython/examples/Parallel Computing/dagdeps.py"
105 ]
106 },
107 "metadata": {},
108 "output_type": "display_data"
109 },
110 {
111 "data": {
112 "text/html": [
113 "<a href='dependencies.py' target='_blank'>dependencies.py</a><br>"
114 ],
115 "text/plain": [
116 "/Users/bgranger/Documents/Computing/IPython/code/ipython/examples/Parallel Computing/dependencies.py"
117 ]
118 },
119 "metadata": {},
120 "output_type": "display_data"
121 },
122 {
123 "data": {
124 "text/html": [
125 "<a href='fetchparse.py' target='_blank'>fetchparse.py</a><br>"
126 ],
127 "text/plain": [
128 "/Users/bgranger/Documents/Computing/IPython/code/ipython/examples/Parallel Computing/fetchparse.py"
129 ]
130 },
131 "metadata": {},
132 "output_type": "display_data"
133 },
134 {
135 "data": {
136 "text/html": [
137 "<a href='iopubwatcher.py' target='_blank'>iopubwatcher.py</a><br>"
138 ],
139 "text/plain": [
140 "/Users/bgranger/Documents/Computing/IPython/code/ipython/examples/Parallel Computing/iopubwatcher.py"
141 ]
142 },
143 "metadata": {},
144 "output_type": "display_data"
145 },
146 {
147 "data": {
148 "text/html": [
149 "<a href='itermapresult.py' target='_blank'>itermapresult.py</a><br>"
150 ],
151 "text/plain": [
152 "/Users/bgranger/Documents/Computing/IPython/code/ipython/examples/Parallel Computing/itermapresult.py"
153 ]
154 },
155 "metadata": {},
156 "output_type": "display_data"
157 },
158 {
159 "data": {
160 "text/html": [
161 "<a href='nwmerge.py' target='_blank'>nwmerge.py</a><br>"
162 ],
163 "text/plain": [
164 "/Users/bgranger/Documents/Computing/IPython/code/ipython/examples/Parallel Computing/nwmerge.py"
165 ]
166 },
167 "metadata": {},
168 "output_type": "display_data"
169 },
170 {
171 "data": {
172 "text/html": [
173 "<a href='phistogram.py' target='_blank'>phistogram.py</a><br>"
174 ],
175 "text/plain": [
176 "/Users/bgranger/Documents/Computing/IPython/code/ipython/examples/Parallel Computing/phistogram.py"
177 ]
178 },
179 "metadata": {},
180 "output_type": "display_data"
181 },
182 {
183 "data": {
184 "text/html": [
185 "<a href='task_profiler.py' target='_blank'>task_profiler.py</a><br>"
186 ],
187 "text/plain": [
188 "/Users/bgranger/Documents/Computing/IPython/code/ipython/examples/Parallel Computing/task_profiler.py"
189 ]
190 },
191 "metadata": {},
192 "output_type": "display_data"
193 },
194 {
195 "data": {
196 "text/html": [
197 "<a href='throughput.py' target='_blank'>throughput.py</a><br>"
198 ],
199 "text/plain": [
200 "/Users/bgranger/Documents/Computing/IPython/code/ipython/examples/Parallel Computing/throughput.py"
201 ]
202 },
203 "metadata": {},
204 "output_type": "display_data"
205 }
206 ],
207 "source": [
208 "%run ../utils/list_pyfiles.ipy"
209 ]
210 },
211 {
212 "cell_type": "markdown",
213 "metadata": {},
214 "source": [
215 "More substantial examples can be found in subdirectories:"
216 ]
217 },
218 {
219 "cell_type": "code",
220 "execution_count": 2,
221 "metadata": {
222 "collapsed": false
223 },
224 "outputs": [
225 {
226 "data": {
227 "text/html": [
228 "daVinci Word Count/<br>\n",
229 "&nbsp;&nbsp;<a href='daVinci Word Count/pwordfreq.py' target='_blank'>pwordfreq.py</a><br>\n",
230 "&nbsp;&nbsp;<a href='daVinci Word Count/wordfreq.py' target='_blank'>wordfreq.py</a><br>"
231 ],
232 "text/plain": [
233 "daVinci Word Count/\n",
234 " pwordfreq.py\n",
235 " wordfreq.py"
236 ]
237 },
238 "metadata": {},
239 "output_type": "display_data"
240 },
241 {
242 "data": {
243 "text/html": [
244 "interengine/<br>\n",
245 "&nbsp;&nbsp;<a href='interengine/bintree.py' target='_blank'>bintree.py</a><br>\n",
246 "&nbsp;&nbsp;<a href='interengine/bintree_script.py' target='_blank'>bintree_script.py</a><br>\n",
247 "&nbsp;&nbsp;<a href='interengine/communicator.py' target='_blank'>communicator.py</a><br>\n",
248 "&nbsp;&nbsp;<a href='interengine/interengine.py' target='_blank'>interengine.py</a><br>"
249 ],
250 "text/plain": [
251 "interengine/\n",
252 " bintree.py\n",
253 " bintree_script.py\n",
254 " communicator.py\n",
255 " interengine.py"
256 ]
257 },
258 "metadata": {},
259 "output_type": "display_data"
260 },
261 {
262 "data": {
263 "text/html": [],
264 "text/plain": []
265 },
266 "metadata": {},
267 "output_type": "display_data"
268 },
269 {
270 "data": {
271 "text/html": [
272 "pi/<br>\n",
273 "&nbsp;&nbsp;<a href='pi/parallelpi.py' target='_blank'>parallelpi.py</a><br>\n",
274 "&nbsp;&nbsp;<a href='pi/pidigits.py' target='_blank'>pidigits.py</a><br>"
275 ],
276 "text/plain": [
277 "pi/\n",
278 " parallelpi.py\n",
279 " pidigits.py"
280 ]
281 },
282 "metadata": {},
283 "output_type": "display_data"
284 },
285 {
286 "data": {
287 "text/html": [
288 "rmt/<br>\n",
289 "&nbsp;&nbsp;<a href='rmt/rmt.ipy' target='_blank'>rmt.ipy</a><br>\n",
290 "&nbsp;&nbsp;<a href='rmt/rmt.ipynb' target='_blank'>rmt.ipynb</a><br>\n",
291 "&nbsp;&nbsp;<a href='rmt/rmtkernel.py' target='_blank'>rmtkernel.py</a><br>"
292 ],
293 "text/plain": [
294 "rmt/\n",
295 " rmt.ipy\n",
296 " rmt.ipynb\n",
297 " rmtkernel.py"
298 ]
299 },
300 "metadata": {},
301 "output_type": "display_data"
302 },
303 {
304 "data": {
305 "text/html": [
306 "wave2D/<br>\n",
307 "&nbsp;&nbsp;<a href='wave2D/communicator.py' target='_blank'>communicator.py</a><br>\n",
308 "&nbsp;&nbsp;<a href='wave2D/parallelwave-mpi.py' target='_blank'>parallelwave-mpi.py</a><br>\n",
309 "&nbsp;&nbsp;<a href='wave2D/parallelwave.py' target='_blank'>parallelwave.py</a><br>\n",
310 "&nbsp;&nbsp;<a href='wave2D/RectPartitioner.py' target='_blank'>RectPartitioner.py</a><br>\n",
311 "&nbsp;&nbsp;<a href='wave2D/wavesolver.py' target='_blank'>wavesolver.py</a><br>"
312 ],
313 "text/plain": [
314 "wave2D/\n",
315 " communicator.py\n",
316 " parallelwave-mpi.py\n",
317 " parallelwave.py\n",
318 " RectPartitioner.py\n",
319 " wavesolver.py"
320 ]
321 },
322 "metadata": {},
323 "output_type": "display_data"
324 }
325 ],
326 "source": [
327 "%run ../utils/list_subdirs.ipy"
328 ]
329 }
330 ],
331 "metadata": {
332 "kernelspec": {
333 "display_name": "Python 3",
334 "language": "python",
335 "name": "python3"
336 },
337 "language_info": {
338 "codemirror_mode": {
339 "name": "ipython",
340 "version": 3
341 },
342 "file_extension": ".py",
343 "mimetype": "text/x-python",
344 "name": "python",
345 "nbconvert_exporter": "python",
346 "pygments_lexer": "ipython3",
347 "version": "3.4.2"
348 }
349 },
350 "nbformat": 4,
351 "nbformat_minor": 0
352 }
This diff has been collapsed as it changes many lines, (884 lines changed) Show them Hide them
@@ -1,884 +0,0 b''
1 {
2 "cells": [
3 {
4 "cell_type": "markdown",
5 "metadata": {
6 "slideshow": {
7 "slide_start": false
8 }
9 },
10 "source": [
11 "# Interactive monitoring of a parallel MPI simulation with the IPython Notebook"
12 ]
13 },
14 {
15 "cell_type": "code",
16 "execution_count": 1,
17 "metadata": {
18 "collapsed": false,
19 "slideshow": {
20 "slide_start": false
21 }
22 },
23 "outputs": [],
24 "source": [
25 "%matplotlib inline\n",
26 "import numpy as np\n",
27 "import matplotlib.pyplot as plt\n",
28 "\n",
29 "from IPython.display import display\n",
30 "from IPython.parallel import Client, error\n",
31 "\n",
32 "cluster = Client(profile=\"mpi\")\n",
33 "view = cluster[:]\n",
34 "view.block = True"
35 ]
36 },
37 {
38 "cell_type": "code",
39 "execution_count": 2,
40 "metadata": {
41 "collapsed": false
42 },
43 "outputs": [
44 {
45 "data": {
46 "text/plain": [
47 "[0, 1, 2, 3]"
48 ]
49 },
50 "execution_count": 2,
51 "metadata": {},
52 "output_type": "execute_result"
53 }
54 ],
55 "source": [
56 "cluster.ids"
57 ]
58 },
59 {
60 "cell_type": "markdown",
61 "metadata": {
62 "slideshow": {
63 "slide_start": false
64 }
65 },
66 "source": [
67 "Now, we load the MPI libraries into the engine namespaces, and do a simple printing of their MPI rank information to verify that all nodes are operational and they match our cluster's real capacity. \n",
68 "\n",
69 "Here, we are making use of IPython's special `%%px` cell magic, which marks the entire cell for parallel execution. This means that the code below will not run in this notebook's kernel, but instead will be sent to *all* engines for execution there. In this way, IPython makes it very natural to control your entire cluster from within the notebook environment:"
70 ]
71 },
72 {
73 "cell_type": "code",
74 "execution_count": 3,
75 "metadata": {
76 "collapsed": false,
77 "slideshow": {
78 "slide_start": false
79 }
80 },
81 "outputs": [
82 {
83 "name": "stdout",
84 "output_type": "stream",
85 "text": [
86 "[stdout:0] MPI rank: 3/4\n",
87 "[stdout:1] MPI rank: 2/4\n",
88 "[stdout:2] MPI rank: 0/4\n",
89 "[stdout:3] MPI rank: 1/4\n"
90 ]
91 }
92 ],
93 "source": [
94 "%%px\n",
95 "# MPI initialization, library imports and sanity checks on all engines\n",
96 "from mpi4py import MPI\n",
97 "import numpy as np\n",
98 "import time\n",
99 "\n",
100 "mpi = MPI.COMM_WORLD\n",
101 "bcast = mpi.bcast\n",
102 "barrier = mpi.barrier\n",
103 "rank = mpi.rank\n",
104 "print(\"MPI rank: %i/%i\" % (mpi.rank,mpi.size))"
105 ]
106 },
107 {
108 "cell_type": "markdown",
109 "metadata": {
110 "slideshow": {
111 "slide_start": false
112 }
113 },
114 "source": [
115 "We write a utility that reorders a list according to the mpi ranks of the engines, since all gather operations will return data in engine id order, not in MPI rank order. We'll need this later on when we want to reassemble in IPython data structures coming from all the engines: IPython will collect the data ordered by engine ID, but our code creates data structures based on MPI rank, so we need to map from one indexing scheme to the other. This simple function does the job:"
116 ]
117 },
118 {
119 "cell_type": "code",
120 "execution_count": 4,
121 "metadata": {
122 "collapsed": false,
123 "slideshow": {
124 "slide_start": false
125 }
126 },
127 "outputs": [],
128 "source": [
129 "ranks = view['rank']\n",
130 "rank_indices = np.argsort(ranks)\n",
131 "\n",
132 "def mpi_order(seq):\n",
133 " \"\"\"Return elements of a sequence ordered by MPI rank.\n",
134 "\n",
135 " The input sequence is assumed to be ordered by engine ID.\"\"\"\n",
136 " return [seq[x] for x in rank_indices]"
137 ]
138 },
139 {
140 "cell_type": "markdown",
141 "metadata": {
142 "slideshow": {
143 "slide_start": false
144 }
145 },
146 "source": [
147 "## MPI simulation example"
148 ]
149 },
150 {
151 "cell_type": "markdown",
152 "metadata": {
153 "slideshow": {
154 "slide_start": false
155 }
156 },
157 "source": [
158 "This is our 'simulation', a toy example that computes $\\sin(f(x^2+y^2))$ for a slowly increasing frequency $f$ over a gradually refined mesh. In a real-world example, there typically is a 'simulate' method that, afer setting up initial parameters, runs the entire computation. But having this simple example will be sufficient to see something that changes visually as the computation evolves and that is quick enough for us to test.\n",
159 "\n",
160 "And while simple, this example has a realistic decomposition of the spatial domain in one array per MPI node that requires care in reordering the data for visualization, as would be needed in a real-world application (unless your code accumulates data in the rank 0 node that you can grab directly)."
161 ]
162 },
163 {
164 "cell_type": "code",
165 "execution_count": 5,
166 "metadata": {
167 "collapsed": false,
168 "slideshow": {
169 "slide_start": false
170 }
171 },
172 "outputs": [],
173 "source": [
174 "%%px\n",
175 "\n",
176 "stop = False\n",
177 "nsteps = 100\n",
178 "delay = 0.1\n",
179 "\n",
180 "xmin, xmax = 0, np.pi\n",
181 "ymin, ymax = 0, 2*np.pi\n",
182 "dy = (ymax-ymin)/mpi.size\n",
183 "\n",
184 "def simulation():\n",
185 " \"\"\"Toy simulation code, computes sin(f*(x**2+y**2)) for a slowly increasing f\n",
186 " over an increasingly fine mesh.\n",
187 "\n",
188 " The purpose of this code is simply to illustrate the basic features of a typical\n",
189 " MPI code: spatial domain decomposition, a solution which is evolving in some \n",
190 " sense, and local per-node computation. In this case the nodes don't really\n",
191 " communicate at all.\n",
192 " \"\"\"\n",
193 " # By making these few variables global, we allow the IPython client to access them\n",
194 " # remotely for interactive introspection\n",
195 " global j, Z, nx, nyt\n",
196 " freqs = np.linspace(0.6, 1, nsteps)\n",
197 " for j in range(nsteps):\n",
198 " nx, ny = 2+j/4, 2+j/2/mpi.size\n",
199 " nyt = mpi.size*ny\n",
200 " Xax = np.linspace(xmin, xmax, nx)\n",
201 " Yax = np.linspace(ymin+rank*dy, ymin+(rank+1)*dy, ny, endpoint=rank==mpi.size)\n",
202 " X, Y = np.meshgrid(Xax, Yax)\n",
203 " f = freqs[j]\n",
204 " Z = np.cos(f*(X**2 + Y**2))\n",
205 " # We add a small delay to simulate that a real-world computation\n",
206 " # would take much longer, and we ensure all nodes are synchronized\n",
207 " time.sleep(delay)\n",
208 " # The stop flag can be set remotely via IPython, allowing the simulation to be\n",
209 " # cleanly stopped from the outside\n",
210 " if stop:\n",
211 " break"
212 ]
213 },
214 {
215 "cell_type": "markdown",
216 "metadata": {
217 "slideshow": {
218 "slide_start": false
219 }
220 },
221 "source": [
222 "## IPython tools to interactively monitor and plot the MPI results"
223 ]
224 },
225 {
226 "cell_type": "markdown",
227 "metadata": {
228 "slideshow": {
229 "slide_start": false
230 }
231 },
232 "source": [
233 "We now define a local (to this notebook) plotting function that fetches data from the engines' global namespace. Once it has retrieved the current state of the relevant variables, it produces and returns a figure:"
234 ]
235 },
236 {
237 "cell_type": "code",
238 "execution_count": 6,
239 "metadata": {
240 "collapsed": false,
241 "slideshow": {
242 "slide_start": false
243 }
244 },
245 "outputs": [],
246 "source": [
247 "from IPython.display import clear_output\n",
248 "\n",
249 "def plot_current_results(in_place=True):\n",
250 " \"\"\"Makes a blocking call to retrieve remote data and displays the solution mesh\n",
251 " as a contour plot.\n",
252 " \n",
253 " Parameters\n",
254 " ----------\n",
255 " in_place : bool\n",
256 " By default it calls clear_output so that new plots replace old ones. Set\n",
257 " to False to allow keeping of all previous outputs.\n",
258 " \"\"\"\n",
259 " \n",
260 " # We make a blocking call to load the remote data from the simulation into simple named \n",
261 " # variables we can read from the engine namespaces\n",
262 " #view.apply_sync(load_simulation_globals)\n",
263 " # And now we can use the view to read these variables from all the engines. Then we\n",
264 " # concatenate all of them into single arrays for local plotting\n",
265 " try:\n",
266 " Z = np.concatenate(mpi_order(view['Z']))\n",
267 " except ValueError:\n",
268 " print(\"dimension mismatch in Z, not plotting\")\n",
269 " ax = plt.gca()\n",
270 " return ax.figure\n",
271 " \n",
272 " nx, nyt, j, nsteps = view.pull(['nx', 'nyt', 'j', 'nsteps'], targets=0)\n",
273 " fig, ax = plt.subplots()\n",
274 " ax.contourf(Z)\n",
275 " ax.set_title('Mesh: %i x %i, step %i/%i' % (nx, nyt, j+1, nsteps))\n",
276 " plt.axis('off')\n",
277 " # We clear the notebook output before plotting this if in-place plot updating is requested\n",
278 " if in_place:\n",
279 " clear_output(wait=True)\n",
280 " display(fig)\n",
281 " return fig"
282 ]
283 },
284 {
285 "cell_type": "markdown",
286 "metadata": {
287 "slideshow": {
288 "slide_start": false
289 }
290 },
291 "source": [
292 "It will also be useful to be able to check whether the simulation is still alive or not. Below we will wrap the main simulation function into a thread to allow IPython to pull data from the engines, and we will call this object `simulation_thread`. So to check whether the code is still running, all we have to do is call the `is_alive` method on all of our engines and see whether any of them returns True:"
293 ]
294 },
295 {
296 "cell_type": "code",
297 "execution_count": 7,
298 "metadata": {
299 "collapsed": false,
300 "slideshow": {
301 "slide_start": false
302 }
303 },
304 "outputs": [],
305 "source": [
306 "def simulation_alive():\n",
307 " \"\"\"Return True if the simulation thread is still running on any engine.\n",
308 " \"\"\"\n",
309 " return any(view.apply_sync(lambda : simulation_thread.is_alive()))"
310 ]
311 },
312 {
313 "cell_type": "markdown",
314 "metadata": {
315 "slideshow": {
316 "slide_start": false
317 }
318 },
319 "source": [
320 "Finally, this is a convenience wrapper around the plotting code so that we can interrupt monitoring at any point, and that will provide basic timing information:"
321 ]
322 },
323 {
324 "cell_type": "code",
325 "execution_count": 8,
326 "metadata": {
327 "collapsed": false,
328 "slideshow": {
329 "slide_start": false
330 }
331 },
332 "outputs": [],
333 "source": [
334 "def monitor_simulation(refresh=5.0, plots_in_place=True):\n",
335 " \"\"\"Monitor the simulation progress and call plotting routine.\n",
336 "\n",
337 " Supress KeyboardInterrupt exception if interrupted, ensure that the last \n",
338 " figure is always displayed and provide basic timing and simulation status.\n",
339 "\n",
340 " Parameters\n",
341 " ----------\n",
342 " refresh : float\n",
343 " Refresh interval between calls to retrieve and plot data. The default\n",
344 " is 5s, adjust depending on the desired refresh rate, but be aware that \n",
345 " very short intervals will start having a significant impact.\n",
346 "\n",
347 " plots_in_place : bool\n",
348 " If true, every new figure replaces the last one, producing a (slow)\n",
349 " animation effect in the notebook. If false, all frames are plotted\n",
350 " in sequence and appended in the output area.\n",
351 " \"\"\"\n",
352 " import datetime as dt, time\n",
353 " \n",
354 " if not simulation_alive():\n",
355 " plot_current_results(in_place=plots_in_place)\n",
356 " plt.close('all')\n",
357 " print('Simulation has already finished, no monitoring to do.')\n",
358 " return\n",
359 " \n",
360 " t0 = dt.datetime.now()\n",
361 " fig = None\n",
362 " try:\n",
363 " while simulation_alive():\n",
364 " fig = plot_current_results(in_place=plots_in_place)\n",
365 " plt.close('all') # prevent re-plot of old figures\n",
366 " time.sleep(refresh) # so we don't hammer the server too fast\n",
367 " except (KeyboardInterrupt, error.TimeoutError):\n",
368 " msg = 'Monitoring interrupted, simulation is ongoing!'\n",
369 " else:\n",
370 " msg = 'Simulation completed!'\n",
371 " tmon = dt.datetime.now() - t0\n",
372 " if plots_in_place and fig is not None:\n",
373 " clear_output(wait=True)\n",
374 " plt.close('all')\n",
375 " display(fig)\n",
376 " print(msg)\n",
377 " print('Monitored for: %s.' % tmon)"
378 ]
379 },
380 {
381 "cell_type": "markdown",
382 "metadata": {
383 "slideshow": {
384 "slide_start": false
385 }
386 },
387 "source": [
388 "## Making a simulation object that can be monitored interactively"
389 ]
390 },
391 {
392 "cell_type": "code",
393 "execution_count": 9,
394 "metadata": {
395 "collapsed": false,
396 "slideshow": {
397 "slide_start": false
398 }
399 },
400 "outputs": [],
401 "source": [
402 "%%px\n",
403 "from threading import Thread\n",
404 "stop = False\n",
405 "nsteps = 100\n",
406 "delay=0.5\n",
407 "# Create a thread wrapper for the simulation. The target must be an argument-less\n",
408 "# function so we wrap the call to 'simulation' in a simple lambda:\n",
409 "simulation_thread = Thread(target = lambda : simulation())\n",
410 "# Now we actually start the simulation\n",
411 "simulation_thread.start()"
412 ]
413 },
414 {
415 "cell_type": "code",
416 "execution_count": 10,
417 "metadata": {
418 "collapsed": false,
419 "slideshow": {
420 "slide_start": false
421 }
422 },
423 "outputs": [
424 {
425 "data": {
426 "image/png": [
427 "iVBORw0KGgoAAAANSUhEUgAAAlMAAAGKCAYAAAAomMSSAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\n",
428 "AAALEgAACxIB0t1+/AAAIABJREFUeJztvXvwnkV5//9+UkNOQAgQKAxnQjgIhFA5lNgQUvyGgA0e\n",
429 "IAK1lFP40VoYEAsjh2GnxVq0RenUEUJQUQtKZ9IhQGxEAgQDgi0QkEkrIVBAwikRwYSEQPb3R/g8\n",
430 "+Tyf53Tvfe/huvZ+v2Yc5Xn23t1g9vq8Ptde927DWmtBCCGEEEJKMSz1BAghhBBCNEOZIoQQQgip\n",
431 "AGWKEEIIIaQClClCCCGEkApQpgghhBBCKkCZIoQQQgipAGWKEGW88MIL2HvvvVNPgwhk06ZNqadA\n",
432 "SC2hTBFSAWMMhg0bhm9+85td28yfPx/Dhg3D2WefHXFmxXj22Wdx8sknY9y4cdhll11w1lln4bXX\n",
433 "XuvY9oknnsCIESPwq1/9yvs8pk2bhmHDhrX85w/+4A86tn3llVewzTbb4Cc/+Yn3ecRm3rx52G+/\n",
434 "/TBixAhMnDgRt956a8v3GzduxD/+4z9izz33xMiRIzFp0iTcc889Xfu77rrr8NWvfrXlsx/96EcY\n",
435 "OXIkFi9e3Nb+nXfewbnnnovx48dj3LhxOOWUU/Dqq6+2tFm1ahU++9nPYty4cRg/fjzOO+88rF27\n",
436 "tsKfmpD8oEwRUpFRo0bhlltu6fr9vHnzMHr0aDQajYiz6s/bb7+N448/HrvtthtWrFiBJ554Au+/\n",
437 "/z4+9alPdWx/0UUX4eyzz8bBBx/sfS6NRgPz5s3Dpk2bmv/54IMPOra97LLLMGXKFMycOdP7PGJy\n",
438 "3XXX4brrrsPNN9+MNWvWYO7cubj22mvxr//6r802F110EebPn4/58+dj9erV+Pu//3ucc845uPvu\n",
439 "u9v6s9billtuwcknn9z87Otf/zr+5m/+BiNHjuw4h1NPPRW///3v8fTTT2PlypXYddddccIJJzT/\n",
440 "3b/33nuYMWMGdt99dzz//PN46qmn8Pbbb2P27Nme/20QohxLCCmNMcZ++tOftmPHjrWPPPJI2/cv\n",
441 "vfSS3WqrreyZZ55pzzrrLC9jPv/883avvfaq3M+9995rp06d2vLZ+vXr7YgRI+yyZctaPr/tttvs\n",
442 "tttua19//fXK43Zi2rRp9pZbbunbbunSpXb48OH2mWee8T6H+++/306bNs17v51477337NZbb22X\n",
443 "LFnS8vmDDz5ot9tuO/v+++/b3/zmN3bYsGH2xRdfbGlz66232oMOOqitz5/+9Kd2ypQpzX/+/ve/\n",
444 "b3fZZRf71FNP2b322sved999Le0feughu/POO9v169e3fH7wwQfb22+/3Vpr7Q9+8AN72GGHtXy/\n",
445 "bt06O378+I5/3wmpK8xMEVKRMWPG4IwzzsC8efPavvvOd76D448/HnvssUeCmfXm+OOPx4MPPtjy\n",
446 "2YgRIzBq1KiW2pu1a9fisssuwxVXXIHx48cHm4/tc7PVpk2bcNFFF+Hcc8/FQQcdFGweMXjjjTew\n",
447 "du1aTJ48ueXzww8/HL/73e/w6quv4oUXXsB2222H3Xffva3N//7v/7b1OXfuXJx//vnNf549ezYe\n",
448 "fvhhHHLIIR3nsGjRIhx//PEYMWJEy+czZ85sbqEuWrQIJ510Usv3o0aNwrRp07LYZiXEF5QpQjww\n",
449 "Z84c3HHHHS21JNZafO9732v5ATeYX/7yl5gyZQpGjRqF3XbbDddee23L1tY999yDQw89FKNHj8ah\n",
450 "hx6K++67r+X5JUuW4GMf+xi23nprfOxjH8PDDz/c8v2RRx6JL37xi05/joHtowMPPLD52Ve/+lV8\n",
451 "5CMfwcUXX1yoj9NPPx3Tpk1rytH69etxwAEH4Oabb+75nDEGo0ePxvjx43H55Zfj/fffb/n+O9/5\n",
452 "Dp599ln83d/9ncsfqYVHH30Uf/zHf4wxY8Zg4sSJ+NGPfgQA2GuvvTB9+nQ8+OCDGDZsGPbZZ5/m\n",
453 "M88++yxmzpyJMWPGYKeddsIll1yCdevWNb+fNm0a7r77blx++eXYbbfdMHr0aEydOhWPPvpo13ns\n",
454 "uOOOGD16NJ588smWz5944gkAwLbbbos99tgDb731Fl5++eW2NmPHjm357PXXX8eSJUvwuc99rvnZ\n",
455 "iBEjsNdee3Wdw8qVKzFp0qS2zydNmoTnnnuuZ5vDDjus2YYQQpkipBLWWjQaDUyePBkTJ07E7bff\n",
456 "3vzu3nvvxYYNG/DJT36yLevyi1/8AqeddhquvPJKrF69GkuXLsXy5cvxhS98AQCwZs0anHrqqfin\n",
457 "f/onvPXWWzj33HNbZOTNN9/EVVddhVtuuQWrVq3CKaecgtmzZ+O9995rttl///2dMmIvvfQS5syZ\n",
458 "gy9/+cvNbMVrr72Gf/7nf8aIESMwYcIE7L777vjSl76Ed999t2s/c+fOxUsvvYTrr78eAHDNNddg\n",
459 "v/32w5w5c7o+c/rpp+Oee+7Bm2++ifnz5+POO+/EBRdc0Px+w4YNuPrqq7H99tvj6KOPxi677IJz\n",
460 "zz0Xa9asKfzns9Zi1qxZOP/887FmzRp85StfwY033ohNmzbhhRdewP33349jjz0WmzZtwsqVKwFs\n",
461 "fnPy+OOPx5lnnolXX30VTz/9NBqNBj7zmc80+200GrjwwguxYcMGPPTQQ3jllVdw3nnnYebMmbjz\n",
462 "zjs7zmWrrbbCZZddhjlz5uDhhx/GunXr8Mgjj2DOnDk4+uijsc0222C33XbDX/7lX2L27Nl4+umn\n",
463 "sXbtWixatAiXXXYZZsyY0dLfd7/7XXzuc59ryzL14re//S223Xbbts+322675r/Xbm3Gjh3r9O+e\n",
464 "kOxJucdIiHauueYa+/nPf95aa+2NN95ojzrqqOZ3p556qr3iiiustdZeeeWVLTVTRx99tL3//vtb\n",
465 "+tqwYYPdeuut7erVq+2yZcvsmDFj7FtvvdU25vPPP28bjYZdvnx5y+e77rpr6VqiN9980x544IH2\n",
466 "z/7sz1o+v/LKK+3w4cOtMcY+/vjjduHChfbwww+3J510Us/+Hn30UbvNNtvYW265xf7hH/6hfe21\n",
467 "15zm86tf/coOHz7cPvfcc9Zaa2+++WbbaDTsRRddZB977DG7ePFi+6d/+qf2sMMOsxs3bizU51tv\n",
468 "vWWHDRtmn3322Y7fd6qZOu200+x3v/vdtrb77bef/e///m9rrbXHHnusPf3009va3HTTTXbixIld\n",
469 "57Np0yZ7/fXX2913392OGDHC7r///rbRaNj58+c322zYsMFeccUVdqeddrKjRo2yEyZMsMOGDbOP\n",
470 "P/54Sz/77befffrpp7uO1almaubMmfamm25qa3vPPffYAw880Fpr7YEHHmgXLVrU1uZb3/qWnTlz\n",
471 "ZtfxCKkbzEwR4onTTz8dzzzzDJ555hm8+eabuOuuu3Deeee1tdu4cSMeffRRTJ8+veUogJEjR2Ld\n",
472 "unVYvnw5DjnkEEyfPh0TJkzA2WefjR/+8IfYsGFDs48dd9wRBxxwQEu/e++9N1avXu0877Vr1+Kk\n",
473 "k07CjjvuiDvuuKPlu0WLFuGSSy7BNddcg8mTJ2PmzJn4z//8Tzz44IN45JFHuvZ55JFH4gtf+ALO\n",
474 "O+88XHfdddhpp52c5vTRj34U++yzDx577LHmPE499VTccMMNOOKII3Dcccfh7rvvxm9/+1v8+7//\n",
475 "e6E+x44diwsuuACHH344TjvtNMydOxdvv/12z2ceeughnHPOOW3HNjz33HNYvnw5gM2ZqdNPP73t\n",
476 "2U996lN49tln8cYbb3Tsu9Fo4JJLLsGLL76I9evXY+rUqTjmmGPw6U9/utlmq622wle+8hW89tpr\n",
477 "WLduHfbZZx+cdtppLbVWixcvxo477uj8luW4cePwu9/9ru3zt956CzvssEPfNttvv73TeITkDGWK\n",
478 "EE9su+22mD17Nm6++WZ8//vfx8c//vHm4ZpDj0VoNBp4+umnW44CGDgOYMqUKWg0GliwYAHuuusu\n",
479 "7LvvvviHf/gHTJ06tVlHNGbMmLbxhw8f3reIeygbN27EKaecgk2bNmHhwoVtr9C//fbb+PjHP97y\n",
480 "2fjx43HAAQf0rJl59913sWDBAhxyyCH4t3/7N6c5DbDVVls1z5rqNI+RI0fiiCOOwIoVKwr3+a1v\n",
481 "fQs///nPccQRR2DevHmYPHlyR1kYYNiwYbjrrrs6/v/053/+5812rv/eh/LUU0/he9/7Hv7lX/6l\n",
482 "a5u7774bS5cuxde//vWWz+fOndtzC7Ub++67L5YtW9b2+ZNPPol99923b5sJEyY4j0lIrlCmCKnA\n",
483 "UEmaM2cOfvCDH2DevHldf8ANHz4cxxxzDH74wx+2fP7BBx/gmWeeaf7z+++/j6OPPhpXXXUVli1b\n",
484 "huXLl+Opp57yNndrLc466yz85je/waJFi7D11lu3tdl///1b5gQA69atw8qVK7Hnnnt27fvSSy/F\n",
485 "Rz/6UTz00EP49a9/ja997Wtd2z733HP49re/3fLZypUrsWLFChx11FFd57Fp0yY888wzPYush/L+\n",
486 "++/j0EMPxaWXXorHHnsMw4cPbxb2NxqNthPEjz322Lb/nwC0CIa1Fj/+8Y/b2tx5552YOHFioTcg\n",
487 "L774Ypx11lk4/PDDO36/ceNGfOlLX8IVV1yBXXfdtfn5G2+8gfvvvx+nnXZa3zGGcsIJJ+BnP/sZ\n",
488 "1q9f3/JnWbhwYfMMrxNOOKHtTKt169bhgQceUH/OFyFeSbrJSIhyBtdMDXDwwQfb8ePH2/fee6/5\n",
489 "2dCaqV/+8pd2zJgx9pvf/KZdvXq1ff755+3s2bPtjBkzrLXWPvDAA3a//fazTz75pH333XftHXfc\n",
490 "YUePHm1fe+21rudMTZs2zT7wwAPNf/785z9vv/GNb3Sd+5e//GU7YcIEu2rVqq5tHn/8cbv99tvb\n",
491 "H//4x/btt9+2K1assCeffLL9xCc+0fWZBQsW2F122cWuXr3aWmvtz3/+cztq1Cj72GOPdWz/i1/8\n",
492 "wm611Vb2G9/4hn3nnXfs448/bidPnmwvv/zyZpuXX37Z7rDDDvbb3/62XbNmjX355Zft+eefbw8+\n",
493 "+GC7YcOGZrtTTz3V/tVf/VXHcX7961/b3XbbzT7wwAN2/fr19r777rPbbLONffLJJ6211v7P//yP\n",
494 "3Xnnne3rr79u/+///s9aa+2LL75ox44da6+44gr76quv2lWrVtkLL7zQHnTQQfaDDz6w1m6umdpz\n",
495 "zz3txRdfbFeuXGnXrFljb731Vrv99tvbO++8s+u/pwHmz59vt9tuO/vGG290bXP99dfbffbZp+XP\n",
496 "aq21X/va1+xf//Vf9x2jU82UtZvrpmbPnm1XrVplV69ebS+88EI7efLk5p9t48aN9tBDD7UXXXSR\n",
497 "XbNmjX3llVfsKaecYj/5yU/2HZOQOkGZIqQCxhj7F3/xFy2f3XDDDfbSSy9t+eyqq66yZ599dstn\n",
498 "//Vf/2X/5E/+xI4cOdLuvPPO9sILL7TvvPNO8/trr73W7rHHHnbkyJH2j/7oj+y9995rrd1cgL73\n",
499 "3nu3zWXatGn2wQcfbP7zEUccYS+55JKuc582bZodNmyYbTQabf+59dZbm+0efvhhe8wxx9iRI0fa\n",
500 "nXbayV5yySUt8xzMK6+8YnfaaSd7zz33tHx+9dVX2wkTJtjf//73HZ+799577ZFHHmlHjRpl99xz\n",
501 "T3vDDTe0tVm+fLmdMWOGHT16tB03bpw9++yzWwrbN23aZHfccUf7s5/9rOufee7cuXbixIl25MiR\n",
502 "9qCDDmoeTjnAOeecY0eMGGEPP/zw5mcrVqywJ554oh0zZozdfvvt7ZlnnmlfffXVln+Pd911l/3i\n",
503 "F79od955Zzty5Eg7ZcqUQodabtiwwU6YMKGn9L755pt23Lhx9j/+4z/avtt///2bMtiLbjL1zjvv\n",
504 "2HPPPdfusMMOduzYsfaUU05pe1lg1apV9rOf/awdO3as3WGHHex5551n165d23dMQupEw9qKm/2E\n",
505 "ECKAxx9/HJ/4xCfwxhtvYNiweBUMxx13HK6++mpMnz492pgDrFixgrVLhAiANVOEkCy47777MGvW\n",
506 "rKgilRqKFCEy+EjqCRBCiA/+9m//NvUUCCE1pT6/whFCCCGEBIA1U4QQQgghFYiyzbd0yFk8pH5M\n",
507 "aT8gWi6XxxtqwaT/573PG/H/ee3vJ0s+07+RC8Zvd23c3/2CYbUcd1S1541b85lT5zu1vwA3FW47\n",
508 "a9lPizW8zmkKLSy9vX8byXiLlwVimUsM6hdbCsUKU3i4ViKva2vd1lyUzBRligxFlVwVJaKEAWFE\n",
509 "DFAgY0B4IRtKSkGrKlL9MO6PiJCtwSgVr2RxsGCsii5aA5jiTZt4XqOUKVIrspMy5VkxwK+MqRYx\n",
510 "X8E9tEz1w7g/4ipbgJtwAfGlqxNFRcxnnBo8ptf4VyL2FIkhReKB8zo3Dm1LrkORMoUzKFP90J6W\n",
511 "rhuqJC6woPmWMh8y5k3CjJ9uOlIkyKcWqSKY8o+GzHAN4CxdnoXLFz5+RlSOWw6xxJdoAQ7r2RRr\n",
512 "VmTtUaZIVyhs+SB9e6AoVUVMjHiZ6l0EJ7W8mfKPukhXcOGKLFup4nbpGOOpTsubaJlC3Wxm0Bqh\n",
513 "TBGvUMDqSTBZ8yBjVQSsjHxVki1T/lFnpBTe+xIy49Y8hHClzGppiL2l4kSfGNBvfXvZNjR9u4Bd\n",
514 "3L/NYOLI1DLFMiU05ZsCDYubhCd4ViyxcAHlM14ixEuKVBUh4luKvmUr5FuJ2mJtjO3D2JJFmSJb\n",
515 "yEgEtQWXOqF5yzHlNmPl7UVT7fGuhJCxFLVfpnjTZKIFqJStaGu+YhZrgDJvGtqphbpuEkWmFmBG\n",
516 "6CFqQam3V6SiXPRSBzOtJBEvj3VequUL0FHbFRJT7rGisiV1+7BsvBL5oo3HYx16rceFcFtvUWTq\n",
517 "RLi/Jps7ZYokpSNe9ihwqkgayD0JmLrtRlPuseB0y5b5zHiZco+FKo6XXBRfFNeYVWnNe37TcBYW\n",
518 "OQ0fRaYaS0KPEIcyZ6fkgkT5EyNvQgPZAHWTsH4ElbQEW49Rs12m9FDlkHhWl3Frnly2EsSn0DEn\n",
519 "ROH7AAPrT6ZMTXdobELNggxFshymkrfogpZIxChYfvAiZiUFLLR0JRct7afOm2LNtNVr9UJSXHFa\n",
520 "m53W4CQ3NZInU6Q6JvUE+hNb5ELLWVAJiyRckgJhTsS8Z20wZbYYXYQrmmxpeTvRVcBMsWa+6rVy\n",
521 "L4rvRak1eJtEmWooWQy90HAScWxM6gnEk7KQMhZMxAJLmOTgKR3vW40RRAuIkN0C8j0ioujPEOPW\n",
522 "bb8YmEqypMSH0muNMiUEyldvTOoJbIFC1oOanPYsAYkHpYbObg2QVLqAdOIVSLCA6pI1gLaMlrd1\n",
523 "RJnKFMpZb0zqCcSRMgqZO9oFLcpbjRUL52MVzUet45IuWAMYt+ZF4pQU0RpMr3Xse40svR2Y4qhG\n",
524 "lCniTt3EzqQZNqSc+ZYyrSKmXbSKknpbERBYwzWAcWirRbAGY4o39SlaQIG4IORN6E5xgDJFCKBH\n",
525 "+Ez4IUJImS8Z8yZhGb+VlJIUbyu6SJe3y3CHYhzapq7NinQlj8+DS1NLVpH1LVOm+r3Nl/ovIyG+\n",
526 "SSVzxm93PkTMh3h5kS4PAZqS1Z3S4hVItoKIlineVOzPtQDbiD5EK7RgOR8gqlKmSHykLnTSmdBy\n",
527 "Zvx1VUXAqohXZeGibHklxuW3Q/EtWkBA2RqKpJhcJN6YYl0Ff9vQcd0WXaMyZSqTE9CDY1JPQDiS\n",
528 "go0EYma/jP8uU2e9pGS7BlMXGYt1bchgQmwfAokON00RCz0KFhDpSIcKbxeKlCntd/N5uWA0J0zq\n",
529 "CUREo8Clrhcz/rtMLV6ATPkaSg4ylupUefWyFTtW5X5ulsSjEbTLlHZUyqBJPQEHNArXYFLLFyBS\n",
530 "wHKXL43i5fWtxMAHnRYRruDna0kVrAFM8aY+zs1yWpMSr5NZgBmhh8iSKheYpkCctJnE42uXrDKk\n",
531 "EDPjp5uU8lVavLjN2JeYW4pFhCuIZJmC7aRuD3bD9G8S7HBSiTKFZY3gQ0ii7FUNUkgtcdGlzMQd\n",
532 "DkA9RassoQTNVO/CVcCiCpcn0cpRsHpR+YLcLkQXLVOsmYhYVGaNm95fV94uxCKn6VCmMkO6yKUS\n",
533 "tVoIGiAjMEol0nk8QymT8XKVrpiiVTe5AsIJFuBPsoAAojVArLji4xcn0/vropIlU6bOyFymKl7F\n",
534 "oBVJ4pZC0qIKmok3VFdyFbUIAbwXZbcXgwvXAJHP99GApDO1kovWUPrFCYGlAJ3W4EK4xXfKVJ1Q\n",
535 "Kn0ppS2mpEWRMxN+iL5okTLfQd+UfzSWcAEy6rc0CljMc7WSnadlCncnH9P7azvVrbsoMrW0IVum\n",
536 "olwkSrYgTOpiylpIOQsuYyZs9x2RKF4hf7M21R6PsaU4QCnpqmEdl7efLwXjpu8jHqJltFwJkQEz\n",
537 "W/4nZYp4JQvRjCxvIeXMt4wFETDjv8smEuWqGzWXrtgZLk2C1Y0Y24c+tg6THFQ6GB9xoM/6tIvd\n",
538 "uqNMEZWIkLyAkuZbyHxImDfxMn66aaJJsFzwKWPG/ZHQwhUjs5WDYHUjRFF8v7jjTbJMsWZtRCyE\n",
539 "p0wR0oOkEqZEvnxlv7zIl6neRRu5yhcQ/Y1FF+EKKloOkpWzYAEOMc5jNiuJZAVex9a6rSXKFCGD\n",
540 "EJHxAoJvTUqUL0Bw9mso2oQs0RuLIWQr1jEQuUhXymMdktZkVVyjlClCAiFGtHqhJPsFCMuAAXEK\n",
541 "aCVImMK3FCWfuwXoE69SsSxiJmuAoNuGfdaiSJni0QhuaFuYpBzi5CyQiEnLglG+CpC4XgsIt4UI\n",
542 "ULY6EePanaiSZQp11RWRNVOUKd1oCASknSSy5lHIfEhYWflivVcBlBXIB9tGBErJlra4GvJNw2hX\n",
543 "7Zj+cxlApkzlcp1MwBvcc0FbgCBuBBW0iiJWRb6iS5cp91gbkuSqFwlqtkLIVmjRyiF++iyAj3KM\n",
544 "g+n8MWUqRzKVuBwCB+lNEPlKJF1RhcuUGqoVLaLVjQCX3w6liHAFyWjVTLAGKBQPPEiWD8ESKVML\n",
545 "MCP0EEEpfficNpRJW05Bpk4E3370sNWoQrgGMOUfbUGbfLnKlnFr7ku06ihZA2u8yBxjCRbgJlki\n",
546 "T0DXLlMaUSeASkROcgDTTrQaL091Xaq2FQcw1R5vQ5qABRYswG82C3CI1UKOeCi6TquM60uwgPKS\n",
547 "JfKi4xNR7pLOulP2ziwJiJQ5Cls2RC2uT5jpAsqJV/JargEkyFbZei3j1jzpmVrCYluIGOb7MNJ+\n",
548 "a3IWFhUccDNRZKqxJPQIMih7s7sWJMpdcmkTFsQGQynrjOQieqCceEXLdJlSw7SiWbAGY4o3rZto\n",
549 "xYg9zuvYUbJkytR0h8Ym1CzIUKTKXyppSyJmmQa63PEmZCXlK4ZwRS2clyBYQ4l4NY/Pw0tD1WgV\n",
550 "RUJ88SJak9zUSJ5MkXKY1BMoRgqBiyFnwUUsgnRJCII54kW8SkiXq3C5yFaUjJZEwepGwFot6YXw\n",
551 "g5EeQ5zW4m0SZaqhaFFUxfdVDdIxaYePIWehZCyIgAWULumBUhNetxo936fWCdFZLUC+eLn8XDDF\n",
552 "m/aLf94kK5O3DIfScx1SppRCCYsORWwIkbYcNQXbmEg6kytkVgtIePYWIEe8AgkWEFGygKxEq2UN\n",
553 "UqZqDIUsKqFlTJWIAVHrv6QHZZ8EK5ivUCwfWraAmhbHJz47S0Lxu5S1PcVRjShTxJ1cpc3EHzKk\n",
554 "kIWQMWbEdJByG3EA9TVbA2gTrAFMsWZJToGvsOZjrWPKFMkbTSJnwg8RQsZ8Spg3+WItWFBSFMmH\n",
555 "kq0oW4haBWsAU6xZVNESJliUKUJCkFLiTJhufYqYDwGrLF6ehYuS1ZlK4uUgXC6yVUS0nCTLFG8K\n",
556 "IL1cdSPQ/YbR3jBMuE0oU6ZyPhpB6iIieoghasZPN1UFrIp0SZAtClZnKme2AmW1gmWzjFtz0T8n\n",
557 "isYf07+JD8nyLVhl1yxlivhFchCoC7GyYsZfVymlC5AhXgPUUcBSHmrqW7SAiDVaEuKt57cMowgW\n",
558 "4F2yZMpUTa6T8YJJPQFBSAgs0om9/Wj8dudjqzEn8RpMzhIWO5M1QHLRMsWb9iR2bAxwjINUyQI2\n",
559 "rz2RMiX1ouPKN7DnjEk9AY/UVcpSF+sbv91RvDqTo3RJLogPcqaWceqynVQxzuMW4QBVJcvbG4US\n",
560 "z5mSKlNaUSuBJvUEelBX4eqH4sxXqq1GL28wspi+LzGzW8mK4U3hYVtJGc8CnZVV9Yws5+t0JMrU\n",
561 "AswIPYQYyt7eLg1RwmZST+BDKFzlCC1kpnoXVcSrjHRVEi7WczlRWroKypbPjFYwyZIQuxJIViXB\n",
562 "knjRMZY1mv+zzL1QxA2pQpdc0Eza4UUENI34ljFT/tEy0hVNtvi2ohOULMiJSZ5Fy4tgSZcpsgXt\n",
563 "YilF2pJJmkkzbAtSgmFqfAqXqfa4q3BRtmQg7fys2p+dlViwZmGR0/BxZOoMB5mqcF8UcUey0KWS\n",
564 "tSRyZuIP2ULqwBmCENuLpnoXuWe3gPykq5RoBTrWIcjZWaZ4UwCy3yYcjOn9da+1uBBuPwfkyRTp\n",
565 "TCaSmVreYglaNCEzcYbpikYJE17DVbZ+K2rtFrNcTUJvFwKKJAuIExOqrGHT++uB9SdSppY2wspU\n",
566 "sFvVSXcEyl1MUQspZUFFzITruolGwepFKPky5R+NJVwpZWsAzdIlQbS8n5llCnfXimTJMu0f2alu\n",
567 "XWQhU6Q3qmUzkbSFEjPfEhZEvIz/LgHkJ1ndEFIwH2M70Vm2uI3YQkjZ8pXNCiJY0s/FAmAXu3VN\n",
568 "mSJeES1uEcQshIT5FDCv8mX8ddWkLsI1lKoCZso95iJcQUWrgmTlIlaDCSVZFKweDFmDlCkiHtHC\n",
569 "NUCkjJh0+QIUCNgAqYNxDAIV4nZCu2jlJFmh3zQULVlAkrVtrdtao0wRNVDCNiNdwLxvPRq/3bWQ\n",
570 "g4AlyGqFPP4h5tahVuGKcfp79CMcTKHh2gm0hilThPRAtJAFFjHfEiZSwIyfbrqiSb581G4Z90dE\n",
571 "iRZQu8yWlsNIpWexKFOEREaMoAWSMV8SVlW+VGw3SpctAcXxoWQr9puHtZEtj5LlRbAAt793Jdek\n",
572 "SJkaes7+J9WjAAAgAElEQVSU1r+EhMQkqqR5FDEf8lVFvCpLl6n2eAvS5aobvqTLuD8SqlYrVkE8\n",
573 "oPdnnFPM8XR8Q3TBKrgmVciUVrQuEFJvgkmZBwGrIl5lhauSbJnyjzbRKljdCHiA4lCKilYQyaph\n",
574 "bdZgfItWlfOxvApWl/VImVJEDguM6CZ49kupcAECpAvIR7wivIXoW7RCSlYOsT9EFqvqAaQ+67BE\n",
575 "Ho2Q7KJjj6fwkjwCAClOlG1GT9uLqYQLECJdgD7xipTRSiZZQK3qsVLVYoXaJqRM1ZEaSKPG4JIz\n",
576 "daznyqaOC5ArXhGPeQhRm8Utwy2kPN3dh2CJvE5mAWaEHkIcpd8s0YRyidMSlHJCS7ZLTS2XKTVU\n",
577 "Z3IVLECPZAHZbxumOt3d9TwskTJ1IspdylmEMjel1xU1gqdA0jQFr1wIImIVxauMdEWTLVNqmFak\n",
578 "CtZgFGezQh5Qqi1GlVrfAQVrIdzWm3qZyhVtkihK1BLLmLYgljteJKyCdLkKVxnZiipaGgSrE2Wk\n",
579 "yxRv6rM2K0QmS3Nc8lnsXlSwRMpUY0noEdJQ5lZ2qUiQtyRCFlm8NAe0XIlxNUc3YmS2om8f1kW2\n",
580 "TPGmRX5W9IvBFKwt+C5277QOZ2GR0xCUKaVoELlUghZVyhJkwbQGQG1421YsKVtl67aiyBZQfRtR\n",
581 "g3QJFyyAkgWEkSuZMjU99AgBMaknkA5JwhZbzChkpBvea7dylC3j/khHJAqXgO3CVJIF6IodlbYH\n",
582 "J7mpEWVKMyb1BMqTUtRiiVkUIUtUH6YpoMZEWpF8dvVaA0iTrMCHkiapx8r4jKxC6/Q2yhQJjUk9\n",
583 "gXZiy1lIIQsqYZHkS0NAlYC2rcSgWS3j1HU7uQgW4FWyvB7fkPH5WG1rUaRMNYT9Jc8J37fAS8Gk\n",
584 "GzqkmIWSMO8CFlC6pAdVqaR4K9FFtlxEK4pkSZOrwQjIZHnLYmWawZriqEaUKeIXyXJnwg8RQsR8\n",
585 "CphX6QogXNIDrFRii1aojFbtJWuAQIXv0d4qdIwNEtc9ZYrkgRQpM2G79y1fvsSL0qWfFFuIIbcN\n",
586 "o9VmSZatAJJFweoMZYrUBynCNRgTtnvKV3lSB2cJeC2QL1GnJWLbEMhHtARnsIACcUHw1TmUKUK6\n",
587 "UTP5yl68WNflnZSnxYsQLVO8aUekyJZLrDPFm9YpiyVTprS+zSdlYRAZSJExE65rXwJG8cqXFCfG\n",
588 "FxUtkZIl6edIAMmKerp7xENHKVNakLTASHxSiJnx36UP+aoqXpWFizVd3qkkXAFqtIJIlincZSvS\n",
589 "Yn+iLFavde/7wNEy65EyVVekLVAShxhSZqp3kVq6SguXR9Gqu2ANJoZs+RatQpJlCnXVjsT4nSCL\n",
590 "5UWwPGWvZMpUHe7mM6knIASJQYG0EkrATPUuqkiXVtmiZMXbOgxRl1WrTFbktwljbg8OXYeUKdIZ\n",
591 "k3oCAZAUZHIhZKbL+OkmhXBV2kr0lN2qm3TFLIanZJUkwVEN0QRL4gnoJ0LOhbkSKX1ru0RM6gmU\n",
592 "QEJQ0kDoLUXjp5uqW4qahQvIW7okH05aRLKCFr1LiGOBLoFOIlgSZWoBZoQeojRlLvbMBfESZ1JP\n",
593 "oAcSApdEalLDVUa4pMgWQOHqSKCrdpJKloQ4pVWwJlGmskSL9IkRNJNwbAkBTAvCtxXLSlfU7Ba3\n",
594 "EZ0oJVues1nJBEtKbEogWM6HjEqUKSxrBB8iNa7XKEhEgrBFlzETdzgAcgKaVnwLmKn2eBnhipLZ\n",
595 "YnG8M86iFVmygtRhSYpHnmuwKr09iEVOU6FMKUa6wKWSs6hCZuIN1YKkAKgBHwJmyj3mKluuohVb\n",
596 "sihYfZCcxTLFmgGQFWM8HtNQVLBkytQZimWq5NUI2pEkaimkLJqQmTjDtCApSKYmoWQBcbJalC3/\n",
597 "ZCVYgD7JiiBXC+H2M4AypRHFgpdS0mJJWXARM2G774iEABoTX1uJptxjsWq1UtRo5SpbIWuxfAkW\n",
598 "EPDIhpQxIsD2oEiZWtrYLFNebywncREocLHFLLSMBZUwE67rJnURLp81W6bcYyIzWgNQtgDIOeXd\n",
599 "m2SZQt20kyouVMxe2aluw0WVKVKdbIU0kayFFrIQAhZEuoz/LpvURbIAMcXxudVpDVB70SoQJ6Nu\n",
600 "E5r+TdpIHQ8KrlG72K1byhQpjWixiyBnoUTMp4B5Ey/jp5s2UgfWlCgpig8qWsxitRGiHku0YEmI\n",
601 "AR3WImWKiEekhCmUL5HSBYQRLwkBNxVVpMu4PxJKtGJIFuXqQyQJluk/lzYErHdr3dYdZYqIhuLl\n",
602 "h1qJFyAiGAeHkuVELqKVxRah6T+XNiKvacoUqTUi5QsILmA+5cuXeFG6EpBg61CEZAG1z2bVMoMF\n",
603 "BFvDlClCKiJGyAIJWLbiZap30UJOoiVcsoDiohVDsnIRLMAxnkV8i1B6/RVlihABJBOyAALmQ76q\n",
604 "SpfIQvocZCtREXyIbFboM7NyEizAIUZJEixTbC5NKqxRyhQhmRBFyDzKV1XpqiJclWXLVHu8SQ6C\n",
605 "NRShtVkSMlm5CRbgV7L6xYTo2SuH9UmZIqSGBBcvT9KVSrgqyZYp/2gLOYrWAGWEy7g1TypZNRas\n",
606 "2BksKdkrkTJVt+tkcltMRC/RthvrLFsAhasbQiQrtWABef1ciClYqbJXlClSiJwWNqmOpi3FKsKV\n",
607 "bCvRlH+0hRxkS4hgAZQsX0jKXvm6e1DkoZ1YVmOZ8nSNgmZyCBakHsKlWrY0i1bZuizj1jyZZNXs\n",
608 "2IbYbxCGyF5Rpkgrmcmc1uBSR4LJlwfhip3domiVIFLhu2/BAgpKVo0OIE1xRU7V7JXIi44XYEbo\n",
609 "IcRT+gZ2iSgSNG1Bp854l68K0hUzs1VatEy5x5poFCxArWSFFCyNcc63YPmWK5EydSL8FQiS/ogX\n",
610 "N2EypjEQ1Rmv0lVSuMrIVjTRMu6PNNEqWAME3i70fT5WKMHSGtNiCla/9bgQbmtPjEzVAY3CKEbM\n",
611 "BAiY1gBVFyRIFpCxaAG6ZStg4bvPLBYFawsp5UqkTDWWFGvnev0A6Y5UcUsqZ4mFTGMw006Qui2K\n",
612 "Vne0yFaErUIKln9iytUsLHIaSpRM1QXN0ihF0pJIWUIZ0xb0tKC9TiuKaBnnIVrRIlhA8GMbKFh+\n",
613 "CSlXlCnShmR5SyFnUUWM2TD1SNg+jCFaUbNZmgQLcJcsU7ypL8Hiie4l12q3NTnJTY3iyNT00CMo\n",
614 "xKSegD9Sy1pMIQsuYpHkS1OA1IA34SohW6FFK5pkaRMsILlkpRAsbbGjdPaKMkUKYVJPoDMxxSy0\n",
615 "hAUTr8DCpS1YasGLcDnKlqtoBZUs49YcgE7BAtwkyxRv2i8+etseBChXt0mUqYbSBRGLKsWQEjHp\n",
616 "hg4tYyEELIh0BRIubYFTOqkyWpSsBATKYkUTrLptDVKmakYuImbSDBtSviheYfrNHU1bhuIkC9Aj\n",
617 "WonkCvC4PZhh9qq5/ihTxAnNMmbiD6lJvjSJF6Ar4KYgpWQBbqIlUrIA+aKlPXsFFIoRGtb6FEc1\n",
618 "okwRP2iSMhNvqFDyJVq8KFxJSFGTBQiRLFO8aUekSlagoxqkZa8krmvKFNGPVDEz4YcIIV8+xcub\n",
619 "dHFrMTqVZStQXVawIxyMU7et5CRXgBfBipm9krCOKVOEDCW1nJlwXfuSL1/CJVm2JARoqcQUrRCS\n",
620 "FVywpMoVEEywfGSvAD/F7SnWLmWKkFDElDLjv0sf4lVVurzIlkfRomD1ppJkBRAsoLhk1VqwgCBb\n",
621 "hFLeHIyxbilThKQmlnQZv91RttqhbLUTK4uVVLCA/CQrgVwBnmqvEtRdyZSpOhzaKXUBEVkwu5Ve\n",
622 "uDxvIdZduGIXvjOL5QnKVU8oU3VG4oIl7sSu8TL+uqJsbYGSVbEDLYIFuK8hibE60ZuDXrYGA5x3\n",
623 "RZki1ZC4yEl3FGa6qgpXDrJF0Sr5YOI3CoMJltS4q7XuyoNcyZSpJaFHEIBJPYFESA0CpDOh5cv4\n",
624 "6SaVcFUSLWa0vFBKtBK9TVi77BXg/WBRSXIFbFl/lKk6YFJPwANSAwVpJ4SAmWqPV5GtMqKVWrLq\n",
625 "LlhACckKkMVKKliSY6bHi52j1F0VWZMSr5M5EWEvn42N8/UHGjGpJ+CI5EBTB4QJV1nZiipalKzK\n",
626 "ULCExr0c5IoyVQ/UCZ1JPYE+SA1K2hEmWQBFK3dCCpb4LUKpcUyjXEmUqQWYEXqIZLhehaAFkbJm\n",
627 "Uk9gCFIDlxYoWqXGomi5k1qwkh3RIDVGaZCrSZSpWiFd5pJLmUk7vNhgJplQRfKm2uNlRCtqIXxF\n",
628 "yaqTYEkock+SvZIcj4que9P7a29yJVGmsKwRfAgJuJx1IhkJghZdwkzc4VqQHOAk41u6TPlHY4lW\n",
629 "bMmiYPVBsmCZAm0kx55IctVtHc7ComLjfwhlSjlSBS6FkEUTMBNnGACyg50GfAqXKfdYDNGKKVl1\n",
630 "EiwgnGSJlCtAdswpsp5N76+LypVMmTpDqUw5vvmhHSliFlPEogiYCT9EC5KDoTR8yZZxf0SkZFGw\n",
631 "+hIygxVVsEyBCQGy40lAuVoIt58NlClpKBe4lEIWS8KCC5gJ230LkgNlSjKWLGax/BLyNHdmrxzp\n",
632 "t25N/y4G1h9livRGuKzFlrHQAhZUvEy4rtuQGjxjknjLkJKlAw1yBXjOXgEyY0QFubJT3YaKIlNL\n",
633 "G3nIlJfb0XNDkJzFErFQAhZMvEyYbluQGEhjIKAIPifJAvIULQ2CRblCy5+PMlVDspG8BGIWWsB8\n",
634 "i1cQ4TL+u2wiMaDGJOF2IeAmWlLrsQbITbKyKGw3/Zs0kRYL+qxNu9itO8oUcUK0uEWSsRAC5lO6\n",
635 "vAqX8ddVE2lBNSVVZcuUe0yMZDGD1UIIwaJcFWTIWqRMEVGIla8I4uVbuihcNaSKbBm35qEEi3JV\n",
636 "nlTZK8oVYK3b2qNMETGIFK/A0iU5y0XhEkpEwQJ0S1ZugkW5igdlitQGUfKlTLrECZfx000LdZIt\n",
637 "ClZhchIs1XJl+s+jhcjrmTJFSB+SS1hA8fIpXT6Ey4tsmepdtFEH0Ypcj0XBSg/lyh+UKUICkUTC\n",
638 "AoiXL+GqKlviRIuCVQxTvGlywaq5XAEl4hblCgBlihBxRJMwj+LlQ7iSypapNHQrlKz+mOJNXc/F\n",
639 "KipZFKziOMUkjXLlYc1SpghRiDbhqipbVUSrckbLVHu8CSWrN8ateVHJ8i5XQCnBolx1p6pcScha\n",
640 "UaYIyZAospWBaAFCMlo5ixYFqyO5yJXvbUEf51ylyFqJlCmJd/Pl8hefEE2iBVSTLYqWUCIJVoga\n",
641 "rJDbgzn8nImdtQJkyBVlKhNyWIREBppkK1VWS8TWYW6SJUywmL3yR+GYom1LcNAapEyRvuSyoIlf\n",
642 "gkqXB9lKIVrMZAWgjGQZt+Y+BSvkHYS5xGJfciUpayXyOhksUypTFS/mzJFcFj+pRhDxqihcZWVL\n",
643 "pWRRsIIIVursVS7xVYpcVRErypRWMhO3XIICKYd32aogWioky5R7rIXcBAtwlyzj1tyXYFGuelMo\n",
644 "HgjbEhQpUwswwz1VSqqhUM5yCRykM7lks6LWZZlSQ7WSm2QFFCzKVXi0ZK3s1L7dtxBNpupIVgKp\n",
645 "RM5yCDZ1g5JFyaqEArkCwrw1mEO8k5q1okzVEDXSJlTIcghIuSJpuxAQLlmm1DBboFz1JUlhe43k\n",
646 "KlbWqohYiZSpE+F2fYA2XO6U0oA4ORMmYdoDVk54la3IdVnRarKM+yNNKFh9kbo1mEOcSpm1Wgi3\n",
647 "tUaZUoYGcRMhYwkFLIcglgveZKukaMWQrKiCRbnqS3S5KhjrtMel2FkrkTLVWBJ6hDi4XtCpASly\n",
648 "lkTAEgmX9qCWCylFy1WyKFiBCXg0A+UqDD6yVr3W4SwscpoPZUo40gUulYxFla+I0qU9wGlHk2AB\n",
649 "bpJFwXIkUPaKcuWfEFkrmTI1PfAAJnD/GSBNymJLWBT5ipzp0hz8NJGzYAERC921C1aN5EpzbPF2\n",
650 "1c0kNzXKQ6YkYlJPwC8SZCyWgOUmXpoDo1RSF76H3CZkBqsANdsW1BxDSm8HUqZqgkk9geKkErEY\n",
651 "8hVUvCIJl+ZAKQ1NWSyRgkW5asOHXFGsNuOUtaJMkVKY1BNoJ6aEhRQvzcKlOXBKo7JoRXijMKhg\n",
652 "GbfmTTQKVkK5YtaqGH3X420SZaqhcDH4pszi0ohJO3wMAQslXkGkK6BsaQ2i0kghWaoFqy5yBRT6\n",
653 "9xNNrjIuZO+4BilTpIk2gTNxhwspXr6Fy7toUbJEU1vBMsWbtqBNsALJVdR6q0yzVs21R5kildAi\n",
654 "YCbeUKGkS7RwBZItjcFVCpUES6tcAfXIXgXaFoxWb5Vh1mqKoxpRpkg1pMuXiTNMCOGibJF+1FKw\n",
655 "TPGmLWgSrERyxazVFihTRDZS5cuEH0KycEkXLemBVxJSBYtyVYEAciUpayVxfVOmSJ5IkTATtnvf\n",
656 "wiVOtjyLlsQgLJXSkuUoWMmzV6Zwl61okStmraJAmSJkgNQCZsJ061O4fMiWRNGSEIw1IE2wRGWv\n",
657 "aixXzFpRpgipRgoBM/679CFcYkTLk2RRsPoTa4swRPaKcvUhCeQqx0NDKVOExCCmdBm/3UkQrcqS\n",
658 "xSxWNGJkryhXgRAoVoCOrBVlihAJxJIt47c7itYWKFmdkZa9olw5EOCy5ihZqwRiJVOmpkPHXzRC\n",
659 "YlBT0cpFsgCK1mCylytTrFkb0n/mCcxaSRIruTKVI9IXC9GHQtFKnc2qJFkULO9Qrrog/eeF56yV\n",
660 "drGiTGlF+kIjsggtXcZPNymzWRIki4K1mRiCpUqupMf7yNuBEk9hp0zVGekLlMQlpHCZ6l1UEa0k\n",
661 "kuVBsChXm6FcDUFy7BZWZxXrzUCZMrUk9AgFMaknoAzJC5xUJ5RsmepdlBWtspJFwUpLtnJlCnXV\n",
662 "ivS4W5PtQMqUFkzqCURAelAg7WQoWEBkyeIWYWW0yRWzVgUxvb+WJFaUqbpjUk+gIpIDR53JULK0\n",
663 "ZbEoVyXxKFfMWvVAkFh5karbBMrUifB/wWtMnM4n0YxJPQEHJAeVOpGZZGkSLMpVSQrIFbcEPeAS\n",
664 "G0zvr5OIFWVKJ2qFzaSeQB8kB5s64Fu2TLXHY2axKFjxCH1Ku4otQcmxTqNYSZSpBZjhpR+XSzDr\n",
665 "imgpM6knMATJwacOCBKtWFms2IJFuXJEqlyZQsO1IjW+eRQroPyRC33X4qSMZUoyuYieKBkzqSfw\n",
666 "IVKDUs74FC1T7jHRgsXslRMh5YpiVYGi69wUa+ZVrChT+aFB1ERImEk8vtSAlQsUrN4we1WYUnKV\n",
667 "SyG71DglbStQokxhWSP4EEVwuZk8R6RJWTIBM2mGBSA3kGmlJoLF7cFw1DprJTUeCRCrWVhUfA6o\n",
668 "mUxJQKvQpRax6OJl4g7XRGpw0wQFqzuUq75oyFpRrDpg+jdx2QakTNUQqYKWSsCiiZeJMwwAuYFO\n",
669 "CzUQLNZe+Sd11orbgUOIJFYAsBBuP0fiyNQZymXK4SZzjUiRsVjyFUW2TPghAMgMeJrwJVmm3GOu\n",
670 "gsXsVVqYtRJGkfVr+jfptA4pU9pQKmopBSyGdAUXLhO2ewByA6B0FAkW5SotoeSKYuVIAKmiTNUR\n",
671 "BUKWQr5CS1dQ4TLhugYgMyBKxodgmXKPhcxeUa78QbESgiexslPdhqVM1RmhEhZTvEIKVzDZMmG6\n",
672 "bSIxQEokkWBRruSTcjswiVhJjRkVxEqkTC1t5CFTle6DygkhEhZLukIJl0rZkho0JaAke0W5ik+q\n",
673 "rBXFahCOYkWZyoAspS2hgIWWrhCyFUS0jP8um0gNoKlRkL0KXndFuWqBYpWYgmvSLnbrljJVE9QJ\n",
674 "WmT5CilcvmXLu2gZv901kRhIJVBVsIz7I2LkimLVQm3ESmos6LEWKVPEO6JFLKJ0hRAu0aJl/HXV\n",
675 "RGpQlUBZyTLlHisqWJSrOEguYK+jWFGmSDLESlcE4ZIuWuIlC5AbZFNRJYNl3B8JIVfcEiwHxSox\n",
676 "xx1FmSKyESlcCmVLpGgZP920IDXYpiCiXIkoaKdYNXGOm9wKrIy1buuNMkXEIka8AsuWVNHyIlmm\n",
677 "ehctCAu4SaFc9YViBYpVSShTpHaIkK5AwuVTtLKVLAGBVwyR6q5CyFWMLcHc5EqiWOUiVZQpQnqQ\n",
678 "TLwCyJYv0RIjWcbLNDZDwdqMMLli1iocFCu/UKYIqUgS4fIsWz5Ey4dkUbCEUUaujPsjlKu0UKyq\n",
679 "Q5kiJCDRRStDyaJgCSKCXCXfEqRYudEn5kSRKqDY37OA65cyRUgiooqWMMnKJotVZ7lSuiVIsSqO\n",
680 "U4yqebaKMkWIQKKJlkfJYhbrQ+oqWBHkKmnWimJVnBqKFWWKEGVEES1BklVFsMS8SVhHwRK0JSgh\n",
681 "a1VLsSoYR6qIlZT7ASlThGREcNHKQLJEZK+AegmWoKwVxcovPsUqeLbK9J9DE8f1SZkipCYEFS1P\n",
682 "kqVSsEz5R1ugXPXGuDUvIlcUK39IylYB8cVKpEzhjDgylctfYkKqIF2yqghWsi1CU/7RJpSr3pji\n",
683 "TZNlrShWvZGQrQK81FbVWqakkMsCIvlAwWqHchUJihWAPH4uqCtaN/3nAKDjWqRMZUoOC5HII5hk\n",
684 "KRQsylUEKFbZxHKfYiVRqihTpCO5LGAShyCSVVGwVGWvTLnHWqBctWPcmieps6JYdSdwtsrnFqBd\n",
685 "XKzdAHFkapkgmSpxtkhdyWWhEz9QsChXQRGQtaJYVSOnbBVlSgo1kbYcAgCphjTJKitYlCthBMxa\n",
686 "+RKrUG8F5hBXfWWriqznStkq0/ljylQuZCJjOQQF4o53waJc9YZi1Yop3pRiFZ5C8UBYwTpliqgU\n",
687 "sRwCBumOJLkCygkW5UoIFCu1xMpW+ZAqkTK1ADNCDxEcp8WhESUCpj2YkC1IEqys5YpitQVTvGl0\n",
688 "saJUtVMxW1VFquzU/mMPhjKVENWCJly+tAebOkK5Kohxf6SFXOUqkFj5PG6BYtWKlIL1TuuQMlUj\n",
689 "VMiYMOnSHHjqiFfBoly1Q7HajCnWLLpY1eRgUIlSJVKmTkSxv4CpKfparGZECpgQ4dIaiOqGN8ES\n",
690 "LlfMWnkg4HELkrcBAZ3xzHlt91jDVeuqFsJt/VGmIqFV1ETIlwDZ0hiY6oKE7FWWclV3sTLFm1Ks\n",
691 "/OJTqoBydVWUqQyRLmLJhSuhbGkLUnVAY+aKYhWZXMQqc6kC4m0BDl2DImWqsST0CFsouqddB6RJ\n",
692 "WDLpSiRbGgNXjqSWq+yyVhSrQkjOVmmMTbHrqmovUzHJTdwkyFd04aJo1Q5tckWxikgOYsVs1RYq\n",
693 "SNUsLCo+IVCmxKBJzFJKV+6ypTGwaSdnuaJYlSRQ4TqzVf7xcbp6pzVImaohUkUshXRFk62IkqU1\n",
694 "yGnFi1wJFCughFwZt+Yt5CBXubwNyGzVZhwOAZUpU9NDj1ABk3oC8ZEkXzGFK4poRZIsjcFOK7nK\n",
695 "FcXKkYTZKqB/rKRU+T1ZHZPc1IgyFRqTegLVSSlfsWQruGhFkCxtgU8rqeQqC7GiVPWE2So/eJEq\n",
696 "ylQmmNQTcCOFcMUQLe2SpSkAaoRiBYqVK6Z/E0qVHypJFWWqxpjUE+hOjrIVTLQoWGqpLFcUK70I\n",
697 "fhOQUuXQeGANUqZIIUzqCbQTS7hCShYFiwygIWslTqxykCogiFhJzVZpih1Oa/I2iTLVULZAyqRu\n",
698 "c8aknoB+0QoiWQEFS1OA1IL0rBXFKgCJslWUqt4UWouUKcHkLGkm7fAxZCuEaGmSLE3BUjoUK0e0\n",
699 "i5V2qQIKxxUtcaLvGqRMZY52ITPxhwwtWr4li4JVLyhWDmiXKkDsFmBdpQrosgYpU6SJRvEy8YYK\n",
700 "KVniBYtyJZZKcqVVrEzxpk20i5XQbBWl6kMoU8QJLcJl4g4XSrR8SpYGwdIUSCVCsSoIxaoNaXVV\n",
701 "2mLBFEc1okyR/kgXLhNvqBCSJVawKFeiiLkdGOq4BdZX9YFSJQbKFEmDVOEy4YegYJVHS2CVRu3E\n",
702 "yjhNYTOapQpwi6mmfxOJ51VJXv+UKSIXScJlwg/hW7JEChazV8mJuRXIbcAEUKqSQJkiupEgXCZc\n",
703 "11IFS6pcSQyyktEuVpSqPhSNj6Z/kyhSVSIeSFnzlCmSJ5SswvgQLMqVfiSKFbNVnshcqiSsc8oU\n",
704 "qRepJcuE6ZZy1R8JAVcLscSK2arIeJQqIMKxCoqkijJFyACpRMuE6daXYInaGqRcRae0WGnKVpnC\n",
705 "XW5Bq1R5rqkCZElVqnVNmSKkHykky4Tp1odg5Zi5olj1h9mqHmgUq8wL1WOvacoUIWWJLVnGf5dS\n",
706 "slfMWulCmliJyVYB+sTK81lVkqQq5jqmTBHim5iSZfx3KSF7JUmuKFbdkSZVQIBslSk8dCuapKpM\n",
707 "zDK9v5Z0+GeMNUyZIiQWsSTL+O0uC7li1io40sSKW4AlyFiqQq9byhQhqaBclUKKWFGquhOjaJ1S\n",
708 "FRCt19QkzFJRpgiRRAzBMn67qypXOWStKFadkSRVgJAtQE1SBUQvVNdapE6ZIkQ6ygQrpVxRrORS\n",
709 "SqwSH68QTKq0CRWgT6oiH6dAmSJEG4rkimJVvY8cCS1WarYAc5YqU6xZLlJFmSIkB0ILlvHTjVq5\n",
710 "olgFQUq2ilJVkiJxx/RvUvU4BQknqcuUqemhR4Dev7yEFKEGckWxygdK1Ydo/LkU8d4/ydfT1Fem\n",
711 "YqBxYZA8CSlXxk83qbJWFCs5UKo+ROPPjkylquj6pExJRuOCIjoQLld1FCtK1RakvAXIQnVHIhap\n",
712 "x3zrD+i/PilTuaBt0RFZZCxXFCu9SJEqoJhY8UgF1PatP8pU3dCyIElaQsmVqd6FKrHiNqAXKFWD\n",
713 "0BLDBUlVjLOpKFOkFS0LlcSDYtUCxSodlKpBaIjVkS9RTnmBskyZWuKxM+OxL6JjAZOwCJWrOokV\n",
714 "parkg5SqNNRAqvKXqViY1BMQhIbFTfwRQq5MtcdjixWzVWmQIlUsVC9A5kJFmZKAST2BSGhY8KQa\n",
715 "FCtmqxIh4VR1r1JlCnXVioYYq02qCgoVZUojJvUEAqAhCBA3MhKrqNuAzFZVQotU1TpLBXgtUBeR\n",
716 "pRCBeVIAAArtSURBVLqNMpUvJvUEPKAlMJD++JYrU+3xmGLFbFV8nKUqp3oqTXEzl8M+KVM1x6Se\n",
717 "QEk0BQvSTgZixWyVDihVSvAkVcnu+ZMoUyei2unHPnC6JiBnTOoJOKApcJAt1FSsmK2KS2qpSlak\n",
718 "ri0uRro82XuWijIVlqylzKSeQAG0BZK6I0isxG8DUqqcqXU9lbZY6EGqogoVZUoeWQiYST2BHmgL\n",
719 "KnXFp1iZao+LzlZRqpwJKVXc+vOIgCxVYaGiTOlFpXSZ1BPogqYAU0eEiJVoqQIqiVXdpCr0GVXi\n",
720 "j1LQEvM8CRUQ+OLkSZSp7FEhXSb1BDqgJdjUDQFiFWsLkFIVnpBSJT5LBeiIc5He+KskVBJlagFm\n",
721 "BOm36G8BdUO0bJnUExiEhqBTJwRIFSA8W0WpKkxWW3+m0HCtSI9vQo5Q6LoO6yRTIchd0ESKlkk9\n",
722 "gQ+RHnzqhACxolTpR8vWX23v+hNy0GfHNUiZik8uAiZKtEzqCUB2EKoTvsTKlHtM9BYgpaoQzFIJ\n",
723 "jmUehQrwuO1HmZKNRvESIVkm9QQgOyDVgZpkqyhV4UgtVRSqLkS836+wUFGm9KNFuJJLlkk7vOjg\n",
724 "lDOUqu5Qqgqh4cDPWr7xJ2nbjzJVD6QKV20FS3qQyhWFW4CUKjmEkirRtVTSY5XHLFUlocIip2lE\n",
725 "kSksawTruuh+dZ2QJlpJBcskGld6wMoNSlVnKFWFcJKqyFmqWh6hIECoaidTvsldziSJVhLJMvGH\n",
726 "FB20coNS1RlKVV8kCxVQwyxVYqGiTEUkJ/GSIFnR5crEHQ6A7OCVGz7EypR7TKRU8ZqavnDbTxgJ\n",
727 "C9MpU0LRKl4pJSt7uZIcxHKCUtUKs1Q9kf7GX+2ECkhSmE6ZUoom2UolWJQrUglKVSuUqp7ULkul\n",
728 "If54OjW9iFBRpjJFumxlL1gmzjBNNAQ2rSiSKsn1VBSqLni6449C1YVIQrUQbj9bKFOZIFG2UghW\n",
729 "dnKlIbhpJWGxujipolB1RbpQAZ7PpNISc4qsX9P7617rUKZMnRFQphzeqqgj0iQrtmBFkSsTfggA\n",
730 "eoKcNpip2gKlqiMh7/gTe8inhngTUKjqJ1NVqaGMSRKsmHJFsSJdUZSlAtykKtbWH4WqC5qFCpAf\n",
731 "awIJFWUqNBnLlwTJiiVX2YiV9ECnDUVSxSxVGqRv+1GoumB6fz10/VGmJJCRcKUUrGzEyoTtvon0\n",
732 "gKeJTLf+mKXyA4VKIB6ECtiy/ihTWlAsXKkEK4ZcZSFW0oOeJpRIFbNU8UkpVICn86hMoaG2IDm2\n",
733 "eHrLD9i89ihTOaBMtChXJTHhum4iOfhpIpFUcetPPurPozLF5tOC1LjiUajsVLehKVPaUCBaucqV\n",
734 "arGSGvy0UVWqTLnHmKWSDYVKEJ6EijJVZwSLVmzBUitWJky3TaQGQG0kkCpRWSqent4GhUoQHoSK\n",
735 "MkW2QLkCEFas1EoVIDcQaqKKVJlyj+WQpQIoVQAoVKHwcJcfZYr0RqBgUaz6YMJ020RiMNREZlkq\n",
736 "ClV1KFQCqChUImVqaSOMTJU+RI20IkiwchArSlVNiSxV3PaTT+GfUdKECsjjtPQKQlUrmaoCRawP\n",
737 "QgQrhlypy1YZ/122IDUwaqDOWSoKVUckCpX3C5IBuXGjpFBRpgJRe/kSIFeaxUpltkpqcJROgmMU\n",
738 "xAgVwLf9hhBiyw/oHw8pVB/iuh7N5v+iTCWkVsKVWK4oVkMw/rtsIjFAaiCjbT8KVTWk1lBRqLpj\n",
739 "F7u1p0xFImvRylysKFWQGSA1UOcsFYWqhdoIldRY4bgWKVPKyFKyEsqVRrGiVGVOnYUK4N1+g6BQ\n",
740 "JcZhLVKmMiArwUokVpQqcOtPEhQqZ3IUqlQHe3p7ww+ojVBRpjIlC8HKUKxqLVVSg6VkWEflBIUK\n",
741 "8oTKFJuP2PhAmSKdUC1ZkeWKUuW3uyZSg6ZUhB+fQKEKD4UqMQXWIGWq5qiUqwQZK01iRanKEApV\n",
742 "YXKUKSCMUIk81FNqXOizBilTpAV1cpVJtqq2mSqpgVMiFConcpOqUrE5klB5rZ+SHBN6rEHKFOmK\n",
743 "KrGiVHVEhVABsgOoNCJflkyhkkMqoWJ2ahBd1h9lihRGjVxFFCtKlUckB1BpUKgKQ6FC35gobrtP\n",
744 "eizosP4oU6QUKsSKUtWGV6ky/rpqQXoglQKFqjA5CVXp2Euh8suQ9UeZIl4QL1eRxEqDVDFLlREU\n",
745 "qsLUXqgk1U+Z/k0AyI8Dg9YfZYp4R7RYUaqaiM9SSQ+kkigrVcb9ERFCxatnWD8lhQ/XHmWKBEWs\n",
746 "WCmWqloJFSA/mEqBQlWIXIRKcnYKqJ9QUaZIFChVlKpKSA+mUqBQFYJC1Rtx232A+BhgrdvaGxZo\n",
747 "HiRzlt4uNIBdh9IB2QXnmpACuPyA6ofrNSI9Mf66auLjrro6UPYHjvE6i3iU/GVI7C93jpSKqRHi\n",
748 "HVAwphiHDjOLAZQpUokBqRInVhGkatayn3qXKgoV8YZxa+6SzXTJooa+dDw3QsTSInHKZ+ypI9zm\n",
749 "I94R91tihK2/2mz7GT/dtCA83S+CiG/5cbsvPbUpRgfErn9u85HkiMtUMUvlL0tl/HTTAjNU/any\n",
750 "A8e4NReRoeJ2XxKKxByvGW8gm/VPmSLBqKtU+YRCRbxgwnUd4h7KKtRWqArEtmjbfcaxfQbrnzJF\n",
751 "giNSqgJCoSJBiLgd4v0g2A+JkZ0i4fGencoAyhSJhiipolBVx/jppkkGv52Kxrg1F7HdV5LaZqcK\n",
752 "wGL0MFCmSHTESJWyOqoLcJO3IEehUkrV7JTxMouOBNnuY3bKjUjHJBTCOLZXvvYpUyQZYoSqplkq\n",
753 "CpVSIgqViO2+kjA71R0fMYlbfa1QpkhS6pSl8gmFisQi1HZfYZidcouRnmJZkq0+xeueMkVEQKFy\n",
754 "h0JVYwRv9xWFRyWkJUp2ylQeQg2UKSIGClUGGM/9UahEkDw7RZLERxaiF4cyRURBoXJDXHaKxIPZ\n",
755 "qdpROD5KKkR3RekvUJQpIg4KlRvihMr46aaJ0uCaG1qzU9zq6w63+vxBmSIiEVGYrui3O6bja4rQ\n",
756 "e81cYHbKjdhxkbGlGJQpIpqchUpi/RSzUzXDhOtaUnaqlij6ZbANheudMkXEQ6EqBrf7SGhCnTvl\n",
757 "RInsFLf6uhPllzoTfojUUKYIKULNhIooom6F6EQcfIGFMkWUkDw7BehOmzvC7BTphtZC9Jxg3ZQ8\n",
758 "KFNEDSKEKhDMThHiAAvRi1GjXwBTQ5kixIUaBSem7hWRwVt9oWHdVHdE1k0py0JTpogqmJ1SiPHc\n",
759 "n7IgqwKTegKsmyK6oUwRdSQXKgXZKW71kZCwbooMpe6ZbMoUIaQrdQ+QqqjbVh/rpqLCX9B6Q5ki\n",
760 "hBBCCKkAZYqoJPlWXyCyfavPeO6PdVOEEEFQpgghhBASHpN6AuGgTBFSBgVF6IRog2/0Fadwdr5g\n",
761 "rMr2beJIUKYIIYRknTUYgGdNkVBQpohacq2bIkQDIi49JkQIDWutTT0JQgghhBCtMDNFCCGEEFIB\n",
762 "yhQhhBBCSAUoU4QQQgghFaBMEUIIIYRUgDJFCCGEEFIByhQhhBBCSAUoU4QQQgghFaBMEUIIIYRU\n",
763 "gDJFCCGEEFIByhQhhBBCSAUoU4QQQgghFaBMEUIIIYRUgDJFCCGEEFIByhQhhBBCSAUoU4QQQggh\n",
764 "FaBMEUIIIYRUgDJFCCGEEFIByhQhhBBCSAUoU4QQQgghFaBMEUIIIYRUgDJFCCGEEFIByhQhhBBC\n",
765 "SAUoU4QQQgghFfj/AcxNvvk8Uc7VAAAAAElFTkSuQmCC\n"
766 ],
767 "text/plain": [
768 "<matplotlib.figure.Figure at 0x1141a1450>"
769 ]
770 },
771 "metadata": {},
772 "output_type": "display_data"
773 },
774 {
775 "name": "stdout",
776 "output_type": "stream",
777 "text": [
778 "Simulation completed!\n",
779 "Monitored for: 0:00:50.653178.\n"
780 ]
781 }
782 ],
783 "source": [
784 "monitor_simulation(refresh=1);"
785 ]
786 },
787 {
788 "cell_type": "markdown",
789 "metadata": {
790 "slideshow": {
791 "slide_start": false
792 }
793 },
794 "source": [
795 "If you execute the following cell before the MPI code is finished running, it will stop the simulation at that point, which you can verify by calling the monitoring again:"
796 ]
797 },
798 {
799 "cell_type": "code",
800 "execution_count": 11,
801 "metadata": {
802 "collapsed": false,
803 "slideshow": {
804 "slide_start": false
805 }
806 },
807 "outputs": [],
808 "source": [
809 "view['stop'] = True"
810 ]
811 },
812 {
813 "cell_type": "code",
814 "execution_count": 12,
815 "metadata": {
816 "collapsed": false
817 },
818 "outputs": [
819 {
820 "name": "stdout",
821 "output_type": "stream",
822 "text": [
823 "{\n",
824 " \"stdin_port\": 65310, \n",
825 " \"ip\": \"127.0.0.1\", \n",
826 " \"control_port\": 58188, \n",
827 " \"hb_port\": 58187, \n",
828 " \"key\": \"e4f5cda8-faa8-48d3-a62c-dbde67db9827\", \n",
829 " \"shell_port\": 65083, \n",
830 " \"transport\": \"tcp\", \n",
831 " \"iopub_port\": 54934\n",
832 "}\n",
833 "\n",
834 "Paste the above JSON into a file, and connect with:\n",
835 " $> ipython <app> --existing <file>\n",
836 "or, if you are local, you can connect with just:\n",
837 " $> ipython <app> --existing kernel-64604.json \n",
838 "or even just:\n",
839 " $> ipython <app> --existing \n",
840 "if this is the most recent IPython session you have started.\n"
841 ]
842 }
843 ],
844 "source": [
845 "%%px --target 0\n",
846 "from IPython.parallel import bind_kernel; bind_kernel()\n",
847 "%connect_info"
848 ]
849 },
850 {
851 "cell_type": "code",
852 "execution_count": 13,
853 "metadata": {
854 "collapsed": false
855 },
856 "outputs": [],
857 "source": [
858 "%%px --target 0\n",
859 "%qtconsole"
860 ]
861 }
862 ],
863 "metadata": {
864 "kernelspec": {
865 "display_name": "Python 3",
866 "language": "python",
867 "name": "python3"
868 },
869 "language_info": {
870 "codemirror_mode": {
871 "name": "ipython",
872 "version": 3
873 },
874 "file_extension": ".py",
875 "mimetype": "text/x-python",
876 "name": "python",
877 "nbconvert_exporter": "python",
878 "pygments_lexer": "ipython3",
879 "version": "3.4.2"
880 }
881 },
882 "nbformat": 4,
883 "nbformat_minor": 0
884 }
This diff has been collapsed as it changes many lines, (533 lines changed) Show them Hide them
@@ -1,533 +0,0 b''
1 {
2 "cells": [
3 {
4 "cell_type": "markdown",
5 "metadata": {},
6 "source": [
7 "# Interactive visualization of MPI simulaitons"
8 ]
9 },
10 {
11 "cell_type": "markdown",
12 "metadata": {},
13 "source": [
14 "In this example, which builds on our previous one of interactive MPI monitoring, we now demonstrate how to use the IPython data publication APIs."
15 ]
16 },
17 {
18 "cell_type": "markdown",
19 "metadata": {},
20 "source": [
21 "## Load IPython support for working with MPI tasks"
22 ]
23 },
24 {
25 "cell_type": "markdown",
26 "metadata": {},
27 "source": [
28 "If you have not done so yet, use [the cluster tab in the Dashboard](/#tab2) to start your `mpi` cluster, it should be OK to leave the number of engines field empty (IPython will auto-detect the number of cores on your machine), unless you want to limit the run to use less cores than available in total. Once your MPI cluster is running, you can proceed with the rest of the code.\n",
29 "\n",
30 "We begin by creating a cluster client that gives us a local handle on the engines running in the (possibly remote) MPI cluster. From the client we make a `view` object, which we set to use blocking mode by default as it is more convenient for interactive control. Since the real computation will be done over MPI without IPython intervention, setting the default behavior to be blocking will have no significant performance impact.\n",
31 "\n",
32 "**Note:** if on first try the following cell gives you an error message, wait a few seconds and run it again. It's possible that the system is simply initializing all your MPI engines, which may take a bit of time to be completely ready if you hadn't used any MPI libraries recently and the disk cache is cold."
33 ]
34 },
35 {
36 "cell_type": "code",
37 "execution_count": 1,
38 "metadata": {
39 "collapsed": false
40 },
41 "outputs": [],
42 "source": [
43 "from IPython.parallel import Client, error\n",
44 "cluster = Client(profile=\"mpi\")\n",
45 "view = cluster[:]\n",
46 "view.block = True"
47 ]
48 },
49 {
50 "cell_type": "markdown",
51 "metadata": {},
52 "source": [
53 "Let's also load the plotting and numerical libraries so we have them ready for visualization later on."
54 ]
55 },
56 {
57 "cell_type": "code",
58 "execution_count": 2,
59 "metadata": {
60 "collapsed": false
61 },
62 "outputs": [],
63 "source": [
64 "%matplotlib inline\n",
65 "import numpy as np\n",
66 "import matplotlib.pyplot as plt"
67 ]
68 },
69 {
70 "cell_type": "markdown",
71 "metadata": {},
72 "source": [
73 "Now, we load the MPI libraries into the engine namespaces, and do a simple printing of their MPI rank information to verify that all nodes are operational and they match our cluster's real capacity. \n",
74 "\n",
75 "Here, we are making use of IPython's special `%%px` cell magic, which marks the entire cell for parallel execution. This means that the code below will not run in this notebook's kernel, but instead will be sent to *all* engines for execution there. In this way, IPython makes it very natural to control your entire cluster from within the notebook environment:"
76 ]
77 },
78 {
79 "cell_type": "code",
80 "execution_count": 3,
81 "metadata": {
82 "collapsed": false
83 },
84 "outputs": [
85 {
86 "name": "stdout",
87 "output_type": "stream",
88 "text": [
89 "[stdout:0] MPI rank: 2/4\n",
90 "[stdout:1] MPI rank: 0/4\n",
91 "[stdout:2] MPI rank: 3/4\n",
92 "[stdout:3] MPI rank: 1/4\n"
93 ]
94 }
95 ],
96 "source": [
97 "%%px\n",
98 "# MPI initialization, library imports and sanity checks on all engines\n",
99 "from mpi4py import MPI\n",
100 "# Load data publication API so engines can send data to notebook client\n",
101 "from IPython.kernel.zmq.datapub import publish_data\n",
102 "import numpy as np\n",
103 "import time\n",
104 "\n",
105 "mpi = MPI.COMM_WORLD\n",
106 "bcast = mpi.bcast\n",
107 "barrier = mpi.barrier\n",
108 "rank = mpi.rank\n",
109 "print(\"MPI rank: %i/%i\" % (mpi.rank,mpi.size))"
110 ]
111 },
112 {
113 "cell_type": "markdown",
114 "metadata": {},
115 "source": [
116 "We write a utility that reorders a list according to the mpi ranks of the engines, since all gather operations will return data in engine id order, not in MPI rank order. We'll need this later on when we want to reassemble in IPython data structures coming from all the engines: IPython will collect the data ordered by engine ID, but our code creates data structures based on MPI rank, so we need to map from one indexing scheme to the other. This simple function does the job:"
117 ]
118 },
119 {
120 "cell_type": "code",
121 "execution_count": 4,
122 "metadata": {
123 "collapsed": false
124 },
125 "outputs": [],
126 "source": [
127 "ranks = view['rank']\n",
128 "engine_mpi = np.argsort(ranks)\n",
129 "\n",
130 "def mpi_order(seq):\n",
131 " \"\"\"Return elements of a sequence ordered by MPI rank.\n",
132 "\n",
133 " The input sequence is assumed to be ordered by engine ID.\"\"\"\n",
134 " return [seq[x] for x in engine_mpi]"
135 ]
136 },
137 {
138 "cell_type": "markdown",
139 "metadata": {},
140 "source": [
141 "## MPI simulation example"
142 ]
143 },
144 {
145 "cell_type": "markdown",
146 "metadata": {},
147 "source": [
148 "This is our 'simulation', a toy example that computes $\\sin(f(x^2+y^2))$ for a slowly increasing frequency $f$ over a gradually refined mesh. In a real-world example, there typically is a 'simulate' method that, afer setting up initial parameters, runs the entire computation. But having this simple example will be sufficient to see something that changes visually as the computation evolves and that is quick enough for us to test.\n",
149 "\n",
150 "And while simple, this example has a realistic decomposition of the spatial domain in one array per MPI node that requires care in reordering the data for visualization, as would be needed in a real-world application (unless your code accumulates data in the rank 0 node that you can grab directly)."
151 ]
152 },
153 {
154 "cell_type": "code",
155 "execution_count": 5,
156 "metadata": {
157 "collapsed": false
158 },
159 "outputs": [],
160 "source": [
161 "%%px\n",
162 "\n",
163 "# Global flag in the namespace\n",
164 "stop = False\n",
165 "\n",
166 "def simulation(nsteps=100, delay=0.1):\n",
167 " \"\"\"Toy simulation code, computes sin(f*(x**2+y**2)) for a slowly increasing f\n",
168 " over an increasingly fine mesh.\n",
169 "\n",
170 " The purpose of this code is simply to illustrate the basic features of a typical\n",
171 " MPI code: spatial domain decomposition, a solution which is evolving in some \n",
172 " sense, and local per-node computation. In this case the nodes only communicate when \n",
173 " gathering results for publication.\"\"\"\n",
174 " # Problem geometry\n",
175 " xmin, xmax = 0, np.pi\n",
176 " ymin, ymax = 0, 2*np.pi\n",
177 " dy = (ymax-ymin)/mpi.size\n",
178 "\n",
179 " freqs = np.linspace(0.6, 1, nsteps)\n",
180 " for j in range(nsteps):\n",
181 " nx, ny = 2+j/4, 2+j/2/mpi.size\n",
182 " nyt = mpi.size*ny\n",
183 " Xax = np.linspace(xmin, xmax, nx)\n",
184 " Yax = np.linspace(ymin+rank*dy, ymin+(rank+1)*dy, ny, endpoint=rank==mpi.size)\n",
185 " X, Y = np.meshgrid(Xax, Yax)\n",
186 " f = freqs[j]\n",
187 " Z = np.cos(f*(X**2 + Y**2))\n",
188 " \n",
189 " # We are now going to publish data to the clients. We take advantage of fast\n",
190 " # MPI communications and gather the Z mesh at the rank 0 node in the Zcat variable:\n",
191 " Zcat = mpi.gather(Z, root=0)\n",
192 " if mpi.rank == 0:\n",
193 " # Then we use numpy's concatenation to construct a single numpy array with the\n",
194 " # full mesh that can be sent to the client for visualization:\n",
195 " Zcat = np.concatenate(Zcat)\n",
196 " # We now can send a dict with the variables we want the client to have access to:\n",
197 " publish_data(dict(Z=Zcat, nx=nx, nyt=nyt, j=j, nsteps=nsteps))\n",
198 " \n",
199 " # We add a small delay to simulate that a real-world computation\n",
200 " # would take much longer, and we ensure all nodes are synchronized\n",
201 " time.sleep(delay)\n",
202 " # The stop flag can be set remotely via IPython, allowing the simulation to be\n",
203 " # cleanly stopped from the outside\n",
204 " if stop:\n",
205 " break"
206 ]
207 },
208 {
209 "cell_type": "markdown",
210 "metadata": {},
211 "source": [
212 "## IPython tools to interactively monitor and plot the MPI results"
213 ]
214 },
215 {
216 "cell_type": "markdown",
217 "metadata": {},
218 "source": [
219 "We now define a local (to this notebook) plotting function that fetches data from the engines' global namespace. Once it has retrieved the current state of the relevant variables, it produces and returns a figure:"
220 ]
221 },
222 {
223 "cell_type": "code",
224 "execution_count": 6,
225 "metadata": {
226 "collapsed": false
227 },
228 "outputs": [],
229 "source": [
230 "from IPython.display import display, clear_output\n",
231 "\n",
232 "def plot_current_results(ar, in_place=True):\n",
233 " \"\"\"Makes a blocking call to retrieve remote data and displays the solution mesh\n",
234 " as a contour plot.\n",
235 " \n",
236 " Parameters\n",
237 " ----------\n",
238 " ar : async result object\n",
239 "\n",
240 " in_place : bool\n",
241 " By default it calls clear_output so that new plots replace old ones. Set\n",
242 " to False to allow keeping of all previous outputs.\n",
243 " \"\"\"\n",
244 " # Read data from MPI rank 0 engine\n",
245 " data = ar.data[engine_mpi[0]]\n",
246 " \n",
247 " try:\n",
248 " nx, nyt, j, nsteps = [data[k] for k in ['nx', 'nyt', 'j', 'nsteps']]\n",
249 " Z = data['Z']\n",
250 " except KeyError:\n",
251 " # This can happen if we read from the engines so quickly that the data \n",
252 " # hasn't arrived yet.\n",
253 " fig, ax = plt.subplots()\n",
254 " ax.plot([])\n",
255 " ax.set_title(\"No data yet\")\n",
256 " display(fig)\n",
257 " return fig\n",
258 " else:\n",
259 " \n",
260 " fig, ax = plt.subplots()\n",
261 " ax.contourf(Z)\n",
262 " ax.set_title('Mesh: %i x %i, step %i/%i' % (nx, nyt, j+1, nsteps))\n",
263 " plt.axis('off')\n",
264 " # We clear the notebook output before plotting this if in-place \n",
265 " # plot updating is requested\n",
266 " if in_place:\n",
267 " clear_output(wait=True)\n",
268 " display(fig)\n",
269 " \n",
270 " return fig"
271 ]
272 },
273 {
274 "cell_type": "markdown",
275 "metadata": {},
276 "source": [
277 "Finally, this is a convenience wrapper around the plotting code so that we can interrupt monitoring at any point, and that will provide basic timing information:"
278 ]
279 },
280 {
281 "cell_type": "code",
282 "execution_count": 7,
283 "metadata": {
284 "collapsed": false
285 },
286 "outputs": [],
287 "source": [
288 "def monitor_simulation(ar, refresh=5.0, plots_in_place=True):\n",
289 " \"\"\"Monitor the simulation progress and call plotting routine.\n",
290 "\n",
291 " Supress KeyboardInterrupt exception if interrupted, ensure that the last \n",
292 " figure is always displayed and provide basic timing and simulation status.\n",
293 "\n",
294 " Parameters\n",
295 " ----------\n",
296 " ar : async result object\n",
297 "\n",
298 " refresh : float\n",
299 " Refresh interval between calls to retrieve and plot data. The default\n",
300 " is 5s, adjust depending on the desired refresh rate, but be aware that \n",
301 " very short intervals will start having a significant impact.\n",
302 "\n",
303 " plots_in_place : bool\n",
304 " If true, every new figure replaces the last one, producing a (slow)\n",
305 " animation effect in the notebook. If false, all frames are plotted\n",
306 " in sequence and appended in the output area.\n",
307 " \"\"\"\n",
308 " import datetime as dt, time\n",
309 " \n",
310 " if ar.ready():\n",
311 " plot_current_results(ar, in_place=plots_in_place)\n",
312 " plt.close('all')\n",
313 " print('Simulation has already finished, no monitoring to do.')\n",
314 " return\n",
315 " \n",
316 " t0 = dt.datetime.now()\n",
317 " fig = None\n",
318 " try:\n",
319 " while not ar.ready():\n",
320 " fig = plot_current_results(ar, in_place=plots_in_place)\n",
321 " plt.close('all') # prevent re-plot of old figures\n",
322 " time.sleep(refresh)\n",
323 " except (KeyboardInterrupt, error.TimeoutError):\n",
324 " msg = 'Monitoring interrupted, simulation is ongoing!'\n",
325 " else:\n",
326 " msg = 'Simulation completed!'\n",
327 " tmon = dt.datetime.now() - t0\n",
328 " if plots_in_place and fig is not None:\n",
329 " clear_output(wait=True)\n",
330 " plt.close('all')\n",
331 " display(fig)\n",
332 " print(msg)\n",
333 " print('Monitored for: %s.' % tmon)"
334 ]
335 },
336 {
337 "cell_type": "markdown",
338 "metadata": {},
339 "source": [
340 "## Interactive monitoring in the client of the published data"
341 ]
342 },
343 {
344 "cell_type": "markdown",
345 "metadata": {},
346 "source": [
347 "Now, we can monitor the published data. We submit the simulation for execution as an asynchronous task, and then monitor this task at any frequency we desire."
348 ]
349 },
350 {
351 "cell_type": "code",
352 "execution_count": 8,
353 "metadata": {
354 "collapsed": false
355 },
356 "outputs": [],
357 "source": [
358 "# Create the local client that controls our IPython cluster with MPI support\n",
359 "from IPython.parallel import Client\n",
360 "cluster = Client(profile=\"mpi\")\n",
361 "# We make a view that encompasses all the engines\n",
362 "view = cluster[:]\n",
363 "# And now we call on all available nodes our simulation routine,\n",
364 "# as an asynchronous task\n",
365 "ar = view.apply_async(lambda : simulation(nsteps=10, delay=0.1))"
366 ]
367 },
368 {
369 "cell_type": "code",
370 "execution_count": 9,
371 "metadata": {
372 "collapsed": false
373 },
374 "outputs": [
375 {
376 "data": {
377 "image/png": [
378 "iVBORw0KGgoAAAANSUhEUgAAAk4AAAGKCAYAAAD6yM7KAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\n",
379 "AAALEgAACxIB0t1+/AAAGIZJREFUeJzt3XuQV3X9+PHXZ9W4SLYIISERuowojZKOoyRFhuYtFUPU\n",
380 "MElMSZukpkgdhWrN+1iTZSEKKKGoQ+p4SR1RQi3FqEa3BiEV0dAcZxCRIkSXPd8//Lm/gF1473I+\n",
381 "+7k9HjPMxOdzzvu8d9f2PDm3TyHLsiwAANiuulJPAACgUggnAIBEwgkAIJFwAgBIJJwAABIJJwCA\n",
382 "RMIJqtwrr7wSe+21V6mnQQds2rSp1FMA2iGcoAs1NjZGXV1dXHfdde0uc88990RdXV2cddZZXTiz\n",
383 "NC+++GKMGTMmevfuHZ/4xCdi4sSJ8eabb+a6jffeey+mTp0an/rUp6K+vj6OP/74eOGFF3LdRils\n",
384 "3LgxTjvttPjkJz+5zeVee+21GD58+GavrVq1Kvbff/+YMGFCm+vMmTMnhg4dGj179ozPfOYz8dBD\n",
385 "D+U2b2Bzwgm6WI8ePWL27Nntvj9r1qzo2bNnFAqFLpzV9q1bty6OPPLIGDhwYLz00kvx7LPPRnNz\n",
386 "c5x00km5bueiiy6KhQsXxoIFC+KVV16J4cOHxzHHHBPr16/PdTtd6e23344vfelL8cwzz2z35zp7\n",
387 "9uw47rjjWv/e1NQUI0aMiHXr1rW57h133BHTpk2LOXPmxJo1a+LKK6+MM844IxYvXpz71wEIJ+hS\n",
388 "hUIhjj766Fi1alU888wzW73/2muvxcKFC+Pkk0+Ocnuo/5IlS2Lw4MHx61//Ovr06RP9+/eP2bNn\n",
389 "x7PPPht/+9vfctvOzTffHNdee20MHTo06uvr44orroiIiEcffTS3bdTVde2vvnHjxkXfvn1j5syZ\n",
390 "2/y5trS0xC233BLf/OY3IyLinXfeiSOOOCK++93vxje+8Y021/3Rj34Uv/rVr+Kzn/1sdO/ePY47\n",
391 "7ri45JJL4tJLLy3a1wO1TDhBF9t1113j9NNPj1mzZm313s033xxHHnlkDBo0qAQz27Yjjzwynnji\n",
392 "ic1e69atW/To0SNaWlpy207Pnj23eq1QKLT5emeUIkhvvPHG+O1vfxsf+chHtrncww8/HHvvvXcM\n",
393 "GTIkIiI+9rGPxR//+Me48MIL25z3ihUr4tVXX41jjjlms9ePPfbYWLRoUbz33nv5fRFARAgnKIlJ\n",
394 "kybF/PnzNzv9lGVZzJkzp/Vow5b+/Oc/x8iRI6NHjx4xcODAuPzyyze7iPjBBx+MAw44IHr27BkH\n",
395 "HHBALFy4cLP1n3zyyTj44IOjV69ecfDBB8fTTz+92fuHHHJIfP/73+/Q1/G73/0uIiL222+/Nt+/\n",
396 "+OKLY999940NGza0fo2jR4+OqVOntjvm5MmT44ILLogXX3wx3nnnnZg2bVr06tUrjjjiiOR5vfnm\n",
397 "mzF27Nior6+PPfbYIy655JKIiJg4cWLstNNOEfHBUaeddtop/vnPf0bEB6civ/Wtb0Xfvn2jV69e\n",
398 "cdJJJ8Wrr77aOmZjY2P88Ic/jFmzZsWwYcOie/fuMWzYsPjNb36z3fkMGTKkdbvbctNNN2318993\n",
399 "333bXX7FihUxZMiQ6N69+2av77ffflFXVxerVq3a7jaBjhFO0IWyLItCoRAHHnhg7LPPPnHHHXe0\n",
400 "vvfoo4/Gxo0b4/jjj9/q6MIzzzwTX/3qV2Pq1Knx1ltvxVNPPRXLli2Lb3/72xERsWbNmjjllFPi\n",
401 "pz/9aaxduzbOPvvsmDlzZuv6q1evjmnTpsXs2bPjjTfeiHHjxsWpp5662RGJoUOHduhI16pVq2LS\n",
402 "pElx8cUXR7du3dpc5vLLL4/evXvHhRdeGBERM2bMiHfeeWebp5GmTJkSu+22WwwdOjR69+4d06dP\n",
403 "j1tuuSUpPD50/vnnR79+/WLVqlXx2GOPxYIFC2LlypUxZ86c1qNjLS0tsWnTphg0aFA0NzfH4Ycf\n",
404 "Hv3794+lS5fGG2+8EWPHjo1Ro0bF2rVrI+KDo1633npr3HnnnXH77bfHmjVrYvr06XHVVVfFD37w\n",
405 "g+S5tedf//pXLFmyJE4++eTkdd5+++3Ybbfdtnq9rq4uPvrRj8aaNWt2eF7AFjKgy/z4xz/Ozjjj\n",
406 "jCzLsmzGjBnZoYce2vreKaeckl1yySVZlmXZ1KlTs4kTJ7a+N2LEiGzRokWbjbVx48asV69e2Vtv\n",
407 "vZU1NTVlu+66a7Z27dqttrly5cqsUChky5Yt2+z1AQMGZEuXLu3U17F69epsv/32y0444YTtLrty\n",
408 "5cqsvr4+u+mmm7KPf/zj2fPPP7/N5U866aRszJgx2UsvvZS9/fbb2fXXX5/169cvW7lyZfL8Djro\n",
409 "oGzmzJntvl8oFDb7+4wZM7Izzzxzq+XOPvvs7Gc/+1mWZR/87BoaGrINGzZstszzzz+fdevWLVu1\n",
410 "atV257Vo0aJs4MCBbb73k5/8JJsyZUq76/7vfzsfuvPOO7MRI0a0uXy/fv2yJUuWbHdOQMc44gQl\n",
411 "Mn78+Fi6dGksXbo0Vq9eHQ888ECcc845Wy33/vvvx5/+9KcYPXp01NXVtf7p3r17/Pe//41ly5bF\n",
412 "/vvvH6NHj44hQ4bEWWedFbfddlts3LixdYy+fftudcpnr732irfeeqvD816/fn18+ctfjr59+8b8\n",
413 "+fO3u/zgwYPj6quvjnPPPTfOP//8dk/rRUQ899xz8fvf/z7mzZsXDQ0NUV9fH+eff34cffTR8ctf\n",
414 "/jJ5jlOmTInvfOc7cdRRR8XVV18dK1eu3Obyf/jDH2Lu3LmbfX/r6urilltuiWXLlrUuN2bMmDZP\n",
415 "izU0NGx16rMjWlpa4uabb45JkyZ1aL3evXvHunXr2hxv3bp1sfvuu3d6TkDbhBOUyG677Rannnpq\n",
416 "zJw5M+bOnRuf+9znWh9UueVt54VCIf7+979HS0vLZn82bdoUI0eOjEKhEPfff3888MAD0dDQEFde\n",
417 "eWWMGjUqmpubI+KDC9K3tMsuu3T4Qun3338/xo0bFy0tLfHQQw9tFRFtybIs5s+fH/vvv3/cdddd\n",
418 "mwXdlpYvXx5DhgzZar4HHXRQLF++PHmep59+erz00ktx6qmnxuLFi2P48OFt3sX4obq6upgyZUqb\n",
419 "398PT3kWCoVtfr925PERjzzySAwaNCiGDh3aofUaGhrixRdf3Op7unz58mhpadnuM6OAjhNO0IW2\n",
420 "3LlOmjQpbr311pg1a1a7Rxt22WWXOOyww+K2227b7PVNmzbF0qVLW//e3NwcI0aMiGnTpkVTU1Ms\n",
421 "W7Ys18cEZFkWEydOjNdffz0eeeSR6NWrV9J61157bWzYsCGWLFkSPXr0iO9973vtLrvnnnvGihUr\n",
422 "tnpm07PPPhsDBw5Mnmtzc3MMGDAgzjnnnLjvvvti/PjxMW/evHaX/8IXvhB33333VgHS1NTU+r+z\n",
423 "LIsHHngg3n333c2WWb58eaxYsSIOO+yw5Pltqa2LwrfUVpg1NDTE4MGDt3rg5YMPPhijR4/e7l18\n",
424 "QMcJJ+hCWx6xGDFiRAwYMCBWr14dX/nKV9pd7uc//3lcf/318Ytf/CLWrFkTr7zySpx++ukxZcqU\n",
425 "iIh44oknYtiwYdHU1BTvvvtu3HvvvbFp06btxsb/bmfChAnbfKL51KlTY8mSJbFgwYLo3bt30tf7\n",
426 "17/+Na6++uqYO3dudOvWLebNmxe33XZb3HvvvW0u//nPfz4OO+ywmDBhQqxcuTLWrl0bN9xwQ9x/\n",
427 "//1x0UUXtS43ffr0GDZsWJtjNDc3x4EHHhjTp0+PDRs2xAsvvBBPP/107LPPPq3L9O/fP5588slY\n",
428 "s2ZNrF+/Ps4666zo06dPnHzyyfHCCy/Ev//977jpppti9OjR8fLLL7eu9/7778cJJ5wQzz33XPzn\n",
429 "P/+Jxx9/PMaOHRuTJ0+OPffcM+l7sqU33ngjFi9eHKeccso2l2vvaNdll10WkydPjqeffjo2bNgQ\n",
430 "Dz74YFx11VXR2NjYqfkA2yacoAsVCoU2jzp9/etfj1122aXd5Q4++OB44okn4u67744999wzRowY\n",
431 "EXvssUfcddddEfHBEZMzzzwzTjzxxOjdu3dcc801cd9990W/fv1ax2tvPh/6xz/+0XprflsWL14c\n",
432 "L7/8cgwYMGCra4Hmzp271fLr16+Pr33ta3H55Ze3PpdoyJAhcd1118U555wTr7/+epvbmT9/fgwe\n",
433 "PDgOP/zwGDx4cNx3333x+OOPR0NDQ+syjz32WLt3n+28885xww03xK233hq77757fPGLX4wxY8bE\n",
434 "5MmTW5e59NJL4/jjj49Pf/rT8eabb0ZdXV0sWrQoBg0aFCNHjoz+/fvH/Pnz45FHHom999679Xs1\n",
435 "YcKEGDduXJx22mnRp0+fOO+88+KCCy6Ia6+9tt3v25a2/FnMmTMnxo8fv92jQ239txMRcdppp8UV\n",
436 "V1zRGn/Tpk2LefPmxaGHHpo8JyBdIevoRQ4AJdTS0hJ9+/aNhQsXxoEHHthl27300kujubk5Lrvs\n",
437 "slzHXbt2bWzatCn69OmT67hAcexc6gkAdMRf/vKXqK+v79JoKqb6+vpSTwHoAKfqgIpyyCGHbHbd\n",
438 "EUBXEk4AiXbkkQNAdXCNEwBAotyucXqqg/8SGzk+ry0DABXtou0vkrvhnTtulNsRp46Ek2gCAHLX\n",
439 "kQCrlHASTQBAyd3eufzp0ovDRRMAUMm65DlOggkAqAZFP+IkmgCAalHUcBJNAEA1KVo4iSYAoNoU\n",
440 "JZxEEwBQjXK9OFwwAQDVLLcjTqIJAKh2PuQXACCRcAIASCScAAASCScAgETCCQAgkXACAEgknAAA\n",
441 "EgknAIBEwgkAIJFwAgBIJJwAABIJJwCARMIJACCRcAIASCScAAASCScAgETCCQAgkXACAEgknAAA\n",
442 "Eu1c6gkAAGzPU3fkO97I2zu3nnACADot76Apd8IJAKpQrQVNVxFOANCFBE1lE04AEIKGNMIJgLIn\n",
443 "aigXwgmAThM01BrhBFCFBA0Uh3AC6EKCBiqbcAIIQQOkEU5AWRM0QDkRTkCnCBqgFgknqEKiBqA4\n",
444 "hBN0IUEDUNmEE4SgASCNcKKsCRoAyolwolMEDQC1SDhVGUEDAMUjnLqQqAGAyiacikAgAUB1Ek5F\n",
445 "MHJ8qWdQPUQoAOVEOFHWRGj5ErVALRJOQKeI2vIlaqF4hBNAlRG15UvUVj7hBABdRNRWvrpSTwAA\n",
446 "oFLkd8TpotxG6rhrSrhtAKBmVMepulJGGx0ndAGoUNURTlQWoVt5xC5ARAgnIIXYrSxCF4pGOAFU\n",
447 "G6FbWYRuRSlkWZblMlJTIZdhAACKbnjn8sfjCAAAEuV2qu7+4UclLXdi04K8NgkA0KW6/Bqn1MCi\n",
448 "/IlgAGqNi8PpNBFcPUQwQBrhBIjgKiKCobiEE0AVEcHVQQCXL+EEAGVGABffiZ1cz+MIAAASCScA\n",
449 "gES5naqbEefmNRRd5Ly4sdRTAICK4hqnGiZ2a4NABsiPcIIqJ5Brg0CGriGcAKqAQK4dIrm0hBMA\n",
450 "VBCRnA+PIwAAKDLhBACQKLdTdQ8/OTavodiOY0fdU+opAEBNco1TBRKpRAhogFIQTlChBDQfEtHQ\n",
451 "dYQTQIUT0UQI6K5SyLIsy2WgJ/MYBQCg+LJRnVvPXXUAAImEEwBAovyucWrMbaTq0ljqCQAAeXFx\n",
452 "eLE1lnoCVJTGUk8AgG0RTlBOGks9ASpKY6knALVHOAFUqsZST4CK0ljqCVSH/B5HMDqPUQAAii/7\n",
453 "fefWc1cdAEAi4QQAkCi/a5wW/Sm3obrcFw8t9QwAgArg4vCIyo4+ypcgB6g6wgmKRZBTLKIcSkY4\n",
454 "AVQaUU6xiPLtyu9xBAX/RwYAKkOWdS4S3VUHAJBIOAEAJBJOAACJhBMAQCJ31QEApVchd/QJJ4Ad\n",
455 "USG/7IF8CKda5Zc9AHRYfuFkRwwAVDkXhwMAJBJOAACJhBMAQCLhBACQyF11ALWosdQTgMqUXzg1\n",
456 "5jYSAEBZcqoOACCRcAIASCScAAASCScAgETCCQAgkXACAEiU2+MIjh11T15DAQAU2dhOreWIEwBA\n",
457 "IuEEAJBIOAEAJBJOAACJhBMAQKLc7qo7L27Mayiq0Iw4t9RTAIAdlls4wbYIawDKi8cRAAAUlXAC\n",
458 "AEgknAAAEgknAIBEwgkAIFFud9Wd2LQgr6GgJtw//KhSTwGADvI4AigR/9iA6uEfQrWjkGVZlstI\n",
459 "TYVchgEAKLrhncsf1zgBACQSTgAAiYQTAECi/C4Ovya3karHRaWeAACQJ3fVFZOYpBQEO0DRCCeo\n",
460 "NoKdUhDs1AjhBMCOE+yUQgmCPb/nOJ3uOU4AQIW43XOcAACKKrdweuqOD/4AAFSr3K9xEk/VaeT4\n",
461 "Us8AAErPxeEkEcTVRwwDdJxwgholhquPGIbiE04AVUIMVx8xXH5yexzBUwWPIwAAKsPITuaPI04A\n",
462 "QElU4hE14QRARajEnSzVRzgBVclOFigG4QRhJwtAGuHUCXayAFCbcgsnMQEAVDsf8gsAkMipOgCg\n",
463 "PF1U6glsTTgBUDpluGOEbRFOQPHYKQJVRjixY+wYAaghuX1WXTT5rDoAoEIM71z+uKsOACCRcAIA\n",
464 "SOQaJwCg4t0//KgOLX9iJ7cjnADotI7urKDSCSeoMHZUAKUjnBLZWQEAuYWTsAAAqp276gAAEgkn\n",
465 "AIBEwgkAIJGLwwGq3Iw4t9RTgLLjOU7UFDsCAEoht3CyIwMAqp1rnAAAEgknAIBEwgkAIJFwAgBI\n",
466 "5K46gBw8/OTYUk8B6IhRnVtNOLXBL0AAoC2FLMuyXAZ6Mo9RAACKL+vkESfXOAEAJBJOAACJhBMA\n",
467 "QCLhBACQyF11QNsaSz0BgCL6fedWyy+cGnMbCQCgLDlVBwCQSDgBACQSTgAAiYQTAEAi4QQAkKgy\n",
468 "H0ew6E+lngEAUNEO7dRa+X3Ib0HMAACVIcs6F05O1QEAJBJOAACJhBMAQCLhBACQSDgBACQSTgAA\n",
469 "iYQTAEAi4QQAkEg4AQAkEk4AAImEEwBAIuEEAJBo51JPAIAy8sXOffAp1ArhRPnyCxyAMpNfONnJ\n",
470 "AQBVzjVOAACJhBMAQCLhBACQSDgBACQSTgAAiTyOAACoHI2l3bxwAoAPNZZ6ApQ74QTQVRpLPQFg\n",
471 "RwknqCaNpZ4AQHUTTqRrLPUEAKC0yiucGks9AQCA9uUXTo25jQQAUJY8xwkAIJFwAgBIVF7XOAEA\n",
472 "VevYUfeUegr/Y2yn1hJOAFAk5RUK5EE4AZALkUAtEE4AnSQUoPYIJyCJSAAQTtAuoQDAloQTrYQC\n",
473 "AGxbzYaTSAAAOiq3cBIiAEC1q9kjTgBAvs6LG0s9hQ7wAEwAqDiVFRsIJwAqjtigVIQTQI0QG7Dj\n",
474 "hBPAdggO4EPCCSgKsQFUI+EEZURsAJQ34UTFExsAdBXhVKPEBgDV6sSmBdtfaHjnxhZOHSQ4AKgV\n",
475 "SQFSY7oknMQGALVKfFSXQpZlWR4D3R9H5zEMALRJgJCr4Z3LH6fqAGqI+IAdI5wAOkGAQG0STkDJ\n",
476 "iA+g0ggnqGHCBaBjhBOUESEDUN6EE2yHmAHgQ8KJiiNkACgV4UQuxAwAtUA4VSkhAwD/zzVtvHZ7\n",
477 "54YSTl1EyABQ89oKmApT0+EkZgCoOVUQL6VUVuEkZACoGQKmIuX2Ib/RVMhlGADoUgKmNt3uQ34B\n",
478 "qFTihQohnAD4/wQMbJNwAihHAgbKknAC2BYBA/wP4QSUP/EClAnhBKQTMECNE05QiQQMQEkIJ+gs\n",
479 "8QJQc4QTlU/AANBFhBP5ETAAVDnhVI0EDAAUhXAqFvECAFWn+sNJwAAAOem6cBIwAECFyy+chBEA\n",
480 "UOXqSj0BAIBKIZwAABJV/8XhAEDVeeqOHVt/5O2dW084AUCN2dHoqGXCCQASCQ6EEwBFJzioFsIJ\n",
481 "oIwJDigvwgmoSoIDKAbhBGxGcAC0TzhBTgQHQPUTTpQF0QFAJRBOFU5wAEDXqdlwEhwAQEcVsizL\n",
482 "8hjoqUIhj2EAAIpuZCfzx4f8AgAkEk4AAImEEwBAIuEEAJBIOAEAJBJOAACJhBMAQCLhBACQSDgB\n",
483 "ACQSTgAAiYQTAEAi4QQAkEg4AQAkEk4AAImEEwBAIuEEAJBIOAEAJBJOAACJhBMAQCLhBACQSDgB\n",
484 "ACQSTgAAiYQTAEAi4QQAkEg4AQAkEk4AAImEEwBAIuEEAJBIOAEAJBJOAACJhBMAQCLhBACQSDgB\n",
485 "ACQSTgAAiYQTAEAi4QQAkEg4AQAkEk4AAImEEwBAIuEEAJBIOAEAJBJOAACJhBMAQCLhBACQSDgB\n",
486 "ACQSTgAAiYQTAEAi4QQAkEg4AQAkEk4AAImEEwBAIuEEAJBIOAEAJBJOAACJhBMAQCLhBACQSDgB\n",
487 "ACQSTgAAiYQTAEAi4QQAkKiQZVlW6kkAAFQCR5wAABIJJwCARMIJACCRcAIASCScAAASCScAgETC\n",
488 "CQAgkXACAEgknAAAEgknAIBEwgkAIJFwAgBIJJwAABIJJwCARMIJACCRcAIASCScAAASCScAgETC\n",
489 "CQAgkXACAEgknAAAEgknAIBEwgkAINH/Acd8GCUQEYlkAAAAAElFTkSuQmCC\n"
490 ],
491 "text/plain": [
492 "<matplotlib.figure.Figure at 0x10b00b350>"
493 ]
494 },
495 "metadata": {},
496 "output_type": "display_data"
497 },
498 {
499 "name": "stdout",
500 "output_type": "stream",
501 "text": [
502 "Simulation completed!\n",
503 "Monitored for: 0:00:01.229672.\n"
504 ]
505 }
506 ],
507 "source": [
508 "monitor_simulation(ar, refresh=1)"
509 ]
510 }
511 ],
512 "metadata": {
513 "kernelspec": {
514 "display_name": "Python 3",
515 "language": "python",
516 "name": "python3"
517 },
518 "language_info": {
519 "codemirror_mode": {
520 "name": "ipython",
521 "version": 3
522 },
523 "file_extension": ".py",
524 "mimetype": "text/x-python",
525 "name": "python",
526 "nbconvert_exporter": "python",
527 "pygments_lexer": "ipython3",
528 "version": "3.4.2"
529 }
530 },
531 "nbformat": 4,
532 "nbformat_minor": 0
533 }
This diff has been collapsed as it changes many lines, (2557 lines changed) Show them Hide them
@@ -1,2557 +0,0 b''
1 {
2 "cells": [
3 {
4 "cell_type": "markdown",
5 "metadata": {},
6 "source": [
7 "# Parallel Monto-Carlo options pricing"
8 ]
9 },
10 {
11 "cell_type": "markdown",
12 "metadata": {},
13 "source": [
14 "This notebook shows how to use `IPython.parallel` to do Monte-Carlo options pricing in parallel. We will compute the price of a large number of options for different strike prices and volatilities."
15 ]
16 },
17 {
18 "cell_type": "markdown",
19 "metadata": {},
20 "source": [
21 "## Problem setup"
22 ]
23 },
24 {
25 "cell_type": "code",
26 "execution_count": 1,
27 "metadata": {
28 "collapsed": false
29 },
30 "outputs": [],
31 "source": [
32 "%matplotlib inline\n",
33 "import matplotlib.pyplot as plt"
34 ]
35 },
36 {
37 "cell_type": "code",
38 "execution_count": 2,
39 "metadata": {
40 "collapsed": true
41 },
42 "outputs": [],
43 "source": [
44 "import sys\n",
45 "import time\n",
46 "from IPython.parallel import Client\n",
47 "import numpy as np"
48 ]
49 },
50 {
51 "cell_type": "markdown",
52 "metadata": {},
53 "source": [
54 "Here are the basic parameters for our computation."
55 ]
56 },
57 {
58 "cell_type": "code",
59 "execution_count": 3,
60 "metadata": {
61 "collapsed": true
62 },
63 "outputs": [],
64 "source": [
65 "price = 100.0 # Initial price\n",
66 "rate = 0.05 # Interest rate\n",
67 "days = 260 # Days to expiration\n",
68 "paths = 10000 # Number of MC paths\n",
69 "n_strikes = 6 # Number of strike values\n",
70 "min_strike = 90.0 # Min strike price\n",
71 "max_strike = 110.0 # Max strike price\n",
72 "n_sigmas = 5 # Number of volatility values\n",
73 "min_sigma = 0.1 # Min volatility\n",
74 "max_sigma = 0.4 # Max volatility"
75 ]
76 },
77 {
78 "cell_type": "code",
79 "execution_count": 4,
80 "metadata": {
81 "collapsed": true
82 },
83 "outputs": [],
84 "source": [
85 "strike_vals = np.linspace(min_strike, max_strike, n_strikes)\n",
86 "sigma_vals = np.linspace(min_sigma, max_sigma, n_sigmas)"
87 ]
88 },
89 {
90 "cell_type": "code",
91 "execution_count": 5,
92 "metadata": {
93 "collapsed": false
94 },
95 "outputs": [
96 {
97 "name": "stdout",
98 "output_type": "stream",
99 "text": [
100 "Strike prices: [ 90. 94. 98. 102. 106. 110.]\n",
101 "Volatilities: [ 0.1 0.175 0.25 0.325 0.4 ]\n"
102 ]
103 }
104 ],
105 "source": [
106 "print \"Strike prices: \", strike_vals\n",
107 "print \"Volatilities: \", sigma_vals"
108 ]
109 },
110 {
111 "cell_type": "markdown",
112 "metadata": {},
113 "source": [
114 "## Monte-Carlo option pricing function"
115 ]
116 },
117 {
118 "cell_type": "markdown",
119 "metadata": {},
120 "source": [
121 "The following function computes the price of a single option. It returns the call and put prices for both European and Asian style options."
122 ]
123 },
124 {
125 "cell_type": "code",
126 "execution_count": 6,
127 "metadata": {
128 "collapsed": false
129 },
130 "outputs": [],
131 "source": [
132 "def price_option(S=100.0, K=100.0, sigma=0.25, r=0.05, days=260, paths=10000):\n",
133 " \"\"\"\n",
134 " Price European and Asian options using a Monte Carlo method.\n",
135 "\n",
136 " Parameters\n",
137 " ----------\n",
138 " S : float\n",
139 " The initial price of the stock.\n",
140 " K : float\n",
141 " The strike price of the option.\n",
142 " sigma : float\n",
143 " The volatility of the stock.\n",
144 " r : float\n",
145 " The risk free interest rate.\n",
146 " days : int\n",
147 " The number of days until the option expires.\n",
148 " paths : int\n",
149 " The number of Monte Carlo paths used to price the option.\n",
150 "\n",
151 " Returns\n",
152 " -------\n",
153 " A tuple of (E. call, E. put, A. call, A. put) option prices.\n",
154 " \"\"\"\n",
155 " import numpy as np\n",
156 " from math import exp,sqrt\n",
157 " \n",
158 " h = 1.0/days\n",
159 " const1 = exp((r-0.5*sigma**2)*h)\n",
160 " const2 = sigma*sqrt(h)\n",
161 " stock_price = S*np.ones(paths, dtype='float64')\n",
162 " stock_price_sum = np.zeros(paths, dtype='float64')\n",
163 " for j in range(days):\n",
164 " growth_factor = const1*np.exp(const2*np.random.standard_normal(paths))\n",
165 " stock_price = stock_price*growth_factor\n",
166 " stock_price_sum = stock_price_sum + stock_price\n",
167 " stock_price_avg = stock_price_sum/days\n",
168 " zeros = np.zeros(paths, dtype='float64')\n",
169 " r_factor = exp(-r*h*days)\n",
170 " euro_put = r_factor*np.mean(np.maximum(zeros, K-stock_price))\n",
171 " asian_put = r_factor*np.mean(np.maximum(zeros, K-stock_price_avg))\n",
172 " euro_call = r_factor*np.mean(np.maximum(zeros, stock_price-K))\n",
173 " asian_call = r_factor*np.mean(np.maximum(zeros, stock_price_avg-K))\n",
174 " return (euro_call, euro_put, asian_call, asian_put)"
175 ]
176 },
177 {
178 "cell_type": "markdown",
179 "metadata": {},
180 "source": [
181 "We can time a single call of this function using the `%timeit` magic:"
182 ]
183 },
184 {
185 "cell_type": "code",
186 "execution_count": 7,
187 "metadata": {
188 "collapsed": false
189 },
190 "outputs": [
191 {
192 "name": "stdout",
193 "output_type": "stream",
194 "text": [
195 "(12.478072469211625, 7.5692079226372924, 6.9498346596114704, 4.5592719279729934)\n",
196 "1 loops, best of 1: 111 ms per loop\n"
197 ]
198 }
199 ],
200 "source": [
201 "%timeit -n1 -r1 print price_option(S=100.0, K=100.0, sigma=0.25, r=0.05, days=260, paths=10000)"
202 ]
203 },
204 {
205 "cell_type": "markdown",
206 "metadata": {},
207 "source": [
208 "## Parallel computation across strike prices and volatilities"
209 ]
210 },
211 {
212 "cell_type": "markdown",
213 "metadata": {},
214 "source": [
215 "The Client is used to setup the calculation and works with all engines."
216 ]
217 },
218 {
219 "cell_type": "code",
220 "execution_count": 8,
221 "metadata": {
222 "collapsed": true
223 },
224 "outputs": [],
225 "source": [
226 "rc = Client()"
227 ]
228 },
229 {
230 "cell_type": "markdown",
231 "metadata": {},
232 "source": [
233 "A `LoadBalancedView` is an interface to the engines that provides dynamic load\n",
234 "balancing at the expense of not knowing which engine will execute the code."
235 ]
236 },
237 {
238 "cell_type": "code",
239 "execution_count": 9,
240 "metadata": {
241 "collapsed": true
242 },
243 "outputs": [],
244 "source": [
245 "view = rc.load_balanced_view()"
246 ]
247 },
248 {
249 "cell_type": "markdown",
250 "metadata": {},
251 "source": [
252 "Submit tasks for each (strike, sigma) pair. Again, we use the `%%timeit` magic to time the entire computation."
253 ]
254 },
255 {
256 "cell_type": "code",
257 "execution_count": 16,
258 "metadata": {
259 "collapsed": false
260 },
261 "outputs": [],
262 "source": [
263 "async_results = []"
264 ]
265 },
266 {
267 "cell_type": "code",
268 "execution_count": 17,
269 "metadata": {
270 "collapsed": true
271 },
272 "outputs": [
273 {
274 "name": "stdout",
275 "output_type": "stream",
276 "text": [
277 "1 loops, best of 1: 810 ms per loop\n"
278 ]
279 }
280 ],
281 "source": [
282 "%%timeit -n1 -r1\n",
283 "\n",
284 "for strike in strike_vals:\n",
285 " for sigma in sigma_vals:\n",
286 " # This line submits the tasks for parallel computation.\n",
287 " ar = view.apply_async(price_option, price, strike, sigma, rate, days, paths)\n",
288 " async_results.append(ar)\n",
289 "\n",
290 "rc.wait(async_results) # Wait until all tasks are done."
291 ]
292 },
293 {
294 "cell_type": "code",
295 "execution_count": 18,
296 "metadata": {
297 "collapsed": false
298 },
299 "outputs": [
300 {
301 "data": {
302 "text/plain": [
303 "30"
304 ]
305 },
306 "execution_count": 18,
307 "metadata": {},
308 "output_type": "execute_result"
309 }
310 ],
311 "source": [
312 "len(async_results)"
313 ]
314 },
315 {
316 "cell_type": "markdown",
317 "metadata": {},
318 "source": [
319 "## Process and visualize results"
320 ]
321 },
322 {
323 "cell_type": "markdown",
324 "metadata": {},
325 "source": [
326 "Retrieve the results using the `get` method:"
327 ]
328 },
329 {
330 "cell_type": "code",
331 "execution_count": 19,
332 "metadata": {
333 "collapsed": true
334 },
335 "outputs": [],
336 "source": [
337 "results = [ar.get() for ar in async_results]"
338 ]
339 },
340 {
341 "cell_type": "markdown",
342 "metadata": {},
343 "source": [
344 "Assemble the result into a structured NumPy array."
345 ]
346 },
347 {
348 "cell_type": "code",
349 "execution_count": 20,
350 "metadata": {
351 "collapsed": true
352 },
353 "outputs": [],
354 "source": [
355 "prices = np.empty(n_strikes*n_sigmas,\n",
356 " dtype=[('ecall',float),('eput',float),('acall',float),('aput',float)]\n",
357 ")\n",
358 "\n",
359 "for i, price in enumerate(results):\n",
360 " prices[i] = tuple(price)\n",
361 "\n",
362 "prices.shape = (n_strikes, n_sigmas)"
363 ]
364 },
365 {
366 "cell_type": "markdown",
367 "metadata": {},
368 "source": [
369 "Plot the value of the European call in (volatility, strike) space."
370 ]
371 },
372 {
373 "cell_type": "code",
374 "execution_count": 21,
375 "metadata": {
376 "collapsed": false
377 },
378 "outputs": [
379 {
380 "data": {
381 "text/plain": [
382 "<matplotlib.text.Text at 0x1100a3290>"
383 ]
384 },
385 "execution_count": 21,
386 "metadata": {},
387 "output_type": "execute_result"
388 },
389 {
390 "data": {
391 "image/png": [
392 "iVBORw0KGgoAAAANSUhEUgAABGcAAAMvCAYAAAB/e73nAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\n",
393 "AAAWJQAAFiUBSVIk8AAAIABJREFUeJzs3Xm4LGdd7+3vzkAgzCAgMhgIiIKCYQyRIYYAMijTeQwc\n",
394 "DRAEQXyRQURBD5GjoExCZJR5UDE8ckBAICSQGGRUmafDwQwMBhPAQCIh097vH9WL3VlZQ6/u6q6q\n",
395 "7vu+rn3V6tXVVbVWcmH2x189lQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n",
396 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMCi7R79ef0G7x009v6xC7wmAGAC+3R9AQCwIIdn719O\n",
397 "d/LHX2SZxO2S/GmSDyf5epIfJvlBkm8kef/ovTst6Fr2zPg+ALBg+3V9AQDQke3+grprtI+/yLKV\n",
398 "n0lyXJIj131/T5p/h35i9OfIJM9I8pUkf5nk5Qu8RgCg58QZAFbRa5L83YT7nj7PC2HQfjnJ3ya5\n",
399 "8uj1fyR5W5JPJPlOkqsmuWGSX0xyRJIDk/xUkmdHnAEAAGAFHZ69tyo9rdtLYQncPskFaf59ujRN\n",
400 "cDlgi/2vmeRZSc5L8t05XdPav9+v2+C9g8bef+aczg8ATMmaMwAAO7N/kuOzN8Y8OckfJrlwi8/8\n",
401 "V5r1iw5J8rG5Xh0AMDjiDADszCOzdwLh7tvse8Zov5M3eX/903VukuQvknwxzWKylyQ5cYPPXTHJ\n",
402 "E5J8MMl/JrlotD0xyW+liQebOSiXXex4V5IHJXlrmoVsL0xydpJ/SHM7ziQOSvK8JJ9KEyEuTHOL\n",
403 "zzuS/I9tPntUklckOTXN4rkXpPm5v5fks0leneSu2xzjlNHPs3YL2o+n+dk+keTcNIvzfnl0jdea\n",
404 "8GfayiPS/LNKknenWUNmUl9Nct913zsgyZOSvDnJvyQ5J83v8KIk307y0SR/luSm018yAAAAdO/w\n",
405 "tHNb0yOz91aWu22z7xmjfT+4yftr13N8mnBw8dj3Lh1tP7nuMz+XJkLs3mDftT//N8nNNznnQWP7\n",
406 "vS3JZ9Z9dv2fp2/zMz41TUjY6npOSLP+yka+usE5139+d5JXpglJGzlltM/Xk/xxmhiz2c/z1STX\n",
407 "2+Zn2s6Hx67zDjMeK2nWpdnoWtf/Hn6Y5JgtjuO2JgAYKAsCA0D3ymj7/TQLzJ6aZl2Saya52th+\n",
408 "P5nkn5JcI83TgN6ZZjrlP5NcP82Uyn3ShJkPJfn5JN/a4rwPGm2/nOQtaSZVkiZkPS7NRMezk3w6\n",
409 "yXs3+PwfZ+9f9L+S5A1JvpAmMt0kyf9M8gtJ7jl67yEbHOPS0WdOHV3Ht9JMzVw9yW3S/G5unuQ3\n",
410 "k3w+yUu3+HluMLqeS9L8Xt6V5KzR9x+T5I5ppk9enORhWxxnK1cZHSdpJn3+ZcrjrHdRkn9N88/t\n",
411 "jDS/h4uSXDvJoWmu95pJXpUm2H2mpfMCAADAwhyevZMDr05yjzSPN97uz03WHeeRaX9yZneSv07y\n",
412 "Y9sc74Sxcz98k30eP3bMv9/g/YPG3v/2Fse5W/ZO8nx2g/d/IZf9fW72//B5ydh+h23w/oGbfG7N\n",
413 "FZN8bvT5L22yzylj5zgxyS022OcKY8e5KE3gmsahY+ea9Ilf29knWy8mnCQHZ+8CxK/YZB+TMwAA\n",
414 "APTa4dn69p3N/hy77jiPTPtx5m0TXP/Pju3/lm32fdfYNR687r2DMvlf0v9mbN9br3vvvaPvfzpb\n",
415 "r2F3YJLzR/set835NvNn2fvzbBRzThm9/7VtjvP7Y8c5YspruX/2/k5eNOUxpvXR0Xk/scn74gwA\n",
416 "DJTbmgBYVXta3m8W35tgn/uMfb3RX77HvTbJ/dKs0XLvJC+f8rrekb23/xyavRM0V00zVZQkb0rz\n",
417 "F/7N/CDNAse3T/Okos38TJJfTnMr082TXDfNbTwHpJl6WXOt0TE3cskWx0+SM0fbXUmus82+m7n6\n",
418 "2NfnTXmMzeyTZvHjI9OsLXRQmuu8Wprfw9p/t7WxqDEA0CPiDACr6A/SLMA7JLcZbfekWZtkK+Pv\n",
419 "32bTvbb35bGvxydwbptk39HXLxj9mcR1N/jeLZO8LJs/+Wp9HJvlSZP/Nfb1drcRbeb7Y19vtsjx\n",
420 "NO6fZrJo/W10a8Z/D562CQBLRpwBgGG49mi7O5eNDBs5Z4PPTWP8POMTI+sjy3bTRWtPWbrCuu/f\n",
421 "Os1CwGuLHv9w9PozaW5R+naa6ZSHJvn1yS55Sxe2cIzx3+1PtHC8pLlVbnwa6ttpbtX6UppFh7+T\n",
422 "Zlroz3P528sAgCUgzgAAm9l/7OvxsLHv2NfPS/L+CY93wbrXL8neMPM3SZ6UJkSsd8cNvteVL6cJ\n",
423 "ZPukWRR5VldL8pejry9J8ow0EzQXb7DvH7RwPgCgh8QZAJjeIm8v+fbYOX9s7PVGrjf29UaxY1LX\n",
424 "H/v6P8e+Hp8e+U42X/B4K9dNs75K0tyGdfQUx+jCuUk+leR2aSZn7pxmod5p/VKax3MnTaya9BYx\n",
425 "AGCJuGcZAHZmbYJkV9pdc2Q7nxk773aTJHcY+/rTM5zz0LGvx58QNP5o7SMznZ8c+3rSyZu+GH9a\n",
426 "1h/PeKzx38N7ZzwWADBQ4gwA7MzZY1/fYov9dqXdCdXxv7g/ept9HzPa7k5ywpTn22/sPN9J8qGx\n",
427 "987J3lhzZJLDJjzm+G1S409W2i5yXXHC4y/KK7L334N7pnlE96RumOYpWGt28nuYdhFjAKDnxBkA\n",
428 "2Jl/y96/UD8ilw0Oa26R5MS0t2BsknwheydMHpi9AWa9Jya51+jrdyQ5bYtjbnTtSROWjkvy06PX\n",
429 "f5nLL6b7p2P7vi1bT/NcJ8mzxz6TNI/XXnsk9gOy99aecVdJ8uIkv7fFsbtwQZLfyN5HiP9Zkhcm\n",
430 "ufIWn7lykqemmYAafzLV+JO1Nru16yZpItudprlYAKD/rDkDwCq6eZJ7ZO9ThLby70lOH3t9bpK/\n",
431 "T/MEoVulWW/lNWkmKX4iyf2S/Erm8/8AeWySTya5ZpK/Gp3nbWnWg7l+kpLk3qN9/zPJb29zvGek\n",
432 "iSrvS/LNJOeneWT2I5McMtrnX9LEh/XenWaNlCekWePmI0nelSYifD3NlMeN0/yejxy9fvHY5y8c\n",
433 "/QxPHu33b2kmUk5LMylzuzRPaBpf96ZP/jHN7/claf576slp4srfJ/lYmjWBrpzm+u+aZsJmbfHj\n",
434 "c8eO86E0P/vt0kS3k9LcNvWtNE/aOjzJUUmuNM8fBgAAABbh8DSTDjv9c+wGx7pukq9s8Zn/TvJH\n",
435 "aSLF7my+YO7a/q/b5P2N/FyaWLTVNf/fNAFqIwdt89nxPx9Ico1trudpaSZJtjvWRWkCxrgD0kwY\n",
436 "bfaZS9PEnjeNfe/GG1zDKaP3tpoSSi7778DDt9l3UndJswbPJL/PS9NMzjxq3TFukuSMLT53YZoI\n",
437 "9K/Z+ufc6t+ng8bef+YOf0YAYM5MzgCwKvas2+70c+POTjNx8rQkD0qzqOtFaaZs3p3kZaN9fmOC\n",
438 "8+30ej6X5najxyR5cJrpnWukmcb4bJpJmtfksmuZbOa5af6iX5LcOs1EzrlpJjlen6ROcIznJXlj\n",
439 "mp/1iCQ/k+RaaSLAOaNres/oWOufHHVhmqcV/XaSY9LcDrYryTfSBK03Jflw9gayzX5Xe7Z4b/1+\n",
440 "Wx1nGv+c5nd37zRTU4emWVfmmmn+nfh2mlj2z2nWDfrUBsc4Pc3kzNPT/DO9QZrfzb+nmdB5XZp/\n",
441 "TidPcO1t//sGAAAAtOygmKAAAOgVCwIDAAAAdGipbmsqpdwizdMs3lJr3eyJB+P7PzrJq5I8ptb6\n",
442 "2i322z/NgocPT3MP/yVpnjLxylrrG9u4dgAAAGAxSim/luQ+SW6fZk27fdKsF/i+JM+ptZ61zeev\n",
443 "luTUNLc3b9kUJjH4OFNKOTjJU9I8DeFeaX6hm95PXUq5d5r1AW6W5t74bLX/yPFpnqBwWpr76vdP\n",
444 "cv8kry+l3KrW+rRZfgYAAABgMUop+yV5c5KLk3w0zUMQ9kvzhMXfbnYpd661nr7J5w9I8o40YSZp\n",
445 "YU23wceZJDdK8luZ/JdxaJLfnHT/UspD0oSZU5Pcq9Z60ej710jy8SS/W0r561rrZ3d64QAAAMDC\n",
446 "7U7ynCQvqrX+6IEFpZRdSV6d5smKz8oGT3cspeyTJuwcluYBBkes32cag19zptZ6Sq11n1rrvpng\n",
447 "l1JrfdbY/s+a4BSPGG2ftRZmRsc5N81TLnaN7QMAQ+GpPQDASqq17q61/tF4mBl9f0+Sl45e3m6T\n",
448 "j784yUOSHJ3kQ21d0+DjzDq75rD/ndP8B+zHNnjvI6PtYTs8LwB05Yw0//d/3yT/u9tLAQDonQNH\n",
449 "2++sf6OU8vQk/1+SJ9daa3beIDa1bHGmVaWUqya5dpL/rrVesMEu3xxtb7q4qwIAAADm5KjR9tTx\n",
450 "b5ZSHpnk2UmeV2v9y7ZPKs5s7aqj7fc3ef8Ho+3VFnAtAAAAwJyUUu6U5HFJvpvkuLHv3zfNk57f\n",
451 "XGv9g3mcexkWBF6ESzb5/tQjTCeddJJ7/QEAAJbYkUce2dptL33Sx7/Pzvq7LqXcMsm70yxr8tBa\n",
452 "6zmj7x+S5K1pnuj0qFmvczPizNbOG22vtMn7B67bDwAAABiQUsptk7wvzd0zR9VaTxp7++5pmsAZ\n",
453 "SZ5bShn/6Nr6s786ijv/VGt91zTXIM5sodZ6Xinlu0muVUq5cq31v9ftcoPR9rRpz3GbQ+8y9fUN\n",
454 "xTfOWf9rA/rs9G/pzbBIZ515bteXAEzhgi+f0/Ul0GM/f5elHJi5nOPv97ddX0KO+sf/OdPnR7cs\n",
455 "HZ/k4iT3qbWevG6XPWnumnnsFoe5V5J7plk6RpyZk48kuX+aWvaede+tlZWNnuREhBkYGmEGFkuY\n",
456 "gWESZmA5lFKekORFSb6e5H611i+u36fWelzG1p9Z9/ljkxyb5NG11tfNci0WBN7em0fbp5ZS9l/7\n",
457 "ZinlGkl+L01Fe1MXFwbQJmEGFkuYgWESZmD4SikHlFJemya6fCjJ7TcKMxNobURq8JMzpZQbJnno\n",
458 "6OXBo+0tSylPHX39uVrrCWP7H5a994Wtbe9dSrnW6Ov3jP9DqbXWUsrRaaZnPl9K+WCS/ZPcN8mP\n",
459 "Jzmu1vrJtn+uZWBqBoZDmIHFEmZgmIQZWBpHJTkmyflJPpPk6evWkllzQq31xEVc0ODjTJKbJXne\n",
460 "2Os9SQ5JctvR6zckOWHs/XumGTta23dPkjL6syfJ2UnWF7OHJHlikqOTPDzJpUm+kOQZtdY3tPNj\n",
461 "LBdhBoZDmIHFEmZgeEQZWDprEy9XTvI7m+yzJ8n3k2wVZ9aaQmsXxIKtPXpsGRcEFmZgOIQZWDxx\n",
462 "BoZFmGEaawsCL/ujtPu0IPDQf9fWnKFVwgwMhzADiyfMwLAIM8CiiDMAAAsgzMCwCDPAIokztMbU\n",
463 "DAyHqRlYLGEGhkWYARZNnKEVwgwMhzADiyXMwLAIM0AXxBlmJszAcAgzsFjCDAyLMAN0ZRkepU2H\n",
464 "hBkYDmEGFkuYgeEQZYCumZwBWAHCDABsTJgB+kCcYWqmZmAYhBlYPFMzMAzCDNAX4gxTEWZgGIQZ\n",
465 "WDxhBoZBmAH6RJxhx4QZANiYMAPDIMwAfSPOsCPCDAyHqRlYLGEGhkGYAfpInAFYQsIMLJYwA8Mg\n",
466 "zAB95VHaTMzUDAyDMAMAlyXKAH1ncoaJCDMwDMIMLJ6pGeg3YQYYAnGGbQkzMAzCDCyeMAP9JswA\n",
467 "QyHOsCVhBoZBmIHFE2ag34QZYEjEGQCAHRJmoN+EGWBoxBk2ZWoGhsHUDCyWMAP9JswAQyTOsCFh\n",
468 "BoZBmIHFEmag34QZYKjEGS5HmIFhEGYAYC9hBhiy/bq+APpFmIFhEGZg8UzNQD+JMsAyMDkDMDDC\n",
469 "DCyeMAP9JMwAy0Kc4UdMzUD/CTOweMIM9JMwAywTcYYkwgwMgTADiyfMQD8JM8CyEWcQZgBgA8IM\n",
470 "9JMwAywjcWbFCTMwDKZmAECYAZaXOLPChBkYBmEGFs/UDPSPMAMsM4/SBugxYQYWT5iBfhFlgFVg\n",
471 "cmZFmZqB/hNmYPGEGegXYQZYFeLMChJmoP+EGVg8YQb6RZgBVok4s2KEGeg/YQYWT5iBfhFmgFUj\n",
472 "zqwQYQb6T5gBYNUJM8AqEmcAgJVmagb6Q5gBVpU4syJMzUD/mZqBxRNmoD+EGWCVeZT2ChBmoP+E\n",
473 "GVg8YQb6QZQBMDmz9IQZ6D9hBhZPmIF+EGYAGuLMEhNmoP+EGVg8YQb6QZgB2EucAeiIMAOLJ8xA\n",
474 "PwgzAJclziwpUzPQb8IMAKtKmAG4PHFmCQkzAHB5pmage8IMwMbEmSUjzED/mZqBxRNmoHvCDMDm\n",
475 "PEp7iQgz0H/CDCyeMAPdEmUAtmdyBmBBhBlYPGEGuiXMAExGnFkSpmag34QZWDxhBrolzABMTpxZ\n",
476 "AsIM9JswA8CqEWYAdkacGThhBvpNmIFumJqB7ggzADsnzgyYMAP9JsxAN4QZ6I4wAzAdcQYAWBrC\n",
477 "DHRHmAGYnkdpD5SpGeg3UzOweMIMdEOUAZidyZkBEmag34QZWDxhBrohzAC0Q5wZGGEG+k2YgcUT\n",
478 "ZqAbwgxAe8SZARFmoN+EGQBWhTAD0C5xBqAFwgx0w9QMLJ4wA9A+cWYgTM1Afwkz0A1hBhZPmAGY\n",
479 "D3FmAIQZALgsYQYWT5gBmB9xpueEGeg3UzOweMIMLJ4wAzBf+3V9AWxOmIF+E2Zg8YQZWCxRBmAx\n",
480 "TM4ATEGYAWDZCTMAiyPO9JSpGegvYQa6YWoGFkeYAVgscaaHhBnoL2EGuiHMwOIIMwCLJ870jDAD\n",
481 "/SXMQDeEGVgcYQagG+JMjwgz0F/CDHRDmIHFEWYAuiPOAAC9JMzA4ggzAN3yKO2eMDUD/WVqBoBl\n",
482 "JcoA9IPJmR4QZqC/hBnohqkZmD9hBqA/xJmOCTPQX8IMdEOYgfkTZgD6RZwB2IAwA90QZmD+hBmY\n",
483 "r91f+HbXl8AAiTMA6wgz0A1hBuZPmIH5EmaYljgDMEaYgW4IMzB/wgzMlzDDLMQZAKBTwgzMnzAD\n",
484 "8yXMMCuP0gYYMTUDwLIRZWC+RBnaIs4ARJiBrpiaAWCohJlhK6X8WpL7JLl9khunubPo60nel+Q5\n",
485 "tdaztvjso5O8Ksljaq2vbeN6xBlg5Qkz0A1hBubL1AzMjzAzbKWU/ZK8OcnFST6a5ANp+shdk/x2\n",
486 "s0u5c6319LHP3DvJg5LcLMkRo2/vaeuaxBlgpQkz0A1hBuZLmIH5EWaWwu4kz0nyolrrd9a+WUrZ\n",
487 "leTVSR6V5FlJHj72mUOT/GZaDDLjxBlgZQkz0A1hBuZLmIH5EWaWQ611d5I/2uD7e0opL00TZ263\n",
488 "7r1npQk2KaUcm+TYNq9JnAFWkjAD3RBmYL6EGZgPUWalHDjafmeLfXa1fVKP0gZWjjADwDISZmA+\n",
489 "hJmVc9Roe+oiTyrOAAALYWoG5keYgfkQZlZLKeVOSR6X5LtJjlvkucUZYKWYmoFuCDMwP8IMzIcw\n",
490 "s1pKKbdM8u40C/4+tNa60P9xteYMsDKEGeiGMAPzI8xA+0SZ1VNKuW2S9yW5apKjaq0nLfoaxBlg\n",
491 "JQgz0A1hBuZHmIH2CTM785MH3bLrS5hZKeW+SY5PcnGS+9RaT+7iOtzWBCw9YQa6IczA/Agz0D5h\n",
492 "ZvWUUp6Q5J1Jvp3kLl2FmcTkDLDkhBkAlo0wA+0TZlZLKeWAJC9PckySf0ryP2qtWz06e+7EGWBp\n",
493 "CTPQHVMzMB/CDLRPmFlJR6UJM+cn+UySp5dSNtrvhFrriUlSSjksyWGj769t711Kudbo6/fUWr84\n",
494 "7QWJM8BSEmagO8IMzIcwA+0SZVbartH2ykl+Z5N99iT5fpITR6/vmeTYsff2JCmjP3uSnJ1EnAFY\n",
495 "I8xAd4QZmA9hBtolzKy2Wusbk7xxh595VpJnzeeKLAgMALREmIH5EGagXcIMfSTOAEvF1AwAy0SY\n",
496 "gXYJM/SVOAMsDWEGumNqBtonzEC7hBn6zJozwFIQZqA7wgy0T5iB9ogyDIHJGWDwhBnojjAD7RNm\n",
497 "oD3CDEMhzgCDJsxAd4QZaJ8wA+0RZhgScQYYLGEGgGUizEB7hBmGxpozwCAJM9AtUzPQLmEG2iHK\n",
498 "MFQmZ4DBEWagW8IMtEuYgXYIMwyZOAMMijAD3RJmoF3CDLRDmGHoxBkAYCLCDLRLmIF2CDMsA3EG\n",
499 "GAxTMwAsC2EG2iHMsCwsCAwMgjAD3TI1A+0RZmB2ogzLxuQM0HvCDHRLmIH2CDMwO2GGZSTOAL0m\n",
500 "zEC3hBlojzADsxNmWFbiDNBbwgx0S5gBoE+EGZaZOAP0kjADwDIxNQOzEWZYdhYEBnpHmIHumZqB\n",
501 "9ggzMD1RhlVhcgboFWEGuifMQHuEGZieMMMqEWeA3hBmoHvCDLRHmIHpCTOsGnEGAEgizECbhBmY\n",
502 "njDDKhJngF4wNQPdEmagPcIMTE+YYVVZEBjonDADwLIQZmA6ogyrzuQM0ClhBrpnagbaIczAdIQZ\n",
503 "EGeADgkz0D1hBtohzMB0hBloiDNAJ4QZ6J4wA+0QZmA6wgzsZc0ZYOGEGeieMAPtEGZg50QZuDyT\n",
504 "M8BCCTMALAthBnZOmIGNiTPAwggz0A+mZmB2wgzsnDADmxNngIUQZqAfhBmYnTADOyfMwNbEGQBY\n",
505 "EcIMzE6YgZ0TZmB7FgQG5s7UDHRPmIHZCTOwM6IMTM7kDDBXwgwAy0CYgZ0RZmBnxBlgboQZ6AdT\n",
506 "MzAbYQZ2RpiBnRNngLkQZqAfhBmYjTADOyPMwHTEGaB1wgz0gzADsxFmYGeEGZieBYGBVgkz0A/C\n",
507 "DMxGmIHJiTIwO5MzQGuEGQCWgTADkxNmoB3iDNAKYQb6w9QMTE+YgckJM9AecQaYmTAD/SHMwPSE\n",
508 "GZicMAPtsuYMACwJYQamJ8zAZEQZmA+TM8BMTM1APwgzMD1hBiYjzMD8iDPA1IQZAIZOmIHJCDMw\n",
509 "X+IMMBVhBvrD1AxMR5iByQgzMH/iDLBjwgz0hzAD0xFmYDLCDCyGOAPsiDAD/SHMADBPwgwsjjgD\n",
510 "TEyYgf4QZmB6pmZge8IMLJY4A0xEmIH+EGZgesIMbE+YgcUTZ4BtCTPQH8IMTE+Yge0JM9ANcQbY\n",
511 "kjAD/SHMwPSEGQD6bL+uLwDoJ1EG+kWYgekJMzAZUzPQHZMzwOUIM9AvwgxMT5iByQgz0C2TM8Bl\n",
512 "CDPQH6IMzEaYgckIM9A9kzPAjwgz0B/CDMxGmIHJCDPQD+IMkESYgT4RZmA2wgwAQyPOAMIM9Igw\n",
513 "A7MRZmBypmagP8QZWHHCDPSHMAOzEWZgcsIM9Is4AytMmIH+EGZgNsIMTE6Ygf4RZ2BFCTPQH8IM\n",
514 "AIsizEA/iTOwgoQZ6A9hBmZnagYmI8xAf4kzsGKEGegPYQZmJ8wAsAzEGVghwgz0hzADsxNmYHKm\n",
515 "ZqDf9uv6AoD5E2WgX4QZmJ0wA5MTZqD/TM7AkhNmoF+EGZidMAOTE2ZgGMQZWGLCDPSLMAOzE2Zg\n",
516 "csIMDIc4A0tKmIF+EWZgdsIMAMtKnIElJMxAvwgzMDthBnbG1AwMizgDS0aYgX4RZmB2wgzsjDAD\n",
517 "wyPOwBIRZqBfhBmYnTADOyPMwDB5lDYsCWEG+kOUgXYIM7AzwgwMl8kZWALCDPSHMAPtEGZgZ4QZ\n",
518 "GLalm5wppdwiyReSvKXWevQW+z0gyZOS/HySA5KcmeT4JM+ttV6wwf67tzn1x2utd576wmFKwgz0\n",
519 "hzADADAskzSEUsr+SX4rydFJfibJ7jQN4f1JXlBrPWvW61iKOFNKOTjJU5JcP8m90kwE7dli/ycm\n",
520 "eVGSc5O8M8n3k9w9yTOT3KOUckSt9eINPnpekr/a5LBnTv0DwJSEGegPYQbaY2oGdsbUDOzMThpC\n",
521 "KeUKSU5I0wz+PcnfJbkoyR2TPDnJMaWUw2utn53lmpYiziS5UZqKtWmQWVNKuUGSP09yTpLb11q/\n",
522 "Pvr+riRvSfKrSR6b5KUbfPx7tdantXXRMAthBvpDmIH2CDOwM8IMTGXihpDk0WnCzFtrrQ8df6OU\n",
523 "8tQkz0vTGO47ywUtxZoztdZTaq371Fr3TXLENrsfleY2pleuhZnRMfYkecbo5THzuVKY3enfOk+Y\n",
524 "gR4RZqA9wgzsjDAD09lhQ7j1aPuWDd575Wh701mvaSnizDq7tnl/bV2Yj65/o9Z6WpKzk9ymlHLF\n",
525 "ti8MZiXKQL8IM9AeYQZ2RpiB1mzXEL4w2h5TSll/99HBo+3l+sJOLcttTTuxVrTO3uT9bya5TpKb\n",
526 "JPnSuvduUEq5MM3v7fwk/y/J25McV2s9fw7XCj8izEC/CDPQHmEGgB57dZKHJPmVJJ8rpRyXpKYZ\n",
527 "dnl1kq8l+aNZT7KMkzPbuWqa+8q+v8n7P0hTzq627vufSvM0p1elGV36UJJbJfmTJB8vpVx9LlcL\n",
528 "EWagb4QZaI8wAztnagYWp9b6wyRHJjklyS2SvDzJt5J8NU1fuGOt9ZuznmcVJ2fWXLLJ9zccaaq1\n",
529 "3m7990op10mzavPPJ3l6kj9o7epgRJiBfhFmoD3CDOycMAOLVUq5SpJ/SHLLNE9o+mGSByb5tTSx\n",
530 "5p9KKaXW+vlZzrOKkzPnpQkwV9rk/QPH9ttSrfWcJE8avdxuESHYMWEG+kWYgfYIM7Bzwgx04nlJ\n",
531 "fjHJ42qt/1pr/Xyt9U/TxJrfTHLzJCeUUq46y0lWcXLm9CSHJPnJXH5NmSS5QZLdo/0m8d3R9iqz\n",
532 "XxrsJcxAvwgz0B5hBnZOmKGPrnDrG3R9CYtQ0iyN8v7xb46e+PyaUspDktw7yV2TvGfak6zi5MxH\n",
533 "RtvLTbqUUm6eZjHgz9daL5jweIeMthuFHpiKMAP9IsxAe4QZ2DlhBjp1wGh7403e33fddiqrGGfe\n",
534 "muSiJI8opfwo85VS9kmzuG+SvHH8A6WUx5ZS7rb+QKWUGyZ5dpqK9pq5XTErRZiBfhFmoD3CDAAD\n",
535 "9N40S6O8ZP2tS6WUI5McnuS/0iwYPLWluK1pFEkeOnq59pzxW5ZSnjr6+nO11hOSpNb6jVLKHyZ5\n",
536 "fpLPlFLeneax2HdN8nNJPp7kZetOcWiSV5RSzkjz/PLvpKlmR6ZZu+Yva63vncfPxmoRZqBfhBlo\n",
537 "jzAD0zEeRniwAAAgAElEQVQ1A+3bSUNI8pQkt0tyjyT/Xkr5QJoY89NpwswPkvxarXWmv8wtRZxJ\n",
538 "crM0i/Ss2ZPmdqPbjl6/Ic1TlZIktdYXllJOS/LENKssH5BmjZk/SfLcWutF647/siQXJLlDmoWA\n",
539 "rp3mUdwfSvLyWuu7Wv55WEHCDPSLMANA14QZmJuJG0Kt9ZullEOS/G6SByT55TQt5RtJ/irJ82ut\n",
540 "p816QRs+Npr5O+mkk/YkybUPPmS7XVkBwgz0izAD7TI1AzsnzAzbbR97nSTJkUceuZR/5177++wn\n",
541 "/6r7/31flt/1skzOwCCJMtA/wgy0S5iBnRNmYPWIM9ARYQb6RZSB9gkzADCZVXxaE3ROmIF+EWag\n",
542 "fcIMTMfUDKwmcQYWTJiBfhFmoH3CDExHmIHVJc7AAgkz0C/CDLRPmIHpCDOw2sQZWBBhBvpFmIH2\n",
543 "CTMwHWEGEGdgAYQZ6BdhBtonzMB0hBkgEWdg7oQZ6BdhBtonzADAbMQZmCNhBvpFmIH2CTMwPVMz\n",
544 "wBpxBuZEmIF+EWagfcIMTE+YAcaJMzAHwgz0izAD7RNmYHrCDLCeOAMtE2agX4QZaJ8wA9MTZoCN\n",
545 "iDPQImEG+kWYAQBgCPbr+gJgGYgy0D/CDMyHqRmYnqkZYDMmZ2BGwgz0jzAD8yHMwPSEGWAr4gzM\n",
546 "QJiB/hFmYD6EGZieMANsR5yBKQkz0D/CDMyHMAPTE2aASYgzMAVhBvpHmIH5EGZgesIMMCkLAsMO\n",
547 "CTPQL6IMzI8wAwCLYXIGdkCYgX4RZmB+hBmYjakZYCfEGZiQMAP9IszA/AgzMBthBtgpcQYmIMxA\n",
548 "vwgzMD/CDMxGmAGmIc7ANoQZ6BdhBuZHmIHZCDPAtMQZ2IIwA/0izMD8CDMA0B1xBjYhzEC/CDMw\n",
549 "P8IMzM7UDDALj9KGdUQZ6B9hBoA+E2aAWZmcgTHCDPSPMAPzZWoGZiPMAG0QZ2BEmIH+EWZgvoQZ\n",
550 "mI0wA7RFnIEIM9BHwgzMlzADAP0hzrDyhBnoH2EG5kuYgdmZmgHaJM6w0oQZ6B9hBuZLmIHZCTNA\n",
551 "28QZVpYwA/0jzMB8CTMwO2EGmAdxhpUkzED/CDMwX8IMzE6YAeZFnGHlCDPQP8IMzJcwA7MTZoB5\n",
552 "EmdYKcIM9I8wA/MlzABA/4kzrAxhBvpHmIH5EmagHaZmgHnbr+sLgEUQZqBfRBmYP2EG2iHMAIsg\n",
553 "zrDURBnoH2EG5k+YgXYIM8CiuK2JpSXMQP8IMwAMhTADLJI4w1ISZqB/hBlYDFMzADA84gxLR5iB\n",
554 "/hFmYDGEGWiHqRlg0cQZloowA/0jzMBiCDPQDmEG6II4w9IQZqB/hBlYDGEG2iHMAF0RZ1gKwgz0\n",
555 "jzADiyHMQDuEGaBL4gyDJ8xA/wgzsBjCDLRDmAG6Js4waMIM9I8wA4shzADA8hBnGCxhBvpHmIHF\n",
556 "EGagPaZmgD4QZxgkYQb6R5iBxRBmoD3CDNAX4gyDI8xA/wgzsBjCDLRHmAH6ZL+uLwAmJcpAPwkz\n",
557 "sBjCDLRHmAH6xuQMgyDMQD8JM7AYwgwALDdxht4TZqCfhBkAhsjUDNBH4gy9JsxAPwkzsDimZqA9\n",
558 "wgzQV+IMvSXMQD8JM7A4wgy0R5gB+syCwPSSMAP9I8rAYgkz0B5hBug7kzP0jjAD/SPMwGIJM9Ae\n",
559 "YQYYAnGGXhFmoH+EGVgsYQYAVo84Q28IM9A/wgwsljAD7TI1AwyFOEMvCDPQP8IMLJYwA+0SZoAh\n",
560 "EWfonDAD/SPMwGIJM9AuYQYYGnGGTgkz0D/CDCyWMAPtEmaAIfIobTohykA/CTOwWMIMAJCYnKED\n",
561 "wgz0kzADiyXMQPtMzQBDJc6wUMIM9JMwA4slzED7hBlgyMQZFkaYgX4SZgAYOmEGGDpxhoUQZqCf\n",
562 "hBlYPFMz0C5hBlgG4gxzJ8xAPwkzsHjCDLRLmAGWhTjDXAkz0E/CDCyeMAMAbEacYW6EGegnYQYW\n",
563 "T5iB9pmaAZaJOMNcCDPQT8IMLJ4wA+0TZoBlI87QOmEG+kmYgcUTZqB9wgywjPbr+gJYLsIM9I8o\n",
564 "A90QZqB9wgywrMQZWiPMQP8IM9ANYQYAhqGUcoskX0jyllrr0Zvsc0qSu21zqCvWWi+a9jrEGWYm\n",
565 "ykA/CTPQDWEG5sPUDNCWUsrBSZ6S5PpJ7pVmyZc9E3z0NUk2+4/sS2e5JnGGmQgz0E/CDHRDmIH5\n",
566 "EGaAlt0oyW9lsiAz7s9rrafN4XrEGaYnzEA/CTPQDWEG5kOYAdpWaz0lowcklVLunuTkTi8ontbE\n",
567 "lIQZ6CdhBrohzMB8CDPAAuya0747YnKGHRNmoJ+EGeiGMAPzIcwAPfSFUsoVkvwwydeTnJjkBbXW\n",
568 "M2Y9sMkZdkSYgX4SZgAAYG5OS/L2JK9P8pIk70xyrSSPT/LpUsrtZz2ByRkmJsxAPwkz0B1TMzAf\n",
569 "pmaAPqm1Pmr990opByR5eZJjkrw0yaGznEOcYSLCDPSTMAPdEWZgPoQZGI59bvVjXV9CZ2qtF5ZS\n",
570 "Hp/kYUnuUEo5sNb6g2mP57YmtiXMQD8JM9AdYQbmQ5gBhqTWemGStSBzlVmOJc6wJWEG+kmYge4I\n",
571 "MzAfwgwwNKWUG6VZe+a7tdazZzmW25rYlDAD/STMQHeEGQBYLaWUI5NcP8nf1VovHvv+FZP81ejl\n",
572 "62Y9jzjDhoQZ6CdhBrojzMD8mJoBFqmUcsMkDx29PHi0vWUp5amjrz9Xaz1h9PUN08SXF5VSPpTm\n",
573 "Edo/luRuSX4iyUeSHDvrNYkzXIYoA/0lzEB3hBmYH2EG6MDNkjxv7PWeJIckue3o9RuSrMWZ9yd5\n",
574 "dpoYc0iSX0pyUZIvjY7x8lrrJbNekDjDjwgz0F/CDHRHmIH5EWaALtRaT8mEa/DWWv8jyf+a6wXF\n",
575 "gsCMCDPQX8IMdEeYgfkRZgD2EmcQZqDHhBnojjAD8yPMAFyW25pWnDAD/STKQLeEGQBgkUzOrDBh\n",
576 "BvpJmIFuCTMwX6ZmAC5PnFlRwgz0kzADwDITZgA2Js6sIGEG+kmYge6ZmoH5EWYANifOrBhhBvpJ\n",
577 "mIHuCTMwP8IMwNbEmRUizEA/CTPQPWEGAOiSOLMihBnoJ2EGuifMwHyZmgHYnjizAoQZ6CdhBron\n",
578 "zMB8CTMAk9mv6wtgfkQZ6C9hBronzMB8CTMAk5tLnCmlXDXJHZJcJ8kBtdY3jb33Y0kOTHJJrfU/\n",
579 "5nF+hBnoM2EGuifMAAB90mqcKaVcLckLkxydZP8ku5LsSfKmsd0OTfLOJJeWUm5caz2rzWtYdaIM\n",
580 "9JcoA/0gzMD8mZoB2JnW1pwppVwxyQeT/MbouF9JE2Yuo9b67iQnJ9k3ycPaOj/CDPSZMAP9IMwA\n",
581 "AH3U5oLAT0hy2zRR5mdrrT+T5OJN9n3NaPvLLZ5/ZZ3+rfOEGegxYQb6QZiBxTA1A7Bzbd7W9Kuj\n",
582 "7VNqrV/ZZt8Pjra3avH8K0mUgf4SZQAAgEm0GWd+Os1tTB+eYN+zR/tevcXzrxRRBvpNmIF+MTUD\n",
583 "i2FqBmA6bd7WtF+a4HL+BPteJc1iwf/d4vlXhjAD/SbMQL8IM7AYwgzA9NqMM19PE1wOnmDfe4y2\n",
584 "X23x/CtBmIF+E2agX4QZAGAI2owz70sTZx6/1U6llCsn+dPRy/e3eP6lZtFf6LezzjxXmIGeEWZg\n",
585 "cUzNAMymzTVnXpDk0UkeX0o5LcnLxt8spexK8otJ/iLJLdPc0vSy9Qfh8kQZ6DdRBvpHmAEAhqS1\n",
586 "yZla69eSPCzNujMvTvKtJPsn2VVK+VSSbyc5Mcmtk1yS5JG11rPaOv8yMi0D/SfMQP8IM7BYpmYA\n",
587 "ZtfmbU2ptf5Dkjsn+eck105zm1OS3CbJNUevP5PkyFrr29o897IRZaD/hBnoH2EGABiiNm9rSpLU\n",
588 "Wj+Z5G6llJsmOSzJ9ZPsm+bx2f9Sa/1c2+dcJqIM9J8oA/0kzMDimZoBaEfrcWZNrfW0JKfN6/jL\n",
589 "SJiB/hNmAKAhzAC0p7U4U0rZN8nL06wz845a6zs32e++SUqSHyZ5fK11T1vXMGTCDPSfMAP9ZWoG\n",
590 "ABiyNidnfiXJY5KcleSJW+x3apJXpbnd6b1JNow4q0KUgWEQZqC/hBlYPFMzAO1qc0Hgo0fbF9da\n",
591 "Ny0Otdbz0zxOe1eSR7Z4foDWnXXmucIM9JgwAwAsgzbjzJ3TPEb77yfY9/+Mtoe2eH6AVoky0G/C\n",
592 "DHTD1AxA+9qMM9dOsrvWevoE+34tTci5dovnB2iNMAP9JswAAMukzTjzvST7lFKuNsG+V0lzW9P3\n",
593 "Wzw/QCuEGeg3YQa6Y2oGYD7ajDOfTBNcygT7Pni0/XyL5weYifVloP+EGeiOMAMwP23GmTeNts8v\n",
594 "pdx5s51KKXdM8oLRy+NbPD/A1EQZ6D9hBgBYVm0+SvstSY5JckSSfyqlvCvJSUm+kWZ9mRslOTLN\n",
595 "I7f3TfKZJK9r8fwAUxFmAGBrpmYA5qu1OFNr3V1KeUiSv0ly3yQPGv3ZyCeSPLjWelFb5weYhjAD\n",
596 "w2BqBgBYZm1OzqTW+r0k9y+l3DfJw9M8Kvt6o7e/nSbKvLXZte5u89wAOyHKwHAIM9AtUzMA89dq\n",
597 "nFlTa31PkvfM49gAsxJmYDiEGQBgFbS5IDBA7wkzMBzCDHTP1AzAYsxlcgagb0QZGBZhBronzAAs\n",
598 "ztRxppRycpILa62/NHr9+jRPZdqRWuujpr0GgEkIMzAswgwAsGpmmZy5e5Ifjr1+xBTH2JNEnAHm\n",
599 "QpSB4RFmoB9MzQAs1ixx5tQkF469/tspjrHjSRuASQgzMDzCDACwqqaOM7XWw9e9/vWZrwagBcIM\n",
600 "AEzP1AzA4rW2IHAp5d5J9qu1/mNbxwTYCVEGhsvUDACwytp8WtPbR9sDWzwmwESEGRguYQb6w9QM\n",
601 "QDfajDP7Jrm0xeMBbEuUgWETZqA/hBmA7uzT4rHOSHJAKeVKLR4TYFPCDAybMAMA0Ggzzrwzya4k\n",
602 "R7Z4TIANCTMwbMIM9IupGYButRlnjktyQZJntHhMgMs468xzhRkYOGEGAOCy2lxz5n5J/i3JXUop\n",
603 "L0/y6Uk+VGt9VYvXACwxUQaGT5iB/jE1A9C9NuPMK8a+ftyEn9mTRJwBtiTKwHIQZgAANtZmnPna\n",
604 "FJ/Z0+L5gSUkzADA/JiaAeiH1uJMrfWgto4FIMrA8jAxAwCwtTYXBAZohTADy0OYgf4yNQPQH61M\n",
605 "zpRSrpDkZkmukuTrtdaz2jgusHqEGVgewgz0lzAD0C8zxZlSyr5J/leS30ly9bHv/2uS36+1njLT\n",
606 "1QErQ5SB5SLMAABMbtbbml6V5JlJrpFk19ifOyQ5sZTysBmPD6wAYQaWizAD/WZqBqB/po4zpZRf\n",
607 "THLM6OWbk9w1yc8mKUk+kmTfJK8ppdxg1osEltNZZ54rzMASueDL5wgzAABTmOW2pkeNtsfXWh8x\n",
608 "9v0vllL+IckH0gSb30ny+zOcB1hCogwsF1EGhsHUDEA/zXJb051G2xevf6PWekmSPx29vMcM5wCW\n",
609 "kDADy0WYAQCYzSyTMzdIsifJv23y/idG25vMcA5giYgysHyEGRgOUzMA/TXL5MyVklw0mpK5nFrr\n",
610 "95LsTnK1Gc4BLAlhBpaPMAPDIcwA9NtMj9JOMzmzlUuS7D/jOYABE2VgOQkzAADtmTXO7Cql/NRm\n",
611 "743+ZIt9Umv9yozXAPSUMAPLR5SB4TE1A9B/s8aZA5J8aYv3d422G+2zK83kzb4zXgPQM6IMLCdh\n",
612 "BgBgPmaNM8neADPNPpN8FhgQYQaWkzADw2RqBmAYZokzN23tKoClIMzAchJmYJiEGYDhmDrO1FrP\n",
613 "aPE6gAETZWB5CTMAAPPXxm1NwAoTZmA5iTIwbKZmAIZFnAGmIsrA8hJmYNiEGYDh2afrCwCGR5iB\n",
614 "5SXMAAAs3lJNzpRSbpHkC0neUms9eov9HpDkSUl+Ps3jwM9McnyS59ZaL9hg//2TPCHJw5PcPMkl\n",
615 "Sb6Y5JW11je2/XNAnwkzsLyEGRg+UzMAO7NdRyilXDXJbyQ5Msltklw3yUVJ/l+Sv0tyXK31wlmv\n",
616 "Y/BxppRycJKnJLl+knulmQbas8X+T0zyoiTnJnlnku8nuXuSZya5RynliFrrxes+dnySByY5Lckb\n",
617 "k+yf5P5JXl9KuVWt9Wmt/lDQQ6IMLDdhBoZPmAGYzA47wp2S/EWS7yU5NckZSa6R5D5J/jzJr5RS\n",
618 "Dq+1XjLLNQ0+ziS5UZLfyhZBZk0p5QZpfnnnJLl9rfXro+/vSvKWJL+a5LFJXjr2mYekCTOnJrlX\n",
619 "rfWi0fevkeTjSX63lPLXtdbPtvlDQZ8IM7DchBkAaM9Fn/1mkut0fRlsbeKOkOTbSX4zyZvWekCS\n",
620 "lFKukuTDSQ5Lc5fN62a5oMGvOVNrPaXWuk+tdd8kR2yz+1FpbmN65VqYGR1jT5JnjF4es+4zjxht\n",
621 "nzX+D6LWem6S5ybZNbYPLJWzzjxXmIEldsGXzxFmYEmYmoF+aMIMfbeTjlBr/XSt9TXjPWD0/fOT\n",
622 "vH708nazXtPg48w6u7Z5/86j7UfXv1FrPS3J2UluU0q50rrP7EnysQ2O95HR9rAdXif0nigDy02U\n",
623 "geUhzADMZLuOsJUDR9vvzHoRy3Bb007cdLQ9e5P31+bPDkrypdHCP9dOcv5GCwWP9h8/LgyeKAPL\n",
624 "T5gBgPaZmlkto+VRyujlqbMeb9kmZ7Zz1TRTMN/f5P0fpKlmVxvbP9vsn7H9YdCEGVh+wgwsF1Mz\n",
625 "0A/CzEp6UpqnN3241nrSrAdrfXKmlHLTNIvl3DnJ9ZJcodZ607H3H5jkAUkuTPL4Wuvutq9hAput\n",
626 "orzZONNO94fBEWZg+QkzANA+YWb1lFIemuQFae6mOaqNY7YaZ0opj0jyyjSL7q5Zv/rxyWlWMb56\n",
627 "krclObHNa9jGeWmCypU2ef/Asf3Gt5PuD4MjysBqEGZg+ZiaAbp2pZ/uw1OpFvu/haPu8dok/5Hk\n",
628 "F2ut/9HGcVu7ramUcvskr0kTZv46ycOywcRJrfV7SV6RJpI8tK3zT+j00fYnN3n/Bkl2r+1Xaz0v\n",
629 "yXeTXKuUcuVN9k+S09q8SFgUYQaWnycywXISZqAfTM2sllLKM9M8oelLSQ6rtX61rWO3uebM7ybZ\n",
630 "N8mLaq0Pr7UenyZ0bORto+0vtHj+Saw9Xelyj8oqpdw8zWLAn1+3+O9H0vxcd9/geHcZbTd6khP0\n",
631 "lkdkw2oQZQBgfoSZ1VFKOaCU8qYkf5zkA0l+odb69TbP0WacuVuaW5heNsG+Xxxtb9Ti+Sfx1iQX\n",
632 "JXlEKWVt6iWllH2S/Mno5RvXfebNo+1TSyn7j33mGkl+L83P/Ka5XTG0TJSB1SDMwPIyNQPdE2ZW\n",
633 "x6gdnJrk15O8JMkv1Vo3e2jQ1Npcc+Y6aULFGRPse9Fo35kX1C2l3DB7b486eLS9ZSnlqaOvP1dr\n",
634 "PSFJaq3fKKX8YZLnJ/lMKeXdSc5PctckP5fk41kXl2qttZRydJL7J/l8KeWDSfZPct8kP57kuFrr\n",
635 "J2f9OWARhBlYDcIMLC9hBmB2O+kIaQY57pDkq2laxnNLKdnAy2utUy950mac+X6Sa47+fGebfW+W\n",
636 "Jsy08V+PN0vyvLHXe5IckuS2o9dvSLL2S02t9YWllNOSPDHJA9OskXN6ml/4c2utF21wjoeM9j86\n",
637 "ycOTXJrkC0meUWt9Qws/A8yVKAOrQ5gBgPkyNbMUdtIRdo3ePzjNci4b2ZPknZlhPdo248ynktwj\n",
638 "zTos/7DNvo8ZbT8x60lrradkh7dn1VrfnuTtO9j/4jSPyXrBji4OekCYgdUgysDyMzUD3RNmlsNO\n",
639 "OkKt9Zgkx8z1gtLumjNra7U8Z7Qey4ZGtwg9efTyzZvtB8zGor+wOoQZWH7CDHRPmGGe2pyc+Zs0\n",
640 "t/3cM8m/lFJemtGaMqWUByS5aZIHZe8Tjt5fa31ni+cHRkQZWB3CDADA8LU2OVNr3ZNmbZa3pbkX\n",
641 "60VpFs7dleYWohdmLMwkOaqtcwMN0zKwWoQZWA2mZqB7pmaYtzYnZ1JrPT9JKaUckeSRSQ5Lcv0k\n",
642 "+6ZZ/PcTSd5ca31Hm+cFTMvAqhFmYDUIM9A9YYZFaDXOrKm1fjDJB+dxbODyhBlYLcIMACyGMMOi\n",
643 "tHZbUynlulN85vFtnR9WkduYYLVc8OVzhBlYIaZmAFZHm09r+lAp5YaT7FhK2VVKeWGSl7R4flgp\n",
644 "ogysFlEGVoswA90zNcMitRlnbp7kn0spN99qp1LKFZPUNI/T3tXi+WElmJaB1SPMAMBiCTMsWptx\n",
645 "5mNJbpzk1FLKrTfaoZRynSQnJ3lwkj1J/rDF88PSE2Vg9QgzsHpMzUC3hBm60GacOTLJe5JcL8nJ\n",
646 "pZRDx98spfxUko8muVOSHyZ5aK31z1o8Pyw1YQZWjzADq0eYAVhNrcWZWusPkjwwyZuSXDPJ+0eP\n",
647 "1E4p5a5pwsxN0zxS+4haa23r3LDM3MYEq0mYAYDFMzVDV9qcnEmt9ZIkxyR5QZKrJHl3KeX5SU5M\n",
648 "E2y+nOTQWuvH2jwvLCtRBlaPJzLB6jI1A90SZujSfm0fsNa6J8nTSinfShNpfnf01slJHlxr/V7b\n",
649 "54RlI8rAahJlAKAbwgxda3VyZlyt9S+SPDzJpUkuSfIUYQa2J8zAahJmYLWZmgFYbVNNzpRS7p3m\n",
650 "aUvbOSfJS5M8Mc0aNI9Pct74DrXW909zDbCMhBlYTcIMrDZhBrplaoY+mPa2pvdmsjiTJLtG2+sk\n",
651 "qWOf2zX6et8prwGWhigDq0uYAYDuCDP0xSxrzuzafpdtPzftMWBpCDOwmkQZIDE1A10SZuiTqeJM\n",
652 "rXVua9XAqhBlYHUJM0AizACwl8gCHRBmYHUJMwDQPVMz9E3rj9IGNifKwGoTZoA1pmagO8IMfWRy\n",
653 "BhZEmIHVJswAa4QZANabenKmlHJykgtrrb80ev36TP4Epx+ptT5q2muAoRBmYLUJMwDQD6Zm6KtZ\n",
654 "bmu6e5Ifjr1+xBTH2JNEnGFpiTKw2kQZYD1TM9AdYYY+myXOnJrkwrHXfzvFMXY8aQNDIczAahNm\n",
655 "gPWEGeiOMEPfTR1naq2Hr3v96zNfDSwBUQYQZgAA2InWFgQupdy7lHK/to4HQyTMAMIMsBFTM9Ad\n",
656 "UzMMQZuP0n77aHtgi8eEwRBmAGEG2IgwA90RZhiKNuPMvkkubfF4MAiiDJAIMwDQN8IMQ9LabU1J\n",
657 "zkhyQCnlSi0eE3pNmAEu+PI5wgywKVMzAEyizTjzziS7khzZ4jGhl84681xhBhBlgC0JM9AdUzMM\n",
658 "TZtx5rgkFyR5RovHhN4RZYBEmAGAvhJmGKI215y5X5J/S3KXUsrLk3x6kg/VWl/V4jXA3IgywBph\n",
659 "BtiOqRnohjDDULUZZ14x9vXjJvzMniTiDL0nzABrhBlgO8IMADvVZpz52hSf2dPi+WEuhBkgEWUA\n",
660 "oO9MzTBkrcWZWutBbR0L+kCUAdYIM8CkTM1AN4QZhq7NBYFhaQgzwBphBgD6TZhhGbQ2OVNKOTbJ\n",
661 "xbXW50yw7yFJfiXJ52qt/6eta4BZiTLAOGEG2AlTMwBMq83JmWOT/NGE+166w/1h7oQZYJwwA+yE\n",
662 "MAPdMDXDsujqtqZ/H21v2tH54TKEGWCcMAMA/SfMsEzafFrTTlx7tD2go/NDElEGuCxRBpiGqRlY\n",
663 "PGGGZbPQOFNK2T/JIUn+9+hbX13k+WGcMAOME2aAaQgzALRh6jhTStmdZM+6b1+xlHLpBB/fNdq+\n",
664 "bNrzw7REGWA9YQYAhsPUDMto1smZXRN+b73/SvL8WusrZzw/7IgwA6wnzADTMjUDiyfMsKxmiTP3\n",
665 "Gm33pAky709ycZL7ZvNAc0mSc5J8udY6yYQNtEKUATYizADTEmZg8YQZltnUcabWetL461LKqUku\n",
666 "rLV+YOarghYJM8B6ogwAAH3S2oLAtdbD2zoWtEWYAdYTZoBZmZqBxTM1w7Jb2NOaSinXSnJ+rfWi\n",
667 "RZ2T1SXKABsRZoBZCTOweMIMq2CmOFNKOSbJVZOcV2t9/QbvXynJsUkem+RqSS4tpZyY5Gm11i/M\n",
668 "cm7YjDADbESYAYDhEWZYFftM+8FSyk2SvDbJi5IcuMlur0nytCRXT7NI8H5J7pPkY6WUX5j23LCR\n",
669 "s848V5gBNiTMAG0wNQPAvEwdZ5Lcf7T9RpJXrH+zlHL3JA8bvfznJL+a5MFJTkxy5SR/M5qsgZmJ\n",
670 "MsBmhBmgDcIMLJ6pGVbJLLc13XW0fWOtdfcG7z9ytD0ryX1qrf+dJKWUdyX5cJI7JnlEklfOcA0g\n",
671 "zAAbEmUAYLiEGVbNLJMzPzfanrTJ+/cabf9uLcwkSa310iR/MXr5gBnOz4pzGxOwGWEGaJOpGVgs\n",
672 "YYZVNEucuX6SPUk+t/6NUsr1Ru8nzZTMemvfu80M52eFiTLAZoQZoE3CDACLMMttTVdOsrvW+l8b\n",
673 "vHfr0XZPkn/d4P1vjd77/9u783BbroLO+z9uCAEUCC1zaEh4BZEwz0NAhhjQprH1YQk0syIQI8KL\n",
674 "Q7cihMCLDQioNCBEW0OEMCxepUEQULRFSYDIIJgYFDNBQEhImDJCkv6j6piTkzPss8+u2lW1P5/n\n",
675 "uU/ds6v2rnVzK3Xv+d5VVTfew/5ZQaIMsB1hBgDGzawZVtVeZs5clGRfKeUGm6xbizPfqrWevcn6\n",
676 "a6d5ehPMTJgBtiPMAItm1gz0S5hhle0lzpyRJrDceZN1D2iXp2zx3tu0y2/tYf+sCPeWAXYizADA\n",
677 "uAkzrLq9xJm/apfPWf9iKeUmSR7Vfvl/tnjvj7TL0/ewf1aAKANs5+LTzhVmgE6YNQNAn/Zyz5k3\n",
678 "pQkzjyulnJXkzUlukeRlSa6f5Iokf7zFe0u7/Mwe9s/ECTPAdkQZoCvCDPTLrBnYw8yZWuvnkxyT\n",
679 "5tKm/5bmEqYP56pLml7fbnM1pZS7JvnRNDcE/uC8+2e6XMYE7ESYAYBpEGagsZfLmlJr/f+S/EqS\n",
680 "b6eJNNdKckmSVyR5/sbtSyn70sy4SZJvJPnzveyf6RFlgJ0IM0CXzJqB/ggzcJW9XNaUJKm1vrqU\n",
681 "8g685TQAACAASURBVIYkd0oTZ06ptV68xeY/kCbOvCnJ2bXWS/e6f6ZBlAFmIcwAXRJmAFiWPceZ\n",
682 "JGljzCdn2O7cJMctYp9MhzAD7ESUAbomzEC/zJqBq1tInIF5iDLALIQZoGvCDPRLmIFrEmfonSgD\n",
683 "zEKUAbomykC/RBnYmjhDb0QZYBaiDNA1UQb6J8zA9sQZOifKALMSZoCuCTPQP2EGdibO0BlRBpiV\n",
684 "KAN0TZSB/okyMDtxhoUTZYBZiTJA10QZWA5hBnZHnGFhRBlgN4QZoGvCDCyHMAO7J86wZ6IMsBui\n",
685 "DNA1UQaWR5iB+YgzzE2UAXZDlAG6JsrA8ogysDfiDLsmygC7JcwAXRNmYHmEGcaqlPKEJM9Oco8k\n",
686 "+yf5QpJ3JXlVrfXCPscizrArwgywG6IM0DVRBpZLmGGMSin7khyX5ElJ/i3Ju5NcnOShSY5O8thS\n",
687 "ymG11m/2NSZxhpmIMsBuCTNAl0QZWC5RhpH72TRh5qQkR6zNkiml7JfkNUmek+TlSY7sa0D7+toR\n",
688 "4/SVs74hzAC7cvFp5wozQKeEGVguYYYJeGK7PGb95Uu11suT/GqSC5I8rZRy3b4GJM6wKVEG2C1R\n",
689 "BujaFaecJ8zAkgkzTMQtk1yZ5IyNK2qtlyb5WJIDktyrrwG5rImrEWSAeYgyQJcEGRgGYYYJOSfJ\n",
690 "7ZPcNcm/bLL+/HZ5s74GJM6QRJQB5iPKAF0SZWAYRBkm6Lg0N/99Qyll/yQfSHJRklsleXiSB7Xb\n",
691 "HdDXgMSZFSfKAPMQZYCuCTMwDMIMU1RrPb6UckiSFyQ5YcPqC5Jcsu7nvRBnVpQoA8xLmAG6JMrA\n",
692 "cAgz7OSWtz1w2UNILpzvz41a6zGllOOSPDLNPWguSXOJ0weTnJjkFklOW8wgdybOrBhRBpiXKAN0\n",
693 "SZSB4RBlWBW11rOSHLv+tVLKQUnukuTMdn0vxJkVIcoA8xJlgK4JMzAcwgzk6HZ57LZbLZg4M3Gi\n",
694 "DLAXwgzQJVEGhkWYYZWVUq6d5NeTPCPJKUle0+f+xZmJEmWAvRBlgC6JMjA8wgyrppRyZJr7zZyd\n",
695 "5MAkD0tyUJJPJnl0rfWyPscjzkyMKAPshSgDdE2YgWERZVhhFyd5RJL9k5yX5NNpZs68pdZ6Zd+D\n",
696 "EWcmQpQB9kqYAbokysDwCDOsslrrcUmOW/Iw/p04M3KiDLBXogzQJVEGhkmYgWERZ0ZKlAH2SpQB\n",
697 "uibMwPCIMjBM4swICTPAXgkzQJdEGRgmYQaGS5wZEVEG2CtRBuiSKAPDJczAsIkzIyDKAHslygBd\n",
698 "E2ZguIQZGD5xZsBEGWARhBmgS6IMDJcoA+MhzgyQKAMsgigDdEmUgWETZmBcxJkBEWWARRBlgK4J\n",
699 "MzBswgyMjzgzAKIMsCjCDNAlUQaGTZSB8RJnlkyYARZBlAG6JMrA8AkzMG77lj0AAPZGmAG6JMzA\n",
700 "8AkzMH5mzgCMlCgDdEmUgXEQZmAaxBmAkRFlgC6JMjAOogxMizgDMCLCDNAVUQbGQ5iB6RFnAEZA\n",
701 "lAG6JMzAeAgzME3iDMCAiTJAl0QZGBdhBqZLnAEYKGEG6IooA+MiysD0iTMAAyPKAF0SZmBchBlY\n",
702 "DeIMwECIMkCXRBkYH2EGVoc4AzAAwgzQFVEGxkeUgdUjzgAskSgDdEmYgfERZmA1iTMASyDKAF0S\n",
703 "ZWCchBlYXeIMQM+EGaArogyMlzADq02cAeiJKAN0SZiBcRJlgEScAeicKAN0SZSB8RJmgDXiDECH\n",
704 "hBmgK6IMjJswA6wnzgB0QJQBuiTMwHiJMsBmxBmABRJlgC6JMjBuwgywFXEGYEGEGaArogyMnzAD\n",
705 "bEecAdgjUQbokjAD4yfMADsRZwDmJMoAXRJlYPxEGWBW4gzAHIQZoCuiDEyDMAPshjgDsAuiDNAl\n",
706 "YQamQZgBdkucAZiRMAN0RZSBaRBlgHmJMwA7EGWArogyMB3CDLAX4gzAFkQZoEvCDEyHMAPslTgD\n",
707 "sAlhBuiKKAPTIswAiyDOAKwjygBdEWVgWkQZYJHEGYCIMkB3RBmYHmEGWLR9yx4AwLIJM0BXhBmY\n",
708 "HmEG6IKZM8DKEmWArogyMD2iDNAlcQZYOaIM0BVRBqZJmAG6Js4AK0OUAbokzMA0CTNAH8QZYPJE\n",
709 "GaBLogxMlzAD9EWcASZLlAG6JMrAdIkyQN/EGWByRBmga8IMTJcwAyyDOANMhigDdE2UgWkTZoBl\n",
710 "EWeA0RNlgK6JMjBtogywbOIMMFqiDNAHYQamTZgBhkCcAUZHlAH6IMrA9AkzwFCIM8BoiDJAH0QZ\n",
711 "WA3CDDAk4gwweKIM0BdhBqZPlAGGSJwBBkuUAfoiysBqEGaAoRJngMERZYC+iDKwOoQZYMjEGWAw\n",
712 "RBmgT8IMrAZRBhgDcQZYOlEG6JMoA6tDmAHGQpwBlkaUAfokysBqEWaAMRFngN6JMkDfhBlYLcIM\n",
713 "MDbiDNAbUQbomygDq0WUAcZKnAE6J8oAfRNlYPUIM8CYiTNAZ0QZYBmEGVg9wgwwduIMsFCCDLAs\n",
714 "ogysJmEGmAJxBlgIUQZYFlEGVpMoA0yJOAPsiSgDLIsoA6tLmAGmRpwB5iLKAMskzMDqEmaAKRJn\n",
715 "gF0RZYBlEmVgdYkywJSJM8BMRBlgmUQZWG3CDDB14gywLVEGWDZhBlabMAOsAnEG2JQoAyybKAMI\n",
716 "M8CqEGeAqxFlgGUTZQBRBlg14gyQRJQBhkGYAYQZYBWtbJwppTwhybOT3CPJ/km+kORdSV5Va71w\n",
717 "w7b/J8lDdvjI69ZaL+tgqNApUQYYAlEGSIQZoH+llBsm+fkk/znJHZIcmOQbSR5Sa/2nvsaxcnGm\n",
718 "lLIvyXFJnpTk35K8O8nFSR6a5Ogkjy2lHFZr/eYmb/+DNL9Jm7l84YOFDokywBCIMkAiygDLUUp5\n",
719 "UJI/TXKTJCclqUm+m+S2Sa7V51hWLs4k+dk0YeakJEeszZIppeyX5DVJnpPk5UmO3OS9L6+1nt7X\n",
720 "QKELogwwFMIMkAgzwHKUUu6Q5INJvpKmDXxmmePZt8ydL8kT2+Ux6y9fqrVenuRXk1yQ5GmllOsu\n",
721 "Y3DQlYtPO1eYAQbhilPOE2aAJMIMsFSvTzM75pHLDjPJas6cuWWSK5OcsXFFrfXSUsrHkvxYknsl\n",
722 "+eiGTXqd1gSLIMgAQyHIAOsJM8CytLNmHpHkj5NcWEp5ZppLmb6T5F+S/Fmt9ZI+x7SKceacJLdP\n",
723 "ctc0/9E3Or9d3myTdaeUUq6T5JIkX0zyF2luIHxmB+OEPRFlgCERZoA1ogwwAD/SLu+TZuLGxitn\n",
724 "vlhK+cla66f6GtAqxpnj0tz89w2llP2TfCDJRUluleThSR7UbnfAuvecnuTrSb6W5LIkN09T2X4+\n",
725 "yZNKKYfXWv++j8HDTkQZYEhEGWA9YQYYiDu0y+8keXqaq2b+LcltkvxSmnvQvr+Ucsda61YPBVqo\n",
726 "lYsztdbjSymHJHlBkhM2rL4gzayYtZ+vvednNn5OKeWAJG9I8xv5uiT372TAMCNRBhgSUQbYSJgB\n",
727 "BuRG7fJ1tdZ3rHv99CRHlVIOTnO7k8cleVMfA1rFGwKn1npMmkubnp3kmCS/luSxaSrZeWnuSXPa\n",
728 "Dp9xaZqZM5ckuU8p5fpdjhm24ka/wNAIM8B6l332HGEGGJrL2uVW38d/oF0e2sNYkqzgzJk1tdaz\n",
729 "khy7/rVSykFJ7pLkzHb9Tp9xaSnlojSXQH1/msujoBeCDDA0ogywkSgD03bILW6w7CHk6/8619vW\n",
730 "Tk4Hb7G+94ksKxtntnB0uzx2261apZT/mOQ/JPl6rfVrnY0KWoIMMESiDLAZYQYYsI+0y/+U5L9v\n",
731 "sv5u7fJz/QxnRS9r2qiUcu1SyouSPCPJKUles27d4aWUJ7c3D17/nuvmqmvP/rC3wbKSXLoEDJUw\n",
732 "A2xGmAGGrNb60ST/kOTQUsqL168rpdwvyZPSPBToHdd8dzdWcuZMKeXIJI9McnaSA5M8LMlBST6Z\n",
733 "5NG11svWbX7rNPHlt0spf5vmEdo3SfKQNE94OjFXzbiBhRJkgKESZYDNiDLAiDw5zQyaF5VSHpPk\n",
734 "E2m6wKPS3Fv28bXWb/U1mFWdOXNxmkdhPzPN47M/k+SpSe5ba/3qhm0/lORlaWbU3CPJz6WZ+vSl\n",
735 "JM9L8tBa6yWBBTJTBhiqK045T5gBNiXMAGNSa/3HJHdP8vtpJmD8TJJ7J3l7knvVWj/c53hWcuZM\n",
736 "rfW4JMfNuO2Xk7ywy/HAGkEGGDJRBtiKMAOMUa317CTPWvY4khWNMzA0ogwwZKIMsBVRBmAxxBlY\n",
737 "IlEGGDJRBtiOMAOwOOIMLIEoAwydMANsR5gBWCxxBnokygBDJ8oAOxFmABZPnIEeiDLAGAgzwHZE\n",
738 "GYDuiDPQIVEGGANRBtiJMAPQLXEGOiDKAGMgygCzEGYAuifOwAKJMsBYCDPATkQZgP6IM7AAogww\n",
739 "FqIMMAthBqBf4gzsgSgDjIUoA8xKmAHonzgDcxBlgDERZoBZCTMAyyHOwC6IMsCYiDLArEQZgOUS\n",
740 "Z2AGogwwJqIMsBvCDMDyiTOwDVEGGBthBtgNYQZgGMQZ2ECQAcZIlAF2Q5QBGBZxBlqiDDBGogyw\n",
741 "W8IMwPCIM6w8UQYYK2EG2C1hBmCYxBlWligDjJUoA8xDmAEYLnGGlSPKAGMlygDzEGUAhk+cYWWI\n",
742 "MsCYCTPAPIQZgHEQZ5g8UQYYM1EGmIcoAzAu4gyTJcoAYybKAPMQZQDGSZxhckQZYOyEGWAewgzA\n",
743 "eIkzTIYoA4ydKAPMQ5QBGD9xhtETZYCxE2WAeYgyANMhzjBaogwwBcIMMA9hBmBaxBlGR5QBpkCU\n",
744 "AeYhygBMkzjDaIgywBSIMsC8hBmA6RJnGDRBBpgSYQaYhygDMH3iDIMjyABTI8oA8xJmAFaDOMMg\n",
745 "CDLAFIkywLxEGYDVIs6wVKIMMEWiDLAXwgzA6hFn6J0gA0yVKAPshSgDsLrEGXohyABTJsoAeyXM\n",
746 "AKw2cYbOCDLAKhBmgL0QZQBIxBk6IMoAq0CUAfZKmAFgjTjDQggywKoQZYBFEGYAWE+cYW6CDLBK\n",
747 "RBlgEUQZADYjzrArggywakQZYFGEGQC2Is4wE1EGWDWiDLAoogwAOxFn2JIgA6wqYQZYFGEGgFmI\n",
748 "M1yNIAOsMlEGWBRRBoDdEGcQZICVJ8oAiyTMALBb4swKE2WAVSfKAIskygAwL3FmxQgyAKIMsHjC\n",
749 "DAB7Ic6sAEEGoCHKAF0QZgDYK3FmogQZgKsTZoBFE2UAWBRxZmJEGYCrE2WALggzACySODMBggzA\n",
750 "NYkyQBdEGQC6IM6MlCADsDlRBuiKMANAV8SZERFkALYmygBdEWUA6Jo4MwKiDMDWRBmgS8IMAH0Q\n",
751 "ZwZKkAHYnigDdEmUAaBP4syACDIAsxFmgC4JM8C8zjrz1CTJ/XP3JY+EsRFnlkyQAZidKAN0TZgB\n",
752 "5rUWZmAe4gwAgyfKAF0TZYB5iTIsgjgDwGCJMkAfhBlgXsIMiyLOADA4ogzQB1EGmJcow6KJMwAM\n",
753 "higD9EWYAeYlzNAFcQaAQRBmgD6IMsC8RBm6JM4AsFSiDNAXYQaYhyhDH8QZAJZClAH6JMwA8xBm\n",
754 "6Is4A0CvRBmgT6IMMA9Rhr6JMwD0QpQB+ibMAPMQZlgGcQaATokyQN9EGWAeogzLtG/ZAwBguoQZ\n",
755 "oG/CDDAPYYZlM3MGgIUTZYC+iTLAPEQZhkKcAWBhRBlgGYQZYB7CDEMizgCwZ6IMsAyiDDAPUYYh\n",
756 "EmcAmJsoAyyLMAPMQ5hhqMQZAHZNlAGWSZgBdkuUYejEGQB2RZgBlkWUAeYhzLCZUsqjkjw2yf2S\n",
757 "HJLkgCQXJDkpyR/VWt/d53g8ShuAmVxxynnCDLA0wgywW2edeaoww3b+IMmTk5yX5Pgkr0/y8SRH\n",
758 "JPmTUsqL+xyMmTMAbEuQAZZJlAHmIcowg19N8sFa69fXv1hK+dEkH0zy3CQv7msw4gwAmxJlgGUT\n",
759 "ZoDdEmWYVa31hC1WndYuv9bXWBJxBoANRBlg2UQZYB7CDHtRSrlxknsneVmSbyc5ss/9izMAJBFl\n",
760 "gGEQZoDdEmXYq1LKN5LcsP3yrUl+stba6x9IbggMgDADLN1lnz1HmAF2xQ1/WaDXJjk2zZOanpjk\n",
761 "+FLKQX0OwMwZgBUmygBDIMoAuyXKsEi11het/byU8l+S/EmStyd5cF9jMHMGYAV5LDYwFMIMsBtm\n",
762 "y9C1Wuu7k/xLkgeVUu7Q137NnAFYIYIMMBSiDLBboszw3Pqm37fsIeTr/9rNxya5fZIbd/LpmxBn\n",
763 "AFaAKAMMiTAD7IYoQ59KKddL8kNJrkxyZl/7FWcAJkyUAYZElAF2S5ihC6WURyR5QJLX1Vq/se71\n",
764 "fWluDnzjJB+otX61rzGJMwATJMoAQyPMALshytCx70/ykiQvKKX8XZJ/TvMo7cOS3DbJ6Ume0eeA\n",
765 "xBmAiRFmgCERZYDdEmbowd8keV6SRyS5W5qnMl2e5AtJXprk1bXWb/U5IHEGYCJEGWBohBlgN0QZ\n",
766 "+tJeyvTa9scgiDMAIyfKAEMkzAC7Icyw6sQZgJESZYAhEmWA3RBloCHOAIyMKAMMlTAD7IYwA1cR\n",
767 "ZwBGQpQBhkqUAXZDlIFr2rfsAQCwM2EGGCphBtgNYQY2Z+YMwICJMsBQiTLAbogysD1xBmCARBlg\n",
768 "yIQZYFaiDMxGnAEYEFEGGDJRBtgNYQZmJ84ADIAoAwydMAPMSpSB3RNnAJZIlAHGQJgBZiXMwHzE\n",
769 "GYAlEWaAoRNlgFmJMrA34gxAz0QZYAyEGWBWwgzsnTgD0BNRBhgDUQaYlSgDiyPOAHRMlAHGQpgB\n",
770 "ZiXMwGKJMwAdEWWAsRBlgFmJMtANcQZgwUQZYEyEGWBWwgx0R5wBWCBhBhgLUQaYlSgD3RNnABZA\n",
771 "lAHGRJgBZiXMQD/EGYA9EGWAsRFmgFmIMtAvcQZgDqIMMDaiDDArYQb6J84A7IIoA4yRMAPMQpSB\n",
772 "5RFnAGYgygBjJMoAsxJmYLnEGYBtiDLAWAkzwCxEGRgGcQZgC8IMMEaiDDALUQaGRZwB2ECUAcZK\n",
773 "mAFmIczA8IgzAC1RBhgrUQaYhSgDwyXOACtPlAHGTJgBZiHMwLCJM8DKEmWAsRNmgJ2IMjAO4gyw\n",
774 "ckQZYOxEGWAWwgyMhzgDrAxRBpgCYQbYiSgD4yPOAJMmyABTIcoAsxBmYJzEGWByBBlgaoQZYCei\n",
775 "DIybOANMgiADTJEoA8xCmIHxE2eAURJjgKkTZoCdiDIwHeIMMBqCDLAqhBlgJ8IMTIs4AwyaIAOs\n",
776 "ElEG2IkoA9MkzgCDI8gAq0iYAXYizMB0iTPAIAgywKoSZYCdiDIwfeIMsBRiDIAwA2xPlIHVIc4A\n",
777 "vRFkABqiDLATYQZWizgDdEqQAbg6YQbYjigDq0mcARZOkAG4JlEG2IkwA6tLnAEWQpAB2JowA2xH\n",
778 "lAHEGWBuggzAzoQZYDvCDJCIM8AuiDEAsxNlgO2IMsB64gywLUEGYPeEGWA7wgywkTgDXIMgAzAf\n",
779 "UQbYjigDbEWcAZIIMgB7JcwA2xFmgO2IM7DCBBmAvRNlgO2IMsAsxBlYMYIMwOIIM8B2hBlgVuIM\n",
780 "TJwYA7B4ogywHVEG2C1xBiZIkAHojjADbEeYAeYhzsBECDIA3RNmgK2IMsBeiDMwYoIMQD9EGWA7\n",
781 "wgywV+IMjIwgA9AvYQbYiigDLIo4AyMgyAD0T5QBtiLKAIsmzsAAiTEAyyXMAFsRZoAuiDMwEIIM\n",
782 "wPKJMsBWRBmgS+IMLJEgAzAcwgywFWEG6Jo4Az0TZACGR5gBNiPKAH0RZ6AHggzAMIkywFaEGaBP\n",
783 "4gx0QIwBGD5hBtiMKAMsgzgDCyLIAIyDKANsRZgBlkWcgT0QZADGRZgBNiPKAMsmzsAuCTIA4yPK\n",
784 "AFsRZoAhEGdgBoIMwHgJM8BmRBlgSMQZ2IIgAzBuogywFWEGGBpxBlpiDMB0CDPAZkQZYKjEGVaa\n",
785 "IAMwPcIMsBlhBhgycYaVI8gATJMoA2xGlAHGQJxhJQgyANMmzAAbiTLAmIgzTJYgAzB9ogywGWEG\n",
786 "GJuVjTOllCckeXaSeyTZP8kXkrwryatqrRdusv1PJHlekrsnOSDJWUnekeQVtdaL+xo32xNkAFaH\n",
787 "MANsJMoAu1FKuXOSFyV5SJIDk5yb5ENJXlxr/WKfY9nX586GoJSyr5RyfJK3Jrl9kncnOT7JdZIc\n",
788 "neRjpZQbbXjPc5P8aZK7JXlPkv+V5LtpfhM/VErZv79fAetdccp5V/sBwPRd9tlzhBngGoQZYDdK\n",
789 "KQ9I8okkP5HkY0nelOSfkjw9ycmllIP7HM8qzpz52SRPSnJSkiPWZsmUUvZL8pokz0ny8iRHtq8f\n",
790 "1H59bpJ7r9WzUsq1krwtyU8neVaS1/X7y1hdIgzA6hJlgI1EGWBOb0ozSeMxtdb3r71YSjkqyf9M\n",
791 "8qokj+1rMCs3cybJE9vlMesvX6q1Xp7kV5NckORppZQD2lWPS3MZ0xvXT2uqtV6Z5NfbL5/e+ahX\n",
792 "nNkxAKvNbBlgM8IMMI9Syj2T3DnJR9eHmSSptb4+yZeSPKaUcuO+xrSKceaWSa5McsbGFbXWS9NM\n",
793 "Zzogyb3alx/QLk/aZPvTk3wtyd1KKdftZLQrTJABIDFbBrims848VZgB9mLL7/NbJ6a50uh+/Qxn\n",
794 "NePMOUmuleSuW6w/v13evF3erl1+bYfPO2Qho1txggwA6wkzwEaiDLAAs3yfn/T4ff4q3nPmuCQP\n",
795 "TfKG9ka+H0hyUZJbJXl4kge1261d1nSDNDNtvrXF512UJs7csJvhTp8QA8BGogywkSgDLNAN2uV2\n",
796 "3+cnPX6fv3JxptZ6fCnlkCQvSHLChtUXJLlk3c/X+94WH3mtvYzn7oft6e3TcNhNlz0CAAbHnw3A\n",
797 "1d0/d1/2EIAN/uFjf7fsIexVJ9/nz2MVL2tKrfWYNI/RfnaSY5L8Wpq7MN8myXlpZsqc1m7+7TS/\n",
798 "Mdfb4uOuv247AAAAYNjWvn8fzPf5KzdzZk2t9awkx65/rX1s9l2SnNmuT5obB98jyW3TPPN8o4OS\n",
799 "XJFNbjC8ncMPP9yUGQAAAEZnAt/Prn3/ftst1h/ULk/vYSxJVnTmzDaObpfro82J7fLhGzcupdw+\n",
800 "zbzrf6y1Xtzx2AAAAIC92+77/H1JHpjk8iQn9zUgcSZJKeXapZQXJXlGklOSvGbd6ncmuSzJU9uZ\n",
801 "NWvv2Zfkpe2Xb+5rrAAAAMD8aq2fSnJqknuVUo7YsPrINDNn3l9r/XpfYxr7VKS5lFKOTPLIJGcn\n",
802 "OTDJw9L8x/9kkkfXWr+6YftfSvJbaR6z/WdJvpPkwWkugfp4kh+ptV7W2y8AAAAAmFsp5UFJ/jLN\n",
803 "pJX3JflSkh9Kcniae9E+sNb6r32NZ7++djQkhx566J2THJXkvklunuQzSV6W5Dm11u9s3P7UU089\n",
804 "6dBDD/1smmehPzTJfdI8cuv3kjyz1nrJxvcAAAAAw3Tqqad+8dBDD/2zNE3gwUkOS3Mj4P8/yRNr\n",
805 "rWcucXgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACwKNda9gDGpJRy5yQvSvKQJAcmOTfJ\n",
806 "h5K8uNb6xTk/86ZJPp/klFrrg3fY9rAkv5bkfkm+P8k5Sd6T5KW11vPn2T/LtcxjqpRyXJKn7PBx\n",
807 "d6y1/vM842A5FnVMlVIeneQnk9w3ySFJ9k/ylSR/neQ3a63/ssX7nKcmZpnHlPPU9CzweHpgkick\n",
808 "eWCSH0xy/STfSvLpJH+c5Pha65WbvM85amKWeUw5R01TF38/X/fZRyc5OslHt/p7uvPU6rr2sgcw\n",
809 "FqWUByT5cJL9kvx5krOS/HCSpyf5T6WU+9daz5zxsw5M8tIkN0tyeJr/6a/xF4gN7/mpJDXJJUne\n",
810 "m+SrSe6T5LlJfqzd/zd2/ytjWZZ9TK3zziRnb7HOHwAjsshjKskbk9wiyd8neUuSK9Kcc56a5LGl\n",
811 "lIfXWk/esH/nqYlZ9jG1jvPUBCz4eHplkgck+XiSdyS5MMl/THJEkocneViSp23Yv3PUxCz7mFrH\n",
812 "OWoiFnxMbfzsZ6YJM8kWf093nlpt4szs3pTkOkkeU2t9/9qLpZSjkvzPJK9K8tgZP+vAJEdlxm+e\n",
813 "SynXT/J7SS5Nclit9dPr1r0yyS8n+Y12yXgs7Zja4Nha61/N8T6GZ5HH1LFJ/mjjvxCVUl6U5MVJ\n",
814 "Xp3mX5TWXneemqalHVMb3+s8NQmLPJ5ek+TjtdZz1r9YSrljklOSPKWU8txa6zfb152jpmlpx9QG\n",
815 "zlHTschj6t+VUn4iyeuTvD/Jj2+xjfPUitu37AGMQSnlnknunGb62fvXr6u1vj7Jl5I8ppRy41k+\n",
816 "r9Z6Zq11X611vyS3m+Etj0py0+atV/1P2jomTVl9cinF7+dIDOCYYmI6OKZessXU3de2y3tteN15\n",
817 "amIGcEwxIR0cT3+y8Zvo1hlpzjcXJvnOutedoyZmAMcUE7PoY2rd5z4oyduTvC/JL26zqfPUivMb\n",
818 "O5sHtMuTtlh/YppZSPeb47Nnue/PlvuvtV6Y5LNp/ke+wxz7ZzmWfUztZXuGqctjar3rt8uvz7p/\n",
819 "56nRWvYxtZ7z1Ph1ejyVUm7Y3jPkf6f5++2RtdbLZ9m/c9RoLfuYWs85ahoWfkyVUu6U5vKkTyZ5\n",
820 "XJpLene9f+ep1eCyptmszUT42hbr1yr7IQPY/2kdjYHFWvYxtd77SinXSTOF8itJPpLk1bXWz/Ww\n",
821 "bxanr2Pqce3yI3vYv/PUOCz7mFrPeWr8OjueSimfSXLX9ssPJbnrJjeYdo6anmUfU+s5R03DQo+p\n",
822 "Usqtk3wgzTHx6FrrpaWURe3feWqCzJyZzQ3a5be2WH9Ru7zhRPfP4g3h9/ScNCX/zUl+N8mfpLn5\n",
823 "2VOSnNw+WYXx6PyYKqXcLskL0/zl83/0vX96t+xjKnGempIuj6fjkrwhyV+luSn+O9p/re5r/yzH\n",
824 "so+pxDlqahZ2TLWXPn0gzayqR854E1/nqRVn5szufG+L1/uayrjs/bN4S/s9rbW+YONr7TWsICTM\n",
825 "hQAADBlJREFUR6f5ZumNpZTb1Fq3m37J8HRyTJVSbpXkg0lulORnaq2n9Ll/lmppx5Tz1CQt/Hiq\n",
826 "tf7O2s9LKfdJ8ndJ3l1KuWut9ZKu98/SLe2Yco6arD0dU+0x8J4kt0rykFrrl/rcP+Nl5sxsvt0u\n",
827 "r7fF+utv2G5q+2fxBvl7Wmu9otZ6dJIzk9wyzaMDGYfOjqlSysFJ/ibNNNrn1Frf3Of+WZplH1Ob\n",
828 "cp4arV7OEe3j2P86yQ/m6k//co6anmUfU1tt7xw1Xos6pm6Y5EFJPpfkaaWUV639SPLr7TaHtK+9\n",
829 "pIP9M1JmzszmjHZ52y3WH9QuT5/o/lm8of+enp/k4CTft6T9s3udHFPtvxi+N83shifXWt/W5/5Z\n",
830 "qmUfUztxnhqXPs8R57fL9U9UcY6anmUfU7O85+A4R43Joo+pw5I8eJvPen6SbyR5UUf7Z2TEmdmc\n",
831 "2C4fvnFFO23tgUkuT3Jyh/t/frv/N27Y/w3T3LDs/CT/3NH+WbxlH1NbKqVcP8kPpZlSud3N7xiW\n",
832 "hR9TpZTHprmO/uIkR9Ra/3aH/TtPTcuyj6ntPsd5anx6+XOvlHKtXHUj1zPWrXKOmp5lH1Pbvcc5\n",
833 "apwWcky195fZ9AqVUspt0xxHf1dr3TgTy3lqxbmsaQa11k8lOTXJvUopR2xYfWSaivn+Wuu/Pwa0\n",
834 "lHJ8KeW0UspvLmAIH0hyXpJHl1LusmHdC5MckOStrmcdj2UfU6WUu5VSntP+5WH96/vS3NDu+5L8\n",
835 "aa31gr3ui34s+pgqpbw0yTuTfCHJfWb4Jtp5amKWfUw5T03LIo+nUsoPl1JeU0q5xSa7+o0kd0py\n",
836 "Sq31E+ted46amGUfU85R09PT38+3u2+M89SKM3Nmds9K8pdJ3ltKeV+SL6Up4ocnOTdN5VzvNmme\n",
837 "QX+Nk3wp5Qbt5yVXTY+8dSnll9ufn11rfefa9rXWi0opRyV5e5ITSynvTfL1JPdM8oA0f9E9Zs+/\n",
838 "Qvq2tGOq3eZ3k7yslPK3aQr+DdMcT/9Pks8n+YU9/epYhoUcU6WUhyR5QZp/8TsxyVFbPPrxE2vH\n",
839 "lfPUZC3tmIrz1BQt6s+9A5I8L8kvlFI+nuSUJNdJcv8kd2w/67+uf4Nz1GQt7ZiKc9RULezv57vl\n",
840 "PIWZMzOqtX40zQn6PWlu8PSsNBX9uDT/AvivG95yZftjMz+Q5JXtj19rt7vtuteevcn+a5opbn+b\n",
841 "5JFJfi7NSeB3k9y/1nr+xvcwbEs+pj6d5hulj6f5S8fTkvxUkgvTPGHg3rXWc+f+xbEUCzym1v5V\n",
842 "Z7/2M56/yY//N8mjNuzfeWpilnxMOU9NzAKPp9PSnF/eneaGq09N8vg0f6/9nSR3q7V+bpP9O0dN\n",
843 "zJKPKeeoCVrw38/n2b/zFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n",
844 "AAAAAAAAAAAAAAAAAAAAAAAAAAAjca1lDwAAGKZSyplJbpPkyFrrm5Y8nCRJKeW4JE9J8o5a6xOW\n",
845 "PBwAgIW49rIHAABsrZTy0iQvSPLNJDevtV42w3uel+Q1Sc5Ncsta6xV7HMaVe3z/vyulvCXJf03y\n",
846 "5lrr07fY5mlJ/rD98uBa69k7jamUcnCS09svn15rffO6dc9IcmySs2qth+zpFwAA0IF9yx4AALCt\n",
847 "t7bLGyb5sRnfszaj5B0LCDNd2S74fC/JpUku2WG7jZ+39p7vzbFPAIClMXMGAAas1npaKeUzSe6e\n",
848 "5PFJ/vd225dSbpfkPmlCxFu323aoaq1vSfKWXb7nrCTX62ZEAADdMnMGAIbvhHb56FLK9XfY9vHt\n",
849 "8oxa68c7HNNeue8dAEDLzBkAGL63JXllku9L8pgkb99m27U4c8L6F0sp90ny/CQPSXKTJN9IcmKS\n",
850 "19VaP7zbAZVSXpjkfkkOSXKLNJddfTvJPyV5T/u5F67b/uBcdU+YJHlqKeWpGz724Frr2aWUw5N8\n",
851 "KElqrTP9Q1Ip5dpJ1u7H87Ba69+0r5+Z5qbGSXJwKWXjZV5PT3Jhkne2779VrfX8LfbxiCR/kebS\n",
852 "qVvWWr85y9gAAHZi5gwADFyt9Zwkf9N++fittiul3CnJndNc0nTCutefl+TjSR6XJqTsSxNofiLJ\n",
853 "X5RSXjHHsF6Q5MeT/HCSA9t93ijJA5L8jyQnl1J+YN32a/eEWYsjV6SJHOt/bLwnzDz3iLlyw/s2\n",
854 "3oNm4z6/l+ZSsXOTXCfNk6C28nPt8h3CDACwSOIMAIzDWmx5ZCnlRltss3Yj4M/UWk9LklLKj6d5\n",
855 "ctOVSV6XZnbK/kluleQl7eu/0j7RaDdOTvIbSe6Z5Lq11uskuWmSZ6SZQXPHJC9c27jWelat9Xpp\n",
856 "ZgElyfG11utv+PHFXY5hR7XWOyY5sv3yzE32+dZa63eTrD3d6Wc3+5xSyk2S/GSa/16DeKw4ADAd\n",
857 "LmsCgHF4V5LXJzkgyU8l+aNNtnlcu1x/SdMr2+WxtdZfXHux1vrVJC8upXwvTaR5WSnl+Fke1d2+\n",
858 "/8GbvHZ+kj9sZ8y8Isl/TvK8DZst414zs+zzD5L8cpI7lVLuX2v92Ib1T0myf5LPbbIOAGBPzJwB\n",
859 "gBGotV6Q5P3tl9e4tKmUcq8kP5jmcqG3ta/dJcmd0sz2ePkWH/3bSS5Oc5nTjy5ouJ9ulwct6PM6\n",
860 "V2v95yQfSRNyNptFtPaaWTMAwMKZOQMA43FCmhsCP6yUctNa67nr1q1d0vSRWuuX25/fp11+uX3U\n",
861 "9DXUWi8spXw6yQOT3CvJ+2YdTCnl9kl+Osl9k9wuzU2Bb9D+SJqZJmPy+2lumPzTpZTnrt3QuJRy\n",
862 "WJrLtC5M8sdLHB8AMFHiDACMx3uSfCfJ9ycpSd6QJKWUa6WJJMnVL2m6Wbv86g6f+5V2efNZBlFK\n",
863 "2S/J7yT5+Vz9kqG1G/F+L8l+s3zWwLwryWuT3DjN7KT/1b6+/kbA317GwACAaXNZEwCMRK31kiTv\n",
864 "br9cf2nTg5LcOs3TkGoPQ3lJkqPShJm/TvLUJHdLcpNa635JjuhhDAtXa700V82MeUaSlFIOTBPC\n",
865 "3AgYAOiMmTMAMC4nJHlSkgeWUm5da/1Srrqk6QMbHvG8NmPmVjt85i3b5dd22nk7S+eo9ss31lp/\n",
866 "fpPNlnHT30X5/SS/mOS+pZRDkzw0yXXTPAHr5GUODACYLjNnAGBc/iLJuWn+DH9cKWVfkse26966\n",
867 "Ydu/b5c3b+8Pcw2llBukeRz2+u23c9M095a5Mldd9rMb32uXB8zx3nnNvM9a6ylJPparbgy8dkmT\n",
868 "WTMAQGfEGQAYkVrr5Une2X75hCSHpwkm30ry3g3bfi7JqWlCwwu3+MhfSjMz5Nw04Wcnl677+c22\n",
869 "2OYe27z/3Bm2WbS1fd68lDLLfXV+v10+K8ld09znZ2P4AgBYGJc1AcD4nJDm0qJ7Jvn19rU/be+Z\n",
870 "stF/SxNtnlRKuTjJb9Zazyql3CLNDX1/I80smBfWWi/bace11m+WUj6e5H5JfquU8vU0j87er33t\n",
871 "17L9PWdOapd3LKX8QpLj0zzV6d5JTuzohrsnJ7m8HeMrSin/Pc2Tl344yTdrrZ/fsP070jxi/Ibt\n",
872 "12+rtX6ng3EBACQxcwYARqfWelKSM9svH9IuT9hi2/cl+ZU0AebnkpxRSrk8yZdzVZj57VrrsbsY\n",
873 "wi8muSjJndJcAnRp+/VfJ3lYkvdv8973JvnH9uevTfKNNDNb/jzNU5L26hr3u6m1fi1XXYL1lDS/\n",
874 "9m+2Y7/fJttflKv+e7oRMADQOXEGAMZpfTz4apK/3GrDWuurkzwgzeVQX07y3TQ3/31PkiNqrb+8\n",
875 "xVuvzFWPx17/eSe3n/feNJdTfTfJGUl+L8ldkvzWNmP5bpKHp4klX2nf+9UkH06yNmvmGvvcaUwb\n",
876 "1m/mqDSXdn0hyWVJLkjyiSSnb7H92uufqrV+apv9AQDs2ZifpgAAsHDtE6k+n+QHkzyz1voHSx4S\n",
877 "ADBxZs4AAFzdI9OEmW9li8vFAAAWSZwBALi6o9rlCe39ZwAAOiXOAAC0SimHJPnxuBEwANAjcQYA\n",
878 "4CpHprkn39/XWv9h2YMBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n",
879 "AAAAAAAAAAAAAAAAAAAAAAAAAAAYnv8LTrFLgrpADnoAAAAASUVORK5CYII=\n"
880 ],
881 "text/plain": [
882 "<matplotlib.figure.Figure at 0x10ffc7c10>"
883 ]
884 },
885 "metadata": {
886 "image/png": {
887 "height": 407,
888 "width": 563
889 }
890 },
891 "output_type": "display_data"
892 }
893 ],
894 "source": [
895 "plt.figure()\n",
896 "plt.contourf(sigma_vals, strike_vals, prices['ecall'])\n",
897 "plt.axis('tight')\n",
898 "plt.colorbar()\n",
899 "plt.title('European Call')\n",
900 "plt.xlabel(\"Volatility\")\n",
901 "plt.ylabel(\"Strike Price\")"
902 ]
903 },
904 {
905 "cell_type": "markdown",
906 "metadata": {},
907 "source": [
908 "Plot the value of the Asian call in (volatility, strike) space."
909 ]
910 },
911 {
912 "cell_type": "code",
913 "execution_count": 22,
914 "metadata": {
915 "collapsed": false
916 },
917 "outputs": [
918 {
919 "data": {
920 "text/plain": [
921 "<matplotlib.text.Text at 0x11009b3d0>"
922 ]
923 },
924 "execution_count": 22,
925 "metadata": {},
926 "output_type": "execute_result"
927 },
928 {
929 "data": {
930 "image/png": [
931 "iVBORw0KGgoAAAANSUhEUgAABGUAAAMvCAYAAAB7jm3aAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\n",
932 "AAAWJQAAFiUBSVIk8AAAIABJREFUeJzs3XmULWdB7+/vOTGEBBJAiMgvTCYMXpBREMgFIiEEBBQE\n",
933 "XwYlBFRE+akgIHrxApeLUwAFlElkCINGeBUUEAhTQmQQQWQKRMUMEEwkgCEJBEJyzv2jqjk7nd7d\n",
934 "e6g91vOs1at676pd9e7OWrrOh7feSgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n",
935 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFguJybZ0/5s5dR231lzGg8AsIR2L3oAANBDP5Z9\n",
936 "/2Dfk+QLc7ru2QPXvPGcrrkI103yhCRvTvO3vTDJZUm+muTjSV6Z5GeSXHMOY9k75X4AAACgQ3+a\n",
937 "K0eZPUnuNofrntVe64qsZ5Q5IMkfJrkkV/37XrHFexcneWmSH5zBWE4cuO5WTm33nzmDawMAK+L7\n",
938 "Fj0AAOiZ/ZM8ov39v5Jcv/39uCQfmfG1fy7J1QeuvU6un+StSe7cvv5ukrcneV+Sc9MEkB9M8qNJ\n",
939 "7pfkJkmukeSXk7yr/SwAAACwxn4y+2ZQ/GKSL7avv5om2DC+qyX5cPbNgPlIkpvt8JmHJPl0mv8O\n",
940 "PzWDMZ0YM2UAAABgqbwpzT/GL01yrSTPy76Y8OAFjmuVPSf7/oYfzL7ZQDvZP8lzk9x/BmM6MaIM\n",
941 "AAAALI1rpYkxe5L8bfveHbMvKPz1iOe5XpL/leS0JF9Lc6vON5J8KsmfJ3lomtkjm52a7Z/4c+sk\n",
942 "v5vkbUnOSHJRksuTfDPJOWluB/qlIefe8JiB73NUmlulfzbN9z0vzYK7/5VmEd677vhNd3addpx7\n",
943 "2nHesINz3jfJi5K8P83iyN9M83e4KMnnk7whyQN3OMeJEWUAAABgafxC9gWLhw28f0b2zZ659g7n\n",
944 "ODbNrU47LWL7T1t89tRsHwL+9xbn2erc/5rkpkPO8ZiBzz07yb9v8fmNn+9m3/o6k3rcwPleMuW5\n",
945 "Nrw3o/0d/i7DZ+WcOPC5rZwaUQYAes9CvwAwP8e120ty5YVlT0ryrDRPD3pYklcM+fzN04SAA5J8\n",
946 "J8lfpJnN8fU0s2funOQBSQ7PZI973pvkK2luAfpYmpktX2mvd6M0M0ge0I7jTWke7T3MriTPaH8/\n",
947 "vR3rZ9PMsjk2TaDaL813fU+aGT+TuPfA76PONNrJniT/kWYm0qfTzOz5epq/6Q+nmYl0hzTrA/1u\n",
948 "kqd2dF0AAABgBm6cfTMsXr9p380H9v3DNud40cBx290+c78kb9zi/VOz/eyMg7Y554bB2TR32WL/\n",
949 "Ywb2n5/kkUPO8/SB4351hOsOszHL6IokB05xnkHX2GH/riQnt9e9MFsv0HxizJQBAHawe9EDAICe\n",
950 "+LmB30/atO/fk3yi/f3IDL816Jbtdk+axzgP864kDx9zfEnyrRGOqQO/32mHY38uV/2uG1418Pt2\n",
951 "M252cr12e1Ga27+68M0d9u9NsyZOkhyc5BYdXRcA6Bm3LwHAfGzcuvS1NLMsNjspzaK/u5I8Ks1t\n",
952 "MZt9ud3uTjMDZfOMm65cK81jou+SZvHfG6SJHweluZVpw3W2OcfeNGvGDPNfaRb9vVqSQ6cca5Jc\n",
953 "PMU5hrlJmidi3T7NbUs/kOS6adaR2Zgdsyvb/x0AAACABfrR7LtV52VDjjks+xaTPWPIMffKlRea\n",
954 "fXeaW3/ulCvHkmFOzfa3zByY5A+TfDtXXdR2q59nbnGOx2TfbTv33GE857XHvn+EsQ/ztfYc/z3F\n",
955 "OTa7UZK/yfDvvXnR362+54lx+xIAsAMzZQBg9jZmyexN8ldDjvlymgV275Hmdpgfy1WfoHRKkv+T\n",
956 "ZgHd/ZIc0/4kzT/+P5vkLUleneTcMce4f5pFhDfOt7c930fShIPz0qyfcu0krx3z3MN8p4NzXJBm\n",
957 "psohaWbyjHIL1nZumORD2fdo7cvb1/+S5vHYF6R5/Pi9kjxlymsBAAAAM7RfmgVvR5l5MvjzJ9uc\n",
958 "8xZpZrR8PE3Y2Dxz48I0tx9tdmqGz8547MDnv5BmbZut3HTguGlnypyd6WfKvHlgPPeZ4jwbXjtw\n",
959 "vpOzL85s9phs/z1PjJkyAMAOLPQLALN1bJq1SMb1iAyf0fpvSX47zW1L10qz9stvZN+Tmw5JExfG\n",
960 "uW5pt3uT/HSSD4853kV538DvP9PB+TbOcW6SB2X8GUcAACNz+xIAzNZxA78/Jc0aKMPsSjMD46g0\n",
961 "C+veL8nbdzj/t9PMmPl4mkdmPz/Jk9PEmqNy5aclbecm7fa8JJ8Z8TPL4M1pvvMBSY5P8gdpZuBM\n",
962 "4tDse6z2aWn+tgAAMyPKAMDsHJxmtkWSfDrJC0b4zH+niSlJE3QGo8y109yatJ0PpIkySXL90YaZ\n",
963 "ZN9tNtfY4birj3HOeTgvyZ+nWfD4amki1FEZbW2Z3WnW5/lYknfkyrcaHbzDZ5ft7wAArCC3LwHA\n",
964 "7Dwk+2ZejPr46ndk32yan0xzK9KGdyV5dpLv3+bzD2i3ezPejJePtduNx2Fv5aGZbv2XWXlGmlu6\n",
965 "kuZJVx9McqsdPnNMmgV8n5V9/yPV15Oc1f5+VLZeT2b/JP87yQvb17smGzIAgJkyADBLG7cuXZHk\n",
966 "L0f8zOVJ3pjkCWlmY/xMmqcppX39jCRPTRNoPpDki+35D0uzFszGYrcfbveP6k+TPDrNwsQnJXlF\n",
967 "kn9MM+Pk8HYc/3OM883TN5I8MM3CvD+U5PZpZia9M8l7su9v9ANJbpvmtrCbt5/du+lcL0xzG9gh\n",
968 "ST6a5MVJPp/m7/IjSR6V5IjZfRUAAABgWodl31ORTh7zs3fJvicAnTLw/l9ntCc3fSBbz6Y5Nds/\n",
969 "8eeX0kShYec9N8lvZbSnL+3JfJ6+NOhaSV6X5LsZ7e/09TS3lB266Tyv3eFzH00Tbrb7nifG05cA\n",
970 "AABgIZ6Wff8of9QEn//X9rOXJ7nRwPs/muT304SX89MsRvvNJP+RZobNg7c55yntObcLAXdL8tY0\n",
971 "t1BdluS/0txS9fNpFtO9SfZ9r62izPED+3eKMme1x3V9S9RNk/xOmpkyZyW5KMmlSb6c5J/SBJUH\n",
972 "pfk+wxyXZrbRxWn+xmenuQXtJ9r9O33P12T7KDPKfwsAAAAAAAAAAAAAWANr9cSAUsotk5ye5KRa\n",
973 "63EjHP+LaRYyfFyt9VXbHLd/kl9LswDizdNMJf9ckpfXWl/bxdgBAACA+RqnI5RSjkjy/6d5sMIN\n",
974 "k1wzyRdqrf9j0uuv/NOX2j/Kk5PcIMmxaR7zvflJCoPH3zfN0yluluTo9u2hx7c27tE/M83if/un\n",
975 "ecrDa0opt661Pm2a7wAAAADMx7gdof3Mryb5ozSTNE5Jsybe1XLltf/GtvJRJs0f4Feyc1jZcNc0\n",
976 "T5cY6fhSykPTBJnTkhxba72sff/aaZ6+8JRSyhtqrZ8ed+AAAADA3I3VEUopP5fkT5K8K8nxtdYL\n",
977 "uhrI7q5OtCi11lNrrbtrrftl38yX7Y5/9sDxzx7hEse322dvBJn2PBcmOSHNLWDHb/VBAAAAYLmM\n",
978 "0xFKKQcn+dMkn03yoC6DTLIeM2UGjbtGzijH3y1NPfvHLfZ9uN0eOeZ1AQAAgMXbqQs8Ism1kzwl\n",
979 "yS3aJVGul+RrST5Za33fNBdftyjTqbaIXTfJJbXWS7c45Mvt9vD5jQoAAACYkx9vt7+e5Habd5ZS\n",
980 "Tkvy0Frr1yY5+crfvjRjB7fbi4bs/1a7PWQOYwEAAADm6xbt9t+THJPkB5JcPcldkpya5J5J/nLS\n",
981 "k5spM5rLh7w/8SPF3/ve9466MDEAAAAr6Jhjjpn434zLbBn/PTvDv/W10ixp8tRa6xcH3v9YKeUn\n",
982 "0zyl+T6llFvWWv913JObKbO9i9vtgUP2H7TpOAAAAGB9bDzw56DNO2qt30zywfblrSc5uZky26i1\n",
983 "XlxK+XqS7y+lXKP9gw86rN2eOek1bnfXu088PoCunXvB5v8zBzB7Z53vf98Clt9551w48rG3vN5X\n",
984 "ZziS5fHGB0x8105nHv73PzvrS3w5ya2S3DTJGVvsn2qyi5kyO/twkv2SHLXFvo2istWTmQBWiiAD\n",
985 "LIIgA6yCcYIMa+e0dvvAzTtKKbuS3CbN7U2fmeTkoszOXt9un1pK2X/jzVLKtZP8Zpo//usWMTCA\n",
986 "rggywCIIMsCyO++cCwUZXp3k20keV0q536Z9T0rzNOZTaq3/PsnJV/72pVLKDdM8NzxJjmi3tyql\n",
987 "PLX9/TO11pMHjj8yyZHty43tfUsp39/+/o5a6+c2jq+11lLKcWmq2GdLKe9Psn+S+yf5wSQvqrV+\n",
988 "ouvvBTAPYgywKIIMsMyEmPU2TkeotZ5XSvnlNHHm70sp705ydprHY981zXImj550LCsfZZLcLMlz\n",
989 "B17vTXKHJHdsX5+Y5OSB/fdJ8qyBY/cmKe3P3iRfSfK5XNlDkzwxyXFp/thXJDk9ydNrrSd28zUA\n",
990 "5kuQARZBjAGWmRjTG2N1hFrr60opX0jytDTLmByd5IvtOf6g1vqNSQeylo/nWgUbjxCz0C+wCIIM\n",
991 "sAiCDLCsuo4xGwv9rvsjsZdpod9V/VtbUwagZwQZYBEEGWBZmR3DIq3D7UsAjEiQARZBkAGWkRjD\n",
992 "MhBlAHpCkAHmTYwBlpEYwzIRZQB6QJAB5k2QAZaNGMMyEmUA1pgYAyyCIAMsG0GGZSXKAKwpQQZY\n",
993 "BEEGWCZiDMtOlAFYQ4IMMG9iDLBMxBhWhSgDsGYEGWDeBBlgWYgxrJrdix4AAN0RZIB5E2SAZSHI\n",
994 "sIrMlAFYE4IMMG+CDLAMxBhWmSgDsAYEGWCexBhgGYgxrANRBmCFiTHAvAkywKKJMawTa8oArChB\n",
995 "Bpg3QQZYNEGGdWOmDMAKEmSAeRNkgEUSY1hXogzAihFkgHkSY4BFEmNYd6IMwAoRZIB5EmSARRFj\n",
996 "6AtRBmBFCDLAPAkywCKIMfSNhX4BVoAgA8yTIAMsgiBDH5kpA7DExBhgnsQYYBHEGPpMlAFYUoIM\n",
997 "ME+CDDBvYgyIMgBLSZAB5kmQAeZJjIF9rCkDsGQEGWCeBBlgngQZuDIzZQCWiCADzIsYA8yTGANb\n",
998 "E2UAloQgA8yLIAPMixgD2xNlAJaAIAPMiyADzIMYA6MRZQAWSIwB5kmQAeZBkIHRiTIACyLIAPMi\n",
999 "xgDzIMbA+EQZgAUQZIB5EWSAWRNjYHKiDMCcCTLAvAgywCyJMTC93YseAECfCDLAvAgywCwJMtAN\n",
1000 "M2UA5kSQAeZBjAFmSYyBbokyAHMgyADzIMgAsyLGwGyIMgAzJMYA8yLIALMgxsBsiTIAMyLIAPMi\n",
1001 "yABdE2NgPkQZgBkQZIB5EGOAWRBkYH5EGYCOCTLAPAgyQNfEGJg/UQagQ4IMMA+CDNAlMQYWR5QB\n",
1002 "6IggA8yDIAN0RYyBxRNlADogyACzJsYAXRJkYDmIMgBTEGOAeRBkgK6IMbBcRBmACQkywDwIMkAX\n",
1003 "xBhYTqIMwAQEGWAeBBlgWmIMLDdRBmBMggwwa2IM0AVBBpafKAMwBkEGmDVBBpiWGAOrQ5QBGJEg\n",
1004 "A8yaIANMQ4yB1SPKAIxAkAFmTZABJiXGwOoSZQC2IcYAsybGANMQZGC1iTIAQwgywKwJMsCkxBhY\n",
1005 "D6IMwBYEGWDWBBlgEmIMrBdRBmATQQaYNUEGGJcYA+tJlAEYIMgAsyTGAOMSY2C97V70AACWhSAD\n",
1006 "zJIgA4xLkIH1Z6YMQAQZYLYEGWAcYgz0hygD9J4gA8ySIAOMSoyB/hFlgN4SY4BZEmOAUYkx0F/W\n",
1007 "lAF6SZABZkmQAUYlyEC/mSkD9I4gA8ySIAOMQowBElEG6BlBBpglQQbYiRgDDBJlgN4QZIBZEWOA\n",
1008 "nYgxwFasKQP0giADzIogA+xEkAGGMVMGWGtiDDBLggywHTEG2IkoA6wtQQaYJUEGGEaMAUYlygBr\n",
1009 "SZABZkWMAYYRY4BxWVMGWDuCDDArggwwjCADTMJMGWCtCDLArAgywFbEGGAaogywNgQZYBbEGGAr\n",
1010 "YgzQBVEGWHliDDArggywmRgDdEmUAVaaIAPMiiADDBJjgFmw0C+wsgQZYFYEGWCQIAPMipkywEoS\n",
1011 "ZIBZEGOAQWIMMGuiDLByBBlgFgQZYIMYA8yLKAOsFEEGmAVBBkjEGOijUsotk5ye5KRa63EjfuaQ\n",
1012 "JKcluW2Sx9VaXzXp9UUZYCWIMcCsCDJAIshAn5RSjkjy5CQ3SHJsmvV294742QOS/G2aIJNRPzeM\n",
1013 "KAMsPUEGmAUxBkjEGOipGyX5lYwZVEopu5O8PsmRSd6f5OhpByLKAEtNkAFmQZABxBjor1rrqWmf\n",
1014 "Rl1KOSrJKSN+9IVJHprkEUlulQ6ijEdiA0tLkAFmQZCBfjvvnAsFGWDQrlEOKqX8ryS/muQ3aq11\n",
1015 "1M/tRJQBlpIgA8yCIAP9JsYAkyilPCbJ7yV5bq31T7o8t9uXgKUjyABdE2Og38QYYFKllPsneUWS\n",
1016 "19daf7vr84sywNIQY4BZEGSgv8QYYBqllDskeVOS9yX5+VlcQ5QBloIgA8yCIAP9JMYAHTkqyYFJ\n",
1017 "zk5yQillcN+R7fZhpZRbJflArfVt415AlAEWTpABZkGQgX4SZGB+bnLTWy16CLO2N82Cvo/f5phj\n",
1018 "k9wnzZq9ogywWgQZoGtiDPSTGAN0rdb6oiQv2mpfKeVZSZ6V5Bdrra+e9BqiDLAwggzQNUEG+keM\n",
1019 "ARakk0diizLAQggyQNcEGegXMQaYVCnlhkke0b48ot3eqpTy1Pb3z9RaT57HWEQZYK7EGGAWBBno\n",
1020 "DzEG6MDNkjx34PXeJHdIcsf29YlJdooye9ufqYgywNwIMkDXxBjoF0EG6EKt9dQ0C/NOc45nJ3n2\n",
1021 "tGMRZYC5EGSArgky0B9iDLCuRBlg5gQZoGuCDPSDGAOsO1EGmClBBuiaIAPrT4wB+kKUAWZGkAG6\n",
1022 "JMZAPwgyQJ+IMkDnxBiga4IMrD8xBugjUQbolCADdE2QgfUmxgB9JsoAnRFkgK4JMrC+xBgAUQbo\n",
1023 "iCADdEmMgfUmyAA0RBlgaoIM0CVBBtaXGANwZaIMMBVBBuiSIAPrSYwB2JooA0xEjAG6JsjA+hFj\n",
1024 "ALYnygBjE2SALokxsJ4EGYCdiTLAWAQZoEuCDKwfMQZgdKIMMDJBBuiSIAPrRYwBGJ8oA4xEkAG6\n",
1025 "JMjA+hBjACYnygA7EmSArogxsD7EGIDpiTLAUGIM0CVBBtaHIAPQDVEG2JIgA3RJkIH1IMYAdEuU\n",
1026 "Aa5CkAG6JMjA6hNjAGZDlAGuRJABuiTIwGoTYwBma/eiBwAsD0EG6JIgA6tNkAGYPTNlgCSCDNAd\n",
1027 "MQZWmxgDMD+iDPScGAN0SZCB1SXGAMyfKAM9JsgAXRJkYDWJMQCLY00Z6ClBBuiSIAOrSZABWCwz\n",
1028 "ZaCHBBmgS4IMrB4xBmA5iDLQM4IM0CVBBlaLGAOwXEQZ6BFBBuiSIAOrQ4wBWE6iDPSAGAN0SYyB\n",
1029 "1SLIACwvUQbWnCADdEmQgdUhxgAsP09fgjUmyABdEmRgdQgyAKtBlIE1JcgAXRJkYHUIMgCrw+1L\n",
1030 "sIYEGaBLggysBjEGYPWYKQNrRpABuiTIwGoQZABWk5kysCbEGKBrggysBkEGYHWJMrAGBBmgS2IM\n",
1031 "rAYxBmD1uX0JVpwgA3RJkIHVIMgArAczZWCFCTJAlwQZWH5iDMB6MVMGVpQgA3RJkIHlJ8gArB9R\n",
1032 "BlaQIAN0SZCB5SfIAKwnty/BChFjgK4JMrDcxBiA9WamDKwIQQbomiADy02QAVh/ZsrAChBkgC6J\n",
1033 "MbD8BBmAfhBlYMkJMkCXBBlYbmIMQL+4fQmWmCADdEmQgeUmyAD0j5kysITEGKBLYgwsP0EGoJ9E\n",
1034 "GVgyggzQJUEGlpsYA9BvogwsCTEG6JIYA8tPkAFAlIEFE2OArgkysNzEGAA2iDKwQIIM0CUxBpaf\n",
1035 "IAPAIFEGFkCMAbokxsDyE2MA2IpHYsOcCTJAlwQZWH6CDADDmCkDcyLGAF0SY2A1CDIAbEeUgTkQ\n",
1036 "ZICuiDGwGsQYAEYhysAMiTFAlwQZWA2CDACjEmVgRgQZoCtiDKwGMQaAcYky0DExBuiKGAOrQ5AB\n",
1037 "YBKiDHREjAG6JMjAahBjAJiGKAMdEGSArogxsDoEGQCmJcrAFMQYoEuCDKwOQQaALogyMCFBBuiK\n",
1038 "GAOrQ4wBoEuiDIxJjAG6IsbAahFkAOiaKANjEGSArggysDrEGABmRZSBEYgxQFfEGFgtggwAsyTK\n",
1039 "wDbEGKArYgysFjEGgHkQZWAIQQboiiADq0WQASZ16RkXNL/cfddiB8LKEGVgEzEG6IoYA6tFjAGm\n",
1040 "8b0gA2MQZWCAIAN0QYyB1SPIAJMSY5iGKAMRY4DuCDKwegQZYFKCDNMSZeg9QQboghgDq0eMASYl\n",
1041 "xtAVUYbeEmOALogxsJoEGWASYgxdW7soU0q5ZZLTk5xUaz1um+MelORJSW6f5IAk5yR5Y5ITaq2X\n",
1042 "bnH8nh0u/dFa690mHjhzI8YAXRFkYPWIMcCkBJn1tFNDKKUcnOQXkhyT5HZJfiDJZUn+PclfJXlR\n",
1043 "rfU7k15/LaJMKeWIJE9OcoMkxybZnWTvNsc/MckLklyY5K1JLkpyVJJnJrl3KeXoWut3t/joxUn+\n",
1044 "bMhpz5n4CzA3ggzQBTEGVpMgA0xCjFk/YzaEuyT54yTfSHJakrOTXDvJTyT5wyQ/VUr58Vrr5ZOM\n",
1045 "ZS2iTJIbJfmVbBNiNpRSDkvzh7sgyZ1qrV9q39+V5KQkD0vy+CQv3uLj36i1Pq2rQTM/YgzQFUEG\n",
1046 "Vo8YA0xCjFlrIzeEJF9N8ktJXldrvWzjzVLKNZN8KMmRSR6d5NWTDGQtokyt9dQ0ZSullKOSnLLN\n",
1047 "4Q9Pc7vSyzeCTHuOvaWUp6eJMo/N1lGGFSTIAF0QY2A1CTLAJASZ9TZOQ6i1fjLJJ7d4/5JSymvS\n",
1048 "zKL50UwYZXZP8qElt2uH/Rvrvnxk845a65lJvpLkdqWUq3c9MObr3Au+KcgAUzvr/IsFGVhRggww\n",
1049 "rkvPuECQ6Z+dGsJ2Dmq3X5v0BGsxU2ZMh7fbrwzZ/+Ukhyb5oSSf37TvsFLKd9L83S5Js7DPW9Is\n",
1050 "7HPJDMbKhMQYoAtiDKwmMQYYlxDDuNolUEr78rRJz9PHKHNwmvvGLhqy/1tpStkhm97/lyT/mqaA\n",
1051 "7U5ykyT3TnLHJD9bSjmy1vqNmYyYkYkxQBfEGFhdggwwLkGGCT0pzdOYPlRrfe+kJ+ljlNkwbGXk\n",
1052 "Lacu1Vp/dPN7pZRDk5yc5rHa/yvJb3c2OsYmyADTEmNgdYkxwLjEGCZVSnlEkuenudPm4dOcax3X\n",
1053 "lNnJxWnCy4FD9h80cNy2aq0XpKljSXL09ENjEtaOAbogyMDqEmSAcQkyTKqUcnySNyT5zyT3qrX+\n",
1054 "5zTn6+NMmbOS3CHN7Ueb14xJksOS7GmPG8XX2+01px8a4xBigC6IMbC6xBhgXGJMt65228MWPYS5\n",
1055 "KqU8M8n/SXJ6kvsPPtF5Un2MMh9O8pA0M1veNbijlHLzNIv8frrWeumI57tDu90q8DAjggwwLTEG\n",
1056 "VpsgA4xDjGEapZQDkvx5kkcleV+Sh9Zah61TO5Y+3r70piSXJTm+lPK9rFdK2Z3kOe3L1w5+oJTy\n",
1057 "+FLKPTefqJRywyS/l2bh4FfObMR8j1uVgC4IMrC6zjvnQkEGGIsgwzTabnBamiDzp0nu11WQSdZk\n",
1058 "pkwbRx7Rvjyi3d6qlPLU9vfP1FpPTpJa67mllN9J8rwknyqlvD3N463vkeQ2ST6a5CWbLnHXJC8r\n",
1059 "pZyd5CNpnsB04yTHpFmb5k9qre+cxXdjHzEGmJYYA6tNjAHGIcYwzDgNIc3kjTsn+UKaCR4nlFKy\n",
1060 "hZfWWs8cdyxrEWWS3CzJcwde701zW9Ed29cnpnlKUpKk1vpHpZQzkzwxyYOTHJBmDZnnJDmh1nrZ\n",
1061 "pvO/JMmlaf5D3CvJddM8Uvsf0vzh39bx92GAGANMS4yB1SfIAKMSYxjBOA1hV7v/iCRPGXK+vUne\n",
1062 "mmTsKLPl45+Zvfe+9717k+R2d737ooey1AQZYFqCDKw2MQYYx7IEmdvfvfmn9jHHHLOW/+be+Pfs\n",
1063 "J/5s8X/vOz7+0CSr+7del5kyrBkxBpiWGAOrT5ABRrUsMQbGJcqwVMQYoAuCDKw2MQYYlRjDqhNl\n",
1064 "WBqCDDAtMQZWnyADjEqQYR2IMiycGANMS4yB1SfGAKMSY1gnuxc9APpNkAGmJcjA6hNkgFEJMqwb\n",
1065 "M2VYCDEGmJYYA6tPjAFGJcawrkQZ5k6QAaYhxsB6EGSAUYgxrDtRhrkRY4BpCTKwHgQZYBSCDH0g\n",
1066 "yjBzYgwwLTEG1oMYA4xCjKFPRBlmSpABpiHGwPoQZICdiDH0kSjDTIgxwLQEGVgPYgwwCkGGvhJl\n",
1067 "6JwgA0xDjIH1IcgAOxFj6DtRhs6IMcA0xBhYH2IMsBMxBhq7Fz0A1oMgA0xDkIH1IcgAOxFkYB8z\n",
1068 "ZZiKGANMQ4yB9SLIANsRY+CqRBkmJsgA0xBkYH2IMcBOBBnYmijD2MQYYBpiDKwXQQbYjhgD2xNl\n",
1069 "GJkYA0xDjIH1IsYA2xFjYDQW+mUkggwwDUEG1osgA2xHkIHRmSnDtsQYYBpiDKwXMQbYjhgD4xNl\n",
1070 "GEqQASYlxsD6EWSAYcQYmJwow1WIMcA0BBlYL2IMsB1BBqYjynAlggwwKTEG1o8gAwwjxkA3RBmS\n",
1071 "iDHA5MQYWE+CDLAVMQa6Jcr0nBgDTEOQgfUjxgDDCDLQPVGmxwQZYFJiDKwnQQbYihgDsyPK9JAY\n",
1072 "A0xKjIH1JMYAwwgyMFuiTM8IMsCkBBlYT4IMsBUxBuZDlOkJMQaYlBgD60mMAbYixsB87V70AJg9\n",
1073 "QQaYlCAD60mQAbYiyMD8mSmzxsQYYFJiDKwvQQbYTIyBxRFl1pAYA0xKjIH1JcYAm4kxsHhuX1oz\n",
1074 "ggwwKUEG1pcgA2wmyMByMFNmTYgxwKTEGFhfYgywmRgDy0WUWQOCDDAJMQbWmyADDBJjYDmJMitM\n",
1075 "jAEmJcjA+hJjgM0EGVheosyKEmSASYgxsN4EGWCQGAPLT5RZMWIMMAkxBtabGANsJsjAahBlVogg\n",
1076 "A0xCkIF+1cbcAAAgAElEQVT1JsgAg8QYWC2izAoQY4BJiDGw/gQZYIMYA6tJlFliYgwwCTEG1p8Y\n",
1077 "AwwSZGB1iTJLSpABJiHIwPoTZIANYgysPlFmyYgxwCTEGFh/YgywQYyB9bF70QNgH0EGmIQgA+tP\n",
1078 "kAE2CDKwXsyUWQJiDDAJMQbWnxgDbBBjYD2JMgsmyADjEmOgHwQZIBFjYN25fQlghQgy0A+CDJAI\n",
1079 "MtAHZsoArAAxBvpBjAESMQb6RJQBWGJiDPSHIAMkggz0jSgDsKQEGegHMQZIxBjoK1EGYMmIMdAf\n",
1080 "ggwgxkC/iTIAS0KMgf4QY4BEkAFEGYClIMhAfwgygBgDbBBlABZIjIH+EGMAMQbYTJQBWAAxBvpF\n",
1081 "kAEEGWArogzAnAky0C+CDPSbGANsR5QBmBMxBvpFjIF+E2OAUexe9AAA+kCQgX4RZKDfBBlgVGbK\n",
1082 "AMyQGAP9IsZAv4kxwLhEGYAZEGOgfwQZ6DdBBpiEKAPQMUEG+kWMgX4TY4BpiDIAHRFjoH8EGegv\n",
1083 "MQbogigDMCUxBvpJkIH+EmSArogyAFMQZKB/xBjoLzEG6JooAzABMQb6SZCBfhJjgFkRZQDGIMZA\n",
1084 "P4kx0F+CDDBLogzAiAQZ6CdBBvpJjAHmQZQB2IEYA/0kxkA/iTHAPIkyAEOIMdBfggz0kyADzJso\n",
1085 "A7AFQQb6SYyBfhJjgEURZQAGiDHQX4IM9JMgAyySKAPQEmSgvwQZ6B8xBlgGogzQe2IM9JcYA/0j\n",
1086 "xgCDSim3THJ6kpNqrcdtc9yDkjwpye2THJDknCRvTHJCrfXSSa8vygC9JcZAvwky0D+CDJAkpZQj\n",
1087 "kjw5yQ2SHJtkd5K92xz/xCQvSHJhkrcmuSjJUUmemeTepZSja63fnWQsogzQS4IM9JcYA/0jxgCb\n",
1088 "3CjJr2SbELOhlHJYkj9MckGSO9Vav9S+vyvJSUkeluTxSV48yUB2T/IhgFV11vkXCzLQY4IM9Mul\n",
1089 "Z1wgyABXUWs9tda6u9a6X5Kjdzj84WluV3r5RpBpz7E3ydPbl4+ddCyiDNALYgz023nnXCjIQM+I\n",
1090 "McCIdu2w/27t9iObd9Raz0zylSS3K6VcfZKLu30JWHtiDPSbGAP9IsYAHTu83X5lyP4vJzk0yQ8l\n",
1091 "+fy4JxdlgLUlxgCCDPSHGAPMyMFp1p65aMj+b6WZbXPIJCcXZYC1JMhAv4kx0C+CDDAHlw95f6fb\n",
1092 "n7YlygBrRYwBBBnoDzEGFmv3ra+36CHMw8VpwsuBQ/YfNHDc2Cz0C6wNQQb6zWK+0C+CDDAnZ7Xb\n",
1093 "mwzZf1iSPQPHjUWUAVaeJysBYgz0h8dcA3P24XZ7lUdnl1JunmaR38/WWi+d5OSiDLCyxBjA7Bjo\n",
1094 "DzEGWJA3JbksyfGllMM23iyl7E7ynPblayc9uTVlgJUkxgBiDPSHGAN0qZRywySPaF8e0W5vVUp5\n",
1095 "avv7Z2qtJydJrfXcUsrvJHlekk+VUt6e5JIk90hymyQfTfKSScciygArRYwBxBjoDzEGmJGbJXnu\n",
1096 "wOu9Se6Q5I7t6xOTnLyxs9b6R6WUM5M8McmDkxyQZg2Z5yQ5odZ62aQDEWWAlSHIAIIM9IMYA8xS\n",
1097 "rfXUjLmcS631LUne0vVYRBlg6YkxQCLIQF8IMkCfiDLAUhNkADEG+kGMAfpIlAGWkhgDJIIM9IEY\n",
1098 "A/SZKAMsFTEGSMQY6AtBBug7UQZYGoIMkAgy0AdiDEBDlAEWTowBEjEG+kKQAdhHlAEWSpABEkEG\n",
1099 "+kCMAbgqUQZYCDEG2CDIwHoTYwCGE2WAuRNkgESMgT4QZAC2J8oAcyPGABsEGVhvYgzAaEQZYObE\n",
1100 "GGCDGAPrTYwBGM9Mokwp5eAkd05yaJIDaq2vG9h3vSQHJbm81vqfs7g+sHhCDDBIjIH1JsYATKbT\n",
1101 "KFNKOSTJHyU5Lsn+SXYl2ZvkdQOH3TXJW5NcUUq5ca31vC7HACyWGAMMEmNg/QkyAJPb3dWJSilX\n",
1102 "T/L+JL/Qnvff0gSZK6m1vj3JKUn2S/LIrq4PLNZZ518syADfc945FwoysOYuPeMCQQZgSp1FmSS/\n",
1103 "luSOaWLMj9Ra/0eS7w459pXt9ic7vD4wZxshRowBNogxsP7EGIDudHn70sPa7ZNrrf+2w7Hvb7e3\n",
1104 "7vD6wJyIMMBmQgysPyEGoHtdRpkfTnO70odGOPYr7bHX6vD6wAwJMcBmQgz0hyADMBtdRpnvSxNa\n",
1105 "Lhnh2GumWQT4mx1eH5gBMQbYTIyB/hBjAGaryyjzpSRHtD873b5073b7hQ6vD3RIjAE2E2OgP8QY\n",
1106 "gPnoMsq8K8mvJnlCkicNO6iUco0kv9u+fHeH1wemJMQAWxFjoD/EGID56jLKPD/JLyZ5QinlzCQv\n",
1107 "GdxZStmV5F5J/jjJrdLcuvSSzScB5k+MAbYixkC/CDIA89fZI7FrrV9M8sg068q8MMn5SfZPsquU\n",
1108 "8i9JvprkPUlum+TyJI+ptZ7X1fWB8XmcNbAVj7WGfvGIa4DF6SzKJEmt9e+S3C3JB5NcN81ivkly\n",
1109 "uyTXaV9/Kskxtda/6fLawGg2QowYA2wmxkC/iDEAi9fl7UtJklrrJ5Lcs5RyeJIjk9wgyX5pHoP9\n",
1110 "sVrrZ7q+JrAzEQYYRoiBfhFiAJZH51FmQ631zCRnzur8wGjEGGArQgz0kyADsFw6izKllP2SvDTN\n",
1111 "OjJ/W2t965Dj7p+kJPl2kifUWvd2NQagIcQAw4gx0E9iDMBy6nKmzE8leVyS85I8cZvjTkvyijS3\n",
1112 "Nb0zyZbxBhifGAMMI8ZAP4kxAMuty4V+j2u3L6y1Dv2XYa31kjSPxd6V5DEdXh96y8K9wDAW74V+\n",
1113 "sogvwGrocqbM3dI8DvuvRzj2zUmen+SuHV4fekWEAbYjxEB/iTEAq6PLKHPdJHtqrWeNcOwX0wSc\n",
1114 "63Z4fegFMQbYjhgD/SXGAKyeLm9f+kaS3aWUQ0Y49pppbl+6qMPrw1pzixKwHbcpQX+5VQlgdXU5\n",
1115 "U+YTSe6T5slKr9rh2Ie02892eH1YOyIMsBMhBvpLiAFYfV3OlHldu31eKeVuww4qpfxYmvVkkuSN\n",
1116 "HV4f1oZZMcBOzIyBfhNkANZDlzNlTkry2CRHJ/lAKeVtSd6b5Nw068fcKMkxaR6dvV+STyV5dYfX\n",
1117 "h5UmwgA7EWEAMQZgvXQWZWqte0opD03yF0nun+Sn25+t/FOSh9RaL+vq+rCqxBhgJ2IMIMYArKcu\n",
1118 "Z8qk1vqNJA8spdw/yaPTPPL6+u3ur6aJMW9qDq17urw2rBoxBtiJGAOIMQDrrdMos6HW+o4k75jF\n",
1119 "uWGVCTHAKMQYQIwB6IeZRBngysQYYBRiDJAIMgB9IsrADIkxwCjEGCARYwD6aOIoU0o5Jcl3aq33\n",
1120 "a1+/Js1TlsZSa/35SccAy0iIAUYlxgCJGAPQZ9PMlDkqybcHXh8/wTn2JhFlWAtiDDAqMQZIxBgA\n",
1121 "posypyX5zsDrv5zgHGPPrIFlI8YAoxBigEGCDADJFFGm1vrjm14/aurRwIoQYoBRiTHAIDEGgEGd\n",
1122 "LfRbSrlvku+rtf59V+eEZSPGAKMSY4BBYgwAW+ny6UtvabcHdXhOWApiDDAqMQYYJMYAsJ0uo8x+\n",
1123 "Sa7o8HywUEIMMA4xBthMkAFgJ7s7PNfZSQ4opRzY4Tlh7s46/2JBBhjZeedcKMgAV3LpGRcIMgCM\n",
1124 "pMuZMm9N8pQkxyR5W4fnhbkQYoBxCDHAZkIMAOPqMsq8KMkTkjw9ogwrQogBxiXGAJuJMQBMqsso\n",
1125 "84Ak/5zk7qWUlyb55CgfqrW+osMxwEjEGGBcYgywmRgDwLS6jDIvG/j9l0f8zN4kogxzIcQA4xJi\n",
1126 "gGEEGQC60GWU+eIEn9nb4fVhS2IMMC4xBhhGjAGgS51FmVrrTbs6F3RBjAHGJcYAw4gxAMxClzNl\n",
1127 "YOGEGGASYgwwjBgDwCx1EmVKKVdLcrMk10zypVrreV2cF0YlxgCTEGOA7QgyAMzaVFGmlLJfkmck\n",
1128 "+fUk1xp4/+NJfqvWeupUo4MdiDHAJMQYYDtiDADzsnvKz78iyTOTXDvJroGfOyd5TynlkVOeH67i\n",
1129 "rPMv/t4PwDjOO+dCQQYY6tIzLhBkAJiriaNMKeVeSR7bvnx9knsk+ZEkJcmHk+yX5JWllMOmHSQk\n",
1130 "EWKAiYkxwHbEGAAWZZrbl36+3b6x1nr8wPufK6X8XZL3pQk1v57kt6a4Dj0nxACTEGGAUYgxACzS\n",
1131 "NLcv3aXdvnDzjlrr5Ul+t3157ymuQU+5RQmYlFkxwCjMjgFgGUwzU+awJHuT/POQ/f/Ubn9oimvQ\n",
1132 "MyIMMCkhBhiFEAPAMplmpsyBSS5rZ8VcRa31G0n2JDlkimvQE2bFAJMyMwYYhZkxACyjqR6JnWam\n",
1133 "zHYuT7L/lNdgTYkwwDSEGGAUQgwAy2zaKLOrlHKLYfvan2xzTGqt/zblGFgxYgwwDTEGGJUgA8zb\n",
1134 "ntO/2vxy90MXOxBWxrRR5oAkn99m/652u9Uxu9LMtNlvyjGwIsQYYBpiDDAqMQZYhO8FGRjDtFEm\n",
1135 "2RdeJjlmlM+ywoQYYFpiDDAqMQZYBDGGaUwTZQ7vbBSsHTEGmJYYA4xKjAEWQYyhCxNHmVrr2R2O\n",
1136 "gzUgxADTEmKAcQkywLyJMXSpi9uX6DkxBpiWGAOMS4wBFkGQoWuiDBMTY4BpiTHAuMQYYBHEGGZF\n",
1137 "lGEsQgzQBTEGGJcYAyyCGMOsrVWUKaXcMsnpSU6qtR63zXEPSvKkJLdP81jvc5K8MckJtdZLtzh+\n",
1138 "/yS/luTRSW6e5PIkn0vy8lrra7v+HstIjAG6IMYAkxBkgHkTY/qhlPLAJL+a5M5JDkpybpKPJ3lu\n",
1139 "rfVf5jGG3fO4yCyVUo4opbyklPLmJP+c5jvt3eb4JyZ5S5LbJXlrklcl+W6SZyZ5dxtgNntjkucn\n",
1140 "uWaS1yZ5U5KbJnlNKeW53X2b5XPW+RcLMsDUzjvnQkEGGNulZ1wgyABzJ8j0Qynl99M0gR9L8q4k\n",
1141 "f57kC0lKko+XUo6fxzjWYabMjZL8SrYJMRtKKYcl+cMkFyS5U631S+37u5KclORhSR6f5MUDn3lo\n",
1142 "kgcnOS3JsbXWy9r3r53ko0meUkp5Q631011+qUUSYYCuCDHAJIQYYBHEmP4opdw6yW8n+bckd621\n",
1143 "Xjiw725JTk3ywlLKX9ZavzvLsaz8TJla66m11t211v2SHL3D4Q9Pc7vSyzeCTHuOvUme3r587KbP\n",
1144 "bNSxZ28EmfYzFyY5IcmugWNWmlkxQFfMjAEmYWYMsAh7Tv+qINM/t2m37xwMMklSa/1Iks8mOSTJ\n",
1145 "dWc9kJWPMpvs2mH/3drtRzbvqLWemeQrSW5XSjlw02f2JvnHLc734XZ75JjjXCpiDNCFjRAjxgDj\n",
1146 "EmOARRBjeu1z7fanSinXH9zRLmlyoyTn1FrPn/VA1uH2pXEc3m6/MmT/l5Mcmma9mM+XUg5OU8Yu\n",
1147 "2WoB4Pb4wfOuDBEG6IoIA0xDjAEWQYzpt1rrp0spz0/y1CSfK6W8OMkbkpyd5CVJDk7ys/MYS9+i\n",
1148 "zMFpZr1cNGT/t9LMtjlk4PjscHwGjl96YgzQFTEGmIYYAyyCGMOGWuvTSimXpVnK5Bntz4VJ9k9y\n",
1149 "dHsb08x1HmVKKYcn+aU0t/1cP8nVaq2HD+x/cJIHJflOkifUWvd0PYYRXD7k/WG3P417/NIRY4Cu\n",
1150 "iDHANMQYYBHEGDYrpZyQ5MlJfiHJO9M84OfhSY5K8vellMfXWuusx9FplGkfGfXyNIvpbtj8VKRT\n",
1151 "krw6ybWS/E2S93Q5hh1cnCakHDhk/0EDxw1uRz1+qQgxQJfEGGAaYgywKILM7Bz4w4cueghJxv/v\n",
1152 "W0p5WJLfTPIntdbXtG+/PMnLSylHpmkVJ5VSzq61fqyzoW6hs4V+Syl3SvLKNEHmDUkemS1mmNRa\n",
1153 "v5HkZWniyCO6uv6Izmq3Nxmy/7AkezaOq7VenOTrSb6/lHKNIccnyZldDnJaFu4FumTxXmBaggyw\n",
1154 "CBbyZRul3b57845a64eTvCBNLymb93ety6cvPSXJfkleUGt9dK31jWkCx1b+pt3+zw6vP4qNpyVd\n",
1155 "5dHZpZSbp1nk97ObFvX9cJrvddQW57t7u93qyUxzJ8YAXRJjgGl5qhKwCGIMI9i4u+fGQ/Zv3FW0\n",
1156 "36wH0mWUuWeaW5VeMsKxG4+fulGH1x/Fm5JcluT4UsrGLJeUUnYneU778rWbPvP6dvvU9tFYG5+5\n",
1157 "dprpTnuTvG5mI97BRogRY4CuiDHAtMQYYBHEGMbwznb7jFLKEYM7Sik3SvLLaf6t/7ezHkiXa8oc\n",
1158 "mmbQZ49w7GXtsVMvlFtKuWH23Qa18ce8VSnlqe3vn6m1npwktdZzSym/k+R5ST5VSnl7kkuS3CPJ\n",
1159 "bZJ8NJuiUq21llKOS/LAJJ8tpbw/zWrM90/yg0leVGv9xLTfY1wiDNAlEQboghADLIoYw5hekeQB\n",
1160 "af5df3op5eQkX0ry/yX5iSRXS/J/a63/MOuBdBllLkpynfbnazsce7M0QaaL/899syTPHXi9N8kd\n",
1161 "ktyxfX1ikpM3dtZa/6iUcmaSJ6ZZXfmANGvIPCfJCbXWy7a4xkPb449L8ugkVyQ5PcnTa60ndvAd\n",
1162 "RiLEAF0TY4CuCDLAIogxTKLWekUp5SeTPDbNv/HvmeSaaVYNfkeaBYA/MI+xdBll/iXJvdOss/J3\n",
1163 "Oxz7uHb7T9NetNZ6asa8DavW+pYkbxnj+O8meX77M3diDNA1MQboihgDLIIYw7RqrXvTPBn61Ysc\n",
1164 "R5drymysxfL77XorW2pvBfqN9uXrhx2HhXuB7lkvBuiKdWOARbBuDOumy5kyf5Hm9p77JPlYKeXF\n",
1165 "adeMKaU8KMnhSX46+55Y9O5a61s7vP5aEGGAWRBigK4IMcCiiDGso86iTK11bynloUlek2YNlhcM\n",
1166 "7N58q9C7kzy8q2uvAzEGmAUxBuiKGAMsihjDOutypkxqrZckKaWUo5M8JsmRSW6Q5tneF6RZQ+b1\n",
1167 "tdaZP1ZqVYgxwCyIMUCXBBlgEcQY+qDTKLOh1vr+JO+fxbkBGE6MAbokxgCLIMbQJ50t9FtK+YEJ\n",
1168 "PvOErq4P0GcW8AW6ZBFfYFEEGfqmy5ky/1BKuXet9dydDiyl7ErzeOknJXlph2MA6A0RBuiaEAMs\n",
1169 "ihhDX3X5SOybJ/lgKeXm2x1USrl6kprmsdi7Orw+QC+YFQPMgiADLIJHXNN3XUaZf0xy4ySnlVJu\n",
1170 "u9UBpZRDk5yS5CFJ9ib5nQ6vD7DWxBhgFtyqBCyKGAPdRpljkrwjyfWTnFJKuevgzlLKLZJ8JMld\n",
1171 "knw7ySNqrX/Q4fUB1pIYA8yCGAMsitkxsE9nUabW+q0kD07yuiTXSfLu9tHYKaXcI02QOTzNo7GP\n",
1172 "rrXWrq4NsI7EGGAWxBhgUcQYuKouZ8qk1np5ksemWcT3mkneXkp5XpL3pAk1ZyS5a631H7u8LsA6\n",
1173 "EWOAWRFjgEUQY2C4Lp++lCSpte5N8rRSyvlp4sxT2l2nJHlIrfUbXV8TYB0IMcCsiDHAoogxsL1O\n",
1174 "Z8oMqrX+cZJHJ7kiyeVJnizIAFyVmTHArLhVCVgUs2NgNBPNlCml3DfN05N2ckGSFyd5Ypo1Zp6Q\n",
1175 "5OLBA2qt755kDACrTIQBZkmIARZFiIHxTHr70jszWpRJkl3t9tAkdeBzu9rf95twDAArR4wBZkmM\n",
1176 "ARZFjIHJTLOmzK6dD9nxc5OeA2CliDHArAkywKIIMjC5iaJMrXVma9EArBMxBpg1MQZYFDEGptf5\n",
1177 "05cAEGOA2RNjgEURY6A7ogxAh8QYYNbEGGCRBBnoligD0AExBpgHQQZYFDEGZmPiKFNKOSXJd2qt\n",
1178 "92tfvyajP5Hpe2qtPz/pGAAWTYwB5kGMARZFjIHZmmamzFFJvj3w+vgJzrE3iSgDrBwxBpgHMQZY\n",
1179 "FDEG5mOaKHNaku8MvP7LCc4x9swagEURYoB5EWOARRJkYH4mjjK11h/f9PpRU48GYAmJMcA8CTLA\n",
1180 "oogxMH+dLfRbSrlvku+rtf59V+cEWCQxBpgnMQZYFDEGFqfLpy+9pd0e1OE5AeZOjAHmSYwBFkWM\n",
1181 "gcXrMsrsl+SKDs8HMFdiDDBPYgywSIIMLIfdHZ7r7CQHlFIO7PCcADN33jkXCjLA3Fx6xgWCDLAw\n",
1182 "e07/qiADS6TLKPPWJLuSHNPhOQFmRowB5k2MARZFjIHl1OXtSy9K8oQkT0/ytg7PC9ApIQaYNzEG\n",
1183 "WBQhBpZbl1HmAUn+OcndSykvTfLJUT5Ua31Fh2MA2JIQAyyCGAMskiADy6/LKPOygd9/ecTP7E0i\n",
1184 "ygAzI8YAiyDGAIskxsDq6DLKfHGCz+zt8PoASYQYYLEEGWBRxBhYPZ1FmVrrTbs6F8AkxBhgkcQY\n",
1185 "YJEEGVhNXc6UAZg7IQZYNDEGWCQxBlZbZ1GmlPKsJN+ttf7+CMfeIclPJflMrfXNXY0B6A8xBlg0\n",
1186 "MQZYJDEG1kOXM2WeleTbSXaMMkmuaI//ZBJRBhiJEAMsC0EGWBQxBtbLom5f+o92e/iCrg+sEDEG\n",
1187 "WBZiDLBIggysn0VFmeu22wMWdH1gBYgxwLIQY4BFEmNgfc01ypRS9k9yhyT/t33rC/O8PrD8hBhg\n",
1188 "mYgxwCKJMbD+Jo4ypZQ9SfZuevvqpZQrRvj4rnb7kkmvD6wXMQZYJmIMsEhiDPTHtDNldo343mb/\n",
1189 "neR5tdaXT3l9YIUJMcAyEmSARRJkoF+miTLHttu9aULMu5N8N8n9MzzMXJ7kgiRn1FpHmVEDrCEx\n",
1190 "BlhGYgywSGIM9NPEUabW+t7B16WU05J8p9b6vqlHBawdIQZYVmIMsEhiDPRbZwv91lp/vKtzAetD\n",
1191 "jAGWlRgDLJogA8zt6UullO9Pckmt9bJ5XRNYDCEGWHaCDLBIYgywYaooU0p5bJKDk1xca33NFvsP\n",
1192 "TPKsJI9PckiSK0op70nytFrr6dNcG1g+Ygyw7MQYYJHEGGCzaR6J/UNJXpVmod9fH3LYK5M8ctP1\n",
1193 "fiLJPUsp96u1fmjS6wPLQYgBVoEYAyySGAMMs3uKzz6w3Z6b5GWbd5ZSjsq+IPPBJA9L8pAk70ly\n",
1194 "jSR/0c6kAVbQeedcKMgAS+/SMy4QZICFEmSA7Uxz+9I92u1ra617ttj/mHZ7XpKfqLV+M0lKKW9L\n",
1195 "8qEkP5bk+CQvn2IMwByJMMAqEWOARRJjgFFMM1PmNu32vUP2H9tu/2ojyCRJrfWKJH/cvnzQFNcH\n",
1196 "5sSsGGCVmB0DLNKe078qyAAjm2amzA3SrCfzmc07SinXb/cnzayYzTbeu90U1wdmTIgBVokQAyyS\n",
1197 "EANMYpooc40ke2qt/73Fvtu2271JPr7F/vPbfdeZ4vrADAgxwKoRY4BFE2SASU1z+9K3kuwupRy8\n",
1198 "xb6NKHNRrfWLW+z/viS7prg20DG3KAGrxm1KwKK5VQmY1jQzZc5KE19+JMlHNu27W7s9fchnb9xu\n",
1199 "L5ri+sCURBhgVYkxwCIJMUBXpoky708TZX4tA1GmlHK9JPdrX5465LNHtdszp7g+MCExBlhVYgyw\n",
1200 "SGIM0LVposyfpQkyDy+lnJPktUl+MMnvJTkoyZ4krx/y2dJuPznF9YExCDHAKhNjgEUTZIBZmHhN\n",
1201 "mVrrvyZ5dpq1YX4rza1K78u+W5de0h5zJaWU2ya5T5qFfk+e9PrAaKwVA6wy68YAi2bdGGCWplno\n",
1202 "N7XW303ym0kuThNndiX5dpITkjx58/GllN1pZtjk/7V35/HW1QW9x7+AimES3STHFLxqKs5EzuTA\n",
1203 "xTF91ctfaJmKWYqoec26mXO+slJTKyXFruGE4s+b5kComWUJKs4GYRmDiqYIIso83T/WOjyH85xz\n",
1204 "njPsvdf0fr9e57Wes/fae/0O5/cszvk8a0hyfpK/3872gdUthRgxBhgyMQbokhgDLMJ2Tl9KktRa\n",
1205 "/6yUclSSO6aJMqfUWi9eY/WfShNl3pjk67XWS7e7fWAHEQYYAzEG6JoYAyzKtqNMkrQR5nMbWO+c\n",
1206 "JMfMYptAQ4gBxkKMAbomxgCLNpMoAyyOCAOMiRADdE2IAbokysAACDHAmAgxQB+IMUAfiDLQQyIM\n",
1207 "MEZiDNAHYgzQJ6IM9IAIA4yVEAP0hRgD9JEoAx0RYoCxEmKAvhBigL4TZWBBRBhgzIQYoE/EGGAo\n",
1208 "RBmYExEGGDMRBugjMQYYGlEGZkiIAcZMiAH6SowBhkqUgW0QYYCxE2KAvhJigDEQZWATRBhgCoQY\n",
1209 "oM/EGGCWSil7J3l6kl9Mcrsk+yQ5P8nBtdZ/n/f2RRnYBSEGmAIhBug7MQaYtVLKfZO8N8mNkpyU\n",
1210 "pCa5PMmtkuy2iDGIMrCCCANMhRAD9J0QA8xLKeV2ST6c5NtJDq21frGLcYgyTJ4IA0yJEAMMgRgD\n",
1211 "LMDr0xwN85Ba6+ldDUKUYXJEGGBqhBhgKMQYYBHao2QenORtSS4spfxWmlOWfpTkP5N8sNZ6ySLG\n",
1212 "IsowCUIMMDVCDDAkYgywYL/QLg9KckaS6694/hullF+qtX5+3gMRZRglEQaYIiEGGBIhBujQ7drl\n",
1213 "j5IcnuSTSf47yS2T/E6SI5IcX0q5fa11rr9cijKMgggDTJUQAwyNGAP0wE+0y9fVWo9b9vjpSY4s\n",
1214 "peyX5GFJDkvyxnkORJRhsIQYYKqEGGCIxBgYn5veap+uh5BcuKV9y2Xtcq81nj8hTZQ5YCtvvhmi\n",
1215 "DIMhwgBTJsQAQyXGAD10drvcb43nd1/QOEQZ+kuEAaZOiAGGSogBeu4T7fIRSX5/lefv2i6/Mu+B\n",
1216 "LKz+wEZ8+6zzr/kAmKKLTzvnmg+AobnqlO8JMkDv1Vo/meRLSQ4opbxk+XOllHsmeXySc5Mct/Or\n",
1217 "Z8uRMnRKfAFwRAwwfEIMMEC/nuaImReVUh6V5DNJbp7koUkuSfLYWusF8x6EKMNCiTAADSEGGAMx\n",
1218 "BhiqWuu/lVLuluT5aS7q++Q0R8e8K8nLaq3/sYhxiDLMnRADIMIA4yHEAGNRa/16kqd2OQZRhpkT\n",
1219 "YQAaQgwwJmIMwOyJMmybCAOwgxADjI0YAzA/ogxbIsQA7CDEAGMjxAAshijDhogwANcmxABjJMYA\n",
1220 "LM2sjb0AACAASURBVJYow6pEGICdCTHAWIkxAN0QZbiGEAOwMyEGGDMxBqBbosyEiTAAqxNigDET\n",
1221 "YgD6Q5SZEBEGYG1CDDB2YgxA/4gyIybCAKxPiAGmQIwB6C9RZmSEGID1CTHAFAgxAMMgygycCAOw\n",
1222 "a0IMMAVCDMDwiDIDI8IAbIwQA0yFGAMwXKLMAAgxABsjxABTIcQAjIMo00MiDMDGCTHAVAgxAOMj\n",
1223 "yvSACAOwOUIMMCViDMB4iTIdE2QANkaIAaZEiAGYBlEGgN4SYoApEWIApkeUAaA3RBhgaoQYgGkT\n",
1224 "ZQDolBADTJEYA0AiygDQASEGmCIhBoCVRBkAFkKIAaZIiAFgPaIMAHMjxABTJcYAsBGiDAAzJcQA\n",
1225 "UyXEALBZogwA2ybEAFMlxACwHaIMAFsixABTJcQAMCuiDAAbJsQAUybGADBrogwA6xJigCkTYgCY\n",
1226 "J1EGgJ0IMcCUCTEALIooA0ASIQZAjAFg0UQZgAkTYoCpE2IA6JIoAzAxQgwwdUIMAH0hygBMgBAD\n",
1227 "TJ0QA0AfiTIAIyXEAIgxAPSbKAMwIkIMgBADwHCIMgADJsIANIQYoGuXffnsZZ/t29k4GBZRBmBg\n",
1228 "hBiAHcQYoGvXjjGwOaIMwAAIMQA7CDFA14QYZkWUAegpIQZgByEG6AMxhlkTZQB6RIgB2EGIAfpA\n",
1229 "iGGeRBmAjgkxANcmxgB9IMawCKIMQAeEGIBrE2KAPhBiWDRRBmBBhBiAaxNigL4QY+iKKAMwR0IM\n",
1230 "wM7EGKAPhBj6QJQBmDEhBmBnQgzQF2IMfSLKAMyAEAOwMyEG6Ashhr4SZQC2SIgBWJ0YA/SFGEPf\n",
1231 "iTIAmyDEAKxOiAH6QohhSEQZgF0QYgBWJ8QAfSLGMESiDMAqhBiA1QkxQJ8IMQydKAPQEmIA1ibG\n",
1232 "AH0ixjAWogwwaUIMwNqEGKBPhBjGSJQBJkWEAVifEAP0jRjDmIkywKiJMAAbI8YAfSLEMBWiDDAa\n",
1233 "AgzA5ggxQN+IMUyNKAMMkgADsDVCDNA3QgxTJsoAvSfAAGyPEAP0kRgDogzQMwIMwOyIMUDfCDFw\n",
1234 "baIM0BkBBmD2hBigj8QYWJ0oAyyEAAMwP0IM0EdCDOyaKAPMnAADsBhiDNBHYgxsnCgDbJsIA7A4\n",
1235 "QgzQR0IMbI0oA2yKAAOweEIM0FdiDGyPKAOsSYAB6I4QA/SVEAOzI8oASQQYgL4QY4C+EmNg9kQZ\n",
1236 "mCABBqBfhBigr4QYmC9RBkZOgAHoJyEG6DMxBhZDlIEREWAA+k+MAfpKiIHFE2VgoAQYgOEQYoA+\n",
1237 "E2OgO6IMDIAAAzA8QgzQZ0IM9IMoAz0kwgAMkxAD9J0YA/0iykDHBBiA4RNjgD4TYqC/JhtlSimP\n",
1238 "S/K0JHdPct0kX0vyniSvqrVeuGLdf0py8C7e8vq11svmMFRGRIABGA8hBug7MQY2r5Ty5iRPSvKO\n",
1239 "Wuuvz3t7k4sypZTdkxyT5PFJ/jvJ+5JcnOQBSV6c5DGllPvVWn+wysv/Osn5a7z1lTMfLIMmwACM\n",
1240 "jxAD9J0QA1tXSnl5miCTJFcvYpuTizJJfiNNkDkpyaFLR8WUUvZI8uokz0zyJ0mOWOW1f1JrPX1R\n",
1241 "A2U4BBiAcRNjgL4TY2B7SinPSPL7SY5P8vBFbXf3RW2oR36tXb50+WlKtdYrk/xeku8neVIp5fpd\n",
1242 "DI7+u/i0c3b6AGB8rjrle9d8APTRZV8++5oPYOtKKSXJa5O8PskrF7ntKUaZm6Y5DOmMlU/UWi9N\n",
1243 "8qkkeyY5cJXX7jbfodE3AgzAtAgxwBAIMTA7pZQHJHlbkvfWWp+ZBf/eP8XTl85Octskd0nyn6s8\n",
1244 "f167/OlVnjullHK9JJck+UaSj6a5MPCZcxgnCya4AEyTAAMMgQgDs1dKuUua68yelB1n1SzUFKPM\n",
1245 "MWku6ntUKeW6SU5IclGSmyV5UJL7tuvtuew1pyc5N8l3k1yW5MZJHpzk6UkeX0o5pNb62UUMntkQ\n",
1246 "YAAQY4AhEGNgPkopt0rTA85K8uiu7qY8uShTa31rKWX/JM9PcuyKp7+f5iiYpT8vvebJK9+nlLJn\n",
1247 "kqOSHJ7kdUnuNZcBMxMiDACJEAMMgxAD81VK2TvJh9McdPGwWusFXY1lclEmSWqtLy2lHJPkIWmu\n",
1248 "MXNJmlOZPpzkxCQ3SXLaLt7j0lLK05M8LslBpZS9aq0XzXXgbIgAA8ByQgwwFGIMLMytk9wuyQeT\n",
1249 "PKe5zu81fqZdHlhKeVWSb9ZaXzuvgUwyyiRJrfWsJEcvf6yUcvMkd05yZvv8rt7j0lLKRWlOdfrx\n",
1250 "NKdBsUACDABrEWOAIRBiGLL9b3LDroeQc/9rSy+7ul0+Iskj11jnDu3HF9PcmWkuJhtl1vDidnn0\n",
1251 "umu1Sik/k+R/JDm31vrduY2KJAIMALsmxABDIcZAd2qtX8oad6MupfxCko8neXut9QnzHosok6SU\n",
1252 "cp0kf5DkKUlOSfLqZc8dkuYUp3fVWi9f9vj1k7yx/fTNixvtNAgwAGyUEAMMhRADg+CW2PNWSjki\n",
1253 "zfVkvp5knyQPTHLzJJ9L8sgVV12+RZro8ppSyr+kuRX2jZIcnOaOTSdmxxE2bIEAA8BmCTHAkIgx\n",
1254 "wFomGWWSXJzmltbXTfK9JF9Ic6TM22utV69Y9yNJ/ihNhLl7koemuULzvyd5RZKjaq1XLGjcgyfA\n",
1255 "ALAdYgwwFEIMsBGTjDK11mOSHLPBdb+V5IXzHM9YCTAAzIIQAwyJGAPDVmv9p6xxvZl5mGSUYfYE\n",
1256 "GABmSYgBhkSIAbZKlGFLRBgA5kGMAYZEjAG2S5RhlwQYAOZJiAGGRIgBZkmU4VoEGAAWQYgBhkaM\n",
1257 "AeZBlJkwAQaARRJigKERYoB5E2UmQoABoAtCDDBEYgywKKLMCAkwAHRJiAGGSIgBuiDKDJwAA0Af\n",
1258 "CDHAUIkxQJdEmQERYADoCxEGGDIhBugLUaanBBgA+kaIAYZOjAH6RpTpCREGgD4SYoChE2KAPhNl\n",
1259 "OibGANAnIgwwFmIMMASiDABMnBADjIUQAwyNKAMAEyPCAGMjxgBDJcoAwAQIMcDYCDHAGIgyADBS\n",
1260 "QgwwRmIMMCaiDACMhAgDjJUQA4yVKAMAAybEAGMmxgBjJ8oAwMAIMcCYCTHAlIgyANBzIgwwBWIM\n",
1261 "MEWiDAD0kBADTIEQA0ydKAMAPSHEAFMhxgA0RBkA6IgIA0yJEAOwM1EGABZIiAGmRowBWJsoAwBz\n",
1262 "JsQAUyPEAGyMKAMAMybCAFMlxgBsjigDADMgxABTJcQAbJ0oAwBbIMIAUyfGAGyfKAMAGyDCAAgx\n",
1263 "ALMmygDAKkQYABEGYN5EGQCICAOwRIgBWBxRBoDJEWAArk2IAeiGKAPA6IkwADsTYgC6J8oAMDoi\n",
1264 "DMDqhBiAfhFlABg8EQZgbUIMQH+JMgAMigADsGtCDMAwiDIA9JoIA7AxQgzA8IgyAPSKCAOwcUIM\n",
1265 "wLCJMgB0SoQB2BwhBmA8RBkAFkaAAdgaIQZgnEQZAOZGhAHYHjEGYNxEGQBmRoQB2D4hBmA6RBkA\n",
1266 "tkyEAZgNIQZgmkQZADZEgAGYLSEGAFEGgFWJMACzJ8QAsJwoA0ASEQZgXoQYANYiygBMlAgDMD9C\n",
1267 "DAAbIcoATIAAAzB/QgwAmyXKAIyQCAOwGEIMANshygCMgAgDsDhCDACzIsoADJAIA7B4YgwAsybK\n",
1268 "AAyACAPQDSEGgHkSZQB6RoAB6JYQA8CiiDIAHRNhALonxADQBVEGYMFEGIB+EGIA6JooAzBnIgxA\n",
1269 "fwgxAPSJKAMwQwIMQP8IMQD0lSgDsA0iDEA/CTEADIEoA7AJIgxAfwkxAAyNKAOwDhEGoP/EGACG\n",
1270 "SpQBaAkwAMMhxAAwBqIMMFkiDMCwCDEAjI0oA4ye+AIwXEIMAGMmygCDJ7oAjIsQA8BUiDJA74ku\n",
1271 "AOMnxAAwRaIM0CnBBWC6hBgApk6UAeZKdAFgOSEGAHYQZYBtEV0A2BUhBgBWJ8oAaxJcANgOMQYA\n",
1272 "1ifKwISJLgDMmhADABsnysCIiS4ALIIQAwBbI8rAQAkuAHRJiAFgyEopv5bkYUl+Lsktk+ye5BtJ\n",
1273 "Tkjy8lrrtxcxDlEGekp0AaBvhBgAxqCUcp0kb0tyeZKTknwsTR+5f5Ijm1XKvWutZ8x7LKIMdEBw\n",
1274 "AWAohBgARuiqJC9P8ppa67lLD5ZSdkvypiRPTvLSJE+Y90BEGZgD0QWAIRNiABizWutVSV6wyuNX\n",
1275 "l1JelybKHLiIsYgysAWiCwBjI8QAQJJkr3Z57rprzYgoAysILgBMhRADADs5rF1+YhEbE2WYHNEF\n",
1276 "gKkTYwBgZ6WUeyZ5WpLzkvz5IrYpyjA6ogsA7EyIAYC1lVLumOSDSa5O8tha6zmL2K4ow6AILgCw\n",
1277 "cUIMwOKcdeap1/z5XrlbhyNhs0op90hyQpIbJjms1voPi9q2KEOviC4AsD1CDMDiLA8xU3SLfW/Q\n",
1278 "9RBy7n9t7/WllIcnOS7J5UkeVmv9+AyGtWGiDAslugDA7AkxAIsx9QgzNqWUZyZ5TZJvJHlErXXh\n",
1279 "32BRhpkRXABgcYQYgMUQYsanlLJnkqOSHJ7kn5M8pta6kFtgryTKsGGiCwB0S4gBWAwhZvQOSxNk\n",
1280 "fpTkS0meV0pZbb0P11o/Os+BiDIkEVwAoK+EGID5E2EmZ7d2eYMkz1pjnauTXJBElGH7RBcAGA4h\n",
1281 "BmD+hJjpqrW+Jclbuh5HIsqMhugCAMMnxgDMlxBD34gyAyC4AMB4CTEA8yPC0HeiTA+ILgAwLUIM\n",
1282 "wPwIMQyJKNMxQQYApkGIAZgfIYahEmUAAOZEiAGYDxGGsRBlAABmSIgBmA8hhjESZQAAtkmIAZgP\n",
1283 "IYaxE2UAALZAiAGYPRGGqRFlAAA2SIgBmD0hhikTZQAA1iHEAMyeEAMNUQYAYBViDMDsiDCwOlEG\n",
1284 "AKAlxADMjhADuybKAACTJsQAzI4QA5sjygAAkyPEAMyGCAPbI8oAAJMgxADMhhADsyPKAACjJcQA\n",
1285 "bJ8IA/MjygAAoyLEAGyfEAOLIcoAAIMnxABsnxADiyfKAACDJMQAbI8IA90TZQCAQRFjALZOiIF+\n",
1286 "EWUAgN4TYgC2ToiB/hJlAIBeEmIAtkaEgeEQZQCA3hBiALZGiIFhEmUAgE4JMQBbI8TA8IkyAMDC\n",
1287 "CTEAmyfCwPiIMgDAQggxAJsnxMC4iTIAwMwJMABbJ8TAdIgyAMCWiS8A2yfCwHSJMgDAhggwALMj\n",
1288 "xACJKAMArCC+AMyHEAOsJMoAwESJLwDzJcIAuyLKAMAECDAAiyHEAJshygDAiIgvAIsnxABbJcoA\n",
1289 "wACJLwDdEWGAWRFlAKDnBBiA7gkxwDyIMgDQE+ILQL8IMcC8iTIAsGDiC0A/iTDAookyADBHAgxA\n",
1290 "vwkxQJdEGQCYAfEFYBhEGKBPRBkA2ATxBWB4hBigr0QZAFiDAAMwXEIMMASiDACTJ74ADJ8IAwyR\n",
1291 "KAPAZIgvAOMixABDJ8oAMEoCDMA4CTHAmIgyAAya+AIwbiIMMGaiDACDIL4ATIcQA0yFKANAr4gv\n",
1292 "ANMkxABTJMoA0BkBBmC6RBgAUQaABRBfAEiEGICVRBkAZkZ8AWAlIQZgbaIMAFsiwACwGhEGYONE\n",
1293 "GQDWJb4AsCtCDMDWiDIAJBFfANgcIQZg+0QZgAkSYADYLBEGYPZEGYARE18A2A4hBmC+RBmAERBf\n",
1294 "AJgVIQZgcUQZgIERYACYJREGoDuiDEBPiS8AzIsQA9APogxAx8QXABZBiAHoH1EGYIEEGAAWRYQB\n",
1295 "6D9RBmAOxBcAuiDEAAyLKAOwDeILAF0TYgCGS5QB2CABBoC+EGIAxkGUAVhBfAGgj4QYgPERZYDJ\n",
1296 "El8A6DshBmDcRBlgVIQWAIZOiAGYDlEG6B1hBYCpEWIApkmUAeZCWAGA9QkxAIgywKpEFQCYPSEG\n",
1297 "gOVEGRgxYQUAuifEALAWUQZ6TlgBgOERYgDYCFEG5kxUAYBpEGIA2KzJRplSyuOSPC3J3ZNcN8nX\n",
1298 "krwnyatqrReusv6jkzw7yd2S7JnkrCTHJfnTWuvFixo33RBWAIC1iDEAw1RKuVOSFyU5OMk+Sc5J\n",
1299 "8pEkL6m1fmMRY5hclCml7J7kmCSPT/LfSd6X5OIkD0jy4iSPKaXcr9b6g2Wv+e0kr0lyfpL3J7kg\n",
1300 "yS+k+eY9uJTyoFrr5Qv8MtgCYQUAmBUhBmDYSin3TvKxJHsk+fs0B17cIcnhSR5RSrlXrfXMeY9j\n",
1301 "clEmyW+kCTInJTl06aiYUsoeSV6d5JlJ/iTJEe3jN28/PyfJzy3VslLKbknemeRXkjw1yesW+2VM\n",
1302 "j6gCAHRJiAEYlTcmuV6SR9Vaj196sJRyZJK/TPKqJI+Z9yB2n/cGeujX2uVLl5+mVGu9MsnvJfl+\n",
1303 "kieVUvZsnzoszelKb1h++FKt9eokf9B+evjcRz0Sl3357C1/AAAs2llnnnrNBwDjUEq5R5I7Jfnk\n",
1304 "8iCTJLXW1yf5ZpJHlVJ+ct5jmeKRMjdNcnWSM1Y+UWu9tJTyqSQPS3JgkhOT3Lt9+qRV1j+9lPLd\n",
1305 "JHctpVy/1nrJ/IbdD+IIADB2AgzA6K35e37rxDRnxdwzyQnzHMgUo8zZSW6b5C5J/nOV589rlzdu\n",
1306 "l7dul99d5/32TbJ/kn+f0RjnSlgBALg2IQZgUjbye37S/J4/V1OMMsekuajvUaWU66apXhcluVmS\n",
1307 "ByW5b7ve0ulLN0xzZM0Fa7zfRUl2S7L3fIa7OmEFAGB7hBiAybphu1zv9/xkAb/nTy7K1FrfWkrZ\n",
1308 "P8nzkxy74unvJ7lk2Z+Xu2KNt9xtO+O5x1P33eIrt/o6AACS5F65W9dDABi0L33qX7sewnbN5ff8\n",
1309 "zZjihX5Ta31pmlOYnpbkpUmel+aqyrdM8r00R8ac1q7+wzTfkB9b4+32WrYeAAAA0G9Lv793/nv+\n",
1310 "5I6UWVJrPSvJ0csfa29/feckZ7bPJ80Fge+e5FZZ/ZoxN09yVVa5cPB6DjnkkIWVNwAAAJiVEfw+\n",
1311 "u/T7+63WeP7m7fL0eQ9kkkfKrOPF7XJ5rDmxXT5o5cqllNumOY/o32qtF895bAAAAMD2rfd7/u5J\n",
1312 "7pPkyiQnz3sgokySUsp1SikvSvKUJKckefWyp9+d5LIkT2yPpFl6ze5JXtZ++pZFjRUAAADYulrr\n",
1313 "55OcmuTAUsqhK54+Is2RMsfXWs+d91iGfsjRlpRSjkjykCRfT7JPkgem+Y/+uSSPrLV+Z8X6v5Pk\n",
1314 "lWlul/3BJD9Kcv80pzp9Oskv1FovW9gXAAAAAGxZKeW+Sf4hzcEqH0ryzSQ/m+SQNNeavU+t9b/m\n",
1315 "PY495r2BPjrggAPulOTIJD+f5MZJvpjkj5I8s9b6o5Xrn3rqqScdcMABX05zL/MHJDkoza2z/irJ\n",
1316 "b9VaL1n5GgAAAKCfTj311G8ccMABH0zTBO6f5H5pLvD7/5L8Wq31zA6HBwAAAAAAAAAAAAAAAAAA\n",
1317 "AAAAAAAAAAAAAAAAAAAAAEt263oAQ1JKuVOSFyU5OMk+Sc5J8pEkL6m1fmOL77lvkq8mOaXWev9d\n",
1318 "rHu/JM9Lcs8kP57k7CTvT/KyWut5W9k+3epyTpVSjknyhF283e1rrf+xlXHQjVnNqVLKI5P8UpKf\n",
1319 "T7J/kusm+XaSjyd5ea31P9d4nf3UyHQ5p+ynxmeG8+k+SR6X5D5JbpNkryQXJPlCkrcleWut9epV\n",
1320 "XmcfNTJdzin7qHGax8/ny977xUlenOSTa/2cbj81PdfpegBDUUq5d5KPJdkjyd8nOSvJHZIcnuQR\n",
1321 "pZR71VrP3OB77ZPkZUl+Oskhaf6y7/SDw4rX/HKSmuSSJB9I8p0kByX57SQPa7d//ua/MrrS9Zxa\n",
1322 "5t1Jvr7Gc3b8AzLLOZXkDUlukuSzSd6e5Ko0+5wnJnlMKeVBtdaTV2zffmpkup5Ty9hPjcCM59Mr\n",
1323 "ktw7yaeTHJfkwiQ/k+TQJA9K8sAkT1qxffuokel6Ti1jHzUSM55TK9/7t9IEmWSNn9Ptp6ZJlNm4\n",
1324 "Nya5XpJH1VqPX3qwlHJkkr9M8qokj9nge+2T5Mhs8JfmUspeSf4qyaVJ7ldr/cKy516R5LlJXtAu\n",
1325 "GY7O5tQKR9da/3ELr6N/Zjmnjk7yNyv/RaiU8qIkL0nyZ2n+BWnpcfupcepsTq18rf3UKMxyPr06\n",
1326 "yadrrWcvf7CUcvskpyR5Qinlt2utP2gft48ap87m1Ar2UeMxyzl1jVLKo5O8PsnxSR6+xjr2UxO1\n",
1327 "e9cDGIJSyj2S3CnNYWbHL3+u1vr6JN9M8qhSyk9u5P1qrWfWWnevte6R5NYbeMlDk+zbvHTHX87W\n",
1328 "S9OU1F8vpfh+DkQP5hQjM4c59YdrHKL7F+3ywBWP20+NTA/mFCMyh/n0tyt/eW6dkWZ/c2GSHy17\n",
1329 "3D5qZHowpxiZWc+pZe973yTvSvKhJM9aZ1X7qYnyDd2Ye7fLk9Z4/sQ0Rx3dcwvvvZHr+qy5/Vrr\n",
1330 "hUm+nOYv8O22sH260fWc2s769NM859Rye7XLcze6ffupwep6Ti1nPzV8c51PpZS922uC/F2an2+P\n",
1331 "qLVeuZHt20cNVtdzajn7qHGY+ZwqpdwxzWlIn0tyWJpTdze9ffupcXP60sYsHXnw3TWeX6rq+/dg\n",
1332 "+6fNaQzMVtdzarkPlVKul+ZQyW8n+USSP6u1fmUB22Z2FjWnDmuXn9jG9u2nhqHrObWc/dTwzW0+\n",
1333 "lVK+mOQu7acfSXKXVS4cbR81Pl3PqeXso8ZhpnOqlHKLJCekmROPrLVeWkqZ1fbtp0bEkTIbc8N2\n",
1334 "ecEaz1/ULvce6faZvT58T89OU+7fkuTPk/xtmouaPSHJye2dUhiOuc+pUsqtk7wwzQ+df7zo7bNw\n",
1335 "Xc+pxH5qTOY5n45JclSSf0xzsfvj2n+dXtT26UbXcyqxjxqbmc2p9hSnE9IcRfWQDV6c135qohwp\n",
1336 "szlXrPH4og5Z7Hr7zF5n39Na6/NXPtaeo/riNL8kvaGUcsta63qHWdI/c5lTpZSbJflwkp9I8uRa\n",
1337 "6ymL3D6d6mxO2U+N0sznU631tUt/LqUclORfk7yvlHKXWusl894+netsTtlHjda25lQ7B96f5GZJ\n",
1338 "Dq61fnOR22d4HCmzMT9slz+2xvN7rVhvbNtn9nr5Pa21XlVrfXGSM5PcNM0tABmGuc2pUsp+Sf45\n",
1339 "zeGyz6y1vmWR26czXc+pVdlPDdZC9hHtbdU/nuQ2ufbdvOyjxqfrObXW+vZRwzWrObV3kvsm+UqS\n",
1340 "J5VSXrX0keQP2nX2bx/7wzlsn4FxpMzGnNEub7XG8zdvl6ePdPvMXt+/p+cl2S/JDTraPps3lznV\n",
1341 "/gvhB9IczfDrtdZ3LnL7dKrrObUr9lPDssh9xHntcvkdUuyjxqfrObWR1+wX+6ghmfWcul+S+6/z\n",
1342 "Xs9Jcn6SF81p+wyEKLMxJ7bLB618oj087T5Jrkxy8hy3/5x2+29Ysf2901yI7Lwk/zGn7TN7Xc+p\n",
1343 "NZVS9krys2kOnVzvonb0y8znVCnlMWnOk784yaG11n/Zxfbtp8al6zm13vvYTw3PQv6/V0rZLTsu\n",
1344 "0HrGsqfso8an6zm13mvso4ZpJnOqvX7MqmeklFJulWYe/WutdeWRV/ZTE+X0pQ2otX4+yalJDiyl\n",
1345 "HLri6SPSVMvja63X3M6zlPLWUspppZSXz2AIJyT5XpJHllLuvOK5FybZM8k7nK86HF3PqVLKXUsp\n",
1346 "z2x/aFj++O5pLlR3gyTvrbV+f7vbYjFmPadKKS9L8u4kX0ty0AZ+ebafGpmu55T91LjMcj6VUu5Q\n",
1347 "Snl1KeUmq2zqBUnumOSUWutnlj1uHzUyXc8p+6jxWdDP5+tdF8Z+aqIcKbNxT03yD0k+UEr5UJJv\n",
1348 "pinghyQ5J03VXO6Wae4hv9POvZRyw/b9kh2HQd6ilPLc9s9fr7W+e2n9WutFpZQjk7wryYmllA8k\n",
1349 "OTfJPdLcz/5rSV667a+QRetsTrXr/HmSPyql/EuaYr93mvn0P5N8NckztvXV0YWZzKlSysFJnp/m\n",
1350 "X/hOTHLkGrdw/MzSvLKfGq3O5lTsp8ZoVv/f2zPJs5M8o5Ty6SSnJLleknsluX37Xr+6/AX2UaPV\n",
1351 "2ZyKfdRYzezn882yn5ouR8psUK31k2l2zO9Pc+Gmp6ap5sek+Re//1rxkqvbj9X8VJJXtB/Pa9e7\n",
1352 "1bLHnrbK9muaQ9n+JclDkvxmmr/8f57kXrXW81a+hn7reE59Ic0vSJ9O88PGk5L8cpIL09wx4Odq\n",
1353 "reds+YujEzOcU0v/irNH+x7PWeXjfyd56Irt20+NTMdzyn5qZGY4n05Ls395X5oLqT4xyWPT/Fz7\n",
1354 "2iR3rbV+ZZXt20eNTMdzyj5qhGb88/lWtm8/BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n",
1355 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAm7db1AACAfiqlnJnklkmOqLW+sePhJElK\n",
1356 "KcckeUKS42qtj+t4OAAA23KdrgcAAKytlPKyJM9P8oMkN661XraB1zw7yauTnJPkprXWq7Y5jKu3\n",
1357 "+fprlFLenuRXk7yl1nr4Gus8Kcmb20/3q7V+fVdjKqXsl+T09tPDa61vWfbcU5IcneSsWuv+2/oC\n",
1358 "AABmaPeuBwAArOsd7XLvJA/b4GuWjiA5bgZBZl7WCz1XJLk0ySW7WG/l+y295ootbBMAYOEcKQMA\n",
1359 "PVZrPa2U8sUkd0vy2CR/t976pZRbJzkoTYB4x3rr9lWt9e1J3r7J15yV5MfmMyIAgPlwpAwA9N+x\n",
1360 "7fKRpZS9drHuY9vlGbXWT89xTNvlunYAwOQ5UgYA+u+dSV6R5AZJHpXkXeusuxRljl3+YCnloCTP\n",
1361 "SXJwkhslOT/JiUleV2v92GYHVEp5YZJ7Jtk/yU3SnF71wyT/nuT97fteuGz9/bLjmi9J8sRSyhNX\n",
1362 "vO1+tdavl1IOSfKRJKm1bugfkEop10mydL2dB9Za/7l9/Mw0FytOkv1KKStP5zo8yYVJ3t2+/ma1\n",
1363 "1vPW2MaDk3w0zSlSN621/mAjYwMAWIsjZQCg52qtZyf55/bTx661XinljknulObUpWOXPf7sX1fY\n",
1364 "rwAABfRJREFUJJ9OcliagLJ7mjDz6CQfLaX86RaG9fwkD09yhyT7tNv8iST3TvLHSU4upfzUsvWX\n",
1365 "rvmyFEWuShM3ln+svObLVq4Bc/WK1628xszKbV6R5pSwc5JcL82dndbym+3yOEEGAJgFUQYAhmEp\n",
1366 "sjyklPITa6yzdIHfL9ZaT0uSUsrD09yJ6eokr0tzNMp1k9wsyR+2j/9ue4eizTg5yQuS3CPJ9Wut\n",
1367 "10uyb5KnpDli5vZJXri0cq31rFrrj6U56idJ3lpr3WvFxzc2OYZdqrXePskR7adnrrLNd9RaL0+y\n",
1368 "dLem31jtfUopN0ryS2n+e/Xi9uAAwPA5fQkAhuE9SV6fZM8kv5zkb1ZZ57B2ufzUpVe0y6Nrrc9a\n",
1369 "erDW+p0kLymlXJEmzvxRKeWtG7nldvv6+6/y2HlJ3tweIfOnSX4xybNXrNbFtWQ2ss2/TvLcJHcs\n",
1370 "pdyr1vqpFc8/Icl1k3xllecAALbEkTIAMAC11u8nOb79dKdTmEopBya5TZrTgt7ZPnbnJHdMc3TH\n",
1371 "n6zx1q9JcnGa05n+14yG+4V2efMZvd/c1Vr/I8kn0gSc1Y4aWnrMUTIAwMw4UgYAhuPYNBf6fWAp\n",
1372 "Zd9a6znLnls6dekTtdZvtX8+qF1+q71l9E5qrReWUr6Q5D5JDkzyoY0OppRy2yS/kuTnk9w6zcV+\n",
1373 "b9h+JM2RJUPypjQXQv6VUspvL12ouJRyvzSnY12Y5G0djg8AGBlRBgCG4/1JfpTkx5OUJEclSSll\n",
1374 "tzRxJLn2qUs/3S6/s4v3/Xa7vPFGBlFK2SPJa5M8Pdc+NWjpArtXJNljI+/VM+9J8hdJfjLN0Uj/\n",
1375 "t318+QV+f9jFwACAcXL6EgAMRK31kiTvaz9dfgrTfZPcIs3djeoChvKHSY5ME2Q+nuSJSe6a5Ea1\n",
1376 "1j2SHLqAMcxcrfXS7DgS5ilJUkrZJ00Ac4FfAGDmHCkDAMNybJLHJ7lPKeUWtdZvZsepSyesuFXz\n",
1377 "0hEyN9vFe960XX53Vxtvj8o5sv30DbXWp6+yWhcX852VNyV5VpKfL6UckOQBSa6f5o5WJ3c5MABg\n",
1378 "fBwpAwDD8tEk56T5f/hhpZTdkzymfe4dK9b9bLu8cXv9l52UUm6Y5rbWy9dfz75prh1zdXac3rMZ\n",
1379 "V7TLPbfw2q3a8DZrrack+VR2XPB36dQlR8kAADMnygDAgNRar0zy7vbTxyU5JE0ouSDJB1as+5Uk\n",
1380 "p6YJDC9c4y1/J82RIOekCT67cumyP//0GuvcfZ3Xn7OBdWZtaZs3LqVs5Lo5b2qXT01ylzTX8VkZ\n",
1381 "vAAAts3pSwAwPMemOYXoHkn+oH3sve01UVb6P2lizeNLKRcneXmt9axSyk3SXKj3BWmOenlhrfWy\n",
1382 "XW241vqDUsqnk9wzyStLKeemuQX2Hu1jz8v615Q5qV3evpTyjCRvTXOXpp9LcuKcLqR7cpIr2zH+\n",
1383 "aSnl99PcSekOSX5Qa/3qivWPS3Or8L3bz99Za/3RHMYFAEycI2UAYGBqrSclObP99OB2eewa634o\n",
1384 "ye+mCS+/meSMUsqVSb6VHUHmNbXWozcxhGcluSjJHdOc6nNp+/nHkzwwyfHrvPYDSf6t/fNfJDk/\n",
1385 "zZEsf5/mrkfbtdP1bGqt382OU62ekOZr/0E79nuusv5F2fHf0wV+AYC5EWUAYJiWR4PvJPmHtVas\n",
1386 "tf5ZknunOe3pW0kuT3NR3/cnObTW+tw1Xnp1dtzmevn7ndy+3wfSnDZ1eZIzkvxVkjsneeU6Y7k8\n",
1387 "yYPSRJJvt6/9TpKPJVk6Smanbe5qTCueX82RaU7h+lqSy5J8P8lnkpy+xvpLj3++1vr5dbYHALBl\n",
1388 "Q747AgDAzLV3mPpqktsk+a1a6193PCQAYKQcKQMAcG0PSRNkLsgap4UBAMyCKAMAcG1Htstj2+vL\n",
1389 "AADMhSgDANAqpeyf5OFxgV8AYAFEGQCAHY5Ic829z9Zav9T1YAAAAAAAAAAAAAAAAAAAAAAAAAAA\n",
1390 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANb2/wG6ZaKxl4EkNQAA\n",
1391 "AABJRU5ErkJggg==\n"
1392 ],
1393 "text/plain": [
1394 "<matplotlib.figure.Figure at 0x10ffc7210>"
1395 ]
1396 },
1397 "metadata": {
1398 "image/png": {
1399 "height": 407,
1400 "width": 562
1401 }
1402 },
1403 "output_type": "display_data"
1404 }
1405 ],
1406 "source": [
1407 "plt.figure()\n",
1408 "plt.contourf(sigma_vals, strike_vals, prices['acall'])\n",
1409 "plt.axis('tight')\n",
1410 "plt.colorbar()\n",
1411 "plt.title(\"Asian Call\")\n",
1412 "plt.xlabel(\"Volatility\")\n",
1413 "plt.ylabel(\"Strike Price\")"
1414 ]
1415 },
1416 {
1417 "cell_type": "markdown",
1418 "metadata": {},
1419 "source": [
1420 "Plot the value of the European put in (volatility, strike) space."
1421 ]
1422 },
1423 {
1424 "cell_type": "code",
1425 "execution_count": 23,
1426 "metadata": {
1427 "collapsed": false
1428 },
1429 "outputs": [
1430 {
1431 "data": {
1432 "text/plain": [
1433 "<matplotlib.text.Text at 0x1109ef450>"
1434 ]
1435 },
1436 "execution_count": 23,
1437 "metadata": {},
1438 "output_type": "execute_result"
1439 },
1440 {
1441 "data": {
1442 "image/png": [
1443 "iVBORw0KGgoAAAANSUhEUgAABHoAAAMvCAYAAACtK7e/AAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\n",
1444 "AAAWJQAAFiUBSVIk8AAAIABJREFUeJzs3Xu8ZXVd+P/XzCAwwOCYM8Y06DCCMjIJQppACIiTePvm\n",
1445 "7fsJ7ftFtL5m2kVTstTS+JGV5I1vXtC8lyF+Mi9YiZAiFKZ984YoEXEREGOQkDsjzvz++Kzt2eec\n",
1446 "fd/rnL3W5/N6Ph7nsc8+e+211j4zj3JevNdngSRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJ\n",
1447 "kiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJkiRJ\n",
1448 "kiRJkiRJkiRJkiRJkiRJkmbseGDnBF+vm8G5qtmOp/fflR8BtwFXAB8FfgnYbRnO52XAHwKnLMOx\n",
1449 "JEmSJElqhONZ/I/yQV+dbV47g3NVsx1P/79LC+PPfwJHLPH5XFMd6/NLfBxJktRgy/FflyRJaqr3\n",
1450 "AB8Zcdurl/JE1Hrdf5dWAfcHHgk8FzgI2Ax8AXgMcPkSn8uuJd6/JEmSJEmNcTxzUxavnO2pqOWO\n",
1451 "Z/jfpVXAe7u2u3AJz+ea6hifW8JjSJKkhls56xOQJEnK2I+AFwM3VM+PBTYt8TFXLPH+JUlSgxl6\n",
1452 "JEmazPOZm9I4bsi21zB47ZTOft5fPd8MvBn4FnAXcB9wfo/37Qn8JmmC47+AHdXj+aS4cL8B53QA\n",
1453 "8xeaXgE8k7R48HXAvcBNwCeBxw/5fN37PAP4KvDf1T6+C3wC+J9D3nsS8E7gIuB64G7S5/4B8A3g\n",
1454 "L4DHDdnHhdXn6Vxmtx/ps30ZuBW4h3TZ1BnAT4z4meqwA/iHrueP6Pr+eOb+HIYtonwh8z9fxweq\n",
1455 "nz+ken4ci9cIGuXvqSRJkiRJrXM89Vy69XzmFuA9dsi21zD4kprO+ZxDihA/7PpZZ2Hfryx4zyNJ\n",
1456 "/+BfuBhw9/N/Bx7W55gHdG33MeDr9I4Dna9XDfmMp5LCzqDzOQ9Y0+f9V/Y4Zq9Fjc+i/8TKhdU2\n",
1457 "15HuPnXPgM9zJfCTQz7TMMd37W/Y36XXd217Uo99/Ah43pB9XFhte9WCn7+f3r+7hQuKD/t7KkmS\n",
1458 "MuBizJIkNUeoHm8D/po03XIL8ABg367tNpEW9l1LWnj3U6Spmf8CNpCmZ55MijwXA48CvjfguM+s\n",
1459 "Hi8HziZN0ECKEL8G7EEKFV9j/mRKxx8yd1eyK0gTJpeRgtVm0u3Ffw74+eq1Z/fYx4+q91xUncf3\n",
1460 "SNM89wcOI/1uHgb8KvBN4G0DPs/G6nzuI/1ezgVurH7+QuBngYcCbyUtlrwc9uv6fnuP16e53OoM\n",
1461 "4K+qr58kRbtX9NjuGz1+JkmSJElSqx3P3NTDXwBPALaN8LV5wX6eT/0TPTtJ/1hfN2R/5zF8CuQl\n",
1462 "Xfv8mx6vH9D1+s0D9nMscxNGvULBzzH/99nvPyL9edd2R/d4fa8+7+vYE7i0ev+3+2xzYdcxzgcO\n",
1463 "7rHN7l372UGKZZM6ntEmevYgXcK2kzRltE+ffUw60dNxDS7GLEmSJEkqzPEMvkSp39frFuzn+dQf\n",
1464 "ej42wvn/dNf2Zw/Z9tyuczxwwWsHdO3ntQz24a5tD13w2j9UP/8ag9f+2wu4o9r2zCHH6+dPmPs8\n",
1465 "vcLQhdXr3xmyn9/t2s8JE54LjBZ6VpDWHups9/4Fr3fvw9AjSZKm5mLMkqTS7Rrja6n9YIRtntz1\n",
1466 "/fuGbPve6nEFcOJEZ5R8ouv7I7u+X0OadgL4ECky9HMXaXFpgMMHbPcIUjT5MGkR5WtIv5d7SIGm\n",
1467 "Y9BiyvcNeA3g2upxBbB+yLaj6r70ak/gwcCzgH8EXlT9/LvA79V0PEmSpJ5co0eSVLLfI61v0iaH\n",
1468 "VY+7gP83ZNvu1w/ru9Vwl3d93z0ZdASwqvr+jdXXKB7U42eHAG+n/52hFoa2af5j1X93fb/HFPvp\n",
1469 "9ifVVz9XAL9AWkdJkiRpyRh6JElqlwdWjzuZHyx66V7094F9txqu+zj37/p+YbAZNvXUmXrZfcHP\n",
1470 "DyUtwtxZcPqe6vnXSZdh3QzcDjwH+N+jnfJA99awj156ff5vkRagfgfplvGSJElLytAjSZKGuV/X\n",
1471 "992RZFXX92cAnx1xfwuDx58zF3k+DLwM+H6P9/3siPufhfcAH6m+/yFwK3A9w2OcJElSrQw9kiRN\n",
1472 "bznXvLu565jrup738pNd3/cKJ6Pa0PV996VH3RND32eyRYAfBDyu+v7/ASdPsI8muJLpF0F27URJ\n",
1473 "kjQ1/weFJEmT6Uy2rCAtSrxcvt513GETLo/p+v5rUxyzewHmL3d933279W1MZlPX96NOBOWke0Jq\n",
1474 "2r9HncWwVw3cSpIkZc3QI0nSZG7q+v7gAdutoN4J2n/o+v7/DNn2hdXjTuC8CY+3W9dxvg9c3PXa\n",
1475 "dubCzzbg6BH32X0pWPcdsoaFjj1H3H+bjPr3COb/3nq5o3qcZj0mSZLUcoYeSZIm82/MRYpT6P2P\n",
1476 "8IOB84GfqvG4lzE3+fIM5mLOQi8Fnlh9/wngqgH77BcQVgBnAluq5/+XxQsZ/1HXth9j8JTReuD1\n",
1477 "Xe+BtFjxXdX3Twf26fG+fYC3Ar8zYN9t9Z/MXX4XgLU9ttlIWv/nqCH7+vfq8RHAT9dydpIkqXVc\n",
1478 "o0eSVLKHAU9g7m5Qg/wncHXX81uBvyHdCWoraX2W95AmNH4KeCrpdtpL8R9VXgR8BXgA8K7qOB8j\n",
1479 "rZ+zgRQMTqy2/S/g14fs79WkQPMZ4AbSZMiBwPOBw6tt/pXetw//NGkx5d8krQl0CXAuaYLoOtLt\n",
1480 "yx9C+j1vq56/tev991af4ber7f4NeCcpTO0J/AzpTlvd6wTl5j3A75FC2MWkoHY9aQ2mnyf9eY5y\n",
1481 "G/hPAs8m/X3+e9Lv+TrSOkg/A7yN9PdGkiRJkqRsHE+6lGncr9f12NeDgCsGvOdO4PdJ/9jeSf/F\n",
1482 "ejvbv2+Mz/FIUngadM7/TopZvRww5L3dX/9I70mTbq8k3U1r2L52kKJOtz1Ik0/93vMjUjj6UNfP\n",
1483 "HtLjHC6sXhs0vQTz/w48b8i2o+7nlVPsZzXwRQb/zt5Kim2DPt9K4IIB+zl2inOUJEkt4aVbkqTS\n",
1484 "7Op6HPdroZtIkzB/Sooq9wC3AV8FTgc2ky5Tuq/P+3ud16guJV1S9VukwLGddFvv7aQw8xLSpNF/\n",
1485 "jLCvN5CmhC4gfabOfj4DnESaxrl1yD7OIMWj3ycFrRtJ0zp3A98hTf68hDSZ85YF770XeBLptupf\n",
1486 "J/0e7yVNUf0FKVCcWD2H/r+rfn9OvbYbtJ9R1bWfu4HHA68iLXB9FykSXkb6XR1M+t3cMeRYO4Gn\n",
1487 "dO3n7mpf1wCfIk0JSZIkSZKkDB3A3KTHa2d7KpIkSaqLEz2SJEmSJEmZyGox5hDCwaQx57NjjCeP\n",
1488 "sP3/Ad4NvDDG+N4B292PtMjk80hrHdxHukvIWTHGD9Zx7pIkSZIkaXmEEP4X8GTg0aS1/1aS1lX8\n",
1489 "DPDHMcYbe7zn6aTLqR9FWmPwWuAc4A0xxrsnOIdjSJdcP5Z0l9EbSJdbnx5jvGWCjwVkEHpCCAcC\n",
1490 "Lydd8/9E0h9O3+vXQwgnAs8EDgJOqH487Nr6c0i3sL0K+CDpNrRPA94fQtgaY5xmAUZJkiRJkrRM\n",
1491 "Qgi7AX9JWpfwi6T1DXcDHke6W2kIIRwVY7y66z0vJa2ddyspxtwGHEe6BP4JIYQTYow/HOMcngVE\n",
1492 "0tqE55LulPoY4KXAk0MIR8YYh62R2FPrQw/wYODFjL4Q4pHAr466fQjh2aTIcxHwxBjjjurna4Ev\n",
1493 "Aa8IIfxVjPEb4564JEmSJEladjuBPwbeEmP8fueHIYQVpBtB/DJwGtXdOUMIG0k339gOPDrGeF3X\n",
1494 "9mcDv0i6scXbRjl4CGEv4J2km08cE2P8atdrZwCnkm5wceokH671a/TEGC+MMa6MMa5ibkJn0Pan\n",
1495 "dW1/2giHOKV6PK0Tear93Eq6S8mKrm0kSWqbae8YJUmS1Coxxp0xxt/vjjzVz3cxF2t+puulk0iX\n",
1496 "ap3ViTxd27+6evqCMU7hScD6tIu5yFM5jTTlc3IIYaJm0/rQs8CKJdj+KNL/CP6XHq9dUj0ePeZx\n",
1497 "JUmatWtI/ztgFfD/zfZUJEmSGmOv6rE7Ah1VPX5x4cYxxquAm4DDQgh7jniMQfu7E/gGKQQ9fMT9\n",
1498 "zZNb6KlVCGEN8EDgzj4LK91QPT50+c5KkiRJkiQtkZOqx4u6ftb5N/9Nfd5zA2mQZPOIxxhlf4yx\n",
1499 "v3kMPYOtqR5v6/P6XdXjvstwLpIkSZIkaYmEEB4L/BpwC3Bm10trSFf6DGoDKxi9DSxpa8hhMebl\n",
1500 "cF+fn497qdiPXXDBBa6JIEmSJEkZ27Zt28T/ZmyyJv57dtrfdQjhEODTpKDznBjj9h6b1d0Gam8N\n",
1501 "4ETPMLdXj6v7vL7Xgu0kSZIkSVKLhBCOAC4kTdqcFGO8YMEmt5PiS11tYElbgxM9A8QYbw8h3AL8\n",
1502 "RAhh72pRpG4bq8erJj3GbXsfNPH5tcGN194661OQWufuy3v9xwOp/XZedvOsT0GaZ8c3bhi+kdRg\n",
1503 "117zrVmfgvo46e9+adansCzOeepfz/oUpv5dhxCeApwD/BB4cozx8z02uxo4HNgEfLvH6xtJt2y/\n",
1504 "esTDdrbb1Of1qVqDEz3DXUK6I8lxPV47pnrsdUcuARs2rZ31KUits3rL+lmfgrQkVm5dN+tTkObZ\n",
1505 "/dCN7H7oxuEbSg216YBD2HTAIbM+Dam1Qgi/CXwKuBk4pk/kgbk7bp/QYx8PI90h65t9buI07v72\n",
1506 "BQ4lrRN0xYj7m8fQM9xfVo+nhhDu1/lhCGEt8Duk6/c+NIsTawtjjzQ+Y49yZexRExl71HbGHmk8\n",
1507 "IYQ9QgjvJS24fDHw6BjjoBG5jwI7gFNCCD/+fxohhJXA6dXTD/Y4zuUhhG+HEJ6x4KXPkOLS00II\n",
1508 "j1zw2h8AewAfjjHuHOdzdbT+0q0Qwv7Ac6qnB1aPh4QQTq2+vzTGeF7X9kcDR1dPO48nhhB+ovr+\n",
1509 "77v/gGOMMYRwMvA04JshhM8B9wOeAuwHnBlj/Erdnys3ndjjpVzS6FZvWe9lXMrSyq3rvIxLjbP7\n",
1510 "oRu9lEut1ok9Xs4ljeQk4AXAHcDXgVeFEHptd16M8fwY4/UhhNcAfwZ8PYTw6eq9jwMeCXwJeHuP\n",
1511 "9z+8epx396wY410hhF8HPgJcEkI4F/g+cARwFHAlcNqkH671oQc4CDij6/ku0rVzR1TPPwCc1/X6\n",
1512 "zwOv69p2FxCqr12k+9gv/L+OzwZeCpwMPA/4EXAZ8OoY4wfq+Rhl2LBprbFHGoOxR7ky9qiJOpM9\n",
1513 "Bh+12aYDDjH2SMN17mq1N/Bbfbbp3E79fIAY45tCCFeR2sAzSFM3V5Mmet4QY9wxYD+LVEMlNwG/\n",
1514 "C5xYncsNpCmj02OMt4z7oTqyvNVbG3RuR5f7Ysz9GHuk8Rh7lDODj5rI2KO2M/bMVmeB4Nxvr96k\n",
1515 "xZhz/V1PwjV6NBOu2yONZ/WW9a7bo2y5bo+ayHV71HYu1CyVy9CjmTH2SOMz9ihXxh41kbFHOTD2\n",
1516 "SOUx9GimNmxaa/CRxmTsUa6MPWoib8GuHDjdI5XF0KNGMPZI4zH2KFfGHjWVsUc5MPZIZTD0qDGM\n",
1517 "PdJ4jD3K1cqt6ww+aiRjj3LgdI+UP0OPGsXYI43H2KOcGXvURMYe5cLYI+XL0KPGcd0eaTzGHuXM\n",
1518 "2KMmct0e5cLpHilPhh41lrFHGp23X1fOjD1qKmOPcmHskfJi6FGjGXuk8Rh7lCvX7VFTGXuUC2OP\n",
1519 "lA9DjxrP2CONx9ijnBl71EReyqVceCmXlAdDj1rBdXuk8Rh7lDNjj5rK2KNcGHukdjP0qFWMPdLo\n",
1520 "jD3KmbFHTWXsUS6c7pHay9Cj1jH2SKMz9ihnxh41lbFHOTH2SO1j6FErGXuk0Rl7lDMXaVZTuW6P\n",
1521 "cuJ0j9Quhh61lrFHGp23X1fujD1qKmOPcmLskdrB0KNWc5FmaTzGHuXM2KOmMvYoJ8YeqfkMPcqC\n",
1522 "sUcanbFHOTP2qKmMPcqJl3JJzWboUTaMPdLojD3KmbFHTeW6PcqNsUdqJkOPsmLskUZn7FHOXKRZ\n",
1523 "TWbsUU6c7pGax9Cj7LhujzQ6Y49yZ+xRUxl7lBtjj9Qchh5ly9gjjcbYo9wZe9RUxh7lxukeqRkM\n",
1524 "PcqasUcajbdfV+6MPWoq1+1Rjow90mwZepQ9Y480OmOPcua6PWoyY49y43SPNDuGHhXBdXuk0Rl7\n",
1525 "lDtjj5rK2KMcGXuk5WfoUVGMPdJojD3KnbFHTeWlXMqRsUdaXoYeFcfYI43G2KPcGXvUZMYe5cZL\n",
1526 "uaTlY+hRkYw90miMPcqdsUdNZuxRjow90tIz9KhYrtsjjcbYo9y5SLOazNijHDndIy0tQ4+KZ+yR\n",
1527 "hvP26yqBsUdN5bo9ypWxR1oahh4JY480KmOPcmfsUZMZe5Qjp3uk+hl6pIqxRxqNsUe5M/aoyYw9\n",
1528 "ypWxR6qPoUfqYuyRRmPsUe6MPWoyY49yZeyR6mHokRZwkWZpNMYe5c5FmtVkrtujXHkplzQ9Q4/U\n",
1529 "h7FHGs7YoxIYe9Rkxh7lytgjTc7QIw1g7JGGM/aoBMYeNZmxR7lyukeajKFHGsLYIw3n7ddVAmOP\n",
1530 "msxLuZQzY480HkOPNALX7ZFGY+xR7ly3R01n7FGunO6RRmfokcZg7JGGM/aoBMYeNZmxRzkz9kjD\n",
1531 "GXqkMRl7pOGMPSqBsUdNZuxRzpzukQYz9EgTMPZIwxl7VAJjj5rMdXuUO2OP1JuhR5qQ6/ZIwxl7\n",
1532 "VAJjj5rO2KOcGXukxQw90pSMPdJgxh6VwEWa1XTGHuXMS7mk+Qw9Ug2MPdJg3n5dpTD2qMm8lEu5\n",
1533 "M/ZIiaFHqomxRxrO2KMSGHvUdMYe5czpHsnQI9XKdXuk4Yw9KoGxR03ndI9yZ+xRyQw90hIw9kiD\n",
1534 "GXtUAmOP2sDYo5w53aNSGXqkJWLskQYz9qgELtKsNnC6R7kz9qg0hh5pCRl7pMGMPSqFsUdtYOxR\n",
1535 "zow9KomhR1pixh5pMO/IpVIYe9QGTvcoZ17KpVIYeqRl4CLN0nDGHpXA2KO2MPYoZ8Ye5c7QIy0j\n",
1536 "Y480mLFHJXDdHrWFsUc5c7pHOTP0SMvM2CMNZuxRKYw9agMv5VLujD3KkaFHmgFjjzSYsUelMPao\n",
1537 "LYw9ypnTPcqNoUeaEdftkQYz9qgUxh61hdM9yp2xR7kw9EgzZuyR+jP2qBTGHrWJsUc5c7pHOTD0\n",
1538 "SA1g7JH68/brKoWLNKtNnO5R7ow9ajNDj9QQxh5pMGOPSmHsUZsYe5QzY4/aytAjNYjr9kiDGXtU\n",
1539 "CmOP2sTYo5x5KZfayNAjNZCxR+rP2KNSGHvUJl7KpdwZe9Qmhh6poYw9Un/GHpXC2KO2MfYoZ073\n",
1540 "qC0MPVKDGXuk/ow9KoWLNKttnO5R7ow9ajpDj9Rwxh6pP2OPSmLsUdsYe5Qzp3vUZIYeqQVcpFnq\n",
1541 "z9uvqyTGHrWN0z3KnbFHTWTokVrE2CP1Z+xRKYw9aiNjj3Jm7FHTGHqkljH2SP0Ze1QK1+1RGxl7\n",
1542 "lDMv5VKTGHqkFjL2SP0Ze1QSY4/axku5lDtjj5rA0CO1lOv2SP0Ze1QSY4/ayNijnBl7NGuGHqnl\n",
1543 "jD1Sb8YelcTYozZyukeSloahR8qAsUfqzdijkhh71FbGHkmql6FHyoSxR+rN26+rJC7SrLZyukeS\n",
1544 "6mPokTLiuj1Sf8YelcTYo7Yy9kjS9Hab9QlIqt+GTWu58dpbZ30aUuOs3rKeuy/fPuvTkJbFyq3r\n",
1545 "2HnZzbM+DWlsux+6kR3fuGHWpyGpECGEg4HLgLNjjCf3eP1C4NgRdvX4GOMXRjzmB4DnDdlsS4zx\n",
1546 "ilH2t5ChR8qUsUfqzdijkhh71FadyR6Dj6SlEEI4EHg5sAF4Iulqp119Nv8I8OUBuzseePSA9w/y\n",
1547 "UeA7fV67ZYL9AYYeKWvGHqk3Y49KYuxRmzndI2mJPBh4MSPEmRjjWf1eCyGsIU3mbAe+MsF5vDvG\n",
1548 "+LkJ3jeQoUfKXGfNHoOPNJ+xRyXprNlj8FEbOd0jqW4xxgup1iwOIRwHfH7CXb0aeBDwGzHGO+o5\n",
1549 "u+m5GLNUCBdplhZzgWaVxkWa1WYu1CxpiayY5E0hhAOA3wauAN61nMcexokeqSBeyiUt1ok9Tveo\n",
1550 "FF7KpTZzukdSg7wB2B34vRjjjybcx9+FEHYH7gVuBC4C3hRjvHSaE3OiRyqMkz1Sb073qCRO9qjt\n",
1551 "nO6RNEshhKOBAPxzjPETE+ziBuBc4IPAmcDfAqtI6/38awjhadOcnxM9UoGc7JF6c90elcR1e9R2\n",
1552 "LtQsaRZCCCuAt5AWcj51kn3EGF/TY78rgdcBfwCcFUJ4SIxx5yT7d6JHKtSGTWud7pF6cLJHpXG6\n",
1553 "R222+6Ebne6RtNx+CXgM8Dcxxi/VtdMY484Y4+uAa0i3fX/EpPtyokcqnNM90mJO9qg0rtujtnO6\n",
1554 "R5qNTQccMutTWFYhhNXAnwA7gFct0WFuAQ4A9p50B070SHKyR+rByR6VxsketZ3TPZKWwanA/sA7\n",
1555 "Y4xX1b3zEMJewMHAfcB/TLofJ3okAU72SL042aPSONmjHDjdI2kphBA2AL8L3AqcPsL2l5PW8XlV\n",
1556 "94LNIYTDgGOB98YY7+r6+UrSwsx7AzHG+N+TnquhR9KPdSZ7DD7SHG+/rtK4SLNy4G3YJQ0SQtgf\n",
1557 "eE719MDq8ZAQQmdx5UtjjOcteNsfA3sBp8UYbxnhMA+vHvdd8PMHkILO60MIFwNXV9scVZ3LvwO/\n",
1558 "Mepn6cXQI2kRp3ukxZzuUWmc7lEOnO6R1MdBwBldz3cBhwNHVM8/APw49IQQDgdOBq4lRZpR7erx\n",
1559 "s68CrwFOALYAx1U/v5J01603xxjvHOMYi6yY5s2a3AUXXLAL4La9D5r1qUh9GXukxYw9Ko2xR7kw\n",
1560 "+Gg5Hfn2RwGwbdu2LP/N3fn37L/8+tdmfSrZ/64n4WLMkvpykWZpMRdpVmlcpFm5cKFmSaUw9Ega\n",
1561 "aMOmtQYfaQFjj0pj7FEujD2SSmDokTQSY480n7FHpVm5dZ3BR1nwNuyScmfokTQyY480n7FHJTL2\n",
1562 "KBfGHkm5MvRIGouxR5pv9Zb1Bh8Vx9ijXDjdIylHhh5JY3PdHmkxY49KY+xRTow9knJi6JE0MWOP\n",
1563 "NJ+xR6Vx3R7lxOkeSbkw9EiairFHms/YoxIZe5QTY4+ktjP0SJqasUeaz9ijEhl7lBNjj6Q2M/RI\n",
1564 "qoWxR5rP2KMSGXuUEy/lktRWhh5JtXGRZmk+Y49KZOxRbow9ktrG0DNjm/dbM+tTkGpn7JHmePt1\n",
1565 "lchFmpUbp3sktYmhpwGMPcqRsUeaz9ijEhl7lBtjj6Q2MPQ0hLFHOTL2SPMZe1QiY49y43SPpKYz\n",
1566 "9DSIsUc5ct0eaT5jj0pk7FGOjD2SmsrQ0zCb91tj8FGWjD3SHGOPSmTsUY6MPZKayNDTUMYe5cjY\n",
1567 "I80x9qhELtKsHHkpl6SmMfQ0mLFHOTL2SHOMPSqVsUc5MvZIagpDT8N5KZdy5Lo90hxvv65SGXuU\n",
1568 "I6d7JDWBoacljD3KkbFHmmPsUYmMPcqVsUfSLBl6WsTYoxwZe6Q5xh6VyHV7lCuneyTNiqGnZbyU\n",
1569 "Szky9khzjD0qlbFHuTL2SFpuhp6WMvYoN8YeaY6xR6Uy9ihXxh5Jy8nQ02LGHuXGRZqlOcYelcrY\n",
1570 "o1x5KZek5WLoaTljj3Jk7JESY49KZexRzow9kpaaoScDrtujHBl7pMTbr6tULtKsnDndI2kpGXoy\n",
1571 "YuxRbow90hxjj0pl7FHOjD2SloKhJzPGHuXGdXukOcYelcrYo5w53SOpboaeDHkpl3Jk7JESY49K\n",
1572 "ZexR7ow9kupi6MmYsUe5MfZIibFHpTL2KHdO90iqg6Enc8Ye5cbYIyXGHpXKRZpVAmOPpGkYegpg\n",
1573 "7FFuXLdHSow9KpmxR7kz9kialKGnEK7boxwZeyRvv66yGXuUOy/lkjQJQ09hjD3KjbFHSow9KpWx\n",
1574 "RyUw9kgah6GnQMYe5cbYIyXGHpXKdXtUAqd7JI3K0FMoL+VSbly3R0qMPSqZsUclMPZIGsbQUzhj\n",
1575 "j3Jj7JGMPSqbsUclcLpH0iCGHhl7lB1jj2TsUdmMPSqFsUdSL4YeAcYe5cfYIxl7VDZjj0ph7JG0\n",
1576 "kKFHP+a6PcqNsUfy9usqm4s0qxReyiWpm6FHixh7lBMXaZYSY49KZuxRKYw9ksDQoz6MPcqNsUcy\n",
1577 "9qhsxh6VwukeSYYe9eWlXMqNsUcy9qhsxh6VxNgjlcvQo6GMPcqJsUcy9qhsxh6VxOkeqUyGHo3E\n",
1578 "2KOcGHskY4/K5iLNKo2xRyqLoUcjM/YoJy7SLBl7JGOPSmLskcph6NFYXLdHuTH2qHTGHpXO2KOS\n",
1579 "eCmXVAZDjyZi7FFOjD0qnbFHpTP2qDTGHilvhh5NzNijnBh7VLrVW9YbfFQ0Y49KY+yR8mXo0VSM\n",
1580 "PcqJsUdyukdlM/aoNMYeKU+GHk3N2KOcGHskY4/KZuxRaYw9Un4MPaqFsUc5MfZIxh6Vzdij0rhI\n",
1581 "s5QXQ49qY+xRTow9krFHZTP2qETGHikPhh7VytijnBh7JGOPymbsUYmMPVL7GXpUO2OPcmLskYw9\n",
1582 "KpuxRyUy9kjtZujRkjD2KCfGHsnYo7IZe1QiY4/UXoYeLRljj3Ji7JGMPSqbsUclcpFmqZ0MPVpS\n",
1583 "xh7lxNgjGXtUtpVb1xl8VCRjj9Quhh4tOWOPcrJh01qDj4pn7FHpjD0qkbFHag9Dj5aFsUe5Mfao\n",
1584 "dMYelc7YoxIZe6R2MPRo2Rh7lBtjj0pn7FHpjD0qkbFHaj5Dj5aVsUe5MfaodMYelc7YoxIZe6Rm\n",
1585 "M/Ro2Rl7lBtjj0pn7FHpjD0qkXfkkprL0KOZMPYoN8Yelc7Yo9IZe1QqY4/UPIYezYyxR7kx9qh0\n",
1586 "xh6VztijUhl7pGYx9GimNu+3xuCjrBh7VDpjj0pn7FGpjD1Scxh61AjGHuXE2KPSGXtUOmOPSmXs\n",
1587 "kZrB0KPGMPYoJ8Yelc7Yo9IZe1QqF2mWZs/Qo0Yx9ignxh6Vztij0hl7VDJjjzQ7hh41jrFHOTH2\n",
1588 "qHTGHpW1CNyBAAAgAElEQVTO2KOSGXuk2TD0qJGMPcqJsUelM/aodMYelczYIy2/3WZ9AnULIRwM\n",
1589 "XAacHWM8ecB2TwdeBjwK2AO4FjgHeEOM8e4e2+8ccugvxRiPmvjEtcjm/dZw9fdun/VpSLXoxJ4b\n",
1590 "r711xmcizcbqLeu5+/Ltsz4NaWZWbl3HzstunvVpSDOx+6Eb2fGNG2Z9GlJPozSEEMKFwLFDdrVn\n",
1591 "jHHHGMc9BngV8FhgH+AG4FPA6THGW0bdTy9ZhJ4QwoHAy4ENwBNJk0q7Bmz/UuAtwK2kX+RtwHHA\n",
1592 "a4EnhBBOiDH+sMdbbwfe1We31078AdSXsUe52bBprbFHxepM9hh8VCpjj0rWmewx+KgJxm0IXd5D\n",
1593 "6gi9/GiM4z8LiMA9wLnAfwGPAV4KPDmEcGSMceJ/NGQReoAHAy9mhD+YEMJG4E+B7cCjY4zXVT9f\n",
1594 "AZwN/CLwIuBtPd7+gxjjK+s6aY3G2KPcGHtUOqd7VDJjj0rndI8aYuSGsMCfxhivmubAIYS9gHcC\n",
1595 "9wLHxBi/2vXaGcCpwO9XjxPJYo2eGOOFMcaVMcZVwAlDNj+JdKnWWZ3IU+1jF/Dq6ukLluZMNSnX\n",
1596 "7FFuXLdHpXPdHpXMNXtUOtft0ayN2RDq9iRgfTqNuchTOY005XNyCGHiXpNF6FlgxZDXO+vofHHh\n",
1597 "C1WZuwk4LISwZ90npukYe5QbY49KZ+xRyYw9Kp2xRw0yrCFMum0/g5rEncA3SCHo4ZMeIJdLt8bx\n",
1598 "0Orxpj6v30D6pW4Gvr3gtY0hhHtJv7c7gP8APg6cGWO8YwnOVQt4GZdy42VcKp2XcalkndjjpVwq\n",
1599 "lZdxqYUuCyHsTpq6uQ44H3hjjPGaMfYxSpOA1CQun+Qkc5zoGWYN6Tq82/q8fhep0u274OdfJd2V\n",
1600 "693AWcDFwFbgdOBLIYT7L8nZahEne5QbJ3tUOid7VDqne1Sy3Q/d6HSP2uAq0pDH+4E/J93U6SeA\n",
1601 "lwBfCyE8eox9df5BO6hJwOImMbISJ3o67uvz856jWDHGn1n4sxDCeuA80i3aXwX8Xm1np4Gc7FFu\n",
1602 "nOxR6ZzsUelcpFmlc7pHTRZj/OWFPwsh7AG8g7TG79uAI8fc7VhNYhwlTvTcTvrFre7z+l5d2w0U\n",
1603 "Y9wOvKx6utwLOBXPyR7lxskelc7JHpXOyR6VzsketUmM8V7SRM89wGOqu2mNotMapm4S/ZQ40XM1\n",
1604 "cDiwicVr8ABsBHZW243ilupxn+lPTeNyske5cbJHpXOyR6Vzskelc7KnXUqPczHGe0MId5Hu7L0P\n",
1605 "c5ddDdJpDZv6vN75pU58G/cSJ3ouqR4XTeCEEB5GWoj5mzHGu0fc3+HVY69opGXgZI9y42SPSudk\n",
1606 "j0rnZI9KV3o8UHuEEB5MWqvnlhhjv8WVFxrUJPYFDiUNlFwx6XmVGHo+CuwATgkh/Pj/glT3qD+9\n",
1607 "evrB7jeEEF4UQjh24Y5CCPsDryct7vyeJTtjDWXsUW6MPSqdsUelM/aodC7SrKYIIWwLIZwcQrjf\n",
1608 "gp/vCbyrevq+Hu+7PITw7RDCMxa89BngZuBpIYRHLnjtD0jTQR+OMe6c9JyzuHSrCi7PqZ4eWD0e\n",
1609 "EkI4tfr+0hjjeQAxxutDCK8B/gz4egjh06RbpT8OeCTwJeDtCw5xJPDOEMI1pHvdfx94CLCNdF3d\n",
1610 "/40x/sNSfDaNzsu4lJtO7PFSLpXKy7hUOi/jkryUS0tjnIYA7E8KOW8JIVxMuq36OuBY4KdIEzqv\n",
1611 "63GYh1eP8+6eFWO8K4Tw68BHgEtCCOeSGsMRwFHAlcBp03y+LEIPcBBwRtfzXaRLqo6onn+AdHcs\n",
1612 "AGKMbwohXAW8FHgGqZhdTZroeUOMcceC/b8duBt4DPB44IGkW6FdDLwjxnhuzZ9HEzL2KEeu26OS\n",
1613 "GXtUOmOPZOzRkhinIXyWdCXPsdU2TyJdJfTtah/viDH2u4PWrl4/jDHGEMJNwO8CJwJ7AzcAZwKn\n",
1614 "xxhv6fW+UU192y5N5oILLtgF8MADDx+2qSZk8FFujD0qmbFHpTP2SLQq9hz59kcBsG3btiz/zd35\n",
1615 "9+xX3jX7//98xIvS5d65/q4nUeIaPSqE6/YoN67bo5K5Zo9K55o9kos0S6My9Chrxh7lxtijkhl7\n",
1616 "VDpjj+QizdIoDD3KnrFHuTH2qGTGHpXO2CMlxh6pP0OPimDsUW6MPSqZsUelM/ZIibFH6s3Qo2IY\n",
1617 "e5QbY49KZuxR6Yw9UmLskRYz9Kgoxh7lxtijkhl7VDpjj5QYe6T5DD0qjrFHuTH2qGTGHpXO2CMl\n",
1618 "xh5pjqFHRTL2KDfGHpVs9Zb1Bh8VzdgjJd6RS0oMPSqWsUe5MfaodMYelczYI80x9qh0hh4Vzdij\n",
1619 "3Bh7VDpjj0q2cus6g49UMfaoZIYeFc/Yo9xs2LTW4KOiGXtUOmOPlBh7VCpDj4SxR3ky9qhkxh6V\n",
1620 "ztgjJcYelcjQI1WMPcqRsUclM/aodMYeKXGRZpXG0CN1MfYoR8YelczYo9IZe6Q5xh6VwtAjLWDs\n",
1621 "UY6MPSqZsUelM/ZIc4w9KoGhR+rB2KMcGXtUMmOPSmfskeYYe5Q7Q4/Uh7FHOTL2qGTGHpXO2CPN\n",
1622 "MfYoZ4YeaQBjj3Jk7FHJjD0qnbFHmuMizcqVoUcawtijHBl7VDJjj0pn7JHmM/YoN4YeaQSb91tj\n",
1623 "8FF2jD0qmbFHpVu5dZ3BR+pi7FFODD3SGIw9yo2xRyUz9khO90jdjD3KhaFHGpOxR7kx9qhkxh7J\n",
1624 "2CN1M/YoB4YeaQLGHuXG2KOSGXskY4/UzUWa1XaGHmlCxh7lZsOmtQYfFcvYIxl7pIWMPWorQ480\n",
1625 "BWOPcmTsUamMPZKxR1rI2KM2MvRIUzL2KEfGHpXK2CMZe6SFjD1qG0OPVANjj3Jk7FGpjD2St1+X\n",
1626 "FjL2qE0MPVJNjD3KkbFHpTL2SImxR5rjIs1qC0OPVCNjj3Jk7FGpjD1SYuyR5jP2qOkMPVLNjD3K\n",
1627 "kbFHpVq9Zb3BR8LYIy1k7FGTGXqkJWDsUY6MPSqZsUcy9kgLGXvUVIYeaYkYe5QjY49KZuyRjD3S\n",
1628 "QsYeNZGhR1pCxh7lyNijkhl7JGOPtJCLNKtpDD3SEjP2KEfGHpXM2CN5+3WpF2OPmsLQIy0DY49y\n",
1629 "ZOxRyYw9UmLskeYz9qgJDD3SMjH2KEfGHpXM2CMlxh5pPmOPZs3QIy0jY49ytGHTWoOPimXskRJj\n",
1630 "jyQ1h6FHWmbGHuXK2KNSGXukxNgjSc1g6JFmYPN+aww+ypKxR6Uy9kiJsUeSZs/QI82QsUc5Mvao\n",
1631 "VMYeKfGOXJI0W4YeacaMPcqRsUelMvZIc4w9kjQbhh6pAYw9ypGxR6Uy9khzjD2StPwMPVJDGHuU\n",
1632 "I2OPSmXskeYYeyRpeRl6pAYx9ihHxh6VytgjzTH2SNLyMfRIDWPsUY6MPSqVsUeaY+yRpOVh6JEa\n",
1633 "yNijHBl7VCpjjzTH2CNJS8/QIzWUsUc5MvaoVMYeaY63X5ekpWXokRrM2KMcGXtUKmOPNJ+xR5KW\n",
1634 "hqFHajhjj3Jk7FGpjD3SfMYeSaqfoUdqAWOPcrRh01qDj4pk7JHmM/ZIUr0MPVJLGHuUK2OPSrR6\n",
1635 "y3qDj9TF2CNJ9TH0SC1i7FGujD0qlbFHmmPskaR6GHqkljH2KFfGHpXK2CPNMfZI0vQMPVILGXuU\n",
1636 "K2OPSmXskeZ4+3VJmo6hR2opY49yZexRqYw90nzGHkmajKFHajFjj3Jl7FGpjD3SfMYeSRqfoUdq\n",
1637 "OWOPcmXsUamMPdJ8xh5JGo+hR8qAsUe5MvaoVMYeaT5jjySNztAjZWLzfmsMPsqSsUelMvZI8xl7\n",
1638 "JGk0hh4pM8Ye5cjYo1IZe6T5jD2SNJyhR8qQsUc5MvaoVMYeaT5vvy5Jgxl6pEwZe5QjY49KZeyR\n",
1639 "FjP2SFJvhh4pY8Ye5WjDprUGHxXJ2CMtZuyRpMUMPVLmjD3KlbFHJTL2SIsZeyRpPkOPVABjj3Jl\n",
1640 "7FGJjD3SYsYeSZpj6JEKYexRrow9KpGxR1rM2CNJiaFHKoixR7ky9qhExh5pMWOPJBl6pOIYe5Qr\n",
1641 "Y49KZOyRFvP265JKZ+iRCmTsUa6MPSqRsUfqzdgjqVSGHqlQxh7lytijEhl7pN6MPZJKZOiRCmbs\n",
1642 "Ua6MPSqRsUfqzdgjqTSGHqlwxh7lytijEhl7pN6MPZJKstusT0DS7G3ebw1Xf+/2WZ+GVLsNm9Zy\n",
1643 "47W3zvo0pGXViT13X759xmciNcvKrevYednNsz4NSQ0TQjgYuAw4O8Z4co/X1wC/AmwDDgMeBOwA\n",
1644 "/gP4CHBmjPHeMY/5AeB5QzbbEmO8Ypz9dhh6JAHGHuXL2KNSrd6y3tgjLWDskQQQQjgQeDmwAXgi\n",
1645 "6WqnXX02fyzwZuAHwEXANcBa4MnAnwK/EEI4PsZ43wSn8lHgO31eu2WC/QGGHkldjD3KVecyLoOP\n",
1646 "SmPskRbrXMZl8JGK9mDgxfSPO91uBn4V+FCMcUfnhyGEfYB/Bo4mTee8b4LzeHeM8XMTvG8gQ4+k\n",
1647 "eYw9ypnTPSqRsUfqzekeqVwxxgup1iwOIRwHfH7Atl8Dvtbj53eEEN5Pmvb5GSYLPUvCxZglLeIC\n",
1648 "zcqZizSrRC7SLPXmIs2SgBVTvHev6vH7Mzh2X070SOrJyR7lzMkelcjJHqk3J3skTSKEsAII1dOL\n",
1649 "JtzN34UQdgfuBW6s9vOmGOOl05ybEz2S+tq83xqne5QtJ3tUIid7pN6c7JE0gZeR7sL1zzHGC8Z8\n",
1650 "7w3AucAHgTOBvwVWkdb6+dcQwtOmOTEneiQN5XSPcuVkj0rkZI/Um5M9kkYVQngO8EZSsDlp3PfH\n",
1651 "GF/TY58rgdcBfwCcFUJ4SIxx5yTnZ+iRNBJjj3Jl7FGJjD1Sb8YeaTwlTsOFEE4B3gt8F3h8jPG7\n",
1652 "dey3ijqvCyGcDGwCHgFcNsm+vHRL0si8jEu58jIulcjLuKTeVm5dV+Q/XiUNF0J4LfB+4NvA0THG\n",
1653 "K5fgMLeQFmnee9IdGHokjcXYo1wZe1QiY4/Un7FHUkcIYY8QwoeAPwT+Efi5GON1S3CcvYCDgfuA\n",
1654 "/5h0P166JWlsXsalXHkZl0rkZVxSf17KJSmEsJG0WPJjgD8HXh5j/NEI77sc2AW8Ksb4ia6fHwYc\n",
1655 "C7w3xnhX189XkhZm3huIMcb/nvScDT2SJmLsUa6MPSqRsUfqz9gj5SeEsD/wnOrpgdXjISGEU6vv\n",
1656 "L40xnld9fzop8lwJ7ADeEEKgh3fEGK/qev7w6nHfBds9gBR0Xh9CuBi4utrmqOpc/h34jUk+V4eh\n",
1657 "R9LEjD3KlbFHJTL2SP0Ze6TsHASc0fV8F3A4cET1/ANAJ/SsqF4/EHhFn/3tAj4FXNXj5wt9FXgN\n",
1658 "cAKwBTiu+vmVpLtuvTnGeOeIn6OnFdO8WZO74IILdgE88MDDZ30q0tSMPcqVsUclMvZI/Rl7NKoj\n",
1659 "XpTWQNu2bVuW/+bu/Hv2a//Uq2Msr0cdk37Fuf6uJ+FizJKm5gLNytWGTWtdpFnFcYFmqT8XaJbU\n",
1660 "BoYeSbUw9ihnxh6Vxtgj9eft1yU1naFHUm2MPcqZsUelMfZIgxl7JDWVoUdSrYw9ypmxR6Ux9kiD\n",
1661 "GXskNZGhR1LtjD3KmbFHpTH2SIMZeyQ1jaFH0pIw9ihnxh6VxtgjDWbskdQkhh5JS8bYo5wZe1Sa\n",
1662 "1VvWG3ykAYw9kprC0CNpSRl7lDNjj0pk7JH6M/ZIagJDj6QlZ+xRzow9KpGxR+rP269LmjVDj6Rl\n",
1663 "YexRzow9KpGxRxrM2CNpVgw9kpaNsUc5M/aoRMYeaTBjj6RZ2G0pdhpCWAM8BlgP7BFj/FDXa+uA\n",
1664 "vYD7YozfXYrjS2quzfut4erv3T7r05CWxIZNa7nx2ltnfRrSslq9ZT13X7591qchNdbKrevYednN\n",
1665 "sz4NSQWpdaInhLBvCOEvgO3A+cDZwPsXbHYkcA1wbQhhQ53Hl9QOTvYoZ072qERO9kiDOdkjaTnV\n",
1666 "FnpCCHsCnwN+pdrvFcCuhdvFGD8NfB5YBTy3ruNLahdjj3K2YdNag4+KY+yRBjP2SFoudU70/CZw\n",
1667 "BCnw/HSM8RHAD/ts+57q8X/UeHxJLWPsUe6MPSqNsUcazNgjaTnUGXp+sXp8eYzxiiHbfq563Frj\n",
1668 "8SW1kLFHuTP2qDTGHmkwY4+kpVZn6NlCulTrn0fY9qZq2/vXeHxJLWXsUe6MPSqNsUcabOXWdQYf\n",
1669 "SUumztCzGyne3DHCtvsAK4A7azy+pBYz9ih3xh6VxtgjDWfskbQU6gw915HizYEjbPuE6vHKGo8v\n",
1670 "qeWMPcqdsUelMfZIwxl7JNWtztDzGVLoecmgjUIIewN/VD39bI3HlySp8Yw9Ko2xRxrO2COpTnWG\n",
1671 "njcC9wAvCSH8VghhVfeLIYQVIYQTSGv4HEK6bOvtNR6/lfZfv/esT0FqFKd6VAJjj0pj7JGGM/ZI\n",
1672 "qkttoSfG+B3guaR1et4KfA+4H7AihPBV4GbgfOBQ4D7g+THGG+s6fpsZe6T5jD0qgbFHpTH2SMMZ\n",
1673 "eyTVoc6JHmKMnwSOAv4JeCDpUi6Aw4AHVM+/DmyLMX6szmO3nbFHms/YoxIYe1QaY480nLFH0rR2\n",
1674 "q3uHMcavAMeGEB4KHA1sAFaRbqn+rzHGS+s+Zi72X78312/3RmRSx+b91nD1926f9WlIS2rDprXc\n",
1675 "eO2tsz4Nadms3rKeuy/fPuvTkBpt5dZ17Lzs5lmfhqSWqj30dMQYrwKuWqr9SyqDsUclMPaoNMYe\n",
1676 "abjOZI/BR9K4ags91eLL7yCty/OJGOOn+mz3FCBQLdwcY9xV1znkwKkeaTFjj0rQuYzL4KNSdC7j\n",
1677 "MvhIgzndI2lcda7R8wvAC4ETgc8P2O4i4OeBXwX+R43Hz4br9UiLuWaPSuG6PSqN6/ZIw7luj6Rx\n",
1678 "1Bl6Tq4e3xpj7Puf3mOMdwBvJi3M/Pwaj58VY48klcvYo9IYe6ThjD2SRlVn6DmKdGv1vxlh27+t\n",
1679 "Ho+s8fjZMfZI8znVo5IYe1QaY480nLFH0ijqDD0PBHbGGK8eYdvvkKLQA2s8vqQCGHtUEmOPSmPs\n",
1680 "kYYz9kgaps7Q8wNgZQhh3xG23Yd06dZtNR4/S071SIsZe1QSY49KY+yRhjP2SBqkztDzFVK8CSNs\n",
1681 "+6zq8Zs1Hj9bxh5pMWOPSmLsUWmMPdJwxh5J/dQZej5UPf5ZCOGofhuFEH4WeGP19Jwaj581Y4+0\n",
1682 "mLFHJTH2qDTGHmm4lVvXGXwkLbJbjfs6G3gBcALwhRDCucAFwPWk9XgeDGwj3YZ9FfB14H01Hj97\n",
1683 "+6/fm+u33znr05AaZfN+a7j6e31v9CdlZcOmtdx47a2zPg1p2azesp67L98+69OQGm/l1nXsvOzm\n",
1684 "WZ+GpIaobaInxrgTeDbw96SA9Ezg7cAngU9V3z+TFHm+DDw1xrijruNLKpeTPSqJkz0qjZM90mic\n",
1685 "7JHUUeelW8QYfxBjfBrwNOCjpLtr3Vt93QB8HHgucHSM8bt1HrsUXsIl9WbsUUmMPSqNsUcajbFH\n",
1686 "EtR76daPxRj/njTZoyXgJVySJC/jUmm8jEsajZdxSap1okfLx8keaTGnelQaJ3tUGid7pNE42SOV\n",
1687 "zdAjKSvGHpVmw6a1Bh8VxdgjjcbYI5Vr4ku3QgifB+6NMT6pev5+0t21xhJj/OVJz6F0XsIl9ead\n",
1688 "uFQiL+VSSbyMSxqNl3FJZZpmjZ7jgHu6np8ywT52AYaeKRh7pN6MPSqRsUclMfZIo+lM9hh8pHJM\n",
1689 "E3ouIt1Nq+OvJ9jH2BNAWszYI/Vm7FGJjD0qibFHGp3TPVI5Jg49McbjFzz/31OfjSZm7JF6M/ao\n",
1690 "RMYelcTYI43O2COVobbFmEMIJ4YQnlrX/iSpLi7QrBK5QLNKsnrLehdplkbkIs1S/uq869bHgVjj\n",
1691 "/jQmb7kuSepm7FFpjD3SaIw9Ut7qDD2ratyXJmTskXpzqkelMvaoNMYeaTTGHilfdYaea4A9Qgir\n",
1692 "a9ynJmDskXoz9qhUxh6VxtgjjcbYI+WpztDzKWAFsK3GfWpCxh6pN2OPSmXsUWmMPdJojD1SfuoM\n",
1693 "PWcCdwOvrnGfklQ7Y49KZexRaYw90mhWbl1n8JEyMvHt1Xt4KvBvwDEhhHcAXxvlTTHGd9d4Duri\n",
1694 "Ldel/rztukrlrddVGm+/Lo3O269Leagz9Lyz6/tfG/E9uwBDzxIy9kj9GXtUqs5kj8FHpTD2SKMz\n",
1695 "9kjtV2fo+c4E79lV4/HVh7FH6s/Yo5I53aOSGHuk0Rl7pHarLfTEGA+oa1+qn7FH6s/Yo5IZe1QS\n",
1696 "Y480OmOP1F51LsYsSa3lAs0qmYs0qyQu0CyNzgWapXaqZaInhLA7cBCwD3BdjPHGOvarejnVIw3m\n",
1697 "ZI9K5mSPSuJkjzQ6J3uk9plqoieEsCqE8IfA94BLgS8C14cQvhRCOH7601Pd9l+/96xPQWo0J3tU\n",
1698 "Mid7VBIne6TROdkjtcu0l269G3gtsBZY0fX1GOD8EMJzp9y/loCxRxrM2KOSGXtUEmOPNLqVW9cZ\n",
1699 "fKSWmDj0hBAeD7ygevqXwOOAnwYCcAmwCnhPCGHjtCep+hl7pMGMPSqZsUclMfZI4zH2SM03zRo9\n",
1700 "v1w9nhNjPKXr598KIXwS+EdS/Pkt4HenOI4kzYRr9qhkrtmjkrhmjzQe1+2Rmm2aS7ceWz2+deEL\n",
1701 "Mcb7gD+qnj5himNoCTnVIw3nZI9K5mSPSuJkjzQeJ3uk5pom9GwEdgH/1uf1L1ePm6c4hpaYsUca\n",
1702 "ztijkhl7VBJjjzQeY4/UTNOEntXAjmp6Z5EY4w+AncC+UxxDy8DYIw1n7FHJjD0qibFHGo+xR2qe\n",
1703 "ae+6tWvI6/fVcAwtA2OPNJyxRyUz9qgkq7esN/hIYzD2SM0yzWLMACtCCA/v91r1xYBtiDFeMeU5\n",
1704 "SNKycYFmlawTe1ykWaVwkWZpdC7QLDXHtKFnD+DbA15fUT322mYFaSJo1ZTnoJrsv35vrt9+56xP\n",
1705 "Q2o8Y49K5x25VBJjjzS6zmSPwUearTouq1ox4GvQNizYRg3gJVzSaLyMS6XzUi6VxMu4pPF4KZc0\n",
1706 "W9NM9Dy0trNQozjZI43GyR6VzskelcTJHmk8Xsolzc7EoSfGeE2N5yFJrWTsUemMPSqJsUcaj7FH\n",
1707 "mg3viKWevIRLGp2Xcal0XsalkngZlzQeL+OSlp+hR30Ze6TRGXtUOmOPSmLskcZj7JGWl6FHAxl7\n",
1708 "pNEZe1Q6Y49KYuyRxmPskZbPtLdXb5QQwsHAZcDZMcaTB2z3dOBlwKNIt4i/FjgHeEOM8e4e298P\n",
1709 "+E3gecDDgPuAbwFnxRg/WPfnaBoXZ5ZG55o9Kp1r9qgkrtkjjcc1e9RES9URRjjuMcCrgMcC+wA3\n",
1710 "AJ8CTo8x3jLu/rq1fqInhHBgCOHtIYS/Bf6N9Jl2Ddj+pcDHgcNIv8T3Aj8EXgt8too6C50DvJH0\n",
1711 "y/8g8FHgAOD9IYQz6vs0knLgZI9K52SPSuJkjzSelVvXOd2jmVumjjDo+M8CvgAcD1wAvAv4L+Cl\n",
1712 "wCUhhKn+x1QOEz0PBl7MgD+UjhDCRuBPge3Ao2OM11U/XwGcDfwi8CLgbV3veTbwDOAi4Ikxxh3V\n",
1713 "z9cCXwJeEUL4qxjjN+r8UE3jVI80Hid7VDone1QSJ3uk8Tndoxlb0o4wZH97Ae8E7gWOiTF+teu1\n",
1714 "M4BTgd+vHifS+omeGOOFMcaVMcZVwAlDNj+JNGJ1VucPp9rHLuDV1dMXLHjPKdXjaZ3IU73nVuAN\n",
1715 "wIqubbLmej3SeJzsUemc7FFJnOyRxudkj2ZlGTrCIE8C1qddzEWeymnAPcDJIYSJe03rQ88CK4a8\n",
1716 "flT1+MWFL8QYrwJuAg4LIaxe8J5dwL/02N8l1ePRY55naxl7pPEYe1S6DZvWGnxUDGOPND5jjxqg\n",
1717 "ro6w54jHG7S/O4FvkELQw0fc3yK5hZ5hHlo93tTn9RtIf8gHAIQQ1gAPBO7ss7jSDQv2WwRjjzQe\n",
1718 "Y4/kdI/KYeyRxmfsUcON2hE217g/xtjfIqWFnjWk6Zzb+rx+F+kPaN+u7RmyPV3bS1JPxh7J2KNy\n",
1719 "GHuk8Rl71GDjdoRR9seQ/THG/hapfTHmEMJDgV8ljSP9JLB7jPGhXa8/A3g6aeGhl8QYd9Z9DiO4\n",
1720 "r8/P+41sjbt99lycWZI0CRdpVilcoFkanws0q+Hq7gJL1hlqDT0hhFOAs0gLFXUsXMX688D7gPsD\n",
1721 "HwPOr/Mchrid9Etb3ef1vbq2634cdfuiGHuk8XgnLikx9qgUxh5pfMaedmnGBOOS/30ZtyOMsj9q\n",
1722 "3N8itV26FUJ4NPAeUuT5K+C59ChUMcYfkG4ltgJ4Tl3HH9HV1eOmPq9vBHZ2tosx3g7cAvxECKHX\n",
1723 "wjQbq8er6jzJNnG9Hmk8XsIlJV7GpVKs3rK+If8Qktpj5dZ1XsqlJhmrI9S0P5iiM9S5Rs8rgFXA\n",
1724 "W2KMz4sxnkP6sL18rHr8uRqPP4rOXbIW3T4thPAw0srW31yw8PIlpM91XI/9HVM99rojVzGMPdJ4\n",
1725 "jD1SYuxRSYw90viMPWqISTrCpPvbFziUNHByxfinmtQZeo4lXab19hG2/Vb1+OAajz+KjwI7gFNC\n",
1726 "CJ1KRnV/+tOrpx9c8J6/rB5PDSHcr+s9a4HfIX3mDy3ZGUvKkrFHSow9KomxRxqfsUcNMElHIIRw\n",
1727 "eQjh29U6xd0+Q7re7GkhhEcueO0PSFdJfXia9YzrXKNnPSl6XDPCtjuqbadeZCiEsD9zl4AdWD0e\n",
1728 "EkI4tfr+0hjjeQAxxutDCK8B/gz4egjh08AdwOOARwJfYkGoijHGEMLJwNOAb4YQPgfcD3gKsB9w\n",
1729 "ZozxK9N+jrZzvR5pfK7ZIyWu2aOSuG6PND7X7VHdlrojVB5ePc67e1aM8a4Qwq8DHwEuCSGcC3wf\n",
1730 "OIJ0U6srgdOm+Xx1TvTcRgo3Dxhh24Oqbev4/3IHAWdUXy8iBaTDu352UvfGMcY3Ac8Gvgk8A/gV\n",
1731 "Urg5HXhCjHFHj2M8G3glcA/wPOAXgWuBX44x/nYNnyELXsIljc/JHilxskclcbJHGp+TParZcnQE\n",
1732 "WPJn4ycAACAASURBVHxzqs7+IunSrYuBE4EXUg2SAEfGGG+Z4rPVOtHzVeAJpHVrPjlk2xdWj1+e\n",
1733 "9qAxxgsZM1jFGD8OfHyM7X8IvLH60gBO9kjjc7JHSpzsUUmc7JHG52SP6rJMHWHg/mOMXwC+MM45\n",
1734 "jKrOiZ7ONWl/XK1f01N1GVRnCuYv+22n9nKyRxqfkz1S4mSPSuJkjzQ+J3uk4eqc6PkwcDLw88C/\n",
1735 "hhDeRrUGTwjh6cBDgWcyd6eqz8YYP1Xj8SWp1ZzskZJO7HG6RyVwskcan5M90mC1TfTEGHeRrln7\n",
1736 "GGkxo7eQrllbQRpvehNdkYcF17wpL071SJNxskea43SPSuFkjzS+lVvXOd0j9VHnpVvEGO+IMQZg\n",
1737 "G/BXwFXA3aS7bN1ACj7PijE+Kcb4gzqPreYx9kiTMfZIc4w9KoWxR5qMsUdarM5Lt34sxvg54HNL\n",
1738 "sW+1i4szS5PxMi5pjos0qxRexiVNxku5pPlqm+gJITxogve8pK7jS1JunOyR5jjZo1I42SNNxske\n",
1739 "aU6dl25dHELYf5QNQwgrQghvAv68xuOrobyES5qcsUeaY+xRKYw90mSMPVJSZ+h5GPBPIYSHDdoo\n",
1740 "hLAnEEm3WF9R4/HVYMYeaXLGHmmOsUelMPZIkzH2SPWGnn8BHgJcFEI4tNcGIYT1wOeBZwG7gNfU\n",
1741 "eHw1nLFHmpyxR5pj7FEpjD3SZIw9Kl2doWcb8PfATwKfDyEc2f1iCOHhwBeBxwL3AM+JMf5JjcdX\n",
1742 "Cxh7pMkZe6Q5xh6VwtgjTcbYo5LVFnpijHcBzwA+BDwA+GwI4QSAEMLjSJHnocB24IQYY6zr2JJU\n",
1743 "CmOPNMfYo1IYe6TJrNy6zuCjItU50UOM8T7gBcAbgX2AT4cQ/gw4nxR/LgeOjDH+S53HVbs41SNN\n",
1744 "x9gjzTH2qBTGHmlyxh6VptbQAxBj3BVjfCVwKrAn8Apgd9LaPEfFGK+u+5hqH2OPNB1jjzRnw6a1\n",
1745 "Bh8VwdgjTc7Yo5LUHno6YoxvBp4H/Ai4D3h5jPEHS3U8tY+xR5qOsUeaz9ijEqzest7gI03I2KNS\n",
1746 "7DbJm0IIJ5LumjXMduBtwEtJa/a8BLi9e4MY42cnOQflYf/1e3P99jtnfRpSa23ebw1Xf+/24RtK\n",
1747 "hdiwaS03XnvrrE9DWnKrt6zn7su3z/o0pNZZuXUdOy+7edanIS2piUIP8A+MFnoAVlSP64HY9b4V\n",
1748 "1ferJjwHSRLGHmkhY49KYeyRJmPsUe6muXRrxYhf/d5Hn9dVGC/hkqbnZVzSfF7GpVJ4GZc0GS/j\n",
1749 "Us4mmuiJMS7Z2j4qk5dwSdNzskeaz8kelcLJHmkyTvYoVwYbNYaTPdL0nOyR5nOyR6VwskeazMqt\n",
1750 "65zuUXYMPWoUY480PWOPNJ+xR6Uw9kiTM/YoJ4YeScqQsUeaz9ij/7+9Ow+X5a7rff9JCIQkEuKB\n",
1751 "LQlgQhAxchhkngIC5oQhXLxy+DEckUFliBFEruJhDNErR5BBOTIrQhAUfjzACYIMQQSZAih4MRAU\n",
1752 "SAJh3BDGEAKEff+oWmRl7TX06q7urq56vZ5nP71Xd3V1bXbRWeu9v/XrsRB7YHpiD0Mx7adupZTy\n",
1753 "ziSX1Frv1n7915n8k7h+rNb669MeA8NkvR7ohjV74PKs2cNYWLMHpmfdHoZg6tCT5BeTfG/d1w+e\n",
1754 "Yh/7kgg97EfsgW6IPXB5Yg9jIfbA9MQeVt0soefdSS5Z9/WrptjHrieAGA+xB7oh9sDlrV3GJfgw\n",
1755 "dGIPTE/sYZVNHXpqrXfa8PUDZz4a2EDsgW6IPbA/0z2MgdgD0xN7WFWdLcZcSrlrKeWkrvYHQLcs\n",
1756 "0Az7s0gzY2CBZpieBZpZRV1+6tbrk9QO9wdJfOQ6dEnsgf2JPYyB2APTO/C/Xl3wYaV0GXqu0OG+\n",
1757 "4HLEHuiO2AP7E3sYg0OO2yP4wAzEHlZFl6HnvCQHl1IO6XCf8GNiD3RH7IH9iT2MhdgD0xN7WAVd\n",
1758 "hp4zkhyQ5IQO9wmXI/ZAd8Qe2J/Yw1iIPTA9sYe+6zL0/HmSi5M8ocN9AjBHYg/sT+xhLMQemJ7Y\n",
1759 "Q59N/fHqmzgpyb8kOb6U8vwkH53kSbXWF3d4DIyAj1yHbvnoddifj15nLHz8OkzPx6/TV12Gnhes\n",
1760 "+/0jJ3zOviRCD7sm9kC3xB7Yn9jDWIg9MD2xhz7qMvR8dorn7Ovw9RkZsQe6JfbA/sQexkLsgemJ\n",
1761 "PfRNZ6Gn1nqdrvYFwHKIPbC/tTV7BB+GTuyB6a2t2SP40AddLsYMC+dTuKB7FmiGzVmkmTE45Lg9\n",
1762 "FmmGGVikmT7obKKnlHJqkh/UWp82wbY3TXKvJB+rtb6uq2NgnFzCBcCiuJSLsTDdA9MTe1i2Lid6\n",
1763 "Tk3ypAm3vXSX28O2TPZAt0z1wNZM9jAWJnsAVtOyLt36dHt73SW9PgMk9kC3xB7YmtjDWIg9AKtn\n",
1764 "WaHnau3twUt6fQAmIPbA1sQexkLsAVgtCw09pZQrllJuleTF7V2fWuTrM3ymeqB7Yg9sTexhLMQe\n",
1765 "gNUx9WLMpZQfJdm34e4rl1IuneDpB7S3z5v29WErFmeG7vnYddiaBZoZCws0A6yGWSd6Dlj3a7P7\n",
1766 "tvr19SRPqLW+cMbXh02Z7IHumeyBrZnsYSxM9gD03ywfr35ie7svTbx5W5IfJLlHLh9+1vthkr1J\n",
1767 "zqm1TjL5A1Mz2QPdM9kDWzPZw1isxR7TPQD9NHXoqbWeuf7rUsq7k1xSa33HzEcFQG+JPbA1sYcx\n",
1768 "cSkXQD/NMtFzObXWO3W1L+iKqR6YD7EHtib2MCZiD0D/LOxTt0op/6WUcqVFvR6ssV4PzIc1e2Br\n",
1769 "Rx1zhHV7GA3r9gD0y0wTPaWUhya5SpJv11r/epPHD0lyapJHJDk8yaWllLcneVyt9exZXht2w2QP\n",
1770 "zIfJHtie6R7GwmQPQH9MPdFTSjk2yV8leU6SQ7fY7C+TPC7JVdMs0HxQkrsn+UAp5fbTvjZMw2QP\n",
1771 "zIfJHtieyR7GwmQPQD/McunWPdvbC5K8YOODpZRfTPKA9sv3JLlvknsneXuSw5K8sp34AWDFiT2w\n",
1772 "PbGHsRB7AJZvltBzh/b25bXWH23y+EPa2y8muXut9bW11jek+fj1DyY5OsmDZ3h92DVTPTA/Yg9s\n",
1773 "T+xhLMQegOWaJfTcqL09c4vHT2xv/67W+uPFUWqtlyZ5dvvlL8/w+jAVsQfmR+yB7Yk9jMUhx+0R\n",
1774 "fACWZJbQc1SSfUk+tvGBUso12seT5L2bPHftvpvM8PowNbEH5kfsge2JPYyJ2AOweLOEnsOS/KjW\n",
1775 "+vVNHrtxe7svyYc3efxL7WM/OcPrw0zEHpgfsQe2J/YwJmIPwGLNEnq+m+TAUspm382vhZ5v1Vo/\n",
1776 "u8njB6X5FC4ABkrsge2JPYyJ2AOwOLOEnnPTxJobbvLYbdvbs7d47tHt7bdmeH2YmakemC+xB7Yn\n",
1777 "9jAmYg/AYswSev6xvX3U+jtLKVdPcrf2y3/a4rm/2N5+ZobXh06IPTBfYg9sT+xhTMQegPk7aIbn\n",
1778 "vihN5LlfKeX8JC9PcmSSP05yaJIfJXnFFs8t7e1HZ3h96My19xyWC/ZetPOGwFSOPfIqOfdL3172\n",
1779 "YUBvrcWeL57/jSUfCczfIcftycXn7F32YQAM1tQTPbXWTyY5Lc3lW3+Q5jKtd+Syy7ae125zOaWU\n",
1780 "Gyf5b2kWY37rtK8PXTPZA/Nlsgd2ZrqHsTDZAzA/s1y6lVrr/5vk95N8O03wOSDJ95I8PcljN25f\n",
1781 "SjkwzSRQknwjyT/M8voArBaxB3Ym9jAWhxy3R/ABmIOZQk+S1FqfleaSrVsmuVWSq9VaH19rvXST\n",
1782 "za+WJvT8epJSa71k1teHLpnqgfkTe2BnYg9jIvYAdGuWNXp+rNZ6cZJ/mWC7vUle1sVrwrxYrwfm\n",
1783 "z5o9sLOjjjnCmj2MhnV7ALoz80QPDJHJHpg/kz2wM5M9jInJHoBuCD0ALI3YAzsTexgTsQdgdkIP\n",
1784 "bMFUDyyG2AM7E3sYE7EHYDZCD2xD7AGgL8QexkTsAZie0AM7EHtg/kz1wGTEHsZE7AGYjtADQC+I\n",
1785 "PTAZsYcxEXsAdk/ogQmY6oHFEHtgMmIPYyL2AOyO0AMTEntgMcQemIzYw5iIPQCTE3pgF8QeWAyx\n",
1786 "ByYj9jAmYg/AZA5a9gHAqrn2nsNywd6Lln0YMHjHHnmVnPulby/7MKD31mLPF8//xpKPBObvkOP2\n",
1787 "5OJz9i77MIAVV0p5apKnTLDpabXW0ybY30OSvHSHzU6utb5ogtecmdADQG+JPTC5o445QuxhFMQe\n",
1788 "oAPvTfLMbR7/mSS/kmTfLvf7/nbfm/nILvc1NaEHpmCqB4A+EnsYC7EHmEWt9e1J3r7V46WUN7e/\n",
1789 "fdsud/2OWuskk0JzZY0emJL1emAxrNcDu2PdHsbCmj3APJRS7prkbkleW2v9wLKPZxpCD8xA7IHF\n",
1790 "EHtgd8QexkLsAbpUSrlCkmcl+X6Sx0+xiwO6PaLpuHQLgJVgvR7YHZdxMRYu4wI69PAkN0jy3Frr\n",
1791 "p6d4/uNKKU9IcmmSryX5cJKX1FrP6PAYd2SiB2ZkqgcWx2QP7I7JHsbCZA8wq1LKVZOcluSbSf5w\n",
1792 "l0//ZpJ3Jnllkue2t19IclKSN5RSntbhoe7IRA90wOLMsDgme2B3TPYwFiZ7gBk9McnVk/zPWuuF\n",
1793 "u3lirfX1SV6/8f5SyklJXpfkD0opr6i1fqKTI92B0AMdEXtgccQe2B2xh7EQe2CxejE5etFXZ95F\n",
1794 "KeW6SR6d5LNJ/mzmHbZqrW8qpbwyyUOS3CXJQkKPS7cAAEagF9+MwwK4jAuYwjOSXCnJk2qt3+94\n",
1795 "32vTQQtb80PogQ5ZrwcWx3o9sHtiD2Mh9gCTKqXcIcm9k3yk1vo3c3iJm7a3C5nmSYQe6JzYA4sj\n",
1796 "9sDuiT2MhdgD7KSUckCS5yTZl+T3d9j29FLKOZstrFxKeVYp5Zqb3P+gJHdO8rkkb+3mqHdmjR6Y\n",
1797 "A+v1wOJYrwd2z5o9jIU1e4AdPCjJzZL8Q631H3fY9ugk109y5CaP/W6SR5dSzkpydnvfjZPcOsm3\n",
1798 "kvzqHC4J25KJHpgTkz2wOCZ7YPdM9jAWJnuAzZRSDk3yx0kuTfK4CZ6yr/21mYcnOSPJ1ZLcN8mD\n",
1799 "k/xUkhcl+YVa63tmPuBdOGCRL8ZlzjzzzH1JcpPbHL/sQ2GOTPXAYpnsgd0z2cNYmOxhkX7h+OZH\n",
1800 "7RNOOGGQP3Ov/Tz7rcOut+xDyeEXfSrJcP+3noaJHpgjUz2wWCZ7YPeOOuYI0z2MgskeYCyEHpgz\n",
1801 "sQcWS+yB6Yg9jIHYA4yB0AMLIPYAsArEHsZA7AGGTugBYHBM9cD0xB7GQOwBhkzogQUx1QOLJfbA\n",
1802 "9MQexkDsAYZK6IEFEntgscQemJ7YwxiIPcAQCT2wYGIPLJbYA9MTexgDsQcYGqEHgMETe2B6Yg9j\n",
1803 "IPYAQyL0wBKY6oHFE3tgemIPYyD2AEMh9MCSiD0ArBKxhzEQe4AhEHpgicQeWCxTPTAbsYcxEHuA\n",
1804 "VSf0ADAqYg/MRuxhDMQeYJUJPbBkpnpg8cQemI3YwxiIPcCqEnqgB8QeWDyxB2Yj9jAGYg+wioQe\n",
1805 "6AmxBxZP7IHZiD2MgdgDrBqhB3pE7IHFE3tgNmIPYyD2AKtE6AFg9MQemM1Rxxwh+DB4Yg+wKoQe\n",
1806 "6BlTPQCsKrGHoRN7gFUg9EAPiT2weKZ6oBtiD0Mn9gB9J/RAT4k9sHhiD3RD7GHoxB6gz4QeAFhH\n",
1807 "7IFuiD0MndgD9JXQAz1mqgeWQ+yBbog9DJ3YA/SR0AM9J/bAcog90A2xh6ETe4C+EXpgBYg9sBxi\n",
1808 "D3RD7GHoxB6gT4QeAADmTuxh6MQeoC+EHlgRpnpgOUz1QHfEHoZO7AH6QOiBFSL2wHKIPdAdsYeh\n",
1809 "E3uAZRN6YMWIPbAcYg90R+xh6A45bo/gAyyN0AMrSOyB5RB7oDtiD2Mg9gDLIPQAwC6IPdAdsYcx\n",
1810 "EHuARRN6YEWZ6oHlEXugO2IPYyD2AIsk9MAKE3tgecQe6I7YwxiIPcCiCD2w4sQeAIbgqGOOEHwY\n",
1811 "PLEHWAShBwCmZKoHuif2MHRiDzBvQg8MgKkeWB6xB7on9jB0Yg8wT0IPDITYA8sj9kD3xB6GTuwB\n",
1812 "5kXogQERe2B5xB7ontjD0Ik9wDwIPQDQEbEHuif2MHRiD9A1oQcGxlQPLJfYA90Texg6sQfoktAD\n",
1813 "AyT2ADA0Yg9DJ/YAXRF6YKDEHlgeUz0wH2IPQyf2AF0QegBgDsQemA+xh6ETe4BZCT0wYKZ6YLnE\n",
1814 "HpgPsYehE3uAWQg9MHBiDyyX2APzIfYwdGIPMC2hB0ZA7IHlEntgPsQehk7sAaYh9MBIiD2wXGIP\n",
1815 "zIfYw9CJPcBuCT0AsCBiD8yH2MPQiT3Abgg9MCKmegAYqqOOOULwYdDEHmBSQg+MjNgDy2WqB+ZL\n",
1816 "7GHIxB5gEkIPjJDYA8sl9sB8iT0MmdgD7EToAYAlEHtgvsQehkzsAbYj9MBImeqB5RN7YL7EHoZM\n",
1817 "7AG2IvTAiIk9sHxiD8yX2MOQiT3AZoQeGDmxB5ZP7IH5EnsYMrEH2EjoAQBg8MQehkzsAdYTegBT\n",
1818 "PdADpnpg/sQehkzsAdYIPUASsQf6QOyB+RN7GDKxB0iEHmAdsQeWT+yB+RN7GDKxBxB6gMsRe2D5\n",
1819 "xB6YP7GHIRN7YNyEHgDoIbEH5k/sYcjEHhgvoQfYj6ke6AexB+ZP7GHIxB4YJ6EH2JTYA/0g9sD8\n",
1820 "iT0MmdgD4yP0AFsSewAYC7GHIRN7YFyEHgDoOVM9sBhHHXOE4MNgiT0wHkIPsC1TPdAPYg8sjtjD\n",
1821 "UIk9MA5CD7AjsQf6QeyBxRF7GCqxB4ZP6AEmIvZAP4g9sDhiD0Ml9sCwCT0AsGLEHlgcsYehEntg\n",
1822 "uIQeYGKmeqA/xB5YHLGHoRJ7YJiEHmBXxB4AxkjsYajEHhgeoQfYNbEH+sFUDyyW2MNQiT0wLAct\n",
1823 "+wCWpZTygCSPTHLTJFdM8qkkr03yzFrrRRu2/ackd9xhl1eutX5/DocKAFs69sir5NwvfXvZhwGj\n",
1824 "cdQxR+SL539j2YcBnTvkuD25+Jy9yz4MWJhSylOTPGWHze5Wa33bhPs7JsmpSU5MsifJ15O8K8lp\n",
1825 "tdaPz3Couza60FNKOTDJy5I8MMmXkrwhycVJ7pTmL+U+pZTja63f3OTpf5lkq/+yX9r5wUKPXXvP\n",
1826 "Yblg70U7bwjMndgDiyX2MFRiDyP11iQf2+KxcyfZQSnleknen+RqSc5M8okk10nyK0lOKqXcqdb6\n",
1827 "4dkPdTKjCz1JfiNN5Hl/khPXpndKKVdI8uwkj0ryJ0lO3uS5f1Jr/cyiDhT6TuyB/hB7YLHEHoZK\n",
1828 "7GGEaq31pTPu4zlpIs8ptdYXrN1ZSrlnkjOSvDDJLWZ8jYmNcY2eX21vT1t/iVat9dIkj0szXvWQ\n",
1829 "UsqVl3FwsGqs1wP9Yc0eWCxr9jBU1uyByZVS9iS5e5Lz10eeJKm1/n2S9yS5WSnlRos6pjGGnqOS\n",
1830 "7MsmI1i11kuSfCDJwUluvslzD5jvocFqEnugP8QeWCyxh6ESexiRWX/Ov1WatnLWFo+/r729/Yyv\n",
1831 "M7ExXrr1+SQ/m+TGSf5zk8cvbG9/apPHzi6lXCnJ95J8Lsnb0yzefN4cjhMApuIyLlgsl3ExVC7j\n",
1832 "YiSeV0p5SZIfJPlKmmVenltrfc+Ez79ue/uVLR7/fHt77PSHuDtjDD0vS7Pw8vNLKVdM8pYk301y\n",
1833 "zSR3yWWV7eB1z/lMkq+l+Yv7fpJrJPmlJL+V5IGllBMWubAS9JH1egAYM7GHoRJ7GLC9aXrA55N8\n",
1834 "J80aO7dKcp8k/72U8lu11hdNsJ+1cepvbfH4d9vbw2c41l0ZXeiptZ5eSjk2yROTvGrDw19PM62z\n",
1835 "9vu15/z6xv2UUg5O8vwkD03yF0luM5cDhhUi9kB/mOqBxRN7GCqxhyGqtT4vyfM23l9KeViSFyV5\n",
1836 "Tinl1bXWSd/Yf7jF/QtfAmaMa/Sk1npamsu3HpnktCSPT1Ptjk7y1TRr+Jyzwz4uSTPR870ktyyl\n",
1837 "HDrPY4ZVYb0e6A/r9cDiHXXMEdbtYZCs2cNY1FpfkuSfklw5k62rs/Yva4ds8fihG7abu9FN9Kyp\n",
1838 "tZ6f5MXr7yulXCvJjZKc1z6+0z4uKaV8N81lXj+Ry0ayAKAXTPbAcpjuYYhM9rBRH/5R6Wufnstu\n",
1839 "19buneRfsT/T3h69xePX2rDd3I1yomcbp7a3L952q1Yp5aeT/JckF9Zat1p4CUbHVA/0Sx++CYMx\n",
1840 "MtnDEJnsYehKKQcmuUmaK30+McFTPthue6ctHj++vf3AzAc3IaEnSSnloFLKU5L8ZpKzkzx73WMn\n",
1841 "lFJ+rV24ef1zrpzmur0keenCDhZWhNgD/SL2wHKIPQyR2MOqK6Vco5Ty5FLKT27y8JOS/EySD9da\n",
1842 "P7buOaeXUs4ppTxt/ca11rVFna/Rru+z/nVOSnLbJB+rtX608z/IFkZ56VYp5eQkd03y2SRHJLlz\n",
1843 "mnGqf0lyz1rr99dtfu00Iec5pZR/TvOx6ldPcsc0n9T1vlw2CQSsY3Fm6BeXccFyuIyLIXIZFyvu\n",
1844 "kDTr9T6hlPK+JJ9MsyTLLdIs5/KlJA/a8Jyjk1w/yZGb7O93k9w6yQtLKfdO8p/t9ielWeLlEXP4\n",
1845 "M2xprBM9F6f5ePSHp/lI9Y8meXCSW9Vav7xh27cl+eM0kz43TfKwNH9ZFyR5TJI71Vq/FwAA2ILJ\n",
1846 "HobIZA8r7Atp4sw7kvx0kgcmuX+SKyV5ZpKb1Fo/ueE5+9pf+6m1/keaSHR6mlD0iDSfzP26JLep\n",
1847 "tS7ssq1kCR/zRePMM8/clyQ3uc3xO20KK89UD/SLqR5YHpM9DJHJnv39wvHNj9onnHDCIH/mXvt5\n",
1848 "9mo/c9NlH0q+9umPJBnu/9bTGOtED7BA1uuBfrFeDyyPyR6GyGQP9IvQAyyE2AP9IvbA8og9DJHY\n",
1849 "A/0h9AALI/ZAv4g9sDxiD0Mk9kA/CD0AMGJiDyyP2MMQiT2wfEIPsFCmeqB/xB5YHrGHIRJ7YLmE\n",
1850 "HmDhxB7oH7EHlkfsYYjEHlgeoQdYCrEHAC4j9jBEYg8sh9ADACQx1QPLJvYwRGIPLJ7QAyyNqR7o\n",
1851 "H7EHluuoY44QfBgcsQcWS+gBlkrsgf4Re2D5xB6GRuyBxRF6gKUTe6B/xB5YPrGHoRF7YDGEHgBg\n",
1852 "U2IPLJ/Yw9CIPTB/Qg/QC6Z6oJ/EHlg+sYehEXtgvoQeoDfEHgDYnNjD0Ig9MD9CD9ArYg/0j6ke\n",
1853 "6Aexh6ERe2A+hB4AYEdiD/SD2MPQiD3QPaEH6B1TPdBPYg/0g9jD0Ig90C2hB+glsQf6SeyBfhB7\n",
1854 "GBqxB7oj9AC9JfZAP4k90A9iD0Mj9kA3hB6g18Qe6CexB/pB7GFoxB6YndADAExF7IF+EHsYGrEH\n",
1855 "ZiP0AL1nqgcAtif2MDRiD0xP6AFWgtgD/WSqB/pD7GFoxB6YjtADrAyxB/pJ7IH+EHsYGrEHdk/o\n",
1856 "AQBmJvZAfxx1zBGCD4Mi9sDuCD3ASjHVA/0l9kC/iD0MidgDkxN6gJUj9kB/iT3QL2IPQyL2wGSE\n",
1857 "HmAliT3QX2IP9IvYw5CIPbAzoQcAAAZO7GFIxB7YntADrCxTPdBfpnqgf8QehkTsga0JPcBKE3ug\n",
1858 "v8QeAOZJ7IHNCT3AyhN7oL/EHugXUz0MjdgD+xN6AIC5EnugX8QehkbsgcsTeoBBMNUDAJMTexga\n",
1859 "sQcuI/QAgyH2QH+Z6oH+EXsYGrEHGkIPMChiD/SX2AP9I/YwNGIPCD0AwAKJPdA/Yg9DI/YwdkIP\n",
1860 "MDimeqDfxB7oH7GHoRF7GDOhBxgksQf6TeyB/hF7GBqxh7ESeoDBEnug38Qe6B+xh6ERexgjoQcA\n",
1861 "WBqxB4B5E3sYG6EHGDRTPdB/Yg/0i6kehkjsYUyEHmDwxB7oP7EH+kXsYYjEHsZC6AFGQewBgN0R\n",
1862 "exgisYcxEHoAgF4w1QP9I/YwRGIPQyf0AKNhqgf6T+yB/hF7GCKxhyETeoBREXug/8Qe6B+xhyES\n",
1863 "exgqoQcA6B2xB/pH7GGIxB6GSOgBRsdUD6wGsQf6R+xhiMQehkboAUZJ7IHVIPZA/4g9DJHYw5AI\n",
1864 "PcBoiT2wGsQeABZB7GEohB4AoPfEHugXUz0MldjDEAg9wKiZ6oHVIfZAv4g9DJXYw6oTeoDRE3tg\n",
1865 "dYg90C9iD0Ml9rDKhB4AYKWIPdAvYg9DJfawqoQegJjqAYBZiD0MldjDKhJ6AFpiD6wOUz3QP2IP\n",
1866 "QyX2sGqEHgBgJYk90D9iD0Ml9rBKhB6AdUz1wGoRe6B/xB6GSuxhVQg9ABuIPbBaxB4AFkXsYRUI\n",
1867 "PQDAyhN7oF9M9TBkYg99J/QAbMJUD6wesQf6RexhyMQe+kzoAdiC2AOrR+yBfhF7GDKxh74SegC2\n",
1868 "IfbA6hF7oF/EHoZM7KGPhB4AYHDEHugXsYchE3voG6EHYAememA1iT3QL2IPQyb20CdCD8AExB4A\n",
1869 "mJ3Yw5CJPfSF0AMADJapHugfsYchE3voA6EHYEKmemA1iT3QP2IPwPwIPQC7IPbAahJ7AFgUUz0s\n",
1870 "m9ADAIyC2AP9YqoHYD4OWvYBAKyaa+85LBfsvWjZhwFM4dgjr5Jzv/TtZR8G0DrqmCPyxfO/sezD\n",
1871 "AEaolPKrSe6e5BZJjk4zCPO5JG9J8rRa6xd3sa+HJHnpDpudXGt90XRHuztCD8AUxB5YXWIP9IvY\n",
1872 "AyxaKeWgJK9I8oMk70/yjjR95A5JTmk2KbettZ67y12/P8l7t3jsI1Me7q4JPQDA6Ig90C9iD7Bg\n",
1873 "P0rytCTPqbV+be3OUsoBSV6S5NeTnJbkQbvc7ztqrU/p7CinZI0egClZmBlWmzV7oF+s2QMsSq31\n",
1874 "R7XWJ62PPO39+5L8RfvlzRd/ZN0QegBmIPbAahN7oF/EHqAHDm1vv7btVps7oMsDmZZLtwBmZL0e\n",
1875 "WG0u44J+cRkXsGT3a2/fPcVzH1dKeUKSS9OEog8neUmt9YyuDm4SJnoAgNEz2QP9YrIHWIZSCAVE\n",
1876 "twAAG7lJREFUyq2TPDLJhUn+fBdP/WaSdyZ5ZZLntrdfSHJSkjeUUp7W8aFuy0QPQAdM9QBAt0z2\n",
1877 "AItUSrlBkr9Psi/J/Wuteyd9bq319Ulev8k+T0ryuiR/UEp5Ra31E10d73ZM9AB0xHo9sNpM9QDA\n",
1878 "OJVSbpbkn5JcJcn9aq1ndrHfWuub0kz3HJDkLl3scxImegAAWtbrgX4x1QP91od/6Pzap2d7finl\n",
1879 "HkleneQHSe5ea31nB4e13oXt7cL+xzLRA9ChPvzHDpiNyR7oF+v1APNSSnlUkjOSfDXJ8XOIPEly\n",
1880 "0/Z2IZdtJSZ6ADpnvR5YfSZ7oF9M9gBdKqUcnOT5SR6a5F1J7lNr3fbj1Esppye5VZLX1VqfsOGx\n",
1881 "ZyV5Vq31Cxvuf1CSOyf5XJK3dvcn2J7QAwCwCbEH+kXsATp0vzSR5ztJ/i3J40spm2331lrr29vf\n",
1882 "H53k+kmO3GS7303y6FLKWUnObu+7cZJbJ/lWkl+ttX6/u8PfntADMAememAYxB7oF7EH6MgB7e1h\n",
1883 "SR69xTb70kSat6/7et8W2z48yd2T3CDJfZMckuTzSV6U5Om11vNmP+TJHbDzJszDmWeeuS9JbnKb\n",
1884 "45d9KMAciT0wDGIP9IvYQ9/93NW/miQ54YQTBvkzd59+nv23D7wnyXD/t56GxZgBAHZggWboFws0\n",
1885 "A2xN6AGYI5/CBcMh9kC/iD0AmxN6AOZM7IHhEHsAgL4TegAAgJVkqgdgf0IPwAKY6oHhMNUD/SL2\n",
1886 "AFye0AOwIGIPDIfYA/0i9gBcRugBWCCxB4ZD7IF+EXsAGkIPAMCUxB7oF7EHQOgBWDhTPTAsYg/0\n",
1887 "i9gDjJ3QA7AEYg8Mi9gD/SL2AGMm9AAAdEDsgX4Re4CxEnoAlsRUDwyP2AP9IvYAYyT0ACyR2APD\n",
1888 "I/YAAMsk9AAAdEzsgf4w1QOMjdADsGSmemCYxB7oD7EHGBOhB6AHxB4AmC+xBxgLoQcAYE5M9UC/\n",
1889 "iD3AGAg9AD1hqgeGSeyBfhF7gKETegB6ROyBYRJ7oF/EHmDIhB6AnhF7YJjEHugXsQcYKqEHAGBB\n",
1890 "xB7oF7EHGCKhB6CHTPXAcIk9AMA8CT0APSX2wHCJPdAfpnqAoRF6AACWQOyB/hB7gCERegB6zFQP\n",
1891 "DJvYA/0h9gBDIfQA9JzYA8Mm9kB/iD3AEAg9AAAALbEHWHVCD8AKMNUDw2aqB/pF7AFWmdADsCLE\n",
1892 "Hhg2sQf6RewBVpXQAwDQE2IP9IvYA6wioQdghZjqgeETe6BfxB5g1Qg9ACtG7IHhE3sAgGkJPQAr\n",
1893 "SOyB4RN7oD9M9QCrROgBAOgpsQf6Q+wBVoXQA7CiTPXAOIg90B9iD7AKhB6AFSb2wDiIPdAfYg/Q\n",
1894 "d0IPAMAKEHugP8QeoM+EHoAVZ6oHxkPsgf4Qe4C+EnoABkDsAYDFE3uAPhJ6AABWiKke6BexB+gb\n",
1895 "oQdgIEz1wHiIPdAvYg/QJ0IPwICIPTAeYg8AsBmhBwBgRYk90B+meoC+EHoABsZUD4yL2AP9IfYA\n",
1896 "fSD0AAyQ2APjIvZAf4g9wLIJPQAAAyD2QH+IPcAyCT0AA2WqB8ZH7IH+EHuAZRF6AAZM7IHxEXug\n",
1897 "P8QeYBmEHoCBE3tgfMQe6A+xB1g0oQcAAGCOxB5gkYQegBEw1QPjY6oHAMZJ6AEYCbEHxkfsgf4w\n",
1898 "1QMsitADADBgYg/0h9gDLILQAzAipnpgnMQe6A+xB5g3oQdgZMQeGCexB/pD7AHmSegBABgJsQf6\n",
1899 "Q+wB5kXoARghUz0wXmIP9IfYA8yD0AMwUmIPjJfYA/0h9gBdE3oAAEZI7IH+EHuALgk9ACNmqgfG\n",
1900 "TeyB/hB7gK4IPQAjJ/YAAMBwCD0AiD0wYqZ6oD9M9QBdEHoAAEZO7IH+EHuAWQk9ACQx1QNjJ/ZA\n",
1901 "f4g9wCyEHgB+TOyBcRN7oD/EHmBaQg8AAD8m9kB/iD3ANIQeAC7HVA8g9kB/iD3Abgk9AOxH7AHE\n",
1902 "HugPsQfYDaEHAIBNiT3QH2IPMCmhB4BNmeoBErEH+kTsASYh9ACwJbEHSMQeAFglQg8AADsSe6Af\n",
1903 "TPUAOzlo2QewLKWUByR5ZJKbJrlikk8leW2SZ9ZaL9pk+19O8pgkv5Dk4CTnJ3l1kqfXWi9e1HED\n",
1904 "LNq19xyWC/bu97YIACzJUccckS+e/41lHwasvFLKDZM8JckdkxyRZG+StyV5aq31c7vc1zFJTk1y\n",
1905 "YpI9Sb6e5F1JTqu1frzL497J6CZ6SikHllJOT/LKJD+b5A1JTk9ypTR/KR8opVx1w3N+J8nrk9wk\n",
1906 "yRlJ/irJD9KcEG8rpVxxcX8CgMVzCReQmOqBPjHZA7Mppdw2yQeT/HKSDyR5UZJPJHlokg+VUq6z\n",
1907 "i31dL8mHkzwkyceTvDDJWUl+JckHSym36PLYdzLGiZ7fSPLAJO9PcuLa9E4p5QpJnp3kUUn+JMnJ\n",
1908 "7f3Xar/em+QWa1WvlHJAkr9Nct8kj0jyF4v9YwAslskeIGliz7lf+vayDwOIyR6Y0YvSDHzcq9b6\n",
1909 "5rU7SymnJPnfSZ6Z5D4T7us5Sa6W5JRa6wvW7eueaYZFXphkYbFndBM9SX61vT1t/SVatdZLkzwu\n",
1910 "zXjVQ0opB7cP3S/NpVovXD+6VWvdl+QJ7ZcPnftRAwD0hMke6A+TPbB7pZSbJblhkveujzxJUmt9\n",
1911 "XpILktyrlPKTE+xrT5K7Jzl/feRp9/X3Sd6T5GallBt1dfw7GWPoOSrJviTnbnyg1npJmpGtg5Pc\n",
1912 "vL37tu3t+zfZ/jNJvpLkJqWUK8/laAF6xCVcwBqxB/pD7IFd2/Ln/Nb70lwBdesJ9nWrNG3lrG32\n",
1913 "lSS3n/joZjTG0PP5JAckufEWj1/Y3l6jvb1ue/uVHfZ3bCdHB9BzYg+wRuyB/hB7YFcm+Tk/mezn\n",
1914 "/C731YkxrtHzsiR3SvL8dhHltyT5bpJrJrlLLqtsa5duXSXNBNC3ttjfd9OEnsPnc7gAAP1lzR7o\n",
1915 "D2v2wMTW/qViu5/zk8l+zu9yX50YXeiptZ5eSjk2yROTvGrDw19P8r11v1/vh1vs8oBZjuffPvCe\n",
1916 "WZ4OALB0/rUL+uPwqy/7CBiTAfw82+XP+XNpBtMY46VbqbWeluaj1R+Z5LQkj0+zmvbRSb6aZoLn\n",
1917 "nHbzb6f5izlki90dum47AAAAoN/Wfn7v4uf8LvfVidFN9KyptZ6f5MXr72s/Sv1GSc5rH0+aRZtv\n",
1918 "muSYJJ/YZFfXSvKjbLK483ZOOOGEhVc9AAAAmNUAfp5d+/n9mC0ev1Z7+5kJ9rW2zdEd7KsTo5zo\n",
1919 "2cap7e36ALS2QvZdNm5cSvnZJHuS/Hut9eI5HxsAAAAwu+1+zj8wye2SXJrkQxPs64Nprgq60xaP\n",
1920 "H9/efmB3hzg9oSdJKeWgUspTkvxmkrOTPHvdw69J8v0kD24nftaec2CSP2q/fPmijhUAAACYXq31\n",
1921 "X5N8PMnNSyknbnj45DRTOG+utX5t7c5SyumllHNKKU/bsK+9aT7k6RqllIetf6yUclKaj3L/WK31\n",
1922 "o3P4o2xqlJdulVJOTnLXJJ9NckSSO6f5i/yXJPestX5/bdta6wWllCcm+dMk/1ZK+fsk30lyhzSX\n",
1923 "eZ2V5HmL/RMAAAAAM3hEkjOTvLGU8qYkFyT5uSQnJNmb5LEbtj86yfWTHLnJvn43ya2TvLCUcu8k\n",
1924 "/9luf1KaT916xDz+AFsZ60TPxUl+KcnD04xqfTTJg5Pcqtb65Y0b11qfleS/J/n3JP93kt9IcsU0\n",
1925 "Ez2/tD4MAQAAAP1Wa31vktskOSPJ7dPEmBskeVmSW9ZaP73hKfvaX5vt6z+S3CLJ6WkGQh7R7vt1\n",
1926 "SW5Ta13YZVsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC3TAsg9glZRSbpjkKUnumOSI\n",
1927 "JHuTvC3JU2utn5tyn3uSfDLJ2bXWO+yw7fFJHp/k1kl+Isnnk5yR5I9qrRdO8/os1zLPqVLKy5I8\n",
1928 "aIfdHVdr/Y9pjoPl6OqcKqXcM8mvJLlVkmOTXDHJF5O8M8nTaq3/ucXzvE8NzDLPKe9Tw9Ph+XS7\n",
1929 "JA9Icrsk10tyaJJvJflIklckOb3Wum+T53mPGphlnlPeo4ZpHt+fr9v3qUlOTfLerb5P9z5FFw5a\n",
1930 "9gGsilLKbZO8I8kVkvxDkvOT/HyShyY5qZRym1rreRPu64gkf5Tkp5KckOYNZL9vRjY8595JapLv\n",
1931 "JXljki8nuWWS30ly9/b1v7H7PxnLsuxzap3XJPnsFo/5j8kK6fKcSvLCJEcm+XCSv0nyozTvOQ9O\n",
1932 "cp9Syl1qrR/a8PrepwZm2efUOt6nBqDj8+kZSW6b5Kwkr05yUZKfTnJikrskuXOSh2x4fe9RA7Ps\n",
1933 "c2od71ED0fE5tXHfD08TeZItvk/3PkVXhJ7JvSjJlZLcq9b65rU7SymnJPnfSZ6Z5D4T7uuIJKdk\n",
1934 "wh/ESymHJnlBkkuSHF9r/ci6x56R5PeSPKm9ZXUs7Zza4MW11n+c4nn0T5fn1IuT/PXGf7kqpTwl\n",
1935 "yVOTPCvNv3St3e99apiWdk5tfK73qUHo8nx6dpKzaq2fX39nKeW4JGcneVAp5Xdqrd9s7/ceNUxL\n",
1936 "O6c28B41HF2eUz9WSvnlJM9L8uYk99hiG+9TdObAZR/AKiil3CzJDdOM2L15/WO11ucluSDJvUop\n",
1937 "PznJ/mqt59VaD6y1XiHJdSd4yt2S7Gmeetn/4VunpSm+v1ZK8fe5InpwTjEwczin/nCL8eTntrc3\n",
1938 "33C/96mB6cE5xYDM4Xx63cYfyFvnpnm/uSjJd9bd7z1qYHpwTjEwXZ9T6/Z7+yR/l+RNSR69zabe\n",
1939 "p+iMk2Qyt21v37/F4+9LMx116yn2Pck6SVu+fq31oiT/X5o3hetP8fosx7LPqVm2p5/meU6td2h7\n",
1940 "+7VJX9/71Mpa9jm1nvep1TfX86mUcni7xsr/SfP97cm11ksneX3vUStr2efUet6jhqHzc6qUcoM0\n",
1941 "l2D9S5L7pblsedev732K3XLp1mTWJiS+ssXja/X/2B68/jlzOga6texzar03lVKulGZM9ItJ3p3k\n",
1942 "WbXWjy3gtenOos6p+7W3757h9b1PrYZln1PreZ9afXM7n0opH01y4/bLtyW58SaLe3uPGp5ln1Pr\n",
1943 "eY8ahk7PqVLKtZO8Jc05cc9a6yWllK5e3/sU2zLRM5mrtLff2uLx77a3hw/09eleH/5OP5/mXxhe\n",
1944 "nuTPk7wuzcJzD0ryofYTclgdcz+nSinXTfLkNN/I/q9Fvz4Lt+xzKvE+NSTzPJ9eluT5Sf4xzQcS\n",
1945 "vLr9V/RFvT7LsexzKvEeNTSdnVPt5V1vSTPtddcJF1D2PkVnTPTszg+3uH9R45rLfn26t7S/01rr\n",
1946 "Ezfe117ze2qaH7xeWEo5uta63Ygp/TOXc6qUcs0kb01y1SS/Xms9e5Gvz1It7ZzyPjVInZ9PtdY/\n",
1947 "W/t9KeWWSd6T5A2llBvXWr8379dn6ZZ2TnmPGqyZzqn2HDgjyTWT3LHWesEiXx8SEz2T+nZ7e8gW\n",
1948 "jx+6YbuhvT7d6+Xfaa31R7XWU5Ocl+SoNB8nyWqY2zlVSrlOknelGRV+VK315Yt8fZZm2efUprxP\n",
1949 "rayFvEfUWj+U5J1JrpfLf4qb96jhWfY5tdX23qNWV1fn1OFJbp/kY0keUkp55tqvJE9otzm2ve8P\n",
1950 "5/D6YKJnQue2t8ds8fi12tvPDPT16V7f/04vTHKdJIct6fXZvbmcU+2/ZL4xzdTFr9Va/3aRr89S\n",
1951 "Lfuc2on3qdWyyPeIC9vb9Z+M4z1qeJZ9Tk3ynOvEe9Qq6fqcOj7JHbbZ12OTfCPJU+b0+oyY0DOZ\n",
1952 "97W3d9n4QDuad7sklyb50Bxf/7Ht679ww+sfnmaxuAuT/MecXp/uLfuc2lIp5dAkP5dmbHS7hQfp\n",
1953 "l87PqVLKfdKsO3BxkhNrrf+8w+t7nxqWZZ9T2+3H+9TqWch/90opB+SyRXTPXfeQ96jhWfY5td1z\n",
1954 "vEetpk7OqXY9nk2vnCmlHJPmPHpPrXXjhJj3KTrj0q0J1Fr/NcnHk9y8lHLihodPTlNX31xr/fFH\n",
1955 "w5ZSTi+lnFNKeVoHh/CWJF9Ncs9Syo02PPbkJAcneaXrf1fHss+pUspNSimPar8RWX//gWkWEzws\n",
1956 "yetrrV+f9bVYjK7PqVLKHyV5TZJPJbnlBD+Qe58amGWfU96nhqXL86mU8vOllGeXUo7c5KWelOQG\n",
1957 "Sc6utX5w3f3eowZm2eeU96jhWdD359uts+N9is6Y6JncI5KcmeSNpZQ3JbkgTak/IcneNPV1vaOT\n",
1958 "XD/Jfv/BKKVcpd1fctkI6LVLKb/X/v6ztdbXrG1fa/1uKeWUJH+X5H2llDcm+VqSmyW5bZpvmk+b\n",
1959 "+U/Ioi3tnGq3+fMkf1xK+ec0/7JweJrz6WeSfDLJb8/0p2MZOjmnSil3TPLENP8S+b4kp2zxcaAf\n",
1960 "XDuvvE8N1tLOqXifGqKu/rt3cJLHJPntUspZSc5OcqUkt0lyXLuv/7H+Cd6jBmtp51S8Rw1VZ9+f\n",
1961 "75b3KbpkomdCtdb3pnmzPyPN4lqPSFP3X5bmXyY/veEp+9pfm7lakme0vx7fbnfMuvseucnr1zRj\n",
1962 "fP+c5K5JHpbmDeXPk9ym1nrhxufQb0s+pz6S5oeus9J8A/OQJPdOclGaT4q4Ra1179R/OJaiw3Nq\n",
1963 "7V+brtDu47Gb/PrdJHfb8PrepwZmyeeU96mB6fB8OifN+8sb0ix2++Ak90/zfe2fJblJrfVjm7y+\n",
1964 "96iBWfI55T1qgDr+/nya1/c+BQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n",
1965 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+zlg2QcAAPRTKeW8JEcnObnW+qIlH06SpJTysiQPSvLq\n",
1966 "WusDlnw4AAC9c9CyDwAA2Fop5Y+SPDHJN5Nco9b6/Qme85gkz06yN8lRtdYfzXgY+2Z8/o+VUv4m\n",
1967 "yf9I8vJa60O32OYhSV7afnmdWutndzqmUsp1knym/fKhtdaXr3vsN5O8OMn5tdZjZ/oDAAD03IHL\n",
1968 "PgAAYFuvbG8PT3L3CZ+zNuny6g4iz7xsF49+mOSSJN/bYbuN+1t7zg+neE0AgEEw0QMAPVZrPaeU\n",
1969 "8tEkv5Dk/kn+z3bbl1Kum+SWaaLGK7fbtq9qrX+T5G92+ZzzkxwynyMCAFgdJnoAoP9e1d7es5Ry\n",
1970 "6A7b3r+9PbfWetYcj2lW1gkEAJgDEz0A0H9/m+QZSQ5Lcq8kf7fNtmuh51Xr7yyl3DLJY5PcMcnV\n",
1971 "k3wjyfuS/EWt9R27PaBSypOT3DrJsUmOTHNp2beTfCLJGe1+L1q3/XVy2Ro6SfLgUsqDN+z2OrXW\n",
1972 "z5ZSTkjytiSptU70j1KllIOSrK1fdOda67va+89Ls6B0klynlLLxUraHJrkoyWva51+z1nrhFq/x\n",
1973 "S0nenubysKNqrd+c5NgAABbJRA8A9Fyt9fNJ3tV+ef+ttiul3CDJDdNctvWqdfc/JslZSe6XJsoc\n",
1974 "mCb2/HKSt5dSnj7FYT0xyT2S/HySI9rXvGqS2yb5X0k+VEq52rrt19bQWQstP0oTTNb/2riGzjRr\n",
1975 "6uzb8LyNa/ZsfM0fprkcbm+SK6X5RK+tPKy9fbXIAwD0ldADAKthLdzctZRy1S22WVuE+aO11nOS\n",
1976 "pJRyjzSfwLUvyV+kmZq5YpJrJvnD9v7fbz+Zajc+lORJSW6W5Mq11isl2ZPkN9NM9hyX5MlrG9da\n",
1977 "z6+1HpJmOilJTq+1Hrrh1+d2eQw7qrUel+Tk9svzNnnNV9Zaf5Bk7VO6fmOz/ZRSrp7kV9L879WL\n",
1978 "j5oHANiMS7cAYDW8Nsnzkhyc5N5J/nqTbe7X3q6/bOsZ7e2La62PXruz1vrlJE8tpfwwTfD541LK\n",
1979 "6ZN8fHv7/Dtsct+FSV7aTvI8Pcn/leQxGzZbxto8k7zmXyb5vSQ3KKXcptb6gQ2PPyjJFZN8bJPH\n",
1980 "AAB6w0QPAKyAWuvXk7y5/XK/y7dKKTdPcr00l0T9bXvfjZLcIM0Uyp9ssevnJLk4zaVc/62jw/1I\n",
1981 "e3utjvY3d7XW/0jy7jRRaLPpprX7TPMAAL1mogcAVser0izGfOdSyp5a6951j61dtvXuWusX2t/f\n",
1982 "sr39Qvvx4/uptV5USvlIktsluXmSN016MKWUn01y3yS3SnLdNAsyX6X9lTQTMKvkJWkWq75vKeV3\n",
1983 "1haTLqUcn+ZStIuSvGKJxwcAsCOhBwBWxxlJvpPkJ5KUJM9PklLKAWmCS3L5y7Z+qr398g77/WJ7\n",
1984 "e41JDqKUcoUkf5bkt3L5y6LWFkH+YZIrTLKvnnltkucm+ck0U1N/1d6/fhHmby/jwAAAJuXSLQBY\n",
1985 "EbXW7yV5Q/vl+su3bp/k2mk+1aou4FD+MMkpaSLPO5M8OMlNkly91nqFJCcu4Bg6V2u9JJdN7Pxm\n",
1986 "kpRSjkgT1SzCDACsBBM9ALBaXpXkgUluV0q5dq31glx22dZbNnzs99okzzV32OdR7e1Xdnrxdnro\n",
1987 "lPbLF9Zaf2uTzZax4HJXXpLk0UluVUr5r0nulOTKaT7J7EPLPDAAgEmY6AGA1fL2JHvT/Df8fqWU\n",
1988 "A5Pcp33slRu2/XB7e412PZ39lFKukuYj0tdvv509adbi2ZfLLm3ajR+2twdP8dxpTfyatdazk3wg\n",
1989 "ly3KvHbZlmkeAGAlCD0AsEJqrZcmeU375QOSnJAmvnwryRs3bPuxJB9PEy2evMUu/580Eyt700Sk\n",
1990 "nVyy7vc/tcU2N93m+Xsn2KZra695jVLKJOsQvaS9fUSSG6dZF2ljRAMA6CWXbgHA6nlVmsunbpbk\n",
1991 "Ce19r2/XmNnoD9IEoAeWUi5O8rRa6/mllCPTLKb8pDTTOU+utX5/pxeutX6zlHJWklsn+dNSytfS\n",
1992 "fJz6Fdr7Hp/t1+h5f3t7XCnlt5OcnubTuW6R5H1zWuz4Q0kubY/x6aWU/5nmE7R+Psk3a62f3LD9\n",
1993 "q9N87Pzh7dd/W2v9zhyOCwCgcyZ6AGDF1Frfn+S89ss7trev2mLbNyX5/TQx52FJzi2lXJrkC7ks\n",
1994 "8jyn1vriXRzCo5N8N8kN0lzmdEn79TuT3DnJm7d57huT/Hv7++cm+UaaiZt/SPNpV7Pab32gWutX\n",
1995 "ctllZg9K82f/Znvst95k++/msv89LcIMAKwUoQcAVtP6EPHlJGdutWGt9VlJbpvmkq8vJPlBmoWX\n",
1996 "z0hyYq3197Z46r5c9pHp6/f3oXZ/b0xzydgPkpyb5AVJbpTkT7c5lh8kuUua8PLF9rlfTvKOJGvT\n",
1997 "PPu95k7HtOHxzZyS5vK1TyX5fpKvJ/lgks9ssf3a/f9aa/3XbV4PAKBXVvlTMQAAOtd+stgnk1wv\n",
1998 "ycNrrX+55EMCAJiYiR4AgMu7a5rI861scUkcAEBfCT0AAJd3Snv7qna9HgCAlSH0AAC0SinHJrlH\n",
1999 "LMIMAKwooQcA4DInp1nD8MO11n9b9sEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n",
2000 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAjNv/D946C4GGg8YDAAAAAElFTkSuQmCC\n"
2001 ],
2002 "text/plain": [
2003 "<matplotlib.figure.Figure at 0x1109e0310>"
2004 ]
2005 },
2006 "metadata": {
2007 "image/png": {
2008 "height": 407,
2009 "width": 573
2010 }
2011 },
2012 "output_type": "display_data"
2013 }
2014 ],
2015 "source": [
2016 "plt.figure()\n",
2017 "plt.contourf(sigma_vals, strike_vals, prices['eput'])\n",
2018 "plt.axis('tight')\n",
2019 "plt.colorbar()\n",
2020 "plt.title(\"European Put\")\n",
2021 "plt.xlabel(\"Volatility\")\n",
2022 "plt.ylabel(\"Strike Price\")"
2023 ]
2024 },
2025 {
2026 "cell_type": "markdown",
2027 "metadata": {},
2028 "source": [
2029 "Plot the value of the Asian put in (volatility, strike) space."
2030 ]
2031 },
2032 {
2033 "cell_type": "code",
2034 "execution_count": 24,
2035 "metadata": {
2036 "collapsed": false
2037 },
2038 "outputs": [
2039 {
2040 "data": {
2041 "text/plain": [
2042 "<matplotlib.text.Text at 0x1109e3b10>"
2043 ]
2044 },
2045 "execution_count": 24,
2046 "metadata": {},
2047 "output_type": "execute_result"
2048 },
2049 {
2050 "data": {
2051 "image/png": [
2052 "iVBORw0KGgoAAAANSUhEUgAABGUAAAMvCAYAAAB7jm3aAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\n",
2053 "AAAWJQAAFiUBSVIk8AAAIABJREFUeJzs3Xm4LWddJuznJIRAIAeUIeFLGJJAsAPIJDIoBAGZBYR+\n",
2054 "GYQQQBSlVRAQbGymRm1AEFFQRAUBNcArg5FmFiIqswgCUcAvCRBkUmQIU0hy+o+qxVnZWWvvNdQa\n",
2055 "676va1+11161qmqfYEyePO/vTQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n",
2056 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACY0A8nuWjo69+WdN9zhu55jSXdcxnOycX/PAdf\n",
2057 "30ryuSRvT/JrSY5dwrPcNslTkzwlyRWWcD8AAABgCr+XSwYIt1zCfc9u73VhtjeUuXDoa1RI84QF\n",
2058 "P8tTs53BFwDQoUut+gEAoKcOS3L/9vsvJDmq/f6UJO9e8L0fmOQyQ/feNp9O8rCh10ckuVaSOye5\n",
2059 "a5LDk/yfJJdO8vQFP8uBBV8fAAAAmNJP5GCj4+FpgoSLkvxHmsCG6Z2T5s/wzF3OuUuSb7fnnZ/k\n",
2060 "2gt6lqdmO9tIAECHDln1AwBAT53SHs9PUpO8sn39/UnutpIn6oc3Jvnd9vtLJfmpJdxz3xLuAQAA\n",
2061 "AEzgCmnmmlyU5HXtz26SgzNI/nLC61w5yf9M8s4k/5nku0m+muTDSf4oyX3SLNHZ6Yz2PmePue71\n",
2062 "kvx6kr9O8q9JvpbkgiTfSPKpJK9P8rNjrj3wkKHf5+QcDEBel2bo7vlplk69Jskt9vxNJ3NO9m7K\n",
2063 "JMmthp7ttDHXeMce13hILv77Ddw2l5xhM+rrJXtcHwAAAFiAn87Bfzm/79DP/zUHB9FecY9r3DHN\n",
2064 "Uqfhf9EfNdT2fSM+e0b73lljrv2/Rlxn1LU/nmZWyygPGfrc05J8csTnB1/fzcH5OvM4J5OFMtcZ\n",
2065 "uvcbx1zj7Xtc4yE5+PvdZujnJ2f073jhjq8X73F9AKAHDPoFgOUbLF06L8npQz8/Lc0WyoenCWte\n",
2066 "NObz10nyV+1530ny52lChC+nac/cLM0SqOOTXH6G5zuQ5ItJ/j7J+9M0W77Y3u/qSe7UXv86SV6V\n",
2067 "ZmvvcfYleVL7/cfaZ/1ompbNHdMEVIem+V3fmqbxs2hHD33/pY6v/eEkP57kwTn41/mBueRA5X/v\n",
2068 "+L4AAADAHq6Rg+2Jl+94b7jB8Xe7XON5Q+fdfZfz7pyDs2qGnZHdmzJH7HLNgeE2zc1HvP+Qofc/\n",
2069 "n+QBY67zxKHzfmGC++7mnEzWlHnO0D0fMeYaszZlBp469L5BvwDASAb9AsByPXDo+53zTD6Z5IPt\n",
2070 "97fK+KVB122PFyV50y73elOS+035fEnyzQnOqUPf/9Ae5z4wl/xdB/5k6PvdGjdduU0Ohj9fSfKK\n",
2071 "JdwTAGAkoQwALNdgSct/JnnziPcH4cW+JA8ac43PtsdDMr6B0oUrpHne56cZfPuvaebYfDMXb6N8\n",
2072 "3y7XOJBmZsw4X0gz9DdJrjLzk453aJodrW6V5Nlplkgd1j7Xo9IMRgYAWAkzZQBgeW6a5Afa7/8y\n",
2073 "zdKWnV6R5Fk5GMr8+ohz/izJQ9vvX5omODk9yXuSfCTNnJl5XDbNbJtHZ/cdlgbm/Y88/5XkqDQz\n",
2074 "a7rwA2laROOcn+SXcsnlYwAASyWUAYDlGbRkDmT8spnPphmwe+skJ6ZZ0rNzB6V3pJlZ8qQ0TZA7\n",
2075 "tF9JE/R8NMlr0+zwc+6Uz3hYmiHCg+sdaK/37jQzaD6XZtnPFdMEQl2YN0Qa5cCIn/1Hkv+b5BlJ\n",
2076 "PrGAewIATEUoAwDLcWgObvu8L02wMokHZfS21v87TbDzsDQByg3S/P/1Q5PcsP16TJpdgE4f8fnd\n",
2077 "7jcIZM5qP/+uEedda4prLtun0/y5JE1j5utpAplPreyJAABGEMoAwHLcMclVZ/jc/dOEKxeMeO8T\n",
2078 "SX61/f4ySa6f5EeS3DtN02Z/mjbLddNsaT2J0h4PJPnJNMuhNs03s/fuSXsxdw8AWDihDAAsxylD\n",
2079 "3z82zaDfcfal2XL55CRXTrO19ev3uP63k3yg/XpemqG2j0kzrPfkXHy3pN1csz1+LpsZyMxrsJTq\n",
2080 "yDmvMzzT5tA5rwUAbCmhDAAs3pFJ7tl+/89JnjvBZ/4rTZiSNIHOcChzxTRzXXbzt2lCmaQZojup\n",
2081 "wfDhy+1x3mWmuOYm+WKS6yQ5IU1bZtzA4MP2uM55Q99fOcnZ8z8aALBtVHMBYPHunWZHo2TyHX/e\n",
2082 "kINtmp9IsxRp4E1JnpZmq+dx7tYeD2S6xsv72+MVktxjzDn3yfzLg9bVe9vj/ozebvxSSf5Hkt9u\n",
2083 "X+8bc52PD71/386eDgDYKpoyALB4g6VLFyb5iwk/c0GSVyZ5ZJpWyn9Ps5tS2tdPSvK4NAHN36YZ\n",
2084 "bnthkmPSzIL58fbcd7XvT+r30gz3PTTJaUlelGar7W8mOb59jh+Z4nqb5sVJHpXm939Rmu2135/m\n",
2085 "P2TdNMkDM9mQ4zOSfC1NuPPLaUK5v0+zxfgPpGk6PavTJwcAAAAu5pg0YclFSd485Wdv3n7uolx8\n",
2086 "t6a/HPr5bl9/m9FtmjPa988ac9+fTRMKjbvuuUmeMPT6ySOu8ZCh92+zx+95TnvevO2bwXXOnPM6\n",
2087 "j8nuf67vSbMEba/f76E5+Nd+59eLx3wGAOgRy5cAYLEemGYJy4FMvnRp4L1JPtl+9tZJrt7+/L8n\n",
2088 "uVmSZyT5uzRzUM5P8q00s0tqmiVTJyf58ojrHmi/xnlRe7/Xp5ltc0GSL6Vp5Tw8zbyVVwxda5QD\n",
2089 "e7w/zfNMqqvr/Haa3bLelGYJ2flJPpvk1WmWdN0iyYeG7jnOS4au8+Uk303z12ra9hIAAAAAAAAA\n",
2090 "AAAAbLBxOwZspFLKdZN8LMlptdZTJjj/4Wkq2j9Ta/2TXc47LMkvphl8eJ00Ne4zk7yw1vrSLp4d\n",
2091 "AAAAWK5pc4T2M/uTvDPJD2aPPGEvG7/7UinlhDQD+a6WZt32IdllfXcp5U5pdqW4dpLbtT/ea/35\n",
2092 "K5PcK81AxJcmOSzJ3ZO8pJRyvVrr4+f5HQAAAIDlmDZH2PHZw5O8Lk0gk0k/N87GhzJphh7+fCb/\n",
2093 "g7hFml0lJv0Dv0+aQOadSe5Yaz2//fkV0wxgfGwp5c9qrf887YMDAAAASzdtjpAkKaUckmbjhlul\n",
2094 "2TXydrt/Ym8bv/tSrfWMWushtdZDM8EfSK31aUPnP22CW5zaHp82CGTa63wlyTPTLAE7ddQHAQAA\n",
2095 "gPUybY4w5HeS3CfJKWl2wJzbxocyO0w7I2eS82+ZJj17z4j33tUebzXlfQEAAIDVmyhHKKX8zyS/\n",
2096 "kOSXa6110s/tZdtCmU6VUo5McqUk36i1fmvEKZ9tj8cv76kAAACAZSmlPCTJbyR5Vq31d7u8tlBm\n",
2097 "d0e2x6+Nef+b7XH/Ep4FAAAAWKJSyl3T7Nr88lrrr3Z9/W0Y9LsMF4z5+cx1pbe97W1zTWgGAABg\n",
2098 "vd3hDnfoZInLulnHf59dxJ91KeXGSV6V5G+SPKzr6ydCmb18vT1edsz7R+w4DwAAANgOJ6fJA85J\n",
2099 "8sxSyvB7g9my9y2lnJTkb2utfz3tDYQyu6i1fr2U8uUk319KuVyt9Rs7TjmmPZ416z0+9PEj9j6J\n",
2100 "jXXgQ59f9SMALN13//Gze58EbJVzz/zoqh8B1sq9X1/2PmkLvObuddWPsOg/6wNpVsg8Ypdz7pjk\n",
2101 "x9OMhxHKLMC7ktw9TUL2hh3v/Wh7HLUzE2TfjY7+3vcCGqAvDrtp898shDPQH8eedP0kwhlgu9Ra\n",
2102 "n5fkeaPeK6U8JclTkjy81vriWe9h0O/eXt4eH1dKOWzww1LKFZP8Sprk7GWreDA2y74bHX2xL4Bt\n",
2103 "d9hNj/neF9APx550/e99AWy5TmbYbHxTppRybJL7ty9PaI8nlVIe137/kVrrm4fOv1UOrv0aHO9U\n",
2104 "Svn+9vs31FrPHJxfa62llFPStGU+Wkp5e5LDktw1ydFJnldr/WDXvxfbb2cwo0kDbDPtGegf7Rlg\n",
2105 "XU2bIyzSxocySa6d5FlDrw8kuXGSm7Sv/zTJ8B/mj6epGA3OPZCktF8HknwxyZm5uPskeVSSU5I8\n",
2106 "OMmFST6W5Im11j/t5teg74Q0QB8Mt2YENNAPw60ZAQ2wJqbNEUYZ5Alz2crtuTbBYAsxg36ZhIAG\n",
2107 "2GbCGegf4QzbbjB8dtu3xF6nQb+b+me9DU0Z2HpaNMA2056B/tGeAWgIZWADCWmAbWX2DPSP2TNA\n",
2108 "nwllYAvYehvYNtoz0D/aM0AfCWVgy2jRANtGewb6R3sG6AuhDGw5IQ2wLbRnoH+0Z4BtJ5SBnrHU\n",
2109 "CdgG2jPQP9ozwDYSykCPadEAm057BvpHewbYJkIZ4HuENMAm056B/tGeATadUAYYy1InYBMJZ6B/\n",
2110 "tGeATSWUASaiRQNsGkuboJ+0Z4BNIpQBZiKkATaJ9gz0j/YMsAmEMkAnLHUCNoH2DPST9gywroQy\n",
2111 "QOe0aIBNoD0D/aM9A6wboQywcEIaYJ1pz0A/ac8A60AoAyydpU7AutKegf7RngFWSSgDrJQWDbCO\n",
2112 "tGegn7RngGUTygBrRUgDrBvtGegf4QywLEIZYK1Z6gSsC+0Z6B9Lm4BFE8oAG0OLBlgX2jPQP9oz\n",
2113 "wCIIZYCNJaQBVk04A/2jPQN0SSgDbA1LnYBVsbQJ+kl7BpiXUAbYSlo0wKpoz0D/aM8AsxLKAL2g\n",
2114 "RQMsm/YM9JP2DDANoQzQOwIaYNm0Z6B/tGeASQhlgF4T0ADLpD0D/aQ9A4wjlAFoDQIa4QywDNoz\n",
2115 "0D/aM8BOQhmAHbRngGXSnoF+0p4BEqEMwK4ENMAyac9A/2jPQL8JZQAmZHkTsCzaM9BP2jPQP0IZ\n",
2116 "gClpzwDLpD0D/aM9A/1xyKofAGCT7bvR0RcLaQAW5bCbHnOxBg3QD8eedP2LhTTAdtGUAeiA9gyw\n",
2117 "LJY2QT9pz8B2EsoAdExAAyyLpU3QT2bPwPYQygAskOHAwDJoz0A/ac/A5hPKACyB9gywLNoz0E/a\n",
2118 "M7CZhDIASyagAZZBewb6SXsGNovdlwBWyO5NwDLYuQn6yc5NsP40ZQDWgPYMsAzaM9BP2jOwvjRl\n",
2119 "ANaM9gywDNoz0E/aM7BeNGUA1pT2DLAMBgNDPxkMDOtBKAOwAQQ0wKJZ2gT9ZGkTrJblSwAbxvIm\n",
2120 "YNEsbYJ+srQJlk9TBmBDac8Ai6Y9A/2kPQPLI5QB2AICGmDRzJ6BfjJ7BhZLKAOwZQYBjXAGWATt\n",
2121 "Gegn7RlYDKEMwJbSngEWTXsG+kl7BrojlAHoAe0ZYJG0Z6CftGdgfkIZgB7RngEWTXsG+kl7BmYj\n",
2122 "lAHoKQENsEjaM9BP2jMwnUNW/QAArN6+Gx19sZAGoEuH3fSYi4U0QD8ce9L1LxbSAJekKQPA92jP\n",
2123 "AItkaRP0k/YMjCeUAWAkAQ2wKJY2QX+ZPQMXJ5QBYE92bwIWRXsG+kl7BhpCGQAmpj0DLIr2DPSX\n",
2124 "9gx9JpQBYCbaM8CiaM9AP2nP0EdCGQDmoj0DLIr2DPSX9gx9IZRZsaNOumq+cOYXV/0YAJ0Q0ACL\n",
2125 "oj0D/aQ9w7YTyqyBo0666ve+F9AA28LyJmARtGegv7Rn2EZCmTUjoAG2jfYMsCjaM9BP2jNsE6HM\n",
2126 "GhPQANtGQAMsgvYM9Jf2DJtOKLMhhgOaREgDbD7Lm4BF0J6BfhLOsKmEMhtKiwbYFtozwCIIZ6Cf\n",
2127 "LG1i0whltoCABtgW2jNA1yxtgv7SnmETCGW2jIAG2AbaM8AiaM9AP2nPsM6EMltMQANsAwEN0DXt\n",
2128 "Gegv7RnWjVCmJwQ0wDawvAnomvYM9JP2DOtCKNNDAhpg02nPAF3TnoH+0p5hlYQyPSegATadgAbo\n",
2129 "mvYM9JP2DKsglOF7BDTAprO8CeiS9gz0l/YMyyKUYSQBDbDJtGeArmnPQD9pz7BoQhn2JKABNpn2\n",
2130 "DNAl7RnoL+0ZFkEow1QENMCm0p4BuqY9A/2kPUOXhDLMTEADbCoBDdAl4Qz0l/YM8xLK0AkBDbCp\n",
2131 "LG8CumJpE/SX9gyzEsrQOQENsIm0Z4Auac9Afw0HNLAXoQwLNRzQJEIaYDMIaICuaM8AsBuhDEul\n",
2132 "RQNsGsubgK5ozwCwk1CGlRHQAJtEewboivYMAANCGdaCgAbYJNozQFe0ZwD6TSjD2hHQAJtCewbo\n",
2133 "ivYMQD8JZVhrAhpgUwhogK5ozwD0h1CGjSGgATaF5U1AF4QzANtPKMNGEtAAm0B7BuiCpU0A20so\n",
2134 "w8YT0ACbQHsG6IL2DEC3SinXTfKxJKfVWk8Z8f6RSX46yR2S3DDJVZOcn+STSV6R5Hm11u/Men+h\n",
2135 "DFtFQAOsO+0ZoAvaMwCzK6WckOQxSa6W5I5JDklyYMzpN0/y20m+muSdSc5JcsUkd0nyjCT3KKXc\n",
2136 "ttZ6wSzPIpRhawlogHUnoAG6oD0DMLWrJ/n5jA9ihv1Hkp9N8rJa6/mDH5ZSLp/kH5LcKsmDk7x4\n",
2137 "lgcRytALAhpg3VneBMxLewZgMrXWM9K0Y1JKOTnJO3Y590NJPjTi5+eVUl6SpkVz0whlYDICGmCd\n",
2138 "ac8AXdCeAZjYvjk+e0R7/M9ZLyCUodcENMA6E9AA89KeAViMUsq+JKV9+c5ZryOUgZaABlhnljcB\n",
2139 "89KeAejUo9PsxvQPtda3zXoRoQyMIKAB1pX2DDAv7RmA+ZRS7p/k2Uk+m+R+81xLKAN7GA5oEiEN\n",
2140 "sD60Z4B5ac8ATKeUcmqSP0ny70l+rNb67/NcTygDU9KiAdaN9gwwL+0ZYBbHnnT9VT/CUpVSnpzk\n",
2141 "qUk+luSutdbPzHtNoQzMQUADrBsBDTAv7RmAiyulHJ7kj5I8KMnfJLlPrfVrXVxbKAMdEdAA68by\n",
2142 "JmAewhmApJRyTJLXJLlZkt9L8pha64VdXV8oAwsgoAHWifYMMA9Lm4BtU0o5Nsn925cntMeTSimP\n",
2143 "a7//SK31ze33T08TyPxbkvOTPLOUkhF+v9Z61rTPIpSBBRPQAOtEQAPMQ3sG2BLXTvKsodcHktw4\n",
2144 "yU3a13+aZBDK7GvfPyHJY8dc70CS05MIZWCdCWiAdWJ5EzAr7Rlgk9Vaz0hyyITnPjTJQxf1LEIZ\n",
2145 "WBEBDbAutGeAeWjPAMxOKANrQEADrAvtGWBW2jMA0xPKwJoR0ADrQHsGmIf2DMBkhDKwxgQ0wDoQ\n",
2146 "0ACz0p4B2J1QBjaEgAZYB5Y3AbPSngG4JKEMbCABDbBq2jPArLRnAA4SysCGE9AAqyagAWalPQP0\n",
2147 "nVAGtoiABlg1y5uAWWjPAH0llIEtNRzQJEIaYLm0Z4BZac8AfSKUgZ7QogFWRXsGmIX2DNAHQhno\n",
2148 "IQENsAraM8CstGeAbSWUgZ4T0ACrIKABZiGcAbaNUAb4HgENsAqWNwHTsrQJ2BZCGWAkAQ2wbNoz\n",
2149 "wCy0Z4BNJpQB9iSgAZZNewaYlvYMsImEMsBUBDTAMmnPALPQngE2hVAGmJmABlgm7RlgWtozwLoT\n",
2150 "ygCdENAAy6I9A8xCewZYR0IZoHMCGmBZtGeAaWnPAOtEKAMslIAGWAbtGWAW2jPAqgllgKUR0ADL\n",
2151 "oD0DTEt7BlgVoQywEgIaYNG0Z4BZaM8AyySUAVZOQAMsmvYMMC3tGWAZhDLAWhHQAIukPQPMQnsG\n",
2152 "WBShDLC2hgOaREgDdEt7BpiWcAbomlAG2BhaNMAiaM8A07K0CeiKUAbYSAIaYBEENMC0tGeAeQhl\n",
2153 "gI0noAEWwfImYBraM8AshDLAVhHQAF3TngGmpT0DTEooA2wtAQ3QNe0ZYBraM8BehDJALwhogC5p\n",
2154 "zwDT0p4BRhHKAL0zCGiEM0AXtGeAaWjPAMOEMkBvac8AXdKeAaalPQMIZQAioAG6pT0DTEN7Bvrr\n",
2155 "kFU/AMC6Oeqkq14spAGY1b4bHX2xBg3AXg676TEXC2mA7SaUARhDOAN0RTgDTEs4A/0glAHYg3AG\n",
2156 "6IpwBpiWcAa2m1AGYELCGaArwhlgWsIZ2E5CGYApCWeArghngGkJZ2C72H0JYEaDYMZuTcC8bKcN\n",
2157 "TMt22rAdhDIAcxLOAF2ynTYwDdtpw2YTygB0ZHhJk4AGmJdwBpiW9gxsHjNlABbA3BmgK+bOANMy\n",
2158 "dwY2h1AGYIGEM0BXhDPAtIQzsP6EMgBLIJwBuiKcAaYlnIH1JZQBWCLhDNAV4QwwLeEMrB+hDMAK\n",
2159 "CGeArghngGkJZ2B9CGUAVkg4A3RFOANMSzgDq2dLbIA1YDttoCu20gamNRzM2E4blktTBmDNaM8A\n",
2160 "XdCcAWahPQPLJZQBWFPCGaALwhlgFsIZWA6hDMCaE84AXRDOALMQzsBiCWVW7MRj9+fEY/ev+jGA\n",
2161 "DSCcAbognAFmIZyBxRDKrAnhDDAp4QzQBeEMMAvhDHRLKLNmhDPApIQzQBeEM8AshDPQDVtir6nh\n",
2162 "YOYT535thU8CrDvbaQNdsJU2MItBMGMrbZiNUGYDDAIa4Qywl0FAI5wBZiWcAWYx3JoR0MDkLF/a\n",
2163 "IJY2AZOytAmYl2VNwKwsbYLJacpsIM0ZYFKaM8C8hoMZ7RlgGpY2wd62LpQppVw3yceSnFZrPWWX\n",
2164 "8+6Z5NFJbpTk8CSfSvLKJM+stX5rxPkX7XHr99Zabznzg89AOANMSjgDdMHSJmAWwhkYbytCmVLK\n",
2165 "CUkek+RqSe6YZlnWgV3Of1SS5yb5SpLTk3wtyclJnpzk9qWU29Vavzvio19P8odjLvupmX+BORkK\n",
2166 "DExKOAN0QTgDzEI4A5e0FaFMkqsn+fnsEsQMlFKOSfKMJF9K8kO11s+0P9+X5LQk903yiCTPH/Hx\n",
2167 "r9ZaH9/VQy+C9gwwCeEM0AXhDDAL4QwctBWDfmutZ9RaD6m1Hprkdnucfr80y5VeOAhk2mscSPLE\n",
2168 "9uVDF/Oky2MoMDAJA4GBLhgKDMzCQGDYklBmh317vD+Y+/LunW/UWs9K8sUkNyylXKbrB1sF4Qww\n",
2169 "iUE4I6AB5iGcAWYhnKHPtmX50jSOb4/jOvufTXKVJMcl+Zcd7x1TSvlOmj+385J8Mslrkzyv1nre\n",
2170 "Ap61M5Y1AZOytAmYl2VNwCyGgxlLm+iLPoYyR6aZPTMunfhmmrbNznrJPyX5eJL/TNMwumaS2ye5\n",
2171 "SZKfKqXcqtb61YU8cYcMBQYmJZwB5iWcAWZl7gx90cdQZuCCMT8fufyp1nrTnT8rpVwlyZvTbKv9\n",
2172 "P5P8amdPtwTaM8AkhDPAvIQzwKyEM2y7bZwps5evpwleLjvm/SOGzttVrfVLSR7dvtxrwPDaMncG\n",
2173 "mISZM8C8zJwBZmXuDNuqj6HM2e3xmmPePybJRUPn7eXL7fHy8zzUOhDOAJMQzgDzEs4AsxLOsG36\n",
2174 "GMq8qz1eotlSSrlOmiG/H621fmvC6924Pe4cCryxBuGMgAbYjXAGmJdwBpiVcIZt0cdQ5lVJzk9y\n",
2175 "ainle/9XXEo5JMnT25cvHf5AKeURpZTb7LxQKeXYJL+RZnDwHy/siVdIOAPsxXbawLyEM8CshDNs\n",
2176 "uq0Y9NuGI/dvX57QHk8qpTyu/f4jtdY3J0mt9dxSyq8l+a0kHy6lvD7N9ta3TnKDJO9N8oIdt7hF\n",
2177 "kj8opZyT5N1pdmC6RpI7pJlN87u11jcu4ndbF4YCA5MwFBiYh4HAwKwMBGZTbUUok+TaSZ419PpA\n",
2178 "mmVFN2lf/2maXZKSJLXW55RSzkryqCT3SnJ4mhkyT0/yzFrr+Tuu/4Ik30pysyQ/luRKabbU/rsk\n",
2179 "v19r/euOf5+1JZwBJiGcAeYhnAFmNdyaEdCwCbYilKm1npEpl2LVWl+b5LUTnvuBJB+Y/sm2l3AG\n",
2180 "mIRwBpiHcAaYh/YMm2ArQhlWZ3jejIAGGEc4A8xDOAPMQzjDOuvjoF8WxFBgYC8GAgPzMBAYmIeh\n",
2181 "wKwjTRk6Z2kTsBfNGWAew8GM9gwwLc0Z1ommDAujOQPsRXMGmJf2DDArzRnWgaYMC6c5A+xlOJjR\n",
2182 "ngFmYe4MMCvNGVZJKMPSGAoMTMLSJmAewhlgVsIZVkEow0pozwB7Ec4A8xDOALMaXtIkoGHRzJRh\n",
2183 "pcydAfZi7gwwDzNngHmYO8OiCWVYC8IZYC/CGWAewhlgHsIZFkUow1oRzgB7Ec4A8xDOAPMQztA1\n",
2184 "M2VYS4YCA3sxcwaYh5kzwDwMBd4epZTrJvlYktNqrafsct49kzw6yY2SHJ7kU0lemeSZtdZvzXp/\n",
2185 "oQxrz1BgYDe20wbmIZwB5iGc2UyllBOSPCbJ1ZLcMc0qogO7nP+oJM9N8pUkpyf5WpKTkzw5ye1L\n",
2186 "KbertX53lmcRyrAxhDPAXrRngFkJZ4B5CGc2ztWT/Hx2CWIGSinHJHlGki8l+aFa62fan+9LclqS\n",
2187 "+yZ5RJLnz/IgZsqwccydAfZi7gwwKzNngHmYObMZaq1n1FoPqbUemuR2e5x+vzTLlV44CGTaaxxI\n",
2188 "8sT25UNnfRahDBtLOAPsRTgDzEo4A9Ab+/Z4/5bt8d0736i1npXki0luWEq5zCw3t3yJjWcoMLAX\n",
2189 "y5qAWVnWBNB7x7fHcf8g+dkkV0lyXJJ/mfbimjJsFe0ZYDeaM8CsNGcAeuvINLNnxjUAvpmmbTPT\n",
2190 "v4gKZdhKwhlgN8IZYFbCGYDeumDMz/da/rQry5fYanZsAnZjWRMwK8uaANKXocZfTxO8XHbM+0cM\n",
2191 "nTc1oQy9YO4MsJvh1oyABpiGcAZg652d5MZJrpnRM2OOSXJRe97ULF+idyxtAnZjaRMwC8uaALbW\n",
2192 "u9rjJbbOLqVcJ82Q34/WWr81y8WFMvSWcAbYjXAGmMUgnBHQAGyNVyU5P8mppZTvrdcqpRyS5Ont\n",
2193 "y5fOenHLl+g9c2eA3Zg7A8zK0iaA9VRKOTbJ/duXJ7THk0opj2u//0it9c1JUms9t5Tya0l+K8mH\n",
2194 "SymvT3JeklsnuUGS9yZ5wazPIpSBlnAG2I1wBpiVcAZg7Vw7ybOGXh9IMzfmJu3rP03y5sGbtdbn\n",
2195 "lFLOSvKoJPdKcniaGTJPT/LMWuv5sz6IUAZ2MBQY2I1wBpiVcAZgPdRaz8iU41xqra9N8tqun8VM\n",
2196 "GdiFuTPAOGbOALMycwaAAU0ZmIClTcA4ttMGZqU5A4CmDExBcwbYjfYMMAvNGYD+EsrADIQzwG6E\n",
2197 "M8AshDMR5muJAAAgAElEQVQA/WP5EszBUGBgN4YCA7OwrAmgPzRloCPaM8A4mjPALDRnALafUAY6\n",
2198 "JpwBxhHOALMQzgBsL6EMLIhwBhhHOAPMQjgDsH3MlIEFs502MI7ttIFZmDkDsD2EMrAkhgIDuzEU\n",
2199 "GJiWcAZg81m+BCtgaRMwjqVNwLQsawLYXEIZWCHhDDCOcAaYlnAGYPMIZWANCGeAcYQzwLSEMwCb\n",
2200 "w0wZWCOGAgPjmDkDTGs4mDF3BmA9CWVgDRkKDIwjnAFmYSgwwHqyfAnWnKVNwCiWNQGzsLQJYL1o\n",
2201 "ysCGsLQJGGU4mNGeASalOQOwHjRlYMNozgDjaM8A09KcAVgtTRnYUJozwDjmzgDTMhQYYDWEMrDh\n",
2202 "DAUGxhHOALOwtAlgeSxfgi1iaRMwimVNwCwsbQJYPKEMbCHhDDCKcAaYhXAGYHEsX4ItZu4MMIpl\n",
2203 "TcAszJ0B6J5QBnrA3BlgFNtpA7MydwagG5YvQc9Y2gSMYmkTMAtLmwDmI5SBnhLOAKMIZ4BZCGcA\n",
2204 "ZiOUgZ4TzgCjCGeAWQhnAKZjpgyQxFBgYDRDgYFZGAoMMBmhDHAxhgIDowhngFkZCgwwnuVLwFiW\n",
2205 "NgE7WdYEzMrSJoBLEsoAexLOADsJZ4BZCWcADrJ8CZiYuTPATsPBjKVNwDTMnQHQlAFmoDkDjKI9\n",
2206 "A8xKewboK00ZYGaGAgOjGAoMzMpQYKBvNGWATmjPADtpzgCz0pwB+kIoA3RKOAPsJJwBZiWcAbad\n",
2207 "5UvAQhgKDOxkWRMwK0OBgW2lKQMslOYMsJPmDDAP7Rlgm2jKAEthKDCwk+YMMA9DgYFtoCkDLJ32\n",
2208 "DDBMcwaYh+YMsMk0ZYCVMXcGGKY5A8zD3BlgE2nKACunOQMM05wB5qU9A2wKoQywNoQzwDDhDDAv\n",
2209 "4Qyw7ixfAtaOocDAMMuagHkZCgysK00ZYK1pzwADmjPAvDRngHWjKQNsBEOBgQHNGWBehgID60JT\n",
2210 "BtgomjPAgOYM0AXtGWCVNGWAjWTuDDCgOQN0wdwZYBU0ZYCNpz0DJJozQDc0Z4Bl0pQBtoa5M0Ci\n",
2211 "OQN0w9wZYBk0ZYCtozkDJJozQHe0Z4BF0ZQBtpbmDJDkYsGM9gwwD3NngK4JZYCtZygwMGBpE9AF\n",
2212 "4QzQFcuXgF6xtAlILG0CumFZEzAvTRmglyxtAhLNGaAbhgIDs9KUAXpNcwZINGeA7mjPANMQygBE\n",
2213 "OAM0hDNAV4QzwCSEMgBDhDNAIpwBuiOcAXZjpgzACGbOAImZM0B3zJ0BRtGUAdiF5gyQaM4A3dKe\n",
2214 "AQaEMgATEM4AiXAG6JZwBhDKAExBOAMkwhmgW8IZ6C8zZQBmMBzMmDsD/WXmDNClQTBj5gz0h1AG\n",
2215 "YE6GAgPCGaBLhgJDf1i+BNARS5sAy5qArlnaBNtNKAPQMeEMIJwBuiacge0klAFYEOEMIJwBuiac\n",
2216 "ge1ipgzAgpk5A5g5A3TN3BnYDpoyAEuiOQNozgCLoD0Dm0soA7BkwhlAOAMsgnAGNo9QBmBFhDOA\n",
2217 "cAZYBOEMbA6hDMCKCWcA4QywCMIZWH8G/a7YcUfvz9mfN/wTMBAYMBAYWAxDgWF9LSSUKaUcmeRm\n",
2218 "Sa6S5PBa68uG3rtykiOSXFBr/fdF3H/TCGaAYcIZQDgDLMogoBHOwHrodPlSKWV/KeWPknwpyVuT\n",
2219 "nJbkJTtOu0WSc5J8qpRytS7vv8mOO3p/jjva8gXgIMuaAMuagEWxrAnWQ2ehTCnlMknenuSn2+t+\n",
2220 "IsmBnefVWl+f5B1JDk3ygK7uvy0EM8BOwhlAMAMsgpkzsHpdNmV+MclN0oQx16+1/rck3x1z7h+3\n",
2221 "x5/o8P5bQ2sGGEU4A/2mNQMsinAGVqfLUOa+7fExtdZP7HHu29vj9Tq8/9YRzACjCGag34QzwKII\n",
2222 "Z2D5ugxlfiDNcqV/mODcL7bnXqHD+28lrRlgFK0ZQDgDLIpwBpany1DmUmmClvMmOPfySfYl+UaH\n",
2223 "999qghlgFOEMIJgBFkU4A4vXZSjzmTRBywkTnHv79vhvHd5/62nNAOMIZ6DftGaARRLOwOJ0Gcq8\n",
2224 "KU0o88jdTiqlXC7Jr7cv39Lh/XtDMAOMI5iBfhPOAIsknIHuXarDaz07ycOTPLKUclaSFwy/WUrZ\n",
2225 "l+THkvx2kpPSLF16wc6LMJlBMHP257+24icB1s0gmPnEuf7+AH01CGa+cOYXV/wkwDYaBDMHPvT5\n",
2226 "FT8JbL7OQpla66dLKQ9I8qokv5PkSUkOS7KvlPJPSa6R5Ipp2jQXJHlIrfVzXd2/r447er9gBhhJ\n",
2227 "OAMcddJVBTPAwghn2HSllLsn+YUkN0tyRJJzk3wgybNqrf+0jGfocvlSaq1/leSWSf4+yZXSBDBJ\n",
2228 "csMk39e+/nCSO9RaX93lvfvMrBlgN+bNQL9Z0gQsmmVNbKJSym8mOT3JD6cZx/JHaebeliQfKKWc\n",
2229 "uozn6HL5UpKk1vrBJLcppRyf5FZJrpbk0DTbYL+/1vqRru9JQ2sG2M2Jx+7XmoEes6QJWDTNGTZF\n",
2230 "KeV6SX41ySeS3KLW+pWh926Z5Iwkv1NK+Yta63cX+SydhzIDtdazkpy1qOszmlkzwG4saQKEM8Ci\n",
2231 "CWfYADdoj28cDmSSpNb67lLKR5PcKM0KoIX+D7mzUKaUcmiS308zR+Z1tdbTx5x31zR1oG8neWSt\n",
2232 "9UBXz8BBWjPAboQzgHkzwKIJZ1hjZ7bHe5RSnlFr/cLgjVLKYUmunuRTtdaF/4+3y6bMPZL8TJLP\n",
2233 "JXnULue9M8mL0ixremOaNVwsgNYMsBfhDPSb1gywDMIZ1k2t9Z9LKc9O8rgkZ5ZSnp/kz5Kck2aX\n",
2234 "6COT/NQynqXLQb+ntMffqbV+fdxJtdbz0myLvS/JQzq8P2MYAgzsxTBg6DfDgIFlMBCYdVJrfXyS\n",
2235 "30yzKdGTknw8yReSPCDJ7Wqtb1vGc3QZytwyyYEkfznBua9pj7fo8P7swg5NwCQEM9BvwhlgGYQz\n",
2236 "rINSyjOTPCHJTyf5/5I8Ms1u0ZdL8n9LKWUZz9Hl8qUrJbmo1nr2BOd+Ok2Ac6UO788EzJoB9mJJ\n",
2237 "E2BZE7AMljWxKqWU+yb5lSS/W2t9SfvjFyZ5YSnlVkleneS0Uso5tdb3L/JZugxlvprk+0sp+2ut\n",
2238 "e/2T/OXTLF/yT/wrYNYMMAnhDGAYMLAMwpnNtR6Np2/O8qFBC+YtO9+otb6rlPLcJM9oz1toKNPl\n",
2239 "8qUPpglaJqn43Ls9frTD+zMly5mASZg3A/1mSROwLJY1sUSHt8drjHl/UGA5dNEP0mUo87L2+Ful\n",
2240 "lFuOO6mU8sNJnt2+fGWH92cGZs0AkxLMQL8JZ4BlEc6wBG9sj08qpZww/EYp5epJfi7NyJXXLfpB\n",
2241 "uly+dFqShya5XZK/LaX8dZK3JTk3zS9z9SR3SLN19qFpBui8uMP7MwezZoBJWNIEmDcDLItlTSzQ\n",
2242 "i5LcLcldk3yslPLmJJ9JM/D3LkkuneR/11r/btEP0llTptZ6UZL7JHlDmrDnJ9Ps7/1XSU5vv//J\n",
2243 "NIHM+5LcrdZ6flf3Z35aM8CkLGkCtGaAZdGcoWu11guT/ESShyd5T5LbJHlEml2l35BmS+ynLeNZ\n",
2244 "umzKpNb61SR3L6XcNcmD02x5fVT79n+kCWNe1ZxaL+ry3nRHawaYlOYM9JvWDLBMmjN0qdZ6IM3q\n",
2245 "nZWu4Ok0lBmotb4hTbrEhrJDEzCNE4/dL5iBHhPOAMu070ZHC2bYGl0O+mULWc4ETMqSJsAwYGBZ\n",
2246 "LGliWwhl2JNZM8A0hDOAYAZYFuEMm27m5UullHck+U6t9c7t65ek2WVpKrXWh836DCyXWTPANMyb\n",
2247 "gX6zpAlYJvNm2FTzzJQ5Ocm3h16fOsM1DiQRymwQs2aAaQlnoN+EM8AyCWfYNPOEMu9M8p2h138x\n",
2248 "wzWmbtawHrRmgGkZBgz9JpwBlkk4w6aYOZSptd52x+sHzf00bBStGWBaWjOAcAZYJuEM666zQb+l\n",
2249 "lDuVUu7W1fXYHIYAA9MyDBgwDBhYJgOBWVfzLF/a6bXt8YgOr8mGsJwJmIXmDPSb1gywbJozrJsu\n",
2250 "t8Q+tMNrsYFsnQ3MSmsG+u2ok66qOQMsleYM66LLUOacJIeXUi7b4TXZQIIZYBaWNAHCGWDZhDOs\n",
2251 "WpehzOlJ9iW5Q4fXZENpzQCzEs4Aghlg2YQzrEqXoczzknwryRM7vCYbTjADzEo4A/2mNQOsgnCG\n",
2252 "Zety0O/dkvxjkh8tpfx+kg9N8qFa64s6fAbWkK2zgXmceOx+g4ChxwwDBlbBQGCWpctQ5g+Gvv+5\n",
2253 "CT9zIIlQpifs0ATMyi5NgHAGWAXhDIvWZSjz6Rk+c6DD+7MBtGaAeQhngKNOuqpgBlg64QyL0lko\n",
2254 "U2u9VlfXYvtpzQDzEM5Av2nNAKsinKFrXQ76hanYoQmYl2HA0G+GAQOrYiAwXemkKVNKuXSSaye5\n",
2255 "fJLP1Fo/18V16QetGWBehgFDv2nOAKuiOcO85gplSimHJnlSkl9KcoWhn38gyRNqrWfM9XT0hlkz\n",
2256 "wLwsaQKEM8CqCGeY1bzLl16U5MlJrphk39DXzZK8tZTygDmvT89YzgTMy5ImwJImYFUsa2JaM4cy\n",
2257 "pZQfS/LQ9uXLk9w6yfWTlCTvSnJokj8upRwz70PSL2bNAF0QzkC/mTcDwCaYZ/nSw9rjK2utpw79\n",
2258 "/MxSyl8l+Zs0Qc0vJXnCHPehp8yaAbpg3gz0myVNAKyzeZYv3bw9/s7ON2qtFyT59fbl7ee4Bz2n\n",
2259 "NQN0QWsG0JwBYB3NE8ock+RAkn8c8/772uNxc9wDkpg1A3RDOAMIZgBYJ/OEMpdNcn7birmEWutX\n",
2260 "k1yUxD/90gmtGaArwhnoN60ZANbFvLsvHdjj/Qs6uAdcjGAG6IpgBvpNOAPAqs0z6DdJ9pVSThz3\n",
2261 "XvuVXc5JrfUTcz4DPTQIZgwCBuY1CGYMA4b+MgwYgFWZN5Q5PMm/7PL+vvY46px9aZo2h875DPSY\n",
2262 "HZqArghngKNOuqpgBoClmjeUSQ4GL7OcM8lnYVdaM0CXhDPQb1ozACzTPKHM8Z09BXRAawboknAG\n",
2263 "+k04A8AyzBzK1FrP6fA5oBNaM0DXTjx2v2AGekw4A8Ai2RmJrWSHJqBLttAG7NQEwCIIZdhaxx29\n",
2264 "XzgDdEo4AwhmAOiSUIatJ5gBuiacgX7TmgGgK13svrQ2SinXTfKxJKfVWk/Z5bx7Jnl0khul2db7\n",
2265 "U0lemeSZtdZvjTj/sCS/mOTBSa6T5IIkZyZ5Ya31pV3/HnTPrBlgEcybgX4zbwaAeW18U6aUckIp\n",
2266 "5QWllNck+cc0v9OBXc5/VJLXJrlhktOT/EmS7yZ5cpK3tAHMTq9M8uwkl0/y0iSvSnKtJC8ppTyr\n",
2267 "u9+GRdOaAbqmNQNozgAwq40PZZJcPcnPJ7lnksvudmIp5Zgkz0jypSQ3rLWeWmv9xTQBzauS/EiS\n",
2268 "R+z4zH2S3CvJO5OcVGt9ZK31Z5L8tySfTPLYUsoPdvsrsUhmzQCLIJwBBDMATGvjQ5la6xm11kNq\n",
2269 "rYcmud0ep98vzXKlF9ZaPzN0jQNJnti+fOiOz5zaHp9Waz1/6DNfSfLMJPuGzmGDCGaARRDOQL9p\n",
2270 "zQAwjY0PZXbYt8f7t2yP7975Rq31rCRfTHLDUspld3zmQJL3jLjeu9rjraZ8TtaE1gywKIIZ6Dfh\n",
2271 "DACT2LZQZi/Ht8dx09g+mybYuVaSlFKOTHKlJN8YNQC4PX/4umwowQywCFozgHAGgN30LZQ5Mk3r\n",
2272 "ZdxWGd9ME8rsHzo/e5yfofPZYFozwKIIZwDBDACjdL4ldinl+CQ/m2bZz1FJLl1rPX7o/XulGcr7\n",
2273 "nSSPrLVe1PUzTOCCMT8ft/xp2vPZYMcdvd/W2cBCDIIZ22hDP9lCG4CdOg1lSimnJnlhmmG6Azu3\n",
2274 "p35HkhcnuUKSVyd5a5fPsIevpwlSxu3SdMTQecPHSc9nSwwaM8IZYBGEM9BvwhkABjpbvlRK+aEk\n",
2275 "f5wmkPmzJA/IiIZJrfWrSf4gTThy/67uP6Gz2+M1x7x/TJKLBufVWr+e5MtJvr+Ucrkx5yfJWV0+\n",
2276 "JOvDciZgkSxpgn4zbwaALmfKPDbJoUmeW2t9cK31lWkCjlFe3R5/pMP7T2KwW9Ilts4upVwnyVWS\n",
2277 "fHTHUN93pfm9Th5xvR9tj6N2ZmJLmDUDLJJ5M4BgBqC/ugxlbpNmqdILJjj3zPZ49Q7vP4lXJTk/\n",
2278 "yamllEHLJaWUQ5I8vX350h2feXl7fFwp5bChz1wxya+k+Z1ftrAnZm0IZoBFEs5Av2nNAPRTlzNl\n",
2279 "rpImoDhngnPPb8+de1BuKeXYHFwGdUJ7PKmU8rj2+4/UWt+cJLXWc0spv5bkt5J8uJTy+iTnJbl1\n",
2280 "khskeW92hEq11lpKOSXJ3ZN8tJTy9iSHJblrkqOTPK/W+sF5fw82g1kzwKKZNwP9Zt4MQL902ZT5\n",
2281 "WpqQ5fsmOPfa7blf6uC+107yrPbrEWnCnhsP/ex+wyfXWp+T5D5JPprkXkl+Ok3I8vQkt6+1nj/i\n",
2282 "HvdJ8vgk307y4CT3TfKpJA+rtf5yB78DG0ZrBlg0rRnoN80ZgH7osinzT0lun2bOyl/tce7PtMf3\n",
2283 "zXvTWusZmTJcqrW+Nslrpzj/u0me3X5BEltnA4unNQNozgBsty6bMoNZLL/ZzlsZqV0KNGiXvHzc\n",
2284 "ebAJDAEGlsG8GUBrBmA7ddmU+fMkpyT58STvL6U8P+3MmFLKPZMcn+Qnc3DHorfUWk/v8P6wMloz\n",
2285 "wDJozkC/ac0AbJ/OmjK11gNpZq+8Os3A3eemmdWyL81SoedkKJDJjlkvsOm0ZoBl0ZyBfjNvBmB7\n",
2286 "dNmUSa31vCSllHK7JA9JcqskV0tyaJqhvu9L8vJa6+u6vC+sE60ZYFlOPHa/1gz0mOYMwObrNJQZ\n",
2287 "qLW+PcnbF3Ft2AS2zgaWxZIm4KiTriqYAdhQnS1fKqVM3aEspTyyq/vDOrKcCVgWS5qg3yxpAthM\n",
2288 "Xe6+9HellGMnObGUsq+U8pwkv9fh/WEtmTUDLJNwBvpNOAOwWboMZa6T5O9LKdfZ7aRSymWS1DTb\n",
2289 "Yu/r8P6w1gQzwDIJZqDfhDMAm6HLUOY9Sa6R5J2llB8cdUIp5SpJ3pHk3kkOJPm1Du8Pa09rBlgm\n",
2290 "rRlAOAOw3roMZe6Q5A1JjkryjlLKLYbfLKWcmOTdSW6e5NtJ7l9r/T8d3h82hmAGWCbhDCCYAVhP\n",
2291 "nYUytdZvJrlXkpcl+b4kb2m3xk4p5dZpApnj02yNfbtaa+3q3rCJtGaAZRPOQL9pzQCsny6bMqm1\n",
2292 "XpDkoUmeneTySV5fSvmtJG9NE9T8a5Jb1Frf0+V9YZMJZoBlE8xAvwlnANZHp6FMktRaD9RaH5/k\n",
2293 "cUkuk+SxSS6dZpbMLWutZ3d9T9h0WjPAsmnNAMIZgNXrPJQZqLX+dpIHJ7kwyQVJHlNr/eqi7gfb\n",
2294 "QDADLJtwBhDOAKzOpWb5UCnlTml2T9rLl5I8P8mj0syYeWSSrw+fUGt9yyzPANtqEMyc/fmvrfhJ\n",
2295 "gD4ZBDOfONffe6CvBsHMF8784oqfBKA/Zgplkrwxk4UySbKvPV4lSR363L72+0NnfAbYascdvV8w\n",
2296 "AyydcAYQzgAszzzLl/ZN+DXucxnzPtAyawZYFUuaAMuaABZvpqZMrXVhs2iAS9KaAVZBawZINGcA\n",
2297 "Fkm4AhtCawZYFcOAgURzBmARhDKwYQQzwKoIZ4BEOAPQJaEMbCCtGWCVBDNAIpwB6MKsuy+llPKO\n",
2298 "JN+ptd65ff2STL4j0/fUWh826zNA35k1A6yKeTPAgJkzALObOZRJcnKSbw+9PnWGaxxIIpSBOQwa\n",
2299 "M8IZYBWEM8CAcAZgevOEMu9M8p2h138xwzWmbtYAo2nNAKsknAEGhDMAk5s5lKm13nbH6wfN/TTA\n",
2300 "XLRmgFU78dj9ghkgiXAGYBKdDfotpdyplHK3rq4HzM4QYGCV7NIEDDMQGGC8Lndfem2S2uH1gDnY\n",
2301 "oQlYNeEMMEwwA3BJXYYyh3Z4LaAjghlg1YQzwIDWDMDFdRnKnJPk8FLKZTu8JtABrRlgHQhngAHh\n",
2302 "DECjy1Dm9CT7ktyhw2sCHRLMAOtAMAMMCGeAvusylHlekm8leWKH1wQ6pjUDrAOtGWCYcAboq5m3\n",
2303 "xB7hbkn+McmPllJ+P8mHJvlQrfVFHT4DMKHjjt5v62xg5QbBjG20gcQ22kD/dBnK/MHQ9z834WcO\n",
2304 "JBHKwIoMGjPCGWDVhDPAMOEM0BddhjKfnuEzBzq8PzAjrRlgXZx47H7BDPA9whlg23UWytRar9XV\n",
2305 "tYDl05oB1oXWDLCTcAZYlFLK/iSPTPITSU5McsUkX0lym1rrvyz6/l02ZYAtoDUDrAvhDLCTcAbo\n",
2306 "UinlR5K8NsmVk7w7SU3y3STXTLO79MJ1FsqUUp6S5Lu11t+c4NwbJ7lHko/UWl/T1TMA3dCaAdaJ\n",
2307 "cAbYSTgDzKuUcmKSNyf5XJI71lon2qyoa11uif2UJP9rwnMvnPJ8YAVsnQ2sE1toAzvZShuYwwvS\n",
2308 "tGHutKpAJlnd8qX/vz0ev6L7AxOynAlYJ1ozwCiaM8A02pbM7ZO8PMk3Sik/m2bJ0nlJPpnk9bXW\n",
2309 "by/jWVYVylypPR6+ovsDU7CcCVg3whlgFOEMMKGT2+PNkpyd5DI73v9MKeUna60fXPSDdLl8aU+l\n",
2310 "lMNKKT+c5EXtj/5tmfcH5mM5E7BuTjx2v2VNwCVY1gTs4cT2eF6Shya5RpJLJ7l2kj9IcvUkbyil\n",
2311 "XHHRDzJzU6aUclGSAzt+fJlSyoUTfHwwxfgFs94fWA2tGWAdnXjsfq0Z4BI0Z4AxrtAen19rfeXQ\n",
2312 "z89K8j9KKddKcpck90vyh4t8kHmbMvuGvkb9bNzXfyV5Yq31hXPeH1gRrRlg3WjNAONozgA7nN8e\n",
2313 "jxjz/pva4/UW/SDzzJS5Y3s8kCZoeUua/bzvmvH7eV+Q5EtJ/rXWOkmjBlhjWjPAOjJvBhjnqJOu\n",
2314 "qjUDHVqLsPPCc2b51Gfb47XGvL+0US8zhzK11rcNvy6lvDPJd2qtfzP3UwEbxQ5NwDoSzgCjWNIE\n",
2315 "JHlne7xbkl8d8f4N2+NHFv0gnaU/tdbb1lrv1NX1gM1y3NH7LWkC1pJlTcAoljRBf9Va/yHJh5Nc\n",
2316 "r5Ty1OH3Sik3T/KgJP+Z5JWX/HS3lrYldinl+5OcV2s9f8+TgY2lNQOsK8OAgVE0Z6C3TknTmHly\n",
2317 "KeUeSd6X5Jgkd07y7ST3r7Uu/B8c5gplSikPTXJkkq/XWl8y4v3LJnlKkkck2Z/kwlLKW5M8vtb6\n",
2318 "sXnuDawvs2aAdWVJEzCOcAb6pdb60VLKjZL8Wpqdlh6Wph3ziiRPr7V+YhnPMc+W2Mcl+ZM0g35/\n",
2319 "acxpf5zkATvud5cktyml3LmtDAFbSmsGWFfCGWAc4Qz0R63102lKJCszz0yZu7fHc5P8wc43Sykn\n",
2320 "52Ag8/dJ7pvk3knemuRySf68bdIAW8ysGWCdmTcDjGPmDLAM84Qyt26PL621XjTi/Ye0x88luUut\n",
2321 "9S9rra9Ls2X2+5JcI8mpc9wf2CCCGWCdCWaAcYQzwCLNE8rcoD2+bcz7d2yPr6i1fmPww1rrhUl+\n",
2322 "u315zznuD2wYrRlgnWnNALsRzgCLME8oc7U082QusW93KeWo9v0kGTU3ZvCzG454D9hyghlgnQln\n",
2323 "gN0IZ4AuzRPKXC7JRbXW/xrx3g+2xwNJPjDi/c+3733fHPcHNpjWDLDuhDPAboQzQBfmCWW+meSQ\n",
2324 "UsqRI94bhDJfa6cZ73SpJPvmuDewJQQzwLoTzAC7Ec4A85gnlDk7TbBy/RHv3bI9fmzMZ6/RHu1D\n",
2325 "CWjNAGtPawbYi3AGmMU8oczb2+MvDv+wlHLlJHduX54x5rMnt8ez5rg/sGUEM8C6E84AexHOANO4\n",
2326 "1Byf/cM0gcz9SimfSvLSJEcn+Y0kRyS5KMnLx3y2tMcPzXF/YAsNgpmzP69IB6yvQTDziXP9vQoY\n",
2327 "bRDMfOHML674SYB1NnNTptb68SRPS7OE6Qlplir9TQ4uXXpBe87FlFJ+MMmPpxn0++ZZ7w9sN60Z\n",
2328 "YBNozQB70ZwBdjPP8qXUWn89ya8k+XqacGZfkm8neWaSx+w8v5RySJqGTZJ8Jckb57k/sN3MmgE2\n",
2329 "gSVNwCSEM8Aoc4UySVJrfU6aZUv/r717D7ftnu89/smFELWlxy1ISRwUcSkRdymaQ6nD0z75NZSG\n",
2330 "qJY0bke1p64RHupWlxYlWk2CED+n1CVVtIo2EXErTYqqJMQ1BCEhkcj5Y4xlr6y95lxzrTVvY4zX\n",
2331 "63n2M7PmbYydPTKy1nt/f2MenOTOSa5da31arfXydZ5+7TRR5tFJSq31ku1uH+g/YQboAnEGmIQw\n",
2332 "A6y2nWvK/Fyt9cdJPjnB885Pcvw0tgkMi2vNAF3hejPARlxvBlix7UkZgHkyNQN0hakZYCOWNAGi\n",
2333 "DNA5wgzQFZY0AZMQZ2C4RBmgk4QZoEvEGWAS4gwMjygDdJZPZwK6RpgBJiHMwHCIMkDnCTNAl5ia\n",
2334 "ASZhagaGQZQBekGYAbpGmAEmIc5Av4kyQG8IM0DXmJoBJiXMQD+JMkCvCDNAF4kzwCRMzUD/iDJA\n",
2335 "77gAMNBVwgwwCXEG+kOUAXpLmAG6yNQMMClhBrpPlAF6TZgBukqYASZhaga6TZQBek+YAbrK1Aww\n",
2336 "KXEGukmUAQZBmAG6TJgBJiXOQLeIMsBguAAw0GWmZoDNEGagG0QZYHCEGaDLxBlgUqZmYPmJMsAg\n",
2337 "CTNA1wkzwKTEGVheogwwWJYzAV1nagbYDGEGlo8oAwyeMAN0nTADTMrUDCwXUQYgpmaA7jM1A2yG\n",
2338 "OAPLQZQBWEWYAbpOmAE2Q5iBxRJlANYwNQN0nakZYDNMzcDiiDIAIwgzQNeJM8BmiDMwf6IMwBjC\n",
2339 "DNAHwgywGcIMzI8oA7ABy5mAPjA1A2yGqRmYD1EGYELCDNAHwgywGeIMzJYoA7AJpmaAPjA1A2yW\n",
2340 "OAOzIcoAbIEwA/SBOANsljAD0yXKAGyRqRmgL4QZYDNMzcD0iDIA2yTMAH1gagbYLHEGtk+UAZgC\n",
2341 "U5RzZI4AACAASURBVDNAXwgzwGYJM7B1ogzAFAkzQB+YmgE2y9QMbI0oAzBlwgzQF8IMsFniDGyO\n",
2342 "KAMwA5YzAX1hagbYCmEGJiPKAMyQMAP0hTgDbJapGdiYKAMwY6ZmgD4RZoDNEmdgNFEGYE6EGaAv\n",
2343 "TM0AWyHMwK5EGYA5MjUD9IkwA2yWqRm4MlEGYAGEGaAvTM0AWyHOQEOUAVgQUzNAn4gzwFYIMwyd\n",
2344 "KAOwYMIM0CfCDLBZpmYYMlEGYAkIM0CfmJoBtkKcYYhEGYAlYTkT0DfCDLAV4gxDIsoALBlhBugT\n",
2345 "UzPAVgkzDIEoA7CETM0AfSPMAFthaoa+E2UAlpgwA/SJqRlgq8QZ+kqUAVhypmaAvhFngK0SZugb\n",
2346 "UQagI4QZoG+EGWArTM3QJ6IMQIcIM0DfmJoBtkqcoQ9EGYCOsZwJ6CNhBtgqYYYuE2UAOkqYAfrG\n",
2347 "1AywVaZm6CpRBqDDTM0AfSTOAFslztA1ogxADwgzQB8JM8BWCTN0hSgD0BOmZoA+MjUDbJWpGbpA\n",
2348 "lAHoGWEG6CNhBtgqcYZlJsoA9JCpGaCPTM0A2yHOsIxEGYAeE2aAPhJmgO0QZlgmogxAzwkzQB+Z\n",
2349 "mgG2w9QMy0KUARgAy5mAvhJngO0QZ1g0UQZgQIQZoK+EGWA7hBkWRZQBGBhTM0BfmZoBtsPUDIsg\n",
2350 "ygAMlDAD9JUwA2yHOMM8iTIAA2ZqBugrUzPAdgkzzIMoA4AwA/SWOANsh6kZZk2UASCJqRmg34QZ\n",
2351 "YDvEGWZFlAHgSoQZoK9MzQDbJcwwbaIMALsQZoA+E2aA7TA1wzSJMgCsy3ImoM9MzQDbJc4wDaIM\n",
2352 "AGMJM0CfCTPAdokzbIcoA8CGTM0AfWZqBpgGYYatEGUAmJgwA/SZOANsl6kZNkuUAWBTTM0AfSfM\n",
2353 "ADAvogwAWyLMAH1magZgmEopbyil/KyU8sZ5bE+UAWDLhBmg74QZgOEopbwgyaPaL6+YxzZFGQC2\n",
2354 "xXImoO9MzQD0Xynl8Un+NMkp89yuKAPAVAgzQN+JMwD9VEopSV6R5NVJXjLPbYsyAEyNqRlgCIQZ\n",
2355 "gP4opdw7yRuTvKPW+oQku81z+6IMAFMnzAB9Z2oGoPtKKbdL8s4kpyV5+CL2QZQBYCZMzQBDIMwA\n",
2356 "dFMp5SZJ3pfk3CQPqbVeuoj9EGUAmClhBug7UzMA3VJK2ZHkH5NcmuQBtdYLF7Uvey5qwwAMx0qY\n",
2357 "OfubC/v/HcDM3WK/Hfniec5zwHAsQ5D+4blbetlNk9wiyXuSPKW5zu/P/VJ7e1Ap5aVJzqu1vmI7\n",
2358 "+ziOKAPA3Byw7w5hBui1lR9QxBmApXZFe/sbSR404jm3an99Js0nM82EKAPAXAkzwBCIMwDLq9b6\n",
2359 "7xlxOZdSyq8m+VCSN9Vaj5j1vrimDABz5yLAwFAsw2g/AJviI7EBGAZhBhgCFwIGYBRRBoCFMjUD\n",
2360 "DIUwA8BarikDwFJwrRlgCFxrBmC51Vr/JXMcYDEpA8DSMDUDDIUlTQAkogwAS0iYAYZCmAEYNlEG\n",
2361 "gKVkagYYClMzAMMlygCw1IQZYCiEGYDhEWUAWHrCDDAUpmYAhkWUAaATLGcChkSYARgGUQaAThFm\n",
2362 "gKEwNQPQf6IMAJ1jagYYEnEGoL9EGQA6S5gBhkSYAeifPRe9A4tSSnlYkscluUOSqyT5UpK3J3lp\n",
2363 "rfWiNc/9lySHbPCWV6u1XjqDXQVgjJUwc/Y3L1zwngDM3kqY+eJ5znkAfTC4KFNK2T3J8UkekeSb\n",
2364 "Sd6Z5MdJ7p3kmCSHlVLuWWv9wTov/+sk3x/x1pdPfWcBmNgB++4QZoDBuMV+O4QZgB4YXJRJ8ntp\n",
2365 "gsxpSe63MhVTStkjycuSPCHJC5Mctc5rX1hr/fK8dhSAzRFmgCExNQPQfUO8pszD29tjVy9TqrVe\n",
2366 "nuRPknwvyaNKKVdbxM4BsD0uAgwMjWvNAHTXEKPMDZJckeTstQ/UWi9J8rEkeyU5aJ3X7jbbXQNg\n",
2367 "WoQZYEh8QhNANw1x+dLXktw8ye2S/Nc6j1/Q3l5vncfOLKVcNclPknw1yQfSXBj4nBnsJwDb5CLA\n",
2368 "wNBY0gTQLUOMMsenuajva0opV0nyviQXJ7lhkvsmuUf7vL1WvebLSb6b5NtJLk1y/SS/luQPkzyi\n",
2369 "lHJorfUT89h5ADbPtWaAoXEhYIBuGFyUqbWeWEo5IMkzkpy05uHvpZmCWfnnldc8eu37lFL2SvKa\n",
2370 "JEcmeVWSu85khwGYClMzwNCYmgFYfkO8pkxqrcemWcL0uCTHJnlaksOS3DjJd9Jcc+bzG7zHJWkm\n",
2371 "ZX6S5OBSyt6z3GcApsO1ZoChcb0ZgOU1uEmZFbXWc5Mct/q+UsqNktw2yTnt4xu9xyWllIvTLHX6\n",
2372 "hTTLoABYcqZmgCGypAlg+QxyUmaMY9rb48Y+q1VK+aUk/yPJBbXWb89srwCYCVMzwNCYmgFYLoOd\n",
2373 "lFmtlLJnkqcneUySM5O8bNVjh6b5GO231lp/uur+qyV5XfvlG+a3twBMk4sAA0PkejMAy2GQUaaU\n",
2374 "clSS+yf5SpJ9ktwnyY2SfDLJg2qtl656+n5posvLSykfTfNR2NdJckiaT2w6NTsnbADoIMuZgKGy\n",
2375 "pAlgsQYZZZL8OM1HWl8lzYV9P51mUuZNtdYr1jz3/UmenybC3CHJr6f5WOz/TPLiJK+ptV42p/0G\n",
2376 "YIZMzQBDZGoGYHEGGWVqrccnOX7C5349ybNmuT8ALA9TM8BQiTMA8+dCvwCwDhcBBobKhYAB5keU\n",
2377 "AYARDth3hzgDDJJPaQKYD1EGADYgzABDJc4AzJYoAwATEGaAIRNmAGZDlAGACVnOBAyZqRmA6RNl\n",
2378 "AGCThBlgyMQZgOkRZQBgC0zNAEMnzABsnygDANsgzABDZmoGYHtEGQDYJlMzwNCJMwBbI8oAwJQI\n",
2379 "M8DQCTMAmyPKAMAUmZoBhs7UDMDkRBkAmAFhBhg6cQZgY6IMAMyIMAMgzgCMI8oAwAxZzgTQEGYA\n",
2380 "diXKAMAcCDMApmYA1hJlAGBOTM0ANMQZgIYoAwBzJswANIQZYOhEGQBYAFMzAA1TM8CQiTIAsEDC\n",
2381 "DEBDnAGGSJQBgAUzNQOwkzADDIkoAwBLQpgBaJiaAYZClAGAJSLMAOwkzgB9J8oAwJKxnAngyoQZ\n",
2382 "oK9EGQBYUsIMwE6mZoA+EmUAYImZmgG4MnEG6BNRBgA6QJgBuDJhBugDUQYAOsLUDMCVmZoBuk6U\n",
2383 "AYCOEWYArkycAbpKlAGADhJmAHYlzABdI8oAQEdZzgSwK1MzQJeIMgDQccIMwK6EGaALRBkA6AFh\n",
2384 "BmBXpmaAZSfKAEBPCDMA6xNmgGUlygBAj7jODMD6hBlgGYkyANBDwgzArixnApaNKAMAPSXMAKxP\n",
2385 "mAGWhSgDAD0mzACsT5gBloEoAwA9J8wArM9yJmDRRBkAGAAXAAYYTZgBFkWUAYABEWYA1ifMAIsg\n",
2386 "ygDAwAgzAOuznAmYN1EGAAZImAEYTZgB5kWUAYCBcp0ZgNGEGWAeRBkAGDhhBmB9ljMBsybKAADC\n",
2387 "DMAYwgwwK6IMAJBEmAEYx9QMMAuiDADwc64zAzCeMANMkygDAOxCmAEYTZgBpkWUAQDWJcwAjGY5\n",
2388 "EzANogwAMJIwAzCeMANshygDAIwlzACMJ8wAWyXKAAAbcgFggPEsZwK2QpQBACYmzACMJ8wAmyHK\n",
2389 "AACbIswAjCfMAJMSZQCATRNmAMaznAmYhCgDAGyJ68wAbEyYAcYRZQCAbRFmAMYzNQOMIsoAANsm\n",
2390 "zABsTJgB1hJlAICpEGYANibMAKvtuegdAAD6YyXMnP3NCxe8JwDLayXMfPE850pYlFLKw5M8IMmd\n",
2391 "ktw4zdDKV5O8L8kLaq3fmMd+mJQBAKbO1AzAxkzNwGKUUvZM8sYkJck3k/xtkhOS/DTJ0Uk+XUo5\n",
2392 "YB77YlIGAJiJA/bdYWIGYAO32G+HiRmYv58leUGSl9dav7tyZylltySvT/LoJMcmOWLWOyLKAAAz\n",
2393 "I8wAbMxyJpivWuvPkjxznfuvKKW8Kk2UOWge+2L5EgAwU5YyAUzGciZYCnu3t98d+6wpEWUAgJk7\n",
2394 "YN8d4gzABIQZWLjD29uPzGNjogwAMDfCDMDGbrHfDnEGFqCUcpckj0tyQZJXzmObogwAMFemZgAm\n",
2395 "I8zA/JRSbp3kPUmuSPLQWuv589iuKAMALIQwA7AxUzMwe6WUOyb5lyTXTHJ4rfWD89q2T18CABbG\n",
2396 "pzMBTMZHZ7OMluEvWD577vZeX0p5YJKTk/w0yQNqrR+awm5NzKQMALBQljMBTMbEDExXKeUJSd6V\n",
2397 "5DtJ7jnvIJOYlAEAloSpGYCNrYQZUzOwdaWUvZK8JsmRST6c5LBa61w+AnstUQYAWBorEzPiDMB4\n",
2398 "ljPBthyeJsj8KMm/J3laKWW95/1jrfUDs9wRUQYAWDqmZgA2JszAlu3W3l4jyRNHPOeKJBcmEWUA\n",
2399 "gOERZgA2ZjkTbF6t9YQkJyx6PxIX+gUAlpiLAANMxkWAoZtEGQBg6QkzABsTZqB7RBkAoBNMzQBs\n",
2400 "7Bb77RBnoENEGQCgU4QZgI0JM9ANogwA0DnCDMDGhBlYfqIMANBJljMBbMxyJlhuogwA0GnCDMDG\n",
2401 "hBlYTqIMANB5pmYANmZqBpaPKAMA9IYwA7AxYQaWhygDAPSKMAOwMWEGloMoAwD0juVMABuznAkW\n",
2402 "T5QBAHpLmAHYmDADiyPKAAC9ZmoGYGPCDCyGKAMADIIwAzCe5Uwwf6IMADAYwgzAxoQZmB9RBgAY\n",
2403 "FMuZADYmzMB8iDIAwCAJMwDjWc4EsyfKAACDZWoGYGPCDMyOKAMADJ4wAzCeqRmYDVEGACCmZgAm\n",
2404 "IczAdIkyAACrCDMA4wkzMD2iDADAGsIMwHiWM8F0iDIAAOuwnAlgY8IMbI8oAwAwhjADMJ4wA1sn\n",
2405 "ygAAbMDUDMB4ljPB1ogyAAATEmYAxhNmYHNEGQCATRBmAMYTZmByogwAwCZZzgQwnuVMMBlRBgBg\n",
2406 "i4QZgPGEGRhPlAEA2AZTMwDjmZqB0UQZAIApEGYAxhNmYFeiDADAlAgzAOMJM3BlogwAwBRZzgQw\n",
2407 "nuVMsJMoAwAwA8IMwHjCDIgyAAAzY2oGYDxhhqETZQAAZkyYARjNciaGTJQBAJgDYQZgPGGGIRJl\n",
2408 "AADmxHImgPGEGYZGlAEAmDNhBmA0y5kYElEGAGABTM0AjCfMMASiDADAAgkzAKOZmqHvRBkAgAUT\n",
2409 "ZgDGE2boK1EGAGAJWM4EMJ4wQx+JMgAAS0SYARjNcib6RpQBAFgypmYAxhNm6AtRBgBgSQkzAKMJ\n",
2410 "M/SBKAMAsMSEGYDRLGei60QZAIAlZzkTwHjCDF0lygAAdIQwAzCaMEMXiTIAAB1iagZgNMuZ6BpR\n",
2411 "BgCgg4QZgNGEGbpClAEA6ChhBmA0YYYuEGUAADrMciaA0SxnYtmJMgAAPSDMAIwmzLCsRBkAgJ4w\n",
2412 "NQMwmqkZlpEoAwDQM8IMwGjCDMtElAEA6CFhBmA0YYZlIcoAAPSU5UwAo1nOxDIQZQAAek6YARhN\n",
2413 "mGGRRBkAgAEwNQMwmjDDoogyAAADIswArM9yJhZBlAEAGBhhBmA0YYZ5EmUAAAbIciaA0YQZ5kWU\n",
2414 "AQAYMGEGYH2WMzEPogwAwMCZmgEYTZhhlkQZAACSmJoBGMXUDLMiygAA8HPCDMBowgzTJsoAAHAl\n",
2415 "ljMBjCbMME2iDAAA6xJmANZnORPTIsoAADCSqRmA0YQZtkuUAQBgQ8IMwPqEGbZDlAEAYCLCDMD6\n",
2416 "LGdiq0QZAAAmZjkTwGjCDJslygAAsGnCDMD6hBk2Q5QBAGBLTM0AwPaIMgAAbIswAwBbI8oAALBt\n",
2417 "wgwAbJ4oAwDAVFjOBACbI8oAADBVwgwATEaUAQBg6kzNAMDGRBkAAGZGmAGA0UQZAABmSpgBgPWJ\n",
2418 "MgAAzJzlTACwK1EGAIC5EWYAYKc9F70Di1JKeViSxyW5Q5KrJPlSkrcneWmt9aJ1nv+QJE9O8itJ\n",
2419 "9kpybpKTk7yo1vrjee03AEDXrYSZs7954YL3BIAhK6XcJsmzkxySZJ8k5yd5f5Ln1Fq/Oo99GNyk\n",
2420 "TCll91LKiUnenOTmSd6Z5MQkV01yTJKPlVKuteY1T0ryjiS3T/KuJH+T5Kdp/vDeX0q5yvx+BwAA\n",
2421 "/WBqBoBFKaXcLcnHkzwkyceSvC7JfyY5MskZpZT957EfQ5yU+b0kj0hyWpL7rUzFlFL2SPKyJE9I\n",
2422 "8sIkR7X336j9+vwkd1qpZaWU3ZK8JclvJ3lsklfN97cBANB9B+y7w8QMAIvwujTDGQ+utZ6ycmcp\n",
2423 "5egkf5nkpUkOm/VODG5SJsnD29tjVy9TqrVenuRPknwvyaNKKXu1Dx2eZrnSa1ePL9Var0jy9PbL\n",
2424 "I2e+1wAAPeUiwADMUynljkluk+TfVgeZJKm1vjrJeUkeXEr5xVnvyxCjzA2SXJHk7LUP1FovSTO2\n",
2425 "tFeSg9q779benrbO87+c5NtJbl9KudpM9hYAYCCEGQDmZOTP+a1T06wsususd2SIUeZrSXZLcrsR\n",
2426 "j1/Q3l6/vb1pe/vtDd7vgKnsHQDAgJmaAWAOJvk5P5nDz/lDvKbM8UnuneQ17QV635fk4iQ3THLf\n",
2427 "JPdon7eyfOmaaSZrRi12vjhNlPHdAwDAlLjWDAAzdM32dtzP+ckcfs4fXJSptZ5YSjkgyTOSnLTm\n",
2428 "4e8l+cmqf17tshFvudt29uezp//rdl4OAAAAC9GDn2dn8nP+Zgxx+VJqrcem+TjsxyU5NsnT0lxV\n",
2429 "+cZJvpNmMubz7dN/mOYP5Ooj3m7vVc8DAAAAltvKz+8L/zl/cJMyK2qt5yY5bvV97cdf3zbJOe3j\n",
2430 "SXNB4DskuUmazyxf60ZJfpZ1Lhw8zqGHHjq38gYAAADT0oOfZ1d+fr/JiMdv1N5+edY7MshJmTGO\n",
2431 "aW9Xx5pT29v7rn1yKeXmSa6b5D9qrT+e8b4BAAAA2zfu5/zdk9w9yeVJzpj1jogySUope5ZSnp3k\n",
2432 "MUnOTPKyVQ+/LcmlSR7ZTtKsvGb3JM9rvzxhXvsKAAAAbF2t9VNJzkpyUCnlfmsePirNpMwptdbv\n",
2433 "znpfuj5ytCWllKOS3D/JV5Lsk+Q+af6lfzLJg2qt31rz/D9K8pI0H5f9niQ/SnKvNEudTk/yq7XW\n",
2434 "S+f2GwAAAAC2rJRyjyQfTDOs8t4k5yX55SSHprnW7N1rrf896/3YY9YbWEYHHnjgbZIcneTOSa6f\n",
2435 "5DNJnp/kCbXWH619/llnnXXagQce+Nk0n2V+7yQHp/norL9K8ge11p+sfQ0AAACwnM4666yvHnjg\n",
2436 "ge9J0wTuleSeaS7w+/+SPLzWes4Cdw8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABW7Lbo\n",
2437 "HeiSUsptkjw7ySFJ9klyfpL3J3lOrfWrW3zP6yb5QpIza6332uC590zytCR3SfILSb6W5F1Jnldr\n",
2438 "vWAr22exFnlMlVKOT3LEBm93y1rrF7eyHyzGtI6pUsqDkvxmkjsnOSDJVZJ8I8mHkryg1vpfI17n\n",
2439 "PNUzizymnKf6Z4rH092TPCzJ3ZPcLMneSS5M8ukkb0xyYq31inVe5xzVM4s8ppyj+mkW35+veu9j\n",
2440 "khyT5N9GfZ/uPDU8ey56B7qilHK3JP+UZI8k/5Dk3CS3SnJkkt8opdy11nrOhO+1T5LnJblekkPT\n",
2441 "/Me+yzcOa17zW0lqkp8keXeSbyU5OMmTkjyg3f73N/87Y1EWfUyt8rYkXxnxmBN/h0zzmEry2iT7\n",
2442 "JvlEkjcl+Vmac84jkxxWSrlvrfWMNdt3nuqZRR9TqzhP9cCUj6cXJ7lbktOTnJzkoiS/lOR+Se6b\n",
2443 "5D5JHrVm+85RPbPoY2oV56iemPIxtfa9/yBNkElGfJ/uPDVMoszkXpfkqkkeXGs9ZeXOUsrRSf4y\n",
2444 "yUuTHDbhe+2T5OhM+ENzKWXvJH+V5JIk96y1fnrVYy9O8tQkz2xv6Y6FHVNrHFdr/ectvI7lM81j\n",
2445 "6rgkf7v2b4RKKc9O8pwkf57mb5BW7nee6qeFHVNrX+s81QvTPJ5eluT0WuvXVt9ZSrllkjOTHFFK\n",
2446 "eVKt9Qft/c5R/bSwY2oN56j+mOYx9XOllIckeXWSU5I8cMRznKcGavdF70AXlFLumOQ2acbMTln9\n",
2447 "WK311UnOS/LgUsovTvJ+tdZzaq2711r3SHLTCV7y60mu27x053+crWPTlNTfLaX48+yIJTim6JkZ\n",
2448 "HFPPHTGi+xft7UFr7nee6pklOKbokRkcT3+39ofn1tlpzjcXJfnRqvudo3pmCY4pembax9Sq971H\n",
2449 "krcmeW+SJ455qvPUQPkDnczd2tvTRjx+apqpo7ts4b0nua7PyO3XWi9K8tk0/wHfYgvbZzEWfUxt\n",
2450 "5/ksp1keU6vt3d5+d9LtO0911qKPqdWcp7pvpsdTKWVHe02Qv0/z/e1RtdbLJ9m+c1RnLfqYWs05\n",
2451 "qh+mfkyVUm6dZhnSJ5Mcnmbp7qa37zzVb5YvTWZl8uDbIx5fqeoHLMH2Pz+jfWC6Fn1MrfbeUspV\n",
2452 "04xKfiPJR5L8ea31c3PYNtMzr2Pq8Pb2I9vYvvNUNyz6mFrNear7ZnY8lVI+k+R27ZfvT3K7dS4c\n",
2453 "7RzVP4s+plZzjuqHqR5TpZT9krwvzTHxoFrrJaWUaW3feapHTMpM5prt7YUjHr+4vd3R0+0zfcvw\n",
2454 "Z/q1NOX+hCSvTPJ3aS5qdkSSM9pPSqE7Zn5MlVJumuRZab7p/LN5b5+5W/QxlThP9cksj6fjk7wm\n",
2455 "yT+nudj9ye3fTs9r+yzGoo+pxDmqb6Z2TLVLnN6XZorq/hNenNd5aqBMymzOZSPun9fI4qK3z/Qt\n",
2456 "7M+01vqMtfe1a1SPSfND0mtLKTeutY4bs2T5zOSYKqXcMMk/JrlWkkfXWs+c5/ZZqIUdU85TvTT1\n",
2457 "46nW+oqVfy6lHJzkX5O8s5Ryu1rrT2a9fRZuYceUc1RvbeuYao+BdyW5YZJDaq3nzXP7dI9Jmcn8\n",
2458 "sL29+ojH917zvL5tn+lbyj/TWuvPaq3HJDknyQ3SfAQg3TCzY6qUsn+SD6cZl31CrfWEeW6fhVn0\n",
2459 "MbUu56nOmss5ov1Y9Q8luVmu/GlezlH9s+hjatTznaO6a1rH1I4k90jyuSSPKqW8dOVXkqe3zzmg\n",
2460 "ve+5M9g+HWNSZjJnt7c3GfH4jdrbL/d0+0zfsv+ZXpBk/yTXWND22byZHFPt3xC+O800w+/WWt8y\n",
2461 "z+2zUIs+pjbiPNUt8zxHXNDerv6EFOeo/ln0MTXJa/aPc1SXTPuYumeSe415r6ck+X6SZ89o+3SE\n",
2462 "KDOZU9vb+659oB1Pu3uSy5OcMcPtP6Xd/mvXbH9HmguRXZDkizPaPtO36GNqpFLK3kl+Oc3o5LiL\n",
2463 "2rFcpn5MlVIOS7NO/sdJ7ldr/egG23ee6pdFH1Pj3sd5qnvm8v+9Uspu2XmB1rNXPeQc1T+LPqbG\n",
2464 "vcY5qpumcky1149Zd0VKKeUmaY6jf621rp28cp4aKMuXJlBr/VSSs5IcVEq535qHj0pTLU+ptf78\n",
2465 "4zxLKSeWUj5fSnnBFHbhfUm+k+RBpZTbrnnsWUn2SvJm61W7Y9HHVCnl9qWUJ7TfNKy+f/c0F6q7\n",
2466 "RpJ31Fq/t91tMR/TPqZKKc9L8rYkX0py8AQ/PDtP9cyijynnqX6Z5vFUSrlVKeVlpZR919nUM5Pc\n",
2467 "OsmZtdaPr7rfOapnFn1MOUf1z5y+Px93XRjnqYEyKTO5xyb5YJJ3l1Lem+S8NAX80CTnp6maq904\n",
2468 "zWfI73JyL6Vcs32/ZOcY5H6llKe2//yVWuvbVp5fa724lHJ0krcmObWU8u4k301yxzSfZ/+lJMdu\n",
2469 "+3fIvC3smGqf88okzy+lfDRNsd+R5nj6n0m+kOTx2/rdsQhTOaZKKYckeUaav+E7NcnRIz7C8eMr\n",
2470 "x5XzVG8t7JiK81QfTev/e3sleXKSx5dSTk9yZpKrJrlrklu27/U7q1/gHNVbCzum4hzVV1P7/nyz\n",
2471 "nKeGy6TMhGqt/5bmxPyuNBduemyaan58mr/x++81L7mi/bWeayd5cfvrae3zbrLqvsets/2aZpTt\n",
2472 "o0nun+T30/zH/8okd621XrD2NSy3BR9Tn07zA9Lpab7ZeFSS30pyUZpPDLhTrfX8Lf/mWIgpHlMr\n",
2473 "f4uzR/seT1nn1/9J8utrtu881TMLPqacp3pmisfT59OcX96Z5kKqj0zy0DTf174iye1rrZ9bZ/vO\n",
2474 "UT2z4GPKOaqHpvz9+Va27zwFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n",
2475 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAwCbttugdAACWUynlnCQ3TnJUrfV1C96dJEkp5fgkRyQ5udb6\n",
2476 "sAXvDgDAtuy56B0AAEYrpTwvyTOS/CDJ9Wutl07wmicneVmS85PcoNb6s23uxhXbfP3PlVLelOR3\n",
2477 "kpxQaz1yxHMeleQN7Zf711q/stE+lVL2T/Ll9ssja60nrHrsMUmOS3JurfWAbf0GAACmaPdF7wAA\n",
2478 "MNab29sdSR4w4WtWJkhOnkKQmZVxoeeyJJck+ckGz1v7fiuvuWwL2wQAmDuTMgCwxGqtny+lfCbJ\n",
2479 "ryR5aJK/H/f8UspNkxycJkC8edxzl1Wt9U1J3rTJ15yb5Oqz2SMAgNkwKQMAy++k9vZBpZS9N3ju\n",
2480 "Q9vbs2utp89wn7bLde0AgMEzKQMAy+8tSV6c5BpJHpzkrWOeuxJlTlp9Zynl4CRPSXJIkusk+X6S\n",
2481 "U5O8qtb6T5vdoVLKs5LcJckBSfZNs7zqh0n+M8m72ve9aNXz98/Oa74kySNLKY9c87b711q/Uko5\n",
2482 "NMn7k6TWOtFfIJVS9kyycr2d+9RaP9zef06aixUnyf6llLXLuY5MclGSt7Wvv2Gt9YIR2/i1JB9I\n",
2483 "s0TqBrXWH0yybwAAo5iUAYAlV2v9WpIPt18+dNTzSim3TnKbNEuXTlp1/5OTnJ7k8DQBZfc0YeYh\n",
2484 "ST5QSnnRFnbrGUkemORWSfZpt3mtJHdL8mdJziilXHvV81eu+bISRX6WJm6s/rX2mi9buQbM+V5h\n",
2485 "AAAABa1JREFUFWtet/YaM2u3eVmaJWHnJ7lqmk92GuX329uTBRkAYBpEGQDohpXIcv9SyrVGPGfl\n",
2486 "Ar+fqbV+PklKKQ9M80lMVyR5VZpplKskuWGS57b3/3H7CUWbcUaSZya5Y5Kr1VqvmuS6SR6TZmLm\n",
2487 "lkmetfLkWuu5tdarp5n6SZITa617r/n11U3uw4ZqrbdMclT75TnrbPPNtdafJln5tKbfW+99SinX\n",
2488 "SfKbaf59LcXHgwMA3Wf5EgB0w9uTvDrJXkl+K8nfrvOcw9vb1UuXXtzeHldrfeLKnbXWbyV5Tinl\n",
2489 "sjRx5vmllBMn+cjt9vX3Wue+C5K8oZ2QeVGS/53kyWuetohryUyyzb9O8tQkty6l3LXW+rE1jx+R\n",
2490 "5CpJPrfOYwAAW2JSBgA6oNb6vSSntF/usoSplHJQkpulWRb0lva+2ya5dZrpjheOeOuXJ/lxmuVM\n",
2491 "/2tKu/vp9vZGU3q/mau1fjHJR9IEnPWmhlbuMyUDAEyNSRkA6I6T0lzo9z6llOvWWs9f9djK0qWP\n",
2492 "1Fq/3v7zwe3t19uPjN5FrfWiUsqnk9w9yUFJ3jvpzpRSbp7kt5PcOclN01zs95rtr6SZLOmS16e5\n",
2493 "EPJvl1KetHKh4lLKPdMsx7ooyRsXuH8AQM+IMgDQHe9K8qMkv5CkJHlNkpRSdksTR5IrL126Xnv7\n",
2494 "rQ3e9xvt7fUn2YlSyh5JXpHkD3PlpUErF9i9LMkek7zXknl7kr9I8otpppH+pr1/9QV+f7iIHQMA\n",
2495 "+snyJQDoiFrrT5K8s/1y9RKmeyTZL82nG9U57MpzkxydJsh8KMkjk9w+yXVqrXskud8c9mHqaq2X\n",
2496 "ZOckzGOSpJSyT5oA5gK/AMDUmZQBgG45Kckjkty9lLJfrfW87Fy69L41H9W8MiFzww3e8wbt7bc3\n",
2497 "2ng7lXN0++Vra61/uM7TFnEx32l5fZInJrlzKeXAJPdOcrU0n2h1xiJ3DADoH5MyANAtH0hyfpr/\n",
2498 "hx9eStk9yWHtY29e89xPtLfXb6//sotSyjXTfKz16uePc9001465IjuX92zGZe3tXlt47VZNvM1a\n",
2499 "65lJPpadF/xdWbpkSgYAmDpRBgA6pNZ6eZK3tV8+LMmhaULJhUnevea5n0tyVprA8KwRb/lHaSZB\n",
2500 "zk8TfDZyyap/vt6I59xhzOvPn+A507ayzeuXUia5bs7r29vHJrldmuv4rA1eAADbZvkSAHTPSWmW\n",
2501 "EN0xydPb+97RXhNlrf+bJtY8opTy4yQvqLWeW0rZN82Fep+ZZurlWbXWSzfacK31B6WU05PcJclL\n",
2502 "SinfTfMR2Hu09z0t468pc1p7e8tSyuOTnJjmU5rulOTUGV1I94wkl7f7+KJSyp+m+SSlWyX5Qa31\n",
2503 "C2uef3Kajwrf0X79llrrj2awXwDAwJmUAYCOqbWeluSc9stD2tuTRjz3vUn+OE14+f0kZ5dSLk/y\n",
2504 "9ewMMi+vtR63iV14YpKLk9w6zVKfS9qvP5TkPklOGfPadyf5j/af/yLJ99NMsvxDmk892q5drmdT\n",
2505 "a/12di61OiLN7/0H7b7fZZ3nX5yd/z5d4BcAmBlRBgC6aXU0+FaSD456Yq31z5PcLc2yp68n+Wma\n",
2506 "i/q+K8n9aq1PHfHSK7LzY65Xv98Z7fu9O82yqZ8mOTvJXyW5bZKXjNmXnya5b5pI8o32td9K8k9J\n",
2507 "VqZkdtnmRvu05vH1HJ1mCdeXklya5HtJPp7kyyOev3L/p2qtnxqzPQCALevypyMAAExd+wlTX0hy\n",
2508 "syR/UGv96wXvEgDQUyZlAACu7P5pgsyFGbEsDABgGkQZAIArO7q9Pam9vgwAwEyIMgAArVLKAUke\n",
2509 "GBf4BQDmQJQBANjpqDTX3PtErfXfF70zAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n",
2510 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAo/1/MrL3Cbz9qMUAAAAASUVORK5CYII=\n"
2511 ],
2512 "text/plain": [
2513 "<matplotlib.figure.Figure at 0x110dd2c50>"
2514 ]
2515 },
2516 "metadata": {
2517 "image/png": {
2518 "height": 407,
2519 "width": 562
2520 }
2521 },
2522 "output_type": "display_data"
2523 }
2524 ],
2525 "source": [
2526 "plt.figure()\n",
2527 "plt.contourf(sigma_vals, strike_vals, prices['aput'])\n",
2528 "plt.axis('tight')\n",
2529 "plt.colorbar()\n",
2530 "plt.title(\"Asian Put\")\n",
2531 "plt.xlabel(\"Volatility\")\n",
2532 "plt.ylabel(\"Strike Price\")"
2533 ]
2534 }
2535 ],
2536 "metadata": {
2537 "kernelspec": {
2538 "display_name": "Python 3",
2539 "language": "python",
2540 "name": "python3"
2541 },
2542 "language_info": {
2543 "codemirror_mode": {
2544 "name": "ipython",
2545 "version": 3
2546 },
2547 "file_extension": ".py",
2548 "mimetype": "text/x-python",
2549 "name": "python",
2550 "nbconvert_exporter": "python",
2551 "pygments_lexer": "ipython3",
2552 "version": "3.4.2"
2553 }
2554 },
2555 "nbformat": 4,
2556 "nbformat_minor": 0
2557 }
@@ -1,131 +0,0 b''
1 {
2 "cells": [
3 {
4 "cell_type": "markdown",
5 "metadata": {},
6 "source": [
7 "# Load balanced map and parallel function decorator"
8 ]
9 },
10 {
11 "cell_type": "code",
12 "execution_count": 1,
13 "metadata": {
14 "collapsed": true
15 },
16 "outputs": [],
17 "source": [
18 "from __future__ import print_function\n",
19 "from IPython.parallel import Client"
20 ]
21 },
22 {
23 "cell_type": "code",
24 "execution_count": 2,
25 "metadata": {
26 "collapsed": false
27 },
28 "outputs": [],
29 "source": [
30 "rc = Client()\n",
31 "v = rc.load_balanced_view()"
32 ]
33 },
34 {
35 "cell_type": "code",
36 "execution_count": 3,
37 "metadata": {
38 "collapsed": false
39 },
40 "outputs": [
41 {
42 "name": "stdout",
43 "output_type": "stream",
44 "text": [
45 "Simple, default map: [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]\n"
46 ]
47 }
48 ],
49 "source": [
50 "result = v.map(lambda x: 2*x, range(10))\n",
51 "print(\"Simple, default map: \", list(result))"
52 ]
53 },
54 {
55 "cell_type": "code",
56 "execution_count": 4,
57 "metadata": {
58 "collapsed": false
59 },
60 "outputs": [
61 {
62 "name": "stdout",
63 "output_type": "stream",
64 "text": [
65 "Submitted tasks, got ids: ['b4d86123-967a-4f21-b9c5-8a77a69a3ced', '97401998-891d-4729-8288-ae1b97c28235', '1586cf7e-32c7-4864-bd0e-6aab16e07a3f', 'f6770223-59c3-4344-a69a-5d555d1dfb7f', '0ebb71da-6e16-44ac-917a-be978fd3787d', '582443c5-937e-45b0-9f13-5607c53cba60', '8d453d87-d70d-4fbd-a2c4-994ab3a8b6c7', '0a680757-1a59-4826-969c-523a45f3d76f', '63a3e983-a205-4f07-b494-ec86b2cdd004', '79b34cbb-aa93-4d11-8746-28969dbdd3ec']\n",
66 "Using a mapper: [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]\n"
67 ]
68 }
69 ],
70 "source": [
71 "ar = v.map_async(lambda x: 2*x, range(10))\n",
72 "print(\"Submitted tasks, got ids: \", ar.msg_ids)\n",
73 "result = ar.get()\n",
74 "print(\"Using a mapper: \", result)"
75 ]
76 },
77 {
78 "cell_type": "code",
79 "execution_count": 5,
80 "metadata": {
81 "collapsed": false
82 },
83 "outputs": [
84 {
85 "name": "stdout",
86 "output_type": "stream",
87 "text": [
88 "Using a parallel function: [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]\n"
89 ]
90 }
91 ],
92 "source": [
93 "@v.parallel(block=True)\n",
94 "def f(x): return 2*x\n",
95 "\n",
96 "result = f.map(range(10))\n",
97 "print(\"Using a parallel function: \", result)"
98 ]
99 },
100 {
101 "cell_type": "code",
102 "execution_count": null,
103 "metadata": {
104 "collapsed": false
105 },
106 "outputs": [],
107 "source": []
108 }
109 ],
110 "metadata": {
111 "kernelspec": {
112 "display_name": "Python 3",
113 "language": "python",
114 "name": "python3"
115 },
116 "language_info": {
117 "codemirror_mode": {
118 "name": "ipython",
119 "version": 3
120 },
121 "file_extension": ".py",
122 "mimetype": "text/x-python",
123 "name": "python",
124 "nbconvert_exporter": "python",
125 "pygments_lexer": "ipython3",
126 "version": "3.4.2"
127 }
128 },
129 "nbformat": 4,
130 "nbformat_minor": 0
131 }
This diff has been collapsed as it changes many lines, (521 lines changed) Show them Hide them
@@ -1,521 +0,0 b''
1 {
2 "cells": [
3 {
4 "cell_type": "markdown",
5 "metadata": {},
6 "source": [
7 "# Using Parallel Magics"
8 ]
9 },
10 {
11 "cell_type": "markdown",
12 "metadata": {},
13 "source": [
14 "IPython has a few magics for working with your engines.\n",
15 "\n",
16 "This assumes you have started an IPython cluster, either with the notebook interface,\n",
17 "or the `ipcluster/controller/engine` commands."
18 ]
19 },
20 {
21 "cell_type": "code",
22 "execution_count": null,
23 "metadata": {
24 "collapsed": false
25 },
26 "outputs": [],
27 "source": [
28 "from IPython import parallel\n",
29 "rc = parallel.Client()\n",
30 "dv = rc[:]\n",
31 "rc.ids"
32 ]
33 },
34 {
35 "cell_type": "markdown",
36 "metadata": {},
37 "source": [
38 "Creating a Client registers the parallel magics `%px`, `%%px`, `%pxresult`, `pxconfig`, and `%autopx`. \n",
39 "These magics are initially associated with a DirectView always associated with all currently registered engines."
40 ]
41 },
42 {
43 "cell_type": "markdown",
44 "metadata": {},
45 "source": [
46 "Now we can execute code remotely with `%px`:"
47 ]
48 },
49 {
50 "cell_type": "code",
51 "execution_count": null,
52 "metadata": {
53 "collapsed": false
54 },
55 "outputs": [],
56 "source": [
57 "%px a=5"
58 ]
59 },
60 {
61 "cell_type": "code",
62 "execution_count": null,
63 "metadata": {
64 "collapsed": false
65 },
66 "outputs": [],
67 "source": [
68 "%px print(a)"
69 ]
70 },
71 {
72 "cell_type": "code",
73 "execution_count": null,
74 "metadata": {
75 "collapsed": false
76 },
77 "outputs": [],
78 "source": [
79 "%px a"
80 ]
81 },
82 {
83 "cell_type": "code",
84 "execution_count": null,
85 "metadata": {
86 "collapsed": false
87 },
88 "outputs": [],
89 "source": [
90 "with dv.sync_imports():\n",
91 " import sys"
92 ]
93 },
94 {
95 "cell_type": "code",
96 "execution_count": null,
97 "metadata": {
98 "collapsed": false
99 },
100 "outputs": [],
101 "source": [
102 "%px from __future__ import print_function\n",
103 "%px print(\"ERROR\", file=sys.stderr)"
104 ]
105 },
106 {
107 "cell_type": "markdown",
108 "metadata": {},
109 "source": [
110 "You don't have to wait for results. The `%pxconfig` magic lets you change the default blocking/targets for the `%px` magics:"
111 ]
112 },
113 {
114 "cell_type": "code",
115 "execution_count": null,
116 "metadata": {
117 "collapsed": false
118 },
119 "outputs": [],
120 "source": [
121 "%pxconfig --noblock"
122 ]
123 },
124 {
125 "cell_type": "code",
126 "execution_count": null,
127 "metadata": {
128 "collapsed": false
129 },
130 "outputs": [],
131 "source": [
132 "%px import time\n",
133 "%px time.sleep(5)\n",
134 "%px time.time()"
135 ]
136 },
137 {
138 "cell_type": "markdown",
139 "metadata": {},
140 "source": [
141 "But you will notice that this didn't output the result of the last command.\n",
142 "For this, we have `%pxresult`, which displays the output of the latest request:"
143 ]
144 },
145 {
146 "cell_type": "code",
147 "execution_count": null,
148 "metadata": {
149 "collapsed": false
150 },
151 "outputs": [],
152 "source": [
153 "%pxresult"
154 ]
155 },
156 {
157 "cell_type": "markdown",
158 "metadata": {},
159 "source": [
160 "Remember, an IPython engine is IPython, so you can do magics remotely as well!"
161 ]
162 },
163 {
164 "cell_type": "code",
165 "execution_count": null,
166 "metadata": {
167 "collapsed": false
168 },
169 "outputs": [],
170 "source": [
171 "%pxconfig --block\n",
172 "%px %matplotlib inline"
173 ]
174 },
175 {
176 "cell_type": "code",
177 "execution_count": null,
178 "metadata": {
179 "collapsed": false
180 },
181 "outputs": [],
182 "source": [
183 "%%px\n",
184 "import numpy as np\n",
185 "import matplotlib.pyplot as plt"
186 ]
187 },
188 {
189 "cell_type": "markdown",
190 "metadata": {},
191 "source": [
192 "`%%px` can also be used as a cell magic, for submitting whole blocks.\n",
193 "This one acceps `--block` and `--noblock` flags to specify\n",
194 "the blocking behavior, though the default is unchanged.\n"
195 ]
196 },
197 {
198 "cell_type": "code",
199 "execution_count": null,
200 "metadata": {
201 "collapsed": false
202 },
203 "outputs": [],
204 "source": [
205 "dv.scatter('id', dv.targets, flatten=True)\n",
206 "dv['stride'] = len(dv)"
207 ]
208 },
209 {
210 "cell_type": "code",
211 "execution_count": null,
212 "metadata": {
213 "collapsed": false
214 },
215 "outputs": [],
216 "source": [
217 "%%px --noblock\n",
218 "x = np.linspace(0,np.pi,1000)\n",
219 "for n in range(id,12, stride):\n",
220 " print(n)\n",
221 " plt.plot(x,np.sin(n*x))\n",
222 "plt.title(\"Plot %i\" % id)"
223 ]
224 },
225 {
226 "cell_type": "code",
227 "execution_count": null,
228 "metadata": {
229 "collapsed": false
230 },
231 "outputs": [],
232 "source": [
233 "%pxresult"
234 ]
235 },
236 {
237 "cell_type": "markdown",
238 "metadata": {},
239 "source": [
240 "It also lets you choose some amount of the grouping of the outputs with `--group-outputs`:\n",
241 "\n",
242 "The choices are:\n",
243 "\n",
244 "* `engine` - all of an engine's output is collected together\n",
245 "* `type` - where stdout of each engine is grouped, etc. (the default)\n",
246 "* `order` - same as `type`, but individual displaypub outputs are interleaved.\n",
247 " That is, it will output the first plot from each engine, then the second from each,\n",
248 " etc."
249 ]
250 },
251 {
252 "cell_type": "code",
253 "execution_count": null,
254 "metadata": {
255 "collapsed": false
256 },
257 "outputs": [],
258 "source": [
259 "%%px --group-outputs=engine\n",
260 "x = np.linspace(0,np.pi,1000)\n",
261 "for n in range(id+1,12, stride):\n",
262 " print(n)\n",
263 " plt.figure()\n",
264 " plt.plot(x,np.sin(n*x))\n",
265 " plt.title(\"Plot %i\" % n)"
266 ]
267 },
268 {
269 "cell_type": "markdown",
270 "metadata": {},
271 "source": [
272 "When you specify 'order', then individual display outputs (e.g. plots) will be interleaved.\n",
273 "\n",
274 "`%pxresult` takes the same output-ordering arguments as `%%px`, \n",
275 "so you can view the previous result in a variety of different ways with a few sequential calls to `%pxresult`:"
276 ]
277 },
278 {
279 "cell_type": "code",
280 "execution_count": null,
281 "metadata": {
282 "collapsed": false
283 },
284 "outputs": [],
285 "source": [
286 "%pxresult --group-outputs=order"
287 ]
288 },
289 {
290 "cell_type": "markdown",
291 "metadata": {},
292 "source": [
293 "## Single-engine views"
294 ]
295 },
296 {
297 "cell_type": "markdown",
298 "metadata": {},
299 "source": [
300 "When a DirectView has a single target, the output is a bit simpler (no prefixes on stdout/err, etc.):"
301 ]
302 },
303 {
304 "cell_type": "code",
305 "execution_count": null,
306 "metadata": {
307 "collapsed": false
308 },
309 "outputs": [],
310 "source": [
311 "from __future__ import print_function\n",
312 "\n",
313 "def generate_output():\n",
314 " \"\"\"function for testing output\n",
315 " \n",
316 " publishes two outputs of each type, and returns something\n",
317 " \"\"\"\n",
318 " \n",
319 " import sys,os\n",
320 " from IPython.display import display, HTML, Math\n",
321 " \n",
322 " print(\"stdout\")\n",
323 " print(\"stderr\", file=sys.stderr)\n",
324 " \n",
325 " display(HTML(\"<b>HTML</b>\"))\n",
326 " \n",
327 " print(\"stdout2\")\n",
328 " print(\"stderr2\", file=sys.stderr)\n",
329 " \n",
330 " display(Math(r\"\\alpha=\\beta\"))\n",
331 " \n",
332 " return os.getpid()\n",
333 "\n",
334 "dv['generate_output'] = generate_output"
335 ]
336 },
337 {
338 "cell_type": "markdown",
339 "metadata": {},
340 "source": [
341 "You can also have more than one set of parallel magics registered at a time.\n",
342 "\n",
343 "The `View.activate()` method takes a suffix argument, which is added to `'px'`."
344 ]
345 },
346 {
347 "cell_type": "code",
348 "execution_count": null,
349 "metadata": {
350 "collapsed": false
351 },
352 "outputs": [],
353 "source": [
354 "e0 = rc[-1]\n",
355 "e0.block = True\n",
356 "e0.activate('0')"
357 ]
358 },
359 {
360 "cell_type": "code",
361 "execution_count": null,
362 "metadata": {
363 "collapsed": false
364 },
365 "outputs": [],
366 "source": [
367 "%px0 generate_output()"
368 ]
369 },
370 {
371 "cell_type": "code",
372 "execution_count": null,
373 "metadata": {
374 "collapsed": false
375 },
376 "outputs": [],
377 "source": [
378 "%px generate_output()"
379 ]
380 },
381 {
382 "cell_type": "markdown",
383 "metadata": {},
384 "source": [
385 "As mentioned above, we can redisplay those same results with various grouping:"
386 ]
387 },
388 {
389 "cell_type": "code",
390 "execution_count": null,
391 "metadata": {
392 "collapsed": false
393 },
394 "outputs": [],
395 "source": [
396 "%pxresult --group-outputs order"
397 ]
398 },
399 {
400 "cell_type": "code",
401 "execution_count": null,
402 "metadata": {
403 "collapsed": false
404 },
405 "outputs": [],
406 "source": [
407 "%pxresult --group-outputs engine"
408 ]
409 },
410 {
411 "cell_type": "markdown",
412 "metadata": {},
413 "source": [
414 "## Parallel Exceptions"
415 ]
416 },
417 {
418 "cell_type": "markdown",
419 "metadata": {},
420 "source": [
421 "When you raise exceptions with the parallel exception,\n",
422 "the CompositeError raised locally will display your remote traceback."
423 ]
424 },
425 {
426 "cell_type": "code",
427 "execution_count": null,
428 "metadata": {
429 "collapsed": false
430 },
431 "outputs": [],
432 "source": [
433 "%%px\n",
434 "from numpy.random import random\n",
435 "A = random((100,100,'invalid shape'))"
436 ]
437 },
438 {
439 "cell_type": "markdown",
440 "metadata": {},
441 "source": [
442 "## Remote Cell Magics"
443 ]
444 },
445 {
446 "cell_type": "markdown",
447 "metadata": {},
448 "source": [
449 "Remember, Engines are IPython too, so the cell that is run remotely by %%px can in turn use a cell magic."
450 ]
451 },
452 {
453 "cell_type": "code",
454 "execution_count": null,
455 "metadata": {
456 "collapsed": false
457 },
458 "outputs": [],
459 "source": [
460 "%%px\n",
461 "%%timeit\n",
462 "from numpy.random import random\n",
463 "from numpy.linalg import norm\n",
464 "A = random((100,100))\n",
465 "norm(A, 2)"
466 ]
467 },
468 {
469 "cell_type": "markdown",
470 "metadata": {},
471 "source": [
472 "## Local Execution"
473 ]
474 },
475 {
476 "cell_type": "markdown",
477 "metadata": {},
478 "source": [
479 "As of IPython 1.0, you can instruct `%%px` to also execute the cell locally.\n",
480 "This is useful for interactive definitions,\n",
481 "or if you want to load a data source everywhere,\n",
482 "not just on the engines."
483 ]
484 },
485 {
486 "cell_type": "code",
487 "execution_count": null,
488 "metadata": {
489 "collapsed": false
490 },
491 "outputs": [],
492 "source": [
493 "%%px --local\n",
494 "import os\n",
495 "thispid = os.getpid()\n",
496 "print(thispid)"
497 ]
498 }
499 ],
500 "metadata": {
501 "kernelspec": {
502 "display_name": "Python 3",
503 "language": "python",
504 "name": "python3"
505 },
506 "language_info": {
507 "codemirror_mode": {
508 "name": "ipython",
509 "version": 3
510 },
511 "file_extension": ".py",
512 "mimetype": "text/x-python",
513 "name": "python",
514 "nbconvert_exporter": "python",
515 "pygments_lexer": "ipython3",
516 "version": "3.4.2"
517 }
518 },
519 "nbformat": 4,
520 "nbformat_minor": 0
521 }
This diff has been collapsed as it changes many lines, (559 lines changed) Show them Hide them
@@ -1,559 +0,0 b''
1 {
2 "cells": [
3 {
4 "cell_type": "markdown",
5 "metadata": {},
6 "source": [
7 "# Using dill to pickle anything"
8 ]
9 },
10 {
11 "cell_type": "markdown",
12 "metadata": {},
13 "source": [
14 "IPython.parallel doesn't do much in the way of serialization.\n",
15 "It has custom zero-copy handling of numpy arrays,\n",
16 "but other than that, it doesn't do anything other than the bare minimum to make basic interactively defined functions and classes sendable.\n",
17 "\n",
18 "There are a few projects that extend pickle to make just about anything sendable, and one of these is [dill](http://www.cacr.caltech.edu/~mmckerns/dill).\n",
19 "\n",
20 "To install dill:\n",
21 " \n",
22 " pip install --pre dill"
23 ]
24 },
25 {
26 "cell_type": "markdown",
27 "metadata": {},
28 "source": [
29 "First, as always, we create a task function, this time with a closure"
30 ]
31 },
32 {
33 "cell_type": "code",
34 "execution_count": 1,
35 "metadata": {
36 "collapsed": false
37 },
38 "outputs": [],
39 "source": [
40 "def make_closure(a):\n",
41 " \"\"\"make a function with a closure, and return it\"\"\"\n",
42 " def has_closure(b):\n",
43 " return a * b\n",
44 " return has_closure"
45 ]
46 },
47 {
48 "cell_type": "code",
49 "execution_count": 2,
50 "metadata": {
51 "collapsed": false
52 },
53 "outputs": [],
54 "source": [
55 "closed = make_closure(5)"
56 ]
57 },
58 {
59 "cell_type": "code",
60 "execution_count": 3,
61 "metadata": {
62 "collapsed": false
63 },
64 "outputs": [
65 {
66 "data": {
67 "text/plain": [
68 "10"
69 ]
70 },
71 "execution_count": 3,
72 "metadata": {},
73 "output_type": "execute_result"
74 }
75 ],
76 "source": [
77 "closed(2)"
78 ]
79 },
80 {
81 "cell_type": "code",
82 "execution_count": 4,
83 "metadata": {
84 "collapsed": false
85 },
86 "outputs": [],
87 "source": [
88 "import pickle"
89 ]
90 },
91 {
92 "cell_type": "markdown",
93 "metadata": {},
94 "source": [
95 "Without help, pickle can't deal with closures"
96 ]
97 },
98 {
99 "cell_type": "code",
100 "execution_count": 5,
101 "metadata": {
102 "collapsed": false
103 },
104 "outputs": [
105 {
106 "ename": "PicklingError",
107 "evalue": "Can't pickle <function has_closure at 0x10412c6e0>: it's not found as __main__.has_closure",
108 "output_type": "error",
109 "traceback": [
110 "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m\n\u001b[1;31mPicklingError\u001b[0m Traceback (most recent call last)",
111 "\u001b[1;32m<ipython-input-5-0f1f376cfea0>\u001b[0m in \u001b[0;36m<module>\u001b[1;34m()\u001b[0m\n\u001b[1;32m----> 1\u001b[1;33m \u001b[0mpickle\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mdumps\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mclosed\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m",
112 "\u001b[1;32m/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/pickle.pyc\u001b[0m in \u001b[0;36mdumps\u001b[1;34m(obj, protocol)\u001b[0m\n\u001b[0;32m 1372\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0mdumps\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mobj\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mprotocol\u001b[0m\u001b[1;33m=\u001b[0m\u001b[0mNone\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 1373\u001b[0m \u001b[0mfile\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mStringIO\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m-> 1374\u001b[1;33m \u001b[0mPickler\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mfile\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mprotocol\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mdump\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mobj\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 1375\u001b[0m \u001b[1;32mreturn\u001b[0m \u001b[0mfile\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mgetvalue\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 1376\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n",
113 "\u001b[1;32m/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/pickle.pyc\u001b[0m in \u001b[0;36mdump\u001b[1;34m(self, obj)\u001b[0m\n\u001b[0;32m 222\u001b[0m \u001b[1;32mif\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mproto\u001b[0m \u001b[1;33m>=\u001b[0m \u001b[1;36m2\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 223\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mwrite\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mPROTO\u001b[0m \u001b[1;33m+\u001b[0m \u001b[0mchr\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mproto\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m--> 224\u001b[1;33m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0msave\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mobj\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 225\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mwrite\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mSTOP\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 226\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n",
114 "\u001b[1;32m/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/pickle.pyc\u001b[0m in \u001b[0;36msave\u001b[1;34m(self, obj)\u001b[0m\n\u001b[0;32m 284\u001b[0m \u001b[0mf\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mdispatch\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mget\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mt\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 285\u001b[0m \u001b[1;32mif\u001b[0m \u001b[0mf\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m--> 286\u001b[1;33m \u001b[0mf\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mobj\u001b[0m\u001b[1;33m)\u001b[0m \u001b[1;31m# Call unbound method with explicit self\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 287\u001b[0m \u001b[1;32mreturn\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 288\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n",
115 "\u001b[1;32m/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/pickle.pyc\u001b[0m in \u001b[0;36msave_global\u001b[1;34m(self, obj, name, pack)\u001b[0m\n\u001b[0;32m 746\u001b[0m raise PicklingError(\n\u001b[0;32m 747\u001b[0m \u001b[1;34m\"Can't pickle %r: it's not found as %s.%s\"\u001b[0m \u001b[1;33m%\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m--> 748\u001b[1;33m (obj, module, name))\n\u001b[0m\u001b[0;32m 749\u001b[0m \u001b[1;32melse\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 750\u001b[0m \u001b[1;32mif\u001b[0m \u001b[0mklass\u001b[0m \u001b[1;32mis\u001b[0m \u001b[1;32mnot\u001b[0m \u001b[0mobj\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n",
116 "\u001b[1;31mPicklingError\u001b[0m: Can't pickle <function has_closure at 0x10412c6e0>: it's not found as __main__.has_closure"
117 ]
118 }
119 ],
120 "source": [
121 "pickle.dumps(closed)"
122 ]
123 },
124 {
125 "cell_type": "markdown",
126 "metadata": {},
127 "source": [
128 "But after we import dill, magic happens"
129 ]
130 },
131 {
132 "cell_type": "code",
133 "execution_count": 6,
134 "metadata": {
135 "collapsed": false
136 },
137 "outputs": [],
138 "source": [
139 "import dill"
140 ]
141 },
142 {
143 "cell_type": "code",
144 "execution_count": 7,
145 "metadata": {
146 "collapsed": false
147 },
148 "outputs": [
149 {
150 "data": {
151 "text/plain": [
152 "\"cdill.dill\\n_load_type\\np0\\n(S'FunctionType'\\np1\\ntp2\\nRp3\\n(cdill.dill...\""
153 ]
154 },
155 "execution_count": 7,
156 "metadata": {},
157 "output_type": "execute_result"
158 }
159 ],
160 "source": [
161 "pickle.dumps(closed)[:64] + '...'"
162 ]
163 },
164 {
165 "cell_type": "markdown",
166 "metadata": {},
167 "source": [
168 "So from now on, pretty much everything is pickleable."
169 ]
170 },
171 {
172 "cell_type": "markdown",
173 "metadata": {},
174 "source": [
175 "## Now use this in IPython.parallel"
176 ]
177 },
178 {
179 "cell_type": "markdown",
180 "metadata": {},
181 "source": [
182 "As usual, we start by creating our Client and View"
183 ]
184 },
185 {
186 "cell_type": "code",
187 "execution_count": 8,
188 "metadata": {
189 "collapsed": false
190 },
191 "outputs": [],
192 "source": [
193 "from IPython import parallel\n",
194 "rc = parallel.Client()\n",
195 "view = rc.load_balanced_view()"
196 ]
197 },
198 {
199 "cell_type": "markdown",
200 "metadata": {},
201 "source": [
202 "Now let's try sending our function with a closure:"
203 ]
204 },
205 {
206 "cell_type": "code",
207 "execution_count": 9,
208 "metadata": {
209 "collapsed": false
210 },
211 "outputs": [
212 {
213 "ename": "ValueError",
214 "evalue": "Sorry, cannot pickle code objects with closures",
215 "output_type": "error",
216 "traceback": [
217 "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m\n\u001b[1;31mValueError\u001b[0m Traceback (most recent call last)",
218 "\u001b[1;32m<ipython-input-9-23a646829fdc>\u001b[0m in \u001b[0;36m<module>\u001b[1;34m()\u001b[0m\n\u001b[1;32m----> 1\u001b[1;33m \u001b[0mview\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mapply_sync\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mclosed\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;36m3\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m",
219 "\u001b[1;32m/Users/minrk/dev/ip/mine/IPython/parallel/client/view.pyc\u001b[0m in \u001b[0;36mapply_sync\u001b[1;34m(self, f, *args, **kwargs)\u001b[0m\n",
220 "\u001b[1;32m/Users/minrk/dev/ip/mine/IPython/parallel/client/view.pyc\u001b[0m in \u001b[0;36mspin_after\u001b[1;34m(f, self, *args, **kwargs)\u001b[0m\n\u001b[0;32m 73\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0mspin_after\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mf\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;33m*\u001b[0m\u001b[0margs\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;33m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 74\u001b[0m \u001b[1;34m\"\"\"call spin after the method.\"\"\"\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m---> 75\u001b[1;33m \u001b[0mret\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mf\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;33m*\u001b[0m\u001b[0margs\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;33m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 76\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mspin\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 77\u001b[0m \u001b[1;32mreturn\u001b[0m \u001b[0mret\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n",
221 "\u001b[1;32m/Users/minrk/dev/ip/mine/IPython/parallel/client/view.pyc\u001b[0m in \u001b[0;36mapply_sync\u001b[1;34m(self, f, *args, **kwargs)\u001b[0m\n\u001b[0;32m 245\u001b[0m \u001b[0mreturning\u001b[0m \u001b[0mthe\u001b[0m \u001b[0mresult\u001b[0m\u001b[1;33m.\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 246\u001b[0m \"\"\"\n\u001b[1;32m--> 247\u001b[1;33m \u001b[1;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m_really_apply\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mf\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0margs\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mkwargs\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mblock\u001b[0m\u001b[1;33m=\u001b[0m\u001b[0mTrue\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 248\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 249\u001b[0m \u001b[1;31m#----------------------------------------------------------------\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n",
222 "\u001b[1;32m/Users/minrk/dev/ip/mine/IPython/parallel/client/view.pyc\u001b[0m in \u001b[0;36m_really_apply\u001b[1;34m(self, f, args, kwargs, block, track, after, follow, timeout, targets, retries)\u001b[0m\n",
223 "\u001b[1;32m/Users/minrk/dev/ip/mine/IPython/parallel/client/view.pyc\u001b[0m in \u001b[0;36msync_results\u001b[1;34m(f, self, *args, **kwargs)\u001b[0m\n\u001b[0;32m 64\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m_in_sync_results\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mTrue\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 65\u001b[0m \u001b[1;32mtry\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m---> 66\u001b[1;33m \u001b[0mret\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mf\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;33m*\u001b[0m\u001b[0margs\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;33m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 67\u001b[0m \u001b[1;32mfinally\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 68\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m_in_sync_results\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mFalse\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n",
224 "\u001b[1;32m/Users/minrk/dev/ip/mine/IPython/parallel/client/view.pyc\u001b[0m in \u001b[0;36m_really_apply\u001b[1;34m(self, f, args, kwargs, block, track, after, follow, timeout, targets, retries)\u001b[0m\n",
225 "\u001b[1;32m/Users/minrk/dev/ip/mine/IPython/parallel/client/view.pyc\u001b[0m in \u001b[0;36msave_ids\u001b[1;34m(f, self, *args, **kwargs)\u001b[0m\n\u001b[0;32m 49\u001b[0m \u001b[0mn_previous\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mlen\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mclient\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mhistory\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 50\u001b[0m \u001b[1;32mtry\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m---> 51\u001b[1;33m \u001b[0mret\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mf\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;33m*\u001b[0m\u001b[0margs\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;33m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 52\u001b[0m \u001b[1;32mfinally\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 53\u001b[0m \u001b[0mnmsgs\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mlen\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mclient\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mhistory\u001b[0m\u001b[1;33m)\u001b[0m \u001b[1;33m-\u001b[0m \u001b[0mn_previous\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n",
226 "\u001b[1;32m/Users/minrk/dev/ip/mine/IPython/parallel/client/view.pyc\u001b[0m in \u001b[0;36m_really_apply\u001b[1;34m(self, f, args, kwargs, block, track, after, follow, timeout, targets, retries)\u001b[0m\n\u001b[0;32m 1049\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 1050\u001b[0m msg = self.client.send_apply_request(self._socket, f, args, kwargs, track=track,\n\u001b[1;32m-> 1051\u001b[1;33m metadata=metadata)\n\u001b[0m\u001b[0;32m 1052\u001b[0m \u001b[0mtracker\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mNone\u001b[0m \u001b[1;32mif\u001b[0m \u001b[0mtrack\u001b[0m \u001b[1;32mis\u001b[0m \u001b[0mFalse\u001b[0m \u001b[1;32melse\u001b[0m \u001b[0mmsg\u001b[0m\u001b[1;33m[\u001b[0m\u001b[1;34m'tracker'\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 1053\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n",
227 "\u001b[1;32m/Users/minrk/dev/ip/mine/IPython/parallel/client/client.pyc\u001b[0m in \u001b[0;36msend_apply_request\u001b[1;34m(self, socket, f, args, kwargs, metadata, track, ident)\u001b[0m\n\u001b[0;32m 1252\u001b[0m bufs = serialize.pack_apply_message(f, args, kwargs,\n\u001b[0;32m 1253\u001b[0m \u001b[0mbuffer_threshold\u001b[0m\u001b[1;33m=\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0msession\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mbuffer_threshold\u001b[0m\u001b[1;33m,\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m-> 1254\u001b[1;33m \u001b[0mitem_threshold\u001b[0m\u001b[1;33m=\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0msession\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mitem_threshold\u001b[0m\u001b[1;33m,\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 1255\u001b[0m )\n\u001b[0;32m 1256\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n",
228 "\u001b[1;32m/Users/minrk/dev/ip/mine/IPython/kernel/zmq/serialize.pyc\u001b[0m in \u001b[0;36mpack_apply_message\u001b[1;34m(f, args, kwargs, buffer_threshold, item_threshold)\u001b[0m\n\u001b[0;32m 163\u001b[0m \u001b[0minfo\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mdict\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mnargs\u001b[0m\u001b[1;33m=\u001b[0m\u001b[0mlen\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0margs\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mnarg_bufs\u001b[0m\u001b[1;33m=\u001b[0m\u001b[0mlen\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0marg_bufs\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mkw_keys\u001b[0m\u001b[1;33m=\u001b[0m\u001b[0mkw_keys\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 164\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m--> 165\u001b[1;33m \u001b[0mmsg\u001b[0m \u001b[1;33m=\u001b[0m \u001b[1;33m[\u001b[0m\u001b[0mpickle\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mdumps\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mcan\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mf\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m,\u001b[0m\u001b[1;33m-\u001b[0m\u001b[1;36m1\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 166\u001b[0m \u001b[0mmsg\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mappend\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mpickle\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mdumps\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0minfo\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;33m-\u001b[0m\u001b[1;36m1\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 167\u001b[0m \u001b[0mmsg\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mextend\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0marg_bufs\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n",
229 "\u001b[1;32m/Users/minrk/dev/ip/mine/IPython/utils/codeutil.pyc\u001b[0m in \u001b[0;36mreduce_code\u001b[1;34m(co)\u001b[0m\n\u001b[0;32m 36\u001b[0m \u001b[1;32mdef\u001b[0m \u001b[0mreduce_code\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mco\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m 37\u001b[0m \u001b[1;32mif\u001b[0m \u001b[0mco\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mco_freevars\u001b[0m \u001b[1;32mor\u001b[0m \u001b[0mco\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mco_cellvars\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m---> 38\u001b[1;33m \u001b[1;32mraise\u001b[0m \u001b[0mValueError\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m\"Sorry, cannot pickle code objects with closures\"\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m 39\u001b[0m args = [co.co_argcount, co.co_nlocals, co.co_stacksize,\n\u001b[0;32m 40\u001b[0m \u001b[0mco\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mco_flags\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mco\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mco_code\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mco\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mco_consts\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mco\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mco_names\u001b[0m\u001b[1;33m,\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n",
230 "\u001b[1;31mValueError\u001b[0m: Sorry, cannot pickle code objects with closures"
231 ]
232 }
233 ],
234 "source": [
235 "view.apply_sync(closed, 3)"
236 ]
237 },
238 {
239 "cell_type": "markdown",
240 "metadata": {},
241 "source": [
242 "Oops, no dice. For IPython to work with dill,\n",
243 "there are one or two more steps. IPython will do these for you if you call `DirectView.use_dill`:"
244 ]
245 },
246 {
247 "cell_type": "code",
248 "execution_count": 10,
249 "metadata": {
250 "collapsed": false
251 },
252 "outputs": [
253 {
254 "data": {
255 "text/plain": [
256 "<AsyncResult: use_dill>"
257 ]
258 },
259 "execution_count": 10,
260 "metadata": {},
261 "output_type": "execute_result"
262 }
263 ],
264 "source": [
265 "rc[:].use_dill()"
266 ]
267 },
268 {
269 "cell_type": "markdown",
270 "metadata": {},
271 "source": [
272 "This is equivalent to\n",
273 "\n",
274 "```python\n",
275 "from IPython.utils.pickleutil import use_dill\n",
276 "use_dill()\n",
277 "rc[:].apply(use_dill)\n",
278 "```"
279 ]
280 },
281 {
282 "cell_type": "markdown",
283 "metadata": {},
284 "source": [
285 "Now let's try again"
286 ]
287 },
288 {
289 "cell_type": "code",
290 "execution_count": 11,
291 "metadata": {
292 "collapsed": false
293 },
294 "outputs": [
295 {
296 "data": {
297 "text/plain": [
298 "15"
299 ]
300 },
301 "execution_count": 11,
302 "metadata": {},
303 "output_type": "execute_result"
304 }
305 ],
306 "source": [
307 "view.apply_sync(closed, 3)"
308 ]
309 },
310 {
311 "cell_type": "markdown",
312 "metadata": {},
313 "source": [
314 "Yay! Now we can use dill to allow IPython.parallel to send anything.\n",
315 "\n",
316 "And that's it! We can send closures and other previously non-pickleables to our engines.\n",
317 "\n",
318 "Let's give it a try now:"
319 ]
320 },
321 {
322 "cell_type": "code",
323 "execution_count": 12,
324 "metadata": {
325 "collapsed": false
326 },
327 "outputs": [
328 {
329 "data": {
330 "text/plain": [
331 "20"
332 ]
333 },
334 "execution_count": 12,
335 "metadata": {},
336 "output_type": "execute_result"
337 }
338 ],
339 "source": [
340 "remote_closure = view.apply_sync(make_closure, 4)\n",
341 "remote_closure(5)"
342 ]
343 },
344 {
345 "cell_type": "markdown",
346 "metadata": {},
347 "source": [
348 "But wait, there's more!\n",
349 "\n",
350 "At this point, we can send/recv all kinds of stuff"
351 ]
352 },
353 {
354 "cell_type": "code",
355 "execution_count": 13,
356 "metadata": {
357 "collapsed": false
358 },
359 "outputs": [],
360 "source": [
361 "def outer(a):\n",
362 " def inner(b):\n",
363 " def inner_again(c):\n",
364 " return c * b * a\n",
365 " return inner_again\n",
366 " return inner"
367 ]
368 },
369 {
370 "cell_type": "markdown",
371 "metadata": {},
372 "source": [
373 "So outer returns a function with a closure, which returns a function with a closure.\n",
374 "\n",
375 "Now, we can resolve the first closure on the engine, the second here, and the third on a different engine,\n",
376 "after passing through a lambda we define here and call there, just for good measure."
377 ]
378 },
379 {
380 "cell_type": "code",
381 "execution_count": 14,
382 "metadata": {
383 "collapsed": false
384 },
385 "outputs": [
386 {
387 "data": {
388 "text/plain": [
389 "6"
390 ]
391 },
392 "execution_count": 14,
393 "metadata": {},
394 "output_type": "execute_result"
395 }
396 ],
397 "source": [
398 "view.apply_sync(lambda f: f(3),view.apply_sync(outer, 1)(2))"
399 ]
400 },
401 {
402 "cell_type": "markdown",
403 "metadata": {},
404 "source": [
405 "And for good measure, let's test that normal execution still works:"
406 ]
407 },
408 {
409 "cell_type": "code",
410 "execution_count": 15,
411 "metadata": {
412 "collapsed": false
413 },
414 "outputs": [
415 {
416 "name": "stdout",
417 "output_type": "stream",
418 "text": [
419 "[5, 5, 5, 5, 5, 5, 5, 5]\n"
420 ]
421 },
422 {
423 "data": {
424 "text/plain": [
425 "[10, 10, 10, 10, 10, 10, 10, 10]"
426 ]
427 },
428 "execution_count": 15,
429 "metadata": {},
430 "output_type": "execute_result"
431 }
432 ],
433 "source": [
434 "%px foo = 5\n",
435 "\n",
436 "print(rc[:]['foo'])\n",
437 "rc[:]['bar'] = lambda : 2 * foo\n",
438 "rc[:].apply_sync(parallel.Reference('bar'))"
439 ]
440 },
441 {
442 "cell_type": "markdown",
443 "metadata": {},
444 "source": [
445 "And test that the `@interactive` decorator works"
446 ]
447 },
448 {
449 "cell_type": "code",
450 "execution_count": 16,
451 "metadata": {
452 "collapsed": false
453 },
454 "outputs": [
455 {
456 "name": "stdout",
457 "output_type": "stream",
458 "text": [
459 "Overwriting testdill.py\n"
460 ]
461 }
462 ],
463 "source": [
464 "%%file testdill.py\n",
465 "from IPython.parallel import interactive\n",
466 "\n",
467 "@interactive\n",
468 "class C(object):\n",
469 " a = 5\n",
470 "\n",
471 "@interactive\n",
472 "class D(C):\n",
473 " b = 10\n",
474 "\n",
475 "@interactive\n",
476 "def foo(a):\n",
477 " return a * b\n"
478 ]
479 },
480 {
481 "cell_type": "code",
482 "execution_count": 17,
483 "metadata": {
484 "collapsed": false
485 },
486 "outputs": [],
487 "source": [
488 "import testdill"
489 ]
490 },
491 {
492 "cell_type": "code",
493 "execution_count": 18,
494 "metadata": {
495 "collapsed": false
496 },
497 "outputs": [
498 {
499 "name": "stdout",
500 "output_type": "stream",
501 "text": [
502 "5 10\n"
503 ]
504 }
505 ],
506 "source": [
507 "v = rc[-1]\n",
508 "v['D'] = testdill.D\n",
509 "d = v.apply_sync(lambda : D())\n",
510 "print d.a, d.b"
511 ]
512 },
513 {
514 "cell_type": "code",
515 "execution_count": 19,
516 "metadata": {
517 "collapsed": false
518 },
519 "outputs": [
520 {
521 "data": {
522 "text/plain": [
523 "50"
524 ]
525 },
526 "execution_count": 19,
527 "metadata": {},
528 "output_type": "execute_result"
529 }
530 ],
531 "source": [
532 "v['b'] = 10\n",
533 "v.apply_sync(testdill.foo, 5)"
534 ]
535 }
536 ],
537 "metadata": {
538 "gist_id": "5241793",
539 "kernelspec": {
540 "display_name": "Python 3",
541 "language": "python",
542 "name": "python3"
543 },
544 "language_info": {
545 "codemirror_mode": {
546 "name": "ipython",
547 "version": 3
548 },
549 "file_extension": ".py",
550 "mimetype": "text/x-python",
551 "name": "python",
552 "nbconvert_exporter": "python",
553 "pygments_lexer": "ipython3",
554 "version": "3.4.2"
555 }
556 },
557 "nbformat": 4,
558 "nbformat_minor": 0
559 }
@@ -1,206 +0,0 b''
1 {
2 "cells": [
3 {
4 "cell_type": "markdown",
5 "metadata": {},
6 "source": [
7 "# Simple usage of a set of MPI engines"
8 ]
9 },
10 {
11 "cell_type": "markdown",
12 "metadata": {},
13 "source": [
14 "This example assumes you've started a cluster of N engines (4 in this example) as part\n",
15 "of an MPI world. \n",
16 "\n",
17 "Our documentation describes [how to create an MPI profile](http://ipython.org/ipython-doc/dev/parallel/parallel_process.html#using-ipcluster-in-mpiexec-mpirun-mode)\n",
18 "and explains [basic MPI usage of the IPython cluster](http://ipython.org/ipython-doc/dev/parallel/parallel_mpi.html).\n",
19 "\n",
20 "\n",
21 "For the simplest possible way to start 4 engines that belong to the same MPI world, \n",
22 "you can run this in a terminal:\n",
23 "\n",
24 "<pre>\n",
25 "ipcluster start --engines=MPI -n 4\n",
26 "</pre>\n",
27 "\n",
28 "or start an MPI cluster from the cluster tab if you have one configured.\n",
29 "\n",
30 "Once the cluster is running, we can connect to it and open a view into it:"
31 ]
32 },
33 {
34 "cell_type": "code",
35 "execution_count": 1,
36 "metadata": {
37 "collapsed": true
38 },
39 "outputs": [],
40 "source": [
41 "from IPython.parallel import Client\n",
42 "c = Client()\n",
43 "view = c[:]"
44 ]
45 },
46 {
47 "cell_type": "markdown",
48 "metadata": {},
49 "source": [
50 "Let's define a simple function that gets the MPI rank from each engine."
51 ]
52 },
53 {
54 "cell_type": "code",
55 "execution_count": 2,
56 "metadata": {
57 "collapsed": true
58 },
59 "outputs": [],
60 "source": [
61 "@view.remote(block=True)\n",
62 "def mpi_rank():\n",
63 " from mpi4py import MPI\n",
64 " comm = MPI.COMM_WORLD\n",
65 " return comm.Get_rank()"
66 ]
67 },
68 {
69 "cell_type": "code",
70 "execution_count": 3,
71 "metadata": {
72 "collapsed": false
73 },
74 "outputs": [
75 {
76 "data": {
77 "text/plain": [
78 "[2, 3, 1, 0]"
79 ]
80 },
81 "execution_count": 3,
82 "metadata": {},
83 "output_type": "execute_result"
84 }
85 ],
86 "source": [
87 "mpi_rank()"
88 ]
89 },
90 {
91 "cell_type": "markdown",
92 "metadata": {},
93 "source": [
94 "To get a mapping of IPython IDs and MPI rank (these do not always match),\n",
95 "you can use the get_dict method on AsyncResults."
96 ]
97 },
98 {
99 "cell_type": "code",
100 "execution_count": 4,
101 "metadata": {
102 "collapsed": false
103 },
104 "outputs": [
105 {
106 "data": {
107 "text/plain": [
108 "{0: 2, 1: 3, 2: 1, 3: 0}"
109 ]
110 },
111 "execution_count": 4,
112 "metadata": {},
113 "output_type": "execute_result"
114 }
115 ],
116 "source": [
117 "mpi_rank.block = False\n",
118 "ar = mpi_rank()\n",
119 "ar.get_dict()"
120 ]
121 },
122 {
123 "cell_type": "markdown",
124 "metadata": {},
125 "source": [
126 "With %%px cell magic, the next cell will actually execute *entirely on each engine*:"
127 ]
128 },
129 {
130 "cell_type": "code",
131 "execution_count": 5,
132 "metadata": {
133 "collapsed": true
134 },
135 "outputs": [],
136 "source": [
137 "%%px\n",
138 "from mpi4py import MPI\n",
139 "\n",
140 "comm = MPI.COMM_WORLD\n",
141 "size = comm.Get_size()\n",
142 "rank = comm.Get_rank()\n",
143 "\n",
144 "if rank == 0:\n",
145 " data = [(i+1)**2 for i in range(size)]\n",
146 "else:\n",
147 " data = None\n",
148 "data = comm.scatter(data, root=0)\n",
149 "\n",
150 "assert data == (rank+1)**2, 'data=%s, rank=%s' % (data, rank)"
151 ]
152 },
153 {
154 "cell_type": "code",
155 "execution_count": 6,
156 "metadata": {
157 "collapsed": false
158 },
159 "outputs": [
160 {
161 "data": {
162 "text/plain": [
163 "[9, 16, 4, 1]"
164 ]
165 },
166 "execution_count": 6,
167 "metadata": {},
168 "output_type": "execute_result"
169 }
170 ],
171 "source": [
172 "view['data']"
173 ]
174 },
175 {
176 "cell_type": "code",
177 "execution_count": 6,
178 "metadata": {
179 "collapsed": true
180 },
181 "outputs": [],
182 "source": []
183 }
184 ],
185 "metadata": {
186 "kernelspec": {
187 "display_name": "Python 3",
188 "language": "python",
189 "name": "python3"
190 },
191 "language_info": {
192 "codemirror_mode": {
193 "name": "ipython",
194 "version": 3
195 },
196 "file_extension": ".py",
197 "mimetype": "text/x-python",
198 "name": "python",
199 "nbconvert_exporter": "python",
200 "pygments_lexer": "ipython3",
201 "version": "3.4.2"
202 }
203 },
204 "nbformat": 4,
205 "nbformat_minor": 0
206 }
@@ -1,61 +0,0 b''
1 """An example for handling results in a way that AsyncMapResult doesn't provide
2
3 Specifically, out-of-order results with some special handing of metadata.
4
5 This just submits a bunch of jobs, waits on the results, and prints the stdout
6 and results of each as they finish.
7
8 Authors
9 -------
10 * MinRK
11 """
12 import time
13 import random
14
15 from IPython import parallel
16
17 # create client & views
18 rc = parallel.Client()
19 dv = rc[:]
20 v = rc.load_balanced_view()
21
22
23 # scatter 'id', so id=0,1,2 on engines 0,1,2
24 dv.scatter('id', rc.ids, flatten=True)
25 print(dv['id'])
26
27
28 def sleep_here(count, t):
29 """simple function that takes args, prints a short message, sleeps for a time, and returns the same args"""
30 import time,sys
31 print("hi from engine %i" % id)
32 sys.stdout.flush()
33 time.sleep(t)
34 return count,t
35
36 amr = v.map(sleep_here, range(100), [ random.random() for i in range(100) ], chunksize=2)
37
38 pending = set(amr.msg_ids)
39 while pending:
40 try:
41 rc.wait(pending, 1e-3)
42 except parallel.TimeoutError:
43 # ignore timeouterrors, since they only mean that at least one isn't done
44 pass
45 # finished is the set of msg_ids that are complete
46 finished = pending.difference(rc.outstanding)
47 # update pending to exclude those that just finished
48 pending = pending.difference(finished)
49 for msg_id in finished:
50 # we know these are done, so don't worry about blocking
51 ar = rc.get_result(msg_id)
52 print("job id %s finished on engine %i" % (msg_id, ar.engine_id))
53 print("with stdout:")
54 print(' ' + ar.stdout.replace('\n', '\n ').rstrip())
55 print("and results:")
56
57 # note that each job in a map always returns a list of length chunksize
58 # even if chunksize == 1
59 for (count,t) in ar.result:
60 print(" item %i: slept for %.2fs" % (count, t))
61
@@ -1,89 +0,0 b''
1 #!/usr/bin/env python
2 """Parallel word frequency counter.
3
4 This only works for a local cluster, because the filenames are local paths.
5 """
6 from __future__ import division
7
8
9 import os
10 import time
11 import urllib
12
13 from itertools import repeat
14
15 from wordfreq import print_wordfreq, wordfreq
16
17 from IPython.parallel import Client, Reference
18
19 try: #python2
20 from urllib import urlretrieve
21 except ImportError: #python3
22 from urllib.request import urlretrieve
23
24 davinci_url = "http://www.gutenberg.org/cache/epub/5000/pg5000.txt"
25
26 def pwordfreq(view, fnames):
27 """Parallel word frequency counter.
28
29 view - An IPython DirectView
30 fnames - The filenames containing the split data.
31 """
32 assert len(fnames) == len(view.targets)
33 view.scatter('fname', fnames, flatten=True)
34 ar = view.apply(wordfreq, Reference('fname'))
35 freqs_list = ar.get()
36 word_set = set()
37 for f in freqs_list:
38 word_set.update(f.keys())
39 freqs = dict(zip(word_set, repeat(0)))
40 for f in freqs_list:
41 for word, count in f.items():
42 freqs[word] += count
43 return freqs
44
45 if __name__ == '__main__':
46 # Create a Client and View
47 rc = Client()
48
49 view = rc[:]
50
51 if not os.path.exists('davinci.txt'):
52 # download from project gutenberg
53 print("Downloading Da Vinci's notebooks from Project Gutenberg")
54 urlretrieve(davinci_url, 'davinci.txt')
55
56 # Run the serial version
57 print("Serial word frequency count:")
58 text = open('davinci.txt').read()
59 tic = time.time()
60 freqs = wordfreq(text)
61 toc = time.time()
62 print_wordfreq(freqs, 10)
63 print("Took %.3f s to calculate"%(toc-tic))
64
65
66 # The parallel version
67 print("\nParallel word frequency count:")
68 # split the davinci.txt into one file per engine:
69 lines = text.splitlines()
70 nlines = len(lines)
71 n = len(rc)
72 block = nlines//n
73 for i in range(n):
74 chunk = lines[i*block:i*(block+1)]
75 with open('davinci%i.txt'%i, 'w') as f:
76 f.write('\n'.join(chunk))
77
78 try: #python2
79 cwd = os.path.abspath(os.getcwdu())
80 except AttributeError: #python3
81 cwd = os.path.abspath(os.getcwd())
82 fnames = [ os.path.join(cwd, 'davinci%i.txt'%i) for i in range(n)]
83 tic = time.time()
84 pfreqs = pwordfreq(view,fnames)
85 toc = time.time()
86 print_wordfreq(freqs)
87 print("Took %.3f s to calculate on %i engines"%(toc-tic, len(view.targets)))
88 # cleanup split files
89 map(os.remove, fnames)
@@ -1,69 +0,0 b''
1 """Count the frequencies of words in a string"""
2
3 from __future__ import division
4 from __future__ import print_function
5
6 import cmath as math
7
8
9 def wordfreq(text, is_filename=False):
10 """Return a dictionary of words and word counts in a string."""
11 if is_filename:
12 with open(text) as f:
13 text = f.read()
14 freqs = {}
15 for word in text.split():
16 lword = word.lower()
17 freqs[lword] = freqs.get(lword, 0) + 1
18 return freqs
19
20
21 def print_wordfreq(freqs, n=10):
22 """Print the n most common words and counts in the freqs dict."""
23
24 words, counts = freqs.keys(), freqs.values()
25 items = zip(counts, words)
26 items.sort(reverse=True)
27 for (count, word) in items[:n]:
28 print(word, count)
29
30
31 def wordfreq_to_weightsize(worddict, minsize=25, maxsize=50, minalpha=0.5, maxalpha=1.0):
32 mincount = min(worddict.itervalues())
33 maxcount = max(worddict.itervalues())
34 weights = {}
35 for k, v in worddict.iteritems():
36 w = (v-mincount)/(maxcount-mincount)
37 alpha = minalpha + (maxalpha-minalpha)*w
38 size = minsize + (maxsize-minsize)*w
39 weights[k] = (alpha, size)
40 return weights
41
42
43 def tagcloud(worddict, n=10, minsize=25, maxsize=50, minalpha=0.5, maxalpha=1.0):
44 from matplotlib import pyplot as plt
45 import random
46
47 worddict = wordfreq_to_weightsize(worddict, minsize, maxsize, minalpha, maxalpha)
48
49 fig = plt.figure()
50 ax = fig.add_subplot(111)
51 ax.set_position([0.0,0.0,1.0,1.0])
52 plt.xticks([])
53 plt.yticks([])
54
55 words = worddict.keys()
56 alphas = [v[0] for v in worddict.values()]
57 sizes = [v[1] for v in worddict.values()]
58 items = zip(alphas, sizes, words)
59 items.sort(reverse=True)
60 for alpha, size, word in items[:n]:
61 # xpos = random.normalvariate(0.5, 0.3)
62 # ypos = random.normalvariate(0.5, 0.3)
63 xpos = random.uniform(0.0,1.0)
64 ypos = random.uniform(0.0,1.0)
65 ax.text(xpos, ypos, word.lower(), alpha=alpha, fontsize=size)
66 ax.autoscale_view()
67 return ax
68
69
@@ -1,120 +0,0 b''
1 """Example for generating an arbitrary DAG as a dependency map.
2
3 This demo uses networkx to generate the graph.
4
5 Authors
6 -------
7 * MinRK
8 """
9 import networkx as nx
10 from random import randint, random
11 from IPython import parallel
12
13 def randomwait():
14 import time
15 from random import random
16 time.sleep(random())
17 return time.time()
18
19
20 def random_dag(nodes, edges):
21 """Generate a random Directed Acyclic Graph (DAG) with a given number of nodes and edges."""
22 G = nx.DiGraph()
23 for i in range(nodes):
24 G.add_node(i)
25 while edges > 0:
26 a = randint(0,nodes-1)
27 b=a
28 while b==a:
29 b = randint(0,nodes-1)
30 G.add_edge(a,b)
31 if nx.is_directed_acyclic_graph(G):
32 edges -= 1
33 else:
34 # we closed a loop!
35 G.remove_edge(a,b)
36 return G
37
38 def add_children(G, parent, level, n=2):
39 """Add children recursively to a binary tree."""
40 if level == 0:
41 return
42 for i in range(n):
43 child = parent+str(i)
44 G.add_node(child)
45 G.add_edge(parent,child)
46 add_children(G, child, level-1, n)
47
48 def make_bintree(levels):
49 """Make a symmetrical binary tree with @levels"""
50 G = nx.DiGraph()
51 root = '0'
52 G.add_node(root)
53 add_children(G, root, levels, 2)
54 return G
55
56 def submit_jobs(view, G, jobs):
57 """Submit jobs via client where G describes the time dependencies."""
58 results = {}
59 for node in nx.topological_sort(G):
60 with view.temp_flags(after=[ results[n] for n in G.predecessors(node) ]):
61 results[node] = view.apply(jobs[node])
62 return results
63
64 def validate_tree(G, results):
65 """Validate that jobs executed after their dependencies."""
66 for node in G:
67 started = results[node].metadata.started
68 for parent in G.predecessors(node):
69 finished = results[parent].metadata.completed
70 assert started > finished, "%s should have happened after %s"%(node, parent)
71
72 def main(nodes, edges):
73 """Generate a random graph, submit jobs, then validate that the
74 dependency order was enforced.
75 Finally, plot the graph, with time on the x-axis, and
76 in-degree on the y (just for spread). All arrows must
77 point at least slightly to the right if the graph is valid.
78 """
79 from matplotlib import pyplot as plt
80 from matplotlib.dates import date2num
81 from matplotlib.cm import gist_rainbow
82 print("building DAG")
83 G = random_dag(nodes, edges)
84 jobs = {}
85 pos = {}
86 colors = {}
87 for node in G:
88 jobs[node] = randomwait
89
90 client = parallel.Client()
91 view = client.load_balanced_view()
92 print("submitting %i tasks with %i dependencies"%(nodes,edges))
93 results = submit_jobs(view, G, jobs)
94 print("waiting for results")
95 view.wait()
96 print("done")
97 for node in G:
98 md = results[node].metadata
99 start = date2num(md.started)
100 runtime = date2num(md.completed) - start
101 pos[node] = (start, runtime)
102 colors[node] = md.engine_id
103 validate_tree(G, results)
104 nx.draw(G, pos, node_list=colors.keys(), node_color=colors.values(), cmap=gist_rainbow,
105 with_labels=False)
106 x,y = zip(*pos.values())
107 xmin,ymin = map(min, (x,y))
108 xmax,ymax = map(max, (x,y))
109 xscale = xmax-xmin
110 yscale = ymax-ymin
111 plt.xlim(xmin-xscale*.1,xmax+xscale*.1)
112 plt.ylim(ymin-yscale*.1,ymax+yscale*.1)
113 return G,results
114
115 if __name__ == '__main__':
116 from matplotlib import pyplot as plt
117 # main(5,10)
118 main(32,96)
119 plt.show()
120
@@ -1,128 +0,0 b''
1 from IPython.parallel import *
2
3 client = Client()
4
5 # this will only run on machines that can import numpy:
6 @require('numpy')
7 def norm(A):
8 from numpy.linalg import norm
9 return norm(A,2)
10
11 def checkpid(pid):
12 """return the pid of the engine"""
13 import os
14 return os.getpid() == pid
15
16 def checkhostname(host):
17 import socket
18 return socket.gethostname() == host
19
20 def getpid():
21 import os
22 return os.getpid()
23
24 pid0 = client[0].apply_sync(getpid)
25
26 # this will depend on the pid being that of target 0:
27 @depend(checkpid, pid0)
28 def getpid2():
29 import os
30 return os.getpid()
31
32 view = client.load_balanced_view()
33 view.block=True
34
35 # will run on anything:
36 pids1 = [ view.apply(getpid) for i in range(len(client.ids)) ]
37 print(pids1)
38 # will only run on e0:
39 pids2 = [ view.apply(getpid2) for i in range(len(client.ids)) ]
40 print(pids2)
41
42 print("now test some dependency behaviors")
43
44 def wait(t):
45 import time
46 time.sleep(t)
47 return t
48
49 # fail after some time:
50 def wait_and_fail(t):
51 import time
52 time.sleep(t)
53 return 1/0
54
55 successes = [ view.apply_async(wait, 1).msg_ids[0] for i in range(len(client.ids)) ]
56 failures = [ view.apply_async(wait_and_fail, 1).msg_ids[0] for i in range(len(client.ids)) ]
57
58 mixed = [failures[0],successes[0]]
59 d1a = Dependency(mixed, all=False, failure=True) # yes
60 d1b = Dependency(mixed, all=False) # yes
61 d2a = Dependency(mixed, all=True, failure=True) # yes after / no follow
62 d2b = Dependency(mixed, all=True) # no
63 d3 = Dependency(failures, all=False) # no
64 d4 = Dependency(failures, all=False, failure=True) # yes
65 d5 = Dependency(failures, all=True, failure=True) # yes after / no follow
66 d6 = Dependency(successes, all=True, failure=True) # yes after / no follow
67
68 view.block = False
69 flags = view.temp_flags
70 with flags(after=d1a):
71 r1a = view.apply(getpid)
72 with flags(follow=d1b):
73 r1b = view.apply(getpid)
74 with flags(after=d2b, follow=d2a):
75 r2a = view.apply(getpid)
76 with flags(after=d2a, follow=d2b):
77 r2b = view.apply(getpid)
78 with flags(after=d3):
79 r3 = view.apply(getpid)
80 with flags(after=d4):
81 r4a = view.apply(getpid)
82 with flags(follow=d4):
83 r4b = view.apply(getpid)
84 with flags(after=d3, follow=d4):
85 r4c = view.apply(getpid)
86 with flags(after=d5):
87 r5 = view.apply(getpid)
88 with flags(follow=d5, after=d3):
89 r5b = view.apply(getpid)
90 with flags(follow=d6):
91 r6 = view.apply(getpid)
92 with flags(after=d6, follow=d2b):
93 r6b = view.apply(getpid)
94
95 def should_fail(f):
96 try:
97 f()
98 except error.KernelError:
99 pass
100 else:
101 print('should have raised')
102 # raise Exception("should have raised")
103
104 # print(r1a.msg_ids)
105 r1a.get()
106 # print(r1b.msg_ids)
107 r1b.get()
108 # print(r2a.msg_ids)
109 should_fail(r2a.get)
110 # print(r2b.msg_ids)
111 should_fail(r2b.get)
112 # print(r3.msg_ids)
113 should_fail(r3.get)
114 # print(r4a.msg_ids)
115 r4a.get()
116 # print(r4b.msg_ids)
117 r4b.get()
118 # print(r4c.msg_ids)
119 should_fail(r4c.get)
120 # print(r5.msg_ids)
121 r5.get()
122 # print(r5b.msg_ids)
123 should_fail(r5b.get)
124 # print(r6.msg_ids)
125 should_fail(r6.get) # assuming > 1 engine
126 # print(r6b.msg_ids)
127 should_fail(r6b.get)
128 print('done')
@@ -1,99 +0,0 b''
1 """
2 An exceptionally lousy site spider
3 Ken Kinder <ken@kenkinder.com>
4
5 Updated for newparallel by Min Ragan-Kelley <benjaminrk@gmail.com>
6
7 This module gives an example of how the task interface to the
8 IPython controller works. Before running this script start the IPython controller
9 and some engines using something like::
10
11 ipcluster start -n 4
12 """
13 from __future__ import print_function
14
15 import sys
16 from IPython.parallel import Client, error
17 import time
18 import BeautifulSoup # this isn't necessary, but it helps throw the dependency error earlier
19
20 def fetchAndParse(url, data=None):
21 import urllib2
22 import urlparse
23 import BeautifulSoup
24 links = []
25 try:
26 page = urllib2.urlopen(url, data=data)
27 except Exception:
28 return links
29 else:
30 if page.headers.type == 'text/html':
31 doc = BeautifulSoup.BeautifulSoup(page.read())
32 for node in doc.findAll('a'):
33 href = node.get('href', None)
34 if href:
35 links.append(urlparse.urljoin(url, href))
36 return links
37
38 class DistributedSpider(object):
39
40 # Time to wait between polling for task results.
41 pollingDelay = 0.5
42
43 def __init__(self, site):
44 self.client = Client()
45 self.view = self.client.load_balanced_view()
46 self.mux = self.client[:]
47
48 self.allLinks = []
49 self.linksWorking = {}
50 self.linksDone = {}
51
52 self.site = site
53
54 def visitLink(self, url):
55 if url not in self.allLinks:
56 self.allLinks.append(url)
57 if url.startswith(self.site):
58 print(' ', url)
59 self.linksWorking[url] = self.view.apply(fetchAndParse, url)
60
61 def onVisitDone(self, links, url):
62 print(url, ':')
63 self.linksDone[url] = None
64 del self.linksWorking[url]
65 for link in links:
66 self.visitLink(link)
67
68 def run(self):
69 self.visitLink(self.site)
70 while self.linksWorking:
71 print(len(self.linksWorking), 'pending...')
72 self.synchronize()
73 time.sleep(self.pollingDelay)
74
75 def synchronize(self):
76 for url, ar in self.linksWorking.items():
77 # Calling get_task_result with block=False will return None if the
78 # task is not done yet. This provides a simple way of polling.
79 try:
80 links = ar.get(0)
81 except error.TimeoutError:
82 continue
83 except Exception as e:
84 self.linksDone[url] = None
85 del self.linksWorking[url]
86 print(url, ':', e.traceback)
87 else:
88 self.onVisitDone(links, url)
89
90 def main():
91 if len(sys.argv) > 1:
92 site = sys.argv[1]
93 else:
94 site = raw_input('Enter site to crawl: ')
95 distributedSpider = DistributedSpider(site)
96 distributedSpider.run()
97
98 if __name__ == '__main__':
99 main()
@@ -1,246 +0,0 b''
1 """
2 BinaryTree inter-engine communication class
3
4 use from bintree_script.py
5
6 Provides parallel [all]reduce functionality
7
8 """
9 from __future__ import print_function
10
11 import cPickle as pickle
12 import re
13 import socket
14 import uuid
15
16 import zmq
17
18 from IPython.parallel.util import disambiguate_url
19
20
21 #----------------------------------------------------------------------------
22 # bintree-related construction/printing helpers
23 #----------------------------------------------------------------------------
24
25 def bintree(ids, parent=None):
26 """construct {child:parent} dict representation of a binary tree
27
28 keys are the nodes in the tree, and values are the parent of each node.
29
30 The root node has parent `parent`, default: None.
31
32 >>> tree = bintree(range(7))
33 >>> tree
34 {0: None, 1: 0, 2: 1, 3: 1, 4: 0, 5: 4, 6: 4}
35 >>> print_bintree(tree)
36 0
37 1
38 2
39 3
40 4
41 5
42 6
43 """
44 parents = {}
45 n = len(ids)
46 if n == 0:
47 return parents
48 root = ids[0]
49 parents[root] = parent
50 if len(ids) == 1:
51 return parents
52 else:
53 ids = ids[1:]
54 n = len(ids)
55 left = bintree(ids[:n/2], parent=root)
56 right = bintree(ids[n/2:], parent=root)
57 parents.update(left)
58 parents.update(right)
59 return parents
60
61 def reverse_bintree(parents):
62 """construct {parent:[children]} dict from {child:parent}
63
64 keys are the nodes in the tree, and values are the lists of children
65 of that node in the tree.
66
67 reverse_tree[None] is the root node
68
69 >>> tree = bintree(range(7))
70 >>> reverse_bintree(tree)
71 {None: 0, 0: [1, 4], 4: [5, 6], 1: [2, 3]}
72 """
73 children = {}
74 for child,parent in parents.iteritems():
75 if parent is None:
76 children[None] = child
77 continue
78 elif parent not in children:
79 children[parent] = []
80 children[parent].append(child)
81
82 return children
83
84 def depth(n, tree):
85 """get depth of an element in the tree"""
86 d = 0
87 parent = tree[n]
88 while parent is not None:
89 d += 1
90 parent = tree[parent]
91 return d
92
93 def print_bintree(tree, indent=' '):
94 """print a binary tree"""
95 for n in sorted(tree.keys()):
96 print("%s%s" % (indent * depth(n,tree), n))
97
98 #----------------------------------------------------------------------------
99 # Communicator class for a binary-tree map
100 #----------------------------------------------------------------------------
101
102 ip_pat = re.compile(r'^\d+\.\d+\.\d+\.\d+$')
103
104 def disambiguate_dns_url(url, location):
105 """accept either IP address or dns name, and return IP"""
106 if not ip_pat.match(location):
107 location = socket.gethostbyname(location)
108 return disambiguate_url(url, location)
109
110 class BinaryTreeCommunicator(object):
111
112 id = None
113 pub = None
114 sub = None
115 downstream = None
116 upstream = None
117 pub_url = None
118 tree_url = None
119
120 def __init__(self, id, interface='tcp://*', root=False):
121 self.id = id
122 self.root = root
123
124 # create context and sockets
125 self._ctx = zmq.Context()
126 if root:
127 self.pub = self._ctx.socket(zmq.PUB)
128 else:
129 self.sub = self._ctx.socket(zmq.SUB)
130 self.sub.setsockopt(zmq.SUBSCRIBE, b'')
131 self.downstream = self._ctx.socket(zmq.PULL)
132 self.upstream = self._ctx.socket(zmq.PUSH)
133
134 # bind to ports
135 interface_f = interface + ":%i"
136 if self.root:
137 pub_port = self.pub.bind_to_random_port(interface)
138 self.pub_url = interface_f % pub_port
139
140 tree_port = self.downstream.bind_to_random_port(interface)
141 self.tree_url = interface_f % tree_port
142 self.downstream_poller = zmq.Poller()
143 self.downstream_poller.register(self.downstream, zmq.POLLIN)
144
145 # guess first public IP from socket
146 self.location = socket.gethostbyname_ex(socket.gethostname())[-1][0]
147
148 def __del__(self):
149 self.downstream.close()
150 self.upstream.close()
151 if self.root:
152 self.pub.close()
153 else:
154 self.sub.close()
155 self._ctx.term()
156
157 @property
158 def info(self):
159 """return the connection info for this object's sockets."""
160 return (self.tree_url, self.location)
161
162 def connect(self, peers, btree, pub_url, root_id=0):
163 """connect to peers. `peers` will be a dict of 4-tuples, keyed by name.
164 {peer : (ident, addr, pub_addr, location)}
165 where peer is the name, ident is the XREP identity, addr,pub_addr are the
166 """
167
168 # count the number of children we have
169 self.nchildren = btree.values().count(self.id)
170
171 if self.root:
172 return # root only binds
173
174 root_location = peers[root_id][-1]
175 self.sub.connect(disambiguate_dns_url(pub_url, root_location))
176
177 parent = btree[self.id]
178
179 tree_url, location = peers[parent]
180 self.upstream.connect(disambiguate_dns_url(tree_url, location))
181
182 def serialize(self, obj):
183 """serialize objects.
184
185 Must return list of sendable buffers.
186
187 Can be extended for more efficient/noncopying serialization of numpy arrays, etc.
188 """
189 return [pickle.dumps(obj)]
190
191 def unserialize(self, msg):
192 """inverse of serialize"""
193 return pickle.loads(msg[0])
194
195 def publish(self, value):
196 assert self.root
197 self.pub.send_multipart(self.serialize(value))
198
199 def consume(self):
200 assert not self.root
201 return self.unserialize(self.sub.recv_multipart())
202
203 def send_upstream(self, value, flags=0):
204 assert not self.root
205 self.upstream.send_multipart(self.serialize(value), flags=flags|zmq.NOBLOCK)
206
207 def recv_downstream(self, flags=0, timeout=2000.):
208 # wait for a message, so we won't block if there was a bug
209 self.downstream_poller.poll(timeout)
210
211 msg = self.downstream.recv_multipart(zmq.NOBLOCK|flags)
212 return self.unserialize(msg)
213
214 def reduce(self, f, value, flat=True, all=False):
215 """parallel reduce on binary tree
216
217 if flat:
218 value is an entry in the sequence
219 else:
220 value is a list of entries in the sequence
221
222 if all:
223 broadcast final result to all nodes
224 else:
225 only root gets final result
226 """
227 if not flat:
228 value = reduce(f, value)
229
230 for i in range(self.nchildren):
231 value = f(value, self.recv_downstream())
232
233 if not self.root:
234 self.send_upstream(value)
235
236 if all:
237 if self.root:
238 self.publish(value)
239 else:
240 value = self.consume()
241 return value
242
243 def allreduce(self, f, value, flat=True):
244 """parallel reduce followed by broadcast of the result"""
245 return self.reduce(f, value, flat=flat, all=True)
246
@@ -1,88 +0,0 b''
1 #!/usr/bin/env python
2 """
3 Script for setting up and using [all]reduce with a binary-tree engine interconnect.
4
5 usage: `python bintree_script.py`
6
7 This spanning tree strategy ensures that a single node node mailbox will never
8 receive more that 2 messages at once. This is very important to scale to large
9 clusters (e.g. 1000 nodes) since if you have many incoming messages of a couple
10 of megabytes you might saturate the network interface of a single node and
11 potentially its memory buffers if the messages are not consumed in a streamed
12 manner.
13
14 Note that the AllReduce scheme implemented with the spanning tree strategy
15 impose the aggregation function to be commutative and distributive. It might
16 not be the case if you implement the naive gather / reduce / broadcast strategy
17 where you can reorder the partial data before performing the reduce.
18 """
19 from __future__ import print_function
20
21 from IPython.parallel import Client, Reference
22
23
24 # connect client and create views
25 rc = Client()
26 rc.block=True
27 ids = rc.ids
28
29 root_id = ids[0]
30 root = rc[root_id]
31
32 view = rc[:]
33
34 # run bintree.py script defining bintree functions, etc.
35 exec(compile(open('bintree.py').read(), 'bintree.py', 'exec'))
36
37 # generate binary tree of parents
38 btree = bintree(ids)
39
40 print("setting up binary tree interconnect:")
41 print_bintree(btree)
42
43 view.run('bintree.py')
44 view.scatter('id', ids, flatten=True)
45 view['root_id'] = root_id
46
47 # create the Communicator objects on the engines
48 view.execute('com = BinaryTreeCommunicator(id, root = id==root_id )')
49 pub_url = root.apply_sync(lambda : com.pub_url)
50
51 # gather the connection information into a dict
52 ar = view.apply_async(lambda : com.info)
53 peers = ar.get_dict()
54 # this is a dict, keyed by engine ID, of the connection info for the EngineCommunicators
55
56 # connect the engines to each other:
57 def connect(com, peers, tree, pub_url, root_id):
58 """this function will be called on the engines"""
59 com.connect(peers, tree, pub_url, root_id)
60
61 view.apply_sync(connect, Reference('com'), peers, btree, pub_url, root_id)
62
63 # functions that can be used for reductions
64 # max and min builtins can be used as well
65 def add(a,b):
66 """cumulative sum reduction"""
67 return a+b
68
69 def mul(a,b):
70 """cumulative product reduction"""
71 return a*b
72
73 view['add'] = add
74 view['mul'] = mul
75
76 # scatter some data
77 data = list(range(1000))
78 view.scatter('data', data)
79
80 # perform cumulative sum via allreduce
81 view.execute("data_sum = com.allreduce(add, data, flat=False)")
82 print("allreduce sum of data on all engines:", view['data_sum'])
83
84 # perform cumulative sum *without* final broadcast
85 # when not broadcasting with allreduce, the final result resides on the root node:
86 view.execute("ids_sum = com.reduce(add, id, flat=True)")
87 print("reduce sum of engine ids (not broadcast):", root['ids_sum'])
88 print("partial result on each engine:", view['ids_sum'])
@@ -1,77 +0,0 b''
1 import socket
2
3 import uuid
4 import zmq
5
6 from IPython.parallel.util import disambiguate_url
7
8 class EngineCommunicator(object):
9
10 def __init__(self, interface='tcp://*', identity=None):
11 self._ctx = zmq.Context()
12 self.socket = self._ctx.socket(zmq.XREP)
13 self.pub = self._ctx.socket(zmq.PUB)
14 self.sub = self._ctx.socket(zmq.SUB)
15
16 # configure sockets
17 self.identity = identity or bytes(uuid.uuid4())
18 print(self.identity)
19 self.socket.setsockopt(zmq.IDENTITY, self.identity)
20 self.sub.setsockopt(zmq.SUBSCRIBE, b'')
21
22 # bind to ports
23 port = self.socket.bind_to_random_port(interface)
24 pub_port = self.pub.bind_to_random_port(interface)
25 self.url = interface+":%i"%port
26 self.pub_url = interface+":%i"%pub_port
27 # guess first public IP from socket
28 self.location = socket.gethostbyname_ex(socket.gethostname())[-1][0]
29 self.peers = {}
30
31 def __del__(self):
32 self.socket.close()
33 self.pub.close()
34 self.sub.close()
35 self._ctx.term()
36
37 @property
38 def info(self):
39 """return the connection info for this object's sockets."""
40 return (self.identity, self.url, self.pub_url, self.location)
41
42 def connect(self, peers):
43 """connect to peers. `peers` will be a dict of 4-tuples, keyed by name.
44 {peer : (ident, addr, pub_addr, location)}
45 where peer is the name, ident is the XREP identity, addr,pub_addr are the
46 """
47 for peer, (ident, url, pub_url, location) in peers.items():
48 self.peers[peer] = ident
49 if ident != self.identity:
50 self.sub.connect(disambiguate_url(pub_url, location))
51 if ident > self.identity:
52 # prevent duplicate xrep, by only connecting
53 # engines to engines with higher IDENTITY
54 # a doubly-connected pair will crash
55 self.socket.connect(disambiguate_url(url, location))
56
57 def send(self, peers, msg, flags=0, copy=True):
58 if not isinstance(peers, list):
59 peers = [peers]
60 if not isinstance(msg, list):
61 msg = [msg]
62 for p in peers:
63 ident = self.peers[p]
64 self.socket.send_multipart([ident]+msg, flags=flags, copy=copy)
65
66 def recv(self, flags=0, copy=True):
67 return self.socket.recv_multipart(flags=flags, copy=copy)[1:]
68
69 def publish(self, msg, flags=0, copy=True):
70 if not isinstance(msg, list):
71 msg = [msg]
72 self.pub.send_multipart(msg, copy=copy)
73
74 def consume(self, flags=0, copy=True):
75 return self.sub.recv_multipart(flags=flags, copy=copy)
76
77
@@ -1,43 +0,0 b''
1 import sys
2
3 from IPython.parallel import Client
4
5
6 rc = Client()
7 rc.block=True
8 view = rc[:]
9 view.run('communicator.py')
10 view.execute('com = EngineCommunicator()')
11
12 # gather the connection information into a dict
13 ar = view.apply_async(lambda : com.info)
14 peers = ar.get_dict()
15 # this is a dict, keyed by engine ID, of the connection info for the EngineCommunicators
16
17 # connect the engines to each other:
18 view.apply_sync(lambda pdict: com.connect(pdict), peers)
19
20 # now all the engines are connected, and we can communicate between them:
21
22 def broadcast(client, sender, msg_name, dest_name=None, block=None):
23 """broadcast a message from one engine to all others."""
24 dest_name = msg_name if dest_name is None else dest_name
25 client[sender].execute('com.publish(%s)'%msg_name, block=None)
26 targets = client.ids
27 targets.remove(sender)
28 return client[targets].execute('%s=com.consume()'%dest_name, block=None)
29
30 def send(client, sender, targets, msg_name, dest_name=None, block=None):
31 """send a message from one to one-or-more engines."""
32 dest_name = msg_name if dest_name is None else dest_name
33 def _send(targets, m_name):
34 msg = globals()[m_name]
35 return com.send(targets, msg)
36
37 client[sender].apply_async(_send, targets, msg_name)
38
39 return client[targets].execute('%s=com.recv()'%dest_name, block=None)
40
41
42
43
@@ -1,77 +0,0 b''
1 """A script for watching all traffic on the IOPub channel (stdout/stderr/pyerr) of engines.
2
3 This connects to the default cluster, or you can pass the path to your ipcontroller-client.json
4
5 Try running this script, and then running a few jobs that print (and call sys.stdout.flush),
6 and you will see the print statements as they arrive, notably not waiting for the results
7 to finish.
8
9 You can use the zeromq SUBSCRIBE mechanism to only receive information from specific engines,
10 and easily filter by message type.
11
12 Authors
13 -------
14 * MinRK
15 """
16
17 import sys
18 import json
19 import zmq
20
21 from IPython.kernel.zmq.session import Session
22 from IPython.utils.py3compat import str_to_bytes
23 from jupyter_client.connect import find_connection_file
24
25 def main(connection_file):
26 """watch iopub channel, and print messages"""
27
28 ctx = zmq.Context.instance()
29
30 with open(connection_file) as f:
31 cfg = json.loads(f.read())
32
33 reg_url = cfg['interface']
34 iopub_port = cfg['iopub']
35 iopub_url = "%s:%s"%(reg_url, iopub_port)
36
37 session = Session(key=str_to_bytes(cfg['key']))
38 sub = ctx.socket(zmq.SUB)
39
40 # This will subscribe to all messages:
41 sub.setsockopt(zmq.SUBSCRIBE, b'')
42 # replace with b'' with b'engine.1.stdout' to subscribe only to engine 1's stdout
43 # 0MQ subscriptions are simple 'foo*' matches, so 'engine.1.' subscribes
44 # to everything from engine 1, but there is no way to subscribe to
45 # just stdout from everyone.
46 # multiple calls to subscribe will add subscriptions, e.g. to subscribe to
47 # engine 1's stderr and engine 2's stdout:
48 # sub.setsockopt(zmq.SUBSCRIBE, b'engine.1.stderr')
49 # sub.setsockopt(zmq.SUBSCRIBE, b'engine.2.stdout')
50 sub.connect(iopub_url)
51 while True:
52 try:
53 idents,msg = session.recv(sub, mode=0)
54 except KeyboardInterrupt:
55 return
56 # ident always length 1 here
57 topic = idents[0]
58 if msg['msg_type'] == 'stream':
59 # stdout/stderr
60 # stream names are in msg['content']['name'], if you want to handle
61 # them differently
62 print("%s: %s" % (topic, msg['content']['text']))
63 elif msg['msg_type'] == 'pyerr':
64 # Python traceback
65 c = msg['content']
66 print(topic + ':')
67 for line in c['traceback']:
68 # indent lines
69 print(' ' + line)
70
71 if __name__ == '__main__':
72 if len(sys.argv) > 1:
73 cf = sys.argv[1]
74 else:
75 # This gets the security file for the default profile:
76 cf = find_connection_file('ipcontroller-client.json')
77 main(cf)
@@ -1,67 +0,0 b''
1 """Example of iteration through AsyncMapResults, without waiting for all results
2
3 When you call view.map(func, sequence), you will receive a special AsyncMapResult
4 object. These objects are used to reconstruct the results of the split call.
5 One feature AsyncResults provide is that they are iterable *immediately*, so
6 you can iterate through the actual results as they complete.
7
8 This is useful if you submit a large number of tasks that may take some time,
9 but want to perform logic on elements in the result, or even abort subsequent
10 tasks in cases where you are searching for the first affirmative result.
11
12 By default, the results will match the ordering of the submitted sequence, but
13 if you call `map(...ordered=False)`, then results will be provided to the iterator
14 on a first come first serve basis.
15
16 Authors
17 -------
18 * MinRK
19 """
20 from __future__ import print_function
21
22 import time
23
24 from IPython import parallel
25
26 # create client & view
27 rc = parallel.Client()
28 dv = rc[:]
29 v = rc.load_balanced_view()
30
31 # scatter 'id', so id=0,1,2 on engines 0,1,2
32 dv.scatter('id', rc.ids, flatten=True)
33 print("Engine IDs: ", dv['id'])
34
35 # create a Reference to `id`. This will be a different value on each engine
36 ref = parallel.Reference('id')
37 print("sleeping for `id` seconds on each engine")
38 tic = time.time()
39 ar = dv.apply(time.sleep, ref)
40 for i,r in enumerate(ar):
41 print("%i: %.3f"%(i, time.time()-tic))
42
43 def sleep_here(t):
44 import time
45 time.sleep(t)
46 return id,t
47
48 # one call per task
49 print("running with one call per task")
50 amr = v.map(sleep_here, [.01*t for t in range(100)])
51 tic = time.time()
52 for i,r in enumerate(amr):
53 print("task %i on engine %i: %.3f" % (i, r[0], time.time()-tic))
54
55 print("running with four calls per task")
56 # with chunksize, we can have four calls per task
57 amr = v.map(sleep_here, [.01*t for t in range(100)], chunksize=4)
58 tic = time.time()
59 for i,r in enumerate(amr):
60 print("task %i on engine %i: %.3f" % (i, r[0], time.time()-tic))
61
62 print("running with two calls per task, with unordered results")
63 # We can even iterate through faster results first, with ordered=False
64 amr = v.map(sleep_here, [.01*t for t in range(100,0,-1)], ordered=False, chunksize=2)
65 tic = time.time()
66 for i,r in enumerate(amr):
67 print("slept %.2fs on engine %i: %.3f" % (r[1], r[0], time.time()-tic))
@@ -1,124 +0,0 b''
1 """Example showing how to merge multiple remote data streams.
2 """
3 # Slightly modified version of:
4 # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/511509
5 from __future__ import print_function
6
7 import heapq
8 from IPython.parallel.error import RemoteError
9
10 def mergesort(list_of_lists, key=None):
11 """ Perform an N-way merge operation on sorted lists.
12
13 @param list_of_lists: (really iterable of iterable) of sorted elements
14 (either by naturally or by C{key})
15 @param key: specify sort key function (like C{sort()}, C{sorted()})
16
17 Yields tuples of the form C{(item, iterator)}, where the iterator is the
18 built-in list iterator or something you pass in, if you pre-generate the
19 iterators.
20
21 This is a stable merge; complexity O(N lg N)
22
23 Examples::
24
25 >>> print list(mergesort([[1,2,3,4],
26 ... [2,3.25,3.75,4.5,6,7],
27 ... [2.625,3.625,6.625,9]]))
28 [1, 2, 2, 2.625, 3, 3.25, 3.625, 3.75, 4, 4.5, 6, 6.625, 7, 9]
29
30 # note stability
31 >>> print list(mergesort([[1,2,3,4],
32 ... [2,3.25,3.75,4.5,6,7],
33 ... [2.625,3.625,6.625,9]],
34 ... key=int))
35 [1, 2, 2, 2.625, 3, 3.25, 3.75, 3.625, 4, 4.5, 6, 6.625, 7, 9]
36
37
38 >>> print list(mergesort([[4, 3, 2, 1],
39 ... [7, 6, 4.5, 3.75, 3.25, 2],
40 ... [9, 6.625, 3.625, 2.625]],
41 ... key=lambda x: -x))
42 [9, 7, 6.625, 6, 4.5, 4, 3.75, 3.625, 3.25, 3, 2.625, 2, 2, 1]
43 """
44
45 heap = []
46 for i, itr in enumerate(iter(pl) for pl in list_of_lists):
47 try:
48 item = itr.next()
49 if key:
50 toadd = (key(item), i, item, itr)
51 else:
52 toadd = (item, i, itr)
53 heap.append(toadd)
54 except StopIteration:
55 pass
56 heapq.heapify(heap)
57
58 if key:
59 while heap:
60 _, idx, item, itr = heap[0]
61 yield item
62 try:
63 item = itr.next()
64 heapq.heapreplace(heap, (key(item), idx, item, itr) )
65 except StopIteration:
66 heapq.heappop(heap)
67
68 else:
69 while heap:
70 item, idx, itr = heap[0]
71 yield item
72 try:
73 heapq.heapreplace(heap, (itr.next(), idx, itr))
74 except StopIteration:
75 heapq.heappop(heap)
76
77
78 def remote_iterator(view,name):
79 """Return an iterator on an object living on a remote engine.
80 """
81 view.execute('it%s=iter(%s)'%(name,name), block=True)
82 while True:
83 try:
84 result = view.apply_sync(lambda x: x.next(), Reference('it'+name))
85 # This causes the StopIteration exception to be raised.
86 except RemoteError as e:
87 if e.ename == 'StopIteration':
88 raise StopIteration
89 else:
90 raise e
91 else:
92 yield result
93
94 # Main, interactive testing
95 if __name__ == '__main__':
96
97 from IPython.parallel import Client, Reference
98 rc = Client()
99 view = rc[:]
100 print('Engine IDs:', rc.ids)
101
102 # Make a set of 'sorted datasets'
103 a0 = range(5,20)
104 a1 = range(10)
105 a2 = range(15,25)
106
107 # Now, imagine these had been created in the remote engines by some long
108 # computation. In this simple example, we just send them over into the
109 # remote engines. They will all be called 'a' in each engine.
110 rc[0]['a'] = a0
111 rc[1]['a'] = a1
112 rc[2]['a'] = a2
113
114 # And we now make a local object which represents the remote iterator
115 aa0 = remote_iterator(rc[0],'a')
116 aa1 = remote_iterator(rc[1],'a')
117 aa2 = remote_iterator(rc[2],'a')
118
119 # Let's merge them, both locally and remotely:
120 print('Merge the local datasets:')
121 print(list(mergesort([a0,a1,a2])))
122
123 print('Locally merge the remote sets:')
124 print(list(mergesort([aa0,aa1,aa2])))
@@ -1,40 +0,0 b''
1 """Parallel histogram function"""
2 import numpy
3 from IPython.parallel import Reference
4
5 def phistogram(view, a, bins=10, rng=None, normed=False):
6 """Compute the histogram of a remote array a.
7
8 Parameters
9 ----------
10 view
11 IPython DirectView instance
12 a : str
13 String name of the remote array
14 bins : int
15 Number of histogram bins
16 rng : (float, float)
17 Tuple of min, max of the range to histogram
18 normed : boolean
19 Should the histogram counts be normalized to 1
20 """
21 nengines = len(view.targets)
22
23 # view.push(dict(bins=bins, rng=rng))
24 with view.sync_imports():
25 import numpy
26 rets = view.apply_sync(lambda a, b, rng: numpy.histogram(a,b,rng), Reference(a), bins, rng)
27 hists = [ r[0] for r in rets ]
28 lower_edges = [ r[1] for r in rets ]
29 # view.execute('hist, lower_edges = numpy.histogram(%s, bins, rng)' % a)
30 lower_edges = view.pull('lower_edges', targets=0)
31 hist_array = numpy.array(hists).reshape(nengines, -1)
32 # hist_array.shape = (nengines,-1)
33 total_hist = numpy.sum(hist_array, 0)
34 if normed:
35 total_hist = total_hist/numpy.sum(total_hist,dtype=float)
36 return total_hist, lower_edges
37
38
39
40
@@ -1,65 +0,0 b''
1 """Calculate statistics on the digits of pi in parallel.
2
3 This program uses the functions in :file:`pidigits.py` to calculate
4 the frequencies of 2 digit sequences in the digits of pi. The
5 results are plotted using matplotlib.
6
7 To run, text files from http://www.super-computing.org/
8 must be installed in the working directory of the IPython engines.
9 The actual filenames to be used can be set with the ``filestring``
10 variable below.
11
12 The dataset we have been using for this is the 200 million digit one here:
13 ftp://pi.super-computing.org/.2/pi200m/
14
15 and the files used will be downloaded if they are not in the working directory
16 of the IPython engines.
17 """
18 from __future__ import print_function
19
20 from IPython.parallel import Client
21 from matplotlib import pyplot as plt
22 import numpy as np
23 from pidigits import *
24 from timeit import default_timer as clock
25
26 # Files with digits of pi (10m digits each)
27 filestring = 'pi200m.ascii.%(i)02dof20'
28 files = [filestring % {'i':i} for i in range(1,21)]
29
30 # Connect to the IPython cluster
31 c = Client()
32 c[:].run('pidigits.py')
33
34 # the number of engines
35 n = len(c)
36 id0 = c.ids[0]
37 v = c[:]
38 v.block=True
39 # fetch the pi-files
40 print("downloading %i files of pi"%n)
41 v.map(fetch_pi_file, files[:n])
42 print("done")
43
44 # Run 10m digits on 1 engine
45 t1 = clock()
46 freqs10m = c[id0].apply_sync(compute_two_digit_freqs, files[0])
47 t2 = clock()
48 digits_per_second1 = 10.0e6/(t2-t1)
49 print("Digits per second (1 core, 10m digits): ", digits_per_second1)
50
51
52 # Run n*10m digits on all engines
53 t1 = clock()
54 freqs_all = v.map(compute_two_digit_freqs, files[:n])
55 freqs150m = reduce_freqs(freqs_all)
56 t2 = clock()
57 digits_per_second8 = n*10.0e6/(t2-t1)
58 print("Digits per second (%i engines, %i0m digits): "%(n,n), digits_per_second8)
59
60 print("Speedup: ", digits_per_second8/digits_per_second1)
61
62 plot_two_digit_freqs(freqs150m)
63 plt.title("2 digit sequences in %i0m digits of pi"%n)
64 plt.show()
65
@@ -1,162 +0,0 b''
1 """Compute statistics on the digits of pi.
2
3 This uses precomputed digits of pi from the website
4 of Professor Yasumasa Kanada at the University of
5 Tokoyo: http://www.super-computing.org/
6
7 Currently, there are only functions to read the
8 .txt (non-compressed, non-binary) files, but adding
9 support for compression and binary files would be
10 straightforward.
11
12 This focuses on computing the number of times that
13 all 1, 2, n digits sequences occur in the digits of pi.
14 If the digits of pi are truly random, these frequencies
15 should be equal.
16 """
17
18 # Import statements
19 from __future__ import division, with_statement
20
21 import numpy as np
22 from matplotlib import pyplot as plt
23
24 try : #python2
25 from urllib import urlretrieve
26 except ImportError : #python3
27 from urllib.request import urlretrieve
28
29 # Top-level functions
30
31 def fetch_pi_file(filename):
32 """This will download a segment of pi from super-computing.org
33 if the file is not already present.
34 """
35 import os, urllib
36 ftpdir="ftp://pi.super-computing.org/.2/pi200m/"
37 if os.path.exists(filename):
38 # we already have it
39 return
40 else:
41 # download it
42 urlretrieve(ftpdir+filename,filename)
43
44 def compute_one_digit_freqs(filename):
45 """
46 Read digits of pi from a file and compute the 1 digit frequencies.
47 """
48 d = txt_file_to_digits(filename)
49 freqs = one_digit_freqs(d)
50 return freqs
51
52 def compute_two_digit_freqs(filename):
53 """
54 Read digits of pi from a file and compute the 2 digit frequencies.
55 """
56 d = txt_file_to_digits(filename)
57 freqs = two_digit_freqs(d)
58 return freqs
59
60 def reduce_freqs(freqlist):
61 """
62 Add up a list of freq counts to get the total counts.
63 """
64 allfreqs = np.zeros_like(freqlist[0])
65 for f in freqlist:
66 allfreqs += f
67 return allfreqs
68
69 def compute_n_digit_freqs(filename, n):
70 """
71 Read digits of pi from a file and compute the n digit frequencies.
72 """
73 d = txt_file_to_digits(filename)
74 freqs = n_digit_freqs(d, n)
75 return freqs
76
77 # Read digits from a txt file
78
79 def txt_file_to_digits(filename, the_type=str):
80 """
81 Yield the digits of pi read from a .txt file.
82 """
83 with open(filename, 'r') as f:
84 for line in f.readlines():
85 for c in line:
86 if c != '\n' and c!= ' ':
87 yield the_type(c)
88
89 # Actual counting functions
90
91 def one_digit_freqs(digits, normalize=False):
92 """
93 Consume digits of pi and compute 1 digit freq. counts.
94 """
95 freqs = np.zeros(10, dtype='i4')
96 for d in digits:
97 freqs[int(d)] += 1
98 if normalize:
99 freqs = freqs/freqs.sum()
100 return freqs
101
102 def two_digit_freqs(digits, normalize=False):
103 """
104 Consume digits of pi and compute 2 digits freq. counts.
105 """
106 freqs = np.zeros(100, dtype='i4')
107 last = next(digits)
108 this = next(digits)
109 for d in digits:
110 index = int(last + this)
111 freqs[index] += 1
112 last = this
113 this = d
114 if normalize:
115 freqs = freqs/freqs.sum()
116 return freqs
117
118 def n_digit_freqs(digits, n, normalize=False):
119 """
120 Consume digits of pi and compute n digits freq. counts.
121
122 This should only be used for 1-6 digits.
123 """
124 freqs = np.zeros(pow(10,n), dtype='i4')
125 current = np.zeros(n, dtype=int)
126 for i in range(n):
127 current[i] = next(digits)
128 for d in digits:
129 index = int(''.join(map(str, current)))
130 freqs[index] += 1
131 current[0:-1] = current[1:]
132 current[-1] = d
133 if normalize:
134 freqs = freqs/freqs.sum()
135 return freqs
136
137 # Plotting functions
138
139 def plot_two_digit_freqs(f2):
140 """
141 Plot two digits frequency counts using matplotlib.
142 """
143 f2_copy = f2.copy()
144 f2_copy.shape = (10,10)
145 ax = plt.matshow(f2_copy)
146 plt.colorbar()
147 for i in range(10):
148 for j in range(10):
149 plt.text(i-0.2, j+0.2, str(j)+str(i))
150 plt.ylabel('First digit')
151 plt.xlabel('Second digit')
152 return ax
153
154 def plot_one_digit_freqs(f1):
155 """
156 Plot one digit frequency counts using matplotlib.
157 """
158 ax = plt.plot(f1,'bo-')
159 plt.title('Single digit counts in pi')
160 plt.xlabel('Digit')
161 plt.ylabel('Count')
162 return ax
@@ -1,146 +0,0 b''
1 # <nbformat>2</nbformat>
2
3 # <markdowncell>
4
5 # # Eigenvalue distribution of Gaussian orthogonal random matrices
6
7 # <markdowncell>
8
9 # The eigenvalues of random matrices obey certain statistical laws. Here we construct random matrices
10 # from the Gaussian Orthogonal Ensemble (GOE), find their eigenvalues and then investigate the nearest
11 # neighbor eigenvalue distribution $\rho(s)$.
12
13 # <codecell>
14
15 from rmtkernel import ensemble_diffs, normalize_diffs, GOE
16 import numpy as np
17 from IPython.parallel import Client
18
19 # <markdowncell>
20
21 # ## Wigner's nearest neighbor eigenvalue distribution
22
23 # <markdowncell>
24
25 # The Wigner distribution gives the theoretical result for the nearest neighbor eigenvalue distribution
26 # for the GOE:
27 #
28 # $$\rho(s) = \frac{\pi s}{2} \exp(-\pi s^2/4)$$
29
30 # <codecell>
31
32 def wigner_dist(s):
33 """Returns (s, rho(s)) for the Wigner GOE distribution."""
34 return (np.pi*s/2.0) * np.exp(-np.pi*s**2/4.)
35
36 # <codecell>
37
38 def generate_wigner_data():
39 s = np.linspace(0.0,4.0,400)
40 rhos = wigner_dist(s)
41 return s, rhos
42
43 # <codecell>
44
45 s, rhos = generate_wigner_data()
46
47 # <codecell>
48
49 plot(s, rhos)
50 xlabel('Normalized level spacing s')
51 ylabel('Probability $\rho(s)$')
52
53 # <markdowncell>
54
55 # ## Serial calculation of nearest neighbor eigenvalue distribution
56
57 # <markdowncell>
58
59 # In this section we numerically construct and diagonalize a large number of GOE random matrices
60 # and compute the nerest neighbor eigenvalue distribution. This comptation is done on a single core.
61
62 # <codecell>
63
64 def serial_diffs(num, N):
65 """Compute the nearest neighbor distribution for num NxX matrices."""
66 diffs = ensemble_diffs(num, N)
67 normalized_diffs = normalize_diffs(diffs)
68 return normalized_diffs
69
70 # <codecell>
71
72 serial_nmats = 1000
73 serial_matsize = 50
74
75 # <codecell>
76
77 %timeit -r1 -n1 serial_diffs(serial_nmats, serial_matsize)
78
79 # <codecell>
80
81 serial_diffs = serial_diffs(serial_nmats, serial_matsize)
82
83 # <markdowncell>
84
85 # The numerical computation agrees with the predictions of Wigner, but it would be nice to get more
86 # statistics. For that we will do a parallel computation.
87
88 # <codecell>
89
90 hist_data = hist(serial_diffs, bins=30, normed=True)
91 plot(s, rhos)
92 xlabel('Normalized level spacing s')
93 ylabel('Probability $P(s)$')
94
95 # <markdowncell>
96
97 # ## Parallel calculation of nearest neighbor eigenvalue distribution
98
99 # <markdowncell>
100
101 # Here we perform a parallel computation, where each process constructs and diagonalizes a subset of
102 # the overall set of random matrices.
103
104 # <codecell>
105
106 def parallel_diffs(rc, num, N):
107 nengines = len(rc.targets)
108 num_per_engine = num/nengines
109 print "Running with", num_per_engine, "per engine."
110 ar = rc.apply_async(ensemble_diffs, num_per_engine, N)
111 diffs = np.array(ar.get()).flatten()
112 normalized_diffs = normalize_diffs(diffs)
113 return normalized_diffs
114
115 # <codecell>
116
117 client = Client()
118 view = client[:]
119 view.run('rmtkernel.py')
120 view.block = False
121
122 # <codecell>
123
124 parallel_nmats = 40*serial_nmats
125 parallel_matsize = 50
126
127 # <codecell>
128
129 %timeit -r1 -n1 parallel_diffs(view, parallel_nmats, parallel_matsize)
130
131 # <codecell>
132
133 pdiffs = parallel_diffs(view, parallel_nmats, parallel_matsize)
134
135 # <markdowncell>
136
137 # Again, the agreement with the Wigner distribution is excellent, but now we have better
138 # statistics.
139
140 # <codecell>
141
142 hist_data = hist(pdiffs, bins=30, normed=True)
143 plot(s, rhos)
144 xlabel('Normalized level spacing s')
145 ylabel('Probability $P(s)$')
146
This diff has been collapsed as it changes many lines, (821 lines changed) Show them Hide them
@@ -1,821 +0,0 b''
1 {
2 "cells": [
3 {
4 "cell_type": "markdown",
5 "metadata": {},
6 "source": [
7 "# Eigenvalue distribution of Gaussian orthogonal random matrices"
8 ]
9 },
10 {
11 "cell_type": "markdown",
12 "metadata": {},
13 "source": [
14 "The eigenvalues of random matrices obey certain statistical laws. Here we construct random matrices \n",
15 "from the Gaussian Orthogonal Ensemble (GOE), find their eigenvalues and then investigate the nearest\n",
16 "neighbor eigenvalue distribution $\\rho(s)$."
17 ]
18 },
19 {
20 "cell_type": "code",
21 "execution_count": 1,
22 "metadata": {
23 "collapsed": false
24 },
25 "outputs": [],
26 "source": [
27 "from rmtkernel import ensemble_diffs, normalize_diffs, GOE\n",
28 "import numpy as np\n",
29 "from IPython.parallel import Client"
30 ]
31 },
32 {
33 "cell_type": "markdown",
34 "metadata": {},
35 "source": [
36 "## Wigner's nearest neighbor eigenvalue distribution"
37 ]
38 },
39 {
40 "cell_type": "markdown",
41 "metadata": {},
42 "source": [
43 "The Wigner distribution gives the theoretical result for the nearest neighbor eigenvalue distribution\n",
44 "for the GOE:\n",
45 "\n",
46 "$$\\rho(s) = \\frac{\\pi s}{2} \\exp(-\\pi s^2/4)$$"
47 ]
48 },
49 {
50 "cell_type": "code",
51 "execution_count": 2,
52 "metadata": {
53 "collapsed": true
54 },
55 "outputs": [],
56 "source": [
57 "def wigner_dist(s):\n",
58 " \"\"\"Returns (s, rho(s)) for the Wigner GOE distribution.\"\"\"\n",
59 " return (np.pi*s/2.0) * np.exp(-np.pi*s**2/4.)"
60 ]
61 },
62 {
63 "cell_type": "code",
64 "execution_count": 3,
65 "metadata": {
66 "collapsed": true
67 },
68 "outputs": [],
69 "source": [
70 "def generate_wigner_data():\n",
71 " s = np.linspace(0.0,4.0,400)\n",
72 " rhos = wigner_dist(s)\n",
73 " return s, rhos"
74 ]
75 },
76 {
77 "cell_type": "code",
78 "execution_count": 4,
79 "metadata": {
80 "collapsed": false
81 },
82 "outputs": [],
83 "source": [
84 "s, rhos = generate_wigner_data()"
85 ]
86 },
87 {
88 "cell_type": "code",
89 "execution_count": 17,
90 "metadata": {
91 "collapsed": false
92 },
93 "outputs": [
94 {
95 "data": {
96 "text/plain": [
97 "&lt;matplotlib.text.Text at 0x3828790&gt;"
98 ]
99 },
100 "execution_count": 17,
101 "metadata": {},
102 "output_type": "execute_result"
103 },
104 {
105 "data": {
106 "image/png": [
107 "iVBORw0KGgoAAAANSUhEUgAAAYwAAAEMCAYAAADXiYGSAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\n",
108 "AAALEgAACxIB0t1+/AAAIABJREFUeJzt3XlcVPX+x/HXqLimmKJpqSBJASIwKqBpOqUCSmZmpdav\n",
109 "TK3wdhO1rKxribabFqZoZLfF1JtmpUldEUtEMxCFcs8VlzSviuDC4ijn98eJCQRkBmbmzAyf5+PB\n",
110 "Q4Y5nvPm9Gg+nu+qUxRFQQghhKhCHa0DCCGEcA5SMIQQQphFCoYQQgizSMEQQghhFikYQgghzCIF\n",
111 "QwghhFk0KRipqan4+fnh4+PD3Llzy71fUFDAqFGj0Ov19O3bl1WrVmmQUgghRGk6LeZh6PV65syZ\n",
112 "g6enJxEREWzatAkPDw/T+x9++CHbt29n/vz5HDlyhLvvvpsDBw6g0+nsHVUIIcRf7P6EkZeXB0Cf\n",
113 "Pn3w9PQkPDyc9PT0Mse4u7tz4cIFjEYjOTk5NG7cWIqFEEJozO4FIyMjA19fX9Nrf39/0tLSyhwz\n",
114 "cuRIrl69ioeHB71792bJkiX2jimEEOIa9bQOUJF58+ZRr149Tp48yY4dO4iKiuLIkSPUqVO2vslT\n",
115 "hxBCVE91eiPs/oQREhLC3r17Ta937dpFjx49yhyTmprKI488QuPGjQkLC+Pmm29m3759FZ5PURSH\n",
116 "/5o2bZrmGSSnZJSckrPkq7rsXjDc3d0BtShkZ2eTnJxMWFhYmWP69evH6tWrKS4u5tChQ+Tk5JRp\n",
117 "xhJCCGF/mjRJxcXFER0djdFoJCYmBg8PDxISEgCIjo5mxIgR7N69m+7du9OqVSvmzJmjRUwhhBCl\n",
118 "aFIw+vbty549e8r8LDo62vS9u7u7SxUJg8GgdQSzSE7rcYaMIDmtzVlyVpcm8zCsRafT1ag9Tggh\n",
119 "aqPqfnbK0iBCCCHMIgVDCCGEWaRgCCGEMIsUDCGEEGZxyJne4vqKiiA9Hdavh8xMuHwZiouhQwe4\n",
120 "4w7o2xe8vbVOKYRwNTJKyokUFEBCAsycCbfcAnfdBWFh0Lgx6HRw4AD8/DP89BPo9TB5MvTrp74n\n",
121 "hBAlqvvZKQXDSaxbB6NGQWgoTJsGwcGVH1tUBEuWwOzZcPPN8Omn0K6d/bIKIRybDKt1UYoC770H\n",
122 "jz4KixfDt99ev1gANGgAY8bAb7+pzVPdusHy5fbJK4RwXfKE4cAKCuDJJ2H3brVQeHpW7zxbtsD/\n",
123 "/R/07q02abm5WTenEMK5yBOGizEa4b771D83bap+sQC1GSsrC/73P3joIbXJSgghLCUFwwEpCjz9\n",
124 "NNSrp/ZFNG5c83M2aQLffAN16qiFqKCg5ucUQtQuUjAc0MyZsHUrfPmlWjSspX59WLYMbrwR7rkH\n",
125 "8vOtd24hhOuTPgwH89VX8Nxz8Msv6tBZW7h6FR55BOrWVTvSZditELWLDKt1AYcOqfMqkpOrHglV\n",
126 "U/n5cOedMHKkOl9DCFF7SMFwcoqiTrIbNMh+H+BHj6oF6rPPICLCPtcUQmhPRkk5uYUL4dIlmDTJ\n",
127 "ftfs0EGdn/HYY+oscSGEuB55wnAAR4+qk+tSUqBzZ/tff8ECmDcP0tKgaVP7X18IYV/SJOWkFEVt\n",
128 "hurdG/71L+0yPPGE2vn98cfaZBBC2I9TNUmlpqbi5+eHj48Pc+fOLff+rFmz0Ov16PV6unTpQr16\n",
129 "9cjNzdUgqe19/TWcOAEvvKBdBp0O4uLgxx9hzRrtcgghHJsmTxh6vZ45c+bg6elJREQEmzZtwsPD\n",
130 "o8JjExMTiYuLY926deXec/YnjCtX1CaouXMhPFzrNGrBGD0aduwAd3et0wghbMVpnjDy8vIA6NOn\n",
131 "D56enoSHh5Oenl7p8UuXLmXkyJH2imdXn32mzrUYMEDrJCp7j9ISQjgXuxeMjIwMfH19Ta/9/f1J\n",
132 "S0ur8Nj8/HySkpIYNmyYveLZTUEBTJ8Ob73lWBPnZs5U54GsXat1EiGEo3HoHfdWr15N7969ad68\n",
133 "eaXHxMbGmr43GAwYDAbbB7OC+HgICVHnQTiSZs3UIb5PPAHbt0vTlBCuICUlhZSUlBqfx+59GHl5\n",
134 "eRgMBrKysgAYP348kZGRREVFlTt26NChDB8+nBEjRlR4Lmftw8jNhdtuU4fR+vtrnaZiTz2lLoMe\n",
135 "H691EiGEtTlNH4b7X/9kTU1NJTs7m+TkZMIq+Gd2Xl4eqampDBkyxN4Rbe799yEqynGLBcDbb6vr\n",
136 "Wu3cqXUSIYSj0KRJKi4ujujoaIxGIzExMXh4eJCQkABAdHQ0ACtXriQiIoJGjRppEdFmCgrUiXKb\n",
137 "Nmmd5PpatIBXXlFnnq9d61j9LEIIbcjEPTv7+GNYuRISE7VOUjWjEQID4d131eXQhRCuwWmapGoz\n",
138 "RYE5c2DiRK2TmMfNTd1P/Lnn4PJlrdMIIbQmBcOOfvrp71VpncXAgeDtDfPna51ECKE1aZKyo8GD\n",
139 "4d574ckntU5imd27oW9f2LMHKpmQL4RwIrL4oIPbvx969YIjR8AZ+/HHj1efjubN0zqJEKKmpGA4\n",
140 "uJgYdenwN97QOkn1nD0Lvr7aLcEuhLAeKRgOLDdX7QfYscN2+3TbQ8mKtqtXa51ECFETMkrKgX3y\n",
141 "idp57MzFAmDcOMjKgowMrZMIIbQgTxg2pijqMiBffAE9emidpubi4+GHH+D777VOIoSoLnnCcFBp\n",
142 "aVCvnuMtMlhdJYsSXmdFeiGEi5KCYWNffAGPPuo6S2s0aAAvvwylFgkWQtQS0iRlQ0VFar9FZiZ0\n",
143 "6KB1GuspKlKb2ZYtc41mNiFqG2mSckDffw9durhWsQB5yhCitpKCYUOLFsFjj2mdwjZGj1Znfv/y\n",
144 "i9ZJhBD2Ik1SNnLmDHTqBEePqrvYuaKPPoIVK2Q7VyGcjTRJOZhly2DQINctFgCPP64uefLzz1on\n",
145 "EULYgxQMG3Hl5qgS9evDv/4F06ZpnUQIYQ9SMGzg99/Vpqj+/bVOYnujRsGhQ7Bxo9ZJhBC2JgXD\n",
146 "Br74Ah5+WJ2w5+rc3OCll+Ctt7ROIoSwNen0tjJFgY4d1W1Yg4O1TmMfhYXq75ycDAEBWqcRQlTF\n",
147 "qTq9U1NT8fPzw8fHh7lz51Z4TEZGBiEhIfj5+WEwGOwbsAYyM9V5CkFBWiexn4YN4ZlnYNYsrZMI\n",
148 "IWxJkycMvV7PnDlz8PT0JCIigk2bNuFRais3RVEIDAzk/fffp3///pw5c6bM+yUc8QnjlVfU/a/f\n",
149 "eUfrJPaVk6MOI3b2JdyFqA2c5gkjLy8PgD59+uDp6Ul4eDjp16xkt3XrVgIDA+n/V69xRcXCUa1c\n",
150 "Cffdp3UK+2vRQl0za84crZMIIWzF7t2yGRkZ+Pr6ml77+/uTlpZGVFSU6WdJSUnodDruvPNOmjdv\n",
151 "zjPPPENERESF54sttT6FwWDQtPnqwAF1wp6rrExrqUmToFs3mDrVteefCOFsUlJSSElJqfF5HHIc\n",
152 "T2FhIb/++ivr1q0jPz+fAQMGsHPnThpVsBl2rAMtaLRqFQweDHVq6dgzLy8ID1dngE+erHUaIUSJ\n",
153 "a/8xPX369Gqdx+4fbSEhIezdu9f0eteuXfS4ZsnTnj17MnDgQNq0aYO3tzfdu3cnNTXV3lEtVlub\n",
154 "o0p7/nm1WeryZa2TCCGsze4Fw93dHVBHSmVnZ5OcnEzYNW04PXr0YMOGDeTn55OTk0NWVha9evWy\n",
155 "d1SL/O9/aofv3XdrnURbXbuqS59/+aXWSYQQ1qZJk1RcXBzR0dEYjUZiYmLw8PAgISEBgOjoaFq2\n",
156 "bMno0aPp3r07rVq1YsaMGdxwww1aRDXb6tVqc0zDhlon0d7zz8MLL7jWxlFCCJm4ZzWDB8PIkeoM\n",
157 "79pOUdR5KDNnQmSk1mmEENeq7menFAwruHgRbr5ZXT+qeXOt0ziGRYvg88/hxx+1TiKEuJbTzMNw\n",
158 "RWvXqluVSrH424gRsG+fOvNdCOEapGBYgYyOKq9+fZgwAd59V+skQghrkSapGjIaoU0b+O03aNdO\n",
159 "0ygO5/x5dVHCbdvUORpCCMcgTVIa2bgRbr1VikVFmjWDsWPh/fe1TiKEsAYpGDUkzVHXN2GCuj9I\n",
160 "To7WSYQQNSUFowYURS0YQ4ZoncRx3XKLOuR44UKtkwghakoKRg3s2aOuG+Xvr3USxzZpEsydK8uF\n",
161 "COHspGDUQHIyDBggs5mrEhysLhfy1VdaJxFC1IQUjBooKRiias8+C++9pzbjCSGckxSMarp8GVJT\n",
162 "ZbFBcw0apM6Id4JFh4UQlZCCUU1paeDjA060GaCm6tRR+zLee0/rJEKI6pKCUU3r1klzlKUeeww2\n",
163 "b4b9+7VOIoSoDosLxoULF9i1axcpKSn89ttvFBQU2CKXw5P+C8s1bgzR0bLvtxDOyuylQVasWMHu\n",
164 "3bs5ceIEXl5etGnThuPHj3P06FGaNWvGgAEDKt1321a0WhokNxfat4fTp2X/C0udPAmdO6v7n7do\n",
165 "oXUaIWonmy1vfvnyZT755BOCg4PLbaVaWmJiIsePH2fcuHEWh6gurQrGt9/Chx9CUpLdL+0SRo0C\n",
166 "Pz+YMkXrJELUTg6xH4aiKOjsOClBq4Lx9NPg7Q2TJ9v90i7h118hKgoOH1ZXtRVC2JfdFh+8cOEC\n",
167 "e/fuBdSnj2tD1AbSf1EzwcHg6wvLl2udRAhhCYsLxrfffsvevXvp0aMHEyZMYOXKlRZfNDU1FT8/\n",
168 "P3x8fJg7d26591NSUnB3d0ev16PX63n99dctvoatZGdDXh506aJ1EucmE/mEcD4WF4yioiLc3Nxo\n",
169 "1qwZCxYs4IYbbrD4ohMmTCAhIYF169YRHx/PmTNnyh3Tt29fsrKyyMrKYurUqRZfw1bWrYP+/dV5\n",
170 "BaL6Bg6E/HzYsEHrJEIIc1X5sVdUVMS5c+dMryMiIti5cyfx8fHMmjWLrKws03tHjhyp8oJ5eXkA\n",
171 "9OnTB09PT8LDw0lPTy93nNYbI1VGmqOsQybyCeF8qiwYDRo0YMOGDaxYsQKj0UiHDh148cUX8fHx\n",
172 "oV+/fowdO5bc3FxiY2PZtWtXlRfMyMjA19fX9Nrf35+0tLQyx+h0OjZv3kxwcDDPPvssBw8erMav\n",
173 "Zn3FxfDjj1IwrOXRR9UZ8/v2aZ1ECGGOeuYcdN9993HkyBHefvttzp49S2FhIUajkXPnztGwYUP8\n",
174 "/f155pln8LDSOhldu3bl2LFjuLm58fnnnzNhwgQSExMrPDY2Ntb0vcFgwGAwWCVDRbKyoFUr2V3P\n",
175 "WkpP5IuP1zqNEK4rJSWFlJSUGp+n2sNqi4uLqVONhvy8vDwMBoOpKWv8+PFERkYSFRVV4fGKotCm\n",
176 "TRuOHj1KgwYNyrxn72G1b78NJ07ABx/Y7ZIu7+RJdT+RgwdlIp8Q9mK3YbV5eXn8+9//ZuXKleTn\n",
177 "51t8QXd3d0AdKZWdnU1ycjJhYWFljjl16pTpl1m9ejWBgYHlioUWZP0o62vbVt2xMCFB6yRCiKpY\n",
178 "XDDeeustGjVqxO+//86DDz5oVr/FteLi4oiOjqZ///48/fTTeHh4kJCQQMJfnxorVqygS5cuBAcH\n",
179 "s2LFCmbPnm3xNaytoADS06FvX62TuJ5Jk2DePNmRTwhHZ3GT1JIlS3jkkUcAtVlq3rx5xMTE2CRc\n",
180 "VezZJLV2LcyYAZs22eVytU7//uqSIY8+qnUSIVyf3Zqk6taty2uvvcaxY8cAqFfPrH5zpyfDaW1L\n",
181 "JvIJ4fgsLhgjRowgNDSUF198kYEDB9KxY0db5HI40n9hW5GRUFgIVhjIIYSwEasuPmhv9mqSKlnO\n",
182 "PCcH3Nxsfrla66OPIDERvvtO6yRCuDa7NUldvHiRAwcOUFxczMaNG1m/fr3FF3U2mzdDaKgUC1uT\n",
183 "iXxCODaLOyBef/11GjduzO7duwHw8PDgrrvusnowR7JxI/TurXUK19eokTqRLy4O5s/XOo0Q4lpm\n",
184 "NUn95z//ITQ0lFtvvZWMjAxCQkIA9WmjTp06NG7c2OZBK2KvJqnevSE2Vh3JI2zrzz/VzZUOHICW\n",
185 "LbVOI4RrsukGSgMHDsTb25vff/+dnJwcQkJCGDZsGD179qRp06bVCmwN9igYhYXg4aF+kFVjYV5R\n",
186 "DaNHg48PvPyy1kmEcE02LRiXLl2iSZMmAOTn57N161a2bNlCRkYGDRo0YNGiRZYntgJ7FIyNG+G5\n",
187 "52DLFpteRpSyfbs6aurwYXCACf5CuByH2KLV3uxRMN58E86ckWW47W3AALUT/LHHtE4ihOux2yip\n",
188 "2mbjRrjzTq1T1D4ykU8IxyMF4zquXoVffpERUlqIiFDXlpKJfEI4DosKxh9//GGrHA5p+3Z1NdVW\n",
189 "rbROUvvIjnxCOB6LCsaAAQMYOHAgy5cvx2g02iqTw9i0SZqjtPR//6euEPz771onEUKAhQVj9+7d\n",
190 "vPLKK6xduxYfHx/Gjx9PZmamrbJpTibsaatRIxg3Tp3IJ4TQXrVHSa1Zs4YxY8Zw9epVOnXqxOzZ\n",
191 "s+nRo4e1812XLUdJKQrcfLO6LEgtWV/RIZVM5Nu/X50PI4SoObuMkjp+/DhvvPEGAQEBfPLJJ3z2\n",
192 "2WecPHmS+fPnM2bMGIsv7sgOHoS6dcHLS+sktVubNjB0qOzIJ4QjsKhgDBw4kEaNGpGSksLy5csJ\n",
193 "Dw+nTp06BAUFMW7cOFtl1ETJcFqdTuskYtIkiI+HoiKtkwhRu1nUJLVlyxZCQ0Or/Jm92LJJauxY\n",
194 "6NoV/vlPm5xeWCg8XO0El4l8QtScXZqkKnqKiI6OtviizkAm7DkWmcgnhPbMKhgZGRnEx8dz+vRp\n",
195 "5s+fT3x8PPHx8cTGxnLjjTdafNHU1FT8/Pzw8fFh7ty5171uvXr1+Oabbyy+Rk38+SecPg0BAXa9\n",
196 "rLiOiAgwGqEWbL8ihMMyq2Dk5eVx7NgxjEYjx44d4/jx4xw/fpw2bdrw6aefWnzRCRMmkJCQwLp1\n",
197 "64iPj+fMmTPljrl69SovvvgikZGRdlnCvLRNm6BXL3XymHAMOh1MnCgT+YTQkkV9GPv27eO2226r\n",
198 "0QXz8vIwGAxkZWUBEBMTQ0REBFFRUWWOi4uLo379+mRkZHDPPfcwbNiw8uFt1IcxcaI6OmfKFKuf\n",
199 "WtRAQYE6am3DBvD11TqNEM7Lpn0YJR/m4eHhdOzYscyXt7e3RRfMyMjAt9T/7f7+/qSlpZU55o8/\n",
200 "/mDVqlX84x//ANRfzp6k/8IxyUQ+IbRl1hatS5YsAWDr1q02DVNi4sSJvP3226YqeL1KGBsba/re\n",
201 "YDBgMBhqdO3z59WlKLp3r9FphI08/bT6dPH66zKRTwhzpaSkkGKFlTztvh/GtU1S48ePJzIyskyT\n",
202 "lLe3t6lInDlzhsaNG7Nw4ULuvffeMueyRZNUUpK6B8aGDVY9rbCisWPV2fdTp2qdRAjnZNMNlE6d\n",
203 "OlVhs5CiKOh0Olq3bm3RRfV6PXPmzKFDhw5ERkayadMmPCr55+Lo0aMZPHgw999/f/nwNigYU6eq\n",
204 "QzffeMOqpxVWtHOnOi9DduQTonqq+9lpVpNU779W4Lu2aJQUjH379ll00bi4OKKjozEajcTExODh\n",
205 "4UHCX2s/aD2v4+ef4YUXNI0gqhAQAF26wJdfwqhRWqcRovYw6wkjKiqK77//Hi8vr3KVSafTcfjw\n",
206 "YZuGrIy1nzCuXoXmzeHIEWjRwmqnFTaQlKQW9l9/leVbhLCUTZuk8vLycHd3r3C+hE6no2XLlhZf\n",
207 "2BqsXTB27IBhw8DCByahAUVRnzQ++AD69dM6jRDOxaZNUu7u7gCV9jO4ivR0CAvTOoUwh0739458\n",
208 "UjCEsA+L5zKfOXOGJUuWsHTpUs6ePWuLTJqRguFcHnkEtm6FPXu0TiJE7WBRwViyZAk9e/bkl19+\n",
209 "YfPmzfTs2dM0R8MVbNkiBcOZNGoE//iHTOQTwl4smocRHBzMmjVraNOmDaAOt42IiODXX3+1WcDr\n",
210 "sWYfxsWLcNNNcO4c1K9vlVMKOzh1Sp3IJzvyCWE+uyxv3qJFCwoKCkyvCwoKaOEiw4m2bVOHakqx\n",
211 "cC433aQOVPjwQ62TCOH6zOr0Hj9+PACtWrWiW7du3HnnnSiKwqZNmxgwYIBNA9qL9F84r0mToH9/\n",
212 "eP55mcgnhC2ZVTC6detmmrQ3cOBA08/vv/9+uy8MaCtbtkAFk8mFE+jcGYKC4D//gccf1zqNEK7L\n",
213 "7mtJWZM1+zDat4eUFLj1VqucTthZUpL6hPHbbzKRT4iq2HTiXomCggLWrl1LUlIS586dMz1dLF26\n",
214 "1OILW4O1CsaJExAYqO6yJx82zqlkIt+cOWrzlBCicnbp9J46dSobN24kKSkJg8HA8ePH8fLysvii\n",
215 "jiY9HUJDpVg4M53u732/hRC2YdETRteuXcnMzKRz587s2rWLvLw8+vfvT0ZGhi0zVspaTxhTpqhj\n",
216 "+qdNs0IooZnCQnVHvp9+An9/rdMI4bjs8oTh5uYGQPfu3UlMTOTUqVMUFhZafFFHIxP2XEPDhjKR\n",
217 "TwhbsugJY9GiRQwePJgjR44wZcoU/vjjD2bMmMHQoUNtmbFS1njCuHoVbrwRsrNlhVpX8L//we23\n",
218 "qwtItmqldRohHJNdOr1BXUsqKSkJgIiICE0XJLRGwdi5Ux1OKyvUuo4nn4QOHeCVV7ROIoRjskuT\n",
219 "VOm1pNLS0rjjjjucfi2pkg5v4TomToT589U+DSGE9dT6taSio9XhmH9NZhcuYuBAeOghGD1a6yRC\n",
220 "OB5ZS6qaZEkQ11SyV4bzTksVwvFUay2pkj2+nX0tqUuX1FVOg4K0TiKsbcAAtVisW6d+L4SouWqt\n",
221 "JVXyfXXXkkpNTSU6OporV64QExNjKkglVq1axauvvopOp+OWW24hNjaWkJAQi69TlW3b1OYoWbDO\n",
222 "9ZSeyCcFQwjrqNZaUtu2bUOn09G1a9dqXVSv1zNnzhw8PT2JiIhg06ZNZUZbXbp0iSZNmgCwYcMG\n",
223 "XnnlFVJTU8uHr2EfxqxZcPSoui+0cD0ykU+IitmlDyM1NZXbbruNl19+mZdeeonbbruNjRs3WnTB\n",
224 "vLw8APr06YOnpyfh4eGkp6eXOaakWJQc37BhQ4uuYS7pv3BtDRvC00/LRD4hrMWigjFz5ky+++47\n",
225 "kpKSSEpKYvXq1bzzzjsWXTAjIwNfX1/Ta39/f9LS0sod9+233+Ll5cWYMWNYuHChRdcwlxQM1/eP\n",
226 "f8DXX6tPkkKImjGrD6NETk4ON998s+l127ZtycnJsXoogKFDhzJ06FCWLVvGfffdR1ZWVoXHxcbG\n",
227 "mr43GAwYDAazzn/ypNrpLcuZu7ZWrdSh06+9Bjb6d4cQDi8lJYWUlJQan8eiPowFCxawdOlSHnzw\n",
228 "QRRF4ZtvvmHkyJGMGzfO7Avm5eVhMBhMBWD8+PFERkYSFRVV6d+56aabyM7OplGjRmXD16APY9Uq\n",
229 "dVvP//63Wn9dOJFz5+C222DzZvDx0TqNENqzeR+GoigMGTKEOXPmkJuby/nz54mLi7OoWAC4u7sD\n",
230 "an9IdnY2ycnJhF3TLnTw4EHTL/PDDz/QrVu3csWipqQ5qva48UZ19resRixEzVjUJBUeHs7OnTur\n",
231 "PTqqRFxcHNHR0RiNRmJiYvDw8CAhIQGA6Ohovv76axYtWoSbmxt6vZ6ZM2fW6HoVSU+HyZOtflrh\n",
232 "oCZMgE6dYPt2dbMsIYTlLGqSGjt2LPfff/91m4/sqbqPVVevqivTHjoELVvaIJhwSHFxsH692hwp\n",
233 "RG1ml9VqAwIC2L17N61bt6Zt27amC2dmZlp8YWuo7i+9ezcMGaLO8ha1R2Gh2pexfDn06KF1GiG0\n",
234 "U93PTouapFatWmWVHe60JivU1k4NG8Krr8K//gU//qh1GiGcj1md3kajkcTERBYuXMixY8fw9vam\n",
235 "U6dOpi9nIx3etdeoUeqcDCkYQljOrILx8ssvs2DBAlq1asWMGTOIc/Kps1Iwai83N5gxQ33KcIGH\n",
236 "ZSHsyqw+jG7dupGWloabmxu5ubkMGTKEDRs22CPfdVWnHS4/X53MlZMjiw7WVsXFEBwMr78O996r\n",
237 "dRoh7M+m8zCKi4txc3MDoHnz5pw/f97iCzmKzEzo3FmKRW1Wp45aLKZOVYuHEMI8ZhWM7du307Rp\n",
238 "U9PXjh07TN83a9bM1hmtats2sMFK6cLJDB4MjRvDsmVaJxHCeZg1Surq1au2zmE3mZnQp4/WKYTW\n",
239 "dDp44w0YNw4eeEDt2xBCXJ9Fq9W6gsxMqOFEdeEi+vWDDh3gk0+0TiKEc6jWBkqOwtKOm/x88PCA\n",
240 "3FyoX9+GwYTTyMyEQYNg715o3lzrNELYh102UHJ2O3aAn58UC/G3rl3V/ozp07VOIoTjq1UFQ5qj\n",
241 "REXeeAMWL4Y9e7ROIoRjk4Ihar3WrdWJfBMnymQ+Ia5HCoYQwD//CceOQWKi1kmEcFy1ptP78mW1\n",
242 "U/PsWbDyXkzCRaxdqxaOnTtlYqdwbdLpXYVdu9T9u6VYiMqEh4O/v7pvhhCivFpTMKQ5Spjjvffg\n",
243 "3Xfh5EmtkwjheKRgCFHKrbfCk0/ClClaJxHC8UjBEOIaL7+s7peRnq51EiEciyYFIzU1FT8/P3x8\n",
244 "fJg7d26595csWUJQUBBBQUE8/PDD7Nu3r0bXu3JFnbQXHFyj04haomlTeOstGD9eVrMVojRNCsaE\n",
245 "CRNISEhg3bp1xMfHc+bMmTLve3t7k5qaym+//UZERASvvfZaja73++9wyy3qB4EQ5njkEXVBwg8/\n",
246 "1DqJEI7D7gUjLy8PgD59+uDp6Ul4eDjp1zz79+zZE3d3dwCioqJqvFmTNEcJS9WpA//+N0ybBkeO\n",
247 "aJ1GCMdg94KRkZGBr6+v6bW/vz9paWmVHv/RRx8xePDgGl1TCoaoDl9feO45tRPceWcrCWE9Zu2H\n",
248 "oZV169axePFiNm/eXOkxsbGxpu8NBgMGg6HcMZmZcM89NggoXN7kybBiBXz6KYwZo3UaIaonJSWF\n",
249 "lJSUGp/H7jO98/LyMBgMZGVlATB+/HgiIyOJiooqc9z27du5//77WbNmDZ06darwXObMViwuhhtv\n",
250 "hMOHoUUL6/wOonbZvh3694esLLUvTAhn5zQzvUv6JlJTU8nOziY5OZmwsLAyxxw9epRhw4axZMmS\n",
251 "SouFuQ4eVAuFFAtRXYGB8PTT6u580jQlajNNmqTi4uKIjo7GaDQSExODh4cHCQkJAERHRzNjxgxy\n",
252 "cnIYN25XGuKtAAAVR0lEQVQcAG5ubmzZsqVa15L+C2ENL78M3brB0qXqCCohaiOXX3zwxRehWTN1\n",
253 "+WohamLrVoiKUpuobrpJ6zRCVJ/TNEnZmzxhCGvp3l3t+H7mGa2TCKENly4YiiIFQ1jXtGnqqgEr\n",
254 "VmidRAj7c+mCcfQoNGwozQfCeho2hE8+UZcNOXFC6zRC2JdLFwx5uhC2cMcd8I9/wMiR6jplQtQW\n",
255 "UjCEqIZ//Qvq11ebqISoLaRgCFENdevCkiXw+eewZo3WaYSwD5ctGIoC27ZJwRC207q1WjQefxyO\n",
256 "H9c6jRC257IF4+RJuHoV2rXTOolwZX37QkwMjBgBRqPWaYSwLZctGCXNUTqd1kmEq5syRd1rZepU\n",
257 "rZMIYVsuXTC6ddM6hagN6tSBL76A//wHEhO1TiOE7bh0wZD+C2EvHh5qwRg7Vp3/I4QrkoIhhJX0\n",
258 "6gXPPw9Dh8LFi1qnEcL6XHLxwdOn4bbbICdH+jCEfSmKukPfn3/CypVQz6G3KBO1lSw+WEpWFuj1\n",
259 "UiyE/el0sGCBOmIqJkb2zxCuxSULhjRHCS25ucFXX8GmTTBrltZphLAeKRhC2ECzZvDDD/DBB2rx\n",
260 "EMIVSMEQwkbatYPVq+Gf/4Sff9Y6jRA153Kd3rm50L69+mfduhoFE6KUpCQYNQpSU9XBGEJoTTq9\n",
261 "//LrrxAUJMVCOI6ICHjrLejfH/bv1zqNENWnScFITU3Fz88PHx8f5s6dW+79vXv30rNnTxo2bMjs\n",
262 "2bMtOrc0RwlHNHo0vPoq3H23FA3hvDQZJT5hwgQSEhLw9PQkIiKCkSNH4uHhYXq/ZcuWzJ07l5Ur\n",
263 "V1p87sxM9V9yQjiaJ55Qh93edRf89JM0TwnnY/cnjLy8PAD69OmDp6cn4eHhpKenlzmmVatWdO/e\n",
264 "HTc3N4vPL08YwpGNHQszZqhPGr//rnUaISxj94KRkZGBr6+v6bW/vz9paWlWOfelS3DkCPj5WeV0\n",
265 "QtjEmDHw+utq0di7V+s0QpjP6RcuiI2NNX3v4WGgc2cD1XgwEcKuHn9cbZ7q108dRRUQoHUi4cpS\n",
266 "UlJISUmp8XnsXjBCQkJ4/vnnTa937dpFZGRktc9XumDMmyfNUcJ5jBoFDRqoTxqLFkEN/jcQ4roM\n",
267 "BgMGg8H0evr06dU6j92bpNzd3QF1pFR2djbJycmEhYVVeKyl44Sl/0I4mxEj4Jtv1FFU8fFapxHi\n",
268 "+jSZuLdhwwbGjRuH0WgkJiaGmJgYEhISAIiOjubPP/8kJCSE8+fPU6dOHZo2bcru3bu54YYbyoa/\n",
269 "ZvJJcDB8/DF0727XX0eIGjt0CKKiYMAAeO89WeVW2FZ1J+65zEzvwkJo0UJd0rxhQ42DCVENubnw\n",
270 "4INQvz58+aW67asQtlDrZ3rv3KmOa5diIZxV8+bqgoXt26ubMR04oHUiIcpymYIh/RfCFbi5qftp\n",
271 "REdDz55qZ7jztgEIVyMFQwgHo9OpK9z++CO88w488gj8Nd9VCE1JwRDCQQUGwtatat9ccLAskS60\n",
272 "5xKd3kaj2v77v/9BkyZapxLC+lavVvcKHzcOXn5Z7RgXorpqdaf3nj3g6SnFQriuwYPVveq3blWX\n",
273 "709O1jqRqI1comBIc5SoDdq2VZ80Zs5UO8WHDVPXThPCXqRgCOFEdDr1aWP3brVfo1s3dfXbggKt\n",
274 "k4naQAqGEE6oYUN45RXYtg22b4fOndUlRoqLtU4mXJnTd3pfuaLQvDkcO6Z2fAtRG61bBy+8AEYj\n",
275 "vPQSPPSQLC8iKldrO73374ebbpJiIWq3/v3Vp41334UPP4Tbb4ePPoKiIq2TCVfi9AVDmqOEUOl0\n",
276 "6hLpqanw+eewahV4e8Ps2XDhgtbphCuQgiGEC+rdG77/HhITYcsWdX2qUaPUvcSln0NUlxQMIVyY\n",
277 "Xg/Llqn7hwcHw7PPQseOaoe5LG4oLOX0nd7u7gr790OrVlqnEcI5/Pqr2mS1dCn4+KhPHvfco87z\n",
278 "ELVDrd0Po317haNHtU4ihPMxGmHNGli8WJ053r49RESo/SC9eqnbxwrXVGsLxpAhCitXap1ECOd2\n",
279 "5QpkZEBSklpEdu+GPn3+LiCdOqmd6sI11NqCMX26wquvap1ECNeSk6PO7SgpIMXFal9ht25//9mu\n",
280 "nRQRZ1VrC8bq1Qr33KN1EiFcl6KoE2O3bVMHmWzbpn4pilo8SgpIly7g5SUr6ToDp5q4l5qaip+f\n",
281 "Hz4+PsydO7fCY1566SW8vb3p1q0be/furfRczjBCKiUlResIZpGc1uMMGcG8nDoddOgAQ4fCa6+p\n",
282 "28j++ae6eu4//6kWiM8/V5uumjZVj+3bF0aPVte5+uILdS+PEyeqP6TXle6nM9Nk8YAJEyaQkJCA\n",
283 "p6cnERERjBw5Eg8PD9P7W7ZsYePGjWzdupWkpCQmT55MYmJihedyhpEdKSkpGAwGrWNUSXJajzNk\n",
284 "hOrn1OngllvUr8GD//75lSvq08jhw3DokPrnf//79/fnz6tPIe3agYcHtGxZ/s/S3zdpol7L1e+n\n",
285 "s7B7wcj7a6/JPn36ABAeHk56ejpRUVGmY9LT03nggQdo0aIFI0eOZOrUqZWeT9pQhXAc9eqp8zw6\n",
286 "doS77y7//sWLkJ0Nf/wBZ8+qX2fOqPNEfv7579cl7129qhaOq1dhwwa1gDRqVPFX48aVv3ftcfXr\n",
287 "Q506ULfu33+W/l4+Vypm94KRkZGBr6+v6bW/vz9paWllCsaWLVt49NFHTa9btWrFwYMHufXWW+2a\n",
288 "VQhhXTfcAAEB6pc5CgrUwvH66+qCivn56s8KCsp+X1CgNpNV9l7pr/x8dUjx1avqV3Fx+T91uooL\n",
289 "SenvK/pZbq46UfJ6f0+n+7sglS5M9vxZdTnkepaKopTrkNFV8ptW9nNHM336dK0jmEVyWo8zZATn\n",
290 "yZmQYL+civJ3QbHU2bPOcT+rw+4FIyQkhOeff970eteuXURGRpY5JiwsjN27dxMREQHA6dOn8fb2\n",
291 "LncuJx7gJYQQTsfuo6Tc3d0BdaRUdnY2ycnJhIWFlTkmLCyMr7/+mrNnz7J06VL8/PzsHVMIIcQ1\n",
292 "NGmSiouLIzo6GqPRSExMDB4eHiQkJAAQHR1NaGgovXv3pnv37rRo0YLFixdrEVMIIURpioPbsGGD\n",
293 "4uvrq3Tq1En54IMPKjxmypQpSseOHZWuXbsqe/bssXNCVVU5169frzRr1kwJDg5WgoODlddee83u\n",
294 "GUePHq20bt1aCQgIqPQYR7iXVeV0hHupKIpy9OhRxWAwKP7+/krfvn2VJUuWVHic1vfUnJxa39OC\n",
295 "ggIlNDRUCQoKUsLCwpT33nuvwuO0vpfm5NT6XpZ25coVJTg4WLnnnnsqfN/S++nwBSM4OFjZsGGD\n",
296 "kp2drdx+++3K6dOny7yfnp6u9OrVSzl79qyydOlSJSoqyiFzrl+/Xhk8eLAm2UqkpqYqmZmZlX4Q\n",
297 "O8q9rCqnI9xLRVGUkydPKllZWYqiKMrp06eVjh07KufPny9zjCPcU3NyOsI9vXTpkqIoilJYWKh0\n",
298 "7txZ2b9/f5n3HeFeKkrVOR3hXpaYPXu28vDDD1eYpzr306H3wyg9Z8PT09M0Z6O0a+ds7NmzxyFz\n",
299 "gvad9HfeeSc33nhjpe87wr2EqnOC9vcSoE2bNgQHBwPg4eFB586d2bp1a5ljHOGempMTtL+njRs3\n",
300 "BuDixYtcuXKFBtcsl+sI9xKqzgna30uA48eP88MPP/DEE09UmKc699OhC0ZlczZK27JlC/7+/qbX\n",
301 "JXM27MmcnDqdjs2bNxMcHMyzzz5r94zmcIR7aQ5HvJcHDhxg165dhIaGlvm5o93TynI6wj0tLi4m\n",
302 "KCiIm266iWeeeYb27duXed9R7mVVOR3hXgJMmjSJd999lzp1Kv6Yr879dOiCYQ7FgjkbWuratSvH\n",
303 "jh0jIyMDf39/JkyYoHWkcuReVs+FCxcYPnw477//Pk2aNCnzniPd0+vldIR7WqdOHX777TcOHDjA\n",
304 "/PnzycrKKvO+o9zLqnI6wr1MTEykdevW6PX6Sp92qnM/HbpghISElFl4cNeuXfTo0aPMMSVzNkpU\n",
305 "NmfDlszJ2bRpUxo3boybmxtjx44lIyODoqIiu+asiiPcS3M40r00Go0MGzaMRx99lCFDhpR731Hu\n",
306 "aVU5Hemeenl5MWjQoHLNuo5yL0tUltMR7uXmzZv57rvv6NixIyNHjuSnn37iscceK3NMde6nQxcM\n",
307 "Z5mzYU7OU6dOmar56tWrCQwMrLDtU0uOcC/N4Sj3UlEUxo4dS0BAABMnTqzwGEe4p+bk1Pqenjlz\n",
308 "htzcXADOnj3L2rVryxU2R7iX5uTU+l4CvPnmmxw7dozDhw/z5Zdfcvfdd7No0aIyx1Tnfjrk0iCl\n",
309 "OcucjapyrlixggULFlCvXj0CAwOZPXu23TOOHDmSDRs2cObMGdq3b8/06dMxGo2mjI5yL6vK6Qj3\n",
310 "EuDnn39m8eLFBAYGotfrAfV/1KN/7RnsKPfUnJxa39OTJ08yatQorl69Sps2bZg8eTJt27Z1uP/X\n",
311 "zcmp9b2sSElTU03vp1NvoCSEEMJ+HLpJSgghhOOQgiGEEMIsUjCEEEKYRQqGEEIIs0jBEFZVp04d\n",
312 "Jk+ebHo9a9Ysu2/QYzAYyMzMBCAqKorz58/X6HwpKSkMLr1xdRU/t8W1bOnEiRM8+OCDdr2mcE5S\n",
313 "MIRV1a9fn2+//ZazZ88Cls/EvVqdLc6uUfqa33//Pc2aNavxOV3ZzTffzFdffaV1DOEEpGAIq3Jz\n",
314 "c+Opp57i/fffL/feiRMnmDBhAkFBQUyaNIlTp04B8Pjjj/Pss88SFhbGiy++yOjRo3nuuecIDQ3l\n",
315 "9ttvJysri6eeeorOnTsTGxtrOt/TTz9NSEgId9xxBwsXLqwwj5eXF2fPnuXDDz9Er9ej1+vp2LEj\n",
316 "d999N6CuA/bYY48RFhbGlClTTDNyMzIy6NevH3q9nqSkpCp/74KCAt577z369u1LVFQUKSkpAPTs\n",
317 "2bPMbNqSp5/CwsIKj6/MsWPHGDhwIMHBwQQFBXHw4EGys7Px9/dn7Nix+Pn5MX36dFP+1157jdDQ\n",
318 "UEJCQnjzzTfLnOe5555Dr9fTrVs3Dh8+THZ2Nl26dAHgs88+Y8SIEQwaNIiAgAA++OAD099ds2YN\n",
319 "PXv2JDQ0lIkTJzJ+/PhyOX/99Vf69etHcHAwXbt25eLFi1XeO+FEarJ0rhDXuuGGG5Tz588rXl5e\n",
320 "Sl5enjJr1iwlNjZWURRFmTRpkjJz5kxFURTlzTffVF544QVFURRl1KhRSt++fU1Lbj/++OPKwIED\n",
321 "laKiIuWzzz5TbrjhBiUlJUUpKipS/Pz8TEvH5+TkKIqiKEVFRUpYWJhy8eJFRVEUxWAwKNu2bVMU\n",
322 "RVG8vLyUs2fPmvIZjUblzjvvVBITE03H5ubmKoqiKC+88ILy5ZdfKoqiKIGBgUp6erpy8eJFJTIy\n",
323 "ssLlodevX2/aZ+DTTz9V5syZoyiKovz5559KaGiooiiK8v777yvTpk1TFEVRTpw4odx+++3XPb70\n",
324 "OUubNm2a8vHHH5t+h4KCAuXw4cOKTqdTvvnmG6WwsFC5//77lRUrVpS5N1euXFEGDx6s7N2713Sv\n",
325 "4+PjTfctPz9fOXz4sGkp+U8//VRp3bq1cuLECeX8+fNKu3btlMuXLytGo1Hx8vJSDh8+rJw9e1bp\n",
326 "2rWrMn78+HI5R40apaxbt05RFHUZ8CtXrpQ7RjgvecIQVte0aVMee+yxMv86Bfjvf//LmDFjABg7\n",
327 "diyrV68G1CakBx54gKZNm5qOfeCBB6hfvz49e/akefPm9O3bl/r166PX600rAScnJxMVFYVer+fQ\n",
328 "oUP89NNPVWaLiYmhX79+REVFsW3bNnbu3InBYECv15OYmEhqaip//PEHiqIQGhpKkyZNGD58eJXL\n",
329 "VX/99dcsXLgQvV5PZGQkp06d4vDhwzz00EOsWLECgOXLl5v6Cio6/tChQ5WePyQkhLi4ON555x1y\n",
330 "cnJo2LAhoC5LM3ToUBo0aMDIkSNZs2YNAFu3bmXYsGEEBgaSmZnJ2rVruXz5MuvXr+fJJ58E1ObD\n",
331 "Ro0albtWeHg4bdu2pWnTpvj7+5OZmUlaWhpdunTBy8uLFi1acO+991Z4T3r27MmUKVOYN28eV65c\n",
332 "oW7dulX+NxHOw+GXBhHOaeLEiXTt2pXRo0eX+XllH7xt27Yt87pkfa769evTvHlz08/r16/P5cuX\n",
333 "uXDhAlOmTGHjxo3ccsstDB06lHPnzl0302effcaxY8eYP38+oC5THRAQwPr168scd/z4cfN+yVKK\n",
334 "i4uJj4+nT58+5d5r2bIlO3bsYPny5aalGSo7vmS5jmtFRUXRrVs3Fi9eTK9evfjqq6/K3JcSJf03\n",
335 "48ePZ8WKFQQEBDBp0iTOnTuHTqercIXSa117vwsLC6lXr16ZvqHKzhEdHc2AAQNMS5Gkp6dz0003\n",
336 "Xfd6wnnIE4awiRtvvJGHHnqIf//736YPmkGDBvH5559TXFzMJ598wr333lutcyuKQm5uLm5ubrRp\n",
337 "04Z9+/bx448/XvfvbNu2jdmzZ/PFF1+YfhYSEsKpU6dMTyyXLl1i//79tGvXjrp165KRkcGlS5dY\n",
338 "vnx5lZkefvhhEhISuHDhAkCZJa+HDx/OO++8w/nz5wkICKjy+IocPnzYtHZRv379TP0ieXl5rFy5\n",
339 "kqKiIpYtW0ZkZCSFhYVcuHABLy8v/vjjD1atWgWo/Ut33XUXCxcuRFEUioqKKCgoqPJ30+l09OjR\n",
340 "gx07dpCdnU1OTg6JiYkVDmg4ePAg3t7evPrqq/j6+jrEXiXCeqRgCKsq/SHy3HPPcebMGdPryZMn\n",
341 "c/ToUfR6PadOneLZZ5+t8O9d+7qi99q3b8+wYcMICAjgmWeeqXQoasm/quPj4zl37hx33XUXer2e\n",
342 "p556CoAvvviCBQsWEBgYyB133MHvv/8OwEcffcRLL71E7969CQoKqvDDUafTmX7+wAMPEBoaSkRE\n",
343 "BAEBAUybNs103AMPPMCyZct46KGHyvysouNLn7O05cuXExAQQEhICPn5+aZz+fr68t133xEcHExA\n",
344 "QABRUVE0bNiQKVOmEBoayvDhwxk0aJDpPG+88QYHDhwgKCiIXr16mQYelFyzsuvXrVuXefPmMXz4\n",
345 "cCIjI+nSpQsdO3Ysd9ycOXPo0qULoaGh+Pr6cscdd1T430U4J1l8UAgnlZ2dzeDBg9mxY4ddrnfp\n",
346 "0iWaNGlCXl4e99xzDx9//DG33367Xa4tHIP0YQjhxOy541xsbCzr1q3Dzc2N//u//5NiUQvJE4YQ\n",
347 "QgizSB+GEEIIs0jBEEIIYRYpGEIIIcwiBUMIIYRZpGAIIYQwixQMIYQQZvl/CHrf0nQauboAAAAA\n",
348 "SUVORK5CYII=\n"
349 ]
350 },
351 "metadata": {},
352 "output_type": "display_data"
353 }
354 ],
355 "source": [
356 "plot(s, rhos)\n",
357 "xlabel('Normalized level spacing s')\n",
358 "ylabel('Probability $\\rho(s)$')"
359 ]
360 },
361 {
362 "cell_type": "markdown",
363 "metadata": {},
364 "source": [
365 "## Serial calculation of nearest neighbor eigenvalue distribution"
366 ]
367 },
368 {
369 "cell_type": "markdown",
370 "metadata": {},
371 "source": [
372 "In this section we numerically construct and diagonalize a large number of GOE random matrices\n",
373 "and compute the nerest neighbor eigenvalue distribution. This comptation is done on a single core."
374 ]
375 },
376 {
377 "cell_type": "code",
378 "execution_count": 6,
379 "metadata": {
380 "collapsed": true
381 },
382 "outputs": [],
383 "source": [
384 "def serial_diffs(num, N):\n",
385 " \"\"\"Compute the nearest neighbor distribution for num NxX matrices.\"\"\"\n",
386 " diffs = ensemble_diffs(num, N)\n",
387 " normalized_diffs = normalize_diffs(diffs)\n",
388 " return normalized_diffs"
389 ]
390 },
391 {
392 "cell_type": "code",
393 "execution_count": 7,
394 "metadata": {
395 "collapsed": true
396 },
397 "outputs": [],
398 "source": [
399 "serial_nmats = 1000\n",
400 "serial_matsize = 50"
401 ]
402 },
403 {
404 "cell_type": "code",
405 "execution_count": 8,
406 "metadata": {
407 "collapsed": false
408 },
409 "outputs": [
410 {
411 "name": "stdout",
412 "output_type": "stream",
413 "text": [
414 "1 loops, best of 1: 1.19 s per loop"
415 ]
416 }
417 ],
418 "source": [
419 "%timeit -r1 -n1 serial_diffs(serial_nmats, serial_matsize)"
420 ]
421 },
422 {
423 "cell_type": "code",
424 "execution_count": 9,
425 "metadata": {
426 "collapsed": false
427 },
428 "outputs": [],
429 "source": [
430 "serial_diffs = serial_diffs(serial_nmats, serial_matsize)"
431 ]
432 },
433 {
434 "cell_type": "markdown",
435 "metadata": {},
436 "source": [
437 "The numerical computation agrees with the predictions of Wigner, but it would be nice to get more\n",
438 "statistics. For that we will do a parallel computation."
439 ]
440 },
441 {
442 "cell_type": "code",
443 "execution_count": 10,
444 "metadata": {
445 "collapsed": false
446 },
447 "outputs": [
448 {
449 "data": {
450 "text/plain": [
451 "&lt;matplotlib.text.Text at 0x3475bd0&gt;"
452 ]
453 },
454 "execution_count": 10,
455 "metadata": {},
456 "output_type": "execute_result"
457 },
458 {
459 "data": {
460 "image/png": [
461 "iVBORw0KGgoAAAANSUhEUgAAAYwAAAEMCAYAAADXiYGSAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\n",
462 "AAALEgAACxIB0t1+/AAAIABJREFUeJzt3XlcVPX+x/HXgGhuqAhupSJpAqKAJrhDuaCilWluv3LN\n",
463 "8N5csrhm3fqp9cvbcs1dI1tvaqlZmVgumIAbi1vllisqbrEoILLz/f2BzJUAmWGZMzN8nvcxj8vM\n",
464 "+c457/kS8/Gc7znfo1NKKYQQQogy2GgdQAghhGWQgiGEEMIgUjCEEEIYRAqGEEIIg0jBEEIIYRAp\n",
465 "GEIIIQxi8oIxadIkmjZtSseOHUtt89prr+Hi4kKXLl04deqUCdMJIYQojckLxsSJE9m2bVupy2Ni\n",
466 "YtizZw8HDx4kODiY4OBgE6YTQghRGpMXjN69e9OoUaNSl0dHRzNixAgcHBwYM2YMJ0+eNGE6IYQQ\n",
467 "pTG7MYyYmBjc3d31z52cnDh37pyGiYQQQgDU0DrAXyml+OtsJTqdrsS2pb0uhBDi/sozK5TZ7WH4\n",
468 "+vpy4sQJ/fOEhARcXFxKbV9YYMz5MXfuXM0zSE7JKDklZ+GjvMyyYGzatImkpCTWrVuHm5ub1pGE\n",
469 "EEKgwSGpMWPGEBERQWJiIi1btmT+/Pnk5OQAEBQUhI+PD7169eLRRx/FwcGBNWvWmDqiEEKIEpi8\n",
470 "YHz99ddltnn33Xd59913TZDGNPz9/bWOYBDJWXksISNIzspmKTnLS6cqckBLYzqdrkLH44QQojoq\n",
471 "73en2Y1hCCGEME9SMIQQQhhECoYQQgiDSMEQQghhECkYQgghDCIFQwghhEGkYAghhDCIFAwhhBAG\n",
472 "kYIhhBDCIFIwhBBCGEQKhhBCCINIwbBw9vYO6HS6Mh41y2xjb++g9UcRQpg5mXzQwhXcdbCsPjCs\n",
473 "TXXvSyGqC5l8UAghRJWSgiGEEMIgUjCEEEIYRAqGEEIIg0jBEEIIYRApGEIIIQwiBUMIIYRBpGAI\n",
474 "IYQwiBQMIYQQBpGCIYQQwiBSMIQQQhhECoYQQgiDSMEQQghhECkY4q4aMv25EOK+ZHpzC1eZ05vf\n",
475 "v430tRDWQqY3F0IIUaWkYAghhDCIFAwhhBAGkYIhhBDCIFIwhBBCGEQKhhBCCINoUjAiIyNxc3Oj\n",
476 "Xbt2LFu2rNjyjIwMxo8fj7e3N35+fmzevFmDlEIIIe6lyXUY3t7eLFmyhNatWxMQEMDevXtxdHTU\n",
477 "L//oo4/47bffWLlyJRcvXuTxxx/n7Nmzd685uCe8XIch12EIIYxmMddhpKSkANCnTx9at27NgAED\n",
478 "iI6OLtKmQYMGpKWlkZOTQ3JyMnXq1ClWLIQQQpiWyQtGbGwsrq6u+ufu7u5ERUUVaTNmzBjy8vJw\n",
479 "dHSkV69erF271tQxhRBC/EUNrQOUZPny5dSoUYNr167x+++/ExgYyMWLF7GxKV7f5s2bp//Z398f\n",
480 "f39/0wUVQggLEB4eTnh4eIXXY/IxjJSUFPz9/Tly5AgA06dPZ+DAgQQGBurbjBw5ksmTJxMQEACA\n",
481 "r68vX375ZZE9E5AxDJAxDCGE8SxmDKNBgwZAwZlScXFx7Ny5E19f3yJt+vbty5YtW8jPz+f8+fMk\n",
482 "JycXKxZCCCFMS5NDUosXLyYoKIicnBxmzJiBo6MjISEhAAQFBTF69GhOnDjBo48+ipOTE0uWLNEi\n",
483 "phBCiHvI9OYWTg5JCSGMZTGHpIQQQlgmKRhCCCEMIgVDCCGEQczyOgxhmPjUeGgLqO2QVwtya0FC\n",
484 "B8iy1zqaEMIKScGwIEopQk+Hsv74evZe2kt6Tjp0B1gIttlgdwccT8J1Lzg3AE4OhwR3rWMLIayE\n",
485 "nCVlAZRSbD2zlXnh88jNz+XvXf+OX2s/Hmn8yN2r3+/pA7s70GoPPLwDOq2FOD+I2AAJcpaUEKJA\n",
486 "eb87pWCYufjUeMZsGsOtzFvM85vHMLdh2Oj+O/R039Nq7dLBZwX0eBXOjYVti+GOUylbkoIhRHUh\n",
487 "p9Vaod0XdtN1dVeGtBvCr1N/Zbj78CLFokw5dWHfbFgCpLWAqV7gElZleYUQ1k32MMyQUoqFBxay\n",
488 "8MBC1gxbQ1+XvqW2NerCPZcweGoC/D4Wfvk/yKtZvM191mGNfS1EdSSHpKzIa7teY9vZbWwevZlW\n",
489 "DVrdt63RV3rXSYBh4wt+3vAt5NQp3qaUdVhjXwtRHckhKSvx/r73+fGPH9n53M4yi0W53HGCr3+E\n",
490 "O47w7EColVr52xBCWCUpGGZk9aHVrDq4ih3P7sCxjmPZbyiv/BrwwxdwoyOMfxzqJFbdtoQQVkMK\n",
491 "hpn47uR3zIuYx87ndvKg/YNVv0FlAz8th3P9YYIf1K/6TQohLJsUDDNw4eYFgkKD2Dx6M20d2ppw\n",
492 "yzrY9a+CQfBnkcNTQoj7kkFvjeXm5+L3hR/DXIcR3CPY6PdXzvTmCgbbgMMAWBcK+XYlrsPS+1oI\n",
493 "UUAGvS3UO5HvUMeuDi93f7nYMnt7B3Q63X0flUMH2wBlC4OnUXYBEkJUR1IwNLTv0j5WHVzFl099\n",
494 "WeIFeWlpNyn48r7fo5LkAxvXw0PR0OPflbdeIYTVkIKhkZTMFJ79/llChoTQon4LreMUyK5fcEjK\n",
495 "dym4f6t1GiGEmZExDI1M+GECte1qsypwValtTHf71b+0aX4Yng2AT/dDcjv9ckvtayFEUTKGYUGi\n",
496 "4qPYeX4nH/T/QOsoJbvWGcLnwYjRYJuldRohhJmQgmFiSilmbZ/FgscXUK9mPa3jlC7275DSCvq9\n",
497 "pnUSIYSZkIJhYt8c+4acvBye83xO6yhl0MGPn4LbJmj3k9ZhhBBmQAqGCd3JucOrYa+yKGCRcdOU\n",
498 "ayXDAb5bA09MlivBhRBSMEzpwwMf4vuQL71b99Y6iuEu9YaDf4NhkJefp3UaIYSGpGCYyNW0qyyK\n",
499 "WsR7/d7Tv1bWhXlmI/KfYAPv7Xuv7LZCCKslBcNE/vnLP5nSeQoujVz0r5V9YZ6ZULbwHSyKWsTx\n",
500 "P49rnUYIoREpGCZwJukMoadDeb3361pHKb9UePuxt5myZQr5Kl/rNEIIDRh14V5+fj4HDhwgLi6O\n",
501 "hIQEGjZsSIcOHejSpQs2NqavPZZy4d6ULVNoUb8F8/3nF3m97AvzNLpwr5Tlefl5+H3hx+gOo3nR\n",
502 "58Uy1ieEMFdVeovW7OxsFi1aRF5eHg0bNsTFxYVmzZoRHx/PuXPnuHbtGk2aNGHmzJnY2tqW6wOU\n",
503 "hyUUjCupV+i4qiNnpp+hcZ3GRZZZWsFQSnEy4SR9vujD4RcO07JByzLWKYQwR1VWMLKysli3bh1D\n",
504 "hgzBycmp1Hbnz59n165dTJkyxegQ5WUJBeOVHa+Qr/JZFLCo2DJLLBgAb0e8TczVGH4c/aN5Dc4L\n",
505 "IQxSpXsY5srcC0bSnSTaLWvHb3/7jYfsHyq23LIKhh2QW/CjLRAERAD3jIHXr9+I1NTkMrYjhNCa\n",
506 "yeaSOnXqFD/99BPJycmcPHnS6A1WJ8tjljPMbViJxcLy5KI/eytPwY/7YWAzqJ2kf73grC8hhLUq\n",
507 "V8Fo1qwZ3bp1Y+vWrXz11VdVkcvi3c6+zYrYFczuMVvrKFUjvjucGAEDjL9LoBDCMhldMK5evcru\n",
508 "3bvx9vYmODiYpk2bVkUui7f60Gr8nP1o79he6yhVZ9cCcAmDNru0TiKEMAGjC8b48eNxdXVl1apV\n",
509 "vPjiixw9erQqclm07LxsFh5YyGu9rHym1+z68NNyCHwRbHK0TiOEqGIVGvS+efMmDRs21OxMGXMd\n",
510 "9F5/bD0fHfqI3eN337edZQ16l7ZcwbOD4OxAiJpllr8PIURRVTbonZWVxR9//FHiskaNGhUpFnv2\n",
511 "7DFoo5GRkbi5udGuXTuWLVtWYpvY2Fi6du2Km5sb/v7+Bq3XXIQcCmFql6laxzARHWxbBL3fgTpa\n",
512 "ZxFCVKUyC0atWrVISkpi+fLlJZ4VlZqayp49e5gxYwaNGjUyaKMzZ84kJCSEsLAwVqxYQWJiYpHl\n",
513 "SikmTZrEv/71L06ePMm331rO/aVPJ53m2J/HeL731PtOLGhV1y8kusHvY+ExrYMIIaqSQWMYp06d\n",
514 "4s0338TT05N69eoxdepUJkyYwNNPP82cOXNITk5myZIleHh4lLmulJQUAPr06UPr1q0ZMGAA0dHR\n",
515 "RdocPHiQTp060a9fPwAcHR2N/VyaWX14NRO8JnD71i3uP7GglR26CZ8HbvDbjd+0TiKEqCI1DGl0\n",
516 "9OhRbty4QVpaGkuXLqVfv3707l2+ezrExsbi6uqqf+7u7k5UVBSBgYH617Zv345Op6N37940bNiQ\n",
517 "adOmERAQUK7tmVJWbhZfHv2SfZP28QFmer/uqpLZCCLgJY+X2DVul3XtQQkhAAMLRqdOnahZsyaN\n",
518 "Gzdm7ty5LF++vNwFwxCZmZkcPXqUsLAw7ty5Q//+/Tl27Bi1a9cu1nbevHn6n/39/TUd7/j+1Pd0\n",
519 "bNqRdo3baZZBU4cg4U4C35/6nqfdntY6jRDirvDwcMLDwyu+ImWASZMmqZMnT+qf//DDD4a8rUS3\n",
520 "bt1SXl5e+ufTpk1ToaGhRdqEhoaq4OBg/fORI0eqbdu2FVuXgfFNxv8Lf7X+2HqllLp7zEmV8Sir\n",
521 "TWWsw7RZws6FqTaL26iMnAyNfxtCiNKU97vToDGMc+fOERwcTJs2bejZsycff/wxa9eu5cKFC2zc\n",
522 "uNGoAtWgQQOg4EypuLg4du7cia+vb5E23bp1IyIigjt37pCcnMyRI0fo2bOnUdsxtdNJpzmRcIKn\n",
523 "XJ/SOoqm+rr0xbOZJ4sOFJ9sUQhh2Qy6DuP48eN06NABKJiVNjo6mtjYWKKjo/n9999JTU01aqMR\n",
524 "ERFMnTqVnJwcZsyYwYwZMwgJCQEgKCgIgFWrVrFs2TKcnJz429/+xujRo4uHN6PrMIJ3BGNrY6u/\n",
525 "BWvZ11hA+a990KKNYetQSnEu+Ry+n/jy299+o0X9FmW8RwhhaprNVrto0SJmzZpVkVWUm7kUjMzc\n",
526 "TFotasX+yftp69AWqN4FA+C1Xa9xNe0qXz71ZRnvEUKYmslmq/2rGTNmVHQVFu/7k9/j2cxTXywE\n",
527 "vN7rdXae20nMlRitowghKkmFC4Yp77BnrkIOhfBC5xe0jmFW6teqz4K+C5jx8wy5B7gQVsL0N+K2\n",
528 "MmeSznAy8SRPuj6pdRSzM85zHLn5uWw4vkHrKEKISmBUwbhy5UpV5bBY3xz7hlEdRlHTtqbWUcyO\n",
529 "jc6G9/u/zxu/vEF2XrbWcYQQFWRUwejfvz+DBg1iw4YN5OTIdNZKKb4+9jWjPYqfwVU91Sg2X1Zf\n",
530 "l76cizlHrR610Ol02Ns7aB1SCFFORhWMEydO8Oabb7Jjxw7atWvH9OnTOXz4cFVlM3vH/jxGek46\n",
531 "3R7qpnUUM3HPbVzvfYQdhT5NoWaq3MZVCAtW7tNqt23bxqRJk8jLy6Nt27YsXLiQbt1M+8Wp9Wm1\n",
532 "hYda3u//frFl1fW02lLbDHsObj4M4fPN4lRoIaozk5xWGx8fzzvvvIOHhwefffYZX3zxBdeuXWPl\n",
533 "ypVMmjTJ6I1bMqWUfvxCGGD32+CzDOpqHUQIUV4GTT5YaNCgQUycOJHw8PAiU457enoydWp1uWFQ\n",
534 "gUPXDqHT6ejcvLPWUSzDLWf4dRz4LdY6iRCinIw6JBUTE4OPj0+Zr5mKloekgncEU9uuNm8/9naJ\n",
535 "y+WQVAnqJMI0J07PPl19Z/QVwgyY5JBUSXsRhXM/VSf5Kp/1x9czuoOcHWWUO45wAN7Y/YbWSYQQ\n",
536 "5WDQIanY2FhiYmJISEhg5cqV+sqUkJBg8G1ZrcmBywdoUKsBHZp00DqK5YmCvU/tJfZKLF0f7Kp1\n",
537 "GiGEEQzaw0hJSeHy5cvk5ORw+fJl4uPjiY+Pp1mzZnz++edVndHsfHP8G7n2orxyYK7fXF4Ne1XO\n",
538 "lhLCwhg1hnH69GkeeeSRqsxjFC3GMHLzc3now4fYM3HPfY/DyxhG6W1y8nLosLIDSwcuJaCt+d96\n",
539 "VwhrU6VjGIX32x4wYABt2rQp8nBxcTF6o5YsIi6Ch+wfkkHbCqhhU4N/9f0Xr4a9KhMTCmFBDNrD\n",
540 "uHXrFg0bNiQxMbHE5feeYmtKWuxhTNkyhfaN2xPcI/i+7WQPo/Q2SimUUnT/tDvTfKbxbKdny3iP\n",
541 "EKIyaXYDJS2ZumBk52XTYmELDgcdplWDVvdtKwWj9DaFv7PIi5GM+34cf0z7g1o1apXxPiFEZSnv\n",
542 "d6dBZ0nduHHj7hdgUUopdDodTZo0MXrDlijsfBjtHdvj0dJL5kQqtxpF/1saAw/0fgCiCp7Wr9+I\n",
543 "1NRkbaIJIe7LoILRq1cvgGJFo7BgnD59uvKTmaFvjn3D6A6j2Z82A8P+tS2KK5yg8K5dx2BcXzhy\n",
544 "GrIakJYm/SaEuTJo0PuRRx7hzJkzZGdnk5OTQ3Z2tv7n6jLNeVZuFltOb2GE+wito1iXPz3gzGDo\n",
545 "+YHWSYQQZTBoD2PdunUAHDx4sNiykg5VWaPwuHDcndxpXr+51lGsT/h8CPKGmBfhttZhhBClMahg\n",
546 "NGjQANDubChzsOX0FoY+MlTrGNYppRUcmQT+8yBU6zBCiNIYNVstQGJiItu3b0en0xEQEEDjxo2r\n",
547 "IpdZUUoRejqUrWO3ah3Feu19Daa11w9+CyHMj1GTD65du5bu3btz4MAB9u/fT/fu3Vm7dm1VZTMb\n",
548 "x/48hk6nw93JXeso1ivDAfYHw+NaBxFClMao6zC8vLzYtm0bzZo1AwpOtw0ICODo0aNVFvB+THUd\n",
549 "xoI9C7h++zpLBy3Vb9fSrn2wiCw1MmB6HQ68fEBueytEFTLJ9OYODg5kZGTon2dkZODg4GD0Ri1N\n",
550 "6OlQhjwyROsY1i+3NoTD7J2zZWJCIcyQQWMY06dPB8DJyYkuXbrQu3dvlFLs3buX/v37V2lArf2Z\n",
551 "/icnEk7g19pP6yjVw6+QlJHET2d+IvCRQK3TCCHuYVDB6NKli/702UGDBulff/rpp63+tNqfz/xM\n",
552 "P5d+MnWFqeTDv/r+izm75jCw7UBsbWy1TiSEuEvmkirDiA0jGPLIECZ4TSiyXasZNzC7LDry8/Pp\n",
553 "/Xlvnu/8fJF+F0JUDpNMPpiRkcGOHTvYvn07N2/e1O9dFF7YZ2pVXTCy87Jp8kETTk8/TZO6/50v\n",
554 "SwpG1W5HKcW+S/sYs2kMf0z7g9p2tctYpxDCGCYZ9H7jjTfYs2cP27dvx9/fn/j4eJydnY3eqKWI\n",
555 "iIvAzcmtSLEQptGzVU+6tOjC0uilWkcRQtxl1B5G586dOXz4MB06dOD48eOkpKTQr18/YmNjqzJj\n",
556 "qap6D2PGzzNoVq8Zr/d+vdh2retf9eaU5b+/09NJp+nxaQ9OvngSp7pOZaxXCGEok+xh2NnZAfDo\n",
557 "o48SGhrKjRs3yMzMNHqjlqDw6m6ZDsTUCqY/1+l0tHdsT1J4Ek2eaaJ/TafTYW9v/adyC2GOjCoY\n",
558 "L774Ijdv3mTWrFksX76c4cOH89Zbb1VVNk2dSDhBnsrDo4mH1lGqmcLpz+8+IhKgY2NofEr/mtyL\n",
559 "RAhtGH2WVOFcUgABAQGaTkhYlYek3tv7HpdTL7N88PISt2tdh4HMKUsJy3u+Dy33wTeb9W0s+OQ+\n",
560 "ITRnkkNS984lFRUVRY8ePax2Lqktp7fI1d3mInoGNP0NnMO1TiJE9aaM4Onpqa5du6Z/fv36deXp\n",
561 "6WnMKpRSSkVERChXV1fVtm1btXTp0lLbxcTEKFtbW7Vp06YSlxsZ32AJ6QnK/l/2KiMno9Ttgirj\n",
562 "URltTLUdc8pSynKPrxUvdFbo8qrs9y5EdVHevyFN5pKaOXMmISEhhIWFsWLFChITE4u1ycvL49VX\n",
563 "X2XgwIEmP/zw85mfebzN4zxQ4wGTblfcx7FRkF8DOmpzzY8QopxzSRXe47s8c0mlpKQA0KdPHwAG\n",
564 "DBhAdHQ0gYFF5w1atmwZI0aM0OSUXblZkjnSwY6FMHwsnNA6ixDVU7nmkir8uTxzScXGxuLq6qp/\n",
565 "7u7uTlRUVJGCceXKFTZv3swvv/xCbGysSeerys7LZuPhb9k4cSOT0yebbLvCAJd6wZWu0P2y1kmE\n",
566 "qJYMKhgTJkwo8vzQoUPodDo6d+5cFZl46aWXePfdd/Uj+fc7JDVv3jz9z/7+/vj7+1do23su7oFE\n",
567 "BellnckjNBH2Hjz/HTdu36BpvaZapxHCIoSHhxMeHl7xFRkz4BEREaHatWunBgwYoAYMGKDatWun\n",
568 "IiMjjRo0uXXrlvLy8tI/nzZtmgoNDS3Spk2bNsrZ2Vk5OzurevXqqSZNmqjNmzcXW5eR8Q3y0raX\n",
569 "FH3MfADYqrMYsI4A1NTQqZX+uxeiuijvd6dR7woMDFQnT57UPz916pQKDAw0eqNeXl4qIiJCXbhw\n",
570 "QbVv314lJCSU2nbChAkmPUuq/bL2iuYW9OVpdVkMWEdtlOP7jur4n8cr/fcvRHVQ3u9Oo86SSk5O\n",
571 "pkWLFvrnzZs3Jzk52ei9msWLFxMUFES/fv34+9//jqOjIyEhIYSEhBi9rsp0OeUySRlJcF3TGKIs\n",
572 "GfBar9eYvXO21kmEqFaMutJ71apVrFu3jmeeeQalFN999x1jxoxh6tSpVZmxVJV9pffnRz5n+7nt\n",
573 "rH9mPZjrVc9Wn8WwdWTmZOK+0p2Ph3xMX5e+ZbQXQtyryu+HoZTi2rVrXL9+ndDQUHQ6HUOGDMHb\n",
574 "29vojVaWyi4YYzeN5fE2jzOlyxQs6cvTurIYtg6lFBuPb+SdPe9w6IVDcmc+IYxgkoLRsWNHjh07\n",
575 "ZvRGqkplFox8lU/zhc2JeT4G50bOWNKXp3VlMbxgKKXo+VlPXujygtyZTwgjVPlcUjqdDl9fX7Zu\n",
576 "3Wr0RizBsT+PYV/LntYNW2sdRRhIp9OxcMBC3vjlDdKz07WOI4TVM2rQOzo6mqFDh9KsWTO8vb3x\n",
577 "9vausmsxTG3nuZ30dzHuqnWhve4tu9OzVU8+PPCh1lGEsHpGDXqfO3euxN2Ytm3bVmooQ1XmIalB\n",
578 "awcxpfMUnnZ72oDpy83r8Ix1ZTFkHXYU3DfjrkbAFGAlcLvgpfr1G5GaavwZfEJUB1U6hpGTk8P2\n",
579 "7dvZu3cvAQEB+Pn5YWNj1M5JlaisgpGVm4XTB05cfOkijWo3koJhidsZEAy1UmHLx/o2lXlChBDW\n",
580 "pErHMF5//XVWrVqFk5MTb731FosXLzZ6Q+bsQPwB3JzcaFS7kdZRRHlF/hPab4bmh7VOIoTVMmgP\n",
581 "o0uXLkRFRWFnZ8etW7d48skniYiIMEW++6qsPYx//vJPdOj4v8f/T79ei/7XtkVnqcA6vD+FLqvh\n",
582 "0/2gbGUPQ4hSVOkeRn5+PnZ2dgA0bNiQ1NRUozdkzsLOh9HPpZ/WMURFHZ0IyqagcAghKp1Bexi2\n",
583 "trbUqVNH/zwjI4PatWsXrECn06yAVMYexs2Mm7Re3JqEfyRQq0Yt/Xot/l/bFpulguto+iuM6w8r\n",
584 "kuFO3n3XIgPjoroq73enQdOb5+Xd/w/Pku2O203PVj31xUJYuBue8Nv/QL/F8OP9/yDS0mSaeiGM\n",
585 "of2pThrbeX4n/drI4SirEj4f2gIt92mdRAirUu0LRtj5MPo/LBfsWZUse9gODPkb2OSW2VwIYZhq\n",
586 "XTDibsWRmpWKRxMPraOIynYcuN0UfJZpnUQIq1GtC0bh2VE2umrdDdbrpxXQ5x2of0XrJEJYhWr9\n",
587 "TSnjF1Yu6RE4OBUCXtY6iRBWodoWjHyVz67zu+T6C2u353V4MAYe3qF1EiEsXrUtGL9e/xXHOo60\n",
588 "bNBS6yiiKuXUgZ+XweAXoUam1mmEsGjVtmDsPL9Tzo6qLk4PgYQO0OMDrZMIYdGqbcEIOx8m4xfV\n",
589 "yc9LoNsSaHRe6yRCWKxqWTAyczM5EH8Af2d/raMIU0lpDXtfhSeeB12+1mmEsEjVsmDsu7SPjk06\n",
590 "0uCBBlpHEaZ04GWokQFdV2qdRAiLVC0LhoxfVFPKFn74AvzngcMZrdMIYXGqZcGQ8YtqLKk9RL4B\n",
591 "T00omPRWCGGwalcwku4kcSb5DL4P+WodRWglegbk14DuWgcRwrJUu4KxO243vVr1oqZtTa2jCK0o\n",
592 "G9j8OfSCEwkntE4jhMWodgUj8mIk/q39tY4htHbTBX6xocM/O6Cz1aHTFX/Y2ztonVIIs1LtCkbE\n",
593 "xQj6tO6jdQxhDg7mQ2Z/6Pl/FNzBr+gjLe2mpvGEMDfVqmAkZyRz4eYFOjfvrHUUYS42fwrdFhfc\n",
594 "2lUIcV/VqmDsvbSXbg91w87WTusowlyktoSd78Ow8WCbrXUaIcxatSoYERcj8Gvtp3UMYW6OToCU\n",
595 "ltDnba2TCGHWqlXBiLwYKeMXogQ62PIxdFkNrSO1DiOE2ao2BSM1K5WTCSfxedBH6yjCHN1uDj98\n",
596 "DsPHQt0bWqcRwixVm4Kx//J+Hm3xKLVq1NI6ijBXZwcVHJ4a/j+gy9M6jRBmp9oUjIiLEfg5+2Fv\n",
597 "71DiOff3PkQ1tnt+wWy2fm9pnUQIs6NJwYiMjMTNzY127dqxbNmyYsvXrl2Lp6cnnp6ejB07ltOn\n",
598 "T1d8mxcj6dOqz91z64ufc1/0IaotZQub1kHnT+BhrcMIYV40KRgzZ84kJCSEsLAwVqxYQWJiYpHl\n",
599 "Li4uREZG8uuvvxIQEMDbb1fs7JU7OXf49fqvdG8pkwcJA9xuBt+thacgPjVe6zRCmA2TF4yUlBQA\n",
600 "+vTpQ+vWrRkwYADR0dFF2nTv3p0GDQruVREYGEhERESFthkVH0Wnpp2oY1enQusR1UicP8TY0HJW\n",
601 "y1KnDpHpQ0R1Y/KCERsbi6urq/65u7s7UVFRpbb/+OOPGTp0aIW2WTh+IYRR9uZD1iDoG0xphy9l\n",
602 "+hBRndTQOsD9hIWFsWbNGvbv319qm3nz5ul/9vf3x9/fv1ibyIuRzO4xuwoSCqumgO++gqDOcKkX\n",
603 "/PGk1omEKJfw8HDCw8MrvB6dUsqko7wpKSn4+/tz5MgRAKZPn87AgQMJDAws0u63337j6aefZtu2\n",
604 "bbRt27bEdel0OsqKn5WbheMHjlx5+Qr2tezvngVV1kcuq01lrMOctmNOWczwMz8YDWOHwn92wg3P\n",
605 "Ym1M/CckRIUZ8t1ZEpMfkiocm4iMjCQuLo6dO3fi61v0ZkaXLl1i+PDhrF27ttRiYaiYKzG4Orpi\n",
606 "X8u+QusbXyY1AAAWJ0lEQVQR1dgVX/hpeUHRqH9F6zRCaEaTQ1KLFy8mKCiInJwcZsyYgaOjIyEh\n",
607 "IQAEBQXx1ltvkZyczNSpUwGws7MjJiamXNuS6UBEpTg+Ehqdh7FD4PNIyK6vdSIhTM7kh6QqkyG7\n",
608 "VQO+GsA0n2k80f4J/Xss57CJGR6esZrtlCeLgqEvQP2r8M3mgtu8yiEpYYEs5pCUKeXk5RAVH0Wv\n",
609 "Vr20jiKsgg62rgSbXBg4E7nIU1Q3Vl0wDl87TJtGbXCoLefKi0qSbwcbNxTMatt9kdZphDApsz6t\n",
610 "tqJk/EJUiawGsG4rTO4BchmGqEaseg9DbpgkqkxKK/h6MwyFA5cPaJ1GCJOw2oKRl5/Hvsv76N2q\n",
611 "t9ZRhLW61gW+hye/eZLo+Oiy2wth4ay2YPx24zea1WtG03pNtY4irNlZ+PzJzxn69VBirpTv1G8h\n",
612 "LIXVFgwZvxCmEvhIIJ89+RlDvx5K7JVYreMIUWWstmDI+IUwpSGPDOHTJz5lyNdDpGgIq2VVF+7Z\n",
613 "2zv8d/bQfwAhQGpJ77SUi8vM+SI2S99O5WW597/BLX9sYfKPk9k6ditdH+xaxnuF0IZcuAf/vZue\n",
614 "0zHIcoFUuZueMK2h7YfyyROfMOTrIRy8elDrOEJUKuu8DqN1JFyU8QuhjSfaP4FSisB1gWx8ZqOM\n",
615 "pQmrYVV7GHrOEXBRxi+Edp50fZI1w9YwYsMI1vy2Rus4QlQKKywYClpHQJwUDKGt/g/3Z/f43by5\n",
616 "+03mhc+TSQqFxbO+guFwtmAW0VvOWicRgg5NOhA1OYqfz/7MuB/GkZWbpXUkIcrNqs6S0ul00Hk1\n",
617 "OIfDd6UdBrCkM3ks74why9lO5WUp60/I3t6BtMybMAyoC3wDZPx3ef36jUhNTS5jO0JUHjlLqpAM\n",
618 "eAszk5Z2E3IUbMyDy6/C822h8R8UnrmnPxVcCDNnhQVDxi+EmVI2EPYu7HsVJvWCDhu0TiSEUazr\n",
619 "tNoGQI0sSHpE6yRClO7w83DdC4aPhbbb4GetAwlhGOvaw3Dm7uEoncZBRPVRA51Od99Hia4+CiGH\n",
620 "QekgCLnIT1gE6yoYrZHxC2FiuRSfTcDA2QWy68GPn8IvMHjtYN7f9z75Kr/qIwtRTtZXMGT8Qlia\n",
621 "4xA7JZYtp7cw4KsBXEm9onUiIUpkNQXjatpVqA0kdNA6ihBGa92wNbvH78avtR+eH3ny7/3/Jicv\n",
622 "R+tYQhRhNQUj8mIkXKLgTBQhLFANmxq86fcm+yfvJ+x8GJ4febLr/C6tYwmhZzXfrpEXI+Gi1imE\n",
623 "qLhHGj/Cz//zMwv6LmDyj5MZuXEkl1Muax1LCOspGBEXIyBO6xRCVA6dTsdTrk9x4sUTuDm54RXi\n",
624 "xYI9C8jIySj7zUJUEasoGAnpCcSnxsMNrZMIUbnq2NVhvv98YqfEEns1loeXPszC/QtJz07XOpqo\n",
625 "hqyiYOy5tIeeLXuCnJEorJRLIxe+H/U9P/3PT0RdicJlqQsL9iwgNavEW0oKUSWsomDI/buFZSv7\n",
626 "4j97ewcAvJp5sfGZjewev5uTiSdxWeLC3PC5JGfI5IWi6llFwYi8GCl3NRMWrOyL//46QaG7kztf\n",
627 "DfuKqOejuJJ6hYeXPsykzZPYd2mf3HdDVBmLn948+U4yrRa3Iml2ErVq1MKcpr22nO2YUxb5zKW1\n",
628 "ud+f6vXb1/nPr//h0yOfYqOzYZLXJMZ5jqNpvaZlrFdUR9V2evN9l/fh+6AvNW1rah1FCM00q9eM\n",
629 "2T1nc+rFU6weupoTiSdov7w9w9YPI/R0KLn5uVpHFFbA4vcwgncEY1/Tnjf93rw70Zv5/IvQcrZj\n",
630 "TlnkM5fWxtg/1bSsNNYfX8+nRz7lTNIZAtoGENgukIFtB+JQ28GodQnrUt49DIsvGD6rfXi/3/v4\n",
631 "OftJwbCKLPKZS2tTkT/V+NR4fjrzE1vPbCU8LpxOTTsR2C6QwHaBeDTxKH1WXWGVqm3BqPtOXRJn\n",
632 "J/JAjQekYFhFFvnMJbOjYHC8dIbe6jUzN5PwuHC2ntnK1tNbyVN5DGw7kO4Pdcf3QV/aO7bHRmfx\n",
633 "R6vFfVTbgtH7s95ETozUPzefP3BL2o45ZZHPXJE2xv45K6U4lXiKHed2EH0lmpgrMSTeSeTRFo/i\n",
634 "86APPg/64PugL83rNzdqvcK8lbdgWPwd9/yc/bSOIITF0ul0uDm54ebkpn8t8U4isVdiib4SzceH\n",
635 "Pub5H5+ntl1tfB70wc3RjbYObWnn0I52jdvhVMdJDmdVI5rsYURGRhIUFERubi4zZsxg+vTpxdq8\n",
636 "9tprrF+/nkaNGrF27VpcXV2LtdHpdOw4u4P+D/fXPzfPfxGGA/4m2E5F2+ym5JymzGItexjh/Lcv\n",
637 "zXcPIzw8HH9///u2UUpx/uZ5Yq/G8kfiH5y9eZYzSWc4m3yWnPwc2jq0/W8RcWinf+5U16nSDm0Z\n",
638 "ktMcWEpOi9rDmDlzJiEhIbRu3ZqAgADGjBmDo6OjfnlMTAx79uzh4MGDbN++neDgYEJDQ0tcV4+W\n",
639 "PUwVuwLCKf2L2JyEYxk5LUE4ltCXhnzB6XQ6HnZ4mIcdHi627GbGTc4kFxSPM0lnCLsQxqqDqzh3\n",
640 "8xy3Mm/RuHZjmtRtQpO6TWhar2nBz3Xu+blwWd2m1LarXaGc5sBScpaXyQtGSkoKAH36FFyZPWDA\n",
641 "AKKjowkMDNS3iY6OZsSIETg4ODBmzBjeeOONUtdXt2bdqg0shMWoYcDhITug6I2Z5s+ff9/lhqzj\n",
642 "r+rXb8Sdm3dIvJPIn+l/6h830m/wZ/qfnEk+o//5z/Q/uXH7Bjqdjno161HXri51a9bV/1yvZj0u\n",
643 "nLzAlS1X9M8L/3/2rDlkpt6B7LuRcimYU+6eR93a9hz77Vdq2NQo9WGrs5VDawYwecGIjY0tcnjJ\n",
644 "3d2dqKioIgUjJiaG5557Tv/cycmJc+fO8fDDxf+FI4QoVDjFyP389dDWvLuP0pYbso7i0tJ02Nna\n",
645 "0bx+c4MGzJVSZORmkJ6dTnpOOrezb5Oefff/c9L5z57/4NPCR78sNTuVq7evktnkDjw4EmreLnjY\n",
646 "ZoNNbpFHus0pHvvyMXLycsjNzy3xkafysNXZFikidrZ2pRaYwkNtOgqKjE6nQ4eOa4euseXjLfpl\n",
647 "hUXor+0Kf9ayXXmY5aC3UqrY8bXSPmTx1w3pjMpoY+w65hvQpjK2U5E28yk9pymzmPIzV2WW+Qa0\n",
648 "qYztVLTNX3/nlbOdyv4X+6ZVm0pZsqHM98YZcLOcvLv/yyLLuGB/cT30eoXeb85MXjC6du3KP/7x\n",
649 "D/3z48ePM3DgwCJtfH19OXHiBAEBAQAkJCTg4uJSbF0WfEawEEJYHJNfndOgQQOg4EypuLg4du7c\n",
650 "ia+vb5E2vr6+bNq0iaSkJNatW4ebm1tJqxJCCGFCmhySWrx4MUFBQeTk5DBjxgwcHR0JCQkBICgo\n",
651 "CB8fH3r16sWjjz6Kg4MDa9as0SKmEEKIeykzFxERoVxdXVXbtm3V0qVLS2wzZ84c1aZNG9W5c2d1\n",
652 "8uRJEycsUFbO3bt3K3t7e+Xl5aW8vLzU22+/bfKMEydOVE2aNFEeHh6ltjGHviwrpzn0pVJKXbp0\n",
653 "Sfn7+yt3d3fl5+en1q5dW2I7rfvUkJxa92lGRoby8fFRnp6eytfXV3344YclttO6Lw3JqXVf3is3\n",
654 "N1d5eXmpIUOGlLjc2P40+4Lh5eWlIiIiVFxcnGrfvr1KSEgosjw6Olr17NlTJSUlqXXr1qnAwECz\n",
655 "zLl79241dOhQTbIVioyMVIcPHy71i9hc+rKsnObQl0opde3aNXXkyBGllFIJCQmqTZs2KjU1tUgb\n",
656 "c+hTQ3KaQ5+mp6crpZTKzMxUHTp0UGfOnCmy3Bz6Uqmyc5pDXxZauHChGjt2bIl5ytOfZj3D2L3X\n",
657 "bLRu3Vp/zca9/nrNxsmTJ80yJ2g/SN+7d28aNWpU6nJz6EsoOydo35cAzZo1w8vLCwBHR0c6dOjA\n",
658 "wYMHi7Qxhz41JCdo36d16tQB4Pbt2+Tm5lKrVq0iy82hL6HsnKB9XwLEx8fz008/8fzzz5eYpzz9\n",
659 "adYFo7RrNu4VExODu7u7/nnhNRumZEhOnU7H/v378fLy4uWXXzZ5RkOYQ18awhz78uzZsxw/fhwf\n",
660 "H58ir5tbn5aW0xz6ND8/H09PT5o2bcq0adNo2bJlkeXm0pdl5TSHvgSYNWsWH3zwATY2JX/Nl6c/\n",
661 "zbpgGEIZcc2Gljp37szly5eJjY3F3d2dmTNnah2pGOnL8klLS2PUqFEsWrSIunWLzjxgTn16v5zm\n",
662 "0Kc2Njb8+uuvnD17lpUrV3LkyJEiy82lL8vKaQ59GRoaSpMmTfD29i51b6c8/WnWBaNr166cOnVK\n",
663 "//z48eN069atSJvCazYKlXbNRlUyJGf9+vWpU6cOdnZ2TJ48mdjYWLKyKnaBUGUzh740hDn1ZU5O\n",
664 "DsOHD+e5557jySefLLbcXPq0rJzm1KfOzs4MHjy42GFdc+nLQqXlNIe+3L9/Pz/++CNt2rRhzJgx\n",
665 "/PLLL4wbN65Im/L0p1kXDEu5ZsOQnDdu3NBX8y1bttCpU6cSj31qyRz60hDm0pdKKSZPnoyHhwcv\n",
666 "vfRSiW3MoU8Nyal1nyYmJnLr1i0AkpKS2LFjR7HCZg59aUhOrfsSYMGCBVy+fJkLFy7wzTff8Pjj\n",
667 "j/Of//ynSJvy9KdZTg1yL0u5ZqOsnN9++y2rVq2iRo0adOrUiYULF5o845gxY4iIiCAxMZGWLVsy\n",
668 "f/58cnJy9BnNpS/LymkOfQmwb98+1qxZQ6dOnfD29gYK/lAvXbqkz2oOfWpITq379Nq1a4wfP568\n",
669 "vDyaNWtGcHAwzZs3N7u/dUNyat2XJSk81FTR/rToO+4JIYQwHbM+JCWEEMJ8SMEQQghhECkYQggh\n",
670 "DCIFQwghhEGkYIhKZWNjQ3BwsP75v//977/cArTq+fv7c/jwYQACAwNJTU2t0PrCw8MZOnSowa9X\n",
671 "xbaq0tWrV3nmmWdMuk1hmaRgiEpVs2ZNvv/+e5KSkgDjr8TNy8urcIZ7t7l161bs7e0rvE5r1qJF\n",
672 "CzZu3Kh1DGEBpGCISmVnZ8cLL7zAokWLii27evUqM2fOxNPTk1mzZnHjxg0AJkyYwMsvv4yvry+v\n",
673 "vvoqEydO5JVXXsHHx4f27dtz5MgRXnjhBTp06MC8efP06/v73/9O165d6dGjB6tXry4xj7OzM0lJ\n",
674 "SXz00Ud4e3vj7e1NmzZtePzxx4GCecDGjRuHr68vc+bM0V+RGxsbS9++ffH29mb79u1lfu6MjAw+\n",
675 "/PBD/Pz8CAwMJDw8HIDu3bsXuZq2cO8nMzOzxPaluXz5MoMGDcLLywtPT0/OnTtHXFwc7u7uTJ48\n",
676 "GTc3N+bPn6/P//bbb+Pj40PXrl1ZsGBBkfW88soreHt706VLFy5cuEBcXBwdO3YE4IsvvmD06NEM\n",
677 "HjwYDw8Pli5dqn/vtm3b6N69Oz4+Prz00ktMnz69WM6jR4/St29fvLy86Ny5M7dv3y6z74QFqcjU\n",
678 "uUL8Vb169VRqaqpydnZWKSkp6t///reaN2+eUkqpWbNmqffff18ppdSCBQvU7NmzlVJKjR8/Xvn5\n",
679 "+emn3J4wYYIaNGiQysrKUl988YWqV6+eCg8PV1lZWcrNzU0/dXxycrJSSqmsrCzl6+urbt++rZRS\n",
680 "yt/fXx06dEgppZSzs7NKSkrS58vJyVG9e/dWoaGh+ra3bt1SSik1e/Zs9c033yillOrUqZOKjo5W\n",
681 "t2/fVgMHDixxeujdu3fr7zPw+eefqyVLliillLp+/bry8fFRSim1aNEiNXfuXKWUUlevXlXt27e/\n",
682 "b/t713mvuXPnqk8++UT/GTIyMtSFCxeUTqdT3333ncrMzFRPP/20+vbbb4v0TW5urho6dKg6deqU\n",
683 "vq9XrFih77c7d+6oCxcu6KeS//zzz1WTJk3U1atXVWpqqnrooYdUdna2ysnJUc7OzurChQsqKSlJ\n",
684 "de7cWU2fPr1YzvHjx6uwsDClVME04Lm5ucXaCMslexii0tWvX59x48YV+dcpwM8//8ykSZMAmDx5\n",
685 "Mlu2bAEKDiGNGDGC+vXr69uOGDGCmjVr0r17dxo2bIifnx81a9bE29tbPxPwzp07CQwMxNvbm/Pn\n",
686 "z/PLL7+UmW3GjBn07duXwMBADh06xLFjx/D398fb25vQ0FAiIyO5cuUKSil8fHyoW7cuo0aNKnO6\n",
687 "6k2bNrF69Wq8vb0ZOHAgN27c4MKFC4wcOZJvv/0WgA0bNujHCkpqf/78+VLX37VrVxYvXsx7771H\n",
688 "cnIyDzzwAFAwLc2wYcOoVasWY8aMYdu2bQAcPHiQ4cOH06lTJw4fPsyOHTvIzs5m9+7dTJkyBSg4\n",
689 "fFi7du1i2xowYADNmzenfv36uLu7c/jwYaKioujYsSPOzs44ODjwxBNPlNgn3bt3Z86cOSxfvpzc\n",
690 "3FxsbW3L/J0Iy2H2U4MIy/TSSy/RuXNnJk6cWOT10r54mzdvXuR54fxcNWvWpGHDhvrXa9asSXZ2\n",
691 "NmlpacyZM4c9e/bw4IMPMmzYMG7evHnfTF988QWXL19m5cqVQME01R4eHuzevbtIu/j4eMM+5D3y\n",
692 "8/NZsWIFffr0KbascePG/P7772zYsEE/NUNp7Qun6/irwMBAunTpwpo1a+jZsycbN24s0i+FCsdv\n",
693 "pk+fzrfffouHhwezZs3i5s2b6HS6Emco/au/9ndmZiY1atQoMjZU2jqCgoLo37+/fiqS6OhomjZt\n",
694 "et/tCcshexiiSjRq1IiRI0fy6aef6r9oBg8ezJdffkl+fj6fffYZTzzxRLnWrZTi1q1b2NnZ0axZ\n",
695 "M06fPs2uXbvu+55Dhw6xcOFCvvrqK/1rXbt25caNG/o9lvT0dM6cOcNDDz2Era0tsbGxpKens2HD\n",
696 "hjIzjR07lpCQENLS0gCKTHk9atQo3nvvPVJTU/Hw8CizfUkuXLign7uob9+++nGRlJQUfvjhB7Ky\n",
697 "sli/fj0DBw4kMzOTtLQ0nJ2duXLlCps3bwYKxpcee+wxVq9ejVKKrKwsMjIyyvxsOp2Obt268fvv\n",
698 "vxMXF0dycjKhoaElntBw7tw5XFxc+N///V9cXV3N4l4lovJIwRCV6t4vkVdeeYXExET98+DgYC5d\n",
699 "uoS3tzc3btzg5ZdfLvF9f31e0rKWLVsyfPhwPDw8mDZtWqmnohb+q3rFihXcvHmTxx57DG9vb154\n",
700 "4QUAvvrqK1atWkWnTp3o0aMHf/zxBwAff/wxr732Gr169cLT07PEL0edTqd/fcSIEfj4+BAQEICH\n",
701 "hwdz587VtxsxYgTr169n5MiRRV4rqf2967zXhg0b8PDwoGvXrty5c0e/LldXV3788Ue8vLzw8PAg\n",
702 "MDCQBx54gDlz5uDj48OoUaMYPHiwfj3vvPMOZ8+exdPTk549e+pPPCjcZmnbt7W1Zfny5YwaNYqB\n",
703 "AwfSsWNH2rRpU6zdkiVL6NixIz4+Pri6utKjR48Sfy/CMsnkg0JYqLi4OIYOHcrvv/9uku2lp6dT\n",
704 "t25dUlJSGDJkCJ988gnt27c3ybaFeZAxDCEsmCnvODdv3jzCwsKws7Pj2WeflWJRDckehhBCCIPI\n",
705 "GIYQQgiDSMEQQghhECkYQgghDCIFQwghhEGkYAghhDCIFAwhhBAG+X8LAxP1Be5fBAAAAABJRU5E\n",
706 "rkJggg==\n"
707 ]
708 },
709 "metadata": {},
710 "output_type": "display_data"
711 }
712 ],
713 "source": [
714 "hist_data = hist(serial_diffs, bins=30, normed=True)\n",
715 "plot(s, rhos)\n",
716 "xlabel('Normalized level spacing s')\n",
717 "ylabel('Probability $P(s)$')"
718 ]
719 },
720 {
721 "cell_type": "markdown",
722 "metadata": {},
723 "source": [
724 "## Parallel calculation of nearest neighbor eigenvalue distribution"
725 ]
726 },
727 {
728 "cell_type": "markdown",
729 "metadata": {},
730 "source": [
731 "Here we perform a parallel computation, where each process constructs and diagonalizes a subset of\n",
732 "the overall set of random matrices."
733 ]
734 },
735 {
736 "cell_type": "code",
737 "execution_count": 11,
738 "metadata": {
739 "collapsed": true
740 },
741 "outputs": [],
742 "source": [
743 "def parallel_diffs(rc, num, N):\n",
744 " nengines = len(rc.targets)\n",
745 " num_per_engine = num/nengines\n",
746 " print \"Running with\", num_per_engine, \"per engine.\"\n",
747 " ar = rc.apply_async(ensemble_diffs, num_per_engine, N)\n",
748 " diffs = np.array(ar.get()).flatten()\n",
749 " normalized_diffs = normalize_diffs(diffs)\n",
750 " return normalized_diffs"
751 ]
752 },
753 {
754 "cell_type": "code",
755 "execution_count": 12,
756 "metadata": {
757 "collapsed": true
758 },
759 "outputs": [],
760 "source": [
761 "client = Client()\n",
762 "view = client[:]\n",
763 "view.run('rmtkernel.py')\n",
764 "view.block = False"
765 ]
766 },
767 {
768 "cell_type": "code",
769 "execution_count": 13,
770 "metadata": {
771 "collapsed": true
772 },
773 "outputs": [],
774 "source": [
775 "parallel_nmats = 40*serial_nmats\n",
776 "parallel_matsize = 50"
777 ]
778 },
779 {
780 "cell_type": "code",
781 "execution_count": 14,
782 "metadata": {
783 "collapsed": false
784 },
785 "outputs": [
786 {
787 "name": "stdout",
788 "output_type": "stream",
789 "text": [
790 "Running with 10000 per engine.\n",
791 "1 loops, best of 1: 14 s per loop"
792 ]
793 }
794 ],
795 "source": [
796 "%timeit -r1 -n1 parallel_diffs(view, parallel_nmats, parallel_matsize)"
797 ]
798 }
799 ],
800 "metadata": {
801 "kernelspec": {
802 "display_name": "Python 3",
803 "language": "python",
804 "name": "python3"
805 },
806 "language_info": {
807 "codemirror_mode": {
808 "name": "ipython",
809 "version": 3
810 },
811 "file_extension": ".py",
812 "mimetype": "text/x-python",
813 "name": "python",
814 "nbconvert_exporter": "python",
815 "pygments_lexer": "ipython3",
816 "version": "3.4.2"
817 }
818 },
819 "nbformat": 4,
820 "nbformat_minor": 0
821 }
@@ -1,42 +0,0 b''
1 #-------------------------------------------------------------------------------
2 # Core routines for computing properties of symmetric random matrices.
3 #-------------------------------------------------------------------------------
4
5 import numpy as np
6 ra = np.random
7 la = np.linalg
8
9 def GOE(N):
10 """Creates an NxN element of the Gaussian Orthogonal Ensemble"""
11 m = ra.standard_normal((N,N))
12 m += m.T
13 return m/2
14
15
16 def center_eigenvalue_diff(mat):
17 """Compute the eigvals of mat and then find the center eigval difference."""
18 N = len(mat)
19 evals = np.sort(la.eigvals(mat))
20 diff = np.abs(evals[N/2] - evals[N/2-1])
21 return diff
22
23
24 def ensemble_diffs(num, N):
25 """Return num eigenvalue diffs for the NxN GOE ensemble."""
26 diffs = np.empty(num)
27 for i in xrange(num):
28 mat = GOE(N)
29 diffs[i] = center_eigenvalue_diff(mat)
30 return diffs
31
32
33 def normalize_diffs(diffs):
34 """Normalize an array of eigenvalue diffs."""
35 return diffs/diffs.mean()
36
37
38 def normalized_ensemble_diffs(num, N):
39 """Return num *normalized* eigenvalue diffs for the NxN GOE ensemble."""
40 diffs = ensemble_diffs(num, N)
41 return normalize_diffs(diffs)
42
@@ -1,71 +0,0 b''
1 #!/usr/bin/env python
2 """Test the performance of the task farming system.
3
4 This script submits a set of tasks via a LoadBalancedView. The tasks
5 are basically just a time.sleep(t), where t is a random number between
6 two limits that can be configured at the command line. To run
7 the script there must first be an IPython controller and engines running::
8
9 ipcluster start -n 16
10
11 A good test to run with 16 engines is::
12
13 python task_profiler.py -n 128 -t 0.01 -T 1.0
14
15 This should show a speedup of 13-14x. The limitation here is that the
16 overhead of a single task is about 0.001-0.01 seconds.
17 """
18 import random, sys
19 from optparse import OptionParser
20
21 from IPython.utils.timing import time
22 from IPython.parallel import Client
23
24 def main():
25 parser = OptionParser()
26 parser.set_defaults(n=100)
27 parser.set_defaults(tmin=1e-3)
28 parser.set_defaults(tmax=1)
29 parser.set_defaults(profile='default')
30
31 parser.add_option("-n", type='int', dest='n',
32 help='the number of tasks to run')
33 parser.add_option("-t", type='float', dest='tmin',
34 help='the minimum task length in seconds')
35 parser.add_option("-T", type='float', dest='tmax',
36 help='the maximum task length in seconds')
37 parser.add_option("-p", '--profile', type='str', dest='profile',
38 help="the cluster profile [default: 'default']")
39
40 (opts, args) = parser.parse_args()
41 assert opts.tmax >= opts.tmin, "tmax must not be smaller than tmin"
42
43 rc = Client()
44 view = rc.load_balanced_view()
45 print(view)
46 rc.block=True
47 nengines = len(rc.ids)
48 with rc[:].sync_imports():
49 from IPython.utils.timing import time
50
51 # the jobs should take a random time within a range
52 times = [random.random()*(opts.tmax-opts.tmin)+opts.tmin for i in range(opts.n)]
53 stime = sum(times)
54
55 print("executing %i tasks, totalling %.1f secs on %i engines"%(opts.n, stime, nengines))
56 time.sleep(1)
57 start = time.time()
58 amr = view.map(time.sleep, times)
59 amr.get()
60 stop = time.time()
61
62 ptime = stop-start
63 scale = stime/ptime
64
65 print("executed %.1f secs in %.1f secs"%(stime, ptime))
66 print("%.3fx parallel performance on %i engines"%(scale, nengines))
67 print("%.1f%% of theoretical max"%(100*scale/nengines))
68
69
70 if __name__ == '__main__':
71 main()
@@ -1,66 +0,0 b''
1 from __future__ import print_function
2
3 import time
4 import numpy as np
5 from IPython import parallel
6
7 nlist = map(int, np.logspace(2,9,16,base=2))
8 nlist2 = map(int, np.logspace(2,8,15,base=2))
9 tlist = map(int, np.logspace(7,22,16,base=2))
10 nt = 16
11 def wait(t=0):
12 import time
13 time.sleep(t)
14
15 def echo(s=''):
16 return s
17
18 def time_throughput(nmessages, t=0, f=wait):
19 client = parallel.Client()
20 view = client.load_balanced_view()
21 # do one ping before starting timing
22 if f is echo:
23 t = np.random.random(t/8)
24 view.apply_sync(echo, '')
25 client.spin()
26 tic = time.time()
27 for i in xrange(nmessages):
28 view.apply(f, t)
29 lap = time.time()
30 client.wait()
31 toc = time.time()
32 return lap-tic, toc-tic
33
34
35 def do_runs(nlist,t=0,f=wait, trials=2, runner=time_throughput):
36 A = np.zeros((len(nlist),2))
37 for i,n in enumerate(nlist):
38 t1 = t2 = 0
39 for _ in range(trials):
40 time.sleep(.25)
41 ts = runner(n,t,f)
42 t1 += ts[0]
43 t2 += ts[1]
44 t1 /= trials
45 t2 /= trials
46 A[i] = (t1,t2)
47 A[i] = n/A[i]
48 print(n,A[i])
49 return A
50
51 def do_echo(n,tlist=[0],f=echo, trials=2, runner=time_throughput):
52 A = np.zeros((len(tlist),2))
53 for i,t in enumerate(tlist):
54 t1 = t2 = 0
55 for _ in range(trials):
56 time.sleep(.25)
57 ts = runner(n,t,f)
58 t1 += ts[0]
59 t2 += ts[1]
60 t1 /= trials
61 t2 /= trials
62 A[i] = (t1,t2)
63 A[i] = n/A[i]
64 print(t,A[i])
65 return A
66
@@ -1,492 +0,0 b''
1 #!/usr/bin/env python
2 """A rectangular domain partitioner and associated communication
3 functionality for solving PDEs in (1D,2D) using FDM
4 written in the python language
5
6 The global solution domain is assumed to be of rectangular shape,
7 where the number of cells in each direction is stored in nx, ny, nz
8
9 The numerical scheme is fully explicit
10
11 Authors
12 -------
13
14 * Xing Cai
15 * Min Ragan-Kelley
16
17 """
18 from __future__ import print_function
19 import time
20
21 from numpy import zeros, ascontiguousarray, frombuffer
22 try:
23 from mpi4py import MPI
24 except ImportError:
25 pass
26 else:
27 mpi = MPI.COMM_WORLD
28
29 class RectPartitioner:
30 """
31 Responsible for a rectangular partitioning of a global domain,
32 which is expressed as the numbers of cells in the different
33 spatial directions. The partitioning info is expressed as an
34 array of integers, each indicating the number of subdomains in
35 one spatial direction.
36 """
37
38 def __init__(self, my_id=-1, num_procs=-1, \
39 global_num_cells=[], num_parts=[]):
40 self.nsd = 0
41 self.my_id = my_id
42 self.num_procs = num_procs
43 self.redim (global_num_cells, num_parts)
44
45 def redim (self, global_num_cells, num_parts):
46 nsd_ = len(global_num_cells)
47 # print("Inside the redim function, nsd=%d" %nsd_)
48
49 if nsd_<1 | nsd_>3 | nsd_!=len(num_parts):
50 print('The input global_num_cells is not ok!')
51 return
52
53 self.nsd = nsd_
54 self.global_num_cells = global_num_cells
55 self.num_parts = num_parts
56
57 def prepare_communication (self):
58 """
59 Find the subdomain rank (tuple) for each processor and
60 determine the neighbor info.
61 """
62
63 nsd_ = self.nsd
64 if nsd_<1:
65 print('Number of space dimensions is %d, nothing to do' %nsd_)
66 return
67
68 self.subd_rank = [-1,-1,-1]
69 self.subd_lo_ix = [-1,-1,-1]
70 self.subd_hi_ix = [-1,-1,-1]
71 self.lower_neighbors = [-1,-1,-1]
72 self.upper_neighbors = [-1,-1,-1]
73
74 num_procs = self.num_procs
75 my_id = self.my_id
76
77 num_subds = 1
78 for i in range(nsd_):
79 num_subds = num_subds*self.num_parts[i]
80 if my_id==0:
81 print("# subds=", num_subds)
82 # should check num_subds againt num_procs
83
84 offsets = [1, 0, 0]
85
86 # find the subdomain rank
87 self.subd_rank[0] = my_id%self.num_parts[0]
88 if nsd_>=2:
89 offsets[1] = self.num_parts[0]
90 self.subd_rank[1] = my_id/offsets[1]
91 if nsd_==3:
92 offsets[1] = self.num_parts[0]
93 offsets[2] = self.num_parts[0]*self.num_parts[1]
94 self.subd_rank[1] = (my_id%offsets[2])/self.num_parts[0]
95 self.subd_rank[2] = my_id/offsets[2]
96
97 print("my_id=%d, subd_rank: "%my_id, self.subd_rank)
98 if my_id==0:
99 print("offsets=", offsets)
100
101 # find the neighbor ids
102 for i in range(nsd_):
103 rank = self.subd_rank[i]
104 if rank>0:
105 self.lower_neighbors[i] = my_id-offsets[i]
106 if rank<self.num_parts[i]-1:
107 self.upper_neighbors[i] = my_id+offsets[i]
108
109 k = self.global_num_cells[i]/self.num_parts[i]
110 m = self.global_num_cells[i]%self.num_parts[i]
111
112 ix = rank*k+max(0,rank+m-self.num_parts[i])
113 self.subd_lo_ix[i] = ix
114
115 ix = ix+k
116 if rank>=(self.num_parts[i]-m):
117 ix = ix+1 # load balancing
118 if rank<self.num_parts[i]-1:
119 ix = ix+1 # one cell of overlap
120 self.subd_hi_ix[i] = ix
121
122 print("subd_rank:",self.subd_rank,\
123 "lower_neig:", self.lower_neighbors, \
124 "upper_neig:", self.upper_neighbors)
125 print("subd_rank:",self.subd_rank,"subd_lo_ix:", self.subd_lo_ix, \
126 "subd_hi_ix:", self.subd_hi_ix)
127
128
129 class RectPartitioner1D(RectPartitioner):
130 """
131 Subclass of RectPartitioner, for 1D problems
132 """
133 def prepare_communication (self):
134 """
135 Prepare the buffers to be used for later communications
136 """
137
138 RectPartitioner.prepare_communication (self)
139
140 if self.lower_neighbors[0]>=0:
141 self.in_lower_buffers = [zeros(1, float)]
142 self.out_lower_buffers = [zeros(1, float)]
143 if self.upper_neighbors[0]>=0:
144 self.in_upper_buffers = [zeros(1, float)]
145 self.out_upper_buffers = [zeros(1, float)]
146
147 def get_num_loc_cells(self):
148 return [self.subd_hi_ix[0]-self.subd_lo_ix[0]]
149
150
151 class RectPartitioner2D(RectPartitioner):
152 """
153 Subclass of RectPartitioner, for 2D problems
154 """
155 def prepare_communication (self):
156 """
157 Prepare the buffers to be used for later communications
158 """
159
160 RectPartitioner.prepare_communication (self)
161
162 self.in_lower_buffers = [[], []]
163 self.out_lower_buffers = [[], []]
164 self.in_upper_buffers = [[], []]
165 self.out_upper_buffers = [[], []]
166
167 size1 = self.subd_hi_ix[1]-self.subd_lo_ix[1]+1
168 if self.lower_neighbors[0]>=0:
169 self.in_lower_buffers[0] = zeros(size1, float)
170 self.out_lower_buffers[0] = zeros(size1, float)
171 if self.upper_neighbors[0]>=0:
172 self.in_upper_buffers[0] = zeros(size1, float)
173 self.out_upper_buffers[0] = zeros(size1, float)
174
175 size0 = self.subd_hi_ix[0]-self.subd_lo_ix[0]+1
176 if self.lower_neighbors[1]>=0:
177 self.in_lower_buffers[1] = zeros(size0, float)
178 self.out_lower_buffers[1] = zeros(size0, float)
179 if self.upper_neighbors[1]>=0:
180 self.in_upper_buffers[1] = zeros(size0, float)
181 self.out_upper_buffers[1] = zeros(size0, float)
182
183 def get_num_loc_cells(self):
184 return [self.subd_hi_ix[0]-self.subd_lo_ix[0],\
185 self.subd_hi_ix[1]-self.subd_lo_ix[1]]
186
187
188 class MPIRectPartitioner2D(RectPartitioner2D):
189 """
190 Subclass of RectPartitioner2D, which uses MPI via mpi4py for communication
191 """
192
193 def __init__(self, my_id=-1, num_procs=-1,
194 global_num_cells=[], num_parts=[],
195 slice_copy=True):
196 RectPartitioner.__init__(self, my_id, num_procs,
197 global_num_cells, num_parts)
198 self.slice_copy = slice_copy
199
200 def update_internal_boundary (self, solution_array):
201 nsd_ = self.nsd
202 if nsd_!=len(self.in_lower_buffers) | nsd_!=len(self.out_lower_buffers):
203 print("Buffers for communicating with lower neighbors not ready")
204 return
205 if nsd_!=len(self.in_upper_buffers) | nsd_!=len(self.out_upper_buffers):
206 print("Buffers for communicating with upper neighbors not ready")
207 return
208
209 loc_nx = self.subd_hi_ix[0]-self.subd_lo_ix[0]
210 loc_ny = self.subd_hi_ix[1]-self.subd_lo_ix[1]
211
212 lower_x_neigh = self.lower_neighbors[0]
213 upper_x_neigh = self.upper_neighbors[0]
214 lower_y_neigh = self.lower_neighbors[1]
215 upper_y_neigh = self.upper_neighbors[1]
216
217 # communicate in the x-direction first
218 if lower_x_neigh>-1:
219 if self.slice_copy:
220 self.out_lower_buffers[0] = ascontiguousarray(solution_array[1,:])
221 else:
222 for i in xrange(0,loc_ny+1):
223 self.out_lower_buffers[0][i] = solution_array[1,i]
224 mpi.Isend(self.out_lower_buffers[0], lower_x_neigh)
225
226 if upper_x_neigh>-1:
227 mpi.Recv(self.in_upper_buffers[0], upper_x_neigh)
228 if self.slice_copy:
229 solution_array[loc_nx,:] = self.in_upper_buffers[0]
230 self.out_upper_buffers[0] = ascontiguousarray(solution_array[loc_nx-1,:])
231 else:
232 for i in xrange(0,loc_ny+1):
233 solution_array[loc_nx,i] = self.in_upper_buffers[0][i]
234 self.out_upper_buffers[0][i] = solution_array[loc_nx-1,i]
235 mpi.Isend(self.out_upper_buffers[0], upper_x_neigh)
236
237 if lower_x_neigh>-1:
238 mpi.Recv(self.in_lower_buffers[0], lower_x_neigh)
239 if self.slice_copy:
240 solution_array[0,:] = self.in_lower_buffers[0]
241 else:
242 for i in xrange(0,loc_ny+1):
243 solution_array[0,i] = self.in_lower_buffers[0][i]
244
245 # communicate in the y-direction afterwards
246 if lower_y_neigh>-1:
247 if self.slice_copy:
248 self.out_lower_buffers[1] = ascontiguousarray(solution_array[:,1])
249 else:
250 for i in xrange(0,loc_nx+1):
251 self.out_lower_buffers[1][i] = solution_array[i,1]
252 mpi.Isend(self.out_lower_buffers[1], lower_y_neigh)
253
254 if upper_y_neigh>-1:
255 mpi.Recv(self.in_upper_buffers[1], upper_y_neigh)
256 if self.slice_copy:
257 solution_array[:,loc_ny] = self.in_upper_buffers[1]
258 self.out_upper_buffers[1] = ascontiguousarray(solution_array[:,loc_ny-1])
259 else:
260 for i in xrange(0,loc_nx+1):
261 solution_array[i,loc_ny] = self.in_upper_buffers[1][i]
262 self.out_upper_buffers[1][i] = solution_array[i,loc_ny-1]
263 mpi.Isend(self.out_upper_buffers[1], upper_y_neigh)
264
265 if lower_y_neigh>-1:
266 mpi.Recv(self.in_lower_buffers[1], lower_y_neigh)
267 if self.slice_copy:
268 solution_array[:,0] = self.in_lower_buffers[1]
269 else:
270 for i in xrange(0,loc_nx+1):
271 solution_array[i,0] = self.in_lower_buffers[1][i]
272
273 class ZMQRectPartitioner2D(RectPartitioner2D):
274 """
275 Subclass of RectPartitioner2D, which uses 0MQ via pyzmq for communication
276 The first two arguments must be `comm`, an EngineCommunicator object,
277 and `addrs`, a dict of connection information for other EngineCommunicator
278 objects.
279 """
280
281 def __init__(self, comm, addrs, my_id=-1, num_procs=-1,
282 global_num_cells=[], num_parts=[],
283 slice_copy=True):
284 RectPartitioner.__init__(self, my_id, num_procs,
285 global_num_cells, num_parts)
286 self.slice_copy = slice_copy
287 self.comm = comm # an Engine
288 self.addrs = addrs
289
290 def prepare_communication(self):
291 RectPartitioner2D.prepare_communication(self)
292 # connect west/south to east/north
293 west_id,south_id = self.lower_neighbors[:2]
294 west = self.addrs.get(west_id, None)
295 south = self.addrs.get(south_id, None)
296 self.comm.connect(south, west)
297
298 def update_internal_boundary_x_y (self, solution_array):
299 """update the inner boundary with the same send/recv pattern as the MPIPartitioner"""
300 nsd_ = self.nsd
301 dtype = solution_array.dtype
302 if nsd_!=len(self.in_lower_buffers) | nsd_!=len(self.out_lower_buffers):
303 print("Buffers for communicating with lower neighbors not ready")
304 return
305 if nsd_!=len(self.in_upper_buffers) | nsd_!=len(self.out_upper_buffers):
306 print("Buffers for communicating with upper neighbors not ready")
307 return
308
309 loc_nx = self.subd_hi_ix[0]-self.subd_lo_ix[0]
310 loc_ny = self.subd_hi_ix[1]-self.subd_lo_ix[1]
311
312 lower_x_neigh = self.lower_neighbors[0]
313 upper_x_neigh = self.upper_neighbors[0]
314 lower_y_neigh = self.lower_neighbors[1]
315 upper_y_neigh = self.upper_neighbors[1]
316 trackers = []
317 flags = dict(copy=False, track=False)
318 # communicate in the x-direction first
319 if lower_x_neigh>-1:
320 if self.slice_copy:
321 self.out_lower_buffers[0] = ascontiguousarray(solution_array[1,:])
322 else:
323 for i in xrange(0,loc_ny+1):
324 self.out_lower_buffers[0][i] = solution_array[1,i]
325 t = self.comm.west.send(self.out_lower_buffers[0], **flags)
326 trackers.append(t)
327
328 if upper_x_neigh>-1:
329 msg = self.comm.east.recv(copy=False)
330 self.in_upper_buffers[0] = frombuffer(msg, dtype=dtype)
331 if self.slice_copy:
332 solution_array[loc_nx,:] = self.in_upper_buffers[0]
333 self.out_upper_buffers[0] = ascontiguousarray(solution_array[loc_nx-1,:])
334 else:
335 for i in xrange(0,loc_ny+1):
336 solution_array[loc_nx,i] = self.in_upper_buffers[0][i]
337 self.out_upper_buffers[0][i] = solution_array[loc_nx-1,i]
338 t = self.comm.east.send(self.out_upper_buffers[0], **flags)
339 trackers.append(t)
340
341
342 if lower_x_neigh>-1:
343 msg = self.comm.west.recv(copy=False)
344 self.in_lower_buffers[0] = frombuffer(msg, dtype=dtype)
345 if self.slice_copy:
346 solution_array[0,:] = self.in_lower_buffers[0]
347 else:
348 for i in xrange(0,loc_ny+1):
349 solution_array[0,i] = self.in_lower_buffers[0][i]
350
351 # communicate in the y-direction afterwards
352 if lower_y_neigh>-1:
353 if self.slice_copy:
354 self.out_lower_buffers[1] = ascontiguousarray(solution_array[:,1])
355 else:
356 for i in xrange(0,loc_nx+1):
357 self.out_lower_buffers[1][i] = solution_array[i,1]
358 t = self.comm.south.send(self.out_lower_buffers[1], **flags)
359 trackers.append(t)
360
361
362 if upper_y_neigh>-1:
363 msg = self.comm.north.recv(copy=False)
364 self.in_upper_buffers[1] = frombuffer(msg, dtype=dtype)
365 if self.slice_copy:
366 solution_array[:,loc_ny] = self.in_upper_buffers[1]
367 self.out_upper_buffers[1] = ascontiguousarray(solution_array[:,loc_ny-1])
368 else:
369 for i in xrange(0,loc_nx+1):
370 solution_array[i,loc_ny] = self.in_upper_buffers[1][i]
371 self.out_upper_buffers[1][i] = solution_array[i,loc_ny-1]
372 t = self.comm.north.send(self.out_upper_buffers[1], **flags)
373 trackers.append(t)
374
375 if lower_y_neigh>-1:
376 msg = self.comm.south.recv(copy=False)
377 self.in_lower_buffers[1] = frombuffer(msg, dtype=dtype)
378 if self.slice_copy:
379 solution_array[:,0] = self.in_lower_buffers[1]
380 else:
381 for i in xrange(0,loc_nx+1):
382 solution_array[i,0] = self.in_lower_buffers[1][i]
383
384 # wait for sends to complete:
385 if flags['track']:
386 for t in trackers:
387 t.wait()
388
389 def update_internal_boundary_send_recv (self, solution_array):
390 """update the inner boundary, sending first, then recving"""
391 nsd_ = self.nsd
392 dtype = solution_array.dtype
393 if nsd_!=len(self.in_lower_buffers) | nsd_!=len(self.out_lower_buffers):
394 print("Buffers for communicating with lower neighbors not ready")
395 return
396 if nsd_!=len(self.in_upper_buffers) | nsd_!=len(self.out_upper_buffers):
397 print("Buffers for communicating with upper neighbors not ready")
398 return
399
400 loc_nx = self.subd_hi_ix[0]-self.subd_lo_ix[0]
401 loc_ny = self.subd_hi_ix[1]-self.subd_lo_ix[1]
402
403 lower_x_neigh = self.lower_neighbors[0]
404 upper_x_neigh = self.upper_neighbors[0]
405 lower_y_neigh = self.lower_neighbors[1]
406 upper_y_neigh = self.upper_neighbors[1]
407 trackers = []
408 flags = dict(copy=False, track=False)
409
410 # send in all directions first
411 if lower_x_neigh>-1:
412 if self.slice_copy:
413 self.out_lower_buffers[0] = ascontiguousarray(solution_array[1,:])
414 else:
415 for i in xrange(0,loc_ny+1):
416 self.out_lower_buffers[0][i] = solution_array[1,i]
417 t = self.comm.west.send(self.out_lower_buffers[0], **flags)
418 trackers.append(t)
419
420 if lower_y_neigh>-1:
421 if self.slice_copy:
422 self.out_lower_buffers[1] = ascontiguousarray(solution_array[:,1])
423 else:
424 for i in xrange(0,loc_nx+1):
425 self.out_lower_buffers[1][i] = solution_array[i,1]
426 t = self.comm.south.send(self.out_lower_buffers[1], **flags)
427 trackers.append(t)
428
429 if upper_x_neigh>-1:
430 if self.slice_copy:
431 self.out_upper_buffers[0] = ascontiguousarray(solution_array[loc_nx-1,:])
432 else:
433 for i in xrange(0,loc_ny+1):
434 self.out_upper_buffers[0][i] = solution_array[loc_nx-1,i]
435 t = self.comm.east.send(self.out_upper_buffers[0], **flags)
436 trackers.append(t)
437
438 if upper_y_neigh>-1:
439 if self.slice_copy:
440 self.out_upper_buffers[1] = ascontiguousarray(solution_array[:,loc_ny-1])
441 else:
442 for i in xrange(0,loc_nx+1):
443 self.out_upper_buffers[1][i] = solution_array[i,loc_ny-1]
444 t = self.comm.north.send(self.out_upper_buffers[1], **flags)
445 trackers.append(t)
446
447
448 # now start receiving
449 if upper_x_neigh>-1:
450 msg = self.comm.east.recv(copy=False)
451 self.in_upper_buffers[0] = frombuffer(msg, dtype=dtype)
452 if self.slice_copy:
453 solution_array[loc_nx,:] = self.in_upper_buffers[0]
454 else:
455 for i in xrange(0,loc_ny+1):
456 solution_array[loc_nx,i] = self.in_upper_buffers[0][i]
457
458 if lower_x_neigh>-1:
459 msg = self.comm.west.recv(copy=False)
460 self.in_lower_buffers[0] = frombuffer(msg, dtype=dtype)
461 if self.slice_copy:
462 solution_array[0,:] = self.in_lower_buffers[0]
463 else:
464 for i in xrange(0,loc_ny+1):
465 solution_array[0,i] = self.in_lower_buffers[0][i]
466
467 if upper_y_neigh>-1:
468 msg = self.comm.north.recv(copy=False)
469 self.in_upper_buffers[1] = frombuffer(msg, dtype=dtype)
470 if self.slice_copy:
471 solution_array[:,loc_ny] = self.in_upper_buffers[1]
472 else:
473 for i in xrange(0,loc_nx+1):
474 solution_array[i,loc_ny] = self.in_upper_buffers[1][i]
475
476 if lower_y_neigh>-1:
477 msg = self.comm.south.recv(copy=False)
478 self.in_lower_buffers[1] = frombuffer(msg, dtype=dtype)
479 if self.slice_copy:
480 solution_array[:,0] = self.in_lower_buffers[1]
481 else:
482 for i in xrange(0,loc_nx+1):
483 solution_array[i,0] = self.in_lower_buffers[1][i]
484
485 # wait for sends to complete:
486 if flags['track']:
487 for t in trackers:
488 t.wait()
489
490 # use send/recv pattern instead of x/y sweeps
491 update_internal_boundary = update_internal_boundary_send_recv
492
@@ -1,59 +0,0 b''
1 #!/usr/bin/env python
2 """A simple Communicator class that has N,E,S,W neighbors connected via 0MQ PEER sockets"""
3
4 import socket
5
6 import zmq
7
8 from IPython.parallel.util import disambiguate_url
9
10 class EngineCommunicator(object):
11 """An object that connects Engines to each other.
12 north and east sockets listen, while south and west sockets connect.
13
14 This class is useful in cases where there is a set of nodes that
15 must communicate only with their nearest neighbors.
16 """
17
18 def __init__(self, interface='tcp://*', identity=None):
19 self._ctx = zmq.Context()
20 self.north = self._ctx.socket(zmq.PAIR)
21 self.west = self._ctx.socket(zmq.PAIR)
22 self.south = self._ctx.socket(zmq.PAIR)
23 self.east = self._ctx.socket(zmq.PAIR)
24
25 # bind to ports
26 northport = self.north.bind_to_random_port(interface)
27 eastport = self.east.bind_to_random_port(interface)
28
29 self.north_url = interface+":%i"%northport
30 self.east_url = interface+":%i"%eastport
31
32 # guess first public IP from socket
33 self.location = socket.gethostbyname_ex(socket.gethostname())[-1][0]
34
35 def __del__(self):
36 self.north.close()
37 self.south.close()
38 self.east.close()
39 self.west.close()
40 self._ctx.term()
41
42 @property
43 def info(self):
44 """return the connection info for this object's sockets."""
45 return (self.location, self.north_url, self.east_url)
46
47 def connect(self, south_peer=None, west_peer=None):
48 """connect to peers. `peers` will be a 3-tuples, of the form:
49 (location, north_addr, east_addr)
50 as produced by
51 """
52 if south_peer is not None:
53 location, url, _ = south_peer
54 self.south.connect(disambiguate_url(url, location))
55 if west_peer is not None:
56 location, _, url = west_peer
57 self.west.connect(disambiguate_url(url, location))
58
59
@@ -1,205 +0,0 b''
1 #!/usr/bin/env python
2 """
3 A simple python program of solving a 2D wave equation in parallel.
4 Domain partitioning and inter-processor communication
5 are done by an object of class MPIRectPartitioner2D
6 (which is a subclass of RectPartitioner2D and uses MPI via mpi4py)
7
8 An example of running the program is (8 processors, 4x2 partition,
9 400x100 grid cells)::
10
11 $ ipcluster start --engines=MPIExec -n 8 # start 8 engines with mpiexec
12 $ python parallelwave-mpi.py --grid 400 100 --partition 4 2
13
14 See also parallelwave-mpi, which runs the same program, but uses MPI
15 (via mpi4py) for the inter-engine communication.
16
17 Authors
18 -------
19
20 * Xing Cai
21 * Min Ragan-Kelley
22
23 """
24
25 import sys
26 import time
27
28 from numpy import exp, zeros, newaxis, sqrt
29
30 from IPython.external import argparse
31 from IPython.parallel import Client, Reference
32
33 def setup_partitioner(index, num_procs, gnum_cells, parts):
34 """create a partitioner in the engine namespace"""
35 global partitioner
36 p = MPIRectPartitioner2D(my_id=index, num_procs=num_procs)
37 p.redim(global_num_cells=gnum_cells, num_parts=parts)
38 p.prepare_communication()
39 # put the partitioner into the global namespace:
40 partitioner=p
41
42 def setup_solver(*args, **kwargs):
43 """create a WaveSolver in the engine namespace"""
44 global solver
45 solver = WaveSolver(*args, **kwargs)
46
47 def wave_saver(u, x, y, t):
48 """save the wave log"""
49 global u_hist
50 global t_hist
51 t_hist.append(t)
52 u_hist.append(1.0*u)
53
54
55 # main program:
56 if __name__ == '__main__':
57
58 parser = argparse.ArgumentParser()
59 paa = parser.add_argument
60 paa('--grid', '-g',
61 type=int, nargs=2, default=[100,100], dest='grid',
62 help="Cells in the grid, e.g. --grid 100 200")
63 paa('--partition', '-p',
64 type=int, nargs=2, default=None,
65 help="Process partition grid, e.g. --partition 4 2 for 4x2")
66 paa('-c',
67 type=float, default=1.,
68 help="Wave speed (I think)")
69 paa('-Ly',
70 type=float, default=1.,
71 help="system size (in y)")
72 paa('-Lx',
73 type=float, default=1.,
74 help="system size (in x)")
75 paa('-t', '--tstop',
76 type=float, default=1.,
77 help="Time units to run")
78 paa('--profile',
79 type=unicode, default=u'default',
80 help="Specify the ipcluster profile for the client to connect to.")
81 paa('--save',
82 action='store_true',
83 help="Add this flag to save the time/wave history during the run.")
84 paa('--scalar',
85 action='store_true',
86 help="Also run with scalar interior implementation, to see vector speedup.")
87
88 ns = parser.parse_args()
89 # set up arguments
90 grid = ns.grid
91 partition = ns.partition
92 Lx = ns.Lx
93 Ly = ns.Ly
94 c = ns.c
95 tstop = ns.tstop
96 if ns.save:
97 user_action = wave_saver
98 else:
99 user_action = None
100
101 num_cells = 1.0*(grid[0]-1)*(grid[1]-1)
102 final_test = True
103
104 # create the Client
105 rc = Client(profile=ns.profile)
106 num_procs = len(rc.ids)
107
108 if partition is None:
109 partition = [1,num_procs]
110
111 assert partition[0]*partition[1] == num_procs, "can't map partition %s to %i engines"%(partition, num_procs)
112
113 view = rc[:]
114 print("Running %s system on %s processes until %f" % (grid, partition, tstop))
115
116 # functions defining initial/boundary/source conditions
117 def I(x,y):
118 from numpy import exp
119 return 1.5*exp(-100*((x-0.5)**2+(y-0.5)**2))
120 def f(x,y,t):
121 return 0.0
122 # from numpy import exp,sin
123 # return 10*exp(-(x - sin(100*t))**2)
124 def bc(x,y,t):
125 return 0.0
126
127 # initial imports, setup rank
128 view.execute('\n'.join([
129 "from mpi4py import MPI",
130 "import numpy",
131 "mpi = MPI.COMM_WORLD",
132 "my_id = MPI.COMM_WORLD.Get_rank()"]), block=True)
133
134 # initialize t_hist/u_hist for saving the state at each step (optional)
135 view['t_hist'] = []
136 view['u_hist'] = []
137
138 # set vector/scalar implementation details
139 impl = {}
140 impl['ic'] = 'vectorized'
141 impl['inner'] = 'scalar'
142 impl['bc'] = 'vectorized'
143
144 # execute some files so that the classes we need will be defined on the engines:
145 view.run('RectPartitioner.py')
146 view.run('wavesolver.py')
147
148 # setup remote partitioner
149 # note that Reference means that the argument passed to setup_partitioner will be the
150 # object named 'my_id' in the engine's namespace
151 view.apply_sync(setup_partitioner, Reference('my_id'), num_procs, grid, partition)
152 # wait for initial communication to complete
153 view.execute('mpi.barrier()')
154 # setup remote solvers
155 view.apply_sync(setup_solver, I,f,c,bc,Lx,Ly,partitioner=Reference('partitioner'), dt=0,implementation=impl)
156
157 # lambda for calling solver.solve:
158 _solve = lambda *args, **kwargs: solver.solve(*args, **kwargs)
159
160 if ns.scalar:
161 impl['inner'] = 'scalar'
162 # run first with element-wise Python operations for each cell
163 t0 = time.time()
164 ar = view.apply_async(_solve, tstop, dt=0, verbose=True, final_test=final_test, user_action=user_action)
165 if final_test:
166 # this sum is performed element-wise as results finish
167 s = sum(ar)
168 # the L2 norm (RMS) of the result:
169 norm = sqrt(s/num_cells)
170 else:
171 norm = -1
172 t1 = time.time()
173 print('scalar inner-version, Wtime=%g, norm=%g' % (t1-t0, norm))
174
175 impl['inner'] = 'vectorized'
176 # setup new solvers
177 view.apply_sync(setup_solver, I,f,c,bc,Lx,Ly,partitioner=Reference('partitioner'), dt=0,implementation=impl)
178 view.execute('mpi.barrier()')
179
180 # run again with numpy vectorized inner-implementation
181 t0 = time.time()
182 ar = view.apply_async(_solve, tstop, dt=0, verbose=True, final_test=final_test, user_action=user_action)
183 if final_test:
184 # this sum is performed element-wise as results finish
185 s = sum(ar)
186 # the L2 norm (RMS) of the result:
187 norm = sqrt(s/num_cells)
188 else:
189 norm = -1
190 t1 = time.time()
191 print('vector inner-version, Wtime=%g, norm=%g' % (t1-t0, norm))
192
193 # if ns.save is True, then u_hist stores the history of u as a list
194 # If the partion scheme is Nx1, then u can be reconstructed via 'gather':
195 if ns.save and partition[-1] == 1:
196 import matplotlib.pyplot as plt
197 view.execute('u_last=u_hist[-1]')
198 # map mpi IDs to IPython IDs, which may not match
199 ranks = view['my_id']
200 targets = range(len(ranks))
201 for idx in range(len(ranks)):
202 targets[idx] = ranks.index(idx)
203 u_last = rc[targets].gather('u_last', block=True)
204 plt.pcolor(u_last)
205 plt.show()
@@ -1,209 +0,0 b''
1 #!/usr/bin/env python
2 """
3 A simple python program of solving a 2D wave equation in parallel.
4 Domain partitioning and inter-processor communication
5 are done by an object of class ZMQRectPartitioner2D
6 (which is a subclass of RectPartitioner2D and uses 0MQ via pyzmq)
7
8 An example of running the program is (8 processors, 4x2 partition,
9 200x200 grid cells)::
10
11 $ ipcluster start -n 8 # start 8 engines
12 $ python parallelwave.py --grid 200 200 --partition 4 2
13
14 See also parallelwave-mpi, which runs the same program, but uses MPI
15 (via mpi4py) for the inter-engine communication.
16
17 Authors
18 -------
19
20 * Xing Cai
21 * Min Ragan-Kelley
22
23 """
24 #
25 import sys
26 import time
27
28 from numpy import exp, zeros, newaxis, sqrt
29
30 from IPython.external import argparse
31 from IPython.parallel import Client, Reference
32
33 def setup_partitioner(comm, addrs, index, num_procs, gnum_cells, parts):
34 """create a partitioner in the engine namespace"""
35 global partitioner
36 p = ZMQRectPartitioner2D(comm, addrs, my_id=index, num_procs=num_procs)
37 p.redim(global_num_cells=gnum_cells, num_parts=parts)
38 p.prepare_communication()
39 # put the partitioner into the global namespace:
40 partitioner=p
41
42 def setup_solver(*args, **kwargs):
43 """create a WaveSolver in the engine namespace."""
44 global solver
45 solver = WaveSolver(*args, **kwargs)
46
47 def wave_saver(u, x, y, t):
48 """save the wave state for each timestep."""
49 global u_hist
50 global t_hist
51 t_hist.append(t)
52 u_hist.append(1.0*u)
53
54
55 # main program:
56 if __name__ == '__main__':
57
58 parser = argparse.ArgumentParser()
59 paa = parser.add_argument
60 paa('--grid', '-g',
61 type=int, nargs=2, default=[100,100], dest='grid',
62 help="Cells in the grid, e.g. --grid 100 200")
63 paa('--partition', '-p',
64 type=int, nargs=2, default=None,
65 help="Process partition grid, e.g. --partition 4 2 for 4x2")
66 paa('-c',
67 type=float, default=1.,
68 help="Wave speed (I think)")
69 paa('-Ly',
70 type=float, default=1.,
71 help="system size (in y)")
72 paa('-Lx',
73 type=float, default=1.,
74 help="system size (in x)")
75 paa('-t', '--tstop',
76 type=float, default=1.,
77 help="Time units to run")
78 paa('--profile',
79 type=unicode, default=u'default',
80 help="Specify the ipcluster profile for the client to connect to.")
81 paa('--save',
82 action='store_true',
83 help="Add this flag to save the time/wave history during the run.")
84 paa('--scalar',
85 action='store_true',
86 help="Also run with scalar interior implementation, to see vector speedup.")
87
88 ns = parser.parse_args()
89 # set up arguments
90 grid = ns.grid
91 partition = ns.partition
92 Lx = ns.Lx
93 Ly = ns.Ly
94 c = ns.c
95 tstop = ns.tstop
96 if ns.save:
97 user_action = wave_saver
98 else:
99 user_action = None
100
101 num_cells = 1.0*(grid[0]-1)*(grid[1]-1)
102 final_test = True
103
104 # create the Client
105 rc = Client(profile=ns.profile)
106 num_procs = len(rc.ids)
107
108 if partition is None:
109 partition = [num_procs,1]
110 else:
111 num_procs = min(num_procs, partition[0]*partition[1])
112
113 assert partition[0]*partition[1] == num_procs, "can't map partition %s to %i engines"%(partition, num_procs)
114
115 # construct the View:
116 view = rc[:num_procs]
117 print("Running %s system on %s processes until %f"%(grid, partition, tstop))
118
119 # functions defining initial/boundary/source conditions
120 def I(x,y):
121 from numpy import exp
122 return 1.5*exp(-100*((x-0.5)**2+(y-0.5)**2))
123 def f(x,y,t):
124 return 0.0
125 # from numpy import exp,sin
126 # return 10*exp(-(x - sin(100*t))**2)
127 def bc(x,y,t):
128 return 0.0
129
130 # initialize t_hist/u_hist for saving the state at each step (optional)
131 view['t_hist'] = []
132 view['u_hist'] = []
133
134 # set vector/scalar implementation details
135 impl = {}
136 impl['ic'] = 'vectorized'
137 impl['inner'] = 'scalar'
138 impl['bc'] = 'vectorized'
139
140 # execute some files so that the classes we need will be defined on the engines:
141 view.execute('import numpy')
142 view.run('communicator.py')
143 view.run('RectPartitioner.py')
144 view.run('wavesolver.py')
145
146 # scatter engine IDs
147 view.scatter('my_id', range(num_procs), flatten=True)
148
149 # create the engine connectors
150 view.execute('com = EngineCommunicator()')
151
152 # gather the connection information into a single dict
153 ar = view.apply_async(lambda : com.info)
154 peers = ar.get_dict()
155 # print peers
156 # this is a dict, keyed by engine ID, of the connection info for the EngineCommunicators
157
158 # setup remote partitioner
159 # note that Reference means that the argument passed to setup_partitioner will be the
160 # object named 'com' in the engine's namespace
161 view.apply_sync(setup_partitioner, Reference('com'), peers, Reference('my_id'), num_procs, grid, partition)
162 time.sleep(1)
163 # convenience lambda to call solver.solve:
164 _solve = lambda *args, **kwargs: solver.solve(*args, **kwargs)
165
166 if ns.scalar:
167 impl['inner'] = 'scalar'
168 # setup remote solvers
169 view.apply_sync(setup_solver, I,f,c,bc,Lx,Ly, partitioner=Reference('partitioner'), dt=0,implementation=impl)
170
171 # run first with element-wise Python operations for each cell
172 t0 = time.time()
173 ar = view.apply_async(_solve, tstop, dt=0, verbose=True, final_test=final_test, user_action=user_action)
174 if final_test:
175 # this sum is performed element-wise as results finish
176 s = sum(ar)
177 # the L2 norm (RMS) of the result:
178 norm = sqrt(s/num_cells)
179 else:
180 norm = -1
181 t1 = time.time()
182 print('scalar inner-version, Wtime=%g, norm=%g'%(t1-t0, norm))
183
184 # run again with faster numpy-vectorized inner implementation:
185 impl['inner'] = 'vectorized'
186 # setup remote solvers
187 view.apply_sync(setup_solver, I,f,c,bc,Lx,Ly,partitioner=Reference('partitioner'), dt=0,implementation=impl)
188
189 t0 = time.time()
190
191 ar = view.apply_async(_solve, tstop, dt=0, verbose=True, final_test=final_test, user_action=user_action)
192 if final_test:
193 # this sum is performed element-wise as results finish
194 s = sum(ar)
195 # the L2 norm (RMS) of the result:
196 norm = sqrt(s/num_cells)
197 else:
198 norm = -1
199 t1 = time.time()
200 print('vector inner-version, Wtime=%g, norm=%g'%(t1-t0, norm))
201
202 # if ns.save is True, then u_hist stores the history of u as a list
203 # If the partion scheme is Nx1, then u can be reconstructed via 'gather':
204 if ns.save and partition[-1] == 1:
205 import matplotlib.pyplot as plt
206 view.execute('u_last=u_hist[-1]')
207 u_last = view.gather('u_last', block=True)
208 plt.pcolor(u_last)
209 plt.show()
@@ -1,267 +0,0 b''
1 #!/usr/bin/env python
2 """
3 A simple WaveSolver class for evolving the wave equation in 2D.
4 This works in parallel by using a RectPartitioner object.
5
6 Authors
7 -------
8
9 * Xing Cai
10 * Min Ragan-Kelley
11
12 """
13 import time
14
15 from numpy import exp, zeros, newaxis, sqrt, arange
16
17 def iseq(start=0, stop=None, inc=1):
18 """
19 Generate integers from start to (and including!) stop,
20 with increment of inc. Alternative to range/xrange.
21 """
22 if stop is None: # allow isequence(3) to be 0, 1, 2, 3
23 # take 1st arg as stop, start as 0, and inc=1
24 stop = start; start = 0; inc = 1
25 return arange(start, stop+inc, inc)
26
27 class WaveSolver(object):
28 """
29 Solve the 2D wave equation u_tt = u_xx + u_yy + f(x,y,t) with
30 u = bc(x,y,t) on the boundary and initial condition du/dt = 0.
31
32 Parallelization by using a RectPartitioner object 'partitioner'
33
34 nx and ny are the total number of global grid cells in the x and y
35 directions. The global grid points are numbered as (0,0), (1,0), (2,0),
36 ..., (nx,0), (0,1), (1,1), ..., (nx, ny).
37
38 dt is the time step. If dt<=0, an optimal time step is used.
39
40 tstop is the stop time for the simulation.
41
42 I, f are functions: I(x,y), f(x,y,t)
43
44 user_action: function of (u, x, y, t) called at each time
45 level (x and y are one-dimensional coordinate vectors).
46 This function allows the calling code to plot the solution,
47 compute errors, etc.
48
49 implementation: a dictionary specifying how the initial
50 condition ('ic'), the scheme over inner points ('inner'),
51 and the boundary conditions ('bc') are to be implemented.
52 Normally, values are legal: 'scalar' or 'vectorized'.
53 'scalar' means straight loops over grid points, while
54 'vectorized' means special NumPy vectorized operations.
55
56 If a key in the implementation dictionary is missing, it
57 defaults in this function to 'scalar' (the safest strategy).
58 Note that if 'vectorized' is specified, the functions I, f,
59 and bc must work in vectorized mode. It is always recommended
60 to first run the 'scalar' mode and then compare 'vectorized'
61 results with the 'scalar' results to check that I, f, and bc
62 work.
63
64 verbose: true if a message at each time step is written,
65 false implies no output during the simulation.
66
67 final_test: true means the discrete L2-norm of the final solution is
68 to be computed.
69 """
70
71 def __init__(self, I, f, c, bc, Lx, Ly, partitioner=None, dt=-1,
72 user_action=None,
73 implementation={'ic': 'vectorized', # or 'scalar'
74 'inner': 'vectorized',
75 'bc': 'vectorized'}):
76
77 nx = partitioner.global_num_cells[0] # number of global cells in x dir
78 ny = partitioner.global_num_cells[1] # number of global cells in y dir
79 dx = Lx/float(nx)
80 dy = Ly/float(ny)
81 loc_nx, loc_ny = partitioner.get_num_loc_cells()
82 nx = loc_nx; ny = loc_ny # now use loc_nx and loc_ny instead
83 lo_ix0 = partitioner.subd_lo_ix[0]
84 lo_ix1 = partitioner.subd_lo_ix[1]
85 hi_ix0 = partitioner.subd_hi_ix[0]
86 hi_ix1 = partitioner.subd_hi_ix[1]
87 x = iseq(dx*lo_ix0, dx*hi_ix0, dx) # local grid points in x dir
88 y = iseq(dy*lo_ix1, dy*hi_ix1, dy) # local grid points in y dir
89 self.x = x
90 self.y = y
91 xv = x[:,newaxis] # for vectorized expressions with f(xv,yv)
92 yv = y[newaxis,:] # -- " --
93 if dt <= 0:
94 dt = (1/float(c))*(1/sqrt(1/dx**2 + 1/dy**2)) # max time step
95 Cx2 = (c*dt/dx)**2; Cy2 = (c*dt/dy)**2; dt2 = dt**2 # help variables
96
97 u = zeros((nx+1,ny+1)) # solution array
98 u_1 = u.copy() # solution at t-dt
99 u_2 = u.copy() # solution at t-2*dt
100
101 # preserve for self.solve
102 implementation=dict(implementation) # copy
103
104 if 'ic' not in implementation:
105 implementation['ic'] = 'scalar'
106 if 'bc' not in implementation:
107 implementation['bc'] = 'scalar'
108 if 'inner' not in implementation:
109 implementation['inner'] = 'scalar'
110
111 self.implementation = implementation
112 self.Lx = Lx
113 self.Ly = Ly
114 self.I=I
115 self.f=f
116 self.c=c
117 self.bc=bc
118 self.user_action = user_action
119 self.partitioner=partitioner
120
121 # set initial condition (pointwise - allows straight if-tests in I(x,y)):
122 t=0.0
123 if implementation['ic'] == 'scalar':
124 for i in xrange(0,nx+1):
125 for j in xrange(0,ny+1):
126 u_1[i,j] = I(x[i], y[j])
127
128 for i in xrange(1,nx):
129 for j in xrange(1,ny):
130 u_2[i,j] = u_1[i,j] + \
131 0.5*Cx2*(u_1[i-1,j] - 2*u_1[i,j] + u_1[i+1,j]) + \
132 0.5*Cy2*(u_1[i,j-1] - 2*u_1[i,j] + u_1[i,j+1]) + \
133 dt2*f(x[i], y[j], 0.0)
134
135 # boundary values of u_2 (equals u(t=dt) due to du/dt=0)
136 i = 0
137 for j in xrange(0,ny+1):
138 u_2[i,j] = bc(x[i], y[j], t+dt)
139 j = 0
140 for i in xrange(0,nx+1):
141 u_2[i,j] = bc(x[i], y[j], t+dt)
142 i = nx
143 for j in xrange(0,ny+1):
144 u_2[i,j] = bc(x[i], y[j], t+dt)
145 j = ny
146 for i in xrange(0,nx+1):
147 u_2[i,j] = bc(x[i], y[j], t+dt)
148
149 elif implementation['ic'] == 'vectorized':
150 u_1 = I(xv,yv)
151 u_2[1:nx,1:ny] = u_1[1:nx,1:ny] + \
152 0.5*Cx2*(u_1[0:nx-1,1:ny] - 2*u_1[1:nx,1:ny] + u_1[2:nx+1,1:ny]) + \
153 0.5*Cy2*(u_1[1:nx,0:ny-1] - 2*u_1[1:nx,1:ny] + u_1[1:nx,2:ny+1]) + \
154 dt2*(f(xv[1:nx,1:ny], yv[1:nx,1:ny], 0.0))
155 # boundary values (t=dt):
156 i = 0; u_2[i,:] = bc(x[i], y, t+dt)
157 j = 0; u_2[:,j] = bc(x, y[j], t+dt)
158 i = nx; u_2[i,:] = bc(x[i], y, t+dt)
159 j = ny; u_2[:,j] = bc(x, y[j], t+dt)
160
161 if user_action is not None:
162 user_action(u_1, x, y, t) # allow user to plot etc.
163 # print(list(self.us[2][2]))
164 self.us = (u,u_1,u_2)
165
166
167 def solve(self, tstop, dt=-1, user_action=None, verbose=False, final_test=False):
168 t0=time.time()
169 f=self.f
170 c=self.c
171 bc=self.bc
172 partitioner = self.partitioner
173 implementation = self.implementation
174 nx = partitioner.global_num_cells[0] # number of global cells in x dir
175 ny = partitioner.global_num_cells[1] # number of global cells in y dir
176 dx = self.Lx/float(nx)
177 dy = self.Ly/float(ny)
178 loc_nx, loc_ny = partitioner.get_num_loc_cells()
179 nx = loc_nx; ny = loc_ny # now use loc_nx and loc_ny instead
180 x = self.x
181 y = self.y
182 xv = x[:,newaxis] # for vectorized expressions with f(xv,yv)
183 yv = y[newaxis,:] # -- " --
184 if dt <= 0:
185 dt = (1/float(c))*(1/sqrt(1/dx**2 + 1/dy**2)) # max time step
186 Cx2 = (c*dt/dx)**2; Cy2 = (c*dt/dy)**2; dt2 = dt**2 # help variables
187 # id for the four possible neighbor subdomains
188 lower_x_neigh = partitioner.lower_neighbors[0]
189 upper_x_neigh = partitioner.upper_neighbors[0]
190 lower_y_neigh = partitioner.lower_neighbors[1]
191 upper_y_neigh = partitioner.upper_neighbors[1]
192 u,u_1,u_2 = self.us
193 # u_1 = self.u_1
194
195 t = 0.0
196 while t <= tstop:
197 t_old = t; t += dt
198 if verbose:
199 print('solving (%s version) at t=%g' % \
200 (implementation['inner'], t))
201 # update all inner points:
202 if implementation['inner'] == 'scalar':
203 for i in xrange(1, nx):
204 for j in xrange(1, ny):
205 u[i,j] = - u_2[i,j] + 2*u_1[i,j] + \
206 Cx2*(u_1[i-1,j] - 2*u_1[i,j] + u_1[i+1,j]) + \
207 Cy2*(u_1[i,j-1] - 2*u_1[i,j] + u_1[i,j+1]) + \
208 dt2*f(x[i], y[j], t_old)
209 elif implementation['inner'] == 'vectorized':
210 u[1:nx,1:ny] = - u_2[1:nx,1:ny] + 2*u_1[1:nx,1:ny] + \
211 Cx2*(u_1[0:nx-1,1:ny] - 2*u_1[1:nx,1:ny] + u_1[2:nx+1,1:ny]) + \
212 Cy2*(u_1[1:nx,0:ny-1] - 2*u_1[1:nx,1:ny] + u_1[1:nx,2:ny+1]) + \
213 dt2*f(xv[1:nx,1:ny], yv[1:nx,1:ny], t_old)
214
215 # insert boundary conditions (if there's no neighbor):
216 if lower_x_neigh < 0:
217 if implementation['bc'] == 'scalar':
218 i = 0
219 for j in xrange(0, ny+1):
220 u[i,j] = bc(x[i], y[j], t)
221 elif implementation['bc'] == 'vectorized':
222 u[0,:] = bc(x[0], y, t)
223 if upper_x_neigh < 0:
224 if implementation['bc'] == 'scalar':
225 i = nx
226 for j in xrange(0, ny+1):
227 u[i,j] = bc(x[i], y[j], t)
228 elif implementation['bc'] == 'vectorized':
229 u[nx,:] = bc(x[nx], y, t)
230 if lower_y_neigh < 0:
231 if implementation['bc'] == 'scalar':
232 j = 0
233 for i in xrange(0, nx+1):
234 u[i,j] = bc(x[i], y[j], t)
235 elif implementation['bc'] == 'vectorized':
236 u[:,0] = bc(x, y[0], t)
237 if upper_y_neigh < 0:
238 if implementation['bc'] == 'scalar':
239 j = ny
240 for i in xrange(0, nx+1):
241 u[i,j] = bc(x[i], y[j], t)
242 elif implementation['bc'] == 'vectorized':
243 u[:,ny] = bc(x, y[ny], t)
244
245 # communication
246 partitioner.update_internal_boundary (u)
247
248 if user_action is not None:
249 user_action(u, x, y, t)
250 # update data structures for next step
251 u_2, u_1, u = u_1, u, u_2
252
253 t1 = time.time()
254 print('my_id=%2d, dt=%g, %s version, slice_copy=%s, net Wtime=%g'\
255 %(partitioner.my_id,dt,implementation['inner'],\
256 partitioner.slice_copy,t1-t0))
257 # save the us
258 self.us = u,u_1,u_2
259 # check final results; compute discrete L2-norm of the solution
260 if final_test:
261 loc_res = 0.0
262 for i in iseq(start=1, stop=nx-1):
263 for j in iseq(start=1, stop=ny-1):
264 loc_res += u_1[i,j]**2
265 return loc_res
266 return dt
267
@@ -1,56 +0,0 b''
1 """The IPython ZMQ-based parallel computing interface."""
2
3 # Copyright (c) IPython Development Team.
4 # Distributed under the terms of the Modified BSD License.
5
6
7 import os
8 import warnings
9
10 import zmq
11
12 from IPython.config.configurable import MultipleInstanceError
13
14 from ipython_kernel.pickleutil import Reference
15
16 from .client.asyncresult import *
17 from .client.client import Client
18 from .client.remotefunction import *
19 from .client.view import *
20 from .controller.dependency import *
21 from .error import *
22 from .util import interactive
23
24 #-----------------------------------------------------------------------------
25 # Functions
26 #-----------------------------------------------------------------------------
27
28
29 def bind_kernel(**kwargs):
30 """Bind an Engine's Kernel to be used as a full IPython kernel.
31
32 This allows a running Engine to be used simultaneously as a full IPython kernel
33 with the QtConsole or other frontends.
34
35 This function returns immediately.
36 """
37 from IPython.kernel.zmq.kernelapp import IPKernelApp
38 from ipython_parallel.apps.ipengineapp import IPEngineApp
39
40 # first check for IPKernelApp, in which case this should be a no-op
41 # because there is already a bound kernel
42 if IPKernelApp.initialized() and isinstance(IPKernelApp._instance, IPKernelApp):
43 return
44
45 if IPEngineApp.initialized():
46 try:
47 app = IPEngineApp.instance()
48 except MultipleInstanceError:
49 pass
50 else:
51 return app.bind_kernel(**kwargs)
52
53 raise RuntimeError("bind_kernel be called from an IPEngineApp instance")
54
55
56
1 NO CONTENT: file was removed
NO CONTENT: file was removed
@@ -1,242 +0,0 b''
1 # encoding: utf-8
2 """
3 The Base Application class for ipython_parallel apps
4 """
5
6
7 import os
8 import logging
9 import re
10 import sys
11
12 from IPython.config.application import catch_config_error, LevelFormatter
13 from IPython.core import release
14 from IPython.core.crashhandler import CrashHandler
15 from IPython.core.application import (
16 BaseIPythonApplication,
17 base_aliases as base_ip_aliases,
18 base_flags as base_ip_flags
19 )
20 from IPython.utils.path import expand_path
21 from IPython.utils.process import check_pid
22 from IPython.utils import py3compat
23 from IPython.utils.py3compat import unicode_type
24
25 from IPython.utils.traitlets import Unicode, Bool, Instance, Dict
26
27 #-----------------------------------------------------------------------------
28 # Module errors
29 #-----------------------------------------------------------------------------
30
31 class PIDFileError(Exception):
32 pass
33
34
35 #-----------------------------------------------------------------------------
36 # Crash handler for this application
37 #-----------------------------------------------------------------------------
38
39 class ParallelCrashHandler(CrashHandler):
40 """sys.excepthook for IPython itself, leaves a detailed report on disk."""
41
42 def __init__(self, app):
43 contact_name = release.authors['Min'][0]
44 contact_email = release.author_email
45 bug_tracker = 'https://github.com/ipython/ipython/issues'
46 super(ParallelCrashHandler,self).__init__(
47 app, contact_name, contact_email, bug_tracker
48 )
49
50
51 #-----------------------------------------------------------------------------
52 # Main application
53 #-----------------------------------------------------------------------------
54 base_aliases = {}
55 base_aliases.update(base_ip_aliases)
56 base_aliases.update({
57 'work-dir' : 'BaseParallelApplication.work_dir',
58 'log-to-file' : 'BaseParallelApplication.log_to_file',
59 'clean-logs' : 'BaseParallelApplication.clean_logs',
60 'log-url' : 'BaseParallelApplication.log_url',
61 'cluster-id' : 'BaseParallelApplication.cluster_id',
62 })
63
64 base_flags = {
65 'log-to-file' : (
66 {'BaseParallelApplication' : {'log_to_file' : True}},
67 "send log output to a file"
68 )
69 }
70 base_flags.update(base_ip_flags)
71
72 class BaseParallelApplication(BaseIPythonApplication):
73 """The base Application for ipython_parallel apps
74
75 Principle extensions to BaseIPyythonApplication:
76
77 * work_dir
78 * remote logging via pyzmq
79 * IOLoop instance
80 """
81
82 crash_handler_class = ParallelCrashHandler
83
84 def _log_level_default(self):
85 # temporarily override default_log_level to INFO
86 return logging.INFO
87
88 def _log_format_default(self):
89 """override default log format to include time"""
90 return u"%(asctime)s.%(msecs).03d [%(name)s]%(highlevel)s %(message)s"
91
92 work_dir = Unicode(py3compat.getcwd(), config=True,
93 help='Set the working dir for the process.'
94 )
95 def _work_dir_changed(self, name, old, new):
96 self.work_dir = unicode_type(expand_path(new))
97
98 log_to_file = Bool(config=True,
99 help="whether to log to a file")
100
101 clean_logs = Bool(False, config=True,
102 help="whether to cleanup old logfiles before starting")
103
104 log_url = Unicode('', config=True,
105 help="The ZMQ URL of the iplogger to aggregate logging.")
106
107 cluster_id = Unicode('', config=True,
108 help="""String id to add to runtime files, to prevent name collisions when
109 using multiple clusters with a single profile simultaneously.
110
111 When set, files will be named like: 'ipcontroller-<cluster_id>-engine.json'
112
113 Since this is text inserted into filenames, typical recommendations apply:
114 Simple character strings are ideal, and spaces are not recommended (but should
115 generally work).
116 """
117 )
118 def _cluster_id_changed(self, name, old, new):
119 self.name = self.__class__.name
120 if new:
121 self.name += '-%s'%new
122
123 def _config_files_default(self):
124 return ['ipcontroller_config.py', 'ipengine_config.py', 'ipcluster_config.py']
125
126 loop = Instance('zmq.eventloop.ioloop.IOLoop')
127 def _loop_default(self):
128 from zmq.eventloop.ioloop import IOLoop
129 return IOLoop.instance()
130
131 aliases = Dict(base_aliases)
132 flags = Dict(base_flags)
133
134 @catch_config_error
135 def initialize(self, argv=None):
136 """initialize the app"""
137 super(BaseParallelApplication, self).initialize(argv)
138 self.to_work_dir()
139 self.reinit_logging()
140
141 def to_work_dir(self):
142 wd = self.work_dir
143 if unicode_type(wd) != py3compat.getcwd():
144 os.chdir(wd)
145 self.log.info("Changing to working dir: %s" % wd)
146 # This is the working dir by now.
147 sys.path.insert(0, '')
148
149 def reinit_logging(self):
150 # Remove old log files
151 log_dir = self.profile_dir.log_dir
152 if self.clean_logs:
153 for f in os.listdir(log_dir):
154 if re.match(r'%s-\d+\.(log|err|out)' % self.name, f):
155 try:
156 os.remove(os.path.join(log_dir, f))
157 except (OSError, IOError):
158 # probably just conflict from sibling process
159 # already removing it
160 pass
161 if self.log_to_file:
162 # Start logging to the new log file
163 log_filename = self.name + u'-' + str(os.getpid()) + u'.log'
164 logfile = os.path.join(log_dir, log_filename)
165 open_log_file = open(logfile, 'w')
166 else:
167 open_log_file = None
168 if open_log_file is not None:
169 while self.log.handlers:
170 self.log.removeHandler(self.log.handlers[0])
171 self._log_handler = logging.StreamHandler(open_log_file)
172 self.log.addHandler(self._log_handler)
173 else:
174 self._log_handler = self.log.handlers[0]
175 # Add timestamps to log format:
176 self._log_formatter = LevelFormatter(self.log_format,
177 datefmt=self.log_datefmt)
178 self._log_handler.setFormatter(self._log_formatter)
179 # do not propagate log messages to root logger
180 # ipcluster app will sometimes print duplicate messages during shutdown
181 # if this is 1 (default):
182 self.log.propagate = False
183
184 def write_pid_file(self, overwrite=False):
185 """Create a .pid file in the pid_dir with my pid.
186
187 This must be called after pre_construct, which sets `self.pid_dir`.
188 This raises :exc:`PIDFileError` if the pid file exists already.
189 """
190 pid_file = os.path.join(self.profile_dir.pid_dir, self.name + u'.pid')
191 if os.path.isfile(pid_file):
192 pid = self.get_pid_from_file()
193 if not overwrite:
194 raise PIDFileError(
195 'The pid file [%s] already exists. \nThis could mean that this '
196 'server is already running with [pid=%s].' % (pid_file, pid)
197 )
198 with open(pid_file, 'w') as f:
199 self.log.info("Creating pid file: %s" % pid_file)
200 f.write(repr(os.getpid())+'\n')
201
202 def remove_pid_file(self):
203 """Remove the pid file.
204
205 This should be called at shutdown by registering a callback with
206 :func:`reactor.addSystemEventTrigger`. This needs to return
207 ``None``.
208 """
209 pid_file = os.path.join(self.profile_dir.pid_dir, self.name + u'.pid')
210 if os.path.isfile(pid_file):
211 try:
212 self.log.info("Removing pid file: %s" % pid_file)
213 os.remove(pid_file)
214 except:
215 self.log.warn("Error removing the pid file: %s" % pid_file)
216
217 def get_pid_from_file(self):
218 """Get the pid from the pid file.
219
220 If the pid file doesn't exist a :exc:`PIDFileError` is raised.
221 """
222 pid_file = os.path.join(self.profile_dir.pid_dir, self.name + u'.pid')
223 if os.path.isfile(pid_file):
224 with open(pid_file, 'r') as f:
225 s = f.read().strip()
226 try:
227 pid = int(s)
228 except:
229 raise PIDFileError("invalid pid file: %s (contents: %r)"%(pid_file, s))
230 return pid
231 else:
232 raise PIDFileError('pid file not found: %s' % pid_file)
233
234 def check_pid(self, pid):
235 try:
236 return check_pid(pid)
237 except Exception:
238 self.log.warn(
239 "Could not determine whether pid %i is running. "
240 " Making the likely assumption that it is."%pid
241 )
242 return True
@@ -1,26 +0,0 b''
1 """daemonize function from twisted.scripts._twistd_unix."""
2
3 #-----------------------------------------------------------------------------
4 # Copyright (c) Twisted Matrix Laboratories.
5 # See Twisted's LICENSE for details.
6 # http://twistedmatrix.com/
7 #-----------------------------------------------------------------------------
8
9 import os, errno
10
11 def daemonize():
12 # See http://www.erlenstar.demon.co.uk/unix/faq_toc.html#TOC16
13 if os.fork(): # launch child and...
14 os._exit(0) # kill off parent
15 os.setsid()
16 if os.fork(): # launch child and...
17 os._exit(0) # kill off parent again.
18 null = os.open('/dev/null', os.O_RDWR)
19 for i in range(3):
20 try:
21 os.dup2(null, i)
22 except OSError as e:
23 if e.errno != errno.EBADF:
24 raise
25 os.close(null)
26
This diff has been collapsed as it changes many lines, (596 lines changed) Show them Hide them
@@ -1,596 +0,0 b''
1 #!/usr/bin/env python
2 # encoding: utf-8
3 """The ipcluster application."""
4 from __future__ import print_function
5
6 import errno
7 import logging
8 import os
9 import re
10 import signal
11
12 from subprocess import check_call, CalledProcessError, PIPE
13 import zmq
14
15 from IPython.config.application import catch_config_error
16 from IPython.config.loader import Config
17 from IPython.core.application import BaseIPythonApplication
18 from IPython.core.profiledir import ProfileDir
19 from IPython.utils.importstring import import_item
20 from IPython.utils.py3compat import string_types
21 from IPython.utils.sysinfo import num_cpus
22 from IPython.utils.traitlets import (Integer, Unicode, Bool, CFloat, Dict, List, Any,
23 DottedObjectName)
24
25 from .baseapp import (
26 BaseParallelApplication,
27 PIDFileError,
28 base_flags, base_aliases
29 )
30 from .daemonize import daemonize
31
32
33 #-----------------------------------------------------------------------------
34 # Module level variables
35 #-----------------------------------------------------------------------------
36
37
38 _description = """Start an IPython cluster for parallel computing.
39
40 An IPython cluster consists of 1 controller and 1 or more engines.
41 This command automates the startup of these processes using a wide range of
42 startup methods (SSH, local processes, PBS, mpiexec, SGE, LSF, HTCondor,
43 Windows HPC Server 2008). To start a cluster with 4 engines on your
44 local host simply do 'ipcluster start --n=4'. For more complex usage
45 you will typically do 'ipython profile create mycluster --parallel', then edit
46 configuration files, followed by 'ipcluster start --profile=mycluster --n=4'.
47 """
48
49 _main_examples = """
50 ipcluster start --n=4 # start a 4 node cluster on localhost
51 ipcluster start -h # show the help string for the start subcmd
52
53 ipcluster stop -h # show the help string for the stop subcmd
54 ipcluster engines -h # show the help string for the engines subcmd
55 """
56
57 _start_examples = """
58 ipython profile create mycluster --parallel # create mycluster profile
59 ipcluster start --profile=mycluster --n=4 # start mycluster with 4 nodes
60 """
61
62 _stop_examples = """
63 ipcluster stop --profile=mycluster # stop a running cluster by profile name
64 """
65
66 _engines_examples = """
67 ipcluster engines --profile=mycluster --n=4 # start 4 engines only
68 """
69
70
71 # Exit codes for ipcluster
72
73 # This will be the exit code if the ipcluster appears to be running because
74 # a .pid file exists
75 ALREADY_STARTED = 10
76
77
78 # This will be the exit code if ipcluster stop is run, but there is not .pid
79 # file to be found.
80 ALREADY_STOPPED = 11
81
82 # This will be the exit code if ipcluster engines is run, but there is not .pid
83 # file to be found.
84 NO_CLUSTER = 12
85
86
87 #-----------------------------------------------------------------------------
88 # Utilities
89 #-----------------------------------------------------------------------------
90
91 def find_launcher_class(clsname, kind):
92 """Return a launcher for a given clsname and kind.
93
94 Parameters
95 ==========
96 clsname : str
97 The full name of the launcher class, either with or without the
98 module path, or an abbreviation (MPI, SSH, SGE, PBS, LSF, HTCondor
99 WindowsHPC).
100 kind : str
101 Either 'EngineSet' or 'Controller'.
102 """
103 if '.' not in clsname:
104 # not a module, presume it's the raw name in apps.launcher
105 if kind and kind not in clsname:
106 # doesn't match necessary full class name, assume it's
107 # just 'PBS' or 'MPI' etc prefix:
108 clsname = clsname + kind + 'Launcher'
109 clsname = 'ipython_parallel.apps.launcher.'+clsname
110 klass = import_item(clsname)
111 return klass
112
113 #-----------------------------------------------------------------------------
114 # Main application
115 #-----------------------------------------------------------------------------
116
117 start_help = """Start an IPython cluster for parallel computing
118
119 Start an ipython cluster by its profile name or cluster
120 directory. Cluster directories contain configuration, log and
121 security related files and are named using the convention
122 'profile_<name>' and should be creating using the 'start'
123 subcommand of 'ipcluster'. If your cluster directory is in
124 the cwd or the ipython directory, you can simply refer to it
125 using its profile name, 'ipcluster start --n=4 --profile=<profile>`,
126 otherwise use the 'profile-dir' option.
127 """
128 stop_help = """Stop a running IPython cluster
129
130 Stop a running ipython cluster by its profile name or cluster
131 directory. Cluster directories are named using the convention
132 'profile_<name>'. If your cluster directory is in
133 the cwd or the ipython directory, you can simply refer to it
134 using its profile name, 'ipcluster stop --profile=<profile>`, otherwise
135 use the '--profile-dir' option.
136 """
137 engines_help = """Start engines connected to an existing IPython cluster
138
139 Start one or more engines to connect to an existing Cluster
140 by profile name or cluster directory.
141 Cluster directories contain configuration, log and
142 security related files and are named using the convention
143 'profile_<name>' and should be creating using the 'start'
144 subcommand of 'ipcluster'. If your cluster directory is in
145 the cwd or the ipython directory, you can simply refer to it
146 using its profile name, 'ipcluster engines --n=4 --profile=<profile>`,
147 otherwise use the 'profile-dir' option.
148 """
149 stop_aliases = dict(
150 signal='IPClusterStop.signal',
151 )
152 stop_aliases.update(base_aliases)
153
154 class IPClusterStop(BaseParallelApplication):
155 name = u'ipcluster'
156 description = stop_help
157 examples = _stop_examples
158
159 signal = Integer(signal.SIGINT, config=True,
160 help="signal to use for stopping processes.")
161
162 aliases = Dict(stop_aliases)
163
164 def start(self):
165 """Start the app for the stop subcommand."""
166 try:
167 pid = self.get_pid_from_file()
168 except PIDFileError:
169 self.log.critical(
170 'Could not read pid file, cluster is probably not running.'
171 )
172 # Here I exit with a unusual exit status that other processes
173 # can watch for to learn how I existed.
174 self.remove_pid_file()
175 self.exit(ALREADY_STOPPED)
176
177 if not self.check_pid(pid):
178 self.log.critical(
179 'Cluster [pid=%r] is not running.' % pid
180 )
181 self.remove_pid_file()
182 # Here I exit with a unusual exit status that other processes
183 # can watch for to learn how I existed.
184 self.exit(ALREADY_STOPPED)
185
186 elif os.name=='posix':
187 sig = self.signal
188 self.log.info(
189 "Stopping cluster [pid=%r] with [signal=%r]" % (pid, sig)
190 )
191 try:
192 os.kill(pid, sig)
193 except OSError:
194 self.log.error("Stopping cluster failed, assuming already dead.",
195 exc_info=True)
196 self.remove_pid_file()
197 elif os.name=='nt':
198 try:
199 # kill the whole tree
200 p = check_call(['taskkill', '-pid', str(pid), '-t', '-f'], stdout=PIPE,stderr=PIPE)
201 except (CalledProcessError, OSError):
202 self.log.error("Stopping cluster failed, assuming already dead.",
203 exc_info=True)
204 self.remove_pid_file()
205
206 engine_aliases = {}
207 engine_aliases.update(base_aliases)
208 engine_aliases.update(dict(
209 n='IPClusterEngines.n',
210 engines = 'IPClusterEngines.engine_launcher_class',
211 daemonize = 'IPClusterEngines.daemonize',
212 ))
213 engine_flags = {}
214 engine_flags.update(base_flags)
215
216 engine_flags.update(dict(
217 daemonize=(
218 {'IPClusterEngines' : {'daemonize' : True}},
219 """run the cluster into the background (not available on Windows)""",
220 )
221 ))
222 class IPClusterEngines(BaseParallelApplication):
223
224 name = u'ipcluster'
225 description = engines_help
226 examples = _engines_examples
227 usage = None
228 default_log_level = logging.INFO
229 classes = List()
230 def _classes_default(self):
231 from ipython_parallel.apps import launcher
232 launchers = launcher.all_launchers
233 eslaunchers = [ l for l in launchers if 'EngineSet' in l.__name__]
234 return [ProfileDir]+eslaunchers
235
236 n = Integer(num_cpus(), config=True,
237 help="""The number of engines to start. The default is to use one for each
238 CPU on your machine""")
239
240 engine_launcher = Any(config=True, help="Deprecated, use engine_launcher_class")
241 def _engine_launcher_changed(self, name, old, new):
242 if isinstance(new, string_types):
243 self.log.warn("WARNING: %s.engine_launcher is deprecated as of 0.12,"
244 " use engine_launcher_class" % self.__class__.__name__)
245 self.engine_launcher_class = new
246 engine_launcher_class = DottedObjectName('LocalEngineSetLauncher',
247 config=True,
248 help="""The class for launching a set of Engines. Change this value
249 to use various batch systems to launch your engines, such as PBS,SGE,MPI,etc.
250 Each launcher class has its own set of configuration options, for making sure
251 it will work in your environment.
252
253 You can also write your own launcher, and specify it's absolute import path,
254 as in 'mymodule.launcher.FTLEnginesLauncher`.
255
256 IPython's bundled examples include:
257
258 Local : start engines locally as subprocesses [default]
259 MPI : use mpiexec to launch engines in an MPI environment
260 PBS : use PBS (qsub) to submit engines to a batch queue
261 SGE : use SGE (qsub) to submit engines to a batch queue
262 LSF : use LSF (bsub) to submit engines to a batch queue
263 SSH : use SSH to start the controller
264 Note that SSH does *not* move the connection files
265 around, so you will likely have to do this manually
266 unless the machines are on a shared file system.
267 HTCondor : use HTCondor to submit engines to a batch queue
268 WindowsHPC : use Windows HPC
269
270 If you are using one of IPython's builtin launchers, you can specify just the
271 prefix, e.g:
272
273 c.IPClusterEngines.engine_launcher_class = 'SSH'
274
275 or:
276
277 ipcluster start --engines=MPI
278
279 """
280 )
281 daemonize = Bool(False, config=True,
282 help="""Daemonize the ipcluster program. This implies --log-to-file.
283 Not available on Windows.
284 """)
285
286 def _daemonize_changed(self, name, old, new):
287 if new:
288 self.log_to_file = True
289
290 early_shutdown = Integer(30, config=True, help="The timeout (in seconds)")
291 _stopping = False
292
293 aliases = Dict(engine_aliases)
294 flags = Dict(engine_flags)
295
296 @catch_config_error
297 def initialize(self, argv=None):
298 super(IPClusterEngines, self).initialize(argv)
299 self.init_signal()
300 self.init_launchers()
301
302 def init_launchers(self):
303 self.engine_launcher = self.build_launcher(self.engine_launcher_class, 'EngineSet')
304
305 def init_signal(self):
306 # Setup signals
307 signal.signal(signal.SIGINT, self.sigint_handler)
308
309 def build_launcher(self, clsname, kind=None):
310 """import and instantiate a Launcher based on importstring"""
311 try:
312 klass = find_launcher_class(clsname, kind)
313 except (ImportError, KeyError):
314 self.log.fatal("Could not import launcher class: %r"%clsname)
315 self.exit(1)
316
317 launcher = klass(
318 work_dir=u'.', parent=self, log=self.log,
319 profile_dir=self.profile_dir.location, cluster_id=self.cluster_id,
320 )
321 return launcher
322
323 def engines_started_ok(self):
324 self.log.info("Engines appear to have started successfully")
325 self.early_shutdown = 0
326
327 def start_engines(self):
328 # Some EngineSetLaunchers ignore `n` and use their own engine count, such as SSH:
329 n = getattr(self.engine_launcher, 'engine_count', self.n)
330 self.log.info("Starting %s Engines with %s", n, self.engine_launcher_class)
331 try:
332 self.engine_launcher.start(self.n)
333 except:
334 self.log.exception("Engine start failed")
335 raise
336 self.engine_launcher.on_stop(self.engines_stopped_early)
337 if self.early_shutdown:
338 self.loop.add_timeout(self.loop.time() + self.early_shutdown, self.engines_started_ok)
339
340 def engines_stopped_early(self, r):
341 if self.early_shutdown and not self._stopping:
342 self.log.error("""
343 Engines shutdown early, they probably failed to connect.
344
345 Check the engine log files for output.
346
347 If your controller and engines are not on the same machine, you probably
348 have to instruct the controller to listen on an interface other than localhost.
349
350 You can set this by adding "--ip='*'" to your ControllerLauncher.controller_args.
351
352 Be sure to read our security docs before instructing your controller to listen on
353 a public interface.
354 """)
355 self.stop_launchers()
356
357 return self.engines_stopped(r)
358
359 def engines_stopped(self, r):
360 return self.loop.stop()
361
362 def stop_engines(self):
363 if self.engine_launcher.running:
364 self.log.info("Stopping Engines...")
365 d = self.engine_launcher.stop()
366 return d
367 else:
368 return None
369
370 def stop_launchers(self, r=None):
371 if not self._stopping:
372 self._stopping = True
373 self.log.error("IPython cluster: stopping")
374 self.stop_engines()
375 # Wait a few seconds to let things shut down.
376 self.loop.add_timeout(self.loop.time() + 3, self.loop.stop)
377
378 def sigint_handler(self, signum, frame):
379 self.log.debug("SIGINT received, stopping launchers...")
380 self.stop_launchers()
381
382 def start_logging(self):
383 # Remove old log files of the controller and engine
384 if self.clean_logs:
385 log_dir = self.profile_dir.log_dir
386 for f in os.listdir(log_dir):
387 if re.match(r'ip(engine|controller)-.+\.(log|err|out)',f):
388 os.remove(os.path.join(log_dir, f))
389
390 def start(self):
391 """Start the app for the engines subcommand."""
392 self.log.info("IPython cluster: started")
393 # First see if the cluster is already running
394
395 # Now log and daemonize
396 self.log.info(
397 'Starting engines with [daemon=%r]' % self.daemonize
398 )
399 # TODO: Get daemonize working on Windows or as a Windows Server.
400 if self.daemonize:
401 if os.name=='posix':
402 daemonize()
403
404 self.loop.add_callback(self.start_engines)
405 # Now write the new pid file AFTER our new forked pid is active.
406 # self.write_pid_file()
407 try:
408 self.loop.start()
409 except KeyboardInterrupt:
410 pass
411 except zmq.ZMQError as e:
412 if e.errno == errno.EINTR:
413 pass
414 else:
415 raise
416
417 start_aliases = {}
418 start_aliases.update(engine_aliases)
419 start_aliases.update(dict(
420 delay='IPClusterStart.delay',
421 controller = 'IPClusterStart.controller_launcher_class',
422 ))
423 start_aliases['clean-logs'] = 'IPClusterStart.clean_logs'
424
425 class IPClusterStart(IPClusterEngines):
426
427 name = u'ipcluster'
428 description = start_help
429 examples = _start_examples
430 default_log_level = logging.INFO
431 auto_create = Bool(True, config=True,
432 help="whether to create the profile_dir if it doesn't exist")
433 classes = List()
434 def _classes_default(self,):
435 from ipython_parallel.apps import launcher
436 return [ProfileDir] + [IPClusterEngines] + launcher.all_launchers
437
438 clean_logs = Bool(True, config=True,
439 help="whether to cleanup old logs before starting")
440
441 delay = CFloat(1., config=True,
442 help="delay (in s) between starting the controller and the engines")
443
444 controller_launcher = Any(config=True, help="Deprecated, use controller_launcher_class")
445 def _controller_launcher_changed(self, name, old, new):
446 if isinstance(new, string_types):
447 # old 0.11-style config
448 self.log.warn("WARNING: %s.controller_launcher is deprecated as of 0.12,"
449 " use controller_launcher_class" % self.__class__.__name__)
450 self.controller_launcher_class = new
451 controller_launcher_class = DottedObjectName('LocalControllerLauncher',
452 config=True,
453 help="""The class for launching a Controller. Change this value if you want
454 your controller to also be launched by a batch system, such as PBS,SGE,MPI,etc.
455
456 Each launcher class has its own set of configuration options, for making sure
457 it will work in your environment.
458
459 Note that using a batch launcher for the controller *does not* put it
460 in the same batch job as the engines, so they will still start separately.
461
462 IPython's bundled examples include:
463
464 Local : start engines locally as subprocesses
465 MPI : use mpiexec to launch the controller in an MPI universe
466 PBS : use PBS (qsub) to submit the controller to a batch queue
467 SGE : use SGE (qsub) to submit the controller to a batch queue
468 LSF : use LSF (bsub) to submit the controller to a batch queue
469 HTCondor : use HTCondor to submit the controller to a batch queue
470 SSH : use SSH to start the controller
471 WindowsHPC : use Windows HPC
472
473 If you are using one of IPython's builtin launchers, you can specify just the
474 prefix, e.g:
475
476 c.IPClusterStart.controller_launcher_class = 'SSH'
477
478 or:
479
480 ipcluster start --controller=MPI
481
482 """
483 )
484 reset = Bool(False, config=True,
485 help="Whether to reset config files as part of '--create'."
486 )
487
488 # flags = Dict(flags)
489 aliases = Dict(start_aliases)
490
491 def init_launchers(self):
492 self.controller_launcher = self.build_launcher(self.controller_launcher_class, 'Controller')
493 self.engine_launcher = self.build_launcher(self.engine_launcher_class, 'EngineSet')
494
495 def engines_stopped(self, r):
496 """prevent parent.engines_stopped from stopping everything on engine shutdown"""
497 pass
498
499 def start_controller(self):
500 self.log.info("Starting Controller with %s", self.controller_launcher_class)
501 self.controller_launcher.on_stop(self.stop_launchers)
502 try:
503 self.controller_launcher.start()
504 except:
505 self.log.exception("Controller start failed")
506 raise
507
508 def stop_controller(self):
509 # self.log.info("In stop_controller")
510 if self.controller_launcher and self.controller_launcher.running:
511 return self.controller_launcher.stop()
512
513 def stop_launchers(self, r=None):
514 if not self._stopping:
515 self.stop_controller()
516 super(IPClusterStart, self).stop_launchers()
517
518 def start(self):
519 """Start the app for the start subcommand."""
520 # First see if the cluster is already running
521 try:
522 pid = self.get_pid_from_file()
523 except PIDFileError:
524 pass
525 else:
526 if self.check_pid(pid):
527 self.log.critical(
528 'Cluster is already running with [pid=%s]. '
529 'use "ipcluster stop" to stop the cluster.' % pid
530 )
531 # Here I exit with a unusual exit status that other processes
532 # can watch for to learn how I existed.
533 self.exit(ALREADY_STARTED)
534 else:
535 self.remove_pid_file()
536
537
538 # Now log and daemonize
539 self.log.info(
540 'Starting ipcluster with [daemon=%r]' % self.daemonize
541 )
542 # TODO: Get daemonize working on Windows or as a Windows Server.
543 if self.daemonize:
544 if os.name=='posix':
545 daemonize()
546
547 def start():
548 self.start_controller()
549 self.loop.add_timeout(self.loop.time() + self.delay, self.start_engines)
550 self.loop.add_callback(start)
551 # Now write the new pid file AFTER our new forked pid is active.
552 self.write_pid_file()
553 try:
554 self.loop.start()
555 except KeyboardInterrupt:
556 pass
557 except zmq.ZMQError as e:
558 if e.errno == errno.EINTR:
559 pass
560 else:
561 raise
562 finally:
563 self.remove_pid_file()
564
565 base='ipython_parallel.apps.ipclusterapp.IPCluster'
566
567 class IPClusterApp(BaseIPythonApplication):
568 name = u'ipcluster'
569 description = _description
570 examples = _main_examples
571
572 subcommands = {
573 'start' : (base+'Start', start_help),
574 'stop' : (base+'Stop', stop_help),
575 'engines' : (base+'Engines', engines_help),
576 }
577
578 # no aliases or flags for parent App
579 aliases = Dict()
580 flags = Dict()
581
582 def start(self):
583 if self.subapp is None:
584 print("No subcommand specified. Must specify one of: %s"%(self.subcommands.keys()))
585 print()
586 self.print_description()
587 self.print_subcommands()
588 self.exit(1)
589 else:
590 return self.subapp.start()
591
592 launch_new_instance = IPClusterApp.launch_instance
593
594 if __name__ == '__main__':
595 launch_new_instance()
596
This diff has been collapsed as it changes many lines, (548 lines changed) Show them Hide them
@@ -1,548 +0,0 b''
1 #!/usr/bin/env python
2 # encoding: utf-8
3 """
4 The IPython controller application.
5
6 Authors:
7
8 * Brian Granger
9 * MinRK
10
11 """
12
13 #-----------------------------------------------------------------------------
14 # Copyright (C) 2008 The IPython Development Team
15 #
16 # Distributed under the terms of the BSD License. The full license is in
17 # the file COPYING, distributed as part of this software.
18 #-----------------------------------------------------------------------------
19
20 #-----------------------------------------------------------------------------
21 # Imports
22 #-----------------------------------------------------------------------------
23
24 from __future__ import with_statement
25
26 import json
27 import os
28 import stat
29 import sys
30
31 from multiprocessing import Process
32 from signal import signal, SIGINT, SIGABRT, SIGTERM
33
34 import zmq
35 from zmq.devices import ProcessMonitoredQueue
36 from zmq.log.handlers import PUBHandler
37
38 from IPython.core.profiledir import ProfileDir
39
40 from ipython_parallel.apps.baseapp import (
41 BaseParallelApplication,
42 base_aliases,
43 base_flags,
44 catch_config_error,
45 )
46 from IPython.utils.importstring import import_item
47 from IPython.utils.localinterfaces import localhost, public_ips
48 from IPython.utils.traitlets import Instance, Unicode, Bool, List, Dict, TraitError
49
50 from IPython.kernel.zmq.session import (
51 Session, session_aliases, session_flags,
52 )
53
54 from ipython_parallel.controller.heartmonitor import HeartMonitor
55 from ipython_parallel.controller.hub import HubFactory
56 from ipython_parallel.controller.scheduler import TaskScheduler,launch_scheduler
57 from ipython_parallel.controller.dictdb import DictDB
58
59 from ipython_parallel.util import split_url, disambiguate_url, set_hwm
60
61 # conditional import of SQLiteDB / MongoDB backend class
62 real_dbs = []
63
64 try:
65 from ipython_parallel.controller.sqlitedb import SQLiteDB
66 except ImportError:
67 pass
68 else:
69 real_dbs.append(SQLiteDB)
70
71 try:
72 from ipython_parallel.controller.mongodb import MongoDB
73 except ImportError:
74 pass
75 else:
76 real_dbs.append(MongoDB)
77
78
79
80 #-----------------------------------------------------------------------------
81 # Module level variables
82 #-----------------------------------------------------------------------------
83
84
85 _description = """Start the IPython controller for parallel computing.
86
87 The IPython controller provides a gateway between the IPython engines and
88 clients. The controller needs to be started before the engines and can be
89 configured using command line options or using a cluster directory. Cluster
90 directories contain config, log and security files and are usually located in
91 your ipython directory and named as "profile_name". See the `profile`
92 and `profile-dir` options for details.
93 """
94
95 _examples = """
96 ipcontroller --ip=192.168.0.1 --port=1000 # listen on ip, port for engines
97 ipcontroller --scheme=pure # use the pure zeromq scheduler
98 """
99
100
101 #-----------------------------------------------------------------------------
102 # The main application
103 #-----------------------------------------------------------------------------
104 flags = {}
105 flags.update(base_flags)
106 flags.update({
107 'usethreads' : ( {'IPControllerApp' : {'use_threads' : True}},
108 'Use threads instead of processes for the schedulers'),
109 'sqlitedb' : ({'HubFactory' : {'db_class' : 'ipython_parallel.controller.sqlitedb.SQLiteDB'}},
110 'use the SQLiteDB backend'),
111 'mongodb' : ({'HubFactory' : {'db_class' : 'ipython_parallel.controller.mongodb.MongoDB'}},
112 'use the MongoDB backend'),
113 'dictdb' : ({'HubFactory' : {'db_class' : 'ipython_parallel.controller.dictdb.DictDB'}},
114 'use the in-memory DictDB backend'),
115 'nodb' : ({'HubFactory' : {'db_class' : 'ipython_parallel.controller.dictdb.NoDB'}},
116 """use dummy DB backend, which doesn't store any information.
117
118 This is the default as of IPython 0.13.
119
120 To enable delayed or repeated retrieval of results from the Hub,
121 select one of the true db backends.
122 """),
123 'reuse' : ({'IPControllerApp' : {'reuse_files' : True}},
124 'reuse existing json connection files'),
125 'restore' : ({'IPControllerApp' : {'restore_engines' : True, 'reuse_files' : True}},
126 'Attempt to restore engines from a JSON file. '
127 'For use when resuming a crashed controller'),
128 })
129
130 flags.update(session_flags)
131
132 aliases = dict(
133 ssh = 'IPControllerApp.ssh_server',
134 enginessh = 'IPControllerApp.engine_ssh_server',
135 location = 'IPControllerApp.location',
136
137 url = 'HubFactory.url',
138 ip = 'HubFactory.ip',
139 transport = 'HubFactory.transport',
140 port = 'HubFactory.regport',
141
142 ping = 'HeartMonitor.period',
143
144 scheme = 'TaskScheduler.scheme_name',
145 hwm = 'TaskScheduler.hwm',
146 )
147 aliases.update(base_aliases)
148 aliases.update(session_aliases)
149
150 class IPControllerApp(BaseParallelApplication):
151
152 name = u'ipcontroller'
153 description = _description
154 examples = _examples
155 classes = [ProfileDir, Session, HubFactory, TaskScheduler, HeartMonitor, DictDB] + real_dbs
156
157 # change default to True
158 auto_create = Bool(True, config=True,
159 help="""Whether to create profile dir if it doesn't exist.""")
160
161 reuse_files = Bool(False, config=True,
162 help="""Whether to reuse existing json connection files.
163 If False, connection files will be removed on a clean exit.
164 """
165 )
166 restore_engines = Bool(False, config=True,
167 help="""Reload engine state from JSON file
168 """
169 )
170 ssh_server = Unicode(u'', config=True,
171 help="""ssh url for clients to use when connecting to the Controller
172 processes. It should be of the form: [user@]server[:port]. The
173 Controller's listening addresses must be accessible from the ssh server""",
174 )
175 engine_ssh_server = Unicode(u'', config=True,
176 help="""ssh url for engines to use when connecting to the Controller
177 processes. It should be of the form: [user@]server[:port]. The
178 Controller's listening addresses must be accessible from the ssh server""",
179 )
180 location = Unicode(u'', config=True,
181 help="""The external IP or domain name of the Controller, used for disambiguating
182 engine and client connections.""",
183 )
184 import_statements = List([], config=True,
185 help="import statements to be run at startup. Necessary in some environments"
186 )
187
188 use_threads = Bool(False, config=True,
189 help='Use threads instead of processes for the schedulers',
190 )
191
192 engine_json_file = Unicode('ipcontroller-engine.json', config=True,
193 help="JSON filename where engine connection info will be stored.")
194 client_json_file = Unicode('ipcontroller-client.json', config=True,
195 help="JSON filename where client connection info will be stored.")
196
197 def _cluster_id_changed(self, name, old, new):
198 super(IPControllerApp, self)._cluster_id_changed(name, old, new)
199 self.engine_json_file = "%s-engine.json" % self.name
200 self.client_json_file = "%s-client.json" % self.name
201
202
203 # internal
204 children = List()
205 mq_class = Unicode('zmq.devices.ProcessMonitoredQueue')
206
207 def _use_threads_changed(self, name, old, new):
208 self.mq_class = 'zmq.devices.%sMonitoredQueue'%('Thread' if new else 'Process')
209
210 write_connection_files = Bool(True,
211 help="""Whether to write connection files to disk.
212 True in all cases other than runs with `reuse_files=True` *after the first*
213 """
214 )
215
216 aliases = Dict(aliases)
217 flags = Dict(flags)
218
219
220 def save_connection_dict(self, fname, cdict):
221 """save a connection dict to json file."""
222 c = self.config
223 url = cdict['registration']
224 location = cdict['location']
225
226 if not location:
227 if public_ips():
228 location = public_ips()[-1]
229 else:
230 self.log.warn("Could not identify this machine's IP, assuming %s."
231 " You may need to specify '--location=<external_ip_address>' to help"
232 " IPython decide when to connect via loopback." % localhost() )
233 location = localhost()
234 cdict['location'] = location
235 fname = os.path.join(self.profile_dir.security_dir, fname)
236 self.log.info("writing connection info to %s", fname)
237 with open(fname, 'w') as f:
238 f.write(json.dumps(cdict, indent=2))
239 os.chmod(fname, stat.S_IRUSR|stat.S_IWUSR)
240
241 def load_config_from_json(self):
242 """load config from existing json connector files."""
243 c = self.config
244 self.log.debug("loading config from JSON")
245
246 # load engine config
247
248 fname = os.path.join(self.profile_dir.security_dir, self.engine_json_file)
249 self.log.info("loading connection info from %s", fname)
250 with open(fname) as f:
251 ecfg = json.loads(f.read())
252
253 # json gives unicode, Session.key wants bytes
254 c.Session.key = ecfg['key'].encode('ascii')
255
256 xport,ip = ecfg['interface'].split('://')
257
258 c.HubFactory.engine_ip = ip
259 c.HubFactory.engine_transport = xport
260
261 self.location = ecfg['location']
262 if not self.engine_ssh_server:
263 self.engine_ssh_server = ecfg['ssh']
264
265 # load client config
266
267 fname = os.path.join(self.profile_dir.security_dir, self.client_json_file)
268 self.log.info("loading connection info from %s", fname)
269 with open(fname) as f:
270 ccfg = json.loads(f.read())
271
272 for key in ('key', 'registration', 'pack', 'unpack', 'signature_scheme'):
273 assert ccfg[key] == ecfg[key], "mismatch between engine and client info: %r" % key
274
275 xport,addr = ccfg['interface'].split('://')
276
277 c.HubFactory.client_transport = xport
278 c.HubFactory.client_ip = ip
279 if not self.ssh_server:
280 self.ssh_server = ccfg['ssh']
281
282 # load port config:
283 c.HubFactory.regport = ecfg['registration']
284 c.HubFactory.hb = (ecfg['hb_ping'], ecfg['hb_pong'])
285 c.HubFactory.control = (ccfg['control'], ecfg['control'])
286 c.HubFactory.mux = (ccfg['mux'], ecfg['mux'])
287 c.HubFactory.task = (ccfg['task'], ecfg['task'])
288 c.HubFactory.iopub = (ccfg['iopub'], ecfg['iopub'])
289 c.HubFactory.notifier_port = ccfg['notification']
290
291 def cleanup_connection_files(self):
292 if self.reuse_files:
293 self.log.debug("leaving JSON connection files for reuse")
294 return
295 self.log.debug("cleaning up JSON connection files")
296 for f in (self.client_json_file, self.engine_json_file):
297 f = os.path.join(self.profile_dir.security_dir, f)
298 try:
299 os.remove(f)
300 except Exception as e:
301 self.log.error("Failed to cleanup connection file: %s", e)
302 else:
303 self.log.debug(u"removed %s", f)
304
305 def load_secondary_config(self):
306 """secondary config, loading from JSON and setting defaults"""
307 if self.reuse_files:
308 try:
309 self.load_config_from_json()
310 except (AssertionError,IOError) as e:
311 self.log.error("Could not load config from JSON: %s" % e)
312 else:
313 # successfully loaded config from JSON, and reuse=True
314 # no need to wite back the same file
315 self.write_connection_files = False
316
317 self.log.debug("Config changed")
318 self.log.debug(repr(self.config))
319
320 def init_hub(self):
321 c = self.config
322
323 self.do_import_statements()
324
325 try:
326 self.factory = HubFactory(config=c, log=self.log)
327 # self.start_logging()
328 self.factory.init_hub()
329 except TraitError:
330 raise
331 except Exception:
332 self.log.error("Couldn't construct the Controller", exc_info=True)
333 self.exit(1)
334
335 if self.write_connection_files:
336 # save to new json config files
337 f = self.factory
338 base = {
339 'key' : f.session.key.decode('ascii'),
340 'location' : self.location,
341 'pack' : f.session.packer,
342 'unpack' : f.session.unpacker,
343 'signature_scheme' : f.session.signature_scheme,
344 }
345
346 cdict = {'ssh' : self.ssh_server}
347 cdict.update(f.client_info)
348 cdict.update(base)
349 self.save_connection_dict(self.client_json_file, cdict)
350
351 edict = {'ssh' : self.engine_ssh_server}
352 edict.update(f.engine_info)
353 edict.update(base)
354 self.save_connection_dict(self.engine_json_file, edict)
355
356 fname = "engines%s.json" % self.cluster_id
357 self.factory.hub.engine_state_file = os.path.join(self.profile_dir.log_dir, fname)
358 if self.restore_engines:
359 self.factory.hub._load_engine_state()
360 # load key into config so other sessions in this process (TaskScheduler)
361 # have the same value
362 self.config.Session.key = self.factory.session.key
363
364 def init_schedulers(self):
365 children = self.children
366 mq = import_item(str(self.mq_class))
367
368 f = self.factory
369 ident = f.session.bsession
370 # disambiguate url, in case of *
371 monitor_url = disambiguate_url(f.monitor_url)
372 # maybe_inproc = 'inproc://monitor' if self.use_threads else monitor_url
373 # IOPub relay (in a Process)
374 q = mq(zmq.PUB, zmq.SUB, zmq.PUB, b'N/A',b'iopub')
375 q.bind_in(f.client_url('iopub'))
376 q.setsockopt_in(zmq.IDENTITY, ident + b"_iopub")
377 q.bind_out(f.engine_url('iopub'))
378 q.setsockopt_out(zmq.SUBSCRIBE, b'')
379 q.connect_mon(monitor_url)
380 q.daemon=True
381 children.append(q)
382
383 # Multiplexer Queue (in a Process)
384 q = mq(zmq.ROUTER, zmq.ROUTER, zmq.PUB, b'in', b'out')
385
386 q.bind_in(f.client_url('mux'))
387 q.setsockopt_in(zmq.IDENTITY, b'mux_in')
388 q.bind_out(f.engine_url('mux'))
389 q.setsockopt_out(zmq.IDENTITY, b'mux_out')
390 q.connect_mon(monitor_url)
391 q.daemon=True
392 children.append(q)
393
394 # Control Queue (in a Process)
395 q = mq(zmq.ROUTER, zmq.ROUTER, zmq.PUB, b'incontrol', b'outcontrol')
396 q.bind_in(f.client_url('control'))
397 q.setsockopt_in(zmq.IDENTITY, b'control_in')
398 q.bind_out(f.engine_url('control'))
399 q.setsockopt_out(zmq.IDENTITY, b'control_out')
400 q.connect_mon(monitor_url)
401 q.daemon=True
402 children.append(q)
403 if 'TaskScheduler.scheme_name' in self.config:
404 scheme = self.config.TaskScheduler.scheme_name
405 else:
406 scheme = TaskScheduler.scheme_name.get_default_value()
407 # Task Queue (in a Process)
408 if scheme == 'pure':
409 self.log.warn("task::using pure DEALER Task scheduler")
410 q = mq(zmq.ROUTER, zmq.DEALER, zmq.PUB, b'intask', b'outtask')
411 # q.setsockopt_out(zmq.HWM, hub.hwm)
412 q.bind_in(f.client_url('task'))
413 q.setsockopt_in(zmq.IDENTITY, b'task_in')
414 q.bind_out(f.engine_url('task'))
415 q.setsockopt_out(zmq.IDENTITY, b'task_out')
416 q.connect_mon(monitor_url)
417 q.daemon=True
418 children.append(q)
419 elif scheme == 'none':
420 self.log.warn("task::using no Task scheduler")
421
422 else:
423 self.log.info("task::using Python %s Task scheduler"%scheme)
424 sargs = (f.client_url('task'), f.engine_url('task'),
425 monitor_url, disambiguate_url(f.client_url('notification')),
426 disambiguate_url(f.client_url('registration')),
427 )
428 kwargs = dict(logname='scheduler', loglevel=self.log_level,
429 log_url = self.log_url, config=dict(self.config))
430 if 'Process' in self.mq_class:
431 # run the Python scheduler in a Process
432 q = Process(target=launch_scheduler, args=sargs, kwargs=kwargs)
433 q.daemon=True
434 children.append(q)
435 else:
436 # single-threaded Controller
437 kwargs['in_thread'] = True
438 launch_scheduler(*sargs, **kwargs)
439
440 # set unlimited HWM for all relay devices
441 if hasattr(zmq, 'SNDHWM'):
442 q = children[0]
443 q.setsockopt_in(zmq.RCVHWM, 0)
444 q.setsockopt_out(zmq.SNDHWM, 0)
445
446 for q in children[1:]:
447 if not hasattr(q, 'setsockopt_in'):
448 continue
449 q.setsockopt_in(zmq.SNDHWM, 0)
450 q.setsockopt_in(zmq.RCVHWM, 0)
451 q.setsockopt_out(zmq.SNDHWM, 0)
452 q.setsockopt_out(zmq.RCVHWM, 0)
453 q.setsockopt_mon(zmq.SNDHWM, 0)
454
455
456 def terminate_children(self):
457 child_procs = []
458 for child in self.children:
459 if isinstance(child, ProcessMonitoredQueue):
460 child_procs.append(child.launcher)
461 elif isinstance(child, Process):
462 child_procs.append(child)
463 if child_procs:
464 self.log.critical("terminating children...")
465 for child in child_procs:
466 try:
467 child.terminate()
468 except OSError:
469 # already dead
470 pass
471
472 def handle_signal(self, sig, frame):
473 self.log.critical("Received signal %i, shutting down", sig)
474 self.terminate_children()
475 self.loop.stop()
476
477 def init_signal(self):
478 for sig in (SIGINT, SIGABRT, SIGTERM):
479 signal(sig, self.handle_signal)
480
481 def do_import_statements(self):
482 statements = self.import_statements
483 for s in statements:
484 try:
485 self.log.msg("Executing statement: '%s'" % s)
486 exec(s, globals(), locals())
487 except:
488 self.log.msg("Error running statement: %s" % s)
489
490 def forward_logging(self):
491 if self.log_url:
492 self.log.info("Forwarding logging to %s"%self.log_url)
493 context = zmq.Context.instance()
494 lsock = context.socket(zmq.PUB)
495 lsock.connect(self.log_url)
496 handler = PUBHandler(lsock)
497 handler.root_topic = 'controller'
498 handler.setLevel(self.log_level)
499 self.log.addHandler(handler)
500
501 @catch_config_error
502 def initialize(self, argv=None):
503 super(IPControllerApp, self).initialize(argv)
504 self.forward_logging()
505 self.load_secondary_config()
506 self.init_hub()
507 self.init_schedulers()
508
509 def start(self):
510 # Start the subprocesses:
511 self.factory.start()
512 # children must be started before signals are setup,
513 # otherwise signal-handling will fire multiple times
514 for child in self.children:
515 child.start()
516 self.init_signal()
517
518 self.write_pid_file(overwrite=True)
519
520 try:
521 self.factory.loop.start()
522 except KeyboardInterrupt:
523 self.log.critical("Interrupted, Exiting...\n")
524 finally:
525 self.cleanup_connection_files()
526
527
528 def launch_new_instance(*args, **kwargs):
529 """Create and run the IPython controller"""
530 if sys.platform == 'win32':
531 # make sure we don't get called from a multiprocessing subprocess
532 # this can result in infinite Controllers being started on Windows
533 # which doesn't have a proper fork, so multiprocessing is wonky
534
535 # this only comes up when IPython has been installed using vanilla
536 # setuptools, and *not* distribute.
537 import multiprocessing
538 p = multiprocessing.current_process()
539 # the main process has name 'MainProcess'
540 # subprocesses will have names like 'Process-1'
541 if p.name != 'MainProcess':
542 # we are a subprocess, don't start another Controller!
543 return
544 return IPControllerApp.launch_instance(*args, **kwargs)
545
546
547 if __name__ == '__main__':
548 launch_new_instance()
@@ -1,397 +0,0 b''
1 #!/usr/bin/env python
2 # encoding: utf-8
3 """
4 The IPython engine application
5
6 Authors:
7
8 * Brian Granger
9 * MinRK
10
11 """
12
13 #-----------------------------------------------------------------------------
14 # Copyright (C) 2008-2011 The IPython Development Team
15 #
16 # Distributed under the terms of the BSD License. The full license is in
17 # the file COPYING, distributed as part of this software.
18 #-----------------------------------------------------------------------------
19
20 #-----------------------------------------------------------------------------
21 # Imports
22 #-----------------------------------------------------------------------------
23
24 import json
25 import os
26 import sys
27 import time
28
29 import zmq
30 from zmq.eventloop import ioloop
31
32 from IPython.core.profiledir import ProfileDir
33 from ipython_parallel.apps.baseapp import (
34 BaseParallelApplication,
35 base_aliases,
36 base_flags,
37 catch_config_error,
38 )
39 from IPython.kernel.zmq.log import EnginePUBHandler
40 from IPython.kernel.zmq.ipkernel import IPythonKernel as Kernel
41 from IPython.kernel.zmq.kernelapp import IPKernelApp
42 from IPython.kernel.zmq.session import (
43 Session, session_aliases, session_flags
44 )
45 from IPython.kernel.zmq.zmqshell import ZMQInteractiveShell
46
47 from IPython.config.configurable import Configurable
48
49 from ipython_parallel.engine.engine import EngineFactory
50 from ipython_parallel.util import disambiguate_ip_address
51
52 from IPython.utils.importstring import import_item
53 from IPython.utils.py3compat import cast_bytes
54 from IPython.utils.traitlets import Bool, Unicode, Dict, List, Float, Instance
55
56
57 #-----------------------------------------------------------------------------
58 # Module level variables
59 #-----------------------------------------------------------------------------
60
61 _description = """Start an IPython engine for parallel computing.
62
63 IPython engines run in parallel and perform computations on behalf of a client
64 and controller. A controller needs to be started before the engines. The
65 engine can be configured using command line options or using a cluster
66 directory. Cluster directories contain config, log and security files and are
67 usually located in your ipython directory and named as "profile_name".
68 See the `profile` and `profile-dir` options for details.
69 """
70
71 _examples = """
72 ipengine --ip=192.168.0.1 --port=1000 # connect to hub at ip and port
73 ipengine --log-to-file --log-level=DEBUG # log to a file with DEBUG verbosity
74 """
75
76 #-----------------------------------------------------------------------------
77 # MPI configuration
78 #-----------------------------------------------------------------------------
79
80 mpi4py_init = """from mpi4py import MPI as mpi
81 mpi.size = mpi.COMM_WORLD.Get_size()
82 mpi.rank = mpi.COMM_WORLD.Get_rank()
83 """
84
85
86 pytrilinos_init = """from PyTrilinos import Epetra
87 class SimpleStruct:
88 pass
89 mpi = SimpleStruct()
90 mpi.rank = 0
91 mpi.size = 0
92 """
93
94 class MPI(Configurable):
95 """Configurable for MPI initialization"""
96 use = Unicode('', config=True,
97 help='How to enable MPI (mpi4py, pytrilinos, or empty string to disable).'
98 )
99
100 def _use_changed(self, name, old, new):
101 # load default init script if it's not set
102 if not self.init_script:
103 self.init_script = self.default_inits.get(new, '')
104
105 init_script = Unicode('', config=True,
106 help="Initialization code for MPI")
107
108 default_inits = Dict({'mpi4py' : mpi4py_init, 'pytrilinos':pytrilinos_init},
109 config=True)
110
111
112 #-----------------------------------------------------------------------------
113 # Main application
114 #-----------------------------------------------------------------------------
115 aliases = dict(
116 file = 'IPEngineApp.url_file',
117 c = 'IPEngineApp.startup_command',
118 s = 'IPEngineApp.startup_script',
119
120 url = 'EngineFactory.url',
121 ssh = 'EngineFactory.sshserver',
122 sshkey = 'EngineFactory.sshkey',
123 ip = 'EngineFactory.ip',
124 transport = 'EngineFactory.transport',
125 port = 'EngineFactory.regport',
126 location = 'EngineFactory.location',
127
128 timeout = 'EngineFactory.timeout',
129
130 mpi = 'MPI.use',
131
132 )
133 aliases.update(base_aliases)
134 aliases.update(session_aliases)
135 flags = {}
136 flags.update(base_flags)
137 flags.update(session_flags)
138
139 class IPEngineApp(BaseParallelApplication):
140
141 name = 'ipengine'
142 description = _description
143 examples = _examples
144 classes = List([ZMQInteractiveShell, ProfileDir, Session, EngineFactory, Kernel, MPI])
145
146 startup_script = Unicode(u'', config=True,
147 help='specify a script to be run at startup')
148 startup_command = Unicode('', config=True,
149 help='specify a command to be run at startup')
150
151 url_file = Unicode(u'', config=True,
152 help="""The full location of the file containing the connection information for
153 the controller. If this is not given, the file must be in the
154 security directory of the cluster directory. This location is
155 resolved using the `profile` or `profile_dir` options.""",
156 )
157 wait_for_url_file = Float(5, config=True,
158 help="""The maximum number of seconds to wait for url_file to exist.
159 This is useful for batch-systems and shared-filesystems where the
160 controller and engine are started at the same time and it
161 may take a moment for the controller to write the connector files.""")
162
163 url_file_name = Unicode(u'ipcontroller-engine.json', config=True)
164
165 def _cluster_id_changed(self, name, old, new):
166 if new:
167 base = 'ipcontroller-%s' % new
168 else:
169 base = 'ipcontroller'
170 self.url_file_name = "%s-engine.json" % base
171
172 log_url = Unicode('', config=True,
173 help="""The URL for the iploggerapp instance, for forwarding
174 logging to a central location.""")
175
176 # an IPKernelApp instance, used to setup listening for shell frontends
177 kernel_app = Instance(IPKernelApp, allow_none=True)
178
179 aliases = Dict(aliases)
180 flags = Dict(flags)
181
182 @property
183 def kernel(self):
184 """allow access to the Kernel object, so I look like IPKernelApp"""
185 return self.engine.kernel
186
187 def find_url_file(self):
188 """Set the url file.
189
190 Here we don't try to actually see if it exists for is valid as that
191 is hadled by the connection logic.
192 """
193 config = self.config
194 # Find the actual controller key file
195 if not self.url_file:
196 self.url_file = os.path.join(
197 self.profile_dir.security_dir,
198 self.url_file_name
199 )
200
201 def load_connector_file(self):
202 """load config from a JSON connector file,
203 at a *lower* priority than command-line/config files.
204 """
205
206 self.log.info("Loading url_file %r", self.url_file)
207 config = self.config
208
209 with open(self.url_file) as f:
210 num_tries = 0
211 max_tries = 5
212 d = ""
213 while not d:
214 try:
215 d = json.loads(f.read())
216 except ValueError:
217 if num_tries > max_tries:
218 raise
219 num_tries += 1
220 time.sleep(0.5)
221
222 # allow hand-override of location for disambiguation
223 # and ssh-server
224 if 'EngineFactory.location' not in config:
225 config.EngineFactory.location = d['location']
226 if 'EngineFactory.sshserver' not in config:
227 config.EngineFactory.sshserver = d.get('ssh')
228
229 location = config.EngineFactory.location
230
231 proto, ip = d['interface'].split('://')
232 ip = disambiguate_ip_address(ip, location)
233 d['interface'] = '%s://%s' % (proto, ip)
234
235 # DO NOT allow override of basic URLs, serialization, or key
236 # JSON file takes top priority there
237 config.Session.key = cast_bytes(d['key'])
238 config.Session.signature_scheme = d['signature_scheme']
239
240 config.EngineFactory.url = d['interface'] + ':%i' % d['registration']
241
242 config.Session.packer = d['pack']
243 config.Session.unpacker = d['unpack']
244
245 self.log.debug("Config changed:")
246 self.log.debug("%r", config)
247 self.connection_info = d
248
249 def bind_kernel(self, **kwargs):
250 """Promote engine to listening kernel, accessible to frontends."""
251 if self.kernel_app is not None:
252 return
253
254 self.log.info("Opening ports for direct connections as an IPython kernel")
255
256 kernel = self.kernel
257
258 kwargs.setdefault('config', self.config)
259 kwargs.setdefault('log', self.log)
260 kwargs.setdefault('profile_dir', self.profile_dir)
261 kwargs.setdefault('session', self.engine.session)
262
263 app = self.kernel_app = IPKernelApp(**kwargs)
264
265 # allow IPKernelApp.instance():
266 IPKernelApp._instance = app
267
268 app.init_connection_file()
269 # relevant contents of init_sockets:
270
271 app.shell_port = app._bind_socket(kernel.shell_streams[0], app.shell_port)
272 app.log.debug("shell ROUTER Channel on port: %i", app.shell_port)
273
274 app.iopub_port = app._bind_socket(kernel.iopub_socket, app.iopub_port)
275 app.log.debug("iopub PUB Channel on port: %i", app.iopub_port)
276
277 kernel.stdin_socket = self.engine.context.socket(zmq.ROUTER)
278 app.stdin_port = app._bind_socket(kernel.stdin_socket, app.stdin_port)
279 app.log.debug("stdin ROUTER Channel on port: %i", app.stdin_port)
280
281 # start the heartbeat, and log connection info:
282
283 app.init_heartbeat()
284
285 app.log_connection_info()
286 app.write_connection_file()
287
288
289 def init_engine(self):
290 # This is the working dir by now.
291 sys.path.insert(0, '')
292 config = self.config
293 # print config
294 self.find_url_file()
295
296 # was the url manually specified?
297 keys = set(self.config.EngineFactory.keys())
298 keys = keys.union(set(self.config.RegistrationFactory.keys()))
299
300 if keys.intersection(set(['ip', 'url', 'port'])):
301 # Connection info was specified, don't wait for the file
302 url_specified = True
303 self.wait_for_url_file = 0
304 else:
305 url_specified = False
306
307 if self.wait_for_url_file and not os.path.exists(self.url_file):
308 self.log.warn("url_file %r not found", self.url_file)
309 self.log.warn("Waiting up to %.1f seconds for it to arrive.", self.wait_for_url_file)
310 tic = time.time()
311 while not os.path.exists(self.url_file) and (time.time()-tic < self.wait_for_url_file):
312 # wait for url_file to exist, or until time limit
313 time.sleep(0.1)
314
315 if os.path.exists(self.url_file):
316 self.load_connector_file()
317 elif not url_specified:
318 self.log.fatal("Fatal: url file never arrived: %s", self.url_file)
319 self.exit(1)
320
321 exec_lines = []
322 for app in ('IPKernelApp', 'InteractiveShellApp'):
323 if '%s.exec_lines' % app in config:
324 exec_lines = config[app].exec_lines
325 break
326
327 exec_files = []
328 for app in ('IPKernelApp', 'InteractiveShellApp'):
329 if '%s.exec_files' % app in config:
330 exec_files = config[app].exec_files
331 break
332
333 config.IPKernelApp.exec_lines = exec_lines
334 config.IPKernelApp.exec_files = exec_files
335
336 if self.startup_script:
337 exec_files.append(self.startup_script)
338 if self.startup_command:
339 exec_lines.append(self.startup_command)
340
341 # Create the underlying shell class and Engine
342 # shell_class = import_item(self.master_config.Global.shell_class)
343 # print self.config
344 try:
345 self.engine = EngineFactory(config=config, log=self.log,
346 connection_info=self.connection_info,
347 )
348 except:
349 self.log.error("Couldn't start the Engine", exc_info=True)
350 self.exit(1)
351
352 def forward_logging(self):
353 if self.log_url:
354 self.log.info("Forwarding logging to %s", self.log_url)
355 context = self.engine.context
356 lsock = context.socket(zmq.PUB)
357 lsock.connect(self.log_url)
358 handler = EnginePUBHandler(self.engine, lsock)
359 handler.setLevel(self.log_level)
360 self.log.addHandler(handler)
361
362 def init_mpi(self):
363 global mpi
364 self.mpi = MPI(parent=self)
365
366 mpi_import_statement = self.mpi.init_script
367 if mpi_import_statement:
368 try:
369 self.log.info("Initializing MPI:")
370 self.log.info(mpi_import_statement)
371 exec(mpi_import_statement, globals())
372 except:
373 mpi = None
374 else:
375 mpi = None
376
377 @catch_config_error
378 def initialize(self, argv=None):
379 super(IPEngineApp, self).initialize(argv)
380 self.init_mpi()
381 self.init_engine()
382 self.forward_logging()
383
384 def start(self):
385 self.engine.start()
386 try:
387 self.engine.loop.start()
388 except KeyboardInterrupt:
389 self.log.critical("Engine Interrupted, shutting down...\n")
390
391
392 launch_new_instance = IPEngineApp.launch_instance
393
394
395 if __name__ == '__main__':
396 launch_new_instance()
397
@@ -1,95 +0,0 b''
1 #!/usr/bin/env python
2 # encoding: utf-8
3 """
4 A simple IPython logger application
5
6 Authors:
7
8 * MinRK
9
10 """
11
12 #-----------------------------------------------------------------------------
13 # Copyright (C) 2011 The IPython Development Team
14 #
15 # Distributed under the terms of the BSD License. The full license is in
16 # the file COPYING, distributed as part of this software.
17 #-----------------------------------------------------------------------------
18
19 #-----------------------------------------------------------------------------
20 # Imports
21 #-----------------------------------------------------------------------------
22
23 import os
24 import sys
25
26 import zmq
27
28 from IPython.core.profiledir import ProfileDir
29 from IPython.utils.traitlets import Bool, Dict, Unicode
30
31 from ipython_parallel.apps.baseapp import (
32 BaseParallelApplication,
33 base_aliases,
34 catch_config_error,
35 )
36 from ipython_parallel.apps.logwatcher import LogWatcher
37
38 #-----------------------------------------------------------------------------
39 # Module level variables
40 #-----------------------------------------------------------------------------
41
42 #: The default config file name for this application
43 _description = """Start an IPython logger for parallel computing.
44
45 IPython controllers and engines (and your own processes) can broadcast log messages
46 by registering a `zmq.log.handlers.PUBHandler` with the `logging` module. The
47 logger can be configured using command line options or using a cluster
48 directory. Cluster directories contain config, log and security files and are
49 usually located in your ipython directory and named as "profile_name".
50 See the `profile` and `profile-dir` options for details.
51 """
52
53
54 #-----------------------------------------------------------------------------
55 # Main application
56 #-----------------------------------------------------------------------------
57 aliases = {}
58 aliases.update(base_aliases)
59 aliases.update(dict(url='LogWatcher.url', topics='LogWatcher.topics'))
60
61 class IPLoggerApp(BaseParallelApplication):
62
63 name = u'iplogger'
64 description = _description
65 classes = [LogWatcher, ProfileDir]
66 aliases = Dict(aliases)
67
68 @catch_config_error
69 def initialize(self, argv=None):
70 super(IPLoggerApp, self).initialize(argv)
71 self.init_watcher()
72
73 def init_watcher(self):
74 try:
75 self.watcher = LogWatcher(parent=self, log=self.log)
76 except:
77 self.log.error("Couldn't start the LogWatcher", exc_info=True)
78 self.exit(1)
79 self.log.info("Listening for log messages on %r"%self.watcher.url)
80
81
82 def start(self):
83 self.watcher.start()
84 try:
85 self.watcher.loop.start()
86 except KeyboardInterrupt:
87 self.log.critical("Logging Interrupted, shutting down...\n")
88
89
90 launch_new_instance = IPLoggerApp.launch_instance
91
92
93 if __name__ == '__main__':
94 launch_new_instance()
95
This diff has been collapsed as it changes many lines, (1445 lines changed) Show them Hide them
@@ -1,1445 +0,0 b''
1 # encoding: utf-8
2 """Facilities for launching IPython processes asynchronously."""
3
4 # Copyright (c) IPython Development Team.
5 # Distributed under the terms of the Modified BSD License.
6
7 import copy
8 import logging
9 import os
10 import pipes
11 import stat
12 import sys
13 import time
14
15 # signal imports, handling various platforms, versions
16
17 from signal import SIGINT, SIGTERM
18 try:
19 from signal import SIGKILL
20 except ImportError:
21 # Windows
22 SIGKILL=SIGTERM
23
24 try:
25 # Windows >= 2.7, 3.2
26 from signal import CTRL_C_EVENT as SIGINT
27 except ImportError:
28 pass
29
30 from subprocess import Popen, PIPE, STDOUT
31 try:
32 from subprocess import check_output
33 except ImportError:
34 # pre-2.7, define check_output with Popen
35 def check_output(*args, **kwargs):
36 kwargs.update(dict(stdout=PIPE))
37 p = Popen(*args, **kwargs)
38 out,err = p.communicate()
39 return out
40
41 from zmq.eventloop import ioloop
42
43 from IPython.config.application import Application
44 from IPython.config.configurable import LoggingConfigurable
45 from IPython.utils.text import EvalFormatter
46 from IPython.utils.traitlets import (
47 Any, Integer, CFloat, List, Unicode, Dict, Instance, HasTraits, CRegExp
48 )
49 from IPython.utils.encoding import DEFAULT_ENCODING
50 from IPython.utils.path import get_home_dir, ensure_dir_exists
51 from IPython.utils.process import find_cmd, FindCmdError
52 from IPython.utils.py3compat import iteritems, itervalues
53
54 from .win32support import forward_read_events
55
56 from .winhpcjob import IPControllerTask, IPEngineTask, IPControllerJob, IPEngineSetJob
57
58 WINDOWS = os.name == 'nt'
59
60 #-----------------------------------------------------------------------------
61 # Paths to the kernel apps
62 #-----------------------------------------------------------------------------
63
64 ipcluster_cmd_argv = [sys.executable, "-m", "ipython_parallel.cluster"]
65
66 ipengine_cmd_argv = [sys.executable, "-m", "ipython_parallel.engine"]
67
68 ipcontroller_cmd_argv = [sys.executable, "-m", "ipython_parallel.controller"]
69
70 if WINDOWS and sys.version_info < (3,):
71 # `python -m package` doesn't work on Windows Python 2
72 # due to weird multiprocessing bugs
73 # and python -m module puts classes in the `__main__` module,
74 # so instance checks get confused
75 ipengine_cmd_argv = [sys.executable, "-c", "from ipython_parallel.engine.__main__ import main; main()"]
76 ipcontroller_cmd_argv = [sys.executable, "-c", "from ipython_parallel.controller.__main__ import main; main()"]
77
78 #-----------------------------------------------------------------------------
79 # Base launchers and errors
80 #-----------------------------------------------------------------------------
81
82 class LauncherError(Exception):
83 pass
84
85
86 class ProcessStateError(LauncherError):
87 pass
88
89
90 class UnknownStatus(LauncherError):
91 pass
92
93
94 class BaseLauncher(LoggingConfigurable):
95 """An asbtraction for starting, stopping and signaling a process."""
96
97 # In all of the launchers, the work_dir is where child processes will be
98 # run. This will usually be the profile_dir, but may not be. any work_dir
99 # passed into the __init__ method will override the config value.
100 # This should not be used to set the work_dir for the actual engine
101 # and controller. Instead, use their own config files or the
102 # controller_args, engine_args attributes of the launchers to add
103 # the work_dir option.
104 work_dir = Unicode(u'.')
105 loop = Instance('zmq.eventloop.ioloop.IOLoop', allow_none=True)
106
107 start_data = Any()
108 stop_data = Any()
109
110 def _loop_default(self):
111 return ioloop.IOLoop.instance()
112
113 def __init__(self, work_dir=u'.', config=None, **kwargs):
114 super(BaseLauncher, self).__init__(work_dir=work_dir, config=config, **kwargs)
115 self.state = 'before' # can be before, running, after
116 self.stop_callbacks = []
117
118 @property
119 def args(self):
120 """A list of cmd and args that will be used to start the process.
121
122 This is what is passed to :func:`spawnProcess` and the first element
123 will be the process name.
124 """
125 return self.find_args()
126
127 def find_args(self):
128 """The ``.args`` property calls this to find the args list.
129
130 Subcommand should implement this to construct the cmd and args.
131 """
132 raise NotImplementedError('find_args must be implemented in a subclass')
133
134 @property
135 def arg_str(self):
136 """The string form of the program arguments."""
137 return ' '.join(self.args)
138
139 @property
140 def running(self):
141 """Am I running."""
142 if self.state == 'running':
143 return True
144 else:
145 return False
146
147 def start(self):
148 """Start the process."""
149 raise NotImplementedError('start must be implemented in a subclass')
150
151 def stop(self):
152 """Stop the process and notify observers of stopping.
153
154 This method will return None immediately.
155 To observe the actual process stopping, see :meth:`on_stop`.
156 """
157 raise NotImplementedError('stop must be implemented in a subclass')
158
159 def on_stop(self, f):
160 """Register a callback to be called with this Launcher's stop_data
161 when the process actually finishes.
162 """
163 if self.state=='after':
164 return f(self.stop_data)
165 else:
166 self.stop_callbacks.append(f)
167
168 def notify_start(self, data):
169 """Call this to trigger startup actions.
170
171 This logs the process startup and sets the state to 'running'. It is
172 a pass-through so it can be used as a callback.
173 """
174
175 self.log.debug('Process %r started: %r', self.args[0], data)
176 self.start_data = data
177 self.state = 'running'
178 return data
179
180 def notify_stop(self, data):
181 """Call this to trigger process stop actions.
182
183 This logs the process stopping and sets the state to 'after'. Call
184 this to trigger callbacks registered via :meth:`on_stop`."""
185
186 self.log.debug('Process %r stopped: %r', self.args[0], data)
187 self.stop_data = data
188 self.state = 'after'
189 for i in range(len(self.stop_callbacks)):
190 d = self.stop_callbacks.pop()
191 d(data)
192 return data
193
194 def signal(self, sig):
195 """Signal the process.
196
197 Parameters
198 ----------
199 sig : str or int
200 'KILL', 'INT', etc., or any signal number
201 """
202 raise NotImplementedError('signal must be implemented in a subclass')
203
204 class ClusterAppMixin(HasTraits):
205 """MixIn for cluster args as traits"""
206 profile_dir=Unicode('')
207 cluster_id=Unicode('')
208
209 @property
210 def cluster_args(self):
211 return ['--profile-dir', self.profile_dir, '--cluster-id', self.cluster_id]
212
213 class ControllerMixin(ClusterAppMixin):
214 controller_cmd = List(ipcontroller_cmd_argv, config=True,
215 help="""Popen command to launch ipcontroller.""")
216 # Command line arguments to ipcontroller.
217 controller_args = List(['--log-to-file','--log-level=%i' % logging.INFO], config=True,
218 help="""command-line args to pass to ipcontroller""")
219
220 class EngineMixin(ClusterAppMixin):
221 engine_cmd = List(ipengine_cmd_argv, config=True,
222 help="""command to launch the Engine.""")
223 # Command line arguments for ipengine.
224 engine_args = List(['--log-to-file','--log-level=%i' % logging.INFO], config=True,
225 help="command-line arguments to pass to ipengine"
226 )
227
228
229 #-----------------------------------------------------------------------------
230 # Local process launchers
231 #-----------------------------------------------------------------------------
232
233
234 class LocalProcessLauncher(BaseLauncher):
235 """Start and stop an external process in an asynchronous manner.
236
237 This will launch the external process with a working directory of
238 ``self.work_dir``.
239 """
240
241 # This is used to to construct self.args, which is passed to
242 # spawnProcess.
243 cmd_and_args = List([])
244 poll_frequency = Integer(100) # in ms
245
246 def __init__(self, work_dir=u'.', config=None, **kwargs):
247 super(LocalProcessLauncher, self).__init__(
248 work_dir=work_dir, config=config, **kwargs
249 )
250 self.process = None
251 self.poller = None
252
253 def find_args(self):
254 return self.cmd_and_args
255
256 def start(self):
257 self.log.debug("Starting %s: %r", self.__class__.__name__, self.args)
258 if self.state == 'before':
259 self.process = Popen(self.args,
260 stdout=PIPE,stderr=PIPE,stdin=PIPE,
261 env=os.environ,
262 cwd=self.work_dir
263 )
264 if WINDOWS:
265 self.stdout = forward_read_events(self.process.stdout)
266 self.stderr = forward_read_events(self.process.stderr)
267 else:
268 self.stdout = self.process.stdout.fileno()
269 self.stderr = self.process.stderr.fileno()
270 self.loop.add_handler(self.stdout, self.handle_stdout, self.loop.READ)
271 self.loop.add_handler(self.stderr, self.handle_stderr, self.loop.READ)
272 self.poller = ioloop.PeriodicCallback(self.poll, self.poll_frequency, self.loop)
273 self.poller.start()
274 self.notify_start(self.process.pid)
275 else:
276 s = 'The process was already started and has state: %r' % self.state
277 raise ProcessStateError(s)
278
279 def stop(self):
280 return self.interrupt_then_kill()
281
282 def signal(self, sig):
283 if self.state == 'running':
284 if WINDOWS and sig != SIGINT:
285 # use Windows tree-kill for better child cleanup
286 check_output(['taskkill', '-pid', str(self.process.pid), '-t', '-f'])
287 else:
288 self.process.send_signal(sig)
289
290 def interrupt_then_kill(self, delay=2.0):
291 """Send INT, wait a delay and then send KILL."""
292 try:
293 self.signal(SIGINT)
294 except Exception:
295 self.log.debug("interrupt failed")
296 pass
297 self.killer = self.loop.add_timeout(self.loop.time() + delay, lambda : self.signal(SIGKILL))
298
299 # callbacks, etc:
300
301 def handle_stdout(self, fd, events):
302 if WINDOWS:
303 line = self.stdout.recv()
304 else:
305 line = self.process.stdout.readline()
306 # a stopped process will be readable but return empty strings
307 if line:
308 self.log.debug(line[:-1])
309 else:
310 self.poll()
311
312 def handle_stderr(self, fd, events):
313 if WINDOWS:
314 line = self.stderr.recv()
315 else:
316 line = self.process.stderr.readline()
317 # a stopped process will be readable but return empty strings
318 if line:
319 self.log.debug(line[:-1])
320 else:
321 self.poll()
322
323 def poll(self):
324 status = self.process.poll()
325 if status is not None:
326 self.poller.stop()
327 self.loop.remove_handler(self.stdout)
328 self.loop.remove_handler(self.stderr)
329 self.notify_stop(dict(exit_code=status, pid=self.process.pid))
330 return status
331
332 class LocalControllerLauncher(LocalProcessLauncher, ControllerMixin):
333 """Launch a controller as a regular external process."""
334
335 def find_args(self):
336 return self.controller_cmd + self.cluster_args + self.controller_args
337
338 def start(self):
339 """Start the controller by profile_dir."""
340 return super(LocalControllerLauncher, self).start()
341
342
343 class LocalEngineLauncher(LocalProcessLauncher, EngineMixin):
344 """Launch a single engine as a regular externall process."""
345
346 def find_args(self):
347 return self.engine_cmd + self.cluster_args + self.engine_args
348
349
350 class LocalEngineSetLauncher(LocalEngineLauncher):
351 """Launch a set of engines as regular external processes."""
352
353 delay = CFloat(0.1, config=True,
354 help="""delay (in seconds) between starting each engine after the first.
355 This can help force the engines to get their ids in order, or limit
356 process flood when starting many engines."""
357 )
358
359 # launcher class
360 launcher_class = LocalEngineLauncher
361
362 launchers = Dict()
363 stop_data = Dict()
364
365 def __init__(self, work_dir=u'.', config=None, **kwargs):
366 super(LocalEngineSetLauncher, self).__init__(
367 work_dir=work_dir, config=config, **kwargs
368 )
369
370 def start(self, n):
371 """Start n engines by profile or profile_dir."""
372 dlist = []
373 for i in range(n):
374 if i > 0:
375 time.sleep(self.delay)
376 el = self.launcher_class(work_dir=self.work_dir, parent=self, log=self.log,
377 profile_dir=self.profile_dir, cluster_id=self.cluster_id,
378 )
379
380 # Copy the engine args over to each engine launcher.
381 el.engine_cmd = copy.deepcopy(self.engine_cmd)
382 el.engine_args = copy.deepcopy(self.engine_args)
383 el.on_stop(self._notice_engine_stopped)
384 d = el.start()
385 self.launchers[i] = el
386 dlist.append(d)
387 self.notify_start(dlist)
388 return dlist
389
390 def find_args(self):
391 return ['engine set']
392
393 def signal(self, sig):
394 dlist = []
395 for el in itervalues(self.launchers):
396 d = el.signal(sig)
397 dlist.append(d)
398 return dlist
399
400 def interrupt_then_kill(self, delay=1.0):
401 dlist = []
402 for el in itervalues(self.launchers):
403 d = el.interrupt_then_kill(delay)
404 dlist.append(d)
405 return dlist
406
407 def stop(self):
408 return self.interrupt_then_kill()
409
410 def _notice_engine_stopped(self, data):
411 pid = data['pid']
412 for idx,el in iteritems(self.launchers):
413 if el.process.pid == pid:
414 break
415 self.launchers.pop(idx)
416 self.stop_data[idx] = data
417 if not self.launchers:
418 self.notify_stop(self.stop_data)
419
420
421 #-----------------------------------------------------------------------------
422 # MPI launchers
423 #-----------------------------------------------------------------------------
424
425
426 class MPILauncher(LocalProcessLauncher):
427 """Launch an external process using mpiexec."""
428
429 mpi_cmd = List(['mpiexec'], config=True,
430 help="The mpiexec command to use in starting the process."
431 )
432 mpi_args = List([], config=True,
433 help="The command line arguments to pass to mpiexec."
434 )
435 program = List(['date'],
436 help="The program to start via mpiexec.")
437 program_args = List([],
438 help="The command line argument to the program."
439 )
440 n = Integer(1)
441
442 def __init__(self, *args, **kwargs):
443 # deprecation for old MPIExec names:
444 config = kwargs.get('config', {})
445 for oldname in ('MPIExecLauncher', 'MPIExecControllerLauncher', 'MPIExecEngineSetLauncher'):
446 deprecated = config.get(oldname)
447 if deprecated:
448 newname = oldname.replace('MPIExec', 'MPI')
449 config[newname].update(deprecated)
450 self.log.warn("WARNING: %s name has been deprecated, use %s", oldname, newname)
451
452 super(MPILauncher, self).__init__(*args, **kwargs)
453
454 def find_args(self):
455 """Build self.args using all the fields."""
456 return self.mpi_cmd + ['-n', str(self.n)] + self.mpi_args + \
457 self.program + self.program_args
458
459 def start(self, n):
460 """Start n instances of the program using mpiexec."""
461 self.n = n
462 return super(MPILauncher, self).start()
463
464
465 class MPIControllerLauncher(MPILauncher, ControllerMixin):
466 """Launch a controller using mpiexec."""
467
468 # alias back to *non-configurable* program[_args] for use in find_args()
469 # this way all Controller/EngineSetLaunchers have the same form, rather
470 # than *some* having `program_args` and others `controller_args`
471 @property
472 def program(self):
473 return self.controller_cmd
474
475 @property
476 def program_args(self):
477 return self.cluster_args + self.controller_args
478
479 def start(self):
480 """Start the controller by profile_dir."""
481 return super(MPIControllerLauncher, self).start(1)
482
483
484 class MPIEngineSetLauncher(MPILauncher, EngineMixin):
485 """Launch engines using mpiexec"""
486
487 # alias back to *non-configurable* program[_args] for use in find_args()
488 # this way all Controller/EngineSetLaunchers have the same form, rather
489 # than *some* having `program_args` and others `controller_args`
490 @property
491 def program(self):
492 return self.engine_cmd
493
494 @property
495 def program_args(self):
496 return self.cluster_args + self.engine_args
497
498 def start(self, n):
499 """Start n engines by profile or profile_dir."""
500 self.n = n
501 return super(MPIEngineSetLauncher, self).start(n)
502
503 # deprecated MPIExec names
504 class DeprecatedMPILauncher(object):
505 def warn(self):
506 oldname = self.__class__.__name__
507 newname = oldname.replace('MPIExec', 'MPI')
508 self.log.warn("WARNING: %s name is deprecated, use %s", oldname, newname)
509
510 class MPIExecLauncher(MPILauncher, DeprecatedMPILauncher):
511 """Deprecated, use MPILauncher"""
512 def __init__(self, *args, **kwargs):
513 super(MPIExecLauncher, self).__init__(*args, **kwargs)
514 self.warn()
515
516 class MPIExecControllerLauncher(MPIControllerLauncher, DeprecatedMPILauncher):
517 """Deprecated, use MPIControllerLauncher"""
518 def __init__(self, *args, **kwargs):
519 super(MPIExecControllerLauncher, self).__init__(*args, **kwargs)
520 self.warn()
521
522 class MPIExecEngineSetLauncher(MPIEngineSetLauncher, DeprecatedMPILauncher):
523 """Deprecated, use MPIEngineSetLauncher"""
524 def __init__(self, *args, **kwargs):
525 super(MPIExecEngineSetLauncher, self).__init__(*args, **kwargs)
526 self.warn()
527
528
529 #-----------------------------------------------------------------------------
530 # SSH launchers
531 #-----------------------------------------------------------------------------
532
533 # TODO: Get SSH Launcher back to level of sshx in 0.10.2
534
535 class SSHLauncher(LocalProcessLauncher):
536 """A minimal launcher for ssh.
537
538 To be useful this will probably have to be extended to use the ``sshx``
539 idea for environment variables. There could be other things this needs
540 as well.
541 """
542
543 ssh_cmd = List(['ssh'], config=True,
544 help="command for starting ssh")
545 ssh_args = List(['-tt'], config=True,
546 help="args to pass to ssh")
547 scp_cmd = List(['scp'], config=True,
548 help="command for sending files")
549 program = List(['date'],
550 help="Program to launch via ssh")
551 program_args = List([],
552 help="args to pass to remote program")
553 hostname = Unicode('', config=True,
554 help="hostname on which to launch the program")
555 user = Unicode('', config=True,
556 help="username for ssh")
557 location = Unicode('', config=True,
558 help="user@hostname location for ssh in one setting")
559 to_fetch = List([], config=True,
560 help="List of (remote, local) files to fetch after starting")
561 to_send = List([], config=True,
562 help="List of (local, remote) files to send before starting")
563
564 def _hostname_changed(self, name, old, new):
565 if self.user:
566 self.location = u'%s@%s' % (self.user, new)
567 else:
568 self.location = new
569
570 def _user_changed(self, name, old, new):
571 self.location = u'%s@%s' % (new, self.hostname)
572
573 def find_args(self):
574 return self.ssh_cmd + self.ssh_args + [self.location] + \
575 list(map(pipes.quote, self.program + self.program_args))
576
577 def _send_file(self, local, remote):
578 """send a single file"""
579 full_remote = "%s:%s" % (self.location, remote)
580 for i in range(10):
581 if not os.path.exists(local):
582 self.log.debug("waiting for %s" % local)
583 time.sleep(1)
584 else:
585 break
586 remote_dir = os.path.dirname(remote)
587 self.log.info("ensuring remote %s:%s/ exists", self.location, remote_dir)
588 check_output(self.ssh_cmd + self.ssh_args + \
589 [self.location, 'mkdir', '-p', '--', remote_dir]
590 )
591 self.log.info("sending %s to %s", local, full_remote)
592 check_output(self.scp_cmd + [local, full_remote])
593
594 def send_files(self):
595 """send our files (called before start)"""
596 if not self.to_send:
597 return
598 for local_file, remote_file in self.to_send:
599 self._send_file(local_file, remote_file)
600
601 def _fetch_file(self, remote, local):
602 """fetch a single file"""
603 full_remote = "%s:%s" % (self.location, remote)
604 self.log.info("fetching %s from %s", local, full_remote)
605 for i in range(10):
606 # wait up to 10s for remote file to exist
607 check = check_output(self.ssh_cmd + self.ssh_args + \
608 [self.location, 'test -e', remote, "&& echo 'yes' || echo 'no'"])
609 check = check.decode(DEFAULT_ENCODING, 'replace').strip()
610 if check == u'no':
611 time.sleep(1)
612 elif check == u'yes':
613 break
614 local_dir = os.path.dirname(local)
615 ensure_dir_exists(local_dir, 775)
616 check_output(self.scp_cmd + [full_remote, local])
617
618 def fetch_files(self):
619 """fetch remote files (called after start)"""
620 if not self.to_fetch:
621 return
622 for remote_file, local_file in self.to_fetch:
623 self._fetch_file(remote_file, local_file)
624
625 def start(self, hostname=None, user=None):
626 if hostname is not None:
627 self.hostname = hostname
628 if user is not None:
629 self.user = user
630
631 self.send_files()
632 super(SSHLauncher, self).start()
633 self.fetch_files()
634
635 def signal(self, sig):
636 if self.state == 'running':
637 # send escaped ssh connection-closer
638 self.process.stdin.write('~.')
639 self.process.stdin.flush()
640
641 class SSHClusterLauncher(SSHLauncher, ClusterAppMixin):
642
643 remote_profile_dir = Unicode('', config=True,
644 help="""The remote profile_dir to use.
645
646 If not specified, use calling profile, stripping out possible leading homedir.
647 """)
648
649 def _profile_dir_changed(self, name, old, new):
650 if not self.remote_profile_dir:
651 # trigger remote_profile_dir_default logic again,
652 # in case it was already triggered before profile_dir was set
653 self.remote_profile_dir = self._strip_home(new)
654
655 @staticmethod
656 def _strip_home(path):
657 """turns /home/you/.ipython/profile_foo into .ipython/profile_foo"""
658 home = get_home_dir()
659 if not home.endswith('/'):
660 home = home+'/'
661
662 if path.startswith(home):
663 return path[len(home):]
664 else:
665 return path
666
667 def _remote_profile_dir_default(self):
668 return self._strip_home(self.profile_dir)
669
670 def _cluster_id_changed(self, name, old, new):
671 if new:
672 raise ValueError("cluster id not supported by SSH launchers")
673
674 @property
675 def cluster_args(self):
676 return ['--profile-dir', self.remote_profile_dir]
677
678 class SSHControllerLauncher(SSHClusterLauncher, ControllerMixin):
679
680 # alias back to *non-configurable* program[_args] for use in find_args()
681 # this way all Controller/EngineSetLaunchers have the same form, rather
682 # than *some* having `program_args` and others `controller_args`
683
684 def _controller_cmd_default(self):
685 return ['ipcontroller']
686
687 @property
688 def program(self):
689 return self.controller_cmd
690
691 @property
692 def program_args(self):
693 return self.cluster_args + self.controller_args
694
695 def _to_fetch_default(self):
696 return [
697 (os.path.join(self.remote_profile_dir, 'security', cf),
698 os.path.join(self.profile_dir, 'security', cf),)
699 for cf in ('ipcontroller-client.json', 'ipcontroller-engine.json')
700 ]
701
702 class SSHEngineLauncher(SSHClusterLauncher, EngineMixin):
703
704 # alias back to *non-configurable* program[_args] for use in find_args()
705 # this way all Controller/EngineSetLaunchers have the same form, rather
706 # than *some* having `program_args` and others `controller_args`
707
708 def _engine_cmd_default(self):
709 return ['ipengine']
710
711 @property
712 def program(self):
713 return self.engine_cmd
714
715 @property
716 def program_args(self):
717 return self.cluster_args + self.engine_args
718
719 def _to_send_default(self):
720 return [
721 (os.path.join(self.profile_dir, 'security', cf),
722 os.path.join(self.remote_profile_dir, 'security', cf))
723 for cf in ('ipcontroller-client.json', 'ipcontroller-engine.json')
724 ]
725
726
727 class SSHEngineSetLauncher(LocalEngineSetLauncher):
728 launcher_class = SSHEngineLauncher
729 engines = Dict(config=True,
730 help="""dict of engines to launch. This is a dict by hostname of ints,
731 corresponding to the number of engines to start on that host.""")
732
733 def _engine_cmd_default(self):
734 return ['ipengine']
735
736 @property
737 def engine_count(self):
738 """determine engine count from `engines` dict"""
739 count = 0
740 for n in itervalues(self.engines):
741 if isinstance(n, (tuple,list)):
742 n,args = n
743 count += n
744 return count
745
746 def start(self, n):
747 """Start engines by profile or profile_dir.
748 `n` is ignored, and the `engines` config property is used instead.
749 """
750
751 dlist = []
752 for host, n in iteritems(self.engines):
753 if isinstance(n, (tuple, list)):
754 n, args = n
755 else:
756 args = copy.deepcopy(self.engine_args)
757
758 if '@' in host:
759 user,host = host.split('@',1)
760 else:
761 user=None
762 for i in range(n):
763 if i > 0:
764 time.sleep(self.delay)
765 el = self.launcher_class(work_dir=self.work_dir, parent=self, log=self.log,
766 profile_dir=self.profile_dir, cluster_id=self.cluster_id,
767 )
768 if i > 0:
769 # only send files for the first engine on each host
770 el.to_send = []
771
772 # Copy the engine args over to each engine launcher.
773 el.engine_cmd = self.engine_cmd
774 el.engine_args = args
775 el.on_stop(self._notice_engine_stopped)
776 d = el.start(user=user, hostname=host)
777 self.launchers[ "%s/%i" % (host,i) ] = el
778 dlist.append(d)
779 self.notify_start(dlist)
780 return dlist
781
782
783 class SSHProxyEngineSetLauncher(SSHClusterLauncher):
784 """Launcher for calling
785 `ipcluster engines` on a remote machine.
786
787 Requires that remote profile is already configured.
788 """
789
790 n = Integer()
791 ipcluster_cmd = List(['ipcluster'], config=True)
792
793 @property
794 def program(self):
795 return self.ipcluster_cmd + ['engines']
796
797 @property
798 def program_args(self):
799 return ['-n', str(self.n), '--profile-dir', self.remote_profile_dir]
800
801 def _to_send_default(self):
802 return [
803 (os.path.join(self.profile_dir, 'security', cf),
804 os.path.join(self.remote_profile_dir, 'security', cf))
805 for cf in ('ipcontroller-client.json', 'ipcontroller-engine.json')
806 ]
807
808 def start(self, n):
809 self.n = n
810 super(SSHProxyEngineSetLauncher, self).start()
811
812
813 #-----------------------------------------------------------------------------
814 # Windows HPC Server 2008 scheduler launchers
815 #-----------------------------------------------------------------------------
816
817
818 # This is only used on Windows.
819 def find_job_cmd():
820 if WINDOWS:
821 try:
822 return find_cmd('job')
823 except (FindCmdError, ImportError):
824 # ImportError will be raised if win32api is not installed
825 return 'job'
826 else:
827 return 'job'
828
829
830 class WindowsHPCLauncher(BaseLauncher):
831
832 job_id_regexp = CRegExp(r'\d+', config=True,
833 help="""A regular expression used to get the job id from the output of the
834 submit_command. """
835 )
836 job_file_name = Unicode(u'ipython_job.xml', config=True,
837 help="The filename of the instantiated job script.")
838 # The full path to the instantiated job script. This gets made dynamically
839 # by combining the work_dir with the job_file_name.
840 job_file = Unicode(u'')
841 scheduler = Unicode('', config=True,
842 help="The hostname of the scheduler to submit the job to.")
843 job_cmd = Unicode(find_job_cmd(), config=True,
844 help="The command for submitting jobs.")
845
846 def __init__(self, work_dir=u'.', config=None, **kwargs):
847 super(WindowsHPCLauncher, self).__init__(
848 work_dir=work_dir, config=config, **kwargs
849 )
850
851 @property
852 def job_file(self):
853 return os.path.join(self.work_dir, self.job_file_name)
854
855 def write_job_file(self, n):
856 raise NotImplementedError("Implement write_job_file in a subclass.")
857
858 def find_args(self):
859 return [u'job.exe']
860
861 def parse_job_id(self, output):
862 """Take the output of the submit command and return the job id."""
863 m = self.job_id_regexp.search(output)
864 if m is not None:
865 job_id = m.group()
866 else:
867 raise LauncherError("Job id couldn't be determined: %s" % output)
868 self.job_id = job_id
869 self.log.info('Job started with id: %r', job_id)
870 return job_id
871
872 def start(self, n):
873 """Start n copies of the process using the Win HPC job scheduler."""
874 self.write_job_file(n)
875 args = [
876 'submit',
877 '/jobfile:%s' % self.job_file,
878 '/scheduler:%s' % self.scheduler
879 ]
880 self.log.debug("Starting Win HPC Job: %s" % (self.job_cmd + ' ' + ' '.join(args),))
881
882 output = check_output([self.job_cmd]+args,
883 env=os.environ,
884 cwd=self.work_dir,
885 stderr=STDOUT
886 )
887 output = output.decode(DEFAULT_ENCODING, 'replace')
888 job_id = self.parse_job_id(output)
889 self.notify_start(job_id)
890 return job_id
891
892 def stop(self):
893 args = [
894 'cancel',
895 self.job_id,
896 '/scheduler:%s' % self.scheduler
897 ]
898 self.log.info("Stopping Win HPC Job: %s" % (self.job_cmd + ' ' + ' '.join(args),))
899 try:
900 output = check_output([self.job_cmd]+args,
901 env=os.environ,
902 cwd=self.work_dir,
903 stderr=STDOUT
904 )
905 output = output.decode(DEFAULT_ENCODING, 'replace')
906 except:
907 output = u'The job already appears to be stopped: %r' % self.job_id
908 self.notify_stop(dict(job_id=self.job_id, output=output)) # Pass the output of the kill cmd
909 return output
910
911
912 class WindowsHPCControllerLauncher(WindowsHPCLauncher, ClusterAppMixin):
913
914 job_file_name = Unicode(u'ipcontroller_job.xml', config=True,
915 help="WinHPC xml job file.")
916 controller_args = List([], config=False,
917 help="extra args to pass to ipcontroller")
918
919 def write_job_file(self, n):
920 job = IPControllerJob(parent=self)
921
922 t = IPControllerTask(parent=self)
923 # The tasks work directory is *not* the actual work directory of
924 # the controller. It is used as the base path for the stdout/stderr
925 # files that the scheduler redirects to.
926 t.work_directory = self.profile_dir
927 # Add the profile_dir and from self.start().
928 t.controller_args.extend(self.cluster_args)
929 t.controller_args.extend(self.controller_args)
930 job.add_task(t)
931
932 self.log.debug("Writing job description file: %s", self.job_file)
933 job.write(self.job_file)
934
935 @property
936 def job_file(self):
937 return os.path.join(self.profile_dir, self.job_file_name)
938
939 def start(self):
940 """Start the controller by profile_dir."""
941 return super(WindowsHPCControllerLauncher, self).start(1)
942
943
944 class WindowsHPCEngineSetLauncher(WindowsHPCLauncher, ClusterAppMixin):
945
946 job_file_name = Unicode(u'ipengineset_job.xml', config=True,
947 help="jobfile for ipengines job")
948 engine_args = List([], config=False,
949 help="extra args to pas to ipengine")
950
951 def write_job_file(self, n):
952 job = IPEngineSetJob(parent=self)
953
954 for i in range(n):
955 t = IPEngineTask(parent=self)
956 # The tasks work directory is *not* the actual work directory of
957 # the engine. It is used as the base path for the stdout/stderr
958 # files that the scheduler redirects to.
959 t.work_directory = self.profile_dir
960 # Add the profile_dir and from self.start().
961 t.engine_args.extend(self.cluster_args)
962 t.engine_args.extend(self.engine_args)
963 job.add_task(t)
964
965 self.log.debug("Writing job description file: %s", self.job_file)
966 job.write(self.job_file)
967
968 @property
969 def job_file(self):
970 return os.path.join(self.profile_dir, self.job_file_name)
971
972 def start(self, n):
973 """Start the controller by profile_dir."""
974 return super(WindowsHPCEngineSetLauncher, self).start(n)
975
976
977 #-----------------------------------------------------------------------------
978 # Batch (PBS) system launchers
979 #-----------------------------------------------------------------------------
980
981 class BatchClusterAppMixin(ClusterAppMixin):
982 """ClusterApp mixin that updates the self.context dict, rather than cl-args."""
983 def _profile_dir_changed(self, name, old, new):
984 self.context[name] = new
985 _cluster_id_changed = _profile_dir_changed
986
987 def _profile_dir_default(self):
988 self.context['profile_dir'] = ''
989 return ''
990 def _cluster_id_default(self):
991 self.context['cluster_id'] = ''
992 return ''
993
994
995 class BatchSystemLauncher(BaseLauncher):
996 """Launch an external process using a batch system.
997
998 This class is designed to work with UNIX batch systems like PBS, LSF,
999 GridEngine, etc. The overall model is that there are different commands
1000 like qsub, qdel, etc. that handle the starting and stopping of the process.
1001
1002 This class also has the notion of a batch script. The ``batch_template``
1003 attribute can be set to a string that is a template for the batch script.
1004 This template is instantiated using string formatting. Thus the template can
1005 use {n} fot the number of instances. Subclasses can add additional variables
1006 to the template dict.
1007 """
1008
1009 # Subclasses must fill these in. See PBSEngineSet
1010 submit_command = List([''], config=True,
1011 help="The name of the command line program used to submit jobs.")
1012 delete_command = List([''], config=True,
1013 help="The name of the command line program used to delete jobs.")
1014 job_id_regexp = CRegExp('', config=True,
1015 help="""A regular expression used to get the job id from the output of the
1016 submit_command.""")
1017 job_id_regexp_group = Integer(0, config=True,
1018 help="""The group we wish to match in job_id_regexp (0 to match all)""")
1019 batch_template = Unicode('', config=True,
1020 help="The string that is the batch script template itself.")
1021 batch_template_file = Unicode(u'', config=True,
1022 help="The file that contains the batch template.")
1023 batch_file_name = Unicode(u'batch_script', config=True,
1024 help="The filename of the instantiated batch script.")
1025 queue = Unicode(u'', config=True,
1026 help="The PBS Queue.")
1027
1028 def _queue_changed(self, name, old, new):
1029 self.context[name] = new
1030
1031 n = Integer(1)
1032 _n_changed = _queue_changed
1033
1034 # not configurable, override in subclasses
1035 # PBS Job Array regex
1036 job_array_regexp = CRegExp('')
1037 job_array_template = Unicode('')
1038 # PBS Queue regex
1039 queue_regexp = CRegExp('')
1040 queue_template = Unicode('')
1041 # The default batch template, override in subclasses
1042 default_template = Unicode('')
1043 # The full path to the instantiated batch script.
1044 batch_file = Unicode(u'')
1045 # the format dict used with batch_template:
1046 context = Dict()
1047
1048 def _context_default(self):
1049 """load the default context with the default values for the basic keys
1050
1051 because the _trait_changed methods only load the context if they
1052 are set to something other than the default value.
1053 """
1054 return dict(n=1, queue=u'', profile_dir=u'', cluster_id=u'')
1055
1056 # the Formatter instance for rendering the templates:
1057 formatter = Instance(EvalFormatter, (), {})
1058
1059 def find_args(self):
1060 return self.submit_command + [self.batch_file]
1061
1062 def __init__(self, work_dir=u'.', config=None, **kwargs):
1063 super(BatchSystemLauncher, self).__init__(
1064 work_dir=work_dir, config=config, **kwargs
1065 )
1066 self.batch_file = os.path.join(self.work_dir, self.batch_file_name)
1067
1068 def parse_job_id(self, output):
1069 """Take the output of the submit command and return the job id."""
1070 m = self.job_id_regexp.search(output)
1071 if m is not None:
1072 job_id = m.group(self.job_id_regexp_group)
1073 else:
1074 raise LauncherError("Job id couldn't be determined: %s" % output)
1075 self.job_id = job_id
1076 self.log.info('Job submitted with job id: %r', job_id)
1077 return job_id
1078
1079 def write_batch_script(self, n):
1080 """Instantiate and write the batch script to the work_dir."""
1081 self.n = n
1082 # first priority is batch_template if set
1083 if self.batch_template_file and not self.batch_template:
1084 # second priority is batch_template_file
1085 with open(self.batch_template_file) as f:
1086 self.batch_template = f.read()
1087 if not self.batch_template:
1088 # third (last) priority is default_template
1089 self.batch_template = self.default_template
1090 # add jobarray or queue lines to user-specified template
1091 # note that this is *only* when user did not specify a template.
1092 self._insert_queue_in_script()
1093 self._insert_job_array_in_script()
1094 script_as_string = self.formatter.format(self.batch_template, **self.context)
1095 self.log.debug('Writing batch script: %s', self.batch_file)
1096 with open(self.batch_file, 'w') as f:
1097 f.write(script_as_string)
1098 os.chmod(self.batch_file, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR)
1099
1100 def _insert_queue_in_script(self):
1101 """Inserts a queue if required into the batch script.
1102 """
1103 if self.queue and not self.queue_regexp.search(self.batch_template):
1104 self.log.debug("adding PBS queue settings to batch script")
1105 firstline, rest = self.batch_template.split('\n',1)
1106 self.batch_template = u'\n'.join([firstline, self.queue_template, rest])
1107
1108 def _insert_job_array_in_script(self):
1109 """Inserts a job array if required into the batch script.
1110 """
1111 if not self.job_array_regexp.search(self.batch_template):
1112 self.log.debug("adding job array settings to batch script")
1113 firstline, rest = self.batch_template.split('\n',1)
1114 self.batch_template = u'\n'.join([firstline, self.job_array_template, rest])
1115
1116 def start(self, n):
1117 """Start n copies of the process using a batch system."""
1118 self.log.debug("Starting %s: %r", self.__class__.__name__, self.args)
1119 # Here we save profile_dir in the context so they
1120 # can be used in the batch script template as {profile_dir}
1121 self.write_batch_script(n)
1122 output = check_output(self.args, env=os.environ)
1123 output = output.decode(DEFAULT_ENCODING, 'replace')
1124
1125 job_id = self.parse_job_id(output)
1126 self.notify_start(job_id)
1127 return job_id
1128
1129 def stop(self):
1130 try:
1131 p = Popen(self.delete_command+[self.job_id], env=os.environ,
1132 stdout=PIPE, stderr=PIPE)
1133 out, err = p.communicate()
1134 output = out + err
1135 except:
1136 self.log.exception("Problem stopping cluster with command: %s" %
1137 (self.delete_command + [self.job_id]))
1138 output = ""
1139 output = output.decode(DEFAULT_ENCODING, 'replace')
1140 self.notify_stop(dict(job_id=self.job_id, output=output)) # Pass the output of the kill cmd
1141 return output
1142
1143
1144 class PBSLauncher(BatchSystemLauncher):
1145 """A BatchSystemLauncher subclass for PBS."""
1146
1147 submit_command = List(['qsub'], config=True,
1148 help="The PBS submit command ['qsub']")
1149 delete_command = List(['qdel'], config=True,
1150 help="The PBS delete command ['qsub']")
1151 job_id_regexp = CRegExp(r'\d+', config=True,
1152 help="Regular expresion for identifying the job ID [r'\d+']")
1153
1154 batch_file = Unicode(u'')
1155 job_array_regexp = CRegExp('#PBS\W+-t\W+[\w\d\-\$]+')
1156 job_array_template = Unicode('#PBS -t 1-{n}')
1157 queue_regexp = CRegExp('#PBS\W+-q\W+\$?\w+')
1158 queue_template = Unicode('#PBS -q {queue}')
1159
1160
1161 class PBSControllerLauncher(PBSLauncher, BatchClusterAppMixin):
1162 """Launch a controller using PBS."""
1163
1164 batch_file_name = Unicode(u'pbs_controller', config=True,
1165 help="batch file name for the controller job.")
1166 default_template= Unicode("""#!/bin/sh
1167 #PBS -V
1168 #PBS -N ipcontroller
1169 %s --log-to-file --profile-dir="{profile_dir}" --cluster-id="{cluster_id}"
1170 """%(' '.join(map(pipes.quote, ipcontroller_cmd_argv))))
1171
1172 def start(self):
1173 """Start the controller by profile or profile_dir."""
1174 return super(PBSControllerLauncher, self).start(1)
1175
1176
1177 class PBSEngineSetLauncher(PBSLauncher, BatchClusterAppMixin):
1178 """Launch Engines using PBS"""
1179 batch_file_name = Unicode(u'pbs_engines', config=True,
1180 help="batch file name for the engine(s) job.")
1181 default_template= Unicode(u"""#!/bin/sh
1182 #PBS -V
1183 #PBS -N ipengine
1184 %s --profile-dir="{profile_dir}" --cluster-id="{cluster_id}"
1185 """%(' '.join(map(pipes.quote,ipengine_cmd_argv))))
1186
1187
1188 #SGE is very similar to PBS
1189
1190 class SGELauncher(PBSLauncher):
1191 """Sun GridEngine is a PBS clone with slightly different syntax"""
1192 job_array_regexp = CRegExp('#\$\W+\-t')
1193 job_array_template = Unicode('#$ -t 1-{n}')
1194 queue_regexp = CRegExp('#\$\W+-q\W+\$?\w+')
1195 queue_template = Unicode('#$ -q {queue}')
1196
1197
1198 class SGEControllerLauncher(SGELauncher, BatchClusterAppMixin):
1199 """Launch a controller using SGE."""
1200
1201 batch_file_name = Unicode(u'sge_controller', config=True,
1202 help="batch file name for the ipontroller job.")
1203 default_template= Unicode(u"""#$ -V
1204 #$ -S /bin/sh
1205 #$ -N ipcontroller
1206 %s --log-to-file --profile-dir="{profile_dir}" --cluster-id="{cluster_id}"
1207 """%(' '.join(map(pipes.quote, ipcontroller_cmd_argv))))
1208
1209 def start(self):
1210 """Start the controller by profile or profile_dir."""
1211 return super(SGEControllerLauncher, self).start(1)
1212
1213
1214 class SGEEngineSetLauncher(SGELauncher, BatchClusterAppMixin):
1215 """Launch Engines with SGE"""
1216 batch_file_name = Unicode(u'sge_engines', config=True,
1217 help="batch file name for the engine(s) job.")
1218 default_template = Unicode("""#$ -V
1219 #$ -S /bin/sh
1220 #$ -N ipengine
1221 %s --profile-dir="{profile_dir}" --cluster-id="{cluster_id}"
1222 """%(' '.join(map(pipes.quote, ipengine_cmd_argv))))
1223
1224
1225 # LSF launchers
1226
1227 class LSFLauncher(BatchSystemLauncher):
1228 """A BatchSystemLauncher subclass for LSF."""
1229
1230 submit_command = List(['bsub'], config=True,
1231 help="The PBS submit command ['bsub']")
1232 delete_command = List(['bkill'], config=True,
1233 help="The PBS delete command ['bkill']")
1234 job_id_regexp = CRegExp(r'\d+', config=True,
1235 help="Regular expresion for identifying the job ID [r'\d+']")
1236
1237 batch_file = Unicode(u'')
1238 job_array_regexp = CRegExp('#BSUB[ \t]-J+\w+\[\d+-\d+\]')
1239 job_array_template = Unicode('#BSUB -J ipengine[1-{n}]')
1240 queue_regexp = CRegExp('#BSUB[ \t]+-q[ \t]+\w+')
1241 queue_template = Unicode('#BSUB -q {queue}')
1242
1243 def start(self, n):
1244 """Start n copies of the process using LSF batch system.
1245 This cant inherit from the base class because bsub expects
1246 to be piped a shell script in order to honor the #BSUB directives :
1247 bsub < script
1248 """
1249 # Here we save profile_dir in the context so they
1250 # can be used in the batch script template as {profile_dir}
1251 self.write_batch_script(n)
1252 piped_cmd = self.args[0]+'<\"'+self.args[1]+'\"'
1253 self.log.debug("Starting %s: %s", self.__class__.__name__, piped_cmd)
1254 p = Popen(piped_cmd, shell=True,env=os.environ,stdout=PIPE)
1255 output,err = p.communicate()
1256 output = output.decode(DEFAULT_ENCODING, 'replace')
1257 job_id = self.parse_job_id(output)
1258 self.notify_start(job_id)
1259 return job_id
1260
1261
1262 class LSFControllerLauncher(LSFLauncher, BatchClusterAppMixin):
1263 """Launch a controller using LSF."""
1264
1265 batch_file_name = Unicode(u'lsf_controller', config=True,
1266 help="batch file name for the controller job.")
1267 default_template= Unicode("""#!/bin/sh
1268 #BSUB -J ipcontroller
1269 #BSUB -oo ipcontroller.o.%%J
1270 #BSUB -eo ipcontroller.e.%%J
1271 %s --log-to-file --profile-dir="{profile_dir}" --cluster-id="{cluster_id}"
1272 """%(' '.join(map(pipes.quote,ipcontroller_cmd_argv))))
1273
1274 def start(self):
1275 """Start the controller by profile or profile_dir."""
1276 return super(LSFControllerLauncher, self).start(1)
1277
1278
1279 class LSFEngineSetLauncher(LSFLauncher, BatchClusterAppMixin):
1280 """Launch Engines using LSF"""
1281 batch_file_name = Unicode(u'lsf_engines', config=True,
1282 help="batch file name for the engine(s) job.")
1283 default_template= Unicode(u"""#!/bin/sh
1284 #BSUB -oo ipengine.o.%%J
1285 #BSUB -eo ipengine.e.%%J
1286 %s --profile-dir="{profile_dir}" --cluster-id="{cluster_id}"
1287 """%(' '.join(map(pipes.quote, ipengine_cmd_argv))))
1288
1289
1290
1291 class HTCondorLauncher(BatchSystemLauncher):
1292 """A BatchSystemLauncher subclass for HTCondor.
1293
1294 HTCondor requires that we launch the ipengine/ipcontroller scripts rather
1295 that the python instance but otherwise is very similar to PBS. This is because
1296 HTCondor destroys sys.executable when launching remote processes - a launched
1297 python process depends on sys.executable to effectively evaluate its
1298 module search paths. Without it, regardless of which python interpreter you launch
1299 you will get the to built in module search paths.
1300
1301 We use the ip{cluster, engine, controller} scripts as our executable to circumvent
1302 this - the mechanism of shebanged scripts means that the python binary will be
1303 launched with argv[0] set to the *location of the ip{cluster, engine, controller}
1304 scripts on the remote node*. This means you need to take care that:
1305
1306 a. Your remote nodes have their paths configured correctly, with the ipengine and ipcontroller
1307 of the python environment you wish to execute code in having top precedence.
1308 b. This functionality is untested on Windows.
1309
1310 If you need different behavior, consider making you own template.
1311 """
1312
1313 submit_command = List(['condor_submit'], config=True,
1314 help="The HTCondor submit command ['condor_submit']")
1315 delete_command = List(['condor_rm'], config=True,
1316 help="The HTCondor delete command ['condor_rm']")
1317 job_id_regexp = CRegExp(r'(\d+)\.$', config=True,
1318 help="Regular expression for identifying the job ID [r'(\d+)\.$']")
1319 job_id_regexp_group = Integer(1, config=True,
1320 help="""The group we wish to match in job_id_regexp [1]""")
1321
1322 job_array_regexp = CRegExp('queue\W+\$')
1323 job_array_template = Unicode('queue {n}')
1324
1325
1326 def _insert_job_array_in_script(self):
1327 """Inserts a job array if required into the batch script.
1328 """
1329 if not self.job_array_regexp.search(self.batch_template):
1330 self.log.debug("adding job array settings to batch script")
1331 #HTCondor requires that the job array goes at the bottom of the script
1332 self.batch_template = '\n'.join([self.batch_template,
1333 self.job_array_template])
1334
1335 def _insert_queue_in_script(self):
1336 """AFAIK, HTCondor doesn't have a concept of multiple queues that can be
1337 specified in the script.
1338 """
1339 pass
1340
1341
1342 class HTCondorControllerLauncher(HTCondorLauncher, BatchClusterAppMixin):
1343 """Launch a controller using HTCondor."""
1344
1345 batch_file_name = Unicode(u'htcondor_controller', config=True,
1346 help="batch file name for the controller job.")
1347 default_template = Unicode(r"""
1348 universe = vanilla
1349 executable = ipcontroller
1350 # by default we expect a shared file system
1351 transfer_executable = False
1352 arguments = --log-to-file '--profile-dir={profile_dir}' --cluster-id='{cluster_id}'
1353 """)
1354
1355 def start(self):
1356 """Start the controller by profile or profile_dir."""
1357 return super(HTCondorControllerLauncher, self).start(1)
1358
1359
1360 class HTCondorEngineSetLauncher(HTCondorLauncher, BatchClusterAppMixin):
1361 """Launch Engines using HTCondor"""
1362 batch_file_name = Unicode(u'htcondor_engines', config=True,
1363 help="batch file name for the engine(s) job.")
1364 default_template = Unicode("""
1365 universe = vanilla
1366 executable = ipengine
1367 # by default we expect a shared file system
1368 transfer_executable = False
1369 arguments = "--log-to-file '--profile-dir={profile_dir}' '--cluster-id={cluster_id}'"
1370 """)
1371
1372
1373 #-----------------------------------------------------------------------------
1374 # A launcher for ipcluster itself!
1375 #-----------------------------------------------------------------------------
1376
1377
1378 class IPClusterLauncher(LocalProcessLauncher):
1379 """Launch the ipcluster program in an external process."""
1380
1381 ipcluster_cmd = List(ipcluster_cmd_argv, config=True,
1382 help="Popen command for ipcluster")
1383 ipcluster_args = List(
1384 ['--clean-logs=True', '--log-to-file', '--log-level=%i'%logging.INFO], config=True,
1385 help="Command line arguments to pass to ipcluster.")
1386 ipcluster_subcommand = Unicode('start')
1387 profile = Unicode('default')
1388 n = Integer(2)
1389
1390 def find_args(self):
1391 return self.ipcluster_cmd + [self.ipcluster_subcommand] + \
1392 ['--n=%i'%self.n, '--profile=%s'%self.profile] + \
1393 self.ipcluster_args
1394
1395 def start(self):
1396 return super(IPClusterLauncher, self).start()
1397
1398 #-----------------------------------------------------------------------------
1399 # Collections of launchers
1400 #-----------------------------------------------------------------------------
1401
1402 local_launchers = [
1403 LocalControllerLauncher,
1404 LocalEngineLauncher,
1405 LocalEngineSetLauncher,
1406 ]
1407 mpi_launchers = [
1408 MPILauncher,
1409 MPIControllerLauncher,
1410 MPIEngineSetLauncher,
1411 ]
1412 ssh_launchers = [
1413 SSHLauncher,
1414 SSHControllerLauncher,
1415 SSHEngineLauncher,
1416 SSHEngineSetLauncher,
1417 SSHProxyEngineSetLauncher,
1418 ]
1419 winhpc_launchers = [
1420 WindowsHPCLauncher,
1421 WindowsHPCControllerLauncher,
1422 WindowsHPCEngineSetLauncher,
1423 ]
1424 pbs_launchers = [
1425 PBSLauncher,
1426 PBSControllerLauncher,
1427 PBSEngineSetLauncher,
1428 ]
1429 sge_launchers = [
1430 SGELauncher,
1431 SGEControllerLauncher,
1432 SGEEngineSetLauncher,
1433 ]
1434 lsf_launchers = [
1435 LSFLauncher,
1436 LSFControllerLauncher,
1437 LSFEngineSetLauncher,
1438 ]
1439 htcondor_launchers = [
1440 HTCondorLauncher,
1441 HTCondorControllerLauncher,
1442 HTCondorEngineSetLauncher,
1443 ]
1444 all_launchers = local_launchers + mpi_launchers + ssh_launchers + winhpc_launchers\
1445 + pbs_launchers + sge_launchers + lsf_launchers + htcondor_launchers
@@ -1,117 +0,0 b''
1 """
2 A simple logger object that consolidates messages incoming from ipcluster processes.
3
4 Authors:
5
6 * MinRK
7
8 """
9
10 #-----------------------------------------------------------------------------
11 # Copyright (C) 2011 The IPython Development Team
12 #
13 # Distributed under the terms of the BSD License. The full license is in
14 # the file COPYING, distributed as part of this software.
15 #-----------------------------------------------------------------------------
16
17 #-----------------------------------------------------------------------------
18 # Imports
19 #-----------------------------------------------------------------------------
20
21
22 import logging
23 import sys
24
25 import zmq
26 from zmq.eventloop import ioloop, zmqstream
27
28 from IPython.config.configurable import LoggingConfigurable
29 from IPython.utils.localinterfaces import localhost
30 from IPython.utils.traitlets import Int, Unicode, Instance, List
31
32 #-----------------------------------------------------------------------------
33 # Classes
34 #-----------------------------------------------------------------------------
35
36
37 class LogWatcher(LoggingConfigurable):
38 """A simple class that receives messages on a SUB socket, as published
39 by subclasses of `zmq.log.handlers.PUBHandler`, and logs them itself.
40
41 This can subscribe to multiple topics, but defaults to all topics.
42 """
43
44 # configurables
45 topics = List([''], config=True,
46 help="The ZMQ topics to subscribe to. Default is to subscribe to all messages")
47 url = Unicode(config=True,
48 help="ZMQ url on which to listen for log messages")
49 def _url_default(self):
50 return 'tcp://%s:20202' % localhost()
51
52 # internals
53 stream = Instance('zmq.eventloop.zmqstream.ZMQStream', allow_none=True)
54
55 context = Instance(zmq.Context)
56 def _context_default(self):
57 return zmq.Context.instance()
58
59 loop = Instance(zmq.eventloop.ioloop.IOLoop)
60 def _loop_default(self):
61 return ioloop.IOLoop.instance()
62
63 def __init__(self, **kwargs):
64 super(LogWatcher, self).__init__(**kwargs)
65 s = self.context.socket(zmq.SUB)
66 s.bind(self.url)
67 self.stream = zmqstream.ZMQStream(s, self.loop)
68 self.subscribe()
69 self.on_trait_change(self.subscribe, 'topics')
70
71 def start(self):
72 self.stream.on_recv(self.log_message)
73
74 def stop(self):
75 self.stream.stop_on_recv()
76
77 def subscribe(self):
78 """Update our SUB socket's subscriptions."""
79 self.stream.setsockopt(zmq.UNSUBSCRIBE, '')
80 if '' in self.topics:
81 self.log.debug("Subscribing to: everything")
82 self.stream.setsockopt(zmq.SUBSCRIBE, '')
83 else:
84 for topic in self.topics:
85 self.log.debug("Subscribing to: %r"%(topic))
86 self.stream.setsockopt(zmq.SUBSCRIBE, topic)
87
88 def _extract_level(self, topic_str):
89 """Turn 'engine.0.INFO.extra' into (logging.INFO, 'engine.0.extra')"""
90 topics = topic_str.split('.')
91 for idx,t in enumerate(topics):
92 level = getattr(logging, t, None)
93 if level is not None:
94 break
95
96 if level is None:
97 level = logging.INFO
98 else:
99 topics.pop(idx)
100
101 return level, '.'.join(topics)
102
103
104 def log_message(self, raw):
105 """receive and parse a message, then log it."""
106 if len(raw) != 2 or '.' not in raw[0]:
107 self.log.error("Invalid log message: %s"%raw)
108 return
109 else:
110 topic, msg = raw
111 # don't newline, since log messages always newline:
112 topic,level_name = topic.rsplit('.',1)
113 level,topic = self._extract_level(topic)
114 if msg[-1] == '\n':
115 msg = msg[:-1]
116 self.log.log(level, "[%s] %s" % (topic, msg))
117
@@ -1,74 +0,0 b''
1 """Utility for forwarding file read events over a zmq socket.
2
3 This is necessary because select on Windows only supports sockets, not FDs.
4
5 Authors:
6
7 * MinRK
8
9 """
10
11 #-----------------------------------------------------------------------------
12 # Copyright (C) 2011 The IPython Development Team
13 #
14 # Distributed under the terms of the BSD License. The full license is in
15 # the file COPYING, distributed as part of this software.
16 #-----------------------------------------------------------------------------
17
18 #-----------------------------------------------------------------------------
19 # Imports
20 #-----------------------------------------------------------------------------
21
22 import uuid
23 import zmq
24
25 from threading import Thread
26
27 from IPython.utils.py3compat import unicode_type
28
29 #-----------------------------------------------------------------------------
30 # Code
31 #-----------------------------------------------------------------------------
32
33 class ForwarderThread(Thread):
34 def __init__(self, sock, fd):
35 Thread.__init__(self)
36 self.daemon=True
37 self.sock = sock
38 self.fd = fd
39
40 def run(self):
41 """Loop through lines in self.fd, and send them over self.sock."""
42 line = self.fd.readline()
43 # allow for files opened in unicode mode
44 if isinstance(line, unicode_type):
45 send = self.sock.send_unicode
46 else:
47 send = self.sock.send
48 while line:
49 send(line)
50 line = self.fd.readline()
51 # line == '' means EOF
52 self.fd.close()
53 self.sock.close()
54
55 def forward_read_events(fd, context=None):
56 """Forward read events from an FD over a socket.
57
58 This method wraps a file in a socket pair, so it can
59 be polled for read events by select (specifically zmq.eventloop.ioloop)
60 """
61 if context is None:
62 context = zmq.Context.instance()
63 push = context.socket(zmq.PUSH)
64 push.setsockopt(zmq.LINGER, -1)
65 pull = context.socket(zmq.PULL)
66 addr='inproc://%s'%uuid.uuid4()
67 push.bind(addr)
68 pull.connect(addr)
69 forwarder = ForwarderThread(push, fd)
70 forwarder.start()
71 return pull
72
73
74 __all__ = ['forward_read_events']
@@ -1,320 +0,0 b''
1 # encoding: utf-8
2 """
3 Job and task components for writing .xml files that the Windows HPC Server
4 2008 can use to start jobs.
5
6 Authors:
7
8 * Brian Granger
9 * MinRK
10
11 """
12
13 #-----------------------------------------------------------------------------
14 # Copyright (C) 2008-2011 The IPython Development Team
15 #
16 # Distributed under the terms of the BSD License. The full license is in
17 # the file COPYING, distributed as part of this software.
18 #-----------------------------------------------------------------------------
19
20 #-----------------------------------------------------------------------------
21 # Imports
22 #-----------------------------------------------------------------------------
23
24 import os
25 import re
26 import uuid
27
28 from xml.etree import ElementTree as ET
29
30 from IPython.config.configurable import Configurable
31 from IPython.utils.py3compat import iteritems
32 from IPython.utils.traitlets import (
33 Unicode, Integer, List, Instance,
34 Enum, Bool
35 )
36
37 #-----------------------------------------------------------------------------
38 # Job and Task classes
39 #-----------------------------------------------------------------------------
40
41
42 def as_str(value):
43 if isinstance(value, str):
44 return value
45 elif isinstance(value, bool):
46 if value:
47 return 'true'
48 else:
49 return 'false'
50 elif isinstance(value, (int, float)):
51 return repr(value)
52 else:
53 return value
54
55
56 def indent(elem, level=0):
57 i = "\n" + level*" "
58 if len(elem):
59 if not elem.text or not elem.text.strip():
60 elem.text = i + " "
61 if not elem.tail or not elem.tail.strip():
62 elem.tail = i
63 for elem in elem:
64 indent(elem, level+1)
65 if not elem.tail or not elem.tail.strip():
66 elem.tail = i
67 else:
68 if level and (not elem.tail or not elem.tail.strip()):
69 elem.tail = i
70
71
72 def find_username():
73 domain = os.environ.get('USERDOMAIN')
74 username = os.environ.get('USERNAME','')
75 if domain is None:
76 return username
77 else:
78 return '%s\\%s' % (domain, username)
79
80
81 class WinHPCJob(Configurable):
82
83 job_id = Unicode('')
84 job_name = Unicode('MyJob', config=True)
85 min_cores = Integer(1, config=True)
86 max_cores = Integer(1, config=True)
87 min_sockets = Integer(1, config=True)
88 max_sockets = Integer(1, config=True)
89 min_nodes = Integer(1, config=True)
90 max_nodes = Integer(1, config=True)
91 unit_type = Unicode("Core", config=True)
92 auto_calculate_min = Bool(True, config=True)
93 auto_calculate_max = Bool(True, config=True)
94 run_until_canceled = Bool(False, config=True)
95 is_exclusive = Bool(False, config=True)
96 username = Unicode(find_username(), config=True)
97 job_type = Unicode('Batch', config=True)
98 priority = Enum(('Lowest','BelowNormal','Normal','AboveNormal','Highest'),
99 default_value='Highest', config=True)
100 requested_nodes = Unicode('', config=True)
101 project = Unicode('IPython', config=True)
102 xmlns = Unicode('http://schemas.microsoft.com/HPCS2008/scheduler/')
103 version = Unicode("2.000")
104 tasks = List([])
105
106 @property
107 def owner(self):
108 return self.username
109
110 def _write_attr(self, root, attr, key):
111 s = as_str(getattr(self, attr, ''))
112 if s:
113 root.set(key, s)
114
115 def as_element(self):
116 # We have to add _A_ type things to get the right order than
117 # the MSFT XML parser expects.
118 root = ET.Element('Job')
119 self._write_attr(root, 'version', '_A_Version')
120 self._write_attr(root, 'job_name', '_B_Name')
121 self._write_attr(root, 'unit_type', '_C_UnitType')
122 self._write_attr(root, 'min_cores', '_D_MinCores')
123 self._write_attr(root, 'max_cores', '_E_MaxCores')
124 self._write_attr(root, 'min_sockets', '_F_MinSockets')
125 self._write_attr(root, 'max_sockets', '_G_MaxSockets')
126 self._write_attr(root, 'min_nodes', '_H_MinNodes')
127 self._write_attr(root, 'max_nodes', '_I_MaxNodes')
128 self._write_attr(root, 'run_until_canceled', '_J_RunUntilCanceled')
129 self._write_attr(root, 'is_exclusive', '_K_IsExclusive')
130 self._write_attr(root, 'username', '_L_UserName')
131 self._write_attr(root, 'job_type', '_M_JobType')
132 self._write_attr(root, 'priority', '_N_Priority')
133 self._write_attr(root, 'requested_nodes', '_O_RequestedNodes')
134 self._write_attr(root, 'auto_calculate_max', '_P_AutoCalculateMax')
135 self._write_attr(root, 'auto_calculate_min', '_Q_AutoCalculateMin')
136 self._write_attr(root, 'project', '_R_Project')
137 self._write_attr(root, 'owner', '_S_Owner')
138 self._write_attr(root, 'xmlns', '_T_xmlns')
139 dependencies = ET.SubElement(root, "Dependencies")
140 etasks = ET.SubElement(root, "Tasks")
141 for t in self.tasks:
142 etasks.append(t.as_element())
143 return root
144
145 def tostring(self):
146 """Return the string representation of the job description XML."""
147 root = self.as_element()
148 indent(root)
149 txt = ET.tostring(root, encoding="utf-8").decode('utf-8')
150 # Now remove the tokens used to order the attributes.
151 txt = re.sub(r'_[A-Z]_','',txt)
152 txt = '<?xml version="1.0" encoding="utf-8"?>\n' + txt
153 return txt
154
155 def write(self, filename):
156 """Write the XML job description to a file."""
157 txt = self.tostring()
158 with open(filename, 'w') as f:
159 f.write(txt)
160
161 def add_task(self, task):
162 """Add a task to the job.
163
164 Parameters
165 ----------
166 task : :class:`WinHPCTask`
167 The task object to add.
168 """
169 self.tasks.append(task)
170
171
172 class WinHPCTask(Configurable):
173
174 task_id = Unicode('')
175 task_name = Unicode('')
176 version = Unicode("2.000")
177 min_cores = Integer(1, config=True)
178 max_cores = Integer(1, config=True)
179 min_sockets = Integer(1, config=True)
180 max_sockets = Integer(1, config=True)
181 min_nodes = Integer(1, config=True)
182 max_nodes = Integer(1, config=True)
183 unit_type = Unicode("Core", config=True)
184 command_line = Unicode('', config=True)
185 work_directory = Unicode('', config=True)
186 is_rerunnaable = Bool(True, config=True)
187 std_out_file_path = Unicode('', config=True)
188 std_err_file_path = Unicode('', config=True)
189 is_parametric = Bool(False, config=True)
190 environment_variables = Instance(dict, args=(), config=True)
191
192 def _write_attr(self, root, attr, key):
193 s = as_str(getattr(self, attr, ''))
194 if s:
195 root.set(key, s)
196
197 def as_element(self):
198 root = ET.Element('Task')
199 self._write_attr(root, 'version', '_A_Version')
200 self._write_attr(root, 'task_name', '_B_Name')
201 self._write_attr(root, 'min_cores', '_C_MinCores')
202 self._write_attr(root, 'max_cores', '_D_MaxCores')
203 self._write_attr(root, 'min_sockets', '_E_MinSockets')
204 self._write_attr(root, 'max_sockets', '_F_MaxSockets')
205 self._write_attr(root, 'min_nodes', '_G_MinNodes')
206 self._write_attr(root, 'max_nodes', '_H_MaxNodes')
207 self._write_attr(root, 'command_line', '_I_CommandLine')
208 self._write_attr(root, 'work_directory', '_J_WorkDirectory')
209 self._write_attr(root, 'is_rerunnaable', '_K_IsRerunnable')
210 self._write_attr(root, 'std_out_file_path', '_L_StdOutFilePath')
211 self._write_attr(root, 'std_err_file_path', '_M_StdErrFilePath')
212 self._write_attr(root, 'is_parametric', '_N_IsParametric')
213 self._write_attr(root, 'unit_type', '_O_UnitType')
214 root.append(self.get_env_vars())
215 return root
216
217 def get_env_vars(self):
218 env_vars = ET.Element('EnvironmentVariables')
219 for k, v in iteritems(self.environment_variables):
220 variable = ET.SubElement(env_vars, "Variable")
221 name = ET.SubElement(variable, "Name")
222 name.text = k
223 value = ET.SubElement(variable, "Value")
224 value.text = v
225 return env_vars
226
227
228
229 # By declaring these, we can configure the controller and engine separately!
230
231 class IPControllerJob(WinHPCJob):
232 job_name = Unicode('IPController', config=False)
233 is_exclusive = Bool(False, config=True)
234 username = Unicode(find_username(), config=True)
235 priority = Enum(('Lowest','BelowNormal','Normal','AboveNormal','Highest'),
236 default_value='Highest', config=True)
237 requested_nodes = Unicode('', config=True)
238 project = Unicode('IPython', config=True)
239
240
241 class IPEngineSetJob(WinHPCJob):
242 job_name = Unicode('IPEngineSet', config=False)
243 is_exclusive = Bool(False, config=True)
244 username = Unicode(find_username(), config=True)
245 priority = Enum(('Lowest','BelowNormal','Normal','AboveNormal','Highest'),
246 default_value='Highest', config=True)
247 requested_nodes = Unicode('', config=True)
248 project = Unicode('IPython', config=True)
249
250
251 class IPControllerTask(WinHPCTask):
252
253 task_name = Unicode('IPController', config=True)
254 controller_cmd = List(['ipcontroller.exe'], config=True)
255 controller_args = List(['--log-to-file', '--log-level=40'], config=True)
256 # I don't want these to be configurable
257 std_out_file_path = Unicode('', config=False)
258 std_err_file_path = Unicode('', config=False)
259 min_cores = Integer(1, config=False)
260 max_cores = Integer(1, config=False)
261 min_sockets = Integer(1, config=False)
262 max_sockets = Integer(1, config=False)
263 min_nodes = Integer(1, config=False)
264 max_nodes = Integer(1, config=False)
265 unit_type = Unicode("Core", config=False)
266 work_directory = Unicode('', config=False)
267
268 def __init__(self, **kwargs):
269 super(IPControllerTask, self).__init__(**kwargs)
270 the_uuid = uuid.uuid1()
271 self.std_out_file_path = os.path.join('log','ipcontroller-%s.out' % the_uuid)
272 self.std_err_file_path = os.path.join('log','ipcontroller-%s.err' % the_uuid)
273
274 @property
275 def command_line(self):
276 return ' '.join(self.controller_cmd + self.controller_args)
277
278
279 class IPEngineTask(WinHPCTask):
280
281 task_name = Unicode('IPEngine', config=True)
282 engine_cmd = List(['ipengine.exe'], config=True)
283 engine_args = List(['--log-to-file', '--log-level=40'], config=True)
284 # I don't want these to be configurable
285 std_out_file_path = Unicode('', config=False)
286 std_err_file_path = Unicode('', config=False)
287 min_cores = Integer(1, config=False)
288 max_cores = Integer(1, config=False)
289 min_sockets = Integer(1, config=False)
290 max_sockets = Integer(1, config=False)
291 min_nodes = Integer(1, config=False)
292 max_nodes = Integer(1, config=False)
293 unit_type = Unicode("Core", config=False)
294 work_directory = Unicode('', config=False)
295
296 def __init__(self, **kwargs):
297 super(IPEngineTask,self).__init__(**kwargs)
298 the_uuid = uuid.uuid1()
299 self.std_out_file_path = os.path.join('log','ipengine-%s.out' % the_uuid)
300 self.std_err_file_path = os.path.join('log','ipengine-%s.err' % the_uuid)
301
302 @property
303 def command_line(self):
304 return ' '.join(self.engine_cmd + self.engine_args)
305
306
307 # j = WinHPCJob(None)
308 # j.job_name = 'IPCluster'
309 # j.username = 'GNET\\bgranger'
310 # j.requested_nodes = 'GREEN'
311 #
312 # t = WinHPCTask(None)
313 # t.task_name = 'Controller'
314 # t.command_line = r"\\blue\domainusers$\bgranger\Python\Python25\Scripts\ipcontroller.exe --log-to-file -p default --log-level 10"
315 # t.work_directory = r"\\blue\domainusers$\bgranger\.ipython\cluster_default"
316 # t.std_out_file_path = 'controller-out.txt'
317 # t.std_err_file_path = 'controller-err.txt'
318 # t.environment_variables['PYTHONPATH'] = r"\\blue\domainusers$\bgranger\Python\Python25\Lib\site-packages"
319 # j.add_task(t)
320
1 NO CONTENT: file was removed
NO CONTENT: file was removed
This diff has been collapsed as it changes many lines, (703 lines changed) Show them Hide them
@@ -1,703 +0,0 b''
1 """AsyncResult objects for the client"""
2
3 # Copyright (c) IPython Development Team.
4 # Distributed under the terms of the Modified BSD License.
5
6 from __future__ import print_function
7
8 import sys
9 import time
10 from datetime import datetime
11
12 from zmq import MessageTracker
13
14 from IPython.core.display import clear_output, display, display_pretty
15 from decorator import decorator
16 from ipython_parallel import error
17 from IPython.utils.py3compat import string_types
18
19
20 def _raw_text(s):
21 display_pretty(s, raw=True)
22
23
24 # global empty tracker that's always done:
25 finished_tracker = MessageTracker()
26
27 @decorator
28 def check_ready(f, self, *args, **kwargs):
29 """Call spin() to sync state prior to calling the method."""
30 self.wait(0)
31 if not self._ready:
32 raise error.TimeoutError("result not ready")
33 return f(self, *args, **kwargs)
34
35 class AsyncResult(object):
36 """Class for representing results of non-blocking calls.
37
38 Provides the same interface as :py:class:`multiprocessing.pool.AsyncResult`.
39 """
40
41 msg_ids = None
42 _targets = None
43 _tracker = None
44 _single_result = False
45 owner = False,
46
47 def __init__(self, client, msg_ids, fname='unknown', targets=None, tracker=None,
48 owner=False,
49 ):
50 if isinstance(msg_ids, string_types):
51 # always a list
52 msg_ids = [msg_ids]
53 self._single_result = True
54 else:
55 self._single_result = False
56 if tracker is None:
57 # default to always done
58 tracker = finished_tracker
59 self._client = client
60 self.msg_ids = msg_ids
61 self._fname=fname
62 self._targets = targets
63 self._tracker = tracker
64 self.owner = owner
65
66 self._ready = False
67 self._outputs_ready = False
68 self._success = None
69 self._metadata = [self._client.metadata[id] for id in self.msg_ids]
70
71 def __repr__(self):
72 if self._ready:
73 return "<%s: finished>"%(self.__class__.__name__)
74 else:
75 return "<%s: %s>"%(self.__class__.__name__,self._fname)
76
77
78 def _reconstruct_result(self, res):
79 """Reconstruct our result from actual result list (always a list)
80
81 Override me in subclasses for turning a list of results
82 into the expected form.
83 """
84 if self._single_result:
85 return res[0]
86 else:
87 return res
88
89 def get(self, timeout=-1):
90 """Return the result when it arrives.
91
92 If `timeout` is not ``None`` and the result does not arrive within
93 `timeout` seconds then ``TimeoutError`` is raised. If the
94 remote call raised an exception then that exception will be reraised
95 by get() inside a `RemoteError`.
96 """
97 if not self.ready():
98 self.wait(timeout)
99
100 if self._ready:
101 if self._success:
102 return self._result
103 else:
104 raise self._exception
105 else:
106 raise error.TimeoutError("Result not ready.")
107
108 def _check_ready(self):
109 if not self.ready():
110 raise error.TimeoutError("Result not ready.")
111
112 def ready(self):
113 """Return whether the call has completed."""
114 if not self._ready:
115 self.wait(0)
116 elif not self._outputs_ready:
117 self._wait_for_outputs(0)
118
119 return self._ready
120
121 def wait(self, timeout=-1):
122 """Wait until the result is available or until `timeout` seconds pass.
123
124 This method always returns None.
125 """
126 if self._ready:
127 self._wait_for_outputs(timeout)
128 return
129 self._ready = self._client.wait(self.msg_ids, timeout)
130 if self._ready:
131 try:
132 results = list(map(self._client.results.get, self.msg_ids))
133 self._result = results
134 if self._single_result:
135 r = results[0]
136 if isinstance(r, Exception):
137 raise r
138 else:
139 results = error.collect_exceptions(results, self._fname)
140 self._result = self._reconstruct_result(results)
141 except Exception as e:
142 self._exception = e
143 self._success = False
144 else:
145 self._success = True
146 finally:
147 if timeout is None or timeout < 0:
148 # cutoff infinite wait at 10s
149 timeout = 10
150 self._wait_for_outputs(timeout)
151
152 if self.owner:
153
154 self._metadata = [self._client.metadata.pop(mid) for mid in self.msg_ids]
155 [self._client.results.pop(mid) for mid in self.msg_ids]
156
157
158
159 def successful(self):
160 """Return whether the call completed without raising an exception.
161
162 Will raise ``AssertionError`` if the result is not ready.
163 """
164 assert self.ready()
165 return self._success
166
167 #----------------------------------------------------------------
168 # Extra methods not in mp.pool.AsyncResult
169 #----------------------------------------------------------------
170
171 def get_dict(self, timeout=-1):
172 """Get the results as a dict, keyed by engine_id.
173
174 timeout behavior is described in `get()`.
175 """
176
177 results = self.get(timeout)
178 if self._single_result:
179 results = [results]
180 engine_ids = [ md['engine_id'] for md in self._metadata ]
181
182
183 rdict = {}
184 for engine_id, result in zip(engine_ids, results):
185 if engine_id in rdict:
186 raise ValueError("Cannot build dict, %i jobs ran on engine #%i" % (
187 engine_ids.count(engine_id), engine_id)
188 )
189 else:
190 rdict[engine_id] = result
191
192 return rdict
193
194 @property
195 def result(self):
196 """result property wrapper for `get(timeout=-1)`."""
197 return self.get()
198
199 # abbreviated alias:
200 r = result
201
202 @property
203 def metadata(self):
204 """property for accessing execution metadata."""
205 if self._single_result:
206 return self._metadata[0]
207 else:
208 return self._metadata
209
210 @property
211 def result_dict(self):
212 """result property as a dict."""
213 return self.get_dict()
214
215 def __dict__(self):
216 return self.get_dict(0)
217
218 def abort(self):
219 """abort my tasks."""
220 assert not self.ready(), "Can't abort, I am already done!"
221 return self._client.abort(self.msg_ids, targets=self._targets, block=True)
222
223 @property
224 def sent(self):
225 """check whether my messages have been sent."""
226 return self._tracker.done
227
228 def wait_for_send(self, timeout=-1):
229 """wait for pyzmq send to complete.
230
231 This is necessary when sending arrays that you intend to edit in-place.
232 `timeout` is in seconds, and will raise TimeoutError if it is reached
233 before the send completes.
234 """
235 return self._tracker.wait(timeout)
236
237 #-------------------------------------
238 # dict-access
239 #-------------------------------------
240
241 def __getitem__(self, key):
242 """getitem returns result value(s) if keyed by int/slice, or metadata if key is str.
243 """
244 if isinstance(key, int):
245 self._check_ready()
246 return error.collect_exceptions([self._result[key]], self._fname)[0]
247 elif isinstance(key, slice):
248 self._check_ready()
249 return error.collect_exceptions(self._result[key], self._fname)
250 elif isinstance(key, string_types):
251 # metadata proxy *does not* require that results are done
252 self.wait(0)
253 values = [ md[key] for md in self._metadata ]
254 if self._single_result:
255 return values[0]
256 else:
257 return values
258 else:
259 raise TypeError("Invalid key type %r, must be 'int','slice', or 'str'"%type(key))
260
261 def __getattr__(self, key):
262 """getattr maps to getitem for convenient attr access to metadata."""
263 try:
264 return self.__getitem__(key)
265 except (error.TimeoutError, KeyError):
266 raise AttributeError("%r object has no attribute %r"%(
267 self.__class__.__name__, key))
268
269 # asynchronous iterator:
270 def __iter__(self):
271 if self._single_result:
272 raise TypeError("AsyncResults with a single result are not iterable.")
273 try:
274 rlist = self.get(0)
275 except error.TimeoutError:
276 # wait for each result individually
277 for msg_id in self.msg_ids:
278 ar = AsyncResult(self._client, msg_id, self._fname)
279 yield ar.get()
280 else:
281 # already done
282 for r in rlist:
283 yield r
284
285 def __len__(self):
286 return len(self.msg_ids)
287
288 #-------------------------------------
289 # Sugar methods and attributes
290 #-------------------------------------
291
292 def timedelta(self, start, end, start_key=min, end_key=max):
293 """compute the difference between two sets of timestamps
294
295 The default behavior is to use the earliest of the first
296 and the latest of the second list, but this can be changed
297 by passing a different
298
299 Parameters
300 ----------
301
302 start : one or more datetime objects (e.g. ar.submitted)
303 end : one or more datetime objects (e.g. ar.received)
304 start_key : callable
305 Function to call on `start` to extract the relevant
306 entry [defalt: min]
307 end_key : callable
308 Function to call on `end` to extract the relevant
309 entry [default: max]
310
311 Returns
312 -------
313
314 dt : float
315 The time elapsed (in seconds) between the two selected timestamps.
316 """
317 if not isinstance(start, datetime):
318 # handle single_result AsyncResults, where ar.stamp is single object,
319 # not a list
320 start = start_key(start)
321 if not isinstance(end, datetime):
322 # handle single_result AsyncResults, where ar.stamp is single object,
323 # not a list
324 end = end_key(end)
325 return (end - start).total_seconds()
326
327 @property
328 def progress(self):
329 """the number of tasks which have been completed at this point.
330
331 Fractional progress would be given by 1.0 * ar.progress / len(ar)
332 """
333 self.wait(0)
334 return len(self) - len(set(self.msg_ids).intersection(self._client.outstanding))
335
336 @property
337 def elapsed(self):
338 """elapsed time since initial submission"""
339 if self.ready():
340 return self.wall_time
341
342 now = submitted = datetime.now()
343 for msg_id in self.msg_ids:
344 if msg_id in self._client.metadata:
345 stamp = self._client.metadata[msg_id]['submitted']
346 if stamp and stamp < submitted:
347 submitted = stamp
348 return (now-submitted).total_seconds()
349
350 @property
351 @check_ready
352 def serial_time(self):
353 """serial computation time of a parallel calculation
354
355 Computed as the sum of (completed-started) of each task
356 """
357 t = 0
358 for md in self._metadata:
359 t += (md['completed'] - md['started']).total_seconds()
360 return t
361
362 @property
363 @check_ready
364 def wall_time(self):
365 """actual computation time of a parallel calculation
366
367 Computed as the time between the latest `received` stamp
368 and the earliest `submitted`.
369
370 Only reliable if Client was spinning/waiting when the task finished, because
371 the `received` timestamp is created when a result is pulled off of the zmq queue,
372 which happens as a result of `client.spin()`.
373
374 For similar comparison of other timestamp pairs, check out AsyncResult.timedelta.
375
376 """
377 return self.timedelta(self.submitted, self.received)
378
379 def wait_interactive(self, interval=1., timeout=-1):
380 """interactive wait, printing progress at regular intervals"""
381 if timeout is None:
382 timeout = -1
383 N = len(self)
384 tic = time.time()
385 while not self.ready() and (timeout < 0 or time.time() - tic <= timeout):
386 self.wait(interval)
387 clear_output(wait=True)
388 print("%4i/%i tasks finished after %4i s" % (self.progress, N, self.elapsed), end="")
389 sys.stdout.flush()
390 print()
391 print("done")
392
393 def _republish_displaypub(self, content, eid):
394 """republish individual displaypub content dicts"""
395 try:
396 ip = get_ipython()
397 except NameError:
398 # displaypub is meaningless outside IPython
399 return
400 md = content['metadata'] or {}
401 md['engine'] = eid
402 ip.display_pub.publish(data=content['data'], metadata=md)
403
404 def _display_stream(self, text, prefix='', file=None):
405 if not text:
406 # nothing to display
407 return
408 if file is None:
409 file = sys.stdout
410 end = '' if text.endswith('\n') else '\n'
411
412 multiline = text.count('\n') > int(text.endswith('\n'))
413 if prefix and multiline and not text.startswith('\n'):
414 prefix = prefix + '\n'
415 print("%s%s" % (prefix, text), file=file, end=end)
416
417
418 def _display_single_result(self):
419 self._display_stream(self.stdout)
420 self._display_stream(self.stderr, file=sys.stderr)
421
422 try:
423 get_ipython()
424 except NameError:
425 # displaypub is meaningless outside IPython
426 return
427
428 for output in self.outputs:
429 self._republish_displaypub(output, self.engine_id)
430
431 if self.execute_result is not None:
432 display(self.get())
433
434 def _wait_for_outputs(self, timeout=-1):
435 """wait for the 'status=idle' message that indicates we have all outputs
436 """
437 if self._outputs_ready or not self._success:
438 # don't wait on errors
439 return
440
441 # cast None to -1 for infinite timeout
442 if timeout is None:
443 timeout = -1
444
445 tic = time.time()
446 while True:
447 self._client._flush_iopub(self._client._iopub_socket)
448 self._outputs_ready = all(md['outputs_ready']
449 for md in self._metadata)
450 if self._outputs_ready or \
451 (timeout >= 0 and time.time() > tic + timeout):
452 break
453 time.sleep(0.01)
454
455 @check_ready
456 def display_outputs(self, groupby="type"):
457 """republish the outputs of the computation
458
459 Parameters
460 ----------
461
462 groupby : str [default: type]
463 if 'type':
464 Group outputs by type (show all stdout, then all stderr, etc.):
465
466 [stdout:1] foo
467 [stdout:2] foo
468 [stderr:1] bar
469 [stderr:2] bar
470 if 'engine':
471 Display outputs for each engine before moving on to the next:
472
473 [stdout:1] foo
474 [stderr:1] bar
475 [stdout:2] foo
476 [stderr:2] bar
477
478 if 'order':
479 Like 'type', but further collate individual displaypub
480 outputs. This is meant for cases of each command producing
481 several plots, and you would like to see all of the first
482 plots together, then all of the second plots, and so on.
483 """
484 if self._single_result:
485 self._display_single_result()
486 return
487
488 stdouts = self.stdout
489 stderrs = self.stderr
490 execute_results = self.execute_result
491 output_lists = self.outputs
492 results = self.get()
493
494 targets = self.engine_id
495
496 if groupby == "engine":
497 for eid,stdout,stderr,outputs,r,execute_result in zip(
498 targets, stdouts, stderrs, output_lists, results, execute_results
499 ):
500 self._display_stream(stdout, '[stdout:%i] ' % eid)
501 self._display_stream(stderr, '[stderr:%i] ' % eid, file=sys.stderr)
502
503 try:
504 get_ipython()
505 except NameError:
506 # displaypub is meaningless outside IPython
507 return
508
509 if outputs or execute_result is not None:
510 _raw_text('[output:%i]' % eid)
511
512 for output in outputs:
513 self._republish_displaypub(output, eid)
514
515 if execute_result is not None:
516 display(r)
517
518 elif groupby in ('type', 'order'):
519 # republish stdout:
520 for eid,stdout in zip(targets, stdouts):
521 self._display_stream(stdout, '[stdout:%i] ' % eid)
522
523 # republish stderr:
524 for eid,stderr in zip(targets, stderrs):
525 self._display_stream(stderr, '[stderr:%i] ' % eid, file=sys.stderr)
526
527 try:
528 get_ipython()
529 except NameError:
530 # displaypub is meaningless outside IPython
531 return
532
533 if groupby == 'order':
534 output_dict = dict((eid, outputs) for eid,outputs in zip(targets, output_lists))
535 N = max(len(outputs) for outputs in output_lists)
536 for i in range(N):
537 for eid in targets:
538 outputs = output_dict[eid]
539 if len(outputs) >= N:
540 _raw_text('[output:%i]' % eid)
541 self._republish_displaypub(outputs[i], eid)
542 else:
543 # republish displaypub output
544 for eid,outputs in zip(targets, output_lists):
545 if outputs:
546 _raw_text('[output:%i]' % eid)
547 for output in outputs:
548 self._republish_displaypub(output, eid)
549
550 # finally, add execute_result:
551 for eid,r,execute_result in zip(targets, results, execute_results):
552 if execute_result is not None:
553 display(r)
554
555 else:
556 raise ValueError("groupby must be one of 'type', 'engine', 'collate', not %r" % groupby)
557
558
559
560
561 class AsyncMapResult(AsyncResult):
562 """Class for representing results of non-blocking gathers.
563
564 This will properly reconstruct the gather.
565
566 This class is iterable at any time, and will wait on results as they come.
567
568 If ordered=False, then the first results to arrive will come first, otherwise
569 results will be yielded in the order they were submitted.
570
571 """
572
573 def __init__(self, client, msg_ids, mapObject, fname='', ordered=True):
574 AsyncResult.__init__(self, client, msg_ids, fname=fname)
575 self._mapObject = mapObject
576 self._single_result = False
577 self.ordered = ordered
578
579 def _reconstruct_result(self, res):
580 """Perform the gather on the actual results."""
581 return self._mapObject.joinPartitions(res)
582
583 # asynchronous iterator:
584 def __iter__(self):
585 it = self._ordered_iter if self.ordered else self._unordered_iter
586 for r in it():
587 yield r
588
589 # asynchronous ordered iterator:
590 def _ordered_iter(self):
591 """iterator for results *as they arrive*, preserving submission order."""
592 try:
593 rlist = self.get(0)
594 except error.TimeoutError:
595 # wait for each result individually
596 for msg_id in self.msg_ids:
597 ar = AsyncResult(self._client, msg_id, self._fname)
598 rlist = ar.get()
599 try:
600 for r in rlist:
601 yield r
602 except TypeError:
603 # flattened, not a list
604 # this could get broken by flattened data that returns iterables
605 # but most calls to map do not expose the `flatten` argument
606 yield rlist
607 else:
608 # already done
609 for r in rlist:
610 yield r
611
612 # asynchronous unordered iterator:
613 def _unordered_iter(self):
614 """iterator for results *as they arrive*, on FCFS basis, ignoring submission order."""
615 try:
616 rlist = self.get(0)
617 except error.TimeoutError:
618 pending = set(self.msg_ids)
619 while pending:
620 try:
621 self._client.wait(pending, 1e-3)
622 except error.TimeoutError:
623 # ignore timeout error, because that only means
624 # *some* jobs are outstanding
625 pass
626 # update ready set with those no longer outstanding:
627 ready = pending.difference(self._client.outstanding)
628 # update pending to exclude those that are finished
629 pending = pending.difference(ready)
630 while ready:
631 msg_id = ready.pop()
632 ar = AsyncResult(self._client, msg_id, self._fname)
633 rlist = ar.get()
634 try:
635 for r in rlist:
636 yield r
637 except TypeError:
638 # flattened, not a list
639 # this could get broken by flattened data that returns iterables
640 # but most calls to map do not expose the `flatten` argument
641 yield rlist
642 else:
643 # already done
644 for r in rlist:
645 yield r
646
647
648 class AsyncHubResult(AsyncResult):
649 """Class to wrap pending results that must be requested from the Hub.
650
651 Note that waiting/polling on these objects requires polling the Hubover the network,
652 so use `AsyncHubResult.wait()` sparingly.
653 """
654
655 def _wait_for_outputs(self, timeout=-1):
656 """no-op, because HubResults are never incomplete"""
657 self._outputs_ready = True
658
659 def wait(self, timeout=-1):
660 """wait for result to complete."""
661 start = time.time()
662 if self._ready:
663 return
664 local_ids = [m for m in self.msg_ids if m in self._client.outstanding]
665 local_ready = self._client.wait(local_ids, timeout)
666 if local_ready:
667 remote_ids = [m for m in self.msg_ids if m not in self._client.results]
668 if not remote_ids:
669 self._ready = True
670 else:
671 rdict = self._client.result_status(remote_ids, status_only=False)
672 pending = rdict['pending']
673 while pending and (timeout < 0 or time.time() < start+timeout):
674 rdict = self._client.result_status(remote_ids, status_only=False)
675 pending = rdict['pending']
676 if pending:
677 time.sleep(0.1)
678 if not pending:
679 self._ready = True
680 if self._ready:
681 try:
682 results = list(map(self._client.results.get, self.msg_ids))
683 self._result = results
684 if self._single_result:
685 r = results[0]
686 if isinstance(r, Exception):
687 raise r
688 else:
689 results = error.collect_exceptions(results, self._fname)
690 self._result = self._reconstruct_result(results)
691 except Exception as e:
692 self._exception = e
693 self._success = False
694 else:
695 self._success = True
696 finally:
697 self._metadata = [self._client.metadata[mid] for mid in self.msg_ids]
698 if self.owner:
699 [self._client.metadata.pop(mid) for mid in self.msg_ids]
700 [self._client.results.pop(mid) for mid in self.msg_ids]
701
702
703 __all__ = ['AsyncResult', 'AsyncMapResult', 'AsyncHubResult']
This diff has been collapsed as it changes many lines, (1892 lines changed) Show them Hide them
@@ -1,1892 +0,0 b''
1 """A semi-synchronous Client for IPython parallel"""
2
3 # Copyright (c) IPython Development Team.
4 # Distributed under the terms of the Modified BSD License.
5
6 from __future__ import print_function
7
8 import os
9 import json
10 import sys
11 from threading import Thread, Event
12 import time
13 import warnings
14 from datetime import datetime
15 from getpass import getpass
16 from pprint import pprint
17
18 pjoin = os.path.join
19
20 import zmq
21
22 from IPython.config.configurable import MultipleInstanceError
23 from IPython.core.application import BaseIPythonApplication
24 from IPython.core.profiledir import ProfileDir, ProfileDirError
25
26 from IPython.utils.capture import RichOutput
27 from IPython.utils.coloransi import TermColors
28 from jupyter_client.jsonutil import rekey, extract_dates, parse_date
29 from IPython.utils.localinterfaces import localhost, is_local_ip
30 from IPython.utils.path import get_ipython_dir, compress_user
31 from IPython.utils.py3compat import cast_bytes, string_types, xrange, iteritems
32 from IPython.utils.traitlets import (HasTraits, Integer, Instance, Unicode,
33 Dict, List, Bool, Set, Any)
34 from decorator import decorator
35
36 from ipython_parallel import Reference
37 from ipython_parallel import error
38 from ipython_parallel import util
39
40 from IPython.kernel.zmq.session import Session, Message
41 from IPython.kernel.zmq import serialize
42
43 from .asyncresult import AsyncResult, AsyncHubResult
44 from .view import DirectView, LoadBalancedView
45
46 #--------------------------------------------------------------------------
47 # Decorators for Client methods
48 #--------------------------------------------------------------------------
49
50
51 @decorator
52 def spin_first(f, self, *args, **kwargs):
53 """Call spin() to sync state prior to calling the method."""
54 self.spin()
55 return f(self, *args, **kwargs)
56
57
58 #--------------------------------------------------------------------------
59 # Classes
60 #--------------------------------------------------------------------------
61
62 _no_connection_file_msg = """
63 Failed to connect because no Controller could be found.
64 Please double-check your profile and ensure that a cluster is running.
65 """
66
67 class ExecuteReply(RichOutput):
68 """wrapper for finished Execute results"""
69 def __init__(self, msg_id, content, metadata):
70 self.msg_id = msg_id
71 self._content = content
72 self.execution_count = content['execution_count']
73 self.metadata = metadata
74
75 # RichOutput overrides
76
77 @property
78 def source(self):
79 execute_result = self.metadata['execute_result']
80 if execute_result:
81 return execute_result.get('source', '')
82
83 @property
84 def data(self):
85 execute_result = self.metadata['execute_result']
86 if execute_result:
87 return execute_result.get('data', {})
88
89 @property
90 def _metadata(self):
91 execute_result = self.metadata['execute_result']
92 if execute_result:
93 return execute_result.get('metadata', {})
94
95 def display(self):
96 from IPython.display import publish_display_data
97 publish_display_data(self.data, self.metadata)
98
99 def _repr_mime_(self, mime):
100 if mime not in self.data:
101 return
102 data = self.data[mime]
103 if mime in self._metadata:
104 return data, self._metadata[mime]
105 else:
106 return data
107
108 def __getitem__(self, key):
109 return self.metadata[key]
110
111 def __getattr__(self, key):
112 if key not in self.metadata:
113 raise AttributeError(key)
114 return self.metadata[key]
115
116 def __repr__(self):
117 execute_result = self.metadata['execute_result'] or {'data':{}}
118 text_out = execute_result['data'].get('text/plain', '')
119 if len(text_out) > 32:
120 text_out = text_out[:29] + '...'
121
122 return "<ExecuteReply[%i]: %s>" % (self.execution_count, text_out)
123
124 def _repr_pretty_(self, p, cycle):
125 execute_result = self.metadata['execute_result'] or {'data':{}}
126 text_out = execute_result['data'].get('text/plain', '')
127
128 if not text_out:
129 return
130
131 try:
132 ip = get_ipython()
133 except NameError:
134 colors = "NoColor"
135 else:
136 colors = ip.colors
137
138 if colors == "NoColor":
139 out = normal = ""
140 else:
141 out = TermColors.Red
142 normal = TermColors.Normal
143
144 if '\n' in text_out and not text_out.startswith('\n'):
145 # add newline for multiline reprs
146 text_out = '\n' + text_out
147
148 p.text(
149 out + u'Out[%i:%i]: ' % (
150 self.metadata['engine_id'], self.execution_count
151 ) + normal + text_out
152 )
153
154
155 class Metadata(dict):
156 """Subclass of dict for initializing metadata values.
157
158 Attribute access works on keys.
159
160 These objects have a strict set of keys - errors will raise if you try
161 to add new keys.
162 """
163 def __init__(self, *args, **kwargs):
164 dict.__init__(self)
165 md = {'msg_id' : None,
166 'submitted' : None,
167 'started' : None,
168 'completed' : None,
169 'received' : None,
170 'engine_uuid' : None,
171 'engine_id' : None,
172 'follow' : None,
173 'after' : None,
174 'status' : None,
175
176 'execute_input' : None,
177 'execute_result' : None,
178 'error' : None,
179 'stdout' : '',
180 'stderr' : '',
181 'outputs' : [],
182 'data': {},
183 'outputs_ready' : False,
184 }
185 self.update(md)
186 self.update(dict(*args, **kwargs))
187
188 def __getattr__(self, key):
189 """getattr aliased to getitem"""
190 if key in self:
191 return self[key]
192 else:
193 raise AttributeError(key)
194
195 def __setattr__(self, key, value):
196 """setattr aliased to setitem, with strict"""
197 if key in self:
198 self[key] = value
199 else:
200 raise AttributeError(key)
201
202 def __setitem__(self, key, value):
203 """strict static key enforcement"""
204 if key in self:
205 dict.__setitem__(self, key, value)
206 else:
207 raise KeyError(key)
208
209
210 class Client(HasTraits):
211 """A semi-synchronous client to the IPython ZMQ cluster
212
213 Parameters
214 ----------
215
216 url_file : str/unicode; path to ipcontroller-client.json
217 This JSON file should contain all the information needed to connect to a cluster,
218 and is likely the only argument needed.
219 Connection information for the Hub's registration. If a json connector
220 file is given, then likely no further configuration is necessary.
221 [Default: use profile]
222 profile : bytes
223 The name of the Cluster profile to be used to find connector information.
224 If run from an IPython application, the default profile will be the same
225 as the running application, otherwise it will be 'default'.
226 cluster_id : str
227 String id to added to runtime files, to prevent name collisions when using
228 multiple clusters with a single profile simultaneously.
229 When set, will look for files named like: 'ipcontroller-<cluster_id>-client.json'
230 Since this is text inserted into filenames, typical recommendations apply:
231 Simple character strings are ideal, and spaces are not recommended (but
232 should generally work)
233 context : zmq.Context
234 Pass an existing zmq.Context instance, otherwise the client will create its own.
235 debug : bool
236 flag for lots of message printing for debug purposes
237 timeout : int/float
238 time (in seconds) to wait for connection replies from the Hub
239 [Default: 10]
240
241 #-------------- session related args ----------------
242
243 config : Config object
244 If specified, this will be relayed to the Session for configuration
245 username : str
246 set username for the session object
247
248 #-------------- ssh related args ----------------
249 # These are args for configuring the ssh tunnel to be used
250 # credentials are used to forward connections over ssh to the Controller
251 # Note that the ip given in `addr` needs to be relative to sshserver
252 # The most basic case is to leave addr as pointing to localhost (127.0.0.1),
253 # and set sshserver as the same machine the Controller is on. However,
254 # the only requirement is that sshserver is able to see the Controller
255 # (i.e. is within the same trusted network).
256
257 sshserver : str
258 A string of the form passed to ssh, i.e. 'server.tld' or 'user@server.tld:port'
259 If keyfile or password is specified, and this is not, it will default to
260 the ip given in addr.
261 sshkey : str; path to ssh private key file
262 This specifies a key to be used in ssh login, default None.
263 Regular default ssh keys will be used without specifying this argument.
264 password : str
265 Your ssh password to sshserver. Note that if this is left None,
266 you will be prompted for it if passwordless key based login is unavailable.
267 paramiko : bool
268 flag for whether to use paramiko instead of shell ssh for tunneling.
269 [default: True on win32, False else]
270
271
272 Attributes
273 ----------
274
275 ids : list of int engine IDs
276 requesting the ids attribute always synchronizes
277 the registration state. To request ids without synchronization,
278 use semi-private _ids attributes.
279
280 history : list of msg_ids
281 a list of msg_ids, keeping track of all the execution
282 messages you have submitted in order.
283
284 outstanding : set of msg_ids
285 a set of msg_ids that have been submitted, but whose
286 results have not yet been received.
287
288 results : dict
289 a dict of all our results, keyed by msg_id
290
291 block : bool
292 determines default behavior when block not specified
293 in execution methods
294
295 Methods
296 -------
297
298 spin
299 flushes incoming results and registration state changes
300 control methods spin, and requesting `ids` also ensures up to date
301
302 wait
303 wait on one or more msg_ids
304
305 execution methods
306 apply
307 legacy: execute, run
308
309 data movement
310 push, pull, scatter, gather
311
312 query methods
313 queue_status, get_result, purge, result_status
314
315 control methods
316 abort, shutdown
317
318 """
319
320
321 block = Bool(False)
322 outstanding = Set()
323 results = Instance('collections.defaultdict', (dict,))
324 metadata = Instance('collections.defaultdict', (Metadata,))
325 history = List()
326 debug = Bool(False)
327 _spin_thread = Any()
328 _stop_spinning = Any()
329
330 profile=Unicode()
331 def _profile_default(self):
332 if BaseIPythonApplication.initialized():
333 # an IPython app *might* be running, try to get its profile
334 try:
335 return BaseIPythonApplication.instance().profile
336 except (AttributeError, MultipleInstanceError):
337 # could be a *different* subclass of config.Application,
338 # which would raise one of these two errors.
339 return u'default'
340 else:
341 return u'default'
342
343
344 _outstanding_dict = Instance('collections.defaultdict', (set,))
345 _ids = List()
346 _connected=Bool(False)
347 _ssh=Bool(False)
348 _context = Instance('zmq.Context', allow_none=True)
349 _config = Dict()
350 _engines=Instance(util.ReverseDict, (), {})
351 _query_socket=Instance('zmq.Socket', allow_none=True)
352 _control_socket=Instance('zmq.Socket', allow_none=True)
353 _iopub_socket=Instance('zmq.Socket', allow_none=True)
354 _notification_socket=Instance('zmq.Socket', allow_none=True)
355 _mux_socket=Instance('zmq.Socket', allow_none=True)
356 _task_socket=Instance('zmq.Socket', allow_none=True)
357 _task_scheme=Unicode()
358 _closed = False
359 _ignored_control_replies=Integer(0)
360 _ignored_hub_replies=Integer(0)
361
362 def __new__(self, *args, **kw):
363 # don't raise on positional args
364 return HasTraits.__new__(self, **kw)
365
366 def __init__(self, url_file=None, profile=None, profile_dir=None, ipython_dir=None,
367 context=None, debug=False,
368 sshserver=None, sshkey=None, password=None, paramiko=None,
369 timeout=10, cluster_id=None, **extra_args
370 ):
371 if profile:
372 super(Client, self).__init__(debug=debug, profile=profile)
373 else:
374 super(Client, self).__init__(debug=debug)
375 if context is None:
376 context = zmq.Context.instance()
377 self._context = context
378 self._stop_spinning = Event()
379
380 if 'url_or_file' in extra_args:
381 url_file = extra_args['url_or_file']
382 warnings.warn("url_or_file arg no longer supported, use url_file", DeprecationWarning)
383
384 if url_file and util.is_url(url_file):
385 raise ValueError("single urls cannot be specified, url-files must be used.")
386
387 self._setup_profile_dir(self.profile, profile_dir, ipython_dir)
388
389 no_file_msg = '\n'.join([
390 "You have attempted to connect to an IPython Cluster but no Controller could be found.",
391 "Please double-check your configuration and ensure that a cluster is running.",
392 ])
393
394 if self._cd is not None:
395 if url_file is None:
396 if not cluster_id:
397 client_json = 'ipcontroller-client.json'
398 else:
399 client_json = 'ipcontroller-%s-client.json' % cluster_id
400 url_file = pjoin(self._cd.security_dir, client_json)
401 if not os.path.exists(url_file):
402 msg = '\n'.join([
403 "Connection file %r not found." % compress_user(url_file),
404 no_file_msg,
405 ])
406 raise IOError(msg)
407 if url_file is None:
408 raise IOError(no_file_msg)
409
410 if not os.path.exists(url_file):
411 # Connection file explicitly specified, but not found
412 raise IOError("Connection file %r not found. Is a controller running?" % \
413 compress_user(url_file)
414 )
415
416 with open(url_file) as f:
417 cfg = json.load(f)
418
419 self._task_scheme = cfg['task_scheme']
420
421 # sync defaults from args, json:
422 if sshserver:
423 cfg['ssh'] = sshserver
424
425 location = cfg.setdefault('location', None)
426
427 proto,addr = cfg['interface'].split('://')
428 addr = util.disambiguate_ip_address(addr, location)
429 cfg['interface'] = "%s://%s" % (proto, addr)
430
431 # turn interface,port into full urls:
432 for key in ('control', 'task', 'mux', 'iopub', 'notification', 'registration'):
433 cfg[key] = cfg['interface'] + ':%i' % cfg[key]
434
435 url = cfg['registration']
436
437 if location is not None and addr == localhost():
438 # location specified, and connection is expected to be local
439 if not is_local_ip(location) and not sshserver:
440 # load ssh from JSON *only* if the controller is not on
441 # this machine
442 sshserver=cfg['ssh']
443 if not is_local_ip(location) and not sshserver:
444 # warn if no ssh specified, but SSH is probably needed
445 # This is only a warning, because the most likely cause
446 # is a local Controller on a laptop whose IP is dynamic
447 warnings.warn("""
448 Controller appears to be listening on localhost, but not on this machine.
449 If this is true, you should specify Client(...,sshserver='you@%s')
450 or instruct your controller to listen on an external IP."""%location,
451 RuntimeWarning)
452 elif not sshserver:
453 # otherwise sync with cfg
454 sshserver = cfg['ssh']
455
456 self._config = cfg
457
458 self._ssh = bool(sshserver or sshkey or password)
459 if self._ssh and sshserver is None:
460 # default to ssh via localhost
461 sshserver = addr
462 if self._ssh and password is None:
463 from zmq.ssh import tunnel
464 if tunnel.try_passwordless_ssh(sshserver, sshkey, paramiko):
465 password=False
466 else:
467 password = getpass("SSH Password for %s: "%sshserver)
468 ssh_kwargs = dict(keyfile=sshkey, password=password, paramiko=paramiko)
469
470 # configure and construct the session
471 try:
472 extra_args['packer'] = cfg['pack']
473 extra_args['unpacker'] = cfg['unpack']
474 extra_args['key'] = cast_bytes(cfg['key'])
475 extra_args['signature_scheme'] = cfg['signature_scheme']
476 except KeyError as exc:
477 msg = '\n'.join([
478 "Connection file is invalid (missing '{}'), possibly from an old version of IPython.",
479 "If you are reusing connection files, remove them and start ipcontroller again."
480 ])
481 raise ValueError(msg.format(exc.message))
482
483 self.session = Session(**extra_args)
484
485 self._query_socket = self._context.socket(zmq.DEALER)
486
487 if self._ssh:
488 from zmq.ssh import tunnel
489 tunnel.tunnel_connection(self._query_socket, cfg['registration'], sshserver, **ssh_kwargs)
490 else:
491 self._query_socket.connect(cfg['registration'])
492
493 self.session.debug = self.debug
494
495 self._notification_handlers = {'registration_notification' : self._register_engine,
496 'unregistration_notification' : self._unregister_engine,
497 'shutdown_notification' : lambda msg: self.close(),
498 }
499 self._queue_handlers = {'execute_reply' : self._handle_execute_reply,
500 'apply_reply' : self._handle_apply_reply}
501
502 try:
503 self._connect(sshserver, ssh_kwargs, timeout)
504 except:
505 self.close(linger=0)
506 raise
507
508 # last step: setup magics, if we are in IPython:
509
510 try:
511 ip = get_ipython()
512 except NameError:
513 return
514 else:
515 if 'px' not in ip.magics_manager.magics:
516 # in IPython but we are the first Client.
517 # activate a default view for parallel magics.
518 self.activate()
519
520 def __del__(self):
521 """cleanup sockets, but _not_ context."""
522 self.close()
523
524 def _setup_profile_dir(self, profile, profile_dir, ipython_dir):
525 if ipython_dir is None:
526 ipython_dir = get_ipython_dir()
527 if profile_dir is not None:
528 try:
529 self._cd = ProfileDir.find_profile_dir(profile_dir)
530 return
531 except ProfileDirError:
532 pass
533 elif profile is not None:
534 try:
535 self._cd = ProfileDir.find_profile_dir_by_name(
536 ipython_dir, profile)
537 return
538 except ProfileDirError:
539 pass
540 self._cd = None
541
542 def _update_engines(self, engines):
543 """Update our engines dict and _ids from a dict of the form: {id:uuid}."""
544 for k,v in iteritems(engines):
545 eid = int(k)
546 if eid not in self._engines:
547 self._ids.append(eid)
548 self._engines[eid] = v
549 self._ids = sorted(self._ids)
550 if sorted(self._engines.keys()) != list(range(len(self._engines))) and \
551 self._task_scheme == 'pure' and self._task_socket:
552 self._stop_scheduling_tasks()
553
554 def _stop_scheduling_tasks(self):
555 """Stop scheduling tasks because an engine has been unregistered
556 from a pure ZMQ scheduler.
557 """
558 self._task_socket.close()
559 self._task_socket = None
560 msg = "An engine has been unregistered, and we are using pure " +\
561 "ZMQ task scheduling. Task farming will be disabled."
562 if self.outstanding:
563 msg += " If you were running tasks when this happened, " +\
564 "some `outstanding` msg_ids may never resolve."
565 warnings.warn(msg, RuntimeWarning)
566
567 def _build_targets(self, targets):
568 """Turn valid target IDs or 'all' into two lists:
569 (int_ids, uuids).
570 """
571 if not self._ids:
572 # flush notification socket if no engines yet, just in case
573 if not self.ids:
574 raise error.NoEnginesRegistered("Can't build targets without any engines")
575
576 if targets is None:
577 targets = self._ids
578 elif isinstance(targets, string_types):
579 if targets.lower() == 'all':
580 targets = self._ids
581 else:
582 raise TypeError("%r not valid str target, must be 'all'"%(targets))
583 elif isinstance(targets, int):
584 if targets < 0:
585 targets = self.ids[targets]
586 if targets not in self._ids:
587 raise IndexError("No such engine: %i"%targets)
588 targets = [targets]
589
590 if isinstance(targets, slice):
591 indices = list(range(len(self._ids))[targets])
592 ids = self.ids
593 targets = [ ids[i] for i in indices ]
594
595 if not isinstance(targets, (tuple, list, xrange)):
596 raise TypeError("targets by int/slice/collection of ints only, not %s"%(type(targets)))
597
598 return [cast_bytes(self._engines[t]) for t in targets], list(targets)
599
600 def _connect(self, sshserver, ssh_kwargs, timeout):
601 """setup all our socket connections to the cluster. This is called from
602 __init__."""
603
604 # Maybe allow reconnecting?
605 if self._connected:
606 return
607 self._connected=True
608
609 def connect_socket(s, url):
610 if self._ssh:
611 from zmq.ssh import tunnel
612 return tunnel.tunnel_connection(s, url, sshserver, **ssh_kwargs)
613 else:
614 return s.connect(url)
615
616 self.session.send(self._query_socket, 'connection_request')
617 # use Poller because zmq.select has wrong units in pyzmq 2.1.7
618 poller = zmq.Poller()
619 poller.register(self._query_socket, zmq.POLLIN)
620 # poll expects milliseconds, timeout is seconds
621 evts = poller.poll(timeout*1000)
622 if not evts:
623 raise error.TimeoutError("Hub connection request timed out")
624 idents,msg = self.session.recv(self._query_socket,mode=0)
625 if self.debug:
626 pprint(msg)
627 content = msg['content']
628 # self._config['registration'] = dict(content)
629 cfg = self._config
630 if content['status'] == 'ok':
631 self._mux_socket = self._context.socket(zmq.DEALER)
632 connect_socket(self._mux_socket, cfg['mux'])
633
634 self._task_socket = self._context.socket(zmq.DEALER)
635 connect_socket(self._task_socket, cfg['task'])
636
637 self._notification_socket = self._context.socket(zmq.SUB)
638 self._notification_socket.setsockopt(zmq.SUBSCRIBE, b'')
639 connect_socket(self._notification_socket, cfg['notification'])
640
641 self._control_socket = self._context.socket(zmq.DEALER)
642 connect_socket(self._control_socket, cfg['control'])
643
644 self._iopub_socket = self._context.socket(zmq.SUB)
645 self._iopub_socket.setsockopt(zmq.SUBSCRIBE, b'')
646 connect_socket(self._iopub_socket, cfg['iopub'])
647
648 self._update_engines(dict(content['engines']))
649 else:
650 self._connected = False
651 raise Exception("Failed to connect!")
652
653 #--------------------------------------------------------------------------
654 # handlers and callbacks for incoming messages
655 #--------------------------------------------------------------------------
656
657 def _unwrap_exception(self, content):
658 """unwrap exception, and remap engine_id to int."""
659 e = error.unwrap_exception(content)
660 # print e.traceback
661 if e.engine_info:
662 e_uuid = e.engine_info['engine_uuid']
663 eid = self._engines[e_uuid]
664 e.engine_info['engine_id'] = eid
665 return e
666
667 def _extract_metadata(self, msg):
668 header = msg['header']
669 parent = msg['parent_header']
670 msg_meta = msg['metadata']
671 content = msg['content']
672 md = {'msg_id' : parent['msg_id'],
673 'received' : datetime.now(),
674 'engine_uuid' : msg_meta.get('engine', None),
675 'follow' : msg_meta.get('follow', []),
676 'after' : msg_meta.get('after', []),
677 'status' : content['status'],
678 }
679
680 if md['engine_uuid'] is not None:
681 md['engine_id'] = self._engines.get(md['engine_uuid'], None)
682
683 if 'date' in parent:
684 md['submitted'] = parent['date']
685 if 'started' in msg_meta:
686 md['started'] = parse_date(msg_meta['started'])
687 if 'date' in header:
688 md['completed'] = header['date']
689 return md
690
691 def _register_engine(self, msg):
692 """Register a new engine, and update our connection info."""
693 content = msg['content']
694 eid = content['id']
695 d = {eid : content['uuid']}
696 self._update_engines(d)
697
698 def _unregister_engine(self, msg):
699 """Unregister an engine that has died."""
700 content = msg['content']
701 eid = int(content['id'])
702 if eid in self._ids:
703 self._ids.remove(eid)
704 uuid = self._engines.pop(eid)
705
706 self._handle_stranded_msgs(eid, uuid)
707
708 if self._task_socket and self._task_scheme == 'pure':
709 self._stop_scheduling_tasks()
710
711 def _handle_stranded_msgs(self, eid, uuid):
712 """Handle messages known to be on an engine when the engine unregisters.
713
714 It is possible that this will fire prematurely - that is, an engine will
715 go down after completing a result, and the client will be notified
716 of the unregistration and later receive the successful result.
717 """
718
719 outstanding = self._outstanding_dict[uuid]
720
721 for msg_id in list(outstanding):
722 if msg_id in self.results:
723 # we already
724 continue
725 try:
726 raise error.EngineError("Engine %r died while running task %r"%(eid, msg_id))
727 except:
728 content = error.wrap_exception()
729 # build a fake message:
730 msg = self.session.msg('apply_reply', content=content)
731 msg['parent_header']['msg_id'] = msg_id
732 msg['metadata']['engine'] = uuid
733 self._handle_apply_reply(msg)
734
735 def _handle_execute_reply(self, msg):
736 """Save the reply to an execute_request into our results.
737
738 execute messages are never actually used. apply is used instead.
739 """
740
741 parent = msg['parent_header']
742 msg_id = parent['msg_id']
743 if msg_id not in self.outstanding:
744 if msg_id in self.history:
745 print("got stale result: %s"%msg_id)
746 else:
747 print("got unknown result: %s"%msg_id)
748 else:
749 self.outstanding.remove(msg_id)
750
751 content = msg['content']
752 header = msg['header']
753
754 # construct metadata:
755 md = self.metadata[msg_id]
756 md.update(self._extract_metadata(msg))
757 # is this redundant?
758 self.metadata[msg_id] = md
759
760 e_outstanding = self._outstanding_dict[md['engine_uuid']]
761 if msg_id in e_outstanding:
762 e_outstanding.remove(msg_id)
763
764 # construct result:
765 if content['status'] == 'ok':
766 self.results[msg_id] = ExecuteReply(msg_id, content, md)
767 elif content['status'] == 'aborted':
768 self.results[msg_id] = error.TaskAborted(msg_id)
769 elif content['status'] == 'resubmitted':
770 # TODO: handle resubmission
771 pass
772 else:
773 self.results[msg_id] = self._unwrap_exception(content)
774
775 def _handle_apply_reply(self, msg):
776 """Save the reply to an apply_request into our results."""
777 parent = msg['parent_header']
778 msg_id = parent['msg_id']
779 if msg_id not in self.outstanding:
780 if msg_id in self.history:
781 print("got stale result: %s"%msg_id)
782 print(self.results[msg_id])
783 print(msg)
784 else:
785 print("got unknown result: %s"%msg_id)
786 else:
787 self.outstanding.remove(msg_id)
788 content = msg['content']
789 header = msg['header']
790
791 # construct metadata:
792 md = self.metadata[msg_id]
793 md.update(self._extract_metadata(msg))
794 # is this redundant?
795 self.metadata[msg_id] = md
796
797 e_outstanding = self._outstanding_dict[md['engine_uuid']]
798 if msg_id in e_outstanding:
799 e_outstanding.remove(msg_id)
800
801 # construct result:
802 if content['status'] == 'ok':
803 self.results[msg_id] = serialize.deserialize_object(msg['buffers'])[0]
804 elif content['status'] == 'aborted':
805 self.results[msg_id] = error.TaskAborted(msg_id)
806 elif content['status'] == 'resubmitted':
807 # TODO: handle resubmission
808 pass
809 else:
810 self.results[msg_id] = self._unwrap_exception(content)
811
812 def _flush_notifications(self):
813 """Flush notifications of engine registrations waiting
814 in ZMQ queue."""
815 idents,msg = self.session.recv(self._notification_socket, mode=zmq.NOBLOCK)
816 while msg is not None:
817 if self.debug:
818 pprint(msg)
819 msg_type = msg['header']['msg_type']
820 handler = self._notification_handlers.get(msg_type, None)
821 if handler is None:
822 raise Exception("Unhandled message type: %s" % msg_type)
823 else:
824 handler(msg)
825 idents,msg = self.session.recv(self._notification_socket, mode=zmq.NOBLOCK)
826
827 def _flush_results(self, sock):
828 """Flush task or queue results waiting in ZMQ queue."""
829 idents,msg = self.session.recv(sock, mode=zmq.NOBLOCK)
830 while msg is not None:
831 if self.debug:
832 pprint(msg)
833 msg_type = msg['header']['msg_type']
834 handler = self._queue_handlers.get(msg_type, None)
835 if handler is None:
836 raise Exception("Unhandled message type: %s" % msg_type)
837 else:
838 handler(msg)
839 idents,msg = self.session.recv(sock, mode=zmq.NOBLOCK)
840
841 def _flush_control(self, sock):
842 """Flush replies from the control channel waiting
843 in the ZMQ queue.
844
845 Currently: ignore them."""
846 if self._ignored_control_replies <= 0:
847 return
848 idents,msg = self.session.recv(sock, mode=zmq.NOBLOCK)
849 while msg is not None:
850 self._ignored_control_replies -= 1
851 if self.debug:
852 pprint(msg)
853 idents,msg = self.session.recv(sock, mode=zmq.NOBLOCK)
854
855 def _flush_ignored_control(self):
856 """flush ignored control replies"""
857 while self._ignored_control_replies > 0:
858 self.session.recv(self._control_socket)
859 self._ignored_control_replies -= 1
860
861 def _flush_ignored_hub_replies(self):
862 ident,msg = self.session.recv(self._query_socket, mode=zmq.NOBLOCK)
863 while msg is not None:
864 ident,msg = self.session.recv(self._query_socket, mode=zmq.NOBLOCK)
865
866 def _flush_iopub(self, sock):
867 """Flush replies from the iopub channel waiting
868 in the ZMQ queue.
869 """
870 idents,msg = self.session.recv(sock, mode=zmq.NOBLOCK)
871 while msg is not None:
872 if self.debug:
873 pprint(msg)
874 parent = msg['parent_header']
875 if not parent or parent['session'] != self.session.session:
876 # ignore IOPub messages not from here
877 idents,msg = self.session.recv(sock, mode=zmq.NOBLOCK)
878 continue
879 msg_id = parent['msg_id']
880 content = msg['content']
881 header = msg['header']
882 msg_type = msg['header']['msg_type']
883
884 if msg_type == 'status' and msg_id not in self.metadata:
885 # ignore status messages if they aren't mine
886 idents,msg = self.session.recv(sock, mode=zmq.NOBLOCK)
887 continue
888
889 # init metadata:
890 md = self.metadata[msg_id]
891
892 if msg_type == 'stream':
893 name = content['name']
894 s = md[name] or ''
895 md[name] = s + content['text']
896 elif msg_type == 'error':
897 md.update({'error' : self._unwrap_exception(content)})
898 elif msg_type == 'execute_input':
899 md.update({'execute_input' : content['code']})
900 elif msg_type == 'display_data':
901 md['outputs'].append(content)
902 elif msg_type == 'execute_result':
903 md['execute_result'] = content
904 elif msg_type == 'data_message':
905 data, remainder = serialize.deserialize_object(msg['buffers'])
906 md['data'].update(data)
907 elif msg_type == 'status':
908 # idle message comes after all outputs
909 if content['execution_state'] == 'idle':
910 md['outputs_ready'] = True
911 else:
912 # unhandled msg_type (status, etc.)
913 pass
914
915 # reduntant?
916 self.metadata[msg_id] = md
917
918 idents,msg = self.session.recv(sock, mode=zmq.NOBLOCK)
919
920 #--------------------------------------------------------------------------
921 # len, getitem
922 #--------------------------------------------------------------------------
923
924 def __len__(self):
925 """len(client) returns # of engines."""
926 return len(self.ids)
927
928 def __getitem__(self, key):
929 """index access returns DirectView multiplexer objects
930
931 Must be int, slice, or list/tuple/xrange of ints"""
932 if not isinstance(key, (int, slice, tuple, list, xrange)):
933 raise TypeError("key by int/slice/iterable of ints only, not %s"%(type(key)))
934 else:
935 return self.direct_view(key)
936
937 def __iter__(self):
938 """Since we define getitem, Client is iterable
939
940 but unless we also define __iter__, it won't work correctly unless engine IDs
941 start at zero and are continuous.
942 """
943 for eid in self.ids:
944 yield self.direct_view(eid)
945
946 #--------------------------------------------------------------------------
947 # Begin public methods
948 #--------------------------------------------------------------------------
949
950 @property
951 def ids(self):
952 """Always up-to-date ids property."""
953 self._flush_notifications()
954 # always copy:
955 return list(self._ids)
956
957 def activate(self, targets='all', suffix=''):
958 """Create a DirectView and register it with IPython magics
959
960 Defines the magics `%px, %autopx, %pxresult, %%px`
961
962 Parameters
963 ----------
964
965 targets: int, list of ints, or 'all'
966 The engines on which the view's magics will run
967 suffix: str [default: '']
968 The suffix, if any, for the magics. This allows you to have
969 multiple views associated with parallel magics at the same time.
970
971 e.g. ``rc.activate(targets=0, suffix='0')`` will give you
972 the magics ``%px0``, ``%pxresult0``, etc. for running magics just
973 on engine 0.
974 """
975 view = self.direct_view(targets)
976 view.block = True
977 view.activate(suffix)
978 return view
979
980 def close(self, linger=None):
981 """Close my zmq Sockets
982
983 If `linger`, set the zmq LINGER socket option,
984 which allows discarding of messages.
985 """
986 if self._closed:
987 return
988 self.stop_spin_thread()
989 snames = [ trait for trait in self.trait_names() if trait.endswith("socket") ]
990 for name in snames:
991 socket = getattr(self, name)
992 if socket is not None and not socket.closed:
993 if linger is not None:
994 socket.close(linger=linger)
995 else:
996 socket.close()
997 self._closed = True
998
999 def _spin_every(self, interval=1):
1000 """target func for use in spin_thread"""
1001 while True:
1002 if self._stop_spinning.is_set():
1003 return
1004 time.sleep(interval)
1005 self.spin()
1006
1007 def spin_thread(self, interval=1):
1008 """call Client.spin() in a background thread on some regular interval
1009
1010 This helps ensure that messages don't pile up too much in the zmq queue
1011 while you are working on other things, or just leaving an idle terminal.
1012
1013 It also helps limit potential padding of the `received` timestamp
1014 on AsyncResult objects, used for timings.
1015
1016 Parameters
1017 ----------
1018
1019 interval : float, optional
1020 The interval on which to spin the client in the background thread
1021 (simply passed to time.sleep).
1022
1023 Notes
1024 -----
1025
1026 For precision timing, you may want to use this method to put a bound
1027 on the jitter (in seconds) in `received` timestamps used
1028 in AsyncResult.wall_time.
1029
1030 """
1031 if self._spin_thread is not None:
1032 self.stop_spin_thread()
1033 self._stop_spinning.clear()
1034 self._spin_thread = Thread(target=self._spin_every, args=(interval,))
1035 self._spin_thread.daemon = True
1036 self._spin_thread.start()
1037
1038 def stop_spin_thread(self):
1039 """stop background spin_thread, if any"""
1040 if self._spin_thread is not None:
1041 self._stop_spinning.set()
1042 self._spin_thread.join()
1043 self._spin_thread = None
1044
1045 def spin(self):
1046 """Flush any registration notifications and execution results
1047 waiting in the ZMQ queue.
1048 """
1049 if self._notification_socket:
1050 self._flush_notifications()
1051 if self._iopub_socket:
1052 self._flush_iopub(self._iopub_socket)
1053 if self._mux_socket:
1054 self._flush_results(self._mux_socket)
1055 if self._task_socket:
1056 self._flush_results(self._task_socket)
1057 if self._control_socket:
1058 self._flush_control(self._control_socket)
1059 if self._query_socket:
1060 self._flush_ignored_hub_replies()
1061
1062 def wait(self, jobs=None, timeout=-1):
1063 """waits on one or more `jobs`, for up to `timeout` seconds.
1064
1065 Parameters
1066 ----------
1067
1068 jobs : int, str, or list of ints and/or strs, or one or more AsyncResult objects
1069 ints are indices to self.history
1070 strs are msg_ids
1071 default: wait on all outstanding messages
1072 timeout : float
1073 a time in seconds, after which to give up.
1074 default is -1, which means no timeout
1075
1076 Returns
1077 -------
1078
1079 True : when all msg_ids are done
1080 False : timeout reached, some msg_ids still outstanding
1081 """
1082 tic = time.time()
1083 if jobs is None:
1084 theids = self.outstanding
1085 else:
1086 if isinstance(jobs, string_types + (int, AsyncResult)):
1087 jobs = [jobs]
1088 theids = set()
1089 for job in jobs:
1090 if isinstance(job, int):
1091 # index access
1092 job = self.history[job]
1093 elif isinstance(job, AsyncResult):
1094 theids.update(job.msg_ids)
1095 continue
1096 theids.add(job)
1097 if not theids.intersection(self.outstanding):
1098 return True
1099 self.spin()
1100 while theids.intersection(self.outstanding):
1101 if timeout >= 0 and ( time.time()-tic ) > timeout:
1102 break
1103 time.sleep(1e-3)
1104 self.spin()
1105 return len(theids.intersection(self.outstanding)) == 0
1106
1107 #--------------------------------------------------------------------------
1108 # Control methods
1109 #--------------------------------------------------------------------------
1110
1111 @spin_first
1112 def clear(self, targets=None, block=None):
1113 """Clear the namespace in target(s)."""
1114 block = self.block if block is None else block
1115 targets = self._build_targets(targets)[0]
1116 for t in targets:
1117 self.session.send(self._control_socket, 'clear_request', content={}, ident=t)
1118 error = False
1119 if block:
1120 self._flush_ignored_control()
1121 for i in range(len(targets)):
1122 idents,msg = self.session.recv(self._control_socket,0)
1123 if self.debug:
1124 pprint(msg)
1125 if msg['content']['status'] != 'ok':
1126 error = self._unwrap_exception(msg['content'])
1127 else:
1128 self._ignored_control_replies += len(targets)
1129 if error:
1130 raise error
1131
1132
1133 @spin_first
1134 def abort(self, jobs=None, targets=None, block=None):
1135 """Abort specific jobs from the execution queues of target(s).
1136
1137 This is a mechanism to prevent jobs that have already been submitted
1138 from executing.
1139
1140 Parameters
1141 ----------
1142
1143 jobs : msg_id, list of msg_ids, or AsyncResult
1144 The jobs to be aborted
1145
1146 If unspecified/None: abort all outstanding jobs.
1147
1148 """
1149 block = self.block if block is None else block
1150 jobs = jobs if jobs is not None else list(self.outstanding)
1151 targets = self._build_targets(targets)[0]
1152
1153 msg_ids = []
1154 if isinstance(jobs, string_types + (AsyncResult,)):
1155 jobs = [jobs]
1156 bad_ids = [obj for obj in jobs if not isinstance(obj, string_types + (AsyncResult,))]
1157 if bad_ids:
1158 raise TypeError("Invalid msg_id type %r, expected str or AsyncResult"%bad_ids[0])
1159 for j in jobs:
1160 if isinstance(j, AsyncResult):
1161 msg_ids.extend(j.msg_ids)
1162 else:
1163 msg_ids.append(j)
1164 content = dict(msg_ids=msg_ids)
1165 for t in targets:
1166 self.session.send(self._control_socket, 'abort_request',
1167 content=content, ident=t)
1168 error = False
1169 if block:
1170 self._flush_ignored_control()
1171 for i in range(len(targets)):
1172 idents,msg = self.session.recv(self._control_socket,0)
1173 if self.debug:
1174 pprint(msg)
1175 if msg['content']['status'] != 'ok':
1176 error = self._unwrap_exception(msg['content'])
1177 else:
1178 self._ignored_control_replies += len(targets)
1179 if error:
1180 raise error
1181
1182 @spin_first
1183 def shutdown(self, targets='all', restart=False, hub=False, block=None):
1184 """Terminates one or more engine processes, optionally including the hub.
1185
1186 Parameters
1187 ----------
1188
1189 targets: list of ints or 'all' [default: all]
1190 Which engines to shutdown.
1191 hub: bool [default: False]
1192 Whether to include the Hub. hub=True implies targets='all'.
1193 block: bool [default: self.block]
1194 Whether to wait for clean shutdown replies or not.
1195 restart: bool [default: False]
1196 NOT IMPLEMENTED
1197 whether to restart engines after shutting them down.
1198 """
1199 from ipython_parallel.error import NoEnginesRegistered
1200 if restart:
1201 raise NotImplementedError("Engine restart is not yet implemented")
1202
1203 block = self.block if block is None else block
1204 if hub:
1205 targets = 'all'
1206 try:
1207 targets = self._build_targets(targets)[0]
1208 except NoEnginesRegistered:
1209 targets = []
1210 for t in targets:
1211 self.session.send(self._control_socket, 'shutdown_request',
1212 content={'restart':restart},ident=t)
1213 error = False
1214 if block or hub:
1215 self._flush_ignored_control()
1216 for i in range(len(targets)):
1217 idents,msg = self.session.recv(self._control_socket, 0)
1218 if self.debug:
1219 pprint(msg)
1220 if msg['content']['status'] != 'ok':
1221 error = self._unwrap_exception(msg['content'])
1222 else:
1223 self._ignored_control_replies += len(targets)
1224
1225 if hub:
1226 time.sleep(0.25)
1227 self.session.send(self._query_socket, 'shutdown_request')
1228 idents,msg = self.session.recv(self._query_socket, 0)
1229 if self.debug:
1230 pprint(msg)
1231 if msg['content']['status'] != 'ok':
1232 error = self._unwrap_exception(msg['content'])
1233
1234 if error:
1235 raise error
1236
1237 #--------------------------------------------------------------------------
1238 # Execution related methods
1239 #--------------------------------------------------------------------------
1240
1241 def _maybe_raise(self, result):
1242 """wrapper for maybe raising an exception if apply failed."""
1243 if isinstance(result, error.RemoteError):
1244 raise result
1245
1246 return result
1247
1248 def send_apply_request(self, socket, f, args=None, kwargs=None, metadata=None, track=False,
1249 ident=None):
1250 """construct and send an apply message via a socket.
1251
1252 This is the principal method with which all engine execution is performed by views.
1253 """
1254
1255 if self._closed:
1256 raise RuntimeError("Client cannot be used after its sockets have been closed")
1257
1258 # defaults:
1259 args = args if args is not None else []
1260 kwargs = kwargs if kwargs is not None else {}
1261 metadata = metadata if metadata is not None else {}
1262
1263 # validate arguments
1264 if not callable(f) and not isinstance(f, Reference):
1265 raise TypeError("f must be callable, not %s"%type(f))
1266 if not isinstance(args, (tuple, list)):
1267 raise TypeError("args must be tuple or list, not %s"%type(args))
1268 if not isinstance(kwargs, dict):
1269 raise TypeError("kwargs must be dict, not %s"%type(kwargs))
1270 if not isinstance(metadata, dict):
1271 raise TypeError("metadata must be dict, not %s"%type(metadata))
1272
1273 bufs = serialize.pack_apply_message(f, args, kwargs,
1274 buffer_threshold=self.session.buffer_threshold,
1275 item_threshold=self.session.item_threshold,
1276 )
1277
1278 msg = self.session.send(socket, "apply_request", buffers=bufs, ident=ident,
1279 metadata=metadata, track=track)
1280
1281 msg_id = msg['header']['msg_id']
1282 self.outstanding.add(msg_id)
1283 if ident:
1284 # possibly routed to a specific engine
1285 if isinstance(ident, list):
1286 ident = ident[-1]
1287 if ident in self._engines.values():
1288 # save for later, in case of engine death
1289 self._outstanding_dict[ident].add(msg_id)
1290 self.history.append(msg_id)
1291 self.metadata[msg_id]['submitted'] = datetime.now()
1292
1293 return msg
1294
1295 def send_execute_request(self, socket, code, silent=True, metadata=None, ident=None):
1296 """construct and send an execute request via a socket.
1297
1298 """
1299
1300 if self._closed:
1301 raise RuntimeError("Client cannot be used after its sockets have been closed")
1302
1303 # defaults:
1304 metadata = metadata if metadata is not None else {}
1305
1306 # validate arguments
1307 if not isinstance(code, string_types):
1308 raise TypeError("code must be text, not %s" % type(code))
1309 if not isinstance(metadata, dict):
1310 raise TypeError("metadata must be dict, not %s" % type(metadata))
1311
1312 content = dict(code=code, silent=bool(silent), user_expressions={})
1313
1314
1315 msg = self.session.send(socket, "execute_request", content=content, ident=ident,
1316 metadata=metadata)
1317
1318 msg_id = msg['header']['msg_id']
1319 self.outstanding.add(msg_id)
1320 if ident:
1321 # possibly routed to a specific engine
1322 if isinstance(ident, list):
1323 ident = ident[-1]
1324 if ident in self._engines.values():
1325 # save for later, in case of engine death
1326 self._outstanding_dict[ident].add(msg_id)
1327 self.history.append(msg_id)
1328 self.metadata[msg_id]['submitted'] = datetime.now()
1329
1330 return msg
1331
1332 #--------------------------------------------------------------------------
1333 # construct a View object
1334 #--------------------------------------------------------------------------
1335
1336 def load_balanced_view(self, targets=None):
1337 """construct a DirectView object.
1338
1339 If no arguments are specified, create a LoadBalancedView
1340 using all engines.
1341
1342 Parameters
1343 ----------
1344
1345 targets: list,slice,int,etc. [default: use all engines]
1346 The subset of engines across which to load-balance
1347 """
1348 if targets == 'all':
1349 targets = None
1350 if targets is not None:
1351 targets = self._build_targets(targets)[1]
1352 return LoadBalancedView(client=self, socket=self._task_socket, targets=targets)
1353
1354 def direct_view(self, targets='all'):
1355 """construct a DirectView object.
1356
1357 If no targets are specified, create a DirectView using all engines.
1358
1359 rc.direct_view('all') is distinguished from rc[:] in that 'all' will
1360 evaluate the target engines at each execution, whereas rc[:] will connect to
1361 all *current* engines, and that list will not change.
1362
1363 That is, 'all' will always use all engines, whereas rc[:] will not use
1364 engines added after the DirectView is constructed.
1365
1366 Parameters
1367 ----------
1368
1369 targets: list,slice,int,etc. [default: use all engines]
1370 The engines to use for the View
1371 """
1372 single = isinstance(targets, int)
1373 # allow 'all' to be lazily evaluated at each execution
1374 if targets != 'all':
1375 targets = self._build_targets(targets)[1]
1376 if single:
1377 targets = targets[0]
1378 return DirectView(client=self, socket=self._mux_socket, targets=targets)
1379
1380 #--------------------------------------------------------------------------
1381 # Query methods
1382 #--------------------------------------------------------------------------
1383
1384 @spin_first
1385 def get_result(self, indices_or_msg_ids=None, block=None, owner=True):
1386 """Retrieve a result by msg_id or history index, wrapped in an AsyncResult object.
1387
1388 If the client already has the results, no request to the Hub will be made.
1389
1390 This is a convenient way to construct AsyncResult objects, which are wrappers
1391 that include metadata about execution, and allow for awaiting results that
1392 were not submitted by this Client.
1393
1394 It can also be a convenient way to retrieve the metadata associated with
1395 blocking execution, since it always retrieves
1396
1397 Examples
1398 --------
1399 ::
1400
1401 In [10]: r = client.apply()
1402
1403 Parameters
1404 ----------
1405
1406 indices_or_msg_ids : integer history index, str msg_id, or list of either
1407 The indices or msg_ids of indices to be retrieved
1408
1409 block : bool
1410 Whether to wait for the result to be done
1411 owner : bool [default: True]
1412 Whether this AsyncResult should own the result.
1413 If so, calling `ar.get()` will remove data from the
1414 client's result and metadata cache.
1415 There should only be one owner of any given msg_id.
1416
1417 Returns
1418 -------
1419
1420 AsyncResult
1421 A single AsyncResult object will always be returned.
1422
1423 AsyncHubResult
1424 A subclass of AsyncResult that retrieves results from the Hub
1425
1426 """
1427 block = self.block if block is None else block
1428 if indices_or_msg_ids is None:
1429 indices_or_msg_ids = -1
1430
1431 single_result = False
1432 if not isinstance(indices_or_msg_ids, (list,tuple)):
1433 indices_or_msg_ids = [indices_or_msg_ids]
1434 single_result = True
1435
1436 theids = []
1437 for id in indices_or_msg_ids:
1438 if isinstance(id, int):
1439 id = self.history[id]
1440 if not isinstance(id, string_types):
1441 raise TypeError("indices must be str or int, not %r"%id)
1442 theids.append(id)
1443
1444 local_ids = [msg_id for msg_id in theids if (msg_id in self.outstanding or msg_id in self.results)]
1445 remote_ids = [msg_id for msg_id in theids if msg_id not in local_ids]
1446
1447 # given single msg_id initially, get_result shot get the result itself,
1448 # not a length-one list
1449 if single_result:
1450 theids = theids[0]
1451
1452 if remote_ids:
1453 ar = AsyncHubResult(self, msg_ids=theids, owner=owner)
1454 else:
1455 ar = AsyncResult(self, msg_ids=theids, owner=owner)
1456
1457 if block:
1458 ar.wait()
1459
1460 return ar
1461
1462 @spin_first
1463 def resubmit(self, indices_or_msg_ids=None, metadata=None, block=None):
1464 """Resubmit one or more tasks.
1465
1466 in-flight tasks may not be resubmitted.
1467
1468 Parameters
1469 ----------
1470
1471 indices_or_msg_ids : integer history index, str msg_id, or list of either
1472 The indices or msg_ids of indices to be retrieved
1473
1474 block : bool
1475 Whether to wait for the result to be done
1476
1477 Returns
1478 -------
1479
1480 AsyncHubResult
1481 A subclass of AsyncResult that retrieves results from the Hub
1482
1483 """
1484 block = self.block if block is None else block
1485 if indices_or_msg_ids is None:
1486 indices_or_msg_ids = -1
1487
1488 if not isinstance(indices_or_msg_ids, (list,tuple)):
1489 indices_or_msg_ids = [indices_or_msg_ids]
1490
1491 theids = []
1492 for id in indices_or_msg_ids:
1493 if isinstance(id, int):
1494 id = self.history[id]
1495 if not isinstance(id, string_types):
1496 raise TypeError("indices must be str or int, not %r"%id)
1497 theids.append(id)
1498
1499 content = dict(msg_ids = theids)
1500
1501 self.session.send(self._query_socket, 'resubmit_request', content)
1502
1503 zmq.select([self._query_socket], [], [])
1504 idents,msg = self.session.recv(self._query_socket, zmq.NOBLOCK)
1505 if self.debug:
1506 pprint(msg)
1507 content = msg['content']
1508 if content['status'] != 'ok':
1509 raise self._unwrap_exception(content)
1510 mapping = content['resubmitted']
1511 new_ids = [ mapping[msg_id] for msg_id in theids ]
1512
1513 ar = AsyncHubResult(self, msg_ids=new_ids)
1514
1515 if block:
1516 ar.wait()
1517
1518 return ar
1519
1520 @spin_first
1521 def result_status(self, msg_ids, status_only=True):
1522 """Check on the status of the result(s) of the apply request with `msg_ids`.
1523
1524 If status_only is False, then the actual results will be retrieved, else
1525 only the status of the results will be checked.
1526
1527 Parameters
1528 ----------
1529
1530 msg_ids : list of msg_ids
1531 if int:
1532 Passed as index to self.history for convenience.
1533 status_only : bool (default: True)
1534 if False:
1535 Retrieve the actual results of completed tasks.
1536
1537 Returns
1538 -------
1539
1540 results : dict
1541 There will always be the keys 'pending' and 'completed', which will
1542 be lists of msg_ids that are incomplete or complete. If `status_only`
1543 is False, then completed results will be keyed by their `msg_id`.
1544 """
1545 if not isinstance(msg_ids, (list,tuple)):
1546 msg_ids = [msg_ids]
1547
1548 theids = []
1549 for msg_id in msg_ids:
1550 if isinstance(msg_id, int):
1551 msg_id = self.history[msg_id]
1552 if not isinstance(msg_id, string_types):
1553 raise TypeError("msg_ids must be str, not %r"%msg_id)
1554 theids.append(msg_id)
1555
1556 completed = []
1557 local_results = {}
1558
1559 # comment this block out to temporarily disable local shortcut:
1560 for msg_id in theids:
1561 if msg_id in self.results:
1562 completed.append(msg_id)
1563 local_results[msg_id] = self.results[msg_id]
1564 theids.remove(msg_id)
1565
1566 if theids: # some not locally cached
1567 content = dict(msg_ids=theids, status_only=status_only)
1568 msg = self.session.send(self._query_socket, "result_request", content=content)
1569 zmq.select([self._query_socket], [], [])
1570 idents,msg = self.session.recv(self._query_socket, zmq.NOBLOCK)
1571 if self.debug:
1572 pprint(msg)
1573 content = msg['content']
1574 if content['status'] != 'ok':
1575 raise self._unwrap_exception(content)
1576 buffers = msg['buffers']
1577 else:
1578 content = dict(completed=[],pending=[])
1579
1580 content['completed'].extend(completed)
1581
1582 if status_only:
1583 return content
1584
1585 failures = []
1586 # load cached results into result:
1587 content.update(local_results)
1588
1589 # update cache with results:
1590 for msg_id in sorted(theids):
1591 if msg_id in content['completed']:
1592 rec = content[msg_id]
1593 parent = extract_dates(rec['header'])
1594 header = extract_dates(rec['result_header'])
1595 rcontent = rec['result_content']
1596 iodict = rec['io']
1597 if isinstance(rcontent, str):
1598 rcontent = self.session.unpack(rcontent)
1599
1600 md = self.metadata[msg_id]
1601 md_msg = dict(
1602 content=rcontent,
1603 parent_header=parent,
1604 header=header,
1605 metadata=rec['result_metadata'],
1606 )
1607 md.update(self._extract_metadata(md_msg))
1608 if rec.get('received'):
1609 md['received'] = parse_date(rec['received'])
1610 md.update(iodict)
1611
1612 if rcontent['status'] == 'ok':
1613 if header['msg_type'] == 'apply_reply':
1614 res,buffers = serialize.deserialize_object(buffers)
1615 elif header['msg_type'] == 'execute_reply':
1616 res = ExecuteReply(msg_id, rcontent, md)
1617 else:
1618 raise KeyError("unhandled msg type: %r" % header['msg_type'])
1619 else:
1620 res = self._unwrap_exception(rcontent)
1621 failures.append(res)
1622
1623 self.results[msg_id] = res
1624 content[msg_id] = res
1625
1626 if len(theids) == 1 and failures:
1627 raise failures[0]
1628
1629 error.collect_exceptions(failures, "result_status")
1630 return content
1631
1632 @spin_first
1633 def queue_status(self, targets='all', verbose=False):
1634 """Fetch the status of engine queues.
1635
1636 Parameters
1637 ----------
1638
1639 targets : int/str/list of ints/strs
1640 the engines whose states are to be queried.
1641 default : all
1642 verbose : bool
1643 Whether to return lengths only, or lists of ids for each element
1644 """
1645 if targets == 'all':
1646 # allow 'all' to be evaluated on the engine
1647 engine_ids = None
1648 else:
1649 engine_ids = self._build_targets(targets)[1]
1650 content = dict(targets=engine_ids, verbose=verbose)
1651 self.session.send(self._query_socket, "queue_request", content=content)
1652 idents,msg = self.session.recv(self._query_socket, 0)
1653 if self.debug:
1654 pprint(msg)
1655 content = msg['content']
1656 status = content.pop('status')
1657 if status != 'ok':
1658 raise self._unwrap_exception(content)
1659 content = rekey(content)
1660 if isinstance(targets, int):
1661 return content[targets]
1662 else:
1663 return content
1664
1665 def _build_msgids_from_target(self, targets=None):
1666 """Build a list of msg_ids from the list of engine targets"""
1667 if not targets: # needed as _build_targets otherwise uses all engines
1668 return []
1669 target_ids = self._build_targets(targets)[0]
1670 return [md_id for md_id in self.metadata if self.metadata[md_id]["engine_uuid"] in target_ids]
1671
1672 def _build_msgids_from_jobs(self, jobs=None):
1673 """Build a list of msg_ids from "jobs" """
1674 if not jobs:
1675 return []
1676 msg_ids = []
1677 if isinstance(jobs, string_types + (AsyncResult,)):
1678 jobs = [jobs]
1679 bad_ids = [obj for obj in jobs if not isinstance(obj, string_types + (AsyncResult,))]
1680 if bad_ids:
1681 raise TypeError("Invalid msg_id type %r, expected str or AsyncResult"%bad_ids[0])
1682 for j in jobs:
1683 if isinstance(j, AsyncResult):
1684 msg_ids.extend(j.msg_ids)
1685 else:
1686 msg_ids.append(j)
1687 return msg_ids
1688
1689 def purge_local_results(self, jobs=[], targets=[]):
1690 """Clears the client caches of results and their metadata.
1691
1692 Individual results can be purged by msg_id, or the entire
1693 history of specific targets can be purged.
1694
1695 Use `purge_local_results('all')` to scrub everything from the Clients's
1696 results and metadata caches.
1697
1698 After this call all `AsyncResults` are invalid and should be discarded.
1699
1700 If you must "reget" the results, you can still do so by using
1701 `client.get_result(msg_id)` or `client.get_result(asyncresult)`. This will
1702 redownload the results from the hub if they are still available
1703 (i.e `client.purge_hub_results(...)` has not been called.
1704
1705 Parameters
1706 ----------
1707
1708 jobs : str or list of str or AsyncResult objects
1709 the msg_ids whose results should be purged.
1710 targets : int/list of ints
1711 The engines, by integer ID, whose entire result histories are to be purged.
1712
1713 Raises
1714 ------
1715
1716 RuntimeError : if any of the tasks to be purged are still outstanding.
1717
1718 """
1719 if not targets and not jobs:
1720 raise ValueError("Must specify at least one of `targets` and `jobs`")
1721
1722 if jobs == 'all':
1723 if self.outstanding:
1724 raise RuntimeError("Can't purge outstanding tasks: %s" % self.outstanding)
1725 self.results.clear()
1726 self.metadata.clear()
1727 else:
1728 msg_ids = set()
1729 msg_ids.update(self._build_msgids_from_target(targets))
1730 msg_ids.update(self._build_msgids_from_jobs(jobs))
1731 still_outstanding = self.outstanding.intersection(msg_ids)
1732 if still_outstanding:
1733 raise RuntimeError("Can't purge outstanding tasks: %s" % still_outstanding)
1734 for mid in msg_ids:
1735 self.results.pop(mid, None)
1736 self.metadata.pop(mid, None)
1737
1738
1739 @spin_first
1740 def purge_hub_results(self, jobs=[], targets=[]):
1741 """Tell the Hub to forget results.
1742
1743 Individual results can be purged by msg_id, or the entire
1744 history of specific targets can be purged.
1745
1746 Use `purge_results('all')` to scrub everything from the Hub's db.
1747
1748 Parameters
1749 ----------
1750
1751 jobs : str or list of str or AsyncResult objects
1752 the msg_ids whose results should be forgotten.
1753 targets : int/str/list of ints/strs
1754 The targets, by int_id, whose entire history is to be purged.
1755
1756 default : None
1757 """
1758 if not targets and not jobs:
1759 raise ValueError("Must specify at least one of `targets` and `jobs`")
1760 if targets:
1761 targets = self._build_targets(targets)[1]
1762
1763 # construct msg_ids from jobs
1764 if jobs == 'all':
1765 msg_ids = jobs
1766 else:
1767 msg_ids = self._build_msgids_from_jobs(jobs)
1768
1769 content = dict(engine_ids=targets, msg_ids=msg_ids)
1770 self.session.send(self._query_socket, "purge_request", content=content)
1771 idents, msg = self.session.recv(self._query_socket, 0)
1772 if self.debug:
1773 pprint(msg)
1774 content = msg['content']
1775 if content['status'] != 'ok':
1776 raise self._unwrap_exception(content)
1777
1778 def purge_results(self, jobs=[], targets=[]):
1779 """Clears the cached results from both the hub and the local client
1780
1781 Individual results can be purged by msg_id, or the entire
1782 history of specific targets can be purged.
1783
1784 Use `purge_results('all')` to scrub every cached result from both the Hub's and
1785 the Client's db.
1786
1787 Equivalent to calling both `purge_hub_results()` and `purge_client_results()` with
1788 the same arguments.
1789
1790 Parameters
1791 ----------
1792
1793 jobs : str or list of str or AsyncResult objects
1794 the msg_ids whose results should be forgotten.
1795 targets : int/str/list of ints/strs
1796 The targets, by int_id, whose entire history is to be purged.
1797
1798 default : None
1799 """
1800 self.purge_local_results(jobs=jobs, targets=targets)
1801 self.purge_hub_results(jobs=jobs, targets=targets)
1802
1803 def purge_everything(self):
1804 """Clears all content from previous Tasks from both the hub and the local client
1805
1806 In addition to calling `purge_results("all")` it also deletes the history and
1807 other bookkeeping lists.
1808 """
1809 self.purge_results("all")
1810 self.history = []
1811 self.session.digest_history.clear()
1812
1813 @spin_first
1814 def hub_history(self):
1815 """Get the Hub's history
1816
1817 Just like the Client, the Hub has a history, which is a list of msg_ids.
1818 This will contain the history of all clients, and, depending on configuration,
1819 may contain history across multiple cluster sessions.
1820
1821 Any msg_id returned here is a valid argument to `get_result`.
1822
1823 Returns
1824 -------
1825
1826 msg_ids : list of strs
1827 list of all msg_ids, ordered by task submission time.
1828 """
1829
1830 self.session.send(self._query_socket, "history_request", content={})
1831 idents, msg = self.session.recv(self._query_socket, 0)
1832
1833 if self.debug:
1834 pprint(msg)
1835 content = msg['content']
1836 if content['status'] != 'ok':
1837 raise self._unwrap_exception(content)
1838 else:
1839 return content['history']
1840
1841 @spin_first
1842 def db_query(self, query, keys=None):
1843 """Query the Hub's TaskRecord database
1844
1845 This will return a list of task record dicts that match `query`
1846
1847 Parameters
1848 ----------
1849
1850 query : mongodb query dict
1851 The search dict. See mongodb query docs for details.
1852 keys : list of strs [optional]
1853 The subset of keys to be returned. The default is to fetch everything but buffers.
1854 'msg_id' will *always* be included.
1855 """
1856 if isinstance(keys, string_types):
1857 keys = [keys]
1858 content = dict(query=query, keys=keys)
1859 self.session.send(self._query_socket, "db_request", content=content)
1860 idents, msg = self.session.recv(self._query_socket, 0)
1861 if self.debug:
1862 pprint(msg)
1863 content = msg['content']
1864 if content['status'] != 'ok':
1865 raise self._unwrap_exception(content)
1866
1867 records = content['records']
1868
1869 buffer_lens = content['buffer_lens']
1870 result_buffer_lens = content['result_buffer_lens']
1871 buffers = msg['buffers']
1872 has_bufs = buffer_lens is not None
1873 has_rbufs = result_buffer_lens is not None
1874 for i,rec in enumerate(records):
1875 # unpack datetime objects
1876 for hkey in ('header', 'result_header'):
1877 if hkey in rec:
1878 rec[hkey] = extract_dates(rec[hkey])
1879 for dtkey in ('submitted', 'started', 'completed', 'received'):
1880 if dtkey in rec:
1881 rec[dtkey] = parse_date(rec[dtkey])
1882 # relink buffers
1883 if has_bufs:
1884 blen = buffer_lens[i]
1885 rec['buffers'], buffers = buffers[:blen],buffers[blen:]
1886 if has_rbufs:
1887 blen = result_buffer_lens[i]
1888 rec['result_buffers'], buffers = buffers[:blen],buffers[blen:]
1889
1890 return records
1891
1892 __all__ = [ 'Client' ]
@@ -1,436 +0,0 b''
1 # encoding: utf-8
2 """
3 =============
4 parallelmagic
5 =============
6
7 Magic command interface for interactive parallel work.
8
9 Usage
10 =====
11
12 ``%autopx``
13
14 {AUTOPX_DOC}
15
16 ``%px``
17
18 {PX_DOC}
19
20 ``%pxresult``
21
22 {RESULT_DOC}
23
24 ``%pxconfig``
25
26 {CONFIG_DOC}
27
28 """
29 from __future__ import print_function
30
31 #-----------------------------------------------------------------------------
32 # Copyright (C) 2008 The IPython Development Team
33 #
34 # Distributed under the terms of the BSD License. The full license is in
35 # the file COPYING, distributed as part of this software.
36 #-----------------------------------------------------------------------------
37
38 #-----------------------------------------------------------------------------
39 # Imports
40 #-----------------------------------------------------------------------------
41
42 import ast
43 import re
44
45 from IPython.core.error import UsageError
46 from IPython.core.magic import Magics
47 from IPython.core import magic_arguments
48 from IPython.utils.text import dedent
49
50 #-----------------------------------------------------------------------------
51 # Definitions of magic functions for use with IPython
52 #-----------------------------------------------------------------------------
53
54
55 NO_LAST_RESULT = "%pxresult recalls last %px result, which has not yet been used."
56
57 def exec_args(f):
58 """decorator for adding block/targets args for execution
59
60 applied to %pxconfig and %%px
61 """
62 args = [
63 magic_arguments.argument('-b', '--block', action="store_const",
64 const=True, dest='block',
65 help="use blocking (sync) execution",
66 ),
67 magic_arguments.argument('-a', '--noblock', action="store_const",
68 const=False, dest='block',
69 help="use non-blocking (async) execution",
70 ),
71 magic_arguments.argument('-t', '--targets', type=str,
72 help="specify the targets on which to execute",
73 ),
74 magic_arguments.argument('--local', action="store_const",
75 const=True, dest="local",
76 help="also execute the cell in the local namespace",
77 ),
78 magic_arguments.argument('--verbose', action="store_const",
79 const=True, dest="set_verbose",
80 help="print a message at each execution",
81 ),
82 magic_arguments.argument('--no-verbose', action="store_const",
83 const=False, dest="set_verbose",
84 help="don't print any messages",
85 ),
86 ]
87 for a in args:
88 f = a(f)
89 return f
90
91 def output_args(f):
92 """decorator for output-formatting args
93
94 applied to %pxresult and %%px
95 """
96 args = [
97 magic_arguments.argument('-r', action="store_const", dest='groupby',
98 const='order',
99 help="collate outputs in order (same as group-outputs=order)"
100 ),
101 magic_arguments.argument('-e', action="store_const", dest='groupby',
102 const='engine',
103 help="group outputs by engine (same as group-outputs=engine)"
104 ),
105 magic_arguments.argument('--group-outputs', dest='groupby', type=str,
106 choices=['engine', 'order', 'type'], default='type',
107 help="""Group the outputs in a particular way.
108
109 Choices are:
110
111 **type**: group outputs of all engines by type (stdout, stderr, displaypub, etc.).
112 **engine**: display all output for each engine together.
113 **order**: like type, but individual displaypub output from each engine is collated.
114 For example, if multiple plots are generated by each engine, the first
115 figure of each engine will be displayed, then the second of each, etc.
116 """
117 ),
118 magic_arguments.argument('-o', '--out', dest='save_name', type=str,
119 help="""store the AsyncResult object for this computation
120 in the global namespace under this name.
121 """
122 ),
123 ]
124 for a in args:
125 f = a(f)
126 return f
127
128 class ParallelMagics(Magics):
129 """A set of magics useful when controlling a parallel IPython cluster.
130 """
131
132 # magic-related
133 magics = None
134 registered = True
135
136 # suffix for magics
137 suffix = ''
138 # A flag showing if autopx is activated or not
139 _autopx = False
140 # the current view used by the magics:
141 view = None
142 # last result cache for %pxresult
143 last_result = None
144 # verbose flag
145 verbose = False
146
147 def __init__(self, shell, view, suffix=''):
148 self.view = view
149 self.suffix = suffix
150
151 # register magics
152 self.magics = dict(cell={},line={})
153 line_magics = self.magics['line']
154
155 px = 'px' + suffix
156 if not suffix:
157 # keep %result for legacy compatibility
158 line_magics['result'] = self.result
159
160 line_magics['pxresult' + suffix] = self.result
161 line_magics[px] = self.px
162 line_magics['pxconfig' + suffix] = self.pxconfig
163 line_magics['auto' + px] = self.autopx
164
165 self.magics['cell'][px] = self.cell_px
166
167 super(ParallelMagics, self).__init__(shell=shell)
168
169 def _eval_target_str(self, ts):
170 if ':' in ts:
171 targets = eval("self.view.client.ids[%s]" % ts)
172 elif 'all' in ts:
173 targets = 'all'
174 else:
175 targets = eval(ts)
176 return targets
177
178 @magic_arguments.magic_arguments()
179 @exec_args
180 def pxconfig(self, line):
181 """configure default targets/blocking for %px magics"""
182 args = magic_arguments.parse_argstring(self.pxconfig, line)
183 if args.targets:
184 self.view.targets = self._eval_target_str(args.targets)
185 if args.block is not None:
186 self.view.block = args.block
187 if args.set_verbose is not None:
188 self.verbose = args.set_verbose
189
190 @magic_arguments.magic_arguments()
191 @output_args
192 def result(self, line=''):
193 """Print the result of the last asynchronous %px command.
194
195 This lets you recall the results of %px computations after
196 asynchronous submission (block=False).
197
198 Examples
199 --------
200 ::
201
202 In [23]: %px os.getpid()
203 Async parallel execution on engine(s): all
204
205 In [24]: %pxresult
206 Out[8:10]: 60920
207 Out[9:10]: 60921
208 Out[10:10]: 60922
209 Out[11:10]: 60923
210 """
211 args = magic_arguments.parse_argstring(self.result, line)
212
213 if self.last_result is None:
214 raise UsageError(NO_LAST_RESULT)
215
216 self.last_result.get()
217 self.last_result.display_outputs(groupby=args.groupby)
218
219 def px(self, line=''):
220 """Executes the given python command in parallel.
221
222 Examples
223 --------
224 ::
225
226 In [24]: %px a = os.getpid()
227 Parallel execution on engine(s): all
228
229 In [25]: %px print a
230 [stdout:0] 1234
231 [stdout:1] 1235
232 [stdout:2] 1236
233 [stdout:3] 1237
234 """
235 return self.parallel_execute(line)
236
237 def parallel_execute(self, cell, block=None, groupby='type', save_name=None):
238 """implementation used by %px and %%parallel"""
239
240 # defaults:
241 block = self.view.block if block is None else block
242
243 base = "Parallel" if block else "Async parallel"
244
245 targets = self.view.targets
246 if isinstance(targets, list) and len(targets) > 10:
247 str_targets = str(targets[:4])[:-1] + ', ..., ' + str(targets[-4:])[1:]
248 else:
249 str_targets = str(targets)
250 if self.verbose:
251 print(base + " execution on engine(s): %s" % str_targets)
252
253 result = self.view.execute(cell, silent=False, block=False)
254 self.last_result = result
255
256 if save_name:
257 self.shell.user_ns[save_name] = result
258
259 if block:
260 result.get()
261 result.display_outputs(groupby)
262 else:
263 # return AsyncResult only on non-blocking submission
264 return result
265
266 @magic_arguments.magic_arguments()
267 @exec_args
268 @output_args
269 def cell_px(self, line='', cell=None):
270 """Executes the cell in parallel.
271
272 Examples
273 --------
274 ::
275
276 In [24]: %%px --noblock
277 ....: a = os.getpid()
278 Async parallel execution on engine(s): all
279
280 In [25]: %%px
281 ....: print a
282 [stdout:0] 1234
283 [stdout:1] 1235
284 [stdout:2] 1236
285 [stdout:3] 1237
286 """
287
288 args = magic_arguments.parse_argstring(self.cell_px, line)
289
290 if args.targets:
291 save_targets = self.view.targets
292 self.view.targets = self._eval_target_str(args.targets)
293 # if running local, don't block until after local has run
294 block = False if args.local else args.block
295 try:
296 ar = self.parallel_execute(cell, block=block,
297 groupby=args.groupby,
298 save_name=args.save_name,
299 )
300 finally:
301 if args.targets:
302 self.view.targets = save_targets
303
304 # run locally after submitting remote
305 block = self.view.block if args.block is None else args.block
306 if args.local:
307 self.shell.run_cell(cell)
308 # now apply blocking behavor to remote execution
309 if block:
310 ar.get()
311 ar.display_outputs(args.groupby)
312 if not block:
313 return ar
314
315 def autopx(self, line=''):
316 """Toggles auto parallel mode.
317
318 Once this is called, all commands typed at the command line are send to
319 the engines to be executed in parallel. To control which engine are
320 used, the ``targets`` attribute of the view before
321 entering ``%autopx`` mode.
322
323
324 Then you can do the following::
325
326 In [25]: %autopx
327 %autopx to enabled
328
329 In [26]: a = 10
330 Parallel execution on engine(s): [0,1,2,3]
331 In [27]: print a
332 Parallel execution on engine(s): [0,1,2,3]
333 [stdout:0] 10
334 [stdout:1] 10
335 [stdout:2] 10
336 [stdout:3] 10
337
338
339 In [27]: %autopx
340 %autopx disabled
341 """
342 if self._autopx:
343 self._disable_autopx()
344 else:
345 self._enable_autopx()
346
347 def _enable_autopx(self):
348 """Enable %autopx mode by saving the original run_cell and installing
349 pxrun_cell.
350 """
351 # override run_cell
352 self._original_run_cell = self.shell.run_cell
353 self.shell.run_cell = self.pxrun_cell
354
355 self._autopx = True
356 print("%autopx enabled")
357
358 def _disable_autopx(self):
359 """Disable %autopx by restoring the original InteractiveShell.run_cell.
360 """
361 if self._autopx:
362 self.shell.run_cell = self._original_run_cell
363 self._autopx = False
364 print("%autopx disabled")
365
366 def pxrun_cell(self, raw_cell, store_history=False, silent=False):
367 """drop-in replacement for InteractiveShell.run_cell.
368
369 This executes code remotely, instead of in the local namespace.
370
371 See InteractiveShell.run_cell for details.
372 """
373
374 if (not raw_cell) or raw_cell.isspace():
375 return
376
377 ipself = self.shell
378
379 with ipself.builtin_trap:
380 cell = ipself.prefilter_manager.prefilter_lines(raw_cell)
381
382 # Store raw and processed history
383 if store_history:
384 ipself.history_manager.store_inputs(ipself.execution_count,
385 cell, raw_cell)
386
387 # ipself.logger.log(cell, raw_cell)
388
389 cell_name = ipself.compile.cache(cell, ipself.execution_count)
390
391 try:
392 ast.parse(cell, filename=cell_name)
393 except (OverflowError, SyntaxError, ValueError, TypeError,
394 MemoryError):
395 # Case 1
396 ipself.showsyntaxerror()
397 ipself.execution_count += 1
398 return None
399 except NameError:
400 # ignore name errors, because we don't know the remote keys
401 pass
402
403 if store_history:
404 # Write output to the database. Does nothing unless
405 # history output logging is enabled.
406 ipself.history_manager.store_output(ipself.execution_count)
407 # Each cell is a *single* input, regardless of how many lines it has
408 ipself.execution_count += 1
409 if re.search(r'get_ipython\(\)\.magic\(u?["\']%?autopx', cell):
410 self._disable_autopx()
411 return False
412 else:
413 try:
414 result = self.view.execute(cell, silent=False, block=False)
415 except:
416 ipself.showtraceback()
417 return True
418 else:
419 if self.view.block:
420 try:
421 result.get()
422 except:
423 self.shell.showtraceback()
424 return True
425 else:
426 with ipself.builtin_trap:
427 result.display_outputs()
428 return False
429
430
431 __doc__ = __doc__.format(
432 AUTOPX_DOC = dedent(ParallelMagics.autopx.__doc__),
433 PX_DOC = dedent(ParallelMagics.px.__doc__),
434 RESULT_DOC = dedent(ParallelMagics.result.__doc__),
435 CONFIG_DOC = dedent(ParallelMagics.pxconfig.__doc__),
436 )
@@ -1,124 +0,0 b''
1 # encoding: utf-8
2
3 """Classes used in scattering and gathering sequences.
4
5 Scattering consists of partitioning a sequence and sending the various
6 pieces to individual nodes in a cluster.
7 """
8
9 # Copyright (c) IPython Development Team.
10 # Distributed under the terms of the Modified BSD License.
11
12 from __future__ import division
13
14 import sys
15 from itertools import islice, chain
16
17 numpy = None
18
19 def is_array(obj):
20 """Is an object a numpy array?
21
22 Avoids importing numpy until it is requested
23 """
24 global numpy
25 if 'numpy' not in sys.modules:
26 return False
27
28 if numpy is None:
29 import numpy
30 return isinstance(obj, numpy.ndarray)
31
32 class Map(object):
33 """A class for partitioning a sequence using a map."""
34
35 def getPartition(self, seq, p, q, n=None):
36 """Returns the pth partition of q partitions of seq.
37
38 The length can be specified as `n`,
39 otherwise it is the value of `len(seq)`
40 """
41 n = len(seq) if n is None else n
42 # Test for error conditions here
43 if p<0 or p>=q:
44 raise ValueError("must have 0 <= p <= q, but have p=%s,q=%s" % (p, q))
45
46 remainder = n % q
47 basesize = n // q
48
49 if p < remainder:
50 low = p * (basesize + 1)
51 high = low + basesize + 1
52 else:
53 low = p * basesize + remainder
54 high = low + basesize
55
56 try:
57 result = seq[low:high]
58 except TypeError:
59 # some objects (iterators) can't be sliced,
60 # use islice:
61 result = list(islice(seq, low, high))
62
63 return result
64
65 def joinPartitions(self, listOfPartitions):
66 return self.concatenate(listOfPartitions)
67
68 def concatenate(self, listOfPartitions):
69 testObject = listOfPartitions[0]
70 # First see if we have a known array type
71 if is_array(testObject):
72 return numpy.concatenate(listOfPartitions)
73 # Next try for Python sequence types
74 if isinstance(testObject, (list, tuple)):
75 return list(chain.from_iterable(listOfPartitions))
76 # If we have scalars, just return listOfPartitions
77 return listOfPartitions
78
79 class RoundRobinMap(Map):
80 """Partitions a sequence in a round robin fashion.
81
82 This currently does not work!
83 """
84
85 def getPartition(self, seq, p, q, n=None):
86 n = len(seq) if n is None else n
87 return seq[p:n:q]
88
89 def joinPartitions(self, listOfPartitions):
90 testObject = listOfPartitions[0]
91 # First see if we have a known array type
92 if is_array(testObject):
93 return self.flatten_array(listOfPartitions)
94 if isinstance(testObject, (list, tuple)):
95 return self.flatten_list(listOfPartitions)
96 return listOfPartitions
97
98 def flatten_array(self, listOfPartitions):
99 test = listOfPartitions[0]
100 shape = list(test.shape)
101 shape[0] = sum([ p.shape[0] for p in listOfPartitions])
102 A = numpy.ndarray(shape)
103 N = shape[0]
104 q = len(listOfPartitions)
105 for p,part in enumerate(listOfPartitions):
106 A[p:N:q] = part
107 return A
108
109 def flatten_list(self, listOfPartitions):
110 flat = []
111 for i in range(len(listOfPartitions[0])):
112 flat.extend([ part[i] for part in listOfPartitions if len(part) > i ])
113 return flat
114
115 def mappable(obj):
116 """return whether an object is mappable or not."""
117 if isinstance(obj, (tuple,list)):
118 return True
119 if is_array(obj):
120 return True
121 return False
122
123 dists = {'b':Map,'r':RoundRobinMap}
124
@@ -1,273 +0,0 b''
1 """Remote Functions and decorators for Views."""
2
3 # Copyright (c) IPython Development Team.
4 # Distributed under the terms of the Modified BSD License.
5
6 from __future__ import division
7
8 import sys
9 import warnings
10
11 from decorator import decorator
12
13 from . import map as Map
14 from .asyncresult import AsyncMapResult
15
16 #-----------------------------------------------------------------------------
17 # Functions and Decorators
18 #-----------------------------------------------------------------------------
19
20 def remote(view, block=None, **flags):
21 """Turn a function into a remote function.
22
23 This method can be used for map:
24
25 In [1]: @remote(view,block=True)
26 ...: def func(a):
27 ...: pass
28 """
29
30 def remote_function(f):
31 return RemoteFunction(view, f, block=block, **flags)
32 return remote_function
33
34 def parallel(view, dist='b', block=None, ordered=True, **flags):
35 """Turn a function into a parallel remote function.
36
37 This method can be used for map:
38
39 In [1]: @parallel(view, block=True)
40 ...: def func(a):
41 ...: pass
42 """
43
44 def parallel_function(f):
45 return ParallelFunction(view, f, dist=dist, block=block, ordered=ordered, **flags)
46 return parallel_function
47
48 def getname(f):
49 """Get the name of an object.
50
51 For use in case of callables that are not functions, and
52 thus may not have __name__ defined.
53
54 Order: f.__name__ > f.name > str(f)
55 """
56 try:
57 return f.__name__
58 except:
59 pass
60 try:
61 return f.name
62 except:
63 pass
64
65 return str(f)
66
67 @decorator
68 def sync_view_results(f, self, *args, **kwargs):
69 """sync relevant results from self.client to our results attribute.
70
71 This is a clone of view.sync_results, but for remote functions
72 """
73 view = self.view
74 if view._in_sync_results:
75 return f(self, *args, **kwargs)
76 view._in_sync_results = True
77 try:
78 ret = f(self, *args, **kwargs)
79 finally:
80 view._in_sync_results = False
81 view._sync_results()
82 return ret
83
84 #--------------------------------------------------------------------------
85 # Classes
86 #--------------------------------------------------------------------------
87
88 class RemoteFunction(object):
89 """Turn an existing function into a remote function.
90
91 Parameters
92 ----------
93
94 view : View instance
95 The view to be used for execution
96 f : callable
97 The function to be wrapped into a remote function
98 block : bool [default: None]
99 Whether to wait for results or not. The default behavior is
100 to use the current `block` attribute of `view`
101
102 **flags : remaining kwargs are passed to View.temp_flags
103 """
104
105 view = None # the remote connection
106 func = None # the wrapped function
107 block = None # whether to block
108 flags = None # dict of extra kwargs for temp_flags
109
110 def __init__(self, view, f, block=None, **flags):
111 self.view = view
112 self.func = f
113 self.block=block
114 self.flags=flags
115
116 def __call__(self, *args, **kwargs):
117 block = self.view.block if self.block is None else self.block
118 with self.view.temp_flags(block=block, **self.flags):
119 return self.view.apply(self.func, *args, **kwargs)
120
121
122 class ParallelFunction(RemoteFunction):
123 """Class for mapping a function to sequences.
124
125 This will distribute the sequences according the a mapper, and call
126 the function on each sub-sequence. If called via map, then the function
127 will be called once on each element, rather that each sub-sequence.
128
129 Parameters
130 ----------
131
132 view : View instance
133 The view to be used for execution
134 f : callable
135 The function to be wrapped into a remote function
136 dist : str [default: 'b']
137 The key for which mapObject to use to distribute sequences
138 options are:
139
140 * 'b' : use contiguous chunks in order
141 * 'r' : use round-robin striping
142
143 block : bool [default: None]
144 Whether to wait for results or not. The default behavior is
145 to use the current `block` attribute of `view`
146 chunksize : int or None
147 The size of chunk to use when breaking up sequences in a load-balanced manner
148 ordered : bool [default: True]
149 Whether the result should be kept in order. If False,
150 results become available as they arrive, regardless of submission order.
151 **flags
152 remaining kwargs are passed to View.temp_flags
153 """
154
155 chunksize = None
156 ordered = None
157 mapObject = None
158 _mapping = False
159
160 def __init__(self, view, f, dist='b', block=None, chunksize=None, ordered=True, **flags):
161 super(ParallelFunction, self).__init__(view, f, block=block, **flags)
162 self.chunksize = chunksize
163 self.ordered = ordered
164
165 mapClass = Map.dists[dist]
166 self.mapObject = mapClass()
167
168 @sync_view_results
169 def __call__(self, *sequences):
170 client = self.view.client
171
172 lens = []
173 maxlen = minlen = -1
174 for i, seq in enumerate(sequences):
175 try:
176 n = len(seq)
177 except Exception:
178 seq = list(seq)
179 if isinstance(sequences, tuple):
180 # can't alter a tuple
181 sequences = list(sequences)
182 sequences[i] = seq
183 n = len(seq)
184 if n > maxlen:
185 maxlen = n
186 if minlen == -1 or n < minlen:
187 minlen = n
188 lens.append(n)
189
190 if maxlen == 0:
191 # nothing to iterate over
192 return []
193
194 # check that the length of sequences match
195 if not self._mapping and minlen != maxlen:
196 msg = 'all sequences must have equal length, but have %s' % lens
197 raise ValueError(msg)
198
199 balanced = 'Balanced' in self.view.__class__.__name__
200 if balanced:
201 if self.chunksize:
202 nparts = maxlen // self.chunksize + int(maxlen % self.chunksize > 0)
203 else:
204 nparts = maxlen
205 targets = [None]*nparts
206 else:
207 if self.chunksize:
208 warnings.warn("`chunksize` is ignored unless load balancing", UserWarning)
209 # multiplexed:
210 targets = self.view.targets
211 # 'all' is lazily evaluated at execution time, which is now:
212 if targets == 'all':
213 targets = client._build_targets(targets)[1]
214 elif isinstance(targets, int):
215 # single-engine view, targets must be iterable
216 targets = [targets]
217 nparts = len(targets)
218
219 msg_ids = []
220 for index, t in enumerate(targets):
221 args = []
222 for seq in sequences:
223 part = self.mapObject.getPartition(seq, index, nparts, maxlen)
224 args.append(part)
225
226 if sum([len(arg) for arg in args]) == 0:
227 continue
228
229 if self._mapping:
230 if sys.version_info[0] >= 3:
231 f = lambda f, *sequences: list(map(f, *sequences))
232 else:
233 f = map
234 args = [self.func] + args
235 else:
236 f=self.func
237
238 view = self.view if balanced else client[t]
239 with view.temp_flags(block=False, **self.flags):
240 ar = view.apply(f, *args)
241
242 msg_ids.extend(ar.msg_ids)
243
244 r = AsyncMapResult(self.view.client, msg_ids, self.mapObject,
245 fname=getname(self.func),
246 ordered=self.ordered
247 )
248
249 if self.block:
250 try:
251 return r.get()
252 except KeyboardInterrupt:
253 return r
254 else:
255 return r
256
257 def map(self, *sequences):
258 """call a function on each element of one or more sequence(s) remotely.
259 This should behave very much like the builtin map, but return an AsyncMapResult
260 if self.block is False.
261
262 That means it can take generators (will be cast to lists locally),
263 and mismatched sequence lengths will be padded with None.
264 """
265 # set _mapping as a flag for use inside self.__call__
266 self._mapping = True
267 try:
268 ret = self(*sequences)
269 finally:
270 self._mapping = False
271 return ret
272
273 __all__ = ['remote', 'parallel', 'RemoteFunction', 'ParallelFunction']
This diff has been collapsed as it changes many lines, (1121 lines changed) Show them Hide them
@@ -1,1121 +0,0 b''
1 """Views of remote engines."""
2
3 # Copyright (c) IPython Development Team.
4 # Distributed under the terms of the Modified BSD License.
5
6 from __future__ import print_function
7
8 import imp
9 import sys
10 import warnings
11 from contextlib import contextmanager
12 from types import ModuleType
13
14 import zmq
15
16 from IPython.utils import pickleutil
17 from IPython.utils.traitlets import (
18 HasTraits, Any, Bool, List, Dict, Set, Instance, CFloat, Integer
19 )
20 from decorator import decorator
21
22 from ipython_parallel import util
23 from ipython_parallel.controller.dependency import Dependency, dependent
24 from IPython.utils.py3compat import string_types, iteritems, PY3
25
26 from . import map as Map
27 from .asyncresult import AsyncResult, AsyncMapResult
28 from .remotefunction import ParallelFunction, parallel, remote, getname
29
30 #-----------------------------------------------------------------------------
31 # Decorators
32 #-----------------------------------------------------------------------------
33
34 @decorator
35 def save_ids(f, self, *args, **kwargs):
36 """Keep our history and outstanding attributes up to date after a method call."""
37 n_previous = len(self.client.history)
38 try:
39 ret = f(self, *args, **kwargs)
40 finally:
41 nmsgs = len(self.client.history) - n_previous
42 msg_ids = self.client.history[-nmsgs:]
43 self.history.extend(msg_ids)
44 self.outstanding.update(msg_ids)
45 return ret
46
47 @decorator
48 def sync_results(f, self, *args, **kwargs):
49 """sync relevant results from self.client to our results attribute."""
50 if self._in_sync_results:
51 return f(self, *args, **kwargs)
52 self._in_sync_results = True
53 try:
54 ret = f(self, *args, **kwargs)
55 finally:
56 self._in_sync_results = False
57 self._sync_results()
58 return ret
59
60 @decorator
61 def spin_after(f, self, *args, **kwargs):
62 """call spin after the method."""
63 ret = f(self, *args, **kwargs)
64 self.spin()
65 return ret
66
67 #-----------------------------------------------------------------------------
68 # Classes
69 #-----------------------------------------------------------------------------
70
71 class View(HasTraits):
72 """Base View class for more convenint apply(f,*args,**kwargs) syntax via attributes.
73
74 Don't use this class, use subclasses.
75
76 Methods
77 -------
78
79 spin
80 flushes incoming results and registration state changes
81 control methods spin, and requesting `ids` also ensures up to date
82
83 wait
84 wait on one or more msg_ids
85
86 execution methods
87 apply
88 legacy: execute, run
89
90 data movement
91 push, pull, scatter, gather
92
93 query methods
94 get_result, queue_status, purge_results, result_status
95
96 control methods
97 abort, shutdown
98
99 """
100 # flags
101 block=Bool(False)
102 track=Bool(True)
103 targets = Any()
104
105 history=List()
106 outstanding = Set()
107 results = Dict()
108 client = Instance('ipython_parallel.Client', allow_none=True)
109
110 _socket = Instance('zmq.Socket', allow_none=True)
111 _flag_names = List(['targets', 'block', 'track'])
112 _in_sync_results = Bool(False)
113 _targets = Any()
114 _idents = Any()
115
116 def __init__(self, client=None, socket=None, **flags):
117 super(View, self).__init__(client=client, _socket=socket)
118 self.results = client.results
119 self.block = client.block
120
121 self.set_flags(**flags)
122
123 assert not self.__class__ is View, "Don't use base View objects, use subclasses"
124
125 def __repr__(self):
126 strtargets = str(self.targets)
127 if len(strtargets) > 16:
128 strtargets = strtargets[:12]+'...]'
129 return "<%s %s>"%(self.__class__.__name__, strtargets)
130
131 def __len__(self):
132 if isinstance(self.targets, list):
133 return len(self.targets)
134 elif isinstance(self.targets, int):
135 return 1
136 else:
137 return len(self.client)
138
139 def set_flags(self, **kwargs):
140 """set my attribute flags by keyword.
141
142 Views determine behavior with a few attributes (`block`, `track`, etc.).
143 These attributes can be set all at once by name with this method.
144
145 Parameters
146 ----------
147
148 block : bool
149 whether to wait for results
150 track : bool
151 whether to create a MessageTracker to allow the user to
152 safely edit after arrays and buffers during non-copying
153 sends.
154 """
155 for name, value in iteritems(kwargs):
156 if name not in self._flag_names:
157 raise KeyError("Invalid name: %r"%name)
158 else:
159 setattr(self, name, value)
160
161 @contextmanager
162 def temp_flags(self, **kwargs):
163 """temporarily set flags, for use in `with` statements.
164
165 See set_flags for permanent setting of flags
166
167 Examples
168 --------
169
170 >>> view.track=False
171 ...
172 >>> with view.temp_flags(track=True):
173 ... ar = view.apply(dostuff, my_big_array)
174 ... ar.tracker.wait() # wait for send to finish
175 >>> view.track
176 False
177
178 """
179 # preflight: save flags, and set temporaries
180 saved_flags = {}
181 for f in self._flag_names:
182 saved_flags[f] = getattr(self, f)
183 self.set_flags(**kwargs)
184 # yield to the with-statement block
185 try:
186 yield
187 finally:
188 # postflight: restore saved flags
189 self.set_flags(**saved_flags)
190
191
192 #----------------------------------------------------------------
193 # apply
194 #----------------------------------------------------------------
195
196 def _sync_results(self):
197 """to be called by @sync_results decorator
198
199 after submitting any tasks.
200 """
201 delta = self.outstanding.difference(self.client.outstanding)
202 completed = self.outstanding.intersection(delta)
203 self.outstanding = self.outstanding.difference(completed)
204
205 @sync_results
206 @save_ids
207 def _really_apply(self, f, args, kwargs, block=None, **options):
208 """wrapper for client.send_apply_request"""
209 raise NotImplementedError("Implement in subclasses")
210
211 def apply(self, f, *args, **kwargs):
212 """calls ``f(*args, **kwargs)`` on remote engines, returning the result.
213
214 This method sets all apply flags via this View's attributes.
215
216 Returns :class:`~IPython.parallel.client.asyncresult.AsyncResult`
217 instance if ``self.block`` is False, otherwise the return value of
218 ``f(*args, **kwargs)``.
219 """
220 return self._really_apply(f, args, kwargs)
221
222 def apply_async(self, f, *args, **kwargs):
223 """calls ``f(*args, **kwargs)`` on remote engines in a nonblocking manner.
224
225 Returns :class:`~IPython.parallel.client.asyncresult.AsyncResult` instance.
226 """
227 return self._really_apply(f, args, kwargs, block=False)
228
229 @spin_after
230 def apply_sync(self, f, *args, **kwargs):
231 """calls ``f(*args, **kwargs)`` on remote engines in a blocking manner,
232 returning the result.
233 """
234 return self._really_apply(f, args, kwargs, block=True)
235
236 #----------------------------------------------------------------
237 # wrappers for client and control methods
238 #----------------------------------------------------------------
239 @sync_results
240 def spin(self):
241 """spin the client, and sync"""
242 self.client.spin()
243
244 @sync_results
245 def wait(self, jobs=None, timeout=-1):
246 """waits on one or more `jobs`, for up to `timeout` seconds.
247
248 Parameters
249 ----------
250
251 jobs : int, str, or list of ints and/or strs, or one or more AsyncResult objects
252 ints are indices to self.history
253 strs are msg_ids
254 default: wait on all outstanding messages
255 timeout : float
256 a time in seconds, after which to give up.
257 default is -1, which means no timeout
258
259 Returns
260 -------
261
262 True : when all msg_ids are done
263 False : timeout reached, some msg_ids still outstanding
264 """
265 if jobs is None:
266 jobs = self.history
267 return self.client.wait(jobs, timeout)
268
269 def abort(self, jobs=None, targets=None, block=None):
270 """Abort jobs on my engines.
271
272 Parameters
273 ----------
274
275 jobs : None, str, list of strs, optional
276 if None: abort all jobs.
277 else: abort specific msg_id(s).
278 """
279 block = block if block is not None else self.block
280 targets = targets if targets is not None else self.targets
281 jobs = jobs if jobs is not None else list(self.outstanding)
282
283 return self.client.abort(jobs=jobs, targets=targets, block=block)
284
285 def queue_status(self, targets=None, verbose=False):
286 """Fetch the Queue status of my engines"""
287 targets = targets if targets is not None else self.targets
288 return self.client.queue_status(targets=targets, verbose=verbose)
289
290 def purge_results(self, jobs=[], targets=[]):
291 """Instruct the controller to forget specific results."""
292 if targets is None or targets == 'all':
293 targets = self.targets
294 return self.client.purge_results(jobs=jobs, targets=targets)
295
296 def shutdown(self, targets=None, restart=False, hub=False, block=None):
297 """Terminates one or more engine processes, optionally including the hub.
298 """
299 block = self.block if block is None else block
300 if targets is None or targets == 'all':
301 targets = self.targets
302 return self.client.shutdown(targets=targets, restart=restart, hub=hub, block=block)
303
304 @spin_after
305 def get_result(self, indices_or_msg_ids=None, block=None, owner=True):
306 """return one or more results, specified by history index or msg_id.
307
308 See :meth:`IPython.parallel.client.client.Client.get_result` for details.
309 """
310
311 if indices_or_msg_ids is None:
312 indices_or_msg_ids = -1
313 if isinstance(indices_or_msg_ids, int):
314 indices_or_msg_ids = self.history[indices_or_msg_ids]
315 elif isinstance(indices_or_msg_ids, (list,tuple,set)):
316 indices_or_msg_ids = list(indices_or_msg_ids)
317 for i,index in enumerate(indices_or_msg_ids):
318 if isinstance(index, int):
319 indices_or_msg_ids[i] = self.history[index]
320 return self.client.get_result(indices_or_msg_ids, block=block, owner=owner)
321
322 #-------------------------------------------------------------------
323 # Map
324 #-------------------------------------------------------------------
325
326 @sync_results
327 def map(self, f, *sequences, **kwargs):
328 """override in subclasses"""
329 raise NotImplementedError
330
331 def map_async(self, f, *sequences, **kwargs):
332 """Parallel version of builtin :func:`python:map`, using this view's engines.
333
334 This is equivalent to ``map(...block=False)``.
335
336 See `self.map` for details.
337 """
338 if 'block' in kwargs:
339 raise TypeError("map_async doesn't take a `block` keyword argument.")
340 kwargs['block'] = False
341 return self.map(f,*sequences,**kwargs)
342
343 def map_sync(self, f, *sequences, **kwargs):
344 """Parallel version of builtin :func:`python:map`, using this view's engines.
345
346 This is equivalent to ``map(...block=True)``.
347
348 See `self.map` for details.
349 """
350 if 'block' in kwargs:
351 raise TypeError("map_sync doesn't take a `block` keyword argument.")
352 kwargs['block'] = True
353 return self.map(f,*sequences,**kwargs)
354
355 def imap(self, f, *sequences, **kwargs):
356 """Parallel version of :func:`itertools.imap`.
357
358 See `self.map` for details.
359
360 """
361
362 return iter(self.map_async(f,*sequences, **kwargs))
363
364 #-------------------------------------------------------------------
365 # Decorators
366 #-------------------------------------------------------------------
367
368 def remote(self, block=None, **flags):
369 """Decorator for making a RemoteFunction"""
370 block = self.block if block is None else block
371 return remote(self, block=block, **flags)
372
373 def parallel(self, dist='b', block=None, **flags):
374 """Decorator for making a ParallelFunction"""
375 block = self.block if block is None else block
376 return parallel(self, dist=dist, block=block, **flags)
377
378 class DirectView(View):
379 """Direct Multiplexer View of one or more engines.
380
381 These are created via indexed access to a client:
382
383 >>> dv_1 = client[1]
384 >>> dv_all = client[:]
385 >>> dv_even = client[::2]
386 >>> dv_some = client[1:3]
387
388 This object provides dictionary access to engine namespaces:
389
390 # push a=5:
391 >>> dv['a'] = 5
392 # pull 'foo':
393 >>> dv['foo']
394
395 """
396
397 def __init__(self, client=None, socket=None, targets=None):
398 super(DirectView, self).__init__(client=client, socket=socket, targets=targets)
399
400 @property
401 def importer(self):
402 """sync_imports(local=True) as a property.
403
404 See sync_imports for details.
405
406 """
407 return self.sync_imports(True)
408
409 @contextmanager
410 def sync_imports(self, local=True, quiet=False):
411 """Context Manager for performing simultaneous local and remote imports.
412
413 'import x as y' will *not* work. The 'as y' part will simply be ignored.
414
415 If `local=True`, then the package will also be imported locally.
416
417 If `quiet=True`, no output will be produced when attempting remote
418 imports.
419
420 Note that remote-only (`local=False`) imports have not been implemented.
421
422 >>> with view.sync_imports():
423 ... from numpy import recarray
424 importing recarray from numpy on engine(s)
425
426 """
427 from IPython.utils.py3compat import builtin_mod
428 local_import = builtin_mod.__import__
429 modules = set()
430 results = []
431 @util.interactive
432 def remote_import(name, fromlist, level):
433 """the function to be passed to apply, that actually performs the import
434 on the engine, and loads up the user namespace.
435 """
436 import sys
437 user_ns = globals()
438 mod = __import__(name, fromlist=fromlist, level=level)
439 if fromlist:
440 for key in fromlist:
441 user_ns[key] = getattr(mod, key)
442 else:
443 user_ns[name] = sys.modules[name]
444
445 def view_import(name, globals={}, locals={}, fromlist=[], level=0):
446 """the drop-in replacement for __import__, that optionally imports
447 locally as well.
448 """
449 # don't override nested imports
450 save_import = builtin_mod.__import__
451 builtin_mod.__import__ = local_import
452
453 if imp.lock_held():
454 # this is a side-effect import, don't do it remotely, or even
455 # ignore the local effects
456 return local_import(name, globals, locals, fromlist, level)
457
458 imp.acquire_lock()
459 if local:
460 mod = local_import(name, globals, locals, fromlist, level)
461 else:
462 raise NotImplementedError("remote-only imports not yet implemented")
463 imp.release_lock()
464
465 key = name+':'+','.join(fromlist or [])
466 if level <= 0 and key not in modules:
467 modules.add(key)
468 if not quiet:
469 if fromlist:
470 print("importing %s from %s on engine(s)"%(','.join(fromlist), name))
471 else:
472 print("importing %s on engine(s)"%name)
473 results.append(self.apply_async(remote_import, name, fromlist, level))
474 # restore override
475 builtin_mod.__import__ = save_import
476
477 return mod
478
479 # override __import__
480 builtin_mod.__import__ = view_import
481 try:
482 # enter the block
483 yield
484 except ImportError:
485 if local:
486 raise
487 else:
488 # ignore import errors if not doing local imports
489 pass
490 finally:
491 # always restore __import__
492 builtin_mod.__import__ = local_import
493
494 for r in results:
495 # raise possible remote ImportErrors here
496 r.get()
497
498 def use_dill(self):
499 """Expand serialization support with dill
500
501 adds support for closures, etc.
502
503 This calls ipython_kernel.pickleutil.use_dill() here and on each engine.
504 """
505 pickleutil.use_dill()
506 return self.apply(pickleutil.use_dill)
507
508 def use_cloudpickle(self):
509 """Expand serialization support with cloudpickle.
510 """
511 pickleutil.use_cloudpickle()
512 return self.apply(pickleutil.use_cloudpickle)
513
514
515 @sync_results
516 @save_ids
517 def _really_apply(self, f, args=None, kwargs=None, targets=None, block=None, track=None):
518 """calls f(*args, **kwargs) on remote engines, returning the result.
519
520 This method sets all of `apply`'s flags via this View's attributes.
521
522 Parameters
523 ----------
524
525 f : callable
526
527 args : list [default: empty]
528
529 kwargs : dict [default: empty]
530
531 targets : target list [default: self.targets]
532 where to run
533 block : bool [default: self.block]
534 whether to block
535 track : bool [default: self.track]
536 whether to ask zmq to track the message, for safe non-copying sends
537
538 Returns
539 -------
540
541 if self.block is False:
542 returns AsyncResult
543 else:
544 returns actual result of f(*args, **kwargs) on the engine(s)
545 This will be a list of self.targets is also a list (even length 1), or
546 the single result if self.targets is an integer engine id
547 """
548 args = [] if args is None else args
549 kwargs = {} if kwargs is None else kwargs
550 block = self.block if block is None else block
551 track = self.track if track is None else track
552 targets = self.targets if targets is None else targets
553
554 _idents, _targets = self.client._build_targets(targets)
555 msg_ids = []
556 trackers = []
557 for ident in _idents:
558 msg = self.client.send_apply_request(self._socket, f, args, kwargs, track=track,
559 ident=ident)
560 if track:
561 trackers.append(msg['tracker'])
562 msg_ids.append(msg['header']['msg_id'])
563 if isinstance(targets, int):
564 msg_ids = msg_ids[0]
565 tracker = None if track is False else zmq.MessageTracker(*trackers)
566 ar = AsyncResult(self.client, msg_ids, fname=getname(f), targets=_targets,
567 tracker=tracker, owner=True,
568 )
569 if block:
570 try:
571 return ar.get()
572 except KeyboardInterrupt:
573 pass
574 return ar
575
576
577 @sync_results
578 def map(self, f, *sequences, **kwargs):
579 """``view.map(f, *sequences, block=self.block)`` => list|AsyncMapResult
580
581 Parallel version of builtin `map`, using this View's `targets`.
582
583 There will be one task per target, so work will be chunked
584 if the sequences are longer than `targets`.
585
586 Results can be iterated as they are ready, but will become available in chunks.
587
588 Parameters
589 ----------
590
591 f : callable
592 function to be mapped
593 *sequences: one or more sequences of matching length
594 the sequences to be distributed and passed to `f`
595 block : bool
596 whether to wait for the result or not [default self.block]
597
598 Returns
599 -------
600
601
602 If block=False
603 An :class:`~ipython_parallel.client.asyncresult.AsyncMapResult` instance.
604 An object like AsyncResult, but which reassembles the sequence of results
605 into a single list. AsyncMapResults can be iterated through before all
606 results are complete.
607 else
608 A list, the result of ``map(f,*sequences)``
609 """
610
611 block = kwargs.pop('block', self.block)
612 for k in kwargs.keys():
613 if k not in ['block', 'track']:
614 raise TypeError("invalid keyword arg, %r"%k)
615
616 assert len(sequences) > 0, "must have some sequences to map onto!"
617 pf = ParallelFunction(self, f, block=block, **kwargs)
618 return pf.map(*sequences)
619
620 @sync_results
621 @save_ids
622 def execute(self, code, silent=True, targets=None, block=None):
623 """Executes `code` on `targets` in blocking or nonblocking manner.
624
625 ``execute`` is always `bound` (affects engine namespace)
626
627 Parameters
628 ----------
629
630 code : str
631 the code string to be executed
632 block : bool
633 whether or not to wait until done to return
634 default: self.block
635 """
636 block = self.block if block is None else block
637 targets = self.targets if targets is None else targets
638
639 _idents, _targets = self.client._build_targets(targets)
640 msg_ids = []
641 trackers = []
642 for ident in _idents:
643 msg = self.client.send_execute_request(self._socket, code, silent=silent, ident=ident)
644 msg_ids.append(msg['header']['msg_id'])
645 if isinstance(targets, int):
646 msg_ids = msg_ids[0]
647 ar = AsyncResult(self.client, msg_ids, fname='execute', targets=_targets, owner=True)
648 if block:
649 try:
650 ar.get()
651 except KeyboardInterrupt:
652 pass
653 return ar
654
655 def run(self, filename, targets=None, block=None):
656 """Execute contents of `filename` on my engine(s).
657
658 This simply reads the contents of the file and calls `execute`.
659
660 Parameters
661 ----------
662
663 filename : str
664 The path to the file
665 targets : int/str/list of ints/strs
666 the engines on which to execute
667 default : all
668 block : bool
669 whether or not to wait until done
670 default: self.block
671
672 """
673 with open(filename, 'r') as f:
674 # add newline in case of trailing indented whitespace
675 # which will cause SyntaxError
676 code = f.read()+'\n'
677 return self.execute(code, block=block, targets=targets)
678
679 def update(self, ns):
680 """update remote namespace with dict `ns`
681
682 See `push` for details.
683 """
684 return self.push(ns, block=self.block, track=self.track)
685
686 def push(self, ns, targets=None, block=None, track=None):
687 """update remote namespace with dict `ns`
688
689 Parameters
690 ----------
691
692 ns : dict
693 dict of keys with which to update engine namespace(s)
694 block : bool [default : self.block]
695 whether to wait to be notified of engine receipt
696
697 """
698
699 block = block if block is not None else self.block
700 track = track if track is not None else self.track
701 targets = targets if targets is not None else self.targets
702 # applier = self.apply_sync if block else self.apply_async
703 if not isinstance(ns, dict):
704 raise TypeError("Must be a dict, not %s"%type(ns))
705 return self._really_apply(util._push, kwargs=ns, block=block, track=track, targets=targets)
706
707 def get(self, key_s):
708 """get object(s) by `key_s` from remote namespace
709
710 see `pull` for details.
711 """
712 # block = block if block is not None else self.block
713 return self.pull(key_s, block=True)
714
715 def pull(self, names, targets=None, block=None):
716 """get object(s) by `name` from remote namespace
717
718 will return one object if it is a key.
719 can also take a list of keys, in which case it will return a list of objects.
720 """
721 block = block if block is not None else self.block
722 targets = targets if targets is not None else self.targets
723 applier = self.apply_sync if block else self.apply_async
724 if isinstance(names, string_types):
725 pass
726 elif isinstance(names, (list,tuple,set)):
727 for key in names:
728 if not isinstance(key, string_types):
729 raise TypeError("keys must be str, not type %r"%type(key))
730 else:
731 raise TypeError("names must be strs, not %r"%names)
732 return self._really_apply(util._pull, (names,), block=block, targets=targets)
733
734 def scatter(self, key, seq, dist='b', flatten=False, targets=None, block=None, track=None):
735 """
736 Partition a Python sequence and send the partitions to a set of engines.
737 """
738 block = block if block is not None else self.block
739 track = track if track is not None else self.track
740 targets = targets if targets is not None else self.targets
741
742 # construct integer ID list:
743 targets = self.client._build_targets(targets)[1]
744
745 mapObject = Map.dists[dist]()
746 nparts = len(targets)
747 msg_ids = []
748 trackers = []
749 for index, engineid in enumerate(targets):
750 partition = mapObject.getPartition(seq, index, nparts)
751 if flatten and len(partition) == 1:
752 ns = {key: partition[0]}
753 else:
754 ns = {key: partition}
755 r = self.push(ns, block=False, track=track, targets=engineid)
756 msg_ids.extend(r.msg_ids)
757 if track:
758 trackers.append(r._tracker)
759
760 if track:
761 tracker = zmq.MessageTracker(*trackers)
762 else:
763 tracker = None
764
765 r = AsyncResult(self.client, msg_ids, fname='scatter', targets=targets,
766 tracker=tracker, owner=True,
767 )
768 if block:
769 r.wait()
770 else:
771 return r
772
773 @sync_results
774 @save_ids
775 def gather(self, key, dist='b', targets=None, block=None):
776 """
777 Gather a partitioned sequence on a set of engines as a single local seq.
778 """
779 block = block if block is not None else self.block
780 targets = targets if targets is not None else self.targets
781 mapObject = Map.dists[dist]()
782 msg_ids = []
783
784 # construct integer ID list:
785 targets = self.client._build_targets(targets)[1]
786
787 for index, engineid in enumerate(targets):
788 msg_ids.extend(self.pull(key, block=False, targets=engineid).msg_ids)
789
790 r = AsyncMapResult(self.client, msg_ids, mapObject, fname='gather')
791
792 if block:
793 try:
794 return r.get()
795 except KeyboardInterrupt:
796 pass
797 return r
798
799 def __getitem__(self, key):
800 return self.get(key)
801
802 def __setitem__(self,key, value):
803 self.update({key:value})
804
805 def clear(self, targets=None, block=None):
806 """Clear the remote namespaces on my engines."""
807 block = block if block is not None else self.block
808 targets = targets if targets is not None else self.targets
809 return self.client.clear(targets=targets, block=block)
810
811 #----------------------------------------
812 # activate for %px, %autopx, etc. magics
813 #----------------------------------------
814
815 def activate(self, suffix=''):
816 """Activate IPython magics associated with this View
817
818 Defines the magics `%px, %autopx, %pxresult, %%px, %pxconfig`
819
820 Parameters
821 ----------
822
823 suffix: str [default: '']
824 The suffix, if any, for the magics. This allows you to have
825 multiple views associated with parallel magics at the same time.
826
827 e.g. ``rc[::2].activate(suffix='_even')`` will give you
828 the magics ``%px_even``, ``%pxresult_even``, etc. for running magics
829 on the even engines.
830 """
831
832 from IPython.parallel.client.magics import ParallelMagics
833
834 try:
835 # This is injected into __builtins__.
836 ip = get_ipython()
837 except NameError:
838 print("The IPython parallel magics (%px, etc.) only work within IPython.")
839 return
840
841 M = ParallelMagics(ip, self, suffix)
842 ip.magics_manager.register(M)
843
844
845 class LoadBalancedView(View):
846 """An load-balancing View that only executes via the Task scheduler.
847
848 Load-balanced views can be created with the client's `view` method:
849
850 >>> v = client.load_balanced_view()
851
852 or targets can be specified, to restrict the potential destinations:
853
854 >>> v = client.load_balanced_view([1,3])
855
856 which would restrict loadbalancing to between engines 1 and 3.
857
858 """
859
860 follow=Any()
861 after=Any()
862 timeout=CFloat()
863 retries = Integer(0)
864
865 _task_scheme = Any()
866 _flag_names = List(['targets', 'block', 'track', 'follow', 'after', 'timeout', 'retries'])
867
868 def __init__(self, client=None, socket=None, **flags):
869 super(LoadBalancedView, self).__init__(client=client, socket=socket, **flags)
870 self._task_scheme=client._task_scheme
871
872 def _validate_dependency(self, dep):
873 """validate a dependency.
874
875 For use in `set_flags`.
876 """
877 if dep is None or isinstance(dep, string_types + (AsyncResult, Dependency)):
878 return True
879 elif isinstance(dep, (list,set, tuple)):
880 for d in dep:
881 if not isinstance(d, string_types + (AsyncResult,)):
882 return False
883 elif isinstance(dep, dict):
884 if set(dep.keys()) != set(Dependency().as_dict().keys()):
885 return False
886 if not isinstance(dep['msg_ids'], list):
887 return False
888 for d in dep['msg_ids']:
889 if not isinstance(d, string_types):
890 return False
891 else:
892 return False
893
894 return True
895
896 def _render_dependency(self, dep):
897 """helper for building jsonable dependencies from various input forms."""
898 if isinstance(dep, Dependency):
899 return dep.as_dict()
900 elif isinstance(dep, AsyncResult):
901 return dep.msg_ids
902 elif dep is None:
903 return []
904 else:
905 # pass to Dependency constructor
906 return list(Dependency(dep))
907
908 def set_flags(self, **kwargs):
909 """set my attribute flags by keyword.
910
911 A View is a wrapper for the Client's apply method, but with attributes
912 that specify keyword arguments, those attributes can be set by keyword
913 argument with this method.
914
915 Parameters
916 ----------
917
918 block : bool
919 whether to wait for results
920 track : bool
921 whether to create a MessageTracker to allow the user to
922 safely edit after arrays and buffers during non-copying
923 sends.
924
925 after : Dependency or collection of msg_ids
926 Only for load-balanced execution (targets=None)
927 Specify a list of msg_ids as a time-based dependency.
928 This job will only be run *after* the dependencies
929 have been met.
930
931 follow : Dependency or collection of msg_ids
932 Only for load-balanced execution (targets=None)
933 Specify a list of msg_ids as a location-based dependency.
934 This job will only be run on an engine where this dependency
935 is met.
936
937 timeout : float/int or None
938 Only for load-balanced execution (targets=None)
939 Specify an amount of time (in seconds) for the scheduler to
940 wait for dependencies to be met before failing with a
941 DependencyTimeout.
942
943 retries : int
944 Number of times a task will be retried on failure.
945 """
946
947 super(LoadBalancedView, self).set_flags(**kwargs)
948 for name in ('follow', 'after'):
949 if name in kwargs:
950 value = kwargs[name]
951 if self._validate_dependency(value):
952 setattr(self, name, value)
953 else:
954 raise ValueError("Invalid dependency: %r"%value)
955 if 'timeout' in kwargs:
956 t = kwargs['timeout']
957 if not isinstance(t, (int, float, type(None))):
958 if (not PY3) and (not isinstance(t, long)):
959 raise TypeError("Invalid type for timeout: %r"%type(t))
960 if t is not None:
961 if t < 0:
962 raise ValueError("Invalid timeout: %s"%t)
963 self.timeout = t
964
965 @sync_results
966 @save_ids
967 def _really_apply(self, f, args=None, kwargs=None, block=None, track=None,
968 after=None, follow=None, timeout=None,
969 targets=None, retries=None):
970 """calls f(*args, **kwargs) on a remote engine, returning the result.
971
972 This method temporarily sets all of `apply`'s flags for a single call.
973
974 Parameters
975 ----------
976
977 f : callable
978
979 args : list [default: empty]
980
981 kwargs : dict [default: empty]
982
983 block : bool [default: self.block]
984 whether to block
985 track : bool [default: self.track]
986 whether to ask zmq to track the message, for safe non-copying sends
987
988 !!!!!! TODO: THE REST HERE !!!!
989
990 Returns
991 -------
992
993 if self.block is False:
994 returns AsyncResult
995 else:
996 returns actual result of f(*args, **kwargs) on the engine(s)
997 This will be a list of self.targets is also a list (even length 1), or
998 the single result if self.targets is an integer engine id
999 """
1000
1001 # validate whether we can run
1002 if self._socket.closed:
1003 msg = "Task farming is disabled"
1004 if self._task_scheme == 'pure':
1005 msg += " because the pure ZMQ scheduler cannot handle"
1006 msg += " disappearing engines."
1007 raise RuntimeError(msg)
1008
1009 if self._task_scheme == 'pure':
1010 # pure zmq scheme doesn't support extra features
1011 msg = "Pure ZMQ scheduler doesn't support the following flags:"
1012 "follow, after, retries, targets, timeout"
1013 if (follow or after or retries or targets or timeout):
1014 # hard fail on Scheduler flags
1015 raise RuntimeError(msg)
1016 if isinstance(f, dependent):
1017 # soft warn on functional dependencies
1018 warnings.warn(msg, RuntimeWarning)
1019
1020 # build args
1021 args = [] if args is None else args
1022 kwargs = {} if kwargs is None else kwargs
1023 block = self.block if block is None else block
1024 track = self.track if track is None else track
1025 after = self.after if after is None else after
1026 retries = self.retries if retries is None else retries
1027 follow = self.follow if follow is None else follow
1028 timeout = self.timeout if timeout is None else timeout
1029 targets = self.targets if targets is None else targets
1030
1031 if not isinstance(retries, int):
1032 raise TypeError('retries must be int, not %r'%type(retries))
1033
1034 if targets is None:
1035 idents = []
1036 else:
1037 idents = self.client._build_targets(targets)[0]
1038 # ensure *not* bytes
1039 idents = [ ident.decode() for ident in idents ]
1040
1041 after = self._render_dependency(after)
1042 follow = self._render_dependency(follow)
1043 metadata = dict(after=after, follow=follow, timeout=timeout, targets=idents, retries=retries)
1044
1045 msg = self.client.send_apply_request(self._socket, f, args, kwargs, track=track,
1046 metadata=metadata)
1047 tracker = None if track is False else msg['tracker']
1048
1049 ar = AsyncResult(self.client, msg['header']['msg_id'], fname=getname(f),
1050 targets=None, tracker=tracker, owner=True,
1051 )
1052 if block:
1053 try:
1054 return ar.get()
1055 except KeyboardInterrupt:
1056 pass
1057 return ar
1058
1059 @sync_results
1060 @save_ids
1061 def map(self, f, *sequences, **kwargs):
1062 """``view.map(f, *sequences, block=self.block, chunksize=1, ordered=True)`` => list|AsyncMapResult
1063
1064 Parallel version of builtin `map`, load-balanced by this View.
1065
1066 `block`, and `chunksize` can be specified by keyword only.
1067
1068 Each `chunksize` elements will be a separate task, and will be
1069 load-balanced. This lets individual elements be available for iteration
1070 as soon as they arrive.
1071
1072 Parameters
1073 ----------
1074
1075 f : callable
1076 function to be mapped
1077 *sequences: one or more sequences of matching length
1078 the sequences to be distributed and passed to `f`
1079 block : bool [default self.block]
1080 whether to wait for the result or not
1081 track : bool
1082 whether to create a MessageTracker to allow the user to
1083 safely edit after arrays and buffers during non-copying
1084 sends.
1085 chunksize : int [default 1]
1086 how many elements should be in each task.
1087 ordered : bool [default True]
1088 Whether the results should be gathered as they arrive, or enforce
1089 the order of submission.
1090
1091 Only applies when iterating through AsyncMapResult as results arrive.
1092 Has no effect when block=True.
1093
1094 Returns
1095 -------
1096
1097 if block=False
1098 An :class:`~ipython_parallel.client.asyncresult.AsyncMapResult` instance.
1099 An object like AsyncResult, but which reassembles the sequence of results
1100 into a single list. AsyncMapResults can be iterated through before all
1101 results are complete.
1102 else
1103 A list, the result of ``map(f,*sequences)``
1104 """
1105
1106 # default
1107 block = kwargs.get('block', self.block)
1108 chunksize = kwargs.get('chunksize', 1)
1109 ordered = kwargs.get('ordered', True)
1110
1111 keyset = set(kwargs.keys())
1112 extra_keys = keyset.difference_update(set(['block', 'chunksize']))
1113 if extra_keys:
1114 raise TypeError("Invalid kwargs: %s"%list(extra_keys))
1115
1116 assert len(sequences) > 0, "must have some sequences to map onto!"
1117
1118 pf = ParallelFunction(self, f, block=block, chunksize=chunksize, ordered=ordered)
1119 return pf.map(*sequences)
1120
1121 __all__ = ['LoadBalancedView', 'DirectView']
@@ -1,3 +0,0 b''
1 if __name__ == '__main__':
2 from ipython_parallel.apps import ipclusterapp as app
3 app.launch_new_instance()
1 NO CONTENT: file was removed
NO CONTENT: file was removed
@@ -1,6 +0,0 b''
1 def main():
2 from ipython_parallel.apps import ipcontrollerapp as app
3 app.launch_new_instance()
4
5 if __name__ == '__main__':
6 main()
@@ -1,229 +0,0 b''
1 """Dependency utilities
2
3 Authors:
4
5 * Min RK
6 """
7 #-----------------------------------------------------------------------------
8 # Copyright (C) 2013 The IPython Development Team
9 #
10 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING, distributed as part of this software.
12 #-----------------------------------------------------------------------------
13
14 from types import ModuleType
15
16 from ipython_parallel.client.asyncresult import AsyncResult
17 from ipython_parallel.error import UnmetDependency
18 from ipython_parallel.util import interactive
19 from IPython.utils import py3compat
20 from IPython.utils.py3compat import string_types
21 from ipython_kernel.pickleutil import can, uncan
22
23 class depend(object):
24 """Dependency decorator, for use with tasks.
25
26 `@depend` lets you define a function for engine dependencies
27 just like you use `apply` for tasks.
28
29
30 Examples
31 --------
32 ::
33
34 @depend(df, a,b, c=5)
35 def f(m,n,p)
36
37 view.apply(f, 1,2,3)
38
39 will call df(a,b,c=5) on the engine, and if it returns False or
40 raises an UnmetDependency error, then the task will not be run
41 and another engine will be tried.
42 """
43 def __init__(self, _wrapped_f, *args, **kwargs):
44 self.f = _wrapped_f
45 self.args = args
46 self.kwargs = kwargs
47
48 def __call__(self, f):
49 return dependent(f, self.f, *self.args, **self.kwargs)
50
51 class dependent(object):
52 """A function that depends on another function.
53 This is an object to prevent the closure used
54 in traditional decorators, which are not picklable.
55 """
56
57 def __init__(self, _wrapped_f, _wrapped_df, *dargs, **dkwargs):
58 self.f = _wrapped_f
59 name = getattr(_wrapped_f, '__name__', 'f')
60 if py3compat.PY3:
61 self.__name__ = name
62 else:
63 self.func_name = name
64 self.df = _wrapped_df
65 self.dargs = dargs
66 self.dkwargs = dkwargs
67
68 def check_dependency(self):
69 if self.df(*self.dargs, **self.dkwargs) is False:
70 raise UnmetDependency()
71
72 def __call__(self, *args, **kwargs):
73 return self.f(*args, **kwargs)
74
75 if not py3compat.PY3:
76 @property
77 def __name__(self):
78 return self.func_name
79
80 @interactive
81 def _require(*modules, **mapping):
82 """Helper for @require decorator."""
83 from ipython_parallel.error import UnmetDependency
84 from ipython_kernel.pickleutil import uncan
85 user_ns = globals()
86 for name in modules:
87 try:
88 exec('import %s' % name, user_ns)
89 except ImportError:
90 raise UnmetDependency(name)
91
92 for name, cobj in mapping.items():
93 user_ns[name] = uncan(cobj, user_ns)
94 return True
95
96 def require(*objects, **mapping):
97 """Simple decorator for requiring local objects and modules to be available
98 when the decorated function is called on the engine.
99
100 Modules specified by name or passed directly will be imported
101 prior to calling the decorated function.
102
103 Objects other than modules will be pushed as a part of the task.
104 Functions can be passed positionally,
105 and will be pushed to the engine with their __name__.
106 Other objects can be passed by keyword arg.
107
108 Examples::
109
110 In [1]: @require('numpy')
111 ...: def norm(a):
112 ...: return numpy.linalg.norm(a,2)
113
114 In [2]: foo = lambda x: x*x
115 In [3]: @require(foo)
116 ...: def bar(a):
117 ...: return foo(1-a)
118 """
119 names = []
120 for obj in objects:
121 if isinstance(obj, ModuleType):
122 obj = obj.__name__
123
124 if isinstance(obj, string_types):
125 names.append(obj)
126 elif hasattr(obj, '__name__'):
127 mapping[obj.__name__] = obj
128 else:
129 raise TypeError("Objects other than modules and functions "
130 "must be passed by kwarg, but got: %s" % type(obj)
131 )
132
133 for name, obj in mapping.items():
134 mapping[name] = can(obj)
135 return depend(_require, *names, **mapping)
136
137 class Dependency(set):
138 """An object for representing a set of msg_id dependencies.
139
140 Subclassed from set().
141
142 Parameters
143 ----------
144 dependencies: list/set of msg_ids or AsyncResult objects or output of Dependency.as_dict()
145 The msg_ids to depend on
146 all : bool [default True]
147 Whether the dependency should be considered met when *all* depending tasks have completed
148 or only when *any* have been completed.
149 success : bool [default True]
150 Whether to consider successes as fulfilling dependencies.
151 failure : bool [default False]
152 Whether to consider failures as fulfilling dependencies.
153
154 If `all=success=True` and `failure=False`, then the task will fail with an ImpossibleDependency
155 as soon as the first depended-upon task fails.
156 """
157
158 all=True
159 success=True
160 failure=True
161
162 def __init__(self, dependencies=[], all=True, success=True, failure=False):
163 if isinstance(dependencies, dict):
164 # load from dict
165 all = dependencies.get('all', True)
166 success = dependencies.get('success', success)
167 failure = dependencies.get('failure', failure)
168 dependencies = dependencies.get('dependencies', [])
169 ids = []
170
171 # extract ids from various sources:
172 if isinstance(dependencies, string_types + (AsyncResult,)):
173 dependencies = [dependencies]
174 for d in dependencies:
175 if isinstance(d, string_types):
176 ids.append(d)
177 elif isinstance(d, AsyncResult):
178 ids.extend(d.msg_ids)
179 else:
180 raise TypeError("invalid dependency type: %r"%type(d))
181
182 set.__init__(self, ids)
183 self.all = all
184 if not (success or failure):
185 raise ValueError("Must depend on at least one of successes or failures!")
186 self.success=success
187 self.failure = failure
188
189 def check(self, completed, failed=None):
190 """check whether our dependencies have been met."""
191 if len(self) == 0:
192 return True
193 against = set()
194 if self.success:
195 against = completed
196 if failed is not None and self.failure:
197 against = against.union(failed)
198 if self.all:
199 return self.issubset(against)
200 else:
201 return not self.isdisjoint(against)
202
203 def unreachable(self, completed, failed=None):
204 """return whether this dependency has become impossible."""
205 if len(self) == 0:
206 return False
207 against = set()
208 if not self.success:
209 against = completed
210 if failed is not None and not self.failure:
211 against = against.union(failed)
212 if self.all:
213 return not self.isdisjoint(against)
214 else:
215 return self.issubset(against)
216
217
218 def as_dict(self):
219 """Represent this dependency as a dict. For json compatibility."""
220 return dict(
221 dependencies=list(self),
222 all=self.all,
223 success=self.success,
224 failure=self.failure
225 )
226
227
228 __all__ = ['depend', 'require', 'dependent', 'Dependency']
229
@@ -1,318 +0,0 b''
1 """A Task logger that presents our DB interface,
2 but exists entirely in memory and implemented with dicts.
3
4
5 TaskRecords are dicts of the form::
6
7 {
8 'msg_id' : str(uuid),
9 'client_uuid' : str(uuid),
10 'engine_uuid' : str(uuid) or None,
11 'header' : dict(header),
12 'content': dict(content),
13 'buffers': list(buffers),
14 'submitted': datetime or None,
15 'started': datetime or None,
16 'completed': datetime or None,
17 'received': datetime or None,
18 'resubmitted': str(uuid) or None,
19 'result_header' : dict(header) or None,
20 'result_content' : dict(content) or None,
21 'result_buffers' : list(buffers) or None,
22 }
23
24 With this info, many of the special categories of tasks can be defined by query,
25 e.g.:
26
27 * pending: completed is None
28 * client's outstanding: client_uuid = uuid && completed is None
29 * MIA: arrived is None (and completed is None)
30
31 DictDB supports a subset of mongodb operators::
32
33 $lt,$gt,$lte,$gte,$ne,$in,$nin,$all,$mod,$exists
34 """
35
36 # Copyright (c) IPython Development Team.
37 # Distributed under the terms of the Modified BSD License.
38
39 import copy
40 from copy import deepcopy
41
42 # Python can't copy memoryviews, but creating another memoryview works for us
43 copy._deepcopy_dispatch[memoryview] = lambda x, memo: memoryview(x)
44
45
46 from datetime import datetime
47
48 from IPython.config.configurable import LoggingConfigurable
49
50 from IPython.utils.py3compat import iteritems, itervalues
51 from IPython.utils.traitlets import Dict, Unicode, Integer, Float
52
53 filters = {
54 '$lt' : lambda a,b: a < b,
55 '$gt' : lambda a,b: b > a,
56 '$eq' : lambda a,b: a == b,
57 '$ne' : lambda a,b: a != b,
58 '$lte': lambda a,b: a <= b,
59 '$gte': lambda a,b: a >= b,
60 '$in' : lambda a,b: a in b,
61 '$nin': lambda a,b: a not in b,
62 '$all': lambda a,b: all([ a in bb for bb in b ]),
63 '$mod': lambda a,b: a%b[0] == b[1],
64 '$exists' : lambda a,b: (b and a is not None) or (a is None and not b)
65 }
66
67
68 class CompositeFilter(object):
69 """Composite filter for matching multiple properties."""
70
71 def __init__(self, dikt):
72 self.tests = []
73 self.values = []
74 for key, value in iteritems(dikt):
75 self.tests.append(filters[key])
76 self.values.append(value)
77
78 def __call__(self, value):
79 for test,check in zip(self.tests, self.values):
80 if not test(value, check):
81 return False
82 return True
83
84 class BaseDB(LoggingConfigurable):
85 """Empty Parent class so traitlets work on DB."""
86 # base configurable traits:
87 session = Unicode("")
88
89 class DictDB(BaseDB):
90 """Basic in-memory dict-based object for saving Task Records.
91
92 This is the first object to present the DB interface
93 for logging tasks out of memory.
94
95 The interface is based on MongoDB, so adding a MongoDB
96 backend should be straightforward.
97 """
98
99 _records = Dict()
100 _culled_ids = set() # set of ids which have been culled
101 _buffer_bytes = Integer(0) # running total of the bytes in the DB
102
103 size_limit = Integer(1024**3, config=True,
104 help="""The maximum total size (in bytes) of the buffers stored in the db
105
106 When the db exceeds this size, the oldest records will be culled until
107 the total size is under size_limit * (1-cull_fraction).
108 default: 1 GB
109 """
110 )
111 record_limit = Integer(1024, config=True,
112 help="""The maximum number of records in the db
113
114 When the history exceeds this size, the first record_limit * cull_fraction
115 records will be culled.
116 """
117 )
118 cull_fraction = Float(0.1, config=True,
119 help="""The fraction by which the db should culled when one of the limits is exceeded
120
121 In general, the db size will spend most of its time with a size in the range:
122
123 [limit * (1-cull_fraction), limit]
124
125 for each of size_limit and record_limit.
126 """
127 )
128
129 def _match_one(self, rec, tests):
130 """Check if a specific record matches tests."""
131 for key,test in iteritems(tests):
132 if not test(rec.get(key, None)):
133 return False
134 return True
135
136 def _match(self, check):
137 """Find all the matches for a check dict."""
138 matches = []
139 tests = {}
140 for k,v in iteritems(check):
141 if isinstance(v, dict):
142 tests[k] = CompositeFilter(v)
143 else:
144 tests[k] = lambda o: o==v
145
146 for rec in itervalues(self._records):
147 if self._match_one(rec, tests):
148 matches.append(deepcopy(rec))
149 return matches
150
151 def _extract_subdict(self, rec, keys):
152 """extract subdict of keys"""
153 d = {}
154 d['msg_id'] = rec['msg_id']
155 for key in keys:
156 d[key] = rec[key]
157 return deepcopy(d)
158
159 # methods for monitoring size / culling history
160
161 def _add_bytes(self, rec):
162 for key in ('buffers', 'result_buffers'):
163 for buf in rec.get(key) or []:
164 self._buffer_bytes += len(buf)
165
166 self._maybe_cull()
167
168 def _drop_bytes(self, rec):
169 for key in ('buffers', 'result_buffers'):
170 for buf in rec.get(key) or []:
171 self._buffer_bytes -= len(buf)
172
173 def _cull_oldest(self, n=1):
174 """cull the oldest N records"""
175 for msg_id in self.get_history()[:n]:
176 self.log.debug("Culling record: %r", msg_id)
177 self._culled_ids.add(msg_id)
178 self.drop_record(msg_id)
179
180 def _maybe_cull(self):
181 # cull by count:
182 if len(self._records) > self.record_limit:
183 to_cull = int(self.cull_fraction * self.record_limit)
184 self.log.info("%i records exceeds limit of %i, culling oldest %i",
185 len(self._records), self.record_limit, to_cull
186 )
187 self._cull_oldest(to_cull)
188
189 # cull by size:
190 if self._buffer_bytes > self.size_limit:
191 limit = self.size_limit * (1 - self.cull_fraction)
192
193 before = self._buffer_bytes
194 before_count = len(self._records)
195 culled = 0
196 while self._buffer_bytes > limit:
197 self._cull_oldest(1)
198 culled += 1
199
200 self.log.info("%i records with total buffer size %i exceeds limit: %i. Culled oldest %i records.",
201 before_count, before, self.size_limit, culled
202 )
203
204 def _check_dates(self, rec):
205 for key in ('submitted', 'started', 'completed'):
206 value = rec.get(key, None)
207 if value is not None and not isinstance(value, datetime):
208 raise ValueError("%s must be None or datetime, not %r" % (key, value))
209
210 # public API methods:
211
212 def add_record(self, msg_id, rec):
213 """Add a new Task Record, by msg_id."""
214 if msg_id in self._records:
215 raise KeyError("Already have msg_id %r"%(msg_id))
216 self._check_dates(rec)
217 self._records[msg_id] = rec
218 self._add_bytes(rec)
219 self._maybe_cull()
220
221 def get_record(self, msg_id):
222 """Get a specific Task Record, by msg_id."""
223 if msg_id in self._culled_ids:
224 raise KeyError("Record %r has been culled for size" % msg_id)
225 if not msg_id in self._records:
226 raise KeyError("No such msg_id %r"%(msg_id))
227 return deepcopy(self._records[msg_id])
228
229 def update_record(self, msg_id, rec):
230 """Update the data in an existing record."""
231 if msg_id in self._culled_ids:
232 raise KeyError("Record %r has been culled for size" % msg_id)
233 self._check_dates(rec)
234 _rec = self._records[msg_id]
235 self._drop_bytes(_rec)
236 _rec.update(rec)
237 self._add_bytes(_rec)
238
239 def drop_matching_records(self, check):
240 """Remove a record from the DB."""
241 matches = self._match(check)
242 for rec in matches:
243 self._drop_bytes(rec)
244 del self._records[rec['msg_id']]
245
246 def drop_record(self, msg_id):
247 """Remove a record from the DB."""
248 rec = self._records[msg_id]
249 self._drop_bytes(rec)
250 del self._records[msg_id]
251
252 def find_records(self, check, keys=None):
253 """Find records matching a query dict, optionally extracting subset of keys.
254
255 Returns dict keyed by msg_id of matching records.
256
257 Parameters
258 ----------
259
260 check: dict
261 mongodb-style query argument
262 keys: list of strs [optional]
263 if specified, the subset of keys to extract. msg_id will *always* be
264 included.
265 """
266 matches = self._match(check)
267 if keys:
268 return [ self._extract_subdict(rec, keys) for rec in matches ]
269 else:
270 return matches
271
272 def get_history(self):
273 """get all msg_ids, ordered by time submitted."""
274 msg_ids = self._records.keys()
275 # Remove any that do not have a submitted timestamp.
276 # This is extremely unlikely to happen,
277 # but it seems to come up in some tests on VMs.
278 msg_ids = [ m for m in msg_ids if self._records[m]['submitted'] is not None ]
279 return sorted(msg_ids, key=lambda m: self._records[m]['submitted'])
280
281
282 class NoData(KeyError):
283 """Special KeyError to raise when requesting data from NoDB"""
284 def __str__(self):
285 return "NoDB backend doesn't store any data. "
286 "Start the Controller with a DB backend to enable resubmission / result persistence."
287
288
289 class NoDB(BaseDB):
290 """A blackhole db backend that actually stores no information.
291
292 Provides the full DB interface, but raises KeyErrors on any
293 method that tries to access the records. This can be used to
294 minimize the memory footprint of the Hub when its record-keeping
295 functionality is not required.
296 """
297
298 def add_record(self, msg_id, record):
299 pass
300
301 def get_record(self, msg_id):
302 raise NoData()
303
304 def update_record(self, msg_id, record):
305 pass
306
307 def drop_matching_records(self, check):
308 pass
309
310 def drop_record(self, msg_id):
311 pass
312
313 def find_records(self, check, keys=None):
314 raise NoData()
315
316 def get_history(self):
317 raise NoData()
318
@@ -1,193 +0,0 b''
1 #!/usr/bin/env python
2 """
3 A multi-heart Heartbeat system using PUB and ROUTER sockets. pings are sent out on the PUB,
4 and hearts are tracked based on their DEALER identities.
5 """
6
7 # Copyright (c) IPython Development Team.
8 # Distributed under the terms of the Modified BSD License.
9
10 from __future__ import print_function
11 import time
12 import uuid
13
14 import zmq
15 from zmq.devices import ThreadDevice, ThreadMonitoredQueue
16 from zmq.eventloop import ioloop, zmqstream
17
18 from IPython.config.configurable import LoggingConfigurable
19 from IPython.utils.py3compat import str_to_bytes
20 from IPython.utils.traitlets import Set, Instance, CFloat, Integer, Dict, Bool
21
22 from ipython_parallel.util import log_errors
23
24 class Heart(object):
25 """A basic heart object for responding to a HeartMonitor.
26 This is a simple wrapper with defaults for the most common
27 Device model for responding to heartbeats.
28
29 It simply builds a threadsafe zmq.FORWARDER Device, defaulting to using
30 SUB/DEALER for in/out.
31
32 You can specify the DEALER's IDENTITY via the optional heart_id argument."""
33 device=None
34 id=None
35 def __init__(self, in_addr, out_addr, mon_addr=None, in_type=zmq.SUB, out_type=zmq.DEALER, mon_type=zmq.PUB, heart_id=None):
36 if mon_addr is None:
37 self.device = ThreadDevice(zmq.FORWARDER, in_type, out_type)
38 else:
39 self.device = ThreadMonitoredQueue(in_type, out_type, mon_type, in_prefix=b"", out_prefix=b"")
40 # do not allow the device to share global Context.instance,
41 # which is the default behavior in pyzmq > 2.1.10
42 self.device.context_factory = zmq.Context
43
44 self.device.daemon=True
45 self.device.connect_in(in_addr)
46 self.device.connect_out(out_addr)
47 if mon_addr is not None:
48 self.device.connect_mon(mon_addr)
49 if in_type == zmq.SUB:
50 self.device.setsockopt_in(zmq.SUBSCRIBE, b"")
51 if heart_id is None:
52 heart_id = uuid.uuid4().bytes
53 self.device.setsockopt_out(zmq.IDENTITY, heart_id)
54 self.id = heart_id
55
56 def start(self):
57 return self.device.start()
58
59
60 class HeartMonitor(LoggingConfigurable):
61 """A basic HeartMonitor class
62 pingstream: a PUB stream
63 pongstream: an ROUTER stream
64 period: the period of the heartbeat in milliseconds"""
65
66 debug = Bool(False, config=True,
67 help="""Whether to include every heartbeat in debugging output.
68
69 Has to be set explicitly, because there will be *a lot* of output.
70 """
71 )
72 period = Integer(3000, config=True,
73 help='The frequency at which the Hub pings the engines for heartbeats '
74 '(in ms)',
75 )
76 max_heartmonitor_misses = Integer(10, config=True,
77 help='Allowed consecutive missed pings from controller Hub to engine before unregistering.',
78 )
79
80 pingstream=Instance('zmq.eventloop.zmqstream.ZMQStream', allow_none=True)
81 pongstream=Instance('zmq.eventloop.zmqstream.ZMQStream', allow_none=True)
82 loop = Instance('zmq.eventloop.ioloop.IOLoop')
83 def _loop_default(self):
84 return ioloop.IOLoop.instance()
85
86 # not settable:
87 hearts=Set()
88 responses=Set()
89 on_probation=Dict()
90 last_ping=CFloat(0)
91 _new_handlers = Set()
92 _failure_handlers = Set()
93 lifetime = CFloat(0)
94 tic = CFloat(0)
95
96 def __init__(self, **kwargs):
97 super(HeartMonitor, self).__init__(**kwargs)
98
99 self.pongstream.on_recv(self.handle_pong)
100
101 def start(self):
102 self.tic = time.time()
103 self.caller = ioloop.PeriodicCallback(self.beat, self.period, self.loop)
104 self.caller.start()
105
106 def add_new_heart_handler(self, handler):
107 """add a new handler for new hearts"""
108 self.log.debug("heartbeat::new_heart_handler: %s", handler)
109 self._new_handlers.add(handler)
110
111 def add_heart_failure_handler(self, handler):
112 """add a new handler for heart failure"""
113 self.log.debug("heartbeat::new heart failure handler: %s", handler)
114 self._failure_handlers.add(handler)
115
116 def beat(self):
117 self.pongstream.flush()
118 self.last_ping = self.lifetime
119
120 toc = time.time()
121 self.lifetime += toc-self.tic
122 self.tic = toc
123 if self.debug:
124 self.log.debug("heartbeat::sending %s", self.lifetime)
125 goodhearts = self.hearts.intersection(self.responses)
126 missed_beats = self.hearts.difference(goodhearts)
127 newhearts = self.responses.difference(goodhearts)
128 for heart in newhearts:
129 self.handle_new_heart(heart)
130 heartfailures, on_probation = self._check_missed(missed_beats, self.on_probation,
131 self.hearts)
132 for failure in heartfailures:
133 self.handle_heart_failure(failure)
134 self.on_probation = on_probation
135 self.responses = set()
136 #print self.on_probation, self.hearts
137 # self.log.debug("heartbeat::beat %.3f, %i beating hearts", self.lifetime, len(self.hearts))
138 self.pingstream.send(str_to_bytes(str(self.lifetime)))
139 # flush stream to force immediate socket send
140 self.pingstream.flush()
141
142 def _check_missed(self, missed_beats, on_probation, hearts):
143 """Update heartbeats on probation, identifying any that have too many misses.
144 """
145 failures = []
146 new_probation = {}
147 for cur_heart in (b for b in missed_beats if b in hearts):
148 miss_count = on_probation.get(cur_heart, 0) + 1
149 self.log.info("heartbeat::missed %s : %s" % (cur_heart, miss_count))
150 if miss_count > self.max_heartmonitor_misses:
151 failures.append(cur_heart)
152 else:
153 new_probation[cur_heart] = miss_count
154 return failures, new_probation
155
156 def handle_new_heart(self, heart):
157 if self._new_handlers:
158 for handler in self._new_handlers:
159 handler(heart)
160 else:
161 self.log.info("heartbeat::yay, got new heart %s!", heart)
162 self.hearts.add(heart)
163
164 def handle_heart_failure(self, heart):
165 if self._failure_handlers:
166 for handler in self._failure_handlers:
167 try:
168 handler(heart)
169 except Exception as e:
170 self.log.error("heartbeat::Bad Handler! %s", handler, exc_info=True)
171 pass
172 else:
173 self.log.info("heartbeat::Heart %s failed :(", heart)
174 self.hearts.remove(heart)
175
176
177 @log_errors
178 def handle_pong(self, msg):
179 "a heart just beat"
180 current = str_to_bytes(str(self.lifetime))
181 last = str_to_bytes(str(self.last_ping))
182 if msg[1] == current:
183 delta = time.time()-self.tic
184 if self.debug:
185 self.log.debug("heartbeat::heart %r took %.2f ms to respond", msg[0], 1000*delta)
186 self.responses.add(msg[0])
187 elif msg[1] == last:
188 delta = time.time()-self.tic + (self.lifetime-self.last_ping)
189 self.log.warn("heartbeat::heart %r missed a beat, and took %.2f ms to respond", msg[0], 1000*delta)
190 self.responses.add(msg[0])
191 else:
192 self.log.warn("heartbeat::got bad heartbeat (possibly old?): %s (current=%.3f)", msg[1], self.lifetime)
193
This diff has been collapsed as it changes many lines, (1438 lines changed) Show them Hide them
@@ -1,1438 +0,0 b''
1 """The IPython Controller Hub with 0MQ
2
3 This is the master object that handles connections from engines and clients,
4 and monitors traffic through the various queues.
5 """
6
7 # Copyright (c) IPython Development Team.
8 # Distributed under the terms of the Modified BSD License.
9
10 from __future__ import print_function
11
12 import json
13 import os
14 import sys
15 import time
16 from datetime import datetime
17
18 import zmq
19 from zmq.eventloop.zmqstream import ZMQStream
20
21 # internal:
22 from IPython.utils.importstring import import_item
23 from jupyter_client.jsonutil import extract_dates
24 from IPython.utils.localinterfaces import localhost
25 from IPython.utils.py3compat import cast_bytes, unicode_type, iteritems
26 from IPython.utils.traitlets import (
27 HasTraits, Any, Instance, Integer, Unicode, Dict, Set, Tuple, DottedObjectName
28 )
29
30 from ipython_parallel import error, util
31 from ipython_parallel.factory import RegistrationFactory
32
33 from IPython.kernel.zmq.session import SessionFactory
34
35 from .heartmonitor import HeartMonitor
36
37
38 def _passer(*args, **kwargs):
39 return
40
41 def _printer(*args, **kwargs):
42 print (args)
43 print (kwargs)
44
45 def empty_record():
46 """Return an empty dict with all record keys."""
47 return {
48 'msg_id' : None,
49 'header' : None,
50 'metadata' : None,
51 'content': None,
52 'buffers': None,
53 'submitted': None,
54 'client_uuid' : None,
55 'engine_uuid' : None,
56 'started': None,
57 'completed': None,
58 'resubmitted': None,
59 'received': None,
60 'result_header' : None,
61 'result_metadata' : None,
62 'result_content' : None,
63 'result_buffers' : None,
64 'queue' : None,
65 'execute_input' : None,
66 'execute_result': None,
67 'error': None,
68 'stdout': '',
69 'stderr': '',
70 }
71
72 def init_record(msg):
73 """Initialize a TaskRecord based on a request."""
74 header = msg['header']
75 return {
76 'msg_id' : header['msg_id'],
77 'header' : header,
78 'content': msg['content'],
79 'metadata': msg['metadata'],
80 'buffers': msg['buffers'],
81 'submitted': header['date'],
82 'client_uuid' : None,
83 'engine_uuid' : None,
84 'started': None,
85 'completed': None,
86 'resubmitted': None,
87 'received': None,
88 'result_header' : None,
89 'result_metadata': None,
90 'result_content' : None,
91 'result_buffers' : None,
92 'queue' : None,
93 'execute_input' : None,
94 'execute_result': None,
95 'error': None,
96 'stdout': '',
97 'stderr': '',
98 }
99
100
101 class EngineConnector(HasTraits):
102 """A simple object for accessing the various zmq connections of an object.
103 Attributes are:
104 id (int): engine ID
105 uuid (unicode): engine UUID
106 pending: set of msg_ids
107 stallback: tornado timeout for stalled registration
108 """
109
110 id = Integer(0)
111 uuid = Unicode()
112 pending = Set()
113 stallback = Any()
114
115
116 _db_shortcuts = {
117 'sqlitedb' : 'ipython_parallel.controller.sqlitedb.SQLiteDB',
118 'mongodb' : 'ipython_parallel.controller.mongodb.MongoDB',
119 'dictdb' : 'ipython_parallel.controller.dictdb.DictDB',
120 'nodb' : 'ipython_parallel.controller.dictdb.NoDB',
121 }
122
123 class HubFactory(RegistrationFactory):
124 """The Configurable for setting up a Hub."""
125
126 # port-pairs for monitoredqueues:
127 hb = Tuple(Integer,Integer,config=True,
128 help="""PUB/ROUTER Port pair for Engine heartbeats""")
129 def _hb_default(self):
130 return tuple(util.select_random_ports(2))
131
132 mux = Tuple(Integer,Integer,config=True,
133 help="""Client/Engine Port pair for MUX queue""")
134
135 def _mux_default(self):
136 return tuple(util.select_random_ports(2))
137
138 task = Tuple(Integer,Integer,config=True,
139 help="""Client/Engine Port pair for Task queue""")
140 def _task_default(self):
141 return tuple(util.select_random_ports(2))
142
143 control = Tuple(Integer,Integer,config=True,
144 help="""Client/Engine Port pair for Control queue""")
145
146 def _control_default(self):
147 return tuple(util.select_random_ports(2))
148
149 iopub = Tuple(Integer,Integer,config=True,
150 help="""Client/Engine Port pair for IOPub relay""")
151
152 def _iopub_default(self):
153 return tuple(util.select_random_ports(2))
154
155 # single ports:
156 mon_port = Integer(config=True,
157 help="""Monitor (SUB) port for queue traffic""")
158
159 def _mon_port_default(self):
160 return util.select_random_ports(1)[0]
161
162 notifier_port = Integer(config=True,
163 help="""PUB port for sending engine status notifications""")
164
165 def _notifier_port_default(self):
166 return util.select_random_ports(1)[0]
167
168 engine_ip = Unicode(config=True,
169 help="IP on which to listen for engine connections. [default: loopback]")
170 def _engine_ip_default(self):
171 return localhost()
172 engine_transport = Unicode('tcp', config=True,
173 help="0MQ transport for engine connections. [default: tcp]")
174
175 client_ip = Unicode(config=True,
176 help="IP on which to listen for client connections. [default: loopback]")
177 client_transport = Unicode('tcp', config=True,
178 help="0MQ transport for client connections. [default : tcp]")
179
180 monitor_ip = Unicode(config=True,
181 help="IP on which to listen for monitor messages. [default: loopback]")
182 monitor_transport = Unicode('tcp', config=True,
183 help="0MQ transport for monitor messages. [default : tcp]")
184
185 _client_ip_default = _monitor_ip_default = _engine_ip_default
186
187
188 monitor_url = Unicode('')
189
190 db_class = DottedObjectName('NoDB',
191 config=True, help="""The class to use for the DB backend
192
193 Options include:
194
195 SQLiteDB: SQLite
196 MongoDB : use MongoDB
197 DictDB : in-memory storage (fastest, but be mindful of memory growth of the Hub)
198 NoDB : disable database altogether (default)
199
200 """)
201
202 registration_timeout = Integer(0, config=True,
203 help="Engine registration timeout in seconds [default: max(30,"
204 "10*heartmonitor.period)]" )
205
206 def _registration_timeout_default(self):
207 if self.heartmonitor is None:
208 # early initialization, this value will be ignored
209 return 0
210 # heartmonitor period is in milliseconds, so 10x in seconds is .01
211 return max(30, int(.01 * self.heartmonitor.period))
212
213 # not configurable
214 db = Instance('ipython_parallel.controller.dictdb.BaseDB', allow_none=True)
215 heartmonitor = Instance('ipython_parallel.controller.heartmonitor.HeartMonitor', allow_none=True)
216
217 def _ip_changed(self, name, old, new):
218 self.engine_ip = new
219 self.client_ip = new
220 self.monitor_ip = new
221 self._update_monitor_url()
222
223 def _update_monitor_url(self):
224 self.monitor_url = "%s://%s:%i" % (self.monitor_transport, self.monitor_ip, self.mon_port)
225
226 def _transport_changed(self, name, old, new):
227 self.engine_transport = new
228 self.client_transport = new
229 self.monitor_transport = new
230 self._update_monitor_url()
231
232 def __init__(self, **kwargs):
233 super(HubFactory, self).__init__(**kwargs)
234 self._update_monitor_url()
235
236
237 def construct(self):
238 self.init_hub()
239
240 def start(self):
241 self.heartmonitor.start()
242 self.log.info("Heartmonitor started")
243
244 def client_url(self, channel):
245 """return full zmq url for a named client channel"""
246 return "%s://%s:%i" % (self.client_transport, self.client_ip, self.client_info[channel])
247
248 def engine_url(self, channel):
249 """return full zmq url for a named engine channel"""
250 return "%s://%s:%i" % (self.engine_transport, self.engine_ip, self.engine_info[channel])
251
252 def init_hub(self):
253 """construct Hub object"""
254
255 ctx = self.context
256 loop = self.loop
257 if 'TaskScheduler.scheme_name' in self.config:
258 scheme = self.config.TaskScheduler.scheme_name
259 else:
260 from .scheduler import TaskScheduler
261 scheme = TaskScheduler.scheme_name.get_default_value()
262
263 # build connection dicts
264 engine = self.engine_info = {
265 'interface' : "%s://%s" % (self.engine_transport, self.engine_ip),
266 'registration' : self.regport,
267 'control' : self.control[1],
268 'mux' : self.mux[1],
269 'hb_ping' : self.hb[0],
270 'hb_pong' : self.hb[1],
271 'task' : self.task[1],
272 'iopub' : self.iopub[1],
273 }
274
275 client = self.client_info = {
276 'interface' : "%s://%s" % (self.client_transport, self.client_ip),
277 'registration' : self.regport,
278 'control' : self.control[0],
279 'mux' : self.mux[0],
280 'task' : self.task[0],
281 'task_scheme' : scheme,
282 'iopub' : self.iopub[0],
283 'notification' : self.notifier_port,
284 }
285
286 self.log.debug("Hub engine addrs: %s", self.engine_info)
287 self.log.debug("Hub client addrs: %s", self.client_info)
288
289 # Registrar socket
290 q = ZMQStream(ctx.socket(zmq.ROUTER), loop)
291 util.set_hwm(q, 0)
292 q.bind(self.client_url('registration'))
293 self.log.info("Hub listening on %s for registration.", self.client_url('registration'))
294 if self.client_ip != self.engine_ip:
295 q.bind(self.engine_url('registration'))
296 self.log.info("Hub listening on %s for registration.", self.engine_url('registration'))
297
298 ### Engine connections ###
299
300 # heartbeat
301 hpub = ctx.socket(zmq.PUB)
302 hpub.bind(self.engine_url('hb_ping'))
303 hrep = ctx.socket(zmq.ROUTER)
304 util.set_hwm(hrep, 0)
305 hrep.bind(self.engine_url('hb_pong'))
306 self.heartmonitor = HeartMonitor(loop=loop, parent=self, log=self.log,
307 pingstream=ZMQStream(hpub,loop),
308 pongstream=ZMQStream(hrep,loop)
309 )
310
311 ### Client connections ###
312
313 # Notifier socket
314 n = ZMQStream(ctx.socket(zmq.PUB), loop)
315 n.bind(self.client_url('notification'))
316
317 ### build and launch the queues ###
318
319 # monitor socket
320 sub = ctx.socket(zmq.SUB)
321 sub.setsockopt(zmq.SUBSCRIBE, b"")
322 sub.bind(self.monitor_url)
323 sub.bind('inproc://monitor')
324 sub = ZMQStream(sub, loop)
325
326 # connect the db
327 db_class = _db_shortcuts.get(self.db_class.lower(), self.db_class)
328 self.log.info('Hub using DB backend: %r', (db_class.split('.')[-1]))
329 self.db = import_item(str(db_class))(session=self.session.session,
330 parent=self, log=self.log)
331 time.sleep(.25)
332
333 # resubmit stream
334 r = ZMQStream(ctx.socket(zmq.DEALER), loop)
335 url = util.disambiguate_url(self.client_url('task'))
336 r.connect(url)
337
338 self.hub = Hub(loop=loop, session=self.session, monitor=sub, heartmonitor=self.heartmonitor,
339 query=q, notifier=n, resubmit=r, db=self.db,
340 engine_info=self.engine_info, client_info=self.client_info,
341 log=self.log, registration_timeout=self.registration_timeout)
342
343
344 class Hub(SessionFactory):
345 """The IPython Controller Hub with 0MQ connections
346
347 Parameters
348 ==========
349 loop: zmq IOLoop instance
350 session: Session object
351 <removed> context: zmq context for creating new connections (?)
352 queue: ZMQStream for monitoring the command queue (SUB)
353 query: ZMQStream for engine registration and client queries requests (ROUTER)
354 heartbeat: HeartMonitor object checking the pulse of the engines
355 notifier: ZMQStream for broadcasting engine registration changes (PUB)
356 db: connection to db for out of memory logging of commands
357 NotImplemented
358 engine_info: dict of zmq connection information for engines to connect
359 to the queues.
360 client_info: dict of zmq connection information for engines to connect
361 to the queues.
362 """
363
364 engine_state_file = Unicode()
365
366 # internal data structures:
367 ids=Set() # engine IDs
368 keytable=Dict()
369 by_ident=Dict()
370 engines=Dict()
371 clients=Dict()
372 hearts=Dict()
373 pending=Set()
374 queues=Dict() # pending msg_ids keyed by engine_id
375 tasks=Dict() # pending msg_ids submitted as tasks, keyed by client_id
376 completed=Dict() # completed msg_ids keyed by engine_id
377 all_completed=Set() # completed msg_ids keyed by engine_id
378 dead_engines=Set() # completed msg_ids keyed by engine_id
379 unassigned=Set() # set of task msg_ds not yet assigned a destination
380 incoming_registrations=Dict()
381 registration_timeout=Integer()
382 _idcounter=Integer(0)
383
384 # objects from constructor:
385 query=Instance(ZMQStream, allow_none=True)
386 monitor=Instance(ZMQStream, allow_none=True)
387 notifier=Instance(ZMQStream, allow_none=True)
388 resubmit=Instance(ZMQStream, allow_none=True)
389 heartmonitor=Instance(HeartMonitor, allow_none=True)
390 db=Instance(object, allow_none=True)
391 client_info=Dict()
392 engine_info=Dict()
393
394
395 def __init__(self, **kwargs):
396 """
397 # universal:
398 loop: IOLoop for creating future connections
399 session: streamsession for sending serialized data
400 # engine:
401 queue: ZMQStream for monitoring queue messages
402 query: ZMQStream for engine+client registration and client requests
403 heartbeat: HeartMonitor object for tracking engines
404 # extra:
405 db: ZMQStream for db connection (NotImplemented)
406 engine_info: zmq address/protocol dict for engine connections
407 client_info: zmq address/protocol dict for client connections
408 """
409
410 super(Hub, self).__init__(**kwargs)
411
412 # register our callbacks
413 self.query.on_recv(self.dispatch_query)
414 self.monitor.on_recv(self.dispatch_monitor_traffic)
415
416 self.heartmonitor.add_heart_failure_handler(self.handle_heart_failure)
417 self.heartmonitor.add_new_heart_handler(self.handle_new_heart)
418
419 self.monitor_handlers = {b'in' : self.save_queue_request,
420 b'out': self.save_queue_result,
421 b'intask': self.save_task_request,
422 b'outtask': self.save_task_result,
423 b'tracktask': self.save_task_destination,
424 b'incontrol': _passer,
425 b'outcontrol': _passer,
426 b'iopub': self.save_iopub_message,
427 }
428
429 self.query_handlers = {'queue_request': self.queue_status,
430 'result_request': self.get_results,
431 'history_request': self.get_history,
432 'db_request': self.db_query,
433 'purge_request': self.purge_results,
434 'load_request': self.check_load,
435 'resubmit_request': self.resubmit_task,
436 'shutdown_request': self.shutdown_request,
437 'registration_request' : self.register_engine,
438 'unregistration_request' : self.unregister_engine,
439 'connection_request': self.connection_request,
440 }
441
442 # ignore resubmit replies
443 self.resubmit.on_recv(lambda msg: None, copy=False)
444
445 self.log.info("hub::created hub")
446
447 @property
448 def _next_id(self):
449 """gemerate a new ID.
450
451 No longer reuse old ids, just count from 0."""
452 newid = self._idcounter
453 self._idcounter += 1
454 return newid
455 # newid = 0
456 # incoming = [id[0] for id in itervalues(self.incoming_registrations)]
457 # # print newid, self.ids, self.incoming_registrations
458 # while newid in self.ids or newid in incoming:
459 # newid += 1
460 # return newid
461
462 #-----------------------------------------------------------------------------
463 # message validation
464 #-----------------------------------------------------------------------------
465
466 def _validate_targets(self, targets):
467 """turn any valid targets argument into a list of integer ids"""
468 if targets is None:
469 # default to all
470 return self.ids
471
472 if isinstance(targets, (int,str,unicode_type)):
473 # only one target specified
474 targets = [targets]
475 _targets = []
476 for t in targets:
477 # map raw identities to ids
478 if isinstance(t, (str,unicode_type)):
479 t = self.by_ident.get(cast_bytes(t), t)
480 _targets.append(t)
481 targets = _targets
482 bad_targets = [ t for t in targets if t not in self.ids ]
483 if bad_targets:
484 raise IndexError("No Such Engine: %r" % bad_targets)
485 if not targets:
486 raise IndexError("No Engines Registered")
487 return targets
488
489 #-----------------------------------------------------------------------------
490 # dispatch methods (1 per stream)
491 #-----------------------------------------------------------------------------
492
493
494 @util.log_errors
495 def dispatch_monitor_traffic(self, msg):
496 """all ME and Task queue messages come through here, as well as
497 IOPub traffic."""
498 self.log.debug("monitor traffic: %r", msg[0])
499 switch = msg[0]
500 try:
501 idents, msg = self.session.feed_identities(msg[1:])
502 except ValueError:
503 idents=[]
504 if not idents:
505 self.log.error("Monitor message without topic: %r", msg)
506 return
507 handler = self.monitor_handlers.get(switch, None)
508 if handler is not None:
509 handler(idents, msg)
510 else:
511 self.log.error("Unrecognized monitor topic: %r", switch)
512
513
514 @util.log_errors
515 def dispatch_query(self, msg):
516 """Route registration requests and queries from clients."""
517 try:
518 idents, msg = self.session.feed_identities(msg)
519 except ValueError:
520 idents = []
521 if not idents:
522 self.log.error("Bad Query Message: %r", msg)
523 return
524 client_id = idents[0]
525 try:
526 msg = self.session.deserialize(msg, content=True)
527 except Exception:
528 content = error.wrap_exception()
529 self.log.error("Bad Query Message: %r", msg, exc_info=True)
530 self.session.send(self.query, "hub_error", ident=client_id,
531 content=content)
532 return
533 # print client_id, header, parent, content
534 #switch on message type:
535 msg_type = msg['header']['msg_type']
536 self.log.info("client::client %r requested %r", client_id, msg_type)
537 handler = self.query_handlers.get(msg_type, None)
538 try:
539 assert handler is not None, "Bad Message Type: %r" % msg_type
540 except:
541 content = error.wrap_exception()
542 self.log.error("Bad Message Type: %r", msg_type, exc_info=True)
543 self.session.send(self.query, "hub_error", ident=client_id,
544 content=content)
545 return
546
547 else:
548 handler(idents, msg)
549
550 def dispatch_db(self, msg):
551 """"""
552 raise NotImplementedError
553
554 #---------------------------------------------------------------------------
555 # handler methods (1 per event)
556 #---------------------------------------------------------------------------
557
558 #----------------------- Heartbeat --------------------------------------
559
560 def handle_new_heart(self, heart):
561 """handler to attach to heartbeater.
562 Called when a new heart starts to beat.
563 Triggers completion of registration."""
564 self.log.debug("heartbeat::handle_new_heart(%r)", heart)
565 if heart not in self.incoming_registrations:
566 self.log.info("heartbeat::ignoring new heart: %r", heart)
567 else:
568 self.finish_registration(heart)
569
570
571 def handle_heart_failure(self, heart):
572 """handler to attach to heartbeater.
573 called when a previously registered heart fails to respond to beat request.
574 triggers unregistration"""
575 self.log.debug("heartbeat::handle_heart_failure(%r)", heart)
576 eid = self.hearts.get(heart, None)
577 uuid = self.engines[eid].uuid
578 if eid is None or self.keytable[eid] in self.dead_engines:
579 self.log.info("heartbeat::ignoring heart failure %r (not an engine or already dead)", heart)
580 else:
581 self.unregister_engine(heart, dict(content=dict(id=eid, queue=uuid)))
582
583 #----------------------- MUX Queue Traffic ------------------------------
584
585 def save_queue_request(self, idents, msg):
586 if len(idents) < 2:
587 self.log.error("invalid identity prefix: %r", idents)
588 return
589 queue_id, client_id = idents[:2]
590 try:
591 msg = self.session.deserialize(msg)
592 except Exception:
593 self.log.error("queue::client %r sent invalid message to %r: %r", client_id, queue_id, msg, exc_info=True)
594 return
595
596 eid = self.by_ident.get(queue_id, None)
597 if eid is None:
598 self.log.error("queue::target %r not registered", queue_id)
599 self.log.debug("queue:: valid are: %r", self.by_ident.keys())
600 return
601 record = init_record(msg)
602 msg_id = record['msg_id']
603 self.log.info("queue::client %r submitted request %r to %s", client_id, msg_id, eid)
604 # Unicode in records
605 record['engine_uuid'] = queue_id.decode('ascii')
606 record['client_uuid'] = msg['header']['session']
607 record['queue'] = 'mux'
608
609 try:
610 # it's posible iopub arrived first:
611 existing = self.db.get_record(msg_id)
612 for key,evalue in iteritems(existing):
613 rvalue = record.get(key, None)
614 if evalue and rvalue and evalue != rvalue:
615 self.log.warn("conflicting initial state for record: %r:%r <%r> %r", msg_id, rvalue, key, evalue)
616 elif evalue and not rvalue:
617 record[key] = evalue
618 try:
619 self.db.update_record(msg_id, record)
620 except Exception:
621 self.log.error("DB Error updating record %r", msg_id, exc_info=True)
622 except KeyError:
623 try:
624 self.db.add_record(msg_id, record)
625 except Exception:
626 self.log.error("DB Error adding record %r", msg_id, exc_info=True)
627
628
629 self.pending.add(msg_id)
630 self.queues[eid].append(msg_id)
631
632 def save_queue_result(self, idents, msg):
633 if len(idents) < 2:
634 self.log.error("invalid identity prefix: %r", idents)
635 return
636
637 client_id, queue_id = idents[:2]
638 try:
639 msg = self.session.deserialize(msg)
640 except Exception:
641 self.log.error("queue::engine %r sent invalid message to %r: %r",
642 queue_id, client_id, msg, exc_info=True)
643 return
644
645 eid = self.by_ident.get(queue_id, None)
646 if eid is None:
647 self.log.error("queue::unknown engine %r is sending a reply: ", queue_id)
648 return
649
650 parent = msg['parent_header']
651 if not parent:
652 return
653 msg_id = parent['msg_id']
654 if msg_id in self.pending:
655 self.pending.remove(msg_id)
656 self.all_completed.add(msg_id)
657 self.queues[eid].remove(msg_id)
658 self.completed[eid].append(msg_id)
659 self.log.info("queue::request %r completed on %s", msg_id, eid)
660 elif msg_id not in self.all_completed:
661 # it could be a result from a dead engine that died before delivering the
662 # result
663 self.log.warn("queue:: unknown msg finished %r", msg_id)
664 return
665 # update record anyway, because the unregistration could have been premature
666 rheader = msg['header']
667 md = msg['metadata']
668 completed = rheader['date']
669 started = extract_dates(md.get('started', None))
670 result = {
671 'result_header' : rheader,
672 'result_metadata': md,
673 'result_content': msg['content'],
674 'received': datetime.now(),
675 'started' : started,
676 'completed' : completed
677 }
678
679 result['result_buffers'] = msg['buffers']
680 try:
681 self.db.update_record(msg_id, result)
682 except Exception:
683 self.log.error("DB Error updating record %r", msg_id, exc_info=True)
684
685
686 #--------------------- Task Queue Traffic ------------------------------
687
688 def save_task_request(self, idents, msg):
689 """Save the submission of a task."""
690 client_id = idents[0]
691
692 try:
693 msg = self.session.deserialize(msg)
694 except Exception:
695 self.log.error("task::client %r sent invalid task message: %r",
696 client_id, msg, exc_info=True)
697 return
698 record = init_record(msg)
699
700 record['client_uuid'] = msg['header']['session']
701 record['queue'] = 'task'
702 header = msg['header']
703 msg_id = header['msg_id']
704 self.pending.add(msg_id)
705 self.unassigned.add(msg_id)
706 try:
707 # it's posible iopub arrived first:
708 existing = self.db.get_record(msg_id)
709 if existing['resubmitted']:
710 for key in ('submitted', 'client_uuid', 'buffers'):
711 # don't clobber these keys on resubmit
712 # submitted and client_uuid should be different
713 # and buffers might be big, and shouldn't have changed
714 record.pop(key)
715 # still check content,header which should not change
716 # but are not expensive to compare as buffers
717
718 for key,evalue in iteritems(existing):
719 if key.endswith('buffers'):
720 # don't compare buffers
721 continue
722 rvalue = record.get(key, None)
723 if evalue and rvalue and evalue != rvalue:
724 self.log.warn("conflicting initial state for record: %r:%r <%r> %r", msg_id, rvalue, key, evalue)
725 elif evalue and not rvalue:
726 record[key] = evalue
727 try:
728 self.db.update_record(msg_id, record)
729 except Exception:
730 self.log.error("DB Error updating record %r", msg_id, exc_info=True)
731 except KeyError:
732 try:
733 self.db.add_record(msg_id, record)
734 except Exception:
735 self.log.error("DB Error adding record %r", msg_id, exc_info=True)
736 except Exception:
737 self.log.error("DB Error saving task request %r", msg_id, exc_info=True)
738
739 def save_task_result(self, idents, msg):
740 """save the result of a completed task."""
741 client_id = idents[0]
742 try:
743 msg = self.session.deserialize(msg)
744 except Exception:
745 self.log.error("task::invalid task result message send to %r: %r",
746 client_id, msg, exc_info=True)
747 return
748
749 parent = msg['parent_header']
750 if not parent:
751 # print msg
752 self.log.warn("Task %r had no parent!", msg)
753 return
754 msg_id = parent['msg_id']
755 if msg_id in self.unassigned:
756 self.unassigned.remove(msg_id)
757
758 header = msg['header']
759 md = msg['metadata']
760 engine_uuid = md.get('engine', u'')
761 eid = self.by_ident.get(cast_bytes(engine_uuid), None)
762
763 status = md.get('status', None)
764
765 if msg_id in self.pending:
766 self.log.info("task::task %r finished on %s", msg_id, eid)
767 self.pending.remove(msg_id)
768 self.all_completed.add(msg_id)
769 if eid is not None:
770 if status != 'aborted':
771 self.completed[eid].append(msg_id)
772 if msg_id in self.tasks[eid]:
773 self.tasks[eid].remove(msg_id)
774 completed = header['date']
775 started = extract_dates(md.get('started', None))
776 result = {
777 'result_header' : header,
778 'result_metadata': msg['metadata'],
779 'result_content': msg['content'],
780 'started' : started,
781 'completed' : completed,
782 'received' : datetime.now(),
783 'engine_uuid': engine_uuid,
784 }
785
786 result['result_buffers'] = msg['buffers']
787 try:
788 self.db.update_record(msg_id, result)
789 except Exception:
790 self.log.error("DB Error saving task request %r", msg_id, exc_info=True)
791
792 else:
793 self.log.debug("task::unknown task %r finished", msg_id)
794
795 def save_task_destination(self, idents, msg):
796 try:
797 msg = self.session.deserialize(msg, content=True)
798 except Exception:
799 self.log.error("task::invalid task tracking message", exc_info=True)
800 return
801 content = msg['content']
802 # print (content)
803 msg_id = content['msg_id']
804 engine_uuid = content['engine_id']
805 eid = self.by_ident[cast_bytes(engine_uuid)]
806
807 self.log.info("task::task %r arrived on %r", msg_id, eid)
808 if msg_id in self.unassigned:
809 self.unassigned.remove(msg_id)
810 # else:
811 # self.log.debug("task::task %r not listed as MIA?!"%(msg_id))
812
813 self.tasks[eid].append(msg_id)
814 # self.pending[msg_id][1].update(received=datetime.now(),engine=(eid,engine_uuid))
815 try:
816 self.db.update_record(msg_id, dict(engine_uuid=engine_uuid))
817 except Exception:
818 self.log.error("DB Error saving task destination %r", msg_id, exc_info=True)
819
820
821 def mia_task_request(self, idents, msg):
822 raise NotImplementedError
823 client_id = idents[0]
824 # content = dict(mia=self.mia,status='ok')
825 # self.session.send('mia_reply', content=content, idents=client_id)
826
827
828 #--------------------- IOPub Traffic ------------------------------
829
830 def save_iopub_message(self, topics, msg):
831 """save an iopub message into the db"""
832 # print (topics)
833 try:
834 msg = self.session.deserialize(msg, content=True)
835 except Exception:
836 self.log.error("iopub::invalid IOPub message", exc_info=True)
837 return
838
839 parent = msg['parent_header']
840 if not parent:
841 self.log.debug("iopub::IOPub message lacks parent: %r", msg)
842 return
843 msg_id = parent['msg_id']
844 msg_type = msg['header']['msg_type']
845 content = msg['content']
846
847 # ensure msg_id is in db
848 try:
849 rec = self.db.get_record(msg_id)
850 except KeyError:
851 rec = None
852
853 # stream
854 d = {}
855 if msg_type == 'stream':
856 name = content['name']
857 s = '' if rec is None else rec[name]
858 d[name] = s + content['text']
859
860 elif msg_type == 'error':
861 d['error'] = content
862 elif msg_type == 'execute_input':
863 d['execute_input'] = content['code']
864 elif msg_type in ('display_data', 'execute_result'):
865 d[msg_type] = content
866 elif msg_type == 'status':
867 pass
868 elif msg_type == 'data_pub':
869 self.log.info("ignored data_pub message for %s" % msg_id)
870 else:
871 self.log.warn("unhandled iopub msg_type: %r", msg_type)
872
873 if not d:
874 return
875
876 if rec is None:
877 # new record
878 rec = empty_record()
879 rec['msg_id'] = msg_id
880 rec.update(d)
881 d = rec
882 update_record = self.db.add_record
883 else:
884 update_record = self.db.update_record
885
886 try:
887 update_record(msg_id, d)
888 except Exception:
889 self.log.error("DB Error saving iopub message %r", msg_id, exc_info=True)
890
891
892
893 #-------------------------------------------------------------------------
894 # Registration requests
895 #-------------------------------------------------------------------------
896
897 def connection_request(self, client_id, msg):
898 """Reply with connection addresses for clients."""
899 self.log.info("client::client %r connected", client_id)
900 content = dict(status='ok')
901 jsonable = {}
902 for k,v in iteritems(self.keytable):
903 if v not in self.dead_engines:
904 jsonable[str(k)] = v
905 content['engines'] = jsonable
906 self.session.send(self.query, 'connection_reply', content, parent=msg, ident=client_id)
907
908 def register_engine(self, reg, msg):
909 """Register a new engine."""
910 content = msg['content']
911 try:
912 uuid = content['uuid']
913 except KeyError:
914 self.log.error("registration::queue not specified", exc_info=True)
915 return
916
917 eid = self._next_id
918
919 self.log.debug("registration::register_engine(%i, %r)", eid, uuid)
920
921 content = dict(id=eid,status='ok',hb_period=self.heartmonitor.period)
922 # check if requesting available IDs:
923 if cast_bytes(uuid) in self.by_ident:
924 try:
925 raise KeyError("uuid %r in use" % uuid)
926 except:
927 content = error.wrap_exception()
928 self.log.error("uuid %r in use", uuid, exc_info=True)
929 else:
930 for h, ec in iteritems(self.incoming_registrations):
931 if uuid == h:
932 try:
933 raise KeyError("heart_id %r in use" % uuid)
934 except:
935 self.log.error("heart_id %r in use", uuid, exc_info=True)
936 content = error.wrap_exception()
937 break
938 elif uuid == ec.uuid:
939 try:
940 raise KeyError("uuid %r in use" % uuid)
941 except:
942 self.log.error("uuid %r in use", uuid, exc_info=True)
943 content = error.wrap_exception()
944 break
945
946 msg = self.session.send(self.query, "registration_reply",
947 content=content,
948 ident=reg)
949
950 heart = cast_bytes(uuid)
951
952 if content['status'] == 'ok':
953 if heart in self.heartmonitor.hearts:
954 # already beating
955 self.incoming_registrations[heart] = EngineConnector(id=eid,uuid=uuid)
956 self.finish_registration(heart)
957 else:
958 purge = lambda : self._purge_stalled_registration(heart)
959 t = self.loop.add_timeout(
960 self.loop.time() + self.registration_timeout,
961 purge,
962 )
963 self.incoming_registrations[heart] = EngineConnector(id=eid,uuid=uuid,stallback=t)
964 else:
965 self.log.error("registration::registration %i failed: %r", eid, content['evalue'])
966
967 return eid
968
969 def unregister_engine(self, ident, msg):
970 """Unregister an engine that explicitly requested to leave."""
971 try:
972 eid = msg['content']['id']
973 except:
974 self.log.error("registration::bad engine id for unregistration: %r", ident, exc_info=True)
975 return
976 self.log.info("registration::unregister_engine(%r)", eid)
977
978 uuid = self.keytable[eid]
979 content=dict(id=eid, uuid=uuid)
980 self.dead_engines.add(uuid)
981
982 self.loop.add_timeout(
983 self.loop.time() + self.registration_timeout,
984 lambda : self._handle_stranded_msgs(eid, uuid),
985 )
986 ############## TODO: HANDLE IT ################
987
988 self._save_engine_state()
989
990 if self.notifier:
991 self.session.send(self.notifier, "unregistration_notification", content=content)
992
993 def _handle_stranded_msgs(self, eid, uuid):
994 """Handle messages known to be on an engine when the engine unregisters.
995
996 It is possible that this will fire prematurely - that is, an engine will
997 go down after completing a result, and the client will be notified
998 that the result failed and later receive the actual result.
999 """
1000
1001 outstanding = self.queues[eid]
1002
1003 for msg_id in outstanding:
1004 self.pending.remove(msg_id)
1005 self.all_completed.add(msg_id)
1006 try:
1007 raise error.EngineError("Engine %r died while running task %r" % (eid, msg_id))
1008 except:
1009 content = error.wrap_exception()
1010 # build a fake header:
1011 header = {}
1012 header['engine'] = uuid
1013 header['date'] = datetime.now()
1014 rec = dict(result_content=content, result_header=header, result_buffers=[])
1015 rec['completed'] = header['date']
1016 rec['engine_uuid'] = uuid
1017 try:
1018 self.db.update_record(msg_id, rec)
1019 except Exception:
1020 self.log.error("DB Error handling stranded msg %r", msg_id, exc_info=True)
1021
1022
1023 def finish_registration(self, heart):
1024 """Second half of engine registration, called after our HeartMonitor
1025 has received a beat from the Engine's Heart."""
1026 try:
1027 ec = self.incoming_registrations.pop(heart)
1028 except KeyError:
1029 self.log.error("registration::tried to finish nonexistant registration", exc_info=True)
1030 return
1031 self.log.info("registration::finished registering engine %i:%s", ec.id, ec.uuid)
1032 if ec.stallback is not None:
1033 self.loop.remove_timeout(ec.stallback)
1034 eid = ec.id
1035 self.ids.add(eid)
1036 self.keytable[eid] = ec.uuid
1037 self.engines[eid] = ec
1038 self.by_ident[cast_bytes(ec.uuid)] = ec.id
1039 self.queues[eid] = list()
1040 self.tasks[eid] = list()
1041 self.completed[eid] = list()
1042 self.hearts[heart] = eid
1043 content = dict(id=eid, uuid=self.engines[eid].uuid)
1044 if self.notifier:
1045 self.session.send(self.notifier, "registration_notification", content=content)
1046 self.log.info("engine::Engine Connected: %i", eid)
1047
1048 self._save_engine_state()
1049
1050 def _purge_stalled_registration(self, heart):
1051 if heart in self.incoming_registrations:
1052 ec = self.incoming_registrations.pop(heart)
1053 self.log.info("registration::purging stalled registration: %i", ec.id)
1054 else:
1055 pass
1056
1057 #-------------------------------------------------------------------------
1058 # Engine State
1059 #-------------------------------------------------------------------------
1060
1061
1062 def _cleanup_engine_state_file(self):
1063 """cleanup engine state mapping"""
1064
1065 if os.path.exists(self.engine_state_file):
1066 self.log.debug("cleaning up engine state: %s", self.engine_state_file)
1067 try:
1068 os.remove(self.engine_state_file)
1069 except IOError:
1070 self.log.error("Couldn't cleanup file: %s", self.engine_state_file, exc_info=True)
1071
1072
1073 def _save_engine_state(self):
1074 """save engine mapping to JSON file"""
1075 if not self.engine_state_file:
1076 return
1077 self.log.debug("save engine state to %s" % self.engine_state_file)
1078 state = {}
1079 engines = {}
1080 for eid, ec in iteritems(self.engines):
1081 if ec.uuid not in self.dead_engines:
1082 engines[eid] = ec.uuid
1083
1084 state['engines'] = engines
1085
1086 state['next_id'] = self._idcounter
1087
1088 with open(self.engine_state_file, 'w') as f:
1089 json.dump(state, f)
1090
1091
1092 def _load_engine_state(self):
1093 """load engine mapping from JSON file"""
1094 if not os.path.exists(self.engine_state_file):
1095 return
1096
1097 self.log.info("loading engine state from %s" % self.engine_state_file)
1098
1099 with open(self.engine_state_file) as f:
1100 state = json.load(f)
1101
1102 save_notifier = self.notifier
1103 self.notifier = None
1104 for eid, uuid in iteritems(state['engines']):
1105 heart = uuid.encode('ascii')
1106 # start with this heart as current and beating:
1107 self.heartmonitor.responses.add(heart)
1108 self.heartmonitor.hearts.add(heart)
1109
1110 self.incoming_registrations[heart] = EngineConnector(id=int(eid), uuid=uuid)
1111 self.finish_registration(heart)
1112
1113 self.notifier = save_notifier
1114
1115 self._idcounter = state['next_id']
1116
1117 #-------------------------------------------------------------------------
1118 # Client Requests
1119 #-------------------------------------------------------------------------
1120
1121 def shutdown_request(self, client_id, msg):
1122 """handle shutdown request."""
1123 self.session.send(self.query, 'shutdown_reply', content={'status': 'ok'}, ident=client_id)
1124 # also notify other clients of shutdown
1125 self.session.send(self.notifier, 'shutdown_notice', content={'status': 'ok'})
1126 self.loop.add_timeout(self.loop.time() + 1, self._shutdown)
1127
1128 def _shutdown(self):
1129 self.log.info("hub::hub shutting down.")
1130 time.sleep(0.1)
1131 sys.exit(0)
1132
1133
1134 def check_load(self, client_id, msg):
1135 content = msg['content']
1136 try:
1137 targets = content['targets']
1138 targets = self._validate_targets(targets)
1139 except:
1140 content = error.wrap_exception()
1141 self.session.send(self.query, "hub_error",
1142 content=content, ident=client_id)
1143 return
1144
1145 content = dict(status='ok')
1146 # loads = {}
1147 for t in targets:
1148 content[bytes(t)] = len(self.queues[t])+len(self.tasks[t])
1149 self.session.send(self.query, "load_reply", content=content, ident=client_id)
1150
1151
1152 def queue_status(self, client_id, msg):
1153 """Return the Queue status of one or more targets.
1154
1155 If verbose, return the msg_ids, else return len of each type.
1156
1157 Keys:
1158
1159 * queue (pending MUX jobs)
1160 * tasks (pending Task jobs)
1161 * completed (finished jobs from both queues)
1162 """
1163 content = msg['content']
1164 targets = content['targets']
1165 try:
1166 targets = self._validate_targets(targets)
1167 except:
1168 content = error.wrap_exception()
1169 self.session.send(self.query, "hub_error",
1170 content=content, ident=client_id)
1171 return
1172 verbose = content.get('verbose', False)
1173 content = dict(status='ok')
1174 for t in targets:
1175 queue = self.queues[t]
1176 completed = self.completed[t]
1177 tasks = self.tasks[t]
1178 if not verbose:
1179 queue = len(queue)
1180 completed = len(completed)
1181 tasks = len(tasks)
1182 content[str(t)] = {'queue': queue, 'completed': completed , 'tasks': tasks}
1183 content['unassigned'] = list(self.unassigned) if verbose else len(self.unassigned)
1184 # print (content)
1185 self.session.send(self.query, "queue_reply", content=content, ident=client_id)
1186
1187 def purge_results(self, client_id, msg):
1188 """Purge results from memory. This method is more valuable before we move
1189 to a DB based message storage mechanism."""
1190 content = msg['content']
1191 self.log.info("Dropping records with %s", content)
1192 msg_ids = content.get('msg_ids', [])
1193 reply = dict(status='ok')
1194 if msg_ids == 'all':
1195 try:
1196 self.db.drop_matching_records(dict(completed={'$ne':None}))
1197 except Exception:
1198 reply = error.wrap_exception()
1199 self.log.exception("Error dropping records")
1200 else:
1201 pending = [m for m in msg_ids if (m in self.pending)]
1202 if pending:
1203 try:
1204 raise IndexError("msg pending: %r" % pending[0])
1205 except:
1206 reply = error.wrap_exception()
1207 self.log.exception("Error dropping records")
1208 else:
1209 try:
1210 self.db.drop_matching_records(dict(msg_id={'$in':msg_ids}))
1211 except Exception:
1212 reply = error.wrap_exception()
1213 self.log.exception("Error dropping records")
1214
1215 if reply['status'] == 'ok':
1216 eids = content.get('engine_ids', [])
1217 for eid in eids:
1218 if eid not in self.engines:
1219 try:
1220 raise IndexError("No such engine: %i" % eid)
1221 except:
1222 reply = error.wrap_exception()
1223 self.log.exception("Error dropping records")
1224 break
1225 uid = self.engines[eid].uuid
1226 try:
1227 self.db.drop_matching_records(dict(engine_uuid=uid, completed={'$ne':None}))
1228 except Exception:
1229 reply = error.wrap_exception()
1230 self.log.exception("Error dropping records")
1231 break
1232
1233 self.session.send(self.query, 'purge_reply', content=reply, ident=client_id)
1234
1235 def resubmit_task(self, client_id, msg):
1236 """Resubmit one or more tasks."""
1237 def finish(reply):
1238 self.session.send(self.query, 'resubmit_reply', content=reply, ident=client_id)
1239
1240 content = msg['content']
1241 msg_ids = content['msg_ids']
1242 reply = dict(status='ok')
1243 try:
1244 records = self.db.find_records({'msg_id' : {'$in' : msg_ids}}, keys=[
1245 'header', 'content', 'buffers'])
1246 except Exception:
1247 self.log.error('db::db error finding tasks to resubmit', exc_info=True)
1248 return finish(error.wrap_exception())
1249
1250 # validate msg_ids
1251 found_ids = [ rec['msg_id'] for rec in records ]
1252 pending_ids = [ msg_id for msg_id in found_ids if msg_id in self.pending ]
1253 if len(records) > len(msg_ids):
1254 try:
1255 raise RuntimeError("DB appears to be in an inconsistent state."
1256 "More matching records were found than should exist")
1257 except Exception:
1258 self.log.exception("Failed to resubmit task")
1259 return finish(error.wrap_exception())
1260 elif len(records) < len(msg_ids):
1261 missing = [ m for m in msg_ids if m not in found_ids ]
1262 try:
1263 raise KeyError("No such msg(s): %r" % missing)
1264 except KeyError:
1265 self.log.exception("Failed to resubmit task")
1266 return finish(error.wrap_exception())
1267 elif pending_ids:
1268 pass
1269 # no need to raise on resubmit of pending task, now that we
1270 # resubmit under new ID, but do we want to raise anyway?
1271 # msg_id = invalid_ids[0]
1272 # try:
1273 # raise ValueError("Task(s) %r appears to be inflight" % )
1274 # except Exception:
1275 # return finish(error.wrap_exception())
1276
1277 # mapping of original IDs to resubmitted IDs
1278 resubmitted = {}
1279
1280 # send the messages
1281 for rec in records:
1282 header = rec['header']
1283 msg = self.session.msg(header['msg_type'], parent=header)
1284 msg_id = msg['msg_id']
1285 msg['content'] = rec['content']
1286
1287 # use the old header, but update msg_id and timestamp
1288 fresh = msg['header']
1289 header['msg_id'] = fresh['msg_id']
1290 header['date'] = fresh['date']
1291 msg['header'] = header
1292
1293 self.session.send(self.resubmit, msg, buffers=rec['buffers'])
1294
1295 resubmitted[rec['msg_id']] = msg_id
1296 self.pending.add(msg_id)
1297 msg['buffers'] = rec['buffers']
1298 try:
1299 self.db.add_record(msg_id, init_record(msg))
1300 except Exception:
1301 self.log.error("db::DB Error updating record: %s", msg_id, exc_info=True)
1302 return finish(error.wrap_exception())
1303
1304 finish(dict(status='ok', resubmitted=resubmitted))
1305
1306 # store the new IDs in the Task DB
1307 for msg_id, resubmit_id in iteritems(resubmitted):
1308 try:
1309 self.db.update_record(msg_id, {'resubmitted' : resubmit_id})
1310 except Exception:
1311 self.log.error("db::DB Error updating record: %s", msg_id, exc_info=True)
1312
1313
1314 def _extract_record(self, rec):
1315 """decompose a TaskRecord dict into subsection of reply for get_result"""
1316 io_dict = {}
1317 for key in ('execute_input', 'execute_result', 'error', 'stdout', 'stderr'):
1318 io_dict[key] = rec[key]
1319 content = {
1320 'header': rec['header'],
1321 'metadata': rec['metadata'],
1322 'result_metadata': rec['result_metadata'],
1323 'result_header' : rec['result_header'],
1324 'result_content': rec['result_content'],
1325 'received' : rec['received'],
1326 'io' : io_dict,
1327 }
1328 if rec['result_buffers']:
1329 buffers = list(map(bytes, rec['result_buffers']))
1330 else:
1331 buffers = []
1332
1333 return content, buffers
1334
1335 def get_results(self, client_id, msg):
1336 """Get the result of 1 or more messages."""
1337 content = msg['content']
1338 msg_ids = sorted(set(content['msg_ids']))
1339 statusonly = content.get('status_only', False)
1340 pending = []
1341 completed = []
1342 content = dict(status='ok')
1343 content['pending'] = pending
1344 content['completed'] = completed
1345 buffers = []
1346 if not statusonly:
1347 try:
1348 matches = self.db.find_records(dict(msg_id={'$in':msg_ids}))
1349 # turn match list into dict, for faster lookup
1350 records = {}
1351 for rec in matches:
1352 records[rec['msg_id']] = rec
1353 except Exception:
1354 content = error.wrap_exception()
1355 self.log.exception("Failed to get results")
1356 self.session.send(self.query, "result_reply", content=content,
1357 parent=msg, ident=client_id)
1358 return
1359 else:
1360 records = {}
1361 for msg_id in msg_ids:
1362 if msg_id in self.pending:
1363 pending.append(msg_id)
1364 elif msg_id in self.all_completed:
1365 completed.append(msg_id)
1366 if not statusonly:
1367 c,bufs = self._extract_record(records[msg_id])
1368 content[msg_id] = c
1369 buffers.extend(bufs)
1370 elif msg_id in records:
1371 if rec['completed']:
1372 completed.append(msg_id)
1373 c,bufs = self._extract_record(records[msg_id])
1374 content[msg_id] = c
1375 buffers.extend(bufs)
1376 else:
1377 pending.append(msg_id)
1378 else:
1379 try:
1380 raise KeyError('No such message: '+msg_id)
1381 except:
1382 content = error.wrap_exception()
1383 break
1384 self.session.send(self.query, "result_reply", content=content,
1385 parent=msg, ident=client_id,
1386 buffers=buffers)
1387
1388 def get_history(self, client_id, msg):
1389 """Get a list of all msg_ids in our DB records"""
1390 try:
1391 msg_ids = self.db.get_history()
1392 except Exception as e:
1393 content = error.wrap_exception()
1394 self.log.exception("Failed to get history")
1395 else:
1396 content = dict(status='ok', history=msg_ids)
1397
1398 self.session.send(self.query, "history_reply", content=content,
1399 parent=msg, ident=client_id)
1400
1401 def db_query(self, client_id, msg):
1402 """Perform a raw query on the task record database."""
1403 content = msg['content']
1404 query = extract_dates(content.get('query', {}))
1405 keys = content.get('keys', None)
1406 buffers = []
1407 empty = list()
1408 try:
1409 records = self.db.find_records(query, keys)
1410 except Exception as e:
1411 content = error.wrap_exception()
1412 self.log.exception("DB query failed")
1413 else:
1414 # extract buffers from reply content:
1415 if keys is not None:
1416 buffer_lens = [] if 'buffers' in keys else None
1417 result_buffer_lens = [] if 'result_buffers' in keys else None
1418 else:
1419 buffer_lens = None
1420 result_buffer_lens = None
1421
1422 for rec in records:
1423 # buffers may be None, so double check
1424 b = rec.pop('buffers', empty) or empty
1425 if buffer_lens is not None:
1426 buffer_lens.append(len(b))
1427 buffers.extend(b)
1428 rb = rec.pop('result_buffers', empty) or empty
1429 if result_buffer_lens is not None:
1430 result_buffer_lens.append(len(rb))
1431 buffers.extend(rb)
1432 content = dict(status='ok', records=records, buffer_lens=buffer_lens,
1433 result_buffer_lens=result_buffer_lens)
1434 # self.log.debug (content)
1435 self.session.send(self.query, "db_reply", content=content,
1436 parent=msg, ident=client_id,
1437 buffers=buffers)
1438
@@ -1,122 +0,0 b''
1 """A TaskRecord backend using mongodb
2
3 Authors:
4
5 * Min RK
6 """
7 #-----------------------------------------------------------------------------
8 # Copyright (C) 2010-2011 The IPython Development Team
9 #
10 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING, distributed as part of this software.
12 #-----------------------------------------------------------------------------
13
14 from pymongo import Connection
15
16 # bson.Binary import moved
17 try:
18 from bson.binary import Binary
19 except ImportError:
20 from bson import Binary
21
22 from IPython.utils.traitlets import Dict, List, Unicode, Instance
23
24 from .dictdb import BaseDB
25
26 #-----------------------------------------------------------------------------
27 # MongoDB class
28 #-----------------------------------------------------------------------------
29
30 class MongoDB(BaseDB):
31 """MongoDB TaskRecord backend."""
32
33 connection_args = List(config=True,
34 help="""Positional arguments to be passed to pymongo.Connection. Only
35 necessary if the default mongodb configuration does not point to your
36 mongod instance.""")
37 connection_kwargs = Dict(config=True,
38 help="""Keyword arguments to be passed to pymongo.Connection. Only
39 necessary if the default mongodb configuration does not point to your
40 mongod instance."""
41 )
42 database = Unicode("ipython-tasks", config=True,
43 help="""The MongoDB database name to use for storing tasks for this session. If unspecified,
44 a new database will be created with the Hub's IDENT. Specifying the database will result
45 in tasks from previous sessions being available via Clients' db_query and
46 get_result methods.""")
47
48 _connection = Instance(Connection, allow_none=True) # pymongo connection
49
50 def __init__(self, **kwargs):
51 super(MongoDB, self).__init__(**kwargs)
52 if self._connection is None:
53 self._connection = Connection(*self.connection_args, **self.connection_kwargs)
54 if not self.database:
55 self.database = self.session
56 self._db = self._connection[self.database]
57 self._records = self._db['task_records']
58 self._records.ensure_index('msg_id', unique=True)
59 self._records.ensure_index('submitted') # for sorting history
60 # for rec in self._records.find
61
62 def _binary_buffers(self, rec):
63 for key in ('buffers', 'result_buffers'):
64 if rec.get(key, None):
65 rec[key] = list(map(Binary, rec[key]))
66 return rec
67
68 def add_record(self, msg_id, rec):
69 """Add a new Task Record, by msg_id."""
70 # print rec
71 rec = self._binary_buffers(rec)
72 self._records.insert(rec)
73
74 def get_record(self, msg_id):
75 """Get a specific Task Record, by msg_id."""
76 r = self._records.find_one({'msg_id': msg_id})
77 if not r:
78 # r will be '' if nothing is found
79 raise KeyError(msg_id)
80 return r
81
82 def update_record(self, msg_id, rec):
83 """Update the data in an existing record."""
84 rec = self._binary_buffers(rec)
85
86 self._records.update({'msg_id':msg_id}, {'$set': rec})
87
88 def drop_matching_records(self, check):
89 """Remove a record from the DB."""
90 self._records.remove(check)
91
92 def drop_record(self, msg_id):
93 """Remove a record from the DB."""
94 self._records.remove({'msg_id':msg_id})
95
96 def find_records(self, check, keys=None):
97 """Find records matching a query dict, optionally extracting subset of keys.
98
99 Returns list of matching records.
100
101 Parameters
102 ----------
103
104 check: dict
105 mongodb-style query argument
106 keys: list of strs [optional]
107 if specified, the subset of keys to extract. msg_id will *always* be
108 included.
109 """
110 if keys and 'msg_id' not in keys:
111 keys.append('msg_id')
112 matches = list(self._records.find(check,keys))
113 for rec in matches:
114 rec.pop('_id')
115 return matches
116
117 def get_history(self):
118 """get all msg_ids, ordered by time submitted."""
119 cursor = self._records.find({},{'msg_id':1}).sort('submitted')
120 return [ rec['msg_id'] for rec in cursor ]
121
122
This diff has been collapsed as it changes many lines, (849 lines changed) Show them Hide them
@@ -1,849 +0,0 b''
1 """The Python scheduler for rich scheduling.
2
3 The Pure ZMQ scheduler does not allow routing schemes other than LRU,
4 nor does it check msg_id DAG dependencies. For those, a slightly slower
5 Python Scheduler exists.
6 """
7
8 # Copyright (c) IPython Development Team.
9 # Distributed under the terms of the Modified BSD License.
10
11 import logging
12 import sys
13 import time
14
15 from collections import deque
16 from datetime import datetime
17 from random import randint, random
18 from types import FunctionType
19
20 try:
21 import numpy
22 except ImportError:
23 numpy = None
24
25 import zmq
26 from zmq.eventloop import ioloop, zmqstream
27
28 # local imports
29 from decorator import decorator
30 from IPython.config.application import Application
31 from IPython.config.loader import Config
32 from IPython.utils.traitlets import Instance, Dict, List, Set, Integer, Enum, CBytes
33 from IPython.utils.py3compat import cast_bytes
34
35 from ipython_parallel import error, util
36 from ipython_parallel.factory import SessionFactory
37 from ipython_parallel.util import connect_logger, local_logger
38
39 from .dependency import Dependency
40
41 @decorator
42 def logged(f,self,*args,**kwargs):
43 # print ("#--------------------")
44 self.log.debug("scheduler::%s(*%s,**%s)", f.__name__, args, kwargs)
45 # print ("#--")
46 return f(self,*args, **kwargs)
47
48 #----------------------------------------------------------------------
49 # Chooser functions
50 #----------------------------------------------------------------------
51
52 def plainrandom(loads):
53 """Plain random pick."""
54 n = len(loads)
55 return randint(0,n-1)
56
57 def lru(loads):
58 """Always pick the front of the line.
59
60 The content of `loads` is ignored.
61
62 Assumes LRU ordering of loads, with oldest first.
63 """
64 return 0
65
66 def twobin(loads):
67 """Pick two at random, use the LRU of the two.
68
69 The content of loads is ignored.
70
71 Assumes LRU ordering of loads, with oldest first.
72 """
73 n = len(loads)
74 a = randint(0,n-1)
75 b = randint(0,n-1)
76 return min(a,b)
77
78 def weighted(loads):
79 """Pick two at random using inverse load as weight.
80
81 Return the less loaded of the two.
82 """
83 # weight 0 a million times more than 1:
84 weights = 1./(1e-6+numpy.array(loads))
85 sums = weights.cumsum()
86 t = sums[-1]
87 x = random()*t
88 y = random()*t
89 idx = 0
90 idy = 0
91 while sums[idx] < x:
92 idx += 1
93 while sums[idy] < y:
94 idy += 1
95 if weights[idy] > weights[idx]:
96 return idy
97 else:
98 return idx
99
100 def leastload(loads):
101 """Always choose the lowest load.
102
103 If the lowest load occurs more than once, the first
104 occurance will be used. If loads has LRU ordering, this means
105 the LRU of those with the lowest load is chosen.
106 """
107 return loads.index(min(loads))
108
109 #---------------------------------------------------------------------
110 # Classes
111 #---------------------------------------------------------------------
112
113
114 # store empty default dependency:
115 MET = Dependency([])
116
117
118 class Job(object):
119 """Simple container for a job"""
120 def __init__(self, msg_id, raw_msg, idents, msg, header, metadata,
121 targets, after, follow, timeout):
122 self.msg_id = msg_id
123 self.raw_msg = raw_msg
124 self.idents = idents
125 self.msg = msg
126 self.header = header
127 self.metadata = metadata
128 self.targets = targets
129 self.after = after
130 self.follow = follow
131 self.timeout = timeout
132
133 self.removed = False # used for lazy-delete from sorted queue
134 self.timestamp = time.time()
135 self.timeout_id = 0
136 self.blacklist = set()
137
138 def __lt__(self, other):
139 return self.timestamp < other.timestamp
140
141 def __cmp__(self, other):
142 return cmp(self.timestamp, other.timestamp)
143
144 @property
145 def dependents(self):
146 return self.follow.union(self.after)
147
148
149 class TaskScheduler(SessionFactory):
150 """Python TaskScheduler object.
151
152 This is the simplest object that supports msg_id based
153 DAG dependencies. *Only* task msg_ids are checked, not
154 msg_ids of jobs submitted via the MUX queue.
155
156 """
157
158 hwm = Integer(1, config=True,
159 help="""specify the High Water Mark (HWM) for the downstream
160 socket in the Task scheduler. This is the maximum number
161 of allowed outstanding tasks on each engine.
162
163 The default (1) means that only one task can be outstanding on each
164 engine. Setting TaskScheduler.hwm=0 means there is no limit, and the
165 engines continue to be assigned tasks while they are working,
166 effectively hiding network latency behind computation, but can result
167 in an imbalance of work when submitting many heterogenous tasks all at
168 once. Any positive value greater than one is a compromise between the
169 two.
170
171 """
172 )
173 scheme_name = Enum(('leastload', 'pure', 'lru', 'plainrandom', 'weighted', 'twobin'),
174 'leastload', config=True,
175 help="""select the task scheduler scheme [default: Python LRU]
176 Options are: 'pure', 'lru', 'plainrandom', 'weighted', 'twobin','leastload'"""
177 )
178 def _scheme_name_changed(self, old, new):
179 self.log.debug("Using scheme %r"%new)
180 self.scheme = globals()[new]
181
182 # input arguments:
183 scheme = Instance(FunctionType) # function for determining the destination
184 def _scheme_default(self):
185 return leastload
186 client_stream = Instance(zmqstream.ZMQStream, allow_none=True) # client-facing stream
187 engine_stream = Instance(zmqstream.ZMQStream, allow_none=True) # engine-facing stream
188 notifier_stream = Instance(zmqstream.ZMQStream, allow_none=True) # hub-facing sub stream
189 mon_stream = Instance(zmqstream.ZMQStream, allow_none=True) # hub-facing pub stream
190 query_stream = Instance(zmqstream.ZMQStream, allow_none=True) # hub-facing DEALER stream
191
192 # internals:
193 queue = Instance(deque) # sorted list of Jobs
194 def _queue_default(self):
195 return deque()
196 queue_map = Dict() # dict by msg_id of Jobs (for O(1) access to the Queue)
197 graph = Dict() # dict by msg_id of [ msg_ids that depend on key ]
198 retries = Dict() # dict by msg_id of retries remaining (non-neg ints)
199 # waiting = List() # list of msg_ids ready to run, but haven't due to HWM
200 pending = Dict() # dict by engine_uuid of submitted tasks
201 completed = Dict() # dict by engine_uuid of completed tasks
202 failed = Dict() # dict by engine_uuid of failed tasks
203 destinations = Dict() # dict by msg_id of engine_uuids where jobs ran (reverse of completed+failed)
204 clients = Dict() # dict by msg_id for who submitted the task
205 targets = List() # list of target IDENTs
206 loads = List() # list of engine loads
207 # full = Set() # set of IDENTs that have HWM outstanding tasks
208 all_completed = Set() # set of all completed tasks
209 all_failed = Set() # set of all failed tasks
210 all_done = Set() # set of all finished tasks=union(completed,failed)
211 all_ids = Set() # set of all submitted task IDs
212
213 ident = CBytes() # ZMQ identity. This should just be self.session.session
214 # but ensure Bytes
215 def _ident_default(self):
216 return self.session.bsession
217
218 def start(self):
219 self.query_stream.on_recv(self.dispatch_query_reply)
220 self.session.send(self.query_stream, "connection_request", {})
221
222 self.engine_stream.on_recv(self.dispatch_result, copy=False)
223 self.client_stream.on_recv(self.dispatch_submission, copy=False)
224
225 self._notification_handlers = dict(
226 registration_notification = self._register_engine,
227 unregistration_notification = self._unregister_engine
228 )
229 self.notifier_stream.on_recv(self.dispatch_notification)
230 self.log.info("Scheduler started [%s]" % self.scheme_name)
231
232 def resume_receiving(self):
233 """Resume accepting jobs."""
234 self.client_stream.on_recv(self.dispatch_submission, copy=False)
235
236 def stop_receiving(self):
237 """Stop accepting jobs while there are no engines.
238 Leave them in the ZMQ queue."""
239 self.client_stream.on_recv(None)
240
241 #-----------------------------------------------------------------------
242 # [Un]Registration Handling
243 #-----------------------------------------------------------------------
244
245
246 def dispatch_query_reply(self, msg):
247 """handle reply to our initial connection request"""
248 try:
249 idents,msg = self.session.feed_identities(msg)
250 except ValueError:
251 self.log.warn("task::Invalid Message: %r",msg)
252 return
253 try:
254 msg = self.session.deserialize(msg)
255 except ValueError:
256 self.log.warn("task::Unauthorized message from: %r"%idents)
257 return
258
259 content = msg['content']
260 for uuid in content.get('engines', {}).values():
261 self._register_engine(cast_bytes(uuid))
262
263
264 @util.log_errors
265 def dispatch_notification(self, msg):
266 """dispatch register/unregister events."""
267 try:
268 idents,msg = self.session.feed_identities(msg)
269 except ValueError:
270 self.log.warn("task::Invalid Message: %r",msg)
271 return
272 try:
273 msg = self.session.deserialize(msg)
274 except ValueError:
275 self.log.warn("task::Unauthorized message from: %r"%idents)
276 return
277
278 msg_type = msg['header']['msg_type']
279
280 handler = self._notification_handlers.get(msg_type, None)
281 if handler is None:
282 self.log.error("Unhandled message type: %r"%msg_type)
283 else:
284 try:
285 handler(cast_bytes(msg['content']['uuid']))
286 except Exception:
287 self.log.error("task::Invalid notification msg: %r", msg, exc_info=True)
288
289 def _register_engine(self, uid):
290 """New engine with ident `uid` became available."""
291 # head of the line:
292 self.targets.insert(0,uid)
293 self.loads.insert(0,0)
294
295 # initialize sets
296 self.completed[uid] = set()
297 self.failed[uid] = set()
298 self.pending[uid] = {}
299
300 # rescan the graph:
301 self.update_graph(None)
302
303 def _unregister_engine(self, uid):
304 """Existing engine with ident `uid` became unavailable."""
305 if len(self.targets) == 1:
306 # this was our only engine
307 pass
308
309 # handle any potentially finished tasks:
310 self.engine_stream.flush()
311
312 # don't pop destinations, because they might be used later
313 # map(self.destinations.pop, self.completed.pop(uid))
314 # map(self.destinations.pop, self.failed.pop(uid))
315
316 # prevent this engine from receiving work
317 idx = self.targets.index(uid)
318 self.targets.pop(idx)
319 self.loads.pop(idx)
320
321 # wait 5 seconds before cleaning up pending jobs, since the results might
322 # still be incoming
323 if self.pending[uid]:
324 self.loop.add_timeout(self.loop.time() + 5,
325 lambda : self.handle_stranded_tasks(uid),
326 )
327 else:
328 self.completed.pop(uid)
329 self.failed.pop(uid)
330
331
332 def handle_stranded_tasks(self, engine):
333 """Deal with jobs resident in an engine that died."""
334 lost = self.pending[engine]
335 for msg_id in lost.keys():
336 if msg_id not in self.pending[engine]:
337 # prevent double-handling of messages
338 continue
339
340 raw_msg = lost[msg_id].raw_msg
341 idents,msg = self.session.feed_identities(raw_msg, copy=False)
342 parent = self.session.unpack(msg[1].bytes)
343 idents = [engine, idents[0]]
344
345 # build fake error reply
346 try:
347 raise error.EngineError("Engine %r died while running task %r"%(engine, msg_id))
348 except:
349 content = error.wrap_exception()
350 # build fake metadata
351 md = dict(
352 status=u'error',
353 engine=engine.decode('ascii'),
354 date=datetime.now(),
355 )
356 msg = self.session.msg('apply_reply', content, parent=parent, metadata=md)
357 raw_reply = list(map(zmq.Message, self.session.serialize(msg, ident=idents)))
358 # and dispatch it
359 self.dispatch_result(raw_reply)
360
361 # finally scrub completed/failed lists
362 self.completed.pop(engine)
363 self.failed.pop(engine)
364
365
366 #-----------------------------------------------------------------------
367 # Job Submission
368 #-----------------------------------------------------------------------
369
370
371 @util.log_errors
372 def dispatch_submission(self, raw_msg):
373 """Dispatch job submission to appropriate handlers."""
374 # ensure targets up to date:
375 self.notifier_stream.flush()
376 try:
377 idents, msg = self.session.feed_identities(raw_msg, copy=False)
378 msg = self.session.deserialize(msg, content=False, copy=False)
379 except Exception:
380 self.log.error("task::Invaid task msg: %r"%raw_msg, exc_info=True)
381 return
382
383
384 # send to monitor
385 self.mon_stream.send_multipart([b'intask']+raw_msg, copy=False)
386
387 header = msg['header']
388 md = msg['metadata']
389 msg_id = header['msg_id']
390 self.all_ids.add(msg_id)
391
392 # get targets as a set of bytes objects
393 # from a list of unicode objects
394 targets = md.get('targets', [])
395 targets = set(map(cast_bytes, targets))
396
397 retries = md.get('retries', 0)
398 self.retries[msg_id] = retries
399
400 # time dependencies
401 after = md.get('after', None)
402 if after:
403 after = Dependency(after)
404 if after.all:
405 if after.success:
406 after = Dependency(after.difference(self.all_completed),
407 success=after.success,
408 failure=after.failure,
409 all=after.all,
410 )
411 if after.failure:
412 after = Dependency(after.difference(self.all_failed),
413 success=after.success,
414 failure=after.failure,
415 all=after.all,
416 )
417 if after.check(self.all_completed, self.all_failed):
418 # recast as empty set, if `after` already met,
419 # to prevent unnecessary set comparisons
420 after = MET
421 else:
422 after = MET
423
424 # location dependencies
425 follow = Dependency(md.get('follow', []))
426
427 timeout = md.get('timeout', None)
428 if timeout:
429 timeout = float(timeout)
430
431 job = Job(msg_id=msg_id, raw_msg=raw_msg, idents=idents, msg=msg,
432 header=header, targets=targets, after=after, follow=follow,
433 timeout=timeout, metadata=md,
434 )
435 # validate and reduce dependencies:
436 for dep in after,follow:
437 if not dep: # empty dependency
438 continue
439 # check valid:
440 if msg_id in dep or dep.difference(self.all_ids):
441 self.queue_map[msg_id] = job
442 return self.fail_unreachable(msg_id, error.InvalidDependency)
443 # check if unreachable:
444 if dep.unreachable(self.all_completed, self.all_failed):
445 self.queue_map[msg_id] = job
446 return self.fail_unreachable(msg_id)
447
448 if after.check(self.all_completed, self.all_failed):
449 # time deps already met, try to run
450 if not self.maybe_run(job):
451 # can't run yet
452 if msg_id not in self.all_failed:
453 # could have failed as unreachable
454 self.save_unmet(job)
455 else:
456 self.save_unmet(job)
457
458 def job_timeout(self, job, timeout_id):
459 """callback for a job's timeout.
460
461 The job may or may not have been run at this point.
462 """
463 if job.timeout_id != timeout_id:
464 # not the most recent call
465 return
466 now = time.time()
467 if job.timeout >= (now + 1):
468 self.log.warn("task %s timeout fired prematurely: %s > %s",
469 job.msg_id, job.timeout, now
470 )
471 if job.msg_id in self.queue_map:
472 # still waiting, but ran out of time
473 self.log.info("task %r timed out", job.msg_id)
474 self.fail_unreachable(job.msg_id, error.TaskTimeout)
475
476 def fail_unreachable(self, msg_id, why=error.ImpossibleDependency):
477 """a task has become unreachable, send a reply with an ImpossibleDependency
478 error."""
479 if msg_id not in self.queue_map:
480 self.log.error("task %r already failed!", msg_id)
481 return
482 job = self.queue_map.pop(msg_id)
483 # lazy-delete from the queue
484 job.removed = True
485 for mid in job.dependents:
486 if mid in self.graph:
487 self.graph[mid].remove(msg_id)
488
489 try:
490 raise why()
491 except:
492 content = error.wrap_exception()
493 self.log.debug("task %r failing as unreachable with: %s", msg_id, content['ename'])
494
495 self.all_done.add(msg_id)
496 self.all_failed.add(msg_id)
497
498 msg = self.session.send(self.client_stream, 'apply_reply', content,
499 parent=job.header, ident=job.idents)
500 self.session.send(self.mon_stream, msg, ident=[b'outtask']+job.idents)
501
502 self.update_graph(msg_id, success=False)
503
504 def available_engines(self):
505 """return a list of available engine indices based on HWM"""
506 if not self.hwm:
507 return list(range(len(self.targets)))
508 available = []
509 for idx in range(len(self.targets)):
510 if self.loads[idx] < self.hwm:
511 available.append(idx)
512 return available
513
514 def maybe_run(self, job):
515 """check location dependencies, and run if they are met."""
516 msg_id = job.msg_id
517 self.log.debug("Attempting to assign task %s", msg_id)
518 available = self.available_engines()
519 if not available:
520 # no engines, definitely can't run
521 return False
522
523 if job.follow or job.targets or job.blacklist or self.hwm:
524 # we need a can_run filter
525 def can_run(idx):
526 # check hwm
527 if self.hwm and self.loads[idx] == self.hwm:
528 return False
529 target = self.targets[idx]
530 # check blacklist
531 if target in job.blacklist:
532 return False
533 # check targets
534 if job.targets and target not in job.targets:
535 return False
536 # check follow
537 return job.follow.check(self.completed[target], self.failed[target])
538
539 indices = list(filter(can_run, available))
540
541 if not indices:
542 # couldn't run
543 if job.follow.all:
544 # check follow for impossibility
545 dests = set()
546 relevant = set()
547 if job.follow.success:
548 relevant = self.all_completed
549 if job.follow.failure:
550 relevant = relevant.union(self.all_failed)
551 for m in job.follow.intersection(relevant):
552 dests.add(self.destinations[m])
553 if len(dests) > 1:
554 self.queue_map[msg_id] = job
555 self.fail_unreachable(msg_id)
556 return False
557 if job.targets:
558 # check blacklist+targets for impossibility
559 job.targets.difference_update(job.blacklist)
560 if not job.targets or not job.targets.intersection(self.targets):
561 self.queue_map[msg_id] = job
562 self.fail_unreachable(msg_id)
563 return False
564 return False
565 else:
566 indices = None
567
568 self.submit_task(job, indices)
569 return True
570
571 def save_unmet(self, job):
572 """Save a message for later submission when its dependencies are met."""
573 msg_id = job.msg_id
574 self.log.debug("Adding task %s to the queue", msg_id)
575 self.queue_map[msg_id] = job
576 self.queue.append(job)
577 # track the ids in follow or after, but not those already finished
578 for dep_id in job.after.union(job.follow).difference(self.all_done):
579 if dep_id not in self.graph:
580 self.graph[dep_id] = set()
581 self.graph[dep_id].add(msg_id)
582
583 # schedule timeout callback
584 if job.timeout:
585 timeout_id = job.timeout_id = job.timeout_id + 1
586 self.loop.add_timeout(time.time() + job.timeout,
587 lambda : self.job_timeout(job, timeout_id)
588 )
589
590
591 def submit_task(self, job, indices=None):
592 """Submit a task to any of a subset of our targets."""
593 if indices:
594 loads = [self.loads[i] for i in indices]
595 else:
596 loads = self.loads
597 idx = self.scheme(loads)
598 if indices:
599 idx = indices[idx]
600 target = self.targets[idx]
601 # print (target, map(str, msg[:3]))
602 # send job to the engine
603 self.engine_stream.send(target, flags=zmq.SNDMORE, copy=False)
604 self.engine_stream.send_multipart(job.raw_msg, copy=False)
605 # update load
606 self.add_job(idx)
607 self.pending[target][job.msg_id] = job
608 # notify Hub
609 content = dict(msg_id=job.msg_id, engine_id=target.decode('ascii'))
610 self.session.send(self.mon_stream, 'task_destination', content=content,
611 ident=[b'tracktask',self.ident])
612
613
614 #-----------------------------------------------------------------------
615 # Result Handling
616 #-----------------------------------------------------------------------
617
618
619 @util.log_errors
620 def dispatch_result(self, raw_msg):
621 """dispatch method for result replies"""
622 try:
623 idents,msg = self.session.feed_identities(raw_msg, copy=False)
624 msg = self.session.deserialize(msg, content=False, copy=False)
625 engine = idents[0]
626 try:
627 idx = self.targets.index(engine)
628 except ValueError:
629 pass # skip load-update for dead engines
630 else:
631 self.finish_job(idx)
632 except Exception:
633 self.log.error("task::Invalid result: %r", raw_msg, exc_info=True)
634 return
635
636 md = msg['metadata']
637 parent = msg['parent_header']
638 if md.get('dependencies_met', True):
639 success = (md['status'] == 'ok')
640 msg_id = parent['msg_id']
641 retries = self.retries[msg_id]
642 if not success and retries > 0:
643 # failed
644 self.retries[msg_id] = retries - 1
645 self.handle_unmet_dependency(idents, parent)
646 else:
647 del self.retries[msg_id]
648 # relay to client and update graph
649 self.handle_result(idents, parent, raw_msg, success)
650 # send to Hub monitor
651 self.mon_stream.send_multipart([b'outtask']+raw_msg, copy=False)
652 else:
653 self.handle_unmet_dependency(idents, parent)
654
655 def handle_result(self, idents, parent, raw_msg, success=True):
656 """handle a real task result, either success or failure"""
657 # first, relay result to client
658 engine = idents[0]
659 client = idents[1]
660 # swap_ids for ROUTER-ROUTER mirror
661 raw_msg[:2] = [client,engine]
662 # print (map(str, raw_msg[:4]))
663 self.client_stream.send_multipart(raw_msg, copy=False)
664 # now, update our data structures
665 msg_id = parent['msg_id']
666 self.pending[engine].pop(msg_id)
667 if success:
668 self.completed[engine].add(msg_id)
669 self.all_completed.add(msg_id)
670 else:
671 self.failed[engine].add(msg_id)
672 self.all_failed.add(msg_id)
673 self.all_done.add(msg_id)
674 self.destinations[msg_id] = engine
675
676 self.update_graph(msg_id, success)
677
678 def handle_unmet_dependency(self, idents, parent):
679 """handle an unmet dependency"""
680 engine = idents[0]
681 msg_id = parent['msg_id']
682
683 job = self.pending[engine].pop(msg_id)
684 job.blacklist.add(engine)
685
686 if job.blacklist == job.targets:
687 self.queue_map[msg_id] = job
688 self.fail_unreachable(msg_id)
689 elif not self.maybe_run(job):
690 # resubmit failed
691 if msg_id not in self.all_failed:
692 # put it back in our dependency tree
693 self.save_unmet(job)
694
695 if self.hwm:
696 try:
697 idx = self.targets.index(engine)
698 except ValueError:
699 pass # skip load-update for dead engines
700 else:
701 if self.loads[idx] == self.hwm-1:
702 self.update_graph(None)
703
704 def update_graph(self, dep_id=None, success=True):
705 """dep_id just finished. Update our dependency
706 graph and submit any jobs that just became runnable.
707
708 Called with dep_id=None to update entire graph for hwm, but without finishing a task.
709 """
710 # print ("\n\n***********")
711 # pprint (dep_id)
712 # pprint (self.graph)
713 # pprint (self.queue_map)
714 # pprint (self.all_completed)
715 # pprint (self.all_failed)
716 # print ("\n\n***********\n\n")
717 # update any jobs that depended on the dependency
718 msg_ids = self.graph.pop(dep_id, [])
719
720 # recheck *all* jobs if
721 # a) we have HWM and an engine just become no longer full
722 # or b) dep_id was given as None
723
724 if dep_id is None or self.hwm and any( [ load==self.hwm-1 for load in self.loads ]):
725 jobs = self.queue
726 using_queue = True
727 else:
728 using_queue = False
729 jobs = deque(sorted( self.queue_map[msg_id] for msg_id in msg_ids ))
730
731 to_restore = []
732 while jobs:
733 job = jobs.popleft()
734 if job.removed:
735 continue
736 msg_id = job.msg_id
737
738 put_it_back = True
739
740 if job.after.unreachable(self.all_completed, self.all_failed)\
741 or job.follow.unreachable(self.all_completed, self.all_failed):
742 self.fail_unreachable(msg_id)
743 put_it_back = False
744
745 elif job.after.check(self.all_completed, self.all_failed): # time deps met, maybe run
746 if self.maybe_run(job):
747 put_it_back = False
748 self.queue_map.pop(msg_id)
749 for mid in job.dependents:
750 if mid in self.graph:
751 self.graph[mid].remove(msg_id)
752
753 # abort the loop if we just filled up all of our engines.
754 # avoids an O(N) operation in situation of full queue,
755 # where graph update is triggered as soon as an engine becomes
756 # non-full, and all tasks after the first are checked,
757 # even though they can't run.
758 if not self.available_engines():
759 break
760
761 if using_queue and put_it_back:
762 # popped a job from the queue but it neither ran nor failed,
763 # so we need to put it back when we are done
764 # make sure to_restore preserves the same ordering
765 to_restore.append(job)
766
767 # put back any tasks we popped but didn't run
768 if using_queue:
769 self.queue.extendleft(to_restore)
770
771 #----------------------------------------------------------------------
772 # methods to be overridden by subclasses
773 #----------------------------------------------------------------------
774
775 def add_job(self, idx):
776 """Called after self.targets[idx] just got the job with header.
777 Override with subclasses. The default ordering is simple LRU.
778 The default loads are the number of outstanding jobs."""
779 self.loads[idx] += 1
780 for lis in (self.targets, self.loads):
781 lis.append(lis.pop(idx))
782
783
784 def finish_job(self, idx):
785 """Called after self.targets[idx] just finished a job.
786 Override with subclasses."""
787 self.loads[idx] -= 1
788
789
790
791 def launch_scheduler(in_addr, out_addr, mon_addr, not_addr, reg_addr, config=None,
792 logname='root', log_url=None, loglevel=logging.DEBUG,
793 identity=b'task', in_thread=False):
794
795 ZMQStream = zmqstream.ZMQStream
796
797 if config:
798 # unwrap dict back into Config
799 config = Config(config)
800
801 if in_thread:
802 # use instance() to get the same Context/Loop as our parent
803 ctx = zmq.Context.instance()
804 loop = ioloop.IOLoop.instance()
805 else:
806 # in a process, don't use instance()
807 # for safety with multiprocessing
808 ctx = zmq.Context()
809 loop = ioloop.IOLoop()
810 ins = ZMQStream(ctx.socket(zmq.ROUTER),loop)
811 util.set_hwm(ins, 0)
812 ins.setsockopt(zmq.IDENTITY, identity + b'_in')
813 ins.bind(in_addr)
814
815 outs = ZMQStream(ctx.socket(zmq.ROUTER),loop)
816 util.set_hwm(outs, 0)
817 outs.setsockopt(zmq.IDENTITY, identity + b'_out')
818 outs.bind(out_addr)
819 mons = zmqstream.ZMQStream(ctx.socket(zmq.PUB),loop)
820 util.set_hwm(mons, 0)
821 mons.connect(mon_addr)
822 nots = zmqstream.ZMQStream(ctx.socket(zmq.SUB),loop)
823 nots.setsockopt(zmq.SUBSCRIBE, b'')
824 nots.connect(not_addr)
825
826 querys = ZMQStream(ctx.socket(zmq.DEALER),loop)
827 querys.connect(reg_addr)
828
829 # setup logging.
830 if in_thread:
831 log = Application.instance().log
832 else:
833 if log_url:
834 log = connect_logger(logname, ctx, log_url, root="scheduler", loglevel=loglevel)
835 else:
836 log = local_logger(logname, loglevel)
837
838 scheduler = TaskScheduler(client_stream=ins, engine_stream=outs,
839 mon_stream=mons, notifier_stream=nots,
840 query_stream=querys,
841 loop=loop, log=log,
842 config=config)
843 scheduler.start()
844 if not in_thread:
845 try:
846 loop.start()
847 except KeyboardInterrupt:
848 scheduler.log.critical("Interrupted, exiting...")
849
@@ -1,414 +0,0 b''
1 """A TaskRecord backend using sqlite3"""
2
3 # Copyright (c) IPython Development Team.
4 # Distributed under the terms of the Modified BSD License.
5
6 import json
7 import os
8 try:
9 import cPickle as pickle
10 except ImportError:
11 import pickle
12 from datetime import datetime
13
14 try:
15 import sqlite3
16 except ImportError:
17 sqlite3 = None
18
19 from zmq.eventloop import ioloop
20
21 from IPython.utils.traitlets import Unicode, Instance, List, Dict
22 from .dictdb import BaseDB
23 from jupyter_client.jsonutil import date_default, extract_dates, squash_dates
24 from IPython.utils.py3compat import iteritems
25
26 #-----------------------------------------------------------------------------
27 # SQLite operators, adapters, and converters
28 #-----------------------------------------------------------------------------
29
30 try:
31 buffer
32 except NameError:
33 # py3k
34 buffer = memoryview
35
36 operators = {
37 '$lt' : "<",
38 '$gt' : ">",
39 # null is handled weird with ==,!=
40 '$eq' : "=",
41 '$ne' : "!=",
42 '$lte': "<=",
43 '$gte': ">=",
44 '$in' : ('=', ' OR '),
45 '$nin': ('!=', ' AND '),
46 # '$all': None,
47 # '$mod': None,
48 # '$exists' : None
49 }
50 null_operators = {
51 '=' : "IS NULL",
52 '!=' : "IS NOT NULL",
53 }
54
55 def _adapt_dict(d):
56 return json.dumps(d, default=date_default)
57
58 def _convert_dict(ds):
59 if ds is None:
60 return ds
61 else:
62 if isinstance(ds, bytes):
63 # If I understand the sqlite doc correctly, this will always be utf8
64 ds = ds.decode('utf8')
65 return extract_dates(json.loads(ds))
66
67 def _adapt_bufs(bufs):
68 # this is *horrible*
69 # copy buffers into single list and pickle it:
70 if bufs and isinstance(bufs[0], (bytes, buffer)):
71 return sqlite3.Binary(pickle.dumps(list(map(bytes, bufs)),-1))
72 elif bufs:
73 return bufs
74 else:
75 return None
76
77 def _convert_bufs(bs):
78 if bs is None:
79 return []
80 else:
81 return pickle.loads(bytes(bs))
82
83 #-----------------------------------------------------------------------------
84 # SQLiteDB class
85 #-----------------------------------------------------------------------------
86
87 class SQLiteDB(BaseDB):
88 """SQLite3 TaskRecord backend."""
89
90 filename = Unicode('tasks.db', config=True,
91 help="""The filename of the sqlite task database. [default: 'tasks.db']""")
92 location = Unicode('', config=True,
93 help="""The directory containing the sqlite task database. The default
94 is to use the cluster_dir location.""")
95 table = Unicode("ipython-tasks", config=True,
96 help="""The SQLite Table to use for storing tasks for this session. If unspecified,
97 a new table will be created with the Hub's IDENT. Specifying the table will result
98 in tasks from previous sessions being available via Clients' db_query and
99 get_result methods.""")
100
101 if sqlite3 is not None:
102 _db = Instance('sqlite3.Connection', allow_none=True)
103 else:
104 _db = None
105 # the ordered list of column names
106 _keys = List(['msg_id' ,
107 'header' ,
108 'metadata',
109 'content',
110 'buffers',
111 'submitted',
112 'client_uuid' ,
113 'engine_uuid' ,
114 'started',
115 'completed',
116 'resubmitted',
117 'received',
118 'result_header' ,
119 'result_metadata',
120 'result_content' ,
121 'result_buffers' ,
122 'queue' ,
123 'execute_input' ,
124 'execute_result',
125 'error',
126 'stdout',
127 'stderr',
128 ])
129 # sqlite datatypes for checking that db is current format
130 _types = Dict({'msg_id' : 'text' ,
131 'header' : 'dict text',
132 'metadata' : 'dict text',
133 'content' : 'dict text',
134 'buffers' : 'bufs blob',
135 'submitted' : 'timestamp',
136 'client_uuid' : 'text',
137 'engine_uuid' : 'text',
138 'started' : 'timestamp',
139 'completed' : 'timestamp',
140 'resubmitted' : 'text',
141 'received' : 'timestamp',
142 'result_header' : 'dict text',
143 'result_metadata' : 'dict text',
144 'result_content' : 'dict text',
145 'result_buffers' : 'bufs blob',
146 'queue' : 'text',
147 'execute_input' : 'text',
148 'execute_result' : 'text',
149 'error' : 'text',
150 'stdout' : 'text',
151 'stderr' : 'text',
152 })
153
154 def __init__(self, **kwargs):
155 super(SQLiteDB, self).__init__(**kwargs)
156 if sqlite3 is None:
157 raise ImportError("SQLiteDB requires sqlite3")
158 if not self.table:
159 # use session, and prefix _, since starting with # is illegal
160 self.table = '_'+self.session.replace('-','_')
161 if not self.location:
162 # get current profile
163 from IPython.core.application import BaseIPythonApplication
164 if BaseIPythonApplication.initialized():
165 app = BaseIPythonApplication.instance()
166 if app.profile_dir is not None:
167 self.location = app.profile_dir.location
168 else:
169 self.location = u'.'
170 else:
171 self.location = u'.'
172 self._init_db()
173
174 # register db commit as 2s periodic callback
175 # to prevent clogging pipes
176 # assumes we are being run in a zmq ioloop app
177 loop = ioloop.IOLoop.instance()
178 pc = ioloop.PeriodicCallback(self._db.commit, 2000, loop)
179 pc.start()
180
181 def _defaults(self, keys=None):
182 """create an empty record"""
183 d = {}
184 keys = self._keys if keys is None else keys
185 for key in keys:
186 d[key] = None
187 return d
188
189 def _check_table(self):
190 """Ensure that an incorrect table doesn't exist
191
192 If a bad (old) table does exist, return False
193 """
194 cursor = self._db.execute("PRAGMA table_info('%s')"%self.table)
195 lines = cursor.fetchall()
196 if not lines:
197 # table does not exist
198 return True
199 types = {}
200 keys = []
201 for line in lines:
202 keys.append(line[1])
203 types[line[1]] = line[2]
204 if self._keys != keys:
205 # key mismatch
206 self.log.warn('keys mismatch')
207 return False
208 for key in self._keys:
209 if types[key] != self._types[key]:
210 self.log.warn(
211 'type mismatch: %s: %s != %s'%(key,types[key],self._types[key])
212 )
213 return False
214 return True
215
216 def _init_db(self):
217 """Connect to the database and get new session number."""
218 # register adapters
219 sqlite3.register_adapter(dict, _adapt_dict)
220 sqlite3.register_converter('dict', _convert_dict)
221 sqlite3.register_adapter(list, _adapt_bufs)
222 sqlite3.register_converter('bufs', _convert_bufs)
223 # connect to the db
224 dbfile = os.path.join(self.location, self.filename)
225 self._db = sqlite3.connect(dbfile, detect_types=sqlite3.PARSE_DECLTYPES,
226 # isolation_level = None)#,
227 cached_statements=64)
228 # print dir(self._db)
229 first_table = previous_table = self.table
230 i=0
231 while not self._check_table():
232 i+=1
233 self.table = first_table+'_%i'%i
234 self.log.warn(
235 "Table %s exists and doesn't match db format, trying %s"%
236 (previous_table, self.table)
237 )
238 previous_table = self.table
239
240 self._db.execute("""CREATE TABLE IF NOT EXISTS '%s'
241 (msg_id text PRIMARY KEY,
242 header dict text,
243 metadata dict text,
244 content dict text,
245 buffers bufs blob,
246 submitted timestamp,
247 client_uuid text,
248 engine_uuid text,
249 started timestamp,
250 completed timestamp,
251 resubmitted text,
252 received timestamp,
253 result_header dict text,
254 result_metadata dict text,
255 result_content dict text,
256 result_buffers bufs blob,
257 queue text,
258 execute_input text,
259 execute_result text,
260 error text,
261 stdout text,
262 stderr text)
263 """%self.table)
264 self._db.commit()
265
266 def _dict_to_list(self, d):
267 """turn a mongodb-style record dict into a list."""
268
269 return [ d[key] for key in self._keys ]
270
271 def _list_to_dict(self, line, keys=None):
272 """Inverse of dict_to_list"""
273 keys = self._keys if keys is None else keys
274 d = self._defaults(keys)
275 for key,value in zip(keys, line):
276 d[key] = value
277
278 return d
279
280 def _render_expression(self, check):
281 """Turn a mongodb-style search dict into an SQL query."""
282 expressions = []
283 args = []
284
285 skeys = set(check.keys())
286 skeys.difference_update(set(self._keys))
287 skeys.difference_update(set(['buffers', 'result_buffers']))
288 if skeys:
289 raise KeyError("Illegal testing key(s): %s"%skeys)
290
291 for name,sub_check in iteritems(check):
292 if isinstance(sub_check, dict):
293 for test,value in iteritems(sub_check):
294 try:
295 op = operators[test]
296 except KeyError:
297 raise KeyError("Unsupported operator: %r"%test)
298 if isinstance(op, tuple):
299 op, join = op
300
301 if value is None and op in null_operators:
302 expr = "%s %s" % (name, null_operators[op])
303 else:
304 expr = "%s %s ?"%(name, op)
305 if isinstance(value, (tuple,list)):
306 if op in null_operators and any([v is None for v in value]):
307 # equality tests don't work with NULL
308 raise ValueError("Cannot use %r test with NULL values on SQLite backend"%test)
309 expr = '( %s )'%( join.join([expr]*len(value)) )
310 args.extend(value)
311 else:
312 args.append(value)
313 expressions.append(expr)
314 else:
315 # it's an equality check
316 if sub_check is None:
317 expressions.append("%s IS NULL" % name)
318 else:
319 expressions.append("%s = ?"%name)
320 args.append(sub_check)
321
322 expr = " AND ".join(expressions)
323 return expr, args
324
325 def add_record(self, msg_id, rec):
326 """Add a new Task Record, by msg_id."""
327 d = self._defaults()
328 d.update(rec)
329 d['msg_id'] = msg_id
330 line = self._dict_to_list(d)
331 tups = '(%s)'%(','.join(['?']*len(line)))
332 self._db.execute("INSERT INTO '%s' VALUES %s"%(self.table, tups), line)
333 # self._db.commit()
334
335 def get_record(self, msg_id):
336 """Get a specific Task Record, by msg_id."""
337 cursor = self._db.execute("""SELECT * FROM '%s' WHERE msg_id==?"""%self.table, (msg_id,))
338 line = cursor.fetchone()
339 if line is None:
340 raise KeyError("No such msg: %r"%msg_id)
341 return self._list_to_dict(line)
342
343 def update_record(self, msg_id, rec):
344 """Update the data in an existing record."""
345 query = "UPDATE '%s' SET "%self.table
346 sets = []
347 keys = sorted(rec.keys())
348 values = []
349 for key in keys:
350 sets.append('%s = ?'%key)
351 values.append(rec[key])
352 query += ', '.join(sets)
353 query += ' WHERE msg_id == ?'
354 values.append(msg_id)
355 self._db.execute(query, values)
356 # self._db.commit()
357
358 def drop_record(self, msg_id):
359 """Remove a record from the DB."""
360 self._db.execute("""DELETE FROM '%s' WHERE msg_id==?"""%self.table, (msg_id,))
361 # self._db.commit()
362
363 def drop_matching_records(self, check):
364 """Remove a record from the DB."""
365 expr,args = self._render_expression(check)
366 query = "DELETE FROM '%s' WHERE %s"%(self.table, expr)
367 self._db.execute(query,args)
368 # self._db.commit()
369
370 def find_records(self, check, keys=None):
371 """Find records matching a query dict, optionally extracting subset of keys.
372
373 Returns list of matching records.
374
375 Parameters
376 ----------
377
378 check: dict
379 mongodb-style query argument
380 keys: list of strs [optional]
381 if specified, the subset of keys to extract. msg_id will *always* be
382 included.
383 """
384 if keys:
385 bad_keys = [ key for key in keys if key not in self._keys ]
386 if bad_keys:
387 raise KeyError("Bad record key(s): %s"%bad_keys)
388
389 if keys:
390 # ensure msg_id is present and first:
391 if 'msg_id' in keys:
392 keys.remove('msg_id')
393 keys.insert(0, 'msg_id')
394 req = ', '.join(keys)
395 else:
396 req = '*'
397 expr,args = self._render_expression(check)
398 query = """SELECT %s FROM '%s' WHERE %s"""%(req, self.table, expr)
399 cursor = self._db.execute(query, args)
400 matches = cursor.fetchall()
401 records = []
402 for line in matches:
403 rec = self._list_to_dict(line, keys)
404 records.append(rec)
405 return records
406
407 def get_history(self):
408 """get all msg_ids, ordered by time submitted."""
409 query = """SELECT msg_id FROM '%s' ORDER by submitted ASC"""%self.table
410 cursor = self._db.execute(query)
411 # will be a list of length 1 tuples
412 return [ tup[0] for tup in cursor.fetchall()]
413
414 __all__ = ['SQLiteDB']
1 NO CONTENT: file was removed
NO CONTENT: file was removed
@@ -1,6 +0,0 b''
1 def main():
2 from ipython_parallel.apps import ipengineapp as app
3 app.launch_new_instance()
4
5 if __name__ == '__main__':
6 main()
@@ -1,301 +0,0 b''
1 """A simple engine that talks to a controller over 0MQ.
2 it handles registration, etc. and launches a kernel
3 connected to the Controller's Schedulers.
4 """
5
6 # Copyright (c) IPython Development Team.
7 # Distributed under the terms of the Modified BSD License.
8
9 from __future__ import print_function
10
11 import sys
12 import time
13 from getpass import getpass
14
15 import zmq
16 from zmq.eventloop import ioloop, zmqstream
17
18 from IPython.utils.localinterfaces import localhost
19 from IPython.utils.traitlets import (
20 Instance, Dict, Integer, Type, Float, Unicode, CBytes, Bool
21 )
22 from IPython.utils.py3compat import cast_bytes
23
24 from ipython_parallel.controller.heartmonitor import Heart
25 from ipython_parallel.factory import RegistrationFactory
26 from ipython_parallel.util import disambiguate_url
27
28 from IPython.kernel.zmq.ipkernel import IPythonKernel as Kernel
29 from IPython.kernel.zmq.kernelapp import IPKernelApp
30
31 class EngineFactory(RegistrationFactory):
32 """IPython engine"""
33
34 # configurables:
35 out_stream_factory=Type('IPython.kernel.zmq.iostream.OutStream', config=True,
36 help="""The OutStream for handling stdout/err.
37 Typically 'IPython.kernel.zmq.iostream.OutStream'""")
38 display_hook_factory=Type('IPython.kernel.zmq.displayhook.ZMQDisplayHook', config=True,
39 help="""The class for handling displayhook.
40 Typically 'IPython.kernel.zmq.displayhook.ZMQDisplayHook'""")
41 location=Unicode(config=True,
42 help="""The location (an IP address) of the controller. This is
43 used for disambiguating URLs, to determine whether
44 loopback should be used to connect or the public address.""")
45 timeout=Float(5.0, config=True,
46 help="""The time (in seconds) to wait for the Controller to respond
47 to registration requests before giving up.""")
48 max_heartbeat_misses=Integer(50, config=True,
49 help="""The maximum number of times a check for the heartbeat ping of a
50 controller can be missed before shutting down the engine.
51
52 If set to 0, the check is disabled.""")
53 sshserver=Unicode(config=True,
54 help="""The SSH server to use for tunneling connections to the Controller.""")
55 sshkey=Unicode(config=True,
56 help="""The SSH private key file to use when tunneling connections to the Controller.""")
57 paramiko=Bool(sys.platform == 'win32', config=True,
58 help="""Whether to use paramiko instead of openssh for tunnels.""")
59
60 @property
61 def tunnel_mod(self):
62 from zmq.ssh import tunnel
63 return tunnel
64
65
66 # not configurable:
67 connection_info = Dict()
68 user_ns = Dict()
69 id = Integer(allow_none=True)
70 registrar = Instance('zmq.eventloop.zmqstream.ZMQStream', allow_none=True)
71 kernel = Instance(Kernel, allow_none=True)
72 hb_check_period=Integer()
73
74 # States for the heartbeat monitoring
75 # Initial values for monitored and pinged must satisfy "monitored > pinged == False" so that
76 # during the first check no "missed" ping is reported. Must be floats for Python 3 compatibility.
77 _hb_last_pinged = 0.0
78 _hb_last_monitored = 0.0
79 _hb_missed_beats = 0
80 # The zmq Stream which receives the pings from the Heart
81 _hb_listener = None
82
83 bident = CBytes()
84 ident = Unicode()
85 def _ident_changed(self, name, old, new):
86 self.bident = cast_bytes(new)
87 using_ssh=Bool(False)
88
89
90 def __init__(self, **kwargs):
91 super(EngineFactory, self).__init__(**kwargs)
92 self.ident = self.session.session
93
94 def init_connector(self):
95 """construct connection function, which handles tunnels."""
96 self.using_ssh = bool(self.sshkey or self.sshserver)
97
98 if self.sshkey and not self.sshserver:
99 # We are using ssh directly to the controller, tunneling localhost to localhost
100 self.sshserver = self.url.split('://')[1].split(':')[0]
101
102 if self.using_ssh:
103 if self.tunnel_mod.try_passwordless_ssh(self.sshserver, self.sshkey, self.paramiko):
104 password=False
105 else:
106 password = getpass("SSH Password for %s: "%self.sshserver)
107 else:
108 password = False
109
110 def connect(s, url):
111 url = disambiguate_url(url, self.location)
112 if self.using_ssh:
113 self.log.debug("Tunneling connection to %s via %s", url, self.sshserver)
114 return self.tunnel_mod.tunnel_connection(s, url, self.sshserver,
115 keyfile=self.sshkey, paramiko=self.paramiko,
116 password=password,
117 )
118 else:
119 return s.connect(url)
120
121 def maybe_tunnel(url):
122 """like connect, but don't complete the connection (for use by heartbeat)"""
123 url = disambiguate_url(url, self.location)
124 if self.using_ssh:
125 self.log.debug("Tunneling connection to %s via %s", url, self.sshserver)
126 url, tunnelobj = self.tunnel_mod.open_tunnel(url, self.sshserver,
127 keyfile=self.sshkey, paramiko=self.paramiko,
128 password=password,
129 )
130 return str(url)
131 return connect, maybe_tunnel
132
133 def register(self):
134 """send the registration_request"""
135
136 self.log.info("Registering with controller at %s"%self.url)
137 ctx = self.context
138 connect,maybe_tunnel = self.init_connector()
139 reg = ctx.socket(zmq.DEALER)
140 reg.setsockopt(zmq.IDENTITY, self.bident)
141 connect(reg, self.url)
142 self.registrar = zmqstream.ZMQStream(reg, self.loop)
143
144
145 content = dict(uuid=self.ident)
146 self.registrar.on_recv(lambda msg: self.complete_registration(msg, connect, maybe_tunnel))
147 # print (self.session.key)
148 self.session.send(self.registrar, "registration_request", content=content)
149
150 def _report_ping(self, msg):
151 """Callback for when the heartmonitor.Heart receives a ping"""
152 #self.log.debug("Received a ping: %s", msg)
153 self._hb_last_pinged = time.time()
154
155 def complete_registration(self, msg, connect, maybe_tunnel):
156 # print msg
157 self.loop.remove_timeout(self._abort_timeout)
158 ctx = self.context
159 loop = self.loop
160 identity = self.bident
161 idents,msg = self.session.feed_identities(msg)
162 msg = self.session.deserialize(msg)
163 content = msg['content']
164 info = self.connection_info
165
166 def url(key):
167 """get zmq url for given channel"""
168 return str(info["interface"] + ":%i" % info[key])
169
170 if content['status'] == 'ok':
171 self.id = int(content['id'])
172
173 # launch heartbeat
174 # possibly forward hb ports with tunnels
175 hb_ping = maybe_tunnel(url('hb_ping'))
176 hb_pong = maybe_tunnel(url('hb_pong'))
177
178 hb_monitor = None
179 if self.max_heartbeat_misses > 0:
180 # Add a monitor socket which will record the last time a ping was seen
181 mon = self.context.socket(zmq.SUB)
182 mport = mon.bind_to_random_port('tcp://%s' % localhost())
183 mon.setsockopt(zmq.SUBSCRIBE, b"")
184 self._hb_listener = zmqstream.ZMQStream(mon, self.loop)
185 self._hb_listener.on_recv(self._report_ping)
186
187
188 hb_monitor = "tcp://%s:%i" % (localhost(), mport)
189
190 heart = Heart(hb_ping, hb_pong, hb_monitor , heart_id=identity)
191 heart.start()
192
193 # create Shell Connections (MUX, Task, etc.):
194 shell_addrs = url('mux'), url('task')
195
196 # Use only one shell stream for mux and tasks
197 stream = zmqstream.ZMQStream(ctx.socket(zmq.ROUTER), loop)
198 stream.setsockopt(zmq.IDENTITY, identity)
199 shell_streams = [stream]
200 for addr in shell_addrs:
201 connect(stream, addr)
202
203 # control stream:
204 control_addr = url('control')
205 control_stream = zmqstream.ZMQStream(ctx.socket(zmq.ROUTER), loop)
206 control_stream.setsockopt(zmq.IDENTITY, identity)
207 connect(control_stream, control_addr)
208
209 # create iopub stream:
210 iopub_addr = url('iopub')
211 iopub_socket = ctx.socket(zmq.PUB)
212 iopub_socket.setsockopt(zmq.IDENTITY, identity)
213 connect(iopub_socket, iopub_addr)
214
215 # disable history:
216 self.config.HistoryManager.hist_file = ':memory:'
217
218 # Redirect input streams and set a display hook.
219 if self.out_stream_factory:
220 sys.stdout = self.out_stream_factory(self.session, iopub_socket, u'stdout')
221 sys.stdout.topic = cast_bytes('engine.%i.stdout' % self.id)
222 sys.stderr = self.out_stream_factory(self.session, iopub_socket, u'stderr')
223 sys.stderr.topic = cast_bytes('engine.%i.stderr' % self.id)
224 if self.display_hook_factory:
225 sys.displayhook = self.display_hook_factory(self.session, iopub_socket)
226 sys.displayhook.topic = cast_bytes('engine.%i.execute_result' % self.id)
227
228 self.kernel = Kernel(parent=self, int_id=self.id, ident=self.ident, session=self.session,
229 control_stream=control_stream, shell_streams=shell_streams, iopub_socket=iopub_socket,
230 loop=loop, user_ns=self.user_ns, log=self.log)
231
232 self.kernel.shell.display_pub.topic = cast_bytes('engine.%i.displaypub' % self.id)
233
234
235 # periodically check the heartbeat pings of the controller
236 # Should be started here and not in "start()" so that the right period can be taken
237 # from the hubs HeartBeatMonitor.period
238 if self.max_heartbeat_misses > 0:
239 # Use a slightly bigger check period than the hub signal period to not warn unnecessary
240 self.hb_check_period = int(content['hb_period'])+10
241 self.log.info("Starting to monitor the heartbeat signal from the hub every %i ms." , self.hb_check_period)
242 self._hb_reporter = ioloop.PeriodicCallback(self._hb_monitor, self.hb_check_period, self.loop)
243 self._hb_reporter.start()
244 else:
245 self.log.info("Monitoring of the heartbeat signal from the hub is not enabled.")
246
247
248 # FIXME: This is a hack until IPKernelApp and IPEngineApp can be fully merged
249 app = IPKernelApp(parent=self, shell=self.kernel.shell, kernel=self.kernel, log=self.log)
250 app.init_profile_dir()
251 app.init_code()
252
253 self.kernel.start()
254 else:
255 self.log.fatal("Registration Failed: %s"%msg)
256 raise Exception("Registration Failed: %s"%msg)
257
258 self.log.info("Completed registration with id %i"%self.id)
259
260
261 def abort(self):
262 self.log.fatal("Registration timed out after %.1f seconds"%self.timeout)
263 if self.url.startswith('127.'):
264 self.log.fatal("""
265 If the controller and engines are not on the same machine,
266 you will have to instruct the controller to listen on an external IP (in ipcontroller_config.py):
267 c.HubFactory.ip='*' # for all interfaces, internal and external
268 c.HubFactory.ip='192.168.1.101' # or any interface that the engines can see
269 or tunnel connections via ssh.
270 """)
271 self.session.send(self.registrar, "unregistration_request", content=dict(id=self.id))
272 time.sleep(1)
273 sys.exit(255)
274
275 def _hb_monitor(self):
276 """Callback to monitor the heartbeat from the controller"""
277 self._hb_listener.flush()
278 if self._hb_last_monitored > self._hb_last_pinged:
279 self._hb_missed_beats += 1
280 self.log.warn("No heartbeat in the last %s ms (%s time(s) in a row).", self.hb_check_period, self._hb_missed_beats)
281 else:
282 #self.log.debug("Heartbeat received (after missing %s beats).", self._hb_missed_beats)
283 self._hb_missed_beats = 0
284
285 if self._hb_missed_beats >= self.max_heartbeat_misses:
286 self.log.fatal("Maximum number of heartbeats misses reached (%s times %s ms), shutting down.",
287 self.max_heartbeat_misses, self.hb_check_period)
288 self.session.send(self.registrar, "unregistration_request", content=dict(id=self.id))
289 self.loop.stop()
290
291 self._hb_last_monitored = time.time()
292
293
294 def start(self):
295 loop = self.loop
296 def _start():
297 self.register()
298 self._abort_timeout = loop.add_timeout(loop.time() + self.timeout, self.abort)
299 self.loop.add_callback(_start)
300
301
@@ -1,252 +0,0 b''
1 # encoding: utf-8
2
3 """Classes and functions for kernel related errors and exceptions.
4
5 Inheritance diagram:
6
7 .. inheritance-diagram:: ipython_parallel.error
8 :parts: 3
9
10 Authors:
11
12 * Brian Granger
13 * Min RK
14 """
15 from __future__ import print_function
16
17 import sys
18 import traceback
19
20 from IPython.utils.py3compat import unicode_type
21
22 __docformat__ = "restructuredtext en"
23
24 # Tell nose to skip this module
25 __test__ = {}
26
27 #-------------------------------------------------------------------------------
28 # Copyright (C) 2008-2011 The IPython Development Team
29 #
30 # Distributed under the terms of the BSD License. The full license is in
31 # the file COPYING, distributed as part of this software.
32 #-------------------------------------------------------------------------------
33
34 #-------------------------------------------------------------------------------
35 # Error classes
36 #-------------------------------------------------------------------------------
37 class IPythonError(Exception):
38 """Base exception that all of our exceptions inherit from.
39
40 This can be raised by code that doesn't have any more specific
41 information."""
42
43 pass
44
45 class KernelError(IPythonError):
46 pass
47
48 class EngineError(KernelError):
49 pass
50
51 class NoEnginesRegistered(KernelError):
52 pass
53
54 class TaskAborted(KernelError):
55 pass
56
57 class TaskTimeout(KernelError):
58 pass
59
60 class TimeoutError(KernelError):
61 pass
62
63 class UnmetDependency(KernelError):
64 pass
65
66 class ImpossibleDependency(UnmetDependency):
67 pass
68
69 class DependencyTimeout(ImpossibleDependency):
70 pass
71
72 class InvalidDependency(ImpossibleDependency):
73 pass
74
75 class RemoteError(KernelError):
76 """Error raised elsewhere"""
77 ename=None
78 evalue=None
79 traceback=None
80 engine_info=None
81
82 def __init__(self, ename, evalue, traceback, engine_info=None):
83 self.ename=ename
84 self.evalue=evalue
85 self.traceback=traceback
86 self.engine_info=engine_info or {}
87 self.args=(ename, evalue)
88
89 def __repr__(self):
90 engineid = self.engine_info.get('engine_id', ' ')
91 return "<Remote[%s]:%s(%s)>"%(engineid, self.ename, self.evalue)
92
93 def __str__(self):
94 return "%s(%s)" % (self.ename, self.evalue)
95
96 def render_traceback(self):
97 """render traceback to a list of lines"""
98 return (self.traceback or "No traceback available").splitlines()
99
100 def _render_traceback_(self):
101 """Special method for custom tracebacks within IPython.
102
103 This will be called by IPython instead of displaying the local traceback.
104
105 It should return a traceback rendered as a list of lines.
106 """
107 return self.render_traceback()
108
109 def print_traceback(self, excid=None):
110 """print my traceback"""
111 print('\n'.join(self.render_traceback()))
112
113
114
115
116 class TaskRejectError(KernelError):
117 """Exception to raise when a task should be rejected by an engine.
118
119 This exception can be used to allow a task running on an engine to test
120 if the engine (or the user's namespace on the engine) has the needed
121 task dependencies. If not, the task should raise this exception. For
122 the task to be retried on another engine, the task should be created
123 with the `retries` argument > 1.
124
125 The advantage of this approach over our older properties system is that
126 tasks have full access to the user's namespace on the engines and the
127 properties don't have to be managed or tested by the controller.
128 """
129
130
131 class CompositeError(RemoteError):
132 """Error for representing possibly multiple errors on engines"""
133 tb_limit = 4 # limit on how many tracebacks to draw
134
135 def __init__(self, message, elist):
136 Exception.__init__(self, *(message, elist))
137 # Don't use pack_exception because it will conflict with the .message
138 # attribute that is being deprecated in 2.6 and beyond.
139 self.msg = message
140 self.elist = elist
141 self.args = [ e[0] for e in elist ]
142
143 def _get_engine_str(self, ei):
144 if not ei:
145 return '[Engine Exception]'
146 else:
147 return '[%s:%s]: ' % (ei['engine_id'], ei['method'])
148
149 def _get_traceback(self, ev):
150 try:
151 tb = ev._ipython_traceback_text
152 except AttributeError:
153 return 'No traceback available'
154 else:
155 return tb
156
157 def __str__(self):
158 s = str(self.msg)
159 for en, ev, etb, ei in self.elist[:self.tb_limit]:
160 engine_str = self._get_engine_str(ei)
161 s = s + '\n' + engine_str + en + ': ' + str(ev)
162 if len(self.elist) > self.tb_limit:
163 s = s + '\n.... %i more exceptions ...' % (len(self.elist) - self.tb_limit)
164 return s
165
166 def __repr__(self):
167 return "CompositeError(%i)" % len(self.elist)
168
169 def render_traceback(self, excid=None):
170 """render one or all of my tracebacks to a list of lines"""
171 lines = []
172 if excid is None:
173 for (en,ev,etb,ei) in self.elist[:self.tb_limit]:
174 lines.append(self._get_engine_str(ei))
175 lines.extend((etb or 'No traceback available').splitlines())
176 lines.append('')
177 if len(self.elist) > self.tb_limit:
178 lines.append(
179 '... %i more exceptions ...' % (len(self.elist) - self.tb_limit)
180 )
181 else:
182 try:
183 en,ev,etb,ei = self.elist[excid]
184 except:
185 raise IndexError("an exception with index %i does not exist"%excid)
186 else:
187 lines.append(self._get_engine_str(ei))
188 lines.extend((etb or 'No traceback available').splitlines())
189
190 return lines
191
192 def print_traceback(self, excid=None):
193 print('\n'.join(self.render_traceback(excid)))
194
195 def raise_exception(self, excid=0):
196 try:
197 en,ev,etb,ei = self.elist[excid]
198 except:
199 raise IndexError("an exception with index %i does not exist"%excid)
200 else:
201 raise RemoteError(en, ev, etb, ei)
202
203
204 def collect_exceptions(rdict_or_list, method='unspecified'):
205 """check a result dict for errors, and raise CompositeError if any exist.
206 Passthrough otherwise."""
207 elist = []
208 if isinstance(rdict_or_list, dict):
209 rlist = rdict_or_list.values()
210 else:
211 rlist = rdict_or_list
212 for r in rlist:
213 if isinstance(r, RemoteError):
214 en, ev, etb, ei = r.ename, r.evalue, r.traceback, r.engine_info
215 # Sometimes we could have CompositeError in our list. Just take
216 # the errors out of them and put them in our new list. This
217 # has the effect of flattening lists of CompositeErrors into one
218 # CompositeError
219 if en=='CompositeError':
220 for e in ev.elist:
221 elist.append(e)
222 else:
223 elist.append((en, ev, etb, ei))
224 if len(elist)==0:
225 return rdict_or_list
226 else:
227 msg = "one or more exceptions from call to method: %s" % (method)
228 # This silliness is needed so the debugger has access to the exception
229 # instance (e in this case)
230 try:
231 raise CompositeError(msg, elist)
232 except CompositeError as e:
233 raise e
234
235 def wrap_exception(engine_info={}):
236 etype, evalue, tb = sys.exc_info()
237 stb = traceback.format_exception(etype, evalue, tb)
238 exc_content = {
239 'status' : 'error',
240 'traceback' : stb,
241 'ename' : unicode_type(etype.__name__),
242 'evalue' : unicode_type(evalue),
243 'engine_info' : engine_info
244 }
245 return exc_content
246
247 def unwrap_exception(content):
248 err = RemoteError(content['ename'], content['evalue'],
249 ''.join(content['traceback']),
250 content.get('engine_info', {}))
251 return err
252
@@ -1,73 +0,0 b''
1 """Base config factories.
2
3 Authors:
4
5 * Min RK
6 """
7
8 #-----------------------------------------------------------------------------
9 # Copyright (C) 2010-2011 The IPython Development Team
10 #
11 # Distributed under the terms of the BSD License. The full license is in
12 # the file COPYING, distributed as part of this software.
13 #-----------------------------------------------------------------------------
14
15 #-----------------------------------------------------------------------------
16 # Imports
17 #-----------------------------------------------------------------------------
18
19 from IPython.utils.localinterfaces import localhost
20 from IPython.utils.traitlets import Integer, Unicode
21
22 from ipython_parallel.util import select_random_ports
23 from IPython.kernel.zmq.session import SessionFactory
24
25 #-----------------------------------------------------------------------------
26 # Classes
27 #-----------------------------------------------------------------------------
28
29
30 class RegistrationFactory(SessionFactory):
31 """The Base Configurable for objects that involve registration."""
32
33 url = Unicode('', config=True,
34 help="""The 0MQ url used for registration. This sets transport, ip, and port
35 in one variable. For example: url='tcp://127.0.0.1:12345' or
36 url='epgm://*:90210'"""
37 ) # url takes precedence over ip,regport,transport
38 transport = Unicode('tcp', config=True,
39 help="""The 0MQ transport for communications. This will likely be
40 the default of 'tcp', but other values include 'ipc', 'epgm', 'inproc'.""")
41 ip = Unicode(config=True,
42 help="""The IP address for registration. This is generally either
43 '127.0.0.1' for loopback only or '*' for all interfaces.
44 """)
45 def _ip_default(self):
46 return localhost()
47 regport = Integer(config=True,
48 help="""The port on which the Hub listens for registration.""")
49 def _regport_default(self):
50 return select_random_ports(1)[0]
51
52 def __init__(self, **kwargs):
53 super(RegistrationFactory, self).__init__(**kwargs)
54 self._propagate_url()
55 self._rebuild_url()
56 self.on_trait_change(self._propagate_url, 'url')
57 self.on_trait_change(self._rebuild_url, 'ip')
58 self.on_trait_change(self._rebuild_url, 'transport')
59 self.on_trait_change(self._rebuild_url, 'regport')
60
61 def _rebuild_url(self):
62 self.url = "%s://%s:%i"%(self.transport, self.ip, self.regport)
63
64 def _propagate_url(self):
65 """Ensure self.url contains full transport://interface:port"""
66 if self.url:
67 iface = self.url.split('://',1)
68 if len(iface) == 2:
69 self.transport,iface = iface
70 iface = iface.split(':')
71 self.ip = iface[0]
72 if iface[1]:
73 self.regport = int(iface[1])
@@ -1,3 +0,0 b''
1 if __name__ == '__main__':
2 from ipython_parallel.apps import iploggerapp as app
3 app.launch_new_instance()
@@ -1,145 +0,0 b''
1 """toplevel setup/teardown for parallel tests."""
2 from __future__ import print_function
3
4 #-------------------------------------------------------------------------------
5 # Copyright (C) 2011 The IPython Development Team
6 #
7 # Distributed under the terms of the BSD License. The full license is in
8 # the file COPYING, distributed as part of this software.
9 #-------------------------------------------------------------------------------
10
11 #-------------------------------------------------------------------------------
12 # Imports
13 #-------------------------------------------------------------------------------
14
15 import os
16 import tempfile
17 import time
18 from subprocess import Popen, PIPE, STDOUT
19
20 import nose
21
22 from IPython.utils.path import get_ipython_dir
23 from ipython_parallel import Client, error
24 from ipython_parallel.apps.launcher import (LocalProcessLauncher,
25 ipengine_cmd_argv,
26 ipcontroller_cmd_argv,
27 SIGKILL,
28 ProcessStateError,
29 )
30
31 # globals
32 launchers = []
33 blackhole = open(os.devnull, 'w')
34
35 # Launcher class
36 class TestProcessLauncher(LocalProcessLauncher):
37 """subclass LocalProcessLauncher, to prevent extra sockets and threads being created on Windows"""
38 def start(self):
39 if self.state == 'before':
40 # Store stdout & stderr to show with failing tests.
41 # This is defined in IPython.testing.iptest
42 self.process = Popen(self.args,
43 stdout=nose.iptest_stdstreams_fileno(), stderr=STDOUT,
44 env=os.environ,
45 cwd=self.work_dir
46 )
47 self.notify_start(self.process.pid)
48 self.poll = self.process.poll
49 else:
50 s = 'The process was already started and has state: %r' % self.state
51 raise ProcessStateError(s)
52
53 # nose setup/teardown
54
55 def setup():
56
57 # show tracebacks for RemoteErrors
58 class RemoteErrorWithTB(error.RemoteError):
59 def __str__(self):
60 s = super(RemoteErrorWithTB, self).__str__()
61 return '\n'.join([s, self.traceback or ''])
62
63 error.RemoteError = RemoteErrorWithTB
64
65 cluster_dir = os.path.join(get_ipython_dir(), 'profile_iptest')
66 engine_json = os.path.join(cluster_dir, 'security', 'ipcontroller-engine.json')
67 client_json = os.path.join(cluster_dir, 'security', 'ipcontroller-client.json')
68 for json in (engine_json, client_json):
69 if os.path.exists(json):
70 os.remove(json)
71
72 cp = TestProcessLauncher()
73 cp.cmd_and_args = ipcontroller_cmd_argv + \
74 ['--profile=iptest', '--log-level=20', '--ping=250', '--dictdb']
75 cp.start()
76 launchers.append(cp)
77 tic = time.time()
78 while not os.path.exists(engine_json) or not os.path.exists(client_json):
79 if cp.poll() is not None:
80 raise RuntimeError("The test controller exited with status %s" % cp.poll())
81 elif time.time()-tic > 15:
82 raise RuntimeError("Timeout waiting for the test controller to start.")
83 time.sleep(0.1)
84 add_engines(1)
85
86 def add_engines(n=1, profile='iptest', total=False):
87 """add a number of engines to a given profile.
88
89 If total is True, then already running engines are counted, and only
90 the additional engines necessary (if any) are started.
91 """
92 rc = Client(profile=profile)
93 base = len(rc)
94
95 if total:
96 n = max(n - base, 0)
97
98 eps = []
99 for i in range(n):
100 ep = TestProcessLauncher()
101 ep.cmd_and_args = ipengine_cmd_argv + [
102 '--profile=%s' % profile,
103 '--log-level=50',
104 '--InteractiveShell.colors=nocolor'
105 ]
106 ep.start()
107 launchers.append(ep)
108 eps.append(ep)
109 tic = time.time()
110 while len(rc) < base+n:
111 if any([ ep.poll() is not None for ep in eps ]):
112 raise RuntimeError("A test engine failed to start.")
113 elif time.time()-tic > 15:
114 raise RuntimeError("Timeout waiting for engines to connect.")
115 time.sleep(.1)
116 rc.spin()
117 rc.close()
118 return eps
119
120 def teardown():
121 try:
122 time.sleep(1)
123 except KeyboardInterrupt:
124 return
125 while launchers:
126 p = launchers.pop()
127 if p.poll() is None:
128 try:
129 p.stop()
130 except Exception as e:
131 print(e)
132 pass
133 if p.poll() is None:
134 try:
135 time.sleep(.25)
136 except KeyboardInterrupt:
137 return
138 if p.poll() is None:
139 try:
140 print('cleaning up test process...')
141 p.signal(SIGKILL)
142 except:
143 print("couldn't shutdown process: ", p)
144 blackhole.close()
145
@@ -1,192 +0,0 b''
1 """base class for parallel client tests
2
3 Authors:
4
5 * Min RK
6 """
7
8 #-------------------------------------------------------------------------------
9 # Copyright (C) 2011 The IPython Development Team
10 #
11 # Distributed under the terms of the BSD License. The full license is in
12 # the file COPYING, distributed as part of this software.
13 #-------------------------------------------------------------------------------
14 from __future__ import print_function
15
16 import sys
17 import tempfile
18 import time
19
20 from nose import SkipTest
21
22 import zmq
23 from zmq.tests import BaseZMQTestCase
24
25 from decorator import decorator
26
27 from ipython_parallel import error
28 from ipython_parallel import Client
29
30 from ipython_parallel.tests import launchers, add_engines
31
32 # simple tasks for use in apply tests
33
34 def segfault():
35 """this will segfault"""
36 import ctypes
37 ctypes.memset(-1,0,1)
38
39 def crash():
40 """from stdlib crashers in the test suite"""
41 import types
42 if sys.platform.startswith('win'):
43 import ctypes
44 ctypes.windll.kernel32.SetErrorMode(0x0002);
45 args = [ 0, 0, 0, 0, b'\x04\x71\x00\x00', (), (), (), '', '', 1, b'']
46 if sys.version_info[0] >= 3:
47 # Python3 adds 'kwonlyargcount' as the second argument to Code
48 args.insert(1, 0)
49
50 co = types.CodeType(*args)
51 exec(co)
52
53 def wait(n):
54 """sleep for a time"""
55 import time
56 time.sleep(n)
57 return n
58
59 def raiser(eclass):
60 """raise an exception"""
61 raise eclass()
62
63 def generate_output():
64 """function for testing output
65
66 publishes two outputs of each type, and returns
67 a rich displayable object.
68 """
69
70 import sys
71 from IPython.core.display import display, HTML, Math
72
73 print("stdout")
74 print("stderr", file=sys.stderr)
75
76 display(HTML("<b>HTML</b>"))
77
78 print("stdout2")
79 print("stderr2", file=sys.stderr)
80
81 display(Math(r"\alpha=\beta"))
82
83 return Math("42")
84
85 # test decorator for skipping tests when libraries are unavailable
86 def skip_without(*names):
87 """skip a test if some names are not importable"""
88 @decorator
89 def skip_without_names(f, *args, **kwargs):
90 """decorator to skip tests in the absence of numpy."""
91 for name in names:
92 try:
93 __import__(name)
94 except ImportError:
95 raise SkipTest
96 return f(*args, **kwargs)
97 return skip_without_names
98
99 #-------------------------------------------------------------------------------
100 # Classes
101 #-------------------------------------------------------------------------------
102
103
104 class ClusterTestCase(BaseZMQTestCase):
105 timeout = 10
106
107 def add_engines(self, n=1, block=True):
108 """add multiple engines to our cluster"""
109 self.engines.extend(add_engines(n))
110 if block:
111 self.wait_on_engines()
112
113 def minimum_engines(self, n=1, block=True):
114 """add engines until there are at least n connected"""
115 self.engines.extend(add_engines(n, total=True))
116 if block:
117 self.wait_on_engines()
118
119
120 def wait_on_engines(self, timeout=5):
121 """wait for our engines to connect."""
122 n = len(self.engines)+self.base_engine_count
123 tic = time.time()
124 while time.time()-tic < timeout and len(self.client.ids) < n:
125 time.sleep(0.1)
126
127 assert not len(self.client.ids) < n, "waiting for engines timed out"
128
129 def client_wait(self, client, jobs=None, timeout=-1):
130 """my wait wrapper, sets a default finite timeout to avoid hangs"""
131 if timeout < 0:
132 timeout = self.timeout
133 return Client.wait(client, jobs, timeout)
134
135 def connect_client(self):
136 """connect a client with my Context, and track its sockets for cleanup"""
137 c = Client(profile='iptest', context=self.context)
138 c.wait = lambda *a, **kw: self.client_wait(c, *a, **kw)
139
140 for name in filter(lambda n:n.endswith('socket'), dir(c)):
141 s = getattr(c, name)
142 s.setsockopt(zmq.LINGER, 0)
143 self.sockets.append(s)
144 return c
145
146 def assertRaisesRemote(self, etype, f, *args, **kwargs):
147 try:
148 try:
149 f(*args, **kwargs)
150 except error.CompositeError as e:
151 e.raise_exception()
152 except error.RemoteError as e:
153 self.assertEqual(etype.__name__, e.ename, "Should have raised %r, but raised %r"%(etype.__name__, e.ename))
154 else:
155 self.fail("should have raised a RemoteError")
156
157 def _wait_for(self, f, timeout=10):
158 """wait for a condition"""
159 tic = time.time()
160 while time.time() <= tic + timeout:
161 if f():
162 return
163 time.sleep(0.1)
164 self.client.spin()
165 if not f():
166 print("Warning: Awaited condition never arrived")
167
168 def setUp(self):
169 BaseZMQTestCase.setUp(self)
170 self.client = self.connect_client()
171 # start every test with clean engine namespaces:
172 self.client.clear(block=True)
173 self.base_engine_count=len(self.client.ids)
174 self.engines=[]
175
176 def tearDown(self):
177 # self.client.clear(block=True)
178 # close fds:
179 for e in filter(lambda e: e.poll() is not None, launchers):
180 launchers.remove(e)
181
182 # allow flushing of incoming messages to prevent crash on socket close
183 self.client.wait(timeout=2)
184 # time.sleep(2)
185 self.client.spin()
186 self.client.close()
187 BaseZMQTestCase.tearDown(self)
188 # this will be redundant when pyzmq merges PR #88
189 # self.context.term()
190 # print tempfile.TemporaryFile().fileno(),
191 # sys.stdout.flush()
192
@@ -1,342 +0,0 b''
1 """Tests for asyncresult.py"""
2
3 # Copyright (c) IPython Development Team.
4 # Distributed under the terms of the Modified BSD License.
5
6 import time
7
8 import nose.tools as nt
9
10 from IPython.utils.io import capture_output
11
12 from ipython_parallel.error import TimeoutError
13 from ipython_parallel import error, Client
14 from ipython_parallel.tests import add_engines
15 from .clienttest import ClusterTestCase
16 from IPython.utils.py3compat import iteritems
17
18 def setup():
19 add_engines(2, total=True)
20
21 def wait(n):
22 import time
23 time.sleep(n)
24 return n
25
26 def echo(x):
27 return x
28
29 class AsyncResultTest(ClusterTestCase):
30
31 def test_single_result_view(self):
32 """various one-target views get the right value for single_result"""
33 eid = self.client.ids[-1]
34 ar = self.client[eid].apply_async(lambda : 42)
35 self.assertEqual(ar.get(), 42)
36 ar = self.client[[eid]].apply_async(lambda : 42)
37 self.assertEqual(ar.get(), [42])
38 ar = self.client[-1:].apply_async(lambda : 42)
39 self.assertEqual(ar.get(), [42])
40
41 def test_get_after_done(self):
42 ar = self.client[-1].apply_async(lambda : 42)
43 ar.wait()
44 self.assertTrue(ar.ready())
45 self.assertEqual(ar.get(), 42)
46 self.assertEqual(ar.get(), 42)
47
48 def test_get_before_done(self):
49 ar = self.client[-1].apply_async(wait, 0.1)
50 self.assertRaises(TimeoutError, ar.get, 0)
51 ar.wait(0)
52 self.assertFalse(ar.ready())
53 self.assertEqual(ar.get(), 0.1)
54
55 def test_get_after_error(self):
56 ar = self.client[-1].apply_async(lambda : 1/0)
57 ar.wait(10)
58 self.assertRaisesRemote(ZeroDivisionError, ar.get)
59 self.assertRaisesRemote(ZeroDivisionError, ar.get)
60 self.assertRaisesRemote(ZeroDivisionError, ar.get_dict)
61
62 def test_get_dict(self):
63 n = len(self.client)
64 ar = self.client[:].apply_async(lambda : 5)
65 self.assertEqual(ar.get(), [5]*n)
66 d = ar.get_dict()
67 self.assertEqual(sorted(d.keys()), sorted(self.client.ids))
68 for eid,r in iteritems(d):
69 self.assertEqual(r, 5)
70
71 def test_get_dict_single(self):
72 view = self.client[-1]
73 for v in (list(range(5)), 5, ('abc', 'def'), 'string'):
74 ar = view.apply_async(echo, v)
75 self.assertEqual(ar.get(), v)
76 d = ar.get_dict()
77 self.assertEqual(d, {view.targets : v})
78
79 def test_get_dict_bad(self):
80 ar = self.client[:].apply_async(lambda : 5)
81 ar2 = self.client[:].apply_async(lambda : 5)
82 ar = self.client.get_result(ar.msg_ids + ar2.msg_ids)
83 self.assertRaises(ValueError, ar.get_dict)
84
85 def test_list_amr(self):
86 ar = self.client.load_balanced_view().map_async(wait, [0.1]*5)
87 rlist = list(ar)
88
89 def test_getattr(self):
90 ar = self.client[:].apply_async(wait, 0.5)
91 self.assertEqual(ar.engine_id, [None] * len(ar))
92 self.assertRaises(AttributeError, lambda : ar._foo)
93 self.assertRaises(AttributeError, lambda : ar.__length_hint__())
94 self.assertRaises(AttributeError, lambda : ar.foo)
95 self.assertFalse(hasattr(ar, '__length_hint__'))
96 self.assertFalse(hasattr(ar, 'foo'))
97 self.assertTrue(hasattr(ar, 'engine_id'))
98 ar.get(5)
99 self.assertRaises(AttributeError, lambda : ar._foo)
100 self.assertRaises(AttributeError, lambda : ar.__length_hint__())
101 self.assertRaises(AttributeError, lambda : ar.foo)
102 self.assertTrue(isinstance(ar.engine_id, list))
103 self.assertEqual(ar.engine_id, ar['engine_id'])
104 self.assertFalse(hasattr(ar, '__length_hint__'))
105 self.assertFalse(hasattr(ar, 'foo'))
106 self.assertTrue(hasattr(ar, 'engine_id'))
107
108 def test_getitem(self):
109 ar = self.client[:].apply_async(wait, 0.5)
110 self.assertEqual(ar['engine_id'], [None] * len(ar))
111 self.assertRaises(KeyError, lambda : ar['foo'])
112 ar.get(5)
113 self.assertRaises(KeyError, lambda : ar['foo'])
114 self.assertTrue(isinstance(ar['engine_id'], list))
115 self.assertEqual(ar.engine_id, ar['engine_id'])
116
117 def test_single_result(self):
118 ar = self.client[-1].apply_async(wait, 0.5)
119 self.assertRaises(KeyError, lambda : ar['foo'])
120 self.assertEqual(ar['engine_id'], None)
121 self.assertTrue(ar.get(5) == 0.5)
122 self.assertTrue(isinstance(ar['engine_id'], int))
123 self.assertTrue(isinstance(ar.engine_id, int))
124 self.assertEqual(ar.engine_id, ar['engine_id'])
125
126 def test_abort(self):
127 e = self.client[-1]
128 ar = e.execute('import time; time.sleep(1)', block=False)
129 ar2 = e.apply_async(lambda : 2)
130 ar2.abort()
131 self.assertRaises(error.TaskAborted, ar2.get)
132 ar.get()
133
134 def test_len(self):
135 v = self.client.load_balanced_view()
136 ar = v.map_async(lambda x: x, list(range(10)))
137 self.assertEqual(len(ar), 10)
138 ar = v.apply_async(lambda x: x, list(range(10)))
139 self.assertEqual(len(ar), 1)
140 ar = self.client[:].apply_async(lambda x: x, list(range(10)))
141 self.assertEqual(len(ar), len(self.client.ids))
142
143 def test_wall_time_single(self):
144 v = self.client.load_balanced_view()
145 ar = v.apply_async(time.sleep, 0.25)
146 self.assertRaises(TimeoutError, getattr, ar, 'wall_time')
147 ar.get(2)
148 self.assertTrue(ar.wall_time < 1.)
149 self.assertTrue(ar.wall_time > 0.2)
150
151 def test_wall_time_multi(self):
152 self.minimum_engines(4)
153 v = self.client[:]
154 ar = v.apply_async(time.sleep, 0.25)
155 self.assertRaises(TimeoutError, getattr, ar, 'wall_time')
156 ar.get(2)
157 self.assertTrue(ar.wall_time < 1.)
158 self.assertTrue(ar.wall_time > 0.2)
159
160 def test_serial_time_single(self):
161 v = self.client.load_balanced_view()
162 ar = v.apply_async(time.sleep, 0.25)
163 self.assertRaises(TimeoutError, getattr, ar, 'serial_time')
164 ar.get(2)
165 self.assertTrue(ar.serial_time < 1.)
166 self.assertTrue(ar.serial_time > 0.2)
167
168 def test_serial_time_multi(self):
169 self.minimum_engines(4)
170 v = self.client[:]
171 ar = v.apply_async(time.sleep, 0.25)
172 self.assertRaises(TimeoutError, getattr, ar, 'serial_time')
173 ar.get(2)
174 self.assertTrue(ar.serial_time < 2.)
175 self.assertTrue(ar.serial_time > 0.8)
176
177 def test_elapsed_single(self):
178 v = self.client.load_balanced_view()
179 ar = v.apply_async(time.sleep, 0.25)
180 while not ar.ready():
181 time.sleep(0.01)
182 self.assertTrue(ar.elapsed < 1)
183 self.assertTrue(ar.elapsed < 1)
184 ar.get(2)
185
186 def test_elapsed_multi(self):
187 v = self.client[:]
188 ar = v.apply_async(time.sleep, 0.25)
189 while not ar.ready():
190 time.sleep(0.01)
191 self.assertLess(ar.elapsed, 1)
192 self.assertLess(ar.elapsed, 1)
193 ar.get(2)
194
195 def test_hubresult_timestamps(self):
196 self.minimum_engines(4)
197 v = self.client[:]
198 ar = v.apply_async(time.sleep, 0.25)
199 ar.get(2)
200 rc2 = Client(profile='iptest')
201 # must have try/finally to close second Client, otherwise
202 # will have dangling sockets causing problems
203 try:
204 time.sleep(0.25)
205 hr = rc2.get_result(ar.msg_ids)
206 self.assertTrue(hr.elapsed > 0., "got bad elapsed: %s" % hr.elapsed)
207 hr.get(1)
208 self.assertTrue(hr.wall_time < ar.wall_time + 0.2, "got bad wall_time: %s > %s" % (hr.wall_time, ar.wall_time))
209 self.assertEqual(hr.serial_time, ar.serial_time)
210 finally:
211 rc2.close()
212
213 def test_display_empty_streams_single(self):
214 """empty stdout/err are not displayed (single result)"""
215 self.minimum_engines(1)
216
217 v = self.client[-1]
218 ar = v.execute("print (5555)")
219 ar.get(5)
220 with capture_output() as io:
221 ar.display_outputs()
222 self.assertEqual(io.stderr, '')
223 self.assertEqual('5555\n', io.stdout)
224
225 ar = v.execute("a=5")
226 ar.get(5)
227 with capture_output() as io:
228 ar.display_outputs()
229 self.assertEqual(io.stderr, '')
230 self.assertEqual(io.stdout, '')
231
232 def test_display_empty_streams_type(self):
233 """empty stdout/err are not displayed (groupby type)"""
234 self.minimum_engines(1)
235
236 v = self.client[:]
237 ar = v.execute("print (5555)")
238 ar.get(5)
239 with capture_output() as io:
240 ar.display_outputs()
241 self.assertEqual(io.stderr, '')
242 self.assertEqual(io.stdout.count('5555'), len(v), io.stdout)
243 self.assertFalse('\n\n' in io.stdout, io.stdout)
244 self.assertEqual(io.stdout.count('[stdout:'), len(v), io.stdout)
245
246 ar = v.execute("a=5")
247 ar.get(5)
248 with capture_output() as io:
249 ar.display_outputs()
250 self.assertEqual(io.stderr, '')
251 self.assertEqual(io.stdout, '')
252
253 def test_display_empty_streams_engine(self):
254 """empty stdout/err are not displayed (groupby engine)"""
255 self.minimum_engines(1)
256
257 v = self.client[:]
258 ar = v.execute("print (5555)")
259 ar.get(5)
260 with capture_output() as io:
261 ar.display_outputs('engine')
262 self.assertEqual(io.stderr, '')
263 self.assertEqual(io.stdout.count('5555'), len(v), io.stdout)
264 self.assertFalse('\n\n' in io.stdout, io.stdout)
265 self.assertEqual(io.stdout.count('[stdout:'), len(v), io.stdout)
266
267 ar = v.execute("a=5")
268 ar.get(5)
269 with capture_output() as io:
270 ar.display_outputs('engine')
271 self.assertEqual(io.stderr, '')
272 self.assertEqual(io.stdout, '')
273
274 def test_await_data(self):
275 """asking for ar.data flushes outputs"""
276 self.minimum_engines(1)
277
278 v = self.client[-1]
279 ar = v.execute('\n'.join([
280 "import time",
281 "from IPython.kernel.zmq.datapub import publish_data",
282 "for i in range(5):",
283 " publish_data(dict(i=i))",
284 " time.sleep(0.1)",
285 ]), block=False)
286 found = set()
287 tic = time.time()
288 # timeout after 10s
289 while time.time() <= tic + 10:
290 if ar.data:
291 i = ar.data['i']
292 found.add(i)
293 if i == 4:
294 break
295 time.sleep(0.05)
296
297 ar.get(5)
298 nt.assert_in(4, found)
299 self.assertTrue(len(found) > 1, "should have seen data multiple times, but got: %s" % found)
300
301 def test_not_single_result(self):
302 save_build = self.client._build_targets
303 def single_engine(*a, **kw):
304 idents, targets = save_build(*a, **kw)
305 return idents[:1], targets[:1]
306 ids = single_engine('all')[1]
307 self.client._build_targets = single_engine
308 for targets in ('all', None, ids):
309 dv = self.client.direct_view(targets=targets)
310 ar = dv.apply_async(lambda : 5)
311 self.assertEqual(ar.get(10), [5])
312 self.client._build_targets = save_build
313
314 def test_owner_pop(self):
315 self.minimum_engines(1)
316
317 view = self.client[-1]
318 ar = view.apply_async(lambda : 1)
319 ar.get()
320 msg_id = ar.msg_ids[0]
321 self.assertNotIn(msg_id, self.client.results)
322 self.assertNotIn(msg_id, self.client.metadata)
323
324 def test_non_owner(self):
325 self.minimum_engines(1)
326
327 view = self.client[-1]
328 ar = view.apply_async(lambda : 1)
329 ar.owner = False
330 ar.get()
331 msg_id = ar.msg_ids[0]
332 self.assertIn(msg_id, self.client.results)
333 self.assertIn(msg_id, self.client.metadata)
334 ar2 = self.client.get_result(msg_id, owner=True)
335 self.assertIs(type(ar2), type(ar))
336 self.assertTrue(ar2.owner)
337 self.assertEqual(ar.get(), ar2.get())
338 ar2.get()
339 self.assertNotIn(msg_id, self.client.results)
340 self.assertNotIn(msg_id, self.client.metadata)
341
342
This diff has been collapsed as it changes many lines, (550 lines changed) Show them Hide them
@@ -1,550 +0,0 b''
1 """Tests for parallel client.py"""
2
3 # Copyright (c) IPython Development Team.
4 # Distributed under the terms of the Modified BSD License.
5
6 from __future__ import division
7
8 import time
9 from datetime import datetime
10
11 import zmq
12
13 from IPython import parallel
14 from ipython_parallel.client import client as clientmod
15 from ipython_parallel import error
16 from ipython_parallel import AsyncResult, AsyncHubResult
17 from ipython_parallel import LoadBalancedView, DirectView
18
19 from .clienttest import ClusterTestCase, segfault, wait, add_engines
20
21 def setup():
22 add_engines(4, total=True)
23
24 class TestClient(ClusterTestCase):
25
26 def test_ids(self):
27 n = len(self.client.ids)
28 self.add_engines(2)
29 self.assertEqual(len(self.client.ids), n+2)
30
31 def test_iter(self):
32 self.minimum_engines(4)
33 engine_ids = [ view.targets for view in self.client ]
34 self.assertEqual(engine_ids, self.client.ids)
35
36 def test_view_indexing(self):
37 """test index access for views"""
38 self.minimum_engines(4)
39 targets = self.client._build_targets('all')[-1]
40 v = self.client[:]
41 self.assertEqual(v.targets, targets)
42 t = self.client.ids[2]
43 v = self.client[t]
44 self.assertTrue(isinstance(v, DirectView))
45 self.assertEqual(v.targets, t)
46 t = self.client.ids[2:4]
47 v = self.client[t]
48 self.assertTrue(isinstance(v, DirectView))
49 self.assertEqual(v.targets, t)
50 v = self.client[::2]
51 self.assertTrue(isinstance(v, DirectView))
52 self.assertEqual(v.targets, targets[::2])
53 v = self.client[1::3]
54 self.assertTrue(isinstance(v, DirectView))
55 self.assertEqual(v.targets, targets[1::3])
56 v = self.client[:-3]
57 self.assertTrue(isinstance(v, DirectView))
58 self.assertEqual(v.targets, targets[:-3])
59 v = self.client[-1]
60 self.assertTrue(isinstance(v, DirectView))
61 self.assertEqual(v.targets, targets[-1])
62 self.assertRaises(TypeError, lambda : self.client[None])
63
64 def test_lbview_targets(self):
65 """test load_balanced_view targets"""
66 v = self.client.load_balanced_view()
67 self.assertEqual(v.targets, None)
68 v = self.client.load_balanced_view(-1)
69 self.assertEqual(v.targets, [self.client.ids[-1]])
70 v = self.client.load_balanced_view('all')
71 self.assertEqual(v.targets, None)
72
73 def test_dview_targets(self):
74 """test direct_view targets"""
75 v = self.client.direct_view()
76 self.assertEqual(v.targets, 'all')
77 v = self.client.direct_view('all')
78 self.assertEqual(v.targets, 'all')
79 v = self.client.direct_view(-1)
80 self.assertEqual(v.targets, self.client.ids[-1])
81
82 def test_lazy_all_targets(self):
83 """test lazy evaluation of rc.direct_view('all')"""
84 v = self.client.direct_view()
85 self.assertEqual(v.targets, 'all')
86
87 def double(x):
88 return x*2
89 seq = list(range(100))
90 ref = [ double(x) for x in seq ]
91
92 # add some engines, which should be used
93 self.add_engines(1)
94 n1 = len(self.client.ids)
95
96 # simple apply
97 r = v.apply_sync(lambda : 1)
98 self.assertEqual(r, [1] * n1)
99
100 # map goes through remotefunction
101 r = v.map_sync(double, seq)
102 self.assertEqual(r, ref)
103
104 # add a couple more engines, and try again
105 self.add_engines(2)
106 n2 = len(self.client.ids)
107 self.assertNotEqual(n2, n1)
108
109 # apply
110 r = v.apply_sync(lambda : 1)
111 self.assertEqual(r, [1] * n2)
112
113 # map
114 r = v.map_sync(double, seq)
115 self.assertEqual(r, ref)
116
117 def test_targets(self):
118 """test various valid targets arguments"""
119 build = self.client._build_targets
120 ids = self.client.ids
121 idents,targets = build(None)
122 self.assertEqual(ids, targets)
123
124 def test_clear(self):
125 """test clear behavior"""
126 self.minimum_engines(2)
127 v = self.client[:]
128 v.block=True
129 v.push(dict(a=5))
130 v.pull('a')
131 id0 = self.client.ids[-1]
132 self.client.clear(targets=id0, block=True)
133 a = self.client[:-1].get('a')
134 self.assertRaisesRemote(NameError, self.client[id0].get, 'a')
135 self.client.clear(block=True)
136 for i in self.client.ids:
137 self.assertRaisesRemote(NameError, self.client[i].get, 'a')
138
139 def test_get_result(self):
140 """test getting results from the Hub."""
141 c = clientmod.Client(profile='iptest')
142 t = c.ids[-1]
143 ar = c[t].apply_async(wait, 1)
144 # give the monitor time to notice the message
145 time.sleep(.25)
146 ahr = self.client.get_result(ar.msg_ids[0], owner=False)
147 self.assertIsInstance(ahr, AsyncHubResult)
148 self.assertEqual(ahr.get(), ar.get())
149 ar2 = self.client.get_result(ar.msg_ids[0])
150 self.assertNotIsInstance(ar2, AsyncHubResult)
151 self.assertEqual(ahr.get(), ar2.get())
152 c.close()
153
154 def test_get_execute_result(self):
155 """test getting execute results from the Hub."""
156 c = clientmod.Client(profile='iptest')
157 t = c.ids[-1]
158 cell = '\n'.join([
159 'import time',
160 'time.sleep(0.25)',
161 '5'
162 ])
163 ar = c[t].execute("import time; time.sleep(1)", silent=False)
164 # give the monitor time to notice the message
165 time.sleep(.25)
166 ahr = self.client.get_result(ar.msg_ids[0], owner=False)
167 self.assertIsInstance(ahr, AsyncHubResult)
168 self.assertEqual(ahr.get().execute_result, ar.get().execute_result)
169 ar2 = self.client.get_result(ar.msg_ids[0])
170 self.assertNotIsInstance(ar2, AsyncHubResult)
171 self.assertEqual(ahr.get(), ar2.get())
172 c.close()
173
174 def test_ids_list(self):
175 """test client.ids"""
176 ids = self.client.ids
177 self.assertEqual(ids, self.client._ids)
178 self.assertFalse(ids is self.client._ids)
179 ids.remove(ids[-1])
180 self.assertNotEqual(ids, self.client._ids)
181
182 def test_queue_status(self):
183 ids = self.client.ids
184 id0 = ids[0]
185 qs = self.client.queue_status(targets=id0)
186 self.assertTrue(isinstance(qs, dict))
187 self.assertEqual(sorted(qs.keys()), ['completed', 'queue', 'tasks'])
188 allqs = self.client.queue_status()
189 self.assertTrue(isinstance(allqs, dict))
190 intkeys = list(allqs.keys())
191 intkeys.remove('unassigned')
192 print("intkeys", intkeys)
193 intkeys = sorted(intkeys)
194 ids = self.client.ids
195 print("client.ids", ids)
196 ids = sorted(self.client.ids)
197 self.assertEqual(intkeys, ids)
198 unassigned = allqs.pop('unassigned')
199 for eid,qs in allqs.items():
200 self.assertTrue(isinstance(qs, dict))
201 self.assertEqual(sorted(qs.keys()), ['completed', 'queue', 'tasks'])
202
203 def test_shutdown(self):
204 ids = self.client.ids
205 id0 = ids[0]
206 self.client.shutdown(id0, block=True)
207 while id0 in self.client.ids:
208 time.sleep(0.1)
209 self.client.spin()
210
211 self.assertRaises(IndexError, lambda : self.client[id0])
212
213 def test_result_status(self):
214 pass
215 # to be written
216
217 def test_db_query_dt(self):
218 """test db query by date"""
219 hist = self.client.hub_history()
220 middle = self.client.db_query({'msg_id' : hist[len(hist)//2]})[0]
221 tic = middle['submitted']
222 before = self.client.db_query({'submitted' : {'$lt' : tic}})
223 after = self.client.db_query({'submitted' : {'$gte' : tic}})
224 self.assertEqual(len(before)+len(after),len(hist))
225 for b in before:
226 self.assertTrue(b['submitted'] < tic)
227 for a in after:
228 self.assertTrue(a['submitted'] >= tic)
229 same = self.client.db_query({'submitted' : tic})
230 for s in same:
231 self.assertTrue(s['submitted'] == tic)
232
233 def test_db_query_keys(self):
234 """test extracting subset of record keys"""
235 found = self.client.db_query({'msg_id': {'$ne' : ''}},keys=['submitted', 'completed'])
236 for rec in found:
237 self.assertEqual(set(rec.keys()), set(['msg_id', 'submitted', 'completed']))
238
239 def test_db_query_default_keys(self):
240 """default db_query excludes buffers"""
241 found = self.client.db_query({'msg_id': {'$ne' : ''}})
242 for rec in found:
243 keys = set(rec.keys())
244 self.assertFalse('buffers' in keys, "'buffers' should not be in: %s" % keys)
245 self.assertFalse('result_buffers' in keys, "'result_buffers' should not be in: %s" % keys)
246
247 def test_db_query_msg_id(self):
248 """ensure msg_id is always in db queries"""
249 found = self.client.db_query({'msg_id': {'$ne' : ''}},keys=['submitted', 'completed'])
250 for rec in found:
251 self.assertTrue('msg_id' in rec.keys())
252 found = self.client.db_query({'msg_id': {'$ne' : ''}},keys=['submitted'])
253 for rec in found:
254 self.assertTrue('msg_id' in rec.keys())
255 found = self.client.db_query({'msg_id': {'$ne' : ''}},keys=['msg_id'])
256 for rec in found:
257 self.assertTrue('msg_id' in rec.keys())
258
259 def test_db_query_get_result(self):
260 """pop in db_query shouldn't pop from result itself"""
261 self.client[:].apply_sync(lambda : 1)
262 found = self.client.db_query({'msg_id': {'$ne' : ''}})
263 rc2 = clientmod.Client(profile='iptest')
264 # If this bug is not fixed, this call will hang:
265 ar = rc2.get_result(self.client.history[-1])
266 ar.wait(2)
267 self.assertTrue(ar.ready())
268 ar.get()
269 rc2.close()
270
271 def test_db_query_in(self):
272 """test db query with '$in','$nin' operators"""
273 hist = self.client.hub_history()
274 even = hist[::2]
275 odd = hist[1::2]
276 recs = self.client.db_query({ 'msg_id' : {'$in' : even}})
277 found = [ r['msg_id'] for r in recs ]
278 self.assertEqual(set(even), set(found))
279 recs = self.client.db_query({ 'msg_id' : {'$nin' : even}})
280 found = [ r['msg_id'] for r in recs ]
281 self.assertEqual(set(odd), set(found))
282
283 def test_hub_history(self):
284 hist = self.client.hub_history()
285 recs = self.client.db_query({ 'msg_id' : {"$ne":''}})
286 recdict = {}
287 for rec in recs:
288 recdict[rec['msg_id']] = rec
289
290 latest = datetime(1984,1,1)
291 for msg_id in hist:
292 rec = recdict[msg_id]
293 newt = rec['submitted']
294 self.assertTrue(newt >= latest)
295 latest = newt
296 ar = self.client[-1].apply_async(lambda : 1)
297 ar.get()
298 time.sleep(0.25)
299 self.assertEqual(self.client.hub_history()[-1:],ar.msg_ids)
300
301 def _wait_for_idle(self):
302 """wait for the cluster to become idle, according to the everyone."""
303 rc = self.client
304
305 # step 0. wait for local results
306 # this should be sufficient 99% of the time.
307 rc.wait(timeout=5)
308
309 # step 1. wait for all requests to be noticed
310 # timeout 5s, polling every 100ms
311 msg_ids = set(rc.history)
312 hub_hist = rc.hub_history()
313 for i in range(50):
314 if msg_ids.difference(hub_hist):
315 time.sleep(0.1)
316 hub_hist = rc.hub_history()
317 else:
318 break
319
320 self.assertEqual(len(msg_ids.difference(hub_hist)), 0)
321
322 # step 2. wait for all requests to be done
323 # timeout 5s, polling every 100ms
324 qs = rc.queue_status()
325 for i in range(50):
326 if qs['unassigned'] or any(qs[eid]['tasks'] + qs[eid]['queue'] for eid in qs if eid != 'unassigned'):
327 time.sleep(0.1)
328 qs = rc.queue_status()
329 else:
330 break
331
332 # ensure Hub up to date:
333 self.assertEqual(qs['unassigned'], 0)
334 for eid in [ eid for eid in qs if eid != 'unassigned' ]:
335 self.assertEqual(qs[eid]['tasks'], 0)
336 self.assertEqual(qs[eid]['queue'], 0)
337
338
339 def test_resubmit(self):
340 def f():
341 import random
342 return random.random()
343 v = self.client.load_balanced_view()
344 ar = v.apply_async(f)
345 r1 = ar.get(1)
346 # give the Hub a chance to notice:
347 self._wait_for_idle()
348 ahr = self.client.resubmit(ar.msg_ids)
349 r2 = ahr.get(1)
350 self.assertFalse(r1 == r2)
351
352 def test_resubmit_chain(self):
353 """resubmit resubmitted tasks"""
354 v = self.client.load_balanced_view()
355 ar = v.apply_async(lambda x: x, 'x'*1024)
356 ar.get()
357 self._wait_for_idle()
358 ars = [ar]
359
360 for i in range(10):
361 ar = ars[-1]
362 ar2 = self.client.resubmit(ar.msg_ids)
363
364 [ ar.get() for ar in ars ]
365
366 def test_resubmit_header(self):
367 """resubmit shouldn't clobber the whole header"""
368 def f():
369 import random
370 return random.random()
371 v = self.client.load_balanced_view()
372 v.retries = 1
373 ar = v.apply_async(f)
374 r1 = ar.get(1)
375 # give the Hub a chance to notice:
376 self._wait_for_idle()
377 ahr = self.client.resubmit(ar.msg_ids)
378 ahr.get(1)
379 time.sleep(0.5)
380 records = self.client.db_query({'msg_id': {'$in': ar.msg_ids + ahr.msg_ids}}, keys='header')
381 h1,h2 = [ r['header'] for r in records ]
382 for key in set(h1.keys()).union(set(h2.keys())):
383 if key in ('msg_id', 'date'):
384 self.assertNotEqual(h1[key], h2[key])
385 else:
386 self.assertEqual(h1[key], h2[key])
387
388 def test_resubmit_aborted(self):
389 def f():
390 import random
391 return random.random()
392 v = self.client.load_balanced_view()
393 # restrict to one engine, so we can put a sleep
394 # ahead of the task, so it will get aborted
395 eid = self.client.ids[-1]
396 v.targets = [eid]
397 sleep = v.apply_async(time.sleep, 0.5)
398 ar = v.apply_async(f)
399 ar.abort()
400 self.assertRaises(error.TaskAborted, ar.get)
401 # Give the Hub a chance to get up to date:
402 self._wait_for_idle()
403 ahr = self.client.resubmit(ar.msg_ids)
404 r2 = ahr.get(1)
405
406 def test_resubmit_inflight(self):
407 """resubmit of inflight task"""
408 v = self.client.load_balanced_view()
409 ar = v.apply_async(time.sleep,1)
410 # give the message a chance to arrive
411 time.sleep(0.2)
412 ahr = self.client.resubmit(ar.msg_ids)
413 ar.get(2)
414 ahr.get(2)
415
416 def test_resubmit_badkey(self):
417 """ensure KeyError on resubmit of nonexistant task"""
418 self.assertRaisesRemote(KeyError, self.client.resubmit, ['invalid'])
419
420 def test_purge_hub_results(self):
421 # ensure there are some tasks
422 for i in range(5):
423 self.client[:].apply_sync(lambda : 1)
424 # Wait for the Hub to realise the result is done:
425 # This prevents a race condition, where we
426 # might purge a result the Hub still thinks is pending.
427 self._wait_for_idle()
428 rc2 = clientmod.Client(profile='iptest')
429 hist = self.client.hub_history()
430 ahr = rc2.get_result([hist[-1]])
431 ahr.wait(10)
432 self.client.purge_hub_results(hist[-1])
433 newhist = self.client.hub_history()
434 self.assertEqual(len(newhist)+1,len(hist))
435 rc2.spin()
436 rc2.close()
437
438 def test_purge_local_results(self):
439 # ensure there are some tasks
440 res = []
441 for i in range(5):
442 res.append(self.client[:].apply_async(lambda : 1))
443 self._wait_for_idle()
444 self.client.wait(10) # wait for the results to come back
445 before = len(self.client.results)
446 self.assertEqual(len(self.client.metadata),before)
447 self.client.purge_local_results(res[-1])
448 self.assertEqual(len(self.client.results),before-len(res[-1]), msg="Not removed from results")
449 self.assertEqual(len(self.client.metadata),before-len(res[-1]), msg="Not removed from metadata")
450
451 def test_purge_local_results_outstanding(self):
452 v = self.client[-1]
453 ar = v.apply_async(lambda : 1)
454 msg_id = ar.msg_ids[0]
455 ar.owner = False
456 ar.get()
457 self._wait_for_idle()
458 ar2 = v.apply_async(time.sleep, 1)
459 self.assertIn(msg_id, self.client.results)
460 self.assertIn(msg_id, self.client.metadata)
461 self.client.purge_local_results(ar)
462 self.assertNotIn(msg_id, self.client.results)
463 self.assertNotIn(msg_id, self.client.metadata)
464 with self.assertRaises(RuntimeError):
465 self.client.purge_local_results(ar2)
466 ar2.get()
467 self.client.purge_local_results(ar2)
468
469 def test_purge_all_local_results_outstanding(self):
470 v = self.client[-1]
471 ar = v.apply_async(time.sleep, 1)
472 with self.assertRaises(RuntimeError):
473 self.client.purge_local_results('all')
474 ar.get()
475 self.client.purge_local_results('all')
476
477 def test_purge_all_hub_results(self):
478 self.client.purge_hub_results('all')
479 hist = self.client.hub_history()
480 self.assertEqual(len(hist), 0)
481
482 def test_purge_all_local_results(self):
483 self.client.purge_local_results('all')
484 self.assertEqual(len(self.client.results), 0, msg="Results not empty")
485 self.assertEqual(len(self.client.metadata), 0, msg="metadata not empty")
486
487 def test_purge_all_results(self):
488 # ensure there are some tasks
489 for i in range(5):
490 self.client[:].apply_sync(lambda : 1)
491 self.client.wait(10)
492 self._wait_for_idle()
493 self.client.purge_results('all')
494 self.assertEqual(len(self.client.results), 0, msg="Results not empty")
495 self.assertEqual(len(self.client.metadata), 0, msg="metadata not empty")
496 hist = self.client.hub_history()
497 self.assertEqual(len(hist), 0, msg="hub history not empty")
498
499 def test_purge_everything(self):
500 # ensure there are some tasks
501 for i in range(5):
502 self.client[:].apply_sync(lambda : 1)
503 self.client.wait(10)
504 self._wait_for_idle()
505 self.client.purge_everything()
506 # The client results
507 self.assertEqual(len(self.client.results), 0, msg="Results not empty")
508 self.assertEqual(len(self.client.metadata), 0, msg="metadata not empty")
509 # The client "bookkeeping"
510 self.assertEqual(len(self.client.session.digest_history), 0, msg="session digest not empty")
511 self.assertEqual(len(self.client.history), 0, msg="client history not empty")
512 # the hub results
513 hist = self.client.hub_history()
514 self.assertEqual(len(hist), 0, msg="hub history not empty")
515
516
517 def test_spin_thread(self):
518 self.client.spin_thread(0.01)
519 ar = self.client[-1].apply_async(lambda : 1)
520 md = self.client.metadata[ar.msg_ids[0]]
521 # 3s timeout, 100ms poll
522 for i in range(30):
523 time.sleep(0.1)
524 if md['received'] is not None:
525 break
526 self.assertIsInstance(md['received'], datetime)
527
528 def test_stop_spin_thread(self):
529 self.client.spin_thread(0.01)
530 self.client.stop_spin_thread()
531 ar = self.client[-1].apply_async(lambda : 1)
532 md = self.client.metadata[ar.msg_ids[0]]
533 # 500ms timeout, 100ms poll
534 for i in range(5):
535 time.sleep(0.1)
536 self.assertIsNone(md['received'], None)
537
538 def test_activate(self):
539 ip = get_ipython()
540 magics = ip.magics_manager.magics
541 self.assertTrue('px' in magics['line'])
542 self.assertTrue('px' in magics['cell'])
543 v0 = self.client.activate(-1, '0')
544 self.assertTrue('px0' in magics['line'])
545 self.assertTrue('px0' in magics['cell'])
546 self.assertEqual(v0.targets, self.client.ids[-1])
547 v0 = self.client.activate('all', 'all')
548 self.assertTrue('pxall' in magics['line'])
549 self.assertTrue('pxall' in magics['cell'])
550 self.assertEqual(v0.targets, 'all')
@@ -1,314 +0,0 b''
1 """Tests for db backends
2
3 Authors:
4
5 * Min RK
6 """
7
8 #-------------------------------------------------------------------------------
9 # Copyright (C) 2011 The IPython Development Team
10 #
11 # Distributed under the terms of the BSD License. The full license is in
12 # the file COPYING, distributed as part of this software.
13 #-------------------------------------------------------------------------------
14
15 #-------------------------------------------------------------------------------
16 # Imports
17 #-------------------------------------------------------------------------------
18
19 from __future__ import division
20
21 import logging
22 import os
23 import tempfile
24 import time
25
26 from datetime import datetime, timedelta
27 from unittest import TestCase
28
29 from ipython_parallel import error
30 from ipython_parallel.controller.dictdb import DictDB
31 from ipython_parallel.controller.sqlitedb import SQLiteDB
32 from ipython_parallel.controller.hub import init_record, empty_record
33
34 from IPython.testing import decorators as dec
35 from IPython.kernel.zmq.session import Session
36
37
38 #-------------------------------------------------------------------------------
39 # TestCases
40 #-------------------------------------------------------------------------------
41
42
43 def setup():
44 global temp_db
45 temp_db = tempfile.NamedTemporaryFile(suffix='.db').name
46
47
48 class TaskDBTest:
49 def setUp(self):
50 self.session = Session()
51 self.db = self.create_db()
52 self.load_records(16)
53
54 def create_db(self):
55 raise NotImplementedError
56
57 def load_records(self, n=1, buffer_size=100):
58 """load n records for testing"""
59 #sleep 1/10 s, to ensure timestamp is different to previous calls
60 time.sleep(0.1)
61 msg_ids = []
62 for i in range(n):
63 msg = self.session.msg('apply_request', content=dict(a=5))
64 msg['buffers'] = [os.urandom(buffer_size)]
65 rec = init_record(msg)
66 msg_id = msg['header']['msg_id']
67 msg_ids.append(msg_id)
68 self.db.add_record(msg_id, rec)
69 return msg_ids
70
71 def test_add_record(self):
72 before = self.db.get_history()
73 self.load_records(5)
74 after = self.db.get_history()
75 self.assertEqual(len(after), len(before)+5)
76 self.assertEqual(after[:-5],before)
77
78 def test_drop_record(self):
79 msg_id = self.load_records()[-1]
80 rec = self.db.get_record(msg_id)
81 self.db.drop_record(msg_id)
82 self.assertRaises(KeyError,self.db.get_record, msg_id)
83
84 def _round_to_millisecond(self, dt):
85 """necessary because mongodb rounds microseconds"""
86 micro = dt.microsecond
87 extra = int(str(micro)[-3:])
88 return dt - timedelta(microseconds=extra)
89
90 def test_update_record(self):
91 now = self._round_to_millisecond(datetime.now())
92 #
93 msg_id = self.db.get_history()[-1]
94 rec1 = self.db.get_record(msg_id)
95 data = {'stdout': 'hello there', 'completed' : now}
96 self.db.update_record(msg_id, data)
97 rec2 = self.db.get_record(msg_id)
98 self.assertEqual(rec2['stdout'], 'hello there')
99 self.assertEqual(rec2['completed'], now)
100 rec1.update(data)
101 self.assertEqual(rec1, rec2)
102
103 # def test_update_record_bad(self):
104 # """test updating nonexistant records"""
105 # msg_id = str(uuid.uuid4())
106 # data = {'stdout': 'hello there'}
107 # self.assertRaises(KeyError, self.db.update_record, msg_id, data)
108
109 def test_find_records_dt(self):
110 """test finding records by date"""
111 hist = self.db.get_history()
112 middle = self.db.get_record(hist[len(hist)//2])
113 tic = middle['submitted']
114 before = self.db.find_records({'submitted' : {'$lt' : tic}})
115 after = self.db.find_records({'submitted' : {'$gte' : tic}})
116 self.assertEqual(len(before)+len(after),len(hist))
117 for b in before:
118 self.assertTrue(b['submitted'] < tic)
119 for a in after:
120 self.assertTrue(a['submitted'] >= tic)
121 same = self.db.find_records({'submitted' : tic})
122 for s in same:
123 self.assertTrue(s['submitted'] == tic)
124
125 def test_find_records_keys(self):
126 """test extracting subset of record keys"""
127 found = self.db.find_records({'msg_id': {'$ne' : ''}},keys=['submitted', 'completed'])
128 for rec in found:
129 self.assertEqual(set(rec.keys()), set(['msg_id', 'submitted', 'completed']))
130
131 def test_find_records_msg_id(self):
132 """ensure msg_id is always in found records"""
133 found = self.db.find_records({'msg_id': {'$ne' : ''}},keys=['submitted', 'completed'])
134 for rec in found:
135 self.assertTrue('msg_id' in rec.keys())
136 found = self.db.find_records({'msg_id': {'$ne' : ''}},keys=['submitted'])
137 for rec in found:
138 self.assertTrue('msg_id' in rec.keys())
139 found = self.db.find_records({'msg_id': {'$ne' : ''}},keys=['msg_id'])
140 for rec in found:
141 self.assertTrue('msg_id' in rec.keys())
142
143 def test_find_records_in(self):
144 """test finding records with '$in','$nin' operators"""
145 hist = self.db.get_history()
146 even = hist[::2]
147 odd = hist[1::2]
148 recs = self.db.find_records({ 'msg_id' : {'$in' : even}})
149 found = [ r['msg_id'] for r in recs ]
150 self.assertEqual(set(even), set(found))
151 recs = self.db.find_records({ 'msg_id' : {'$nin' : even}})
152 found = [ r['msg_id'] for r in recs ]
153 self.assertEqual(set(odd), set(found))
154
155 def test_get_history(self):
156 msg_ids = self.db.get_history()
157 latest = datetime(1984,1,1)
158 for msg_id in msg_ids:
159 rec = self.db.get_record(msg_id)
160 newt = rec['submitted']
161 self.assertTrue(newt >= latest)
162 latest = newt
163 msg_id = self.load_records(1)[-1]
164 self.assertEqual(self.db.get_history()[-1],msg_id)
165
166 def test_datetime(self):
167 """get/set timestamps with datetime objects"""
168 msg_id = self.db.get_history()[-1]
169 rec = self.db.get_record(msg_id)
170 self.assertTrue(isinstance(rec['submitted'], datetime))
171 self.db.update_record(msg_id, dict(completed=datetime.now()))
172 rec = self.db.get_record(msg_id)
173 self.assertTrue(isinstance(rec['completed'], datetime))
174
175 def test_drop_matching(self):
176 msg_ids = self.load_records(10)
177 query = {'msg_id' : {'$in':msg_ids}}
178 self.db.drop_matching_records(query)
179 recs = self.db.find_records(query)
180 self.assertEqual(len(recs), 0)
181
182 def test_null(self):
183 """test None comparison queries"""
184 msg_ids = self.load_records(10)
185
186 query = {'msg_id' : None}
187 recs = self.db.find_records(query)
188 self.assertEqual(len(recs), 0)
189
190 query = {'msg_id' : {'$ne' : None}}
191 recs = self.db.find_records(query)
192 self.assertTrue(len(recs) >= 10)
193
194 def test_pop_safe_get(self):
195 """editing query results shouldn't affect record [get]"""
196 msg_id = self.db.get_history()[-1]
197 rec = self.db.get_record(msg_id)
198 rec.pop('buffers')
199 rec['garbage'] = 'hello'
200 rec['header']['msg_id'] = 'fubar'
201 rec2 = self.db.get_record(msg_id)
202 self.assertTrue('buffers' in rec2)
203 self.assertFalse('garbage' in rec2)
204 self.assertEqual(rec2['header']['msg_id'], msg_id)
205
206 def test_pop_safe_find(self):
207 """editing query results shouldn't affect record [find]"""
208 msg_id = self.db.get_history()[-1]
209 rec = self.db.find_records({'msg_id' : msg_id})[0]
210 rec.pop('buffers')
211 rec['garbage'] = 'hello'
212 rec['header']['msg_id'] = 'fubar'
213 rec2 = self.db.find_records({'msg_id' : msg_id})[0]
214 self.assertTrue('buffers' in rec2)
215 self.assertFalse('garbage' in rec2)
216 self.assertEqual(rec2['header']['msg_id'], msg_id)
217
218 def test_pop_safe_find_keys(self):
219 """editing query results shouldn't affect record [find+keys]"""
220 msg_id = self.db.get_history()[-1]
221 rec = self.db.find_records({'msg_id' : msg_id}, keys=['buffers', 'header'])[0]
222 rec.pop('buffers')
223 rec['garbage'] = 'hello'
224 rec['header']['msg_id'] = 'fubar'
225 rec2 = self.db.find_records({'msg_id' : msg_id})[0]
226 self.assertTrue('buffers' in rec2)
227 self.assertFalse('garbage' in rec2)
228 self.assertEqual(rec2['header']['msg_id'], msg_id)
229
230
231 class TestDictBackend(TaskDBTest, TestCase):
232
233 def create_db(self):
234 return DictDB()
235
236 def test_cull_count(self):
237 self.db = self.create_db() # skip the load-records init from setUp
238 self.db.record_limit = 20
239 self.db.cull_fraction = 0.2
240 self.load_records(20)
241 self.assertEqual(len(self.db.get_history()), 20)
242 self.load_records(1)
243 # 0.2 * 20 = 4, 21 - 4 = 17
244 self.assertEqual(len(self.db.get_history()), 17)
245 self.load_records(3)
246 self.assertEqual(len(self.db.get_history()), 20)
247 self.load_records(1)
248 self.assertEqual(len(self.db.get_history()), 17)
249
250 for i in range(25):
251 self.load_records(1)
252 self.assertTrue(len(self.db.get_history()) >= 17)
253 self.assertTrue(len(self.db.get_history()) <= 20)
254
255 def test_cull_size(self):
256 self.db = self.create_db() # skip the load-records init from setUp
257 self.db.size_limit = 1000
258 self.db.cull_fraction = 0.2
259 self.load_records(100, buffer_size=10)
260 self.assertEqual(len(self.db.get_history()), 100)
261 self.load_records(1, buffer_size=0)
262 self.assertEqual(len(self.db.get_history()), 101)
263 self.load_records(1, buffer_size=1)
264 # 0.2 * 100 = 20, 101 - 20 = 81
265 self.assertEqual(len(self.db.get_history()), 81)
266
267 def test_cull_size_drop(self):
268 """dropping records updates tracked buffer size"""
269 self.db = self.create_db() # skip the load-records init from setUp
270 self.db.size_limit = 1000
271 self.db.cull_fraction = 0.2
272 self.load_records(100, buffer_size=10)
273 self.assertEqual(len(self.db.get_history()), 100)
274 self.db.drop_record(self.db.get_history()[-1])
275 self.assertEqual(len(self.db.get_history()), 99)
276 self.load_records(1, buffer_size=5)
277 self.assertEqual(len(self.db.get_history()), 100)
278 self.load_records(1, buffer_size=5)
279 self.assertEqual(len(self.db.get_history()), 101)
280 self.load_records(1, buffer_size=1)
281 self.assertEqual(len(self.db.get_history()), 81)
282
283 def test_cull_size_update(self):
284 """updating records updates tracked buffer size"""
285 self.db = self.create_db() # skip the load-records init from setUp
286 self.db.size_limit = 1000
287 self.db.cull_fraction = 0.2
288 self.load_records(100, buffer_size=10)
289 self.assertEqual(len(self.db.get_history()), 100)
290 msg_id = self.db.get_history()[-1]
291 self.db.update_record(msg_id, dict(result_buffers = [os.urandom(10)], buffers=[]))
292 self.assertEqual(len(self.db.get_history()), 100)
293 self.db.update_record(msg_id, dict(result_buffers = [os.urandom(11)], buffers=[]))
294 self.assertEqual(len(self.db.get_history()), 79)
295
296 class TestSQLiteBackend(TaskDBTest, TestCase):
297
298 @dec.skip_without('sqlite3')
299 def create_db(self):
300 location, fname = os.path.split(temp_db)
301 log = logging.getLogger('test')
302 log.setLevel(logging.CRITICAL)
303 return SQLiteDB(location=location, fname=fname, log=log)
304
305 def tearDown(self):
306 self.db._db.close()
307
308
309 def teardown():
310 """cleanup task db file after all tests have run"""
311 try:
312 os.remove(temp_db)
313 except:
314 pass
@@ -1,136 +0,0 b''
1 """Tests for dependency.py
2
3 Authors:
4
5 * Min RK
6 """
7
8 __docformat__ = "restructuredtext en"
9
10 #-------------------------------------------------------------------------------
11 # Copyright (C) 2011 The IPython Development Team
12 #
13 # Distributed under the terms of the BSD License. The full license is in
14 # the file COPYING, distributed as part of this software.
15 #-------------------------------------------------------------------------------
16
17 #-------------------------------------------------------------------------------
18 # Imports
19 #-------------------------------------------------------------------------------
20
21 # import
22 import os
23
24 from ipython_kernel.pickleutil import can, uncan
25
26 import ipython_parallel as pmod
27 from ipython_parallel.util import interactive
28
29 from ipython_parallel.tests import add_engines
30 from .clienttest import ClusterTestCase
31
32 def setup():
33 add_engines(1, total=True)
34
35 @pmod.require('time')
36 def wait(n):
37 time.sleep(n)
38 return n
39
40 @pmod.interactive
41 def func(x):
42 return x*x
43
44 mixed = list(map(str, range(10)))
45 completed = list(map(str, range(0,10,2)))
46 failed = list(map(str, range(1,10,2)))
47
48 class DependencyTest(ClusterTestCase):
49
50 def setUp(self):
51 ClusterTestCase.setUp(self)
52 self.user_ns = {'__builtins__' : __builtins__}
53 self.view = self.client.load_balanced_view()
54 self.dview = self.client[-1]
55 self.succeeded = set(map(str, range(0,25,2)))
56 self.failed = set(map(str, range(1,25,2)))
57
58 def assertMet(self, dep):
59 self.assertTrue(dep.check(self.succeeded, self.failed), "Dependency should be met")
60
61 def assertUnmet(self, dep):
62 self.assertFalse(dep.check(self.succeeded, self.failed), "Dependency should not be met")
63
64 def assertUnreachable(self, dep):
65 self.assertTrue(dep.unreachable(self.succeeded, self.failed), "Dependency should be unreachable")
66
67 def assertReachable(self, dep):
68 self.assertFalse(dep.unreachable(self.succeeded, self.failed), "Dependency should be reachable")
69
70 def cancan(self, f):
71 """decorator to pass through canning into self.user_ns"""
72 return uncan(can(f), self.user_ns)
73
74 def test_require_imports(self):
75 """test that @require imports names"""
76 @self.cancan
77 @pmod.require('base64')
78 @interactive
79 def encode(arg):
80 return base64.b64encode(arg)
81 # must pass through canning to properly connect namespaces
82 self.assertEqual(encode(b'foo'), b'Zm9v')
83
84 def test_success_only(self):
85 dep = pmod.Dependency(mixed, success=True, failure=False)
86 self.assertUnmet(dep)
87 self.assertUnreachable(dep)
88 dep.all=False
89 self.assertMet(dep)
90 self.assertReachable(dep)
91 dep = pmod.Dependency(completed, success=True, failure=False)
92 self.assertMet(dep)
93 self.assertReachable(dep)
94 dep.all=False
95 self.assertMet(dep)
96 self.assertReachable(dep)
97
98 def test_failure_only(self):
99 dep = pmod.Dependency(mixed, success=False, failure=True)
100 self.assertUnmet(dep)
101 self.assertUnreachable(dep)
102 dep.all=False
103 self.assertMet(dep)
104 self.assertReachable(dep)
105 dep = pmod.Dependency(completed, success=False, failure=True)
106 self.assertUnmet(dep)
107 self.assertUnreachable(dep)
108 dep.all=False
109 self.assertUnmet(dep)
110 self.assertUnreachable(dep)
111
112 def test_require_function(self):
113
114 @pmod.interactive
115 def bar(a):
116 return func(a)
117
118 @pmod.require(func)
119 @pmod.interactive
120 def bar2(a):
121 return func(a)
122
123 self.client[:].clear()
124 self.assertRaisesRemote(NameError, self.view.apply_sync, bar, 5)
125 ar = self.view.apply_async(bar2, 5)
126 self.assertEqual(ar.get(5), func(5))
127
128 def test_require_object(self):
129
130 @pmod.require(foo=func)
131 @pmod.interactive
132 def bar(a):
133 return foo(a)
134
135 ar = self.view.apply_async(bar, 5)
136 self.assertEqual(ar.get(5), func(5))
@@ -1,194 +0,0 b''
1 """Tests for launchers
2
3 Doesn't actually start any subprocesses, but goes through the motions of constructing
4 objects, which should test basic config.
5
6 Authors:
7
8 * Min RK
9 """
10
11 #-------------------------------------------------------------------------------
12 # Copyright (C) 2013 The IPython Development Team
13 #
14 # Distributed under the terms of the BSD License. The full license is in
15 # the file COPYING, distributed as part of this software.
16 #-------------------------------------------------------------------------------
17
18 #-------------------------------------------------------------------------------
19 # Imports
20 #-------------------------------------------------------------------------------
21
22 import logging
23 import os
24 import shutil
25 import sys
26 import tempfile
27
28 from unittest import TestCase
29
30 from nose import SkipTest
31
32 from IPython.config import Config
33
34 from ipython_parallel.apps import launcher
35
36 from IPython.testing import decorators as dec
37 from IPython.utils.py3compat import string_types
38
39
40 #-------------------------------------------------------------------------------
41 # TestCase Mixins
42 #-------------------------------------------------------------------------------
43
44 class LauncherTest:
45 """Mixin for generic launcher tests"""
46 def setUp(self):
47 self.profile_dir = tempfile.mkdtemp(prefix="profile_")
48
49 def tearDown(self):
50 shutil.rmtree(self.profile_dir)
51
52 @property
53 def config(self):
54 return Config()
55
56 def build_launcher(self, **kwargs):
57 kw = dict(
58 work_dir=self.profile_dir,
59 profile_dir=self.profile_dir,
60 config=self.config,
61 cluster_id='',
62 log=logging.getLogger(),
63 )
64 kw.update(kwargs)
65 return self.launcher_class(**kw)
66
67 def test_profile_dir_arg(self):
68 launcher = self.build_launcher()
69 self.assertTrue("--profile-dir" in launcher.cluster_args)
70 self.assertTrue(self.profile_dir in launcher.cluster_args)
71
72 def test_cluster_id_arg(self):
73 launcher = self.build_launcher()
74 self.assertTrue("--cluster-id" in launcher.cluster_args)
75 idx = launcher.cluster_args.index("--cluster-id")
76 self.assertEqual(launcher.cluster_args[idx+1], '')
77 launcher.cluster_id = 'foo'
78 self.assertEqual(launcher.cluster_args[idx+1], 'foo')
79
80 def test_args(self):
81 launcher = self.build_launcher()
82 for arg in launcher.args:
83 self.assertTrue(isinstance(arg, string_types), str(arg))
84
85 class BatchTest:
86 """Tests for batch-system launchers (LSF, SGE, PBS)"""
87 def test_batch_template(self):
88 launcher = self.build_launcher()
89 batch_file = os.path.join(self.profile_dir, launcher.batch_file_name)
90 self.assertEqual(launcher.batch_file, batch_file)
91 launcher.write_batch_script(1)
92 self.assertTrue(os.path.isfile(batch_file))
93
94 class SSHTest:
95 """Tests for SSH launchers"""
96 def test_cluster_id_arg(self):
97 raise SkipTest("SSH Launchers don't support cluster ID")
98
99 def test_remote_profile_dir(self):
100 cfg = Config()
101 launcher_cfg = getattr(cfg, self.launcher_class.__name__)
102 launcher_cfg.remote_profile_dir = "foo"
103 launcher = self.build_launcher(config=cfg)
104 self.assertEqual(launcher.remote_profile_dir, "foo")
105
106 def test_remote_profile_dir_default(self):
107 launcher = self.build_launcher()
108 self.assertEqual(launcher.remote_profile_dir, self.profile_dir)
109
110 #-------------------------------------------------------------------------------
111 # Controller Launcher Tests
112 #-------------------------------------------------------------------------------
113
114 class ControllerLauncherTest(LauncherTest):
115 """Tests for Controller Launchers"""
116 pass
117
118 class TestLocalControllerLauncher(ControllerLauncherTest, TestCase):
119 launcher_class = launcher.LocalControllerLauncher
120
121 class TestMPIControllerLauncher(ControllerLauncherTest, TestCase):
122 launcher_class = launcher.MPIControllerLauncher
123
124 class TestPBSControllerLauncher(BatchTest, ControllerLauncherTest, TestCase):
125 launcher_class = launcher.PBSControllerLauncher
126
127 class TestSGEControllerLauncher(BatchTest, ControllerLauncherTest, TestCase):
128 launcher_class = launcher.SGEControllerLauncher
129
130 class TestLSFControllerLauncher(BatchTest, ControllerLauncherTest, TestCase):
131 launcher_class = launcher.LSFControllerLauncher
132
133 class TestHTCondorControllerLauncher(BatchTest, ControllerLauncherTest, TestCase):
134 launcher_class = launcher.HTCondorControllerLauncher
135
136 class TestSSHControllerLauncher(SSHTest, ControllerLauncherTest, TestCase):
137 launcher_class = launcher.SSHControllerLauncher
138
139 #-------------------------------------------------------------------------------
140 # Engine Set Launcher Tests
141 #-------------------------------------------------------------------------------
142
143 class EngineSetLauncherTest(LauncherTest):
144 """Tests for EngineSet launchers"""
145 pass
146
147 class TestLocalEngineSetLauncher(EngineSetLauncherTest, TestCase):
148 launcher_class = launcher.LocalEngineSetLauncher
149
150 class TestMPIEngineSetLauncher(EngineSetLauncherTest, TestCase):
151 launcher_class = launcher.MPIEngineSetLauncher
152
153 class TestPBSEngineSetLauncher(BatchTest, EngineSetLauncherTest, TestCase):
154 launcher_class = launcher.PBSEngineSetLauncher
155
156 class TestSGEEngineSetLauncher(BatchTest, EngineSetLauncherTest, TestCase):
157 launcher_class = launcher.SGEEngineSetLauncher
158
159 class TestLSFEngineSetLauncher(BatchTest, EngineSetLauncherTest, TestCase):
160 launcher_class = launcher.LSFEngineSetLauncher
161
162 class TestHTCondorEngineSetLauncher(BatchTest, EngineSetLauncherTest, TestCase):
163 launcher_class = launcher.HTCondorEngineSetLauncher
164
165 class TestSSHEngineSetLauncher(EngineSetLauncherTest, TestCase):
166 launcher_class = launcher.SSHEngineSetLauncher
167
168 def test_cluster_id_arg(self):
169 raise SkipTest("SSH Launchers don't support cluster ID")
170
171 class TestSSHProxyEngineSetLauncher(SSHTest, LauncherTest, TestCase):
172 launcher_class = launcher.SSHProxyEngineSetLauncher
173
174 class TestSSHEngineLauncher(SSHTest, LauncherTest, TestCase):
175 launcher_class = launcher.SSHEngineLauncher
176
177 #-------------------------------------------------------------------------------
178 # Windows Launcher Tests
179 #-------------------------------------------------------------------------------
180
181 class WinHPCTest:
182 """Tests for WinHPC Launchers"""
183 def test_batch_template(self):
184 launcher = self.build_launcher()
185 job_file = os.path.join(self.profile_dir, launcher.job_file_name)
186 self.assertEqual(launcher.job_file, job_file)
187 launcher.write_job_file(1)
188 self.assertTrue(os.path.isfile(job_file))
189
190 class TestWinHPCControllerLauncher(WinHPCTest, ControllerLauncherTest, TestCase):
191 launcher_class = launcher.WindowsHPCControllerLauncher
192
193 class TestWinHPCEngineSetLauncher(WinHPCTest, EngineSetLauncherTest, TestCase):
194 launcher_class = launcher.WindowsHPCEngineSetLauncher
@@ -1,221 +0,0 b''
1 # -*- coding: utf-8 -*-
2 """test LoadBalancedView objects
3
4 Authors:
5
6 * Min RK
7 """
8 #-------------------------------------------------------------------------------
9 # Copyright (C) 2011 The IPython Development Team
10 #
11 # Distributed under the terms of the BSD License. The full license is in
12 # the file COPYING, distributed as part of this software.
13 #-------------------------------------------------------------------------------
14
15 #-------------------------------------------------------------------------------
16 # Imports
17 #-------------------------------------------------------------------------------
18
19 import sys
20 import time
21
22 import zmq
23 from nose import SkipTest
24 from nose.plugins.attrib import attr
25
26 from IPython import parallel as pmod
27 from ipython_parallel import error
28
29 from ipython_parallel.tests import add_engines
30
31 from .clienttest import ClusterTestCase, crash, wait, skip_without
32
33 def setup():
34 add_engines(3, total=True)
35
36 class TestLoadBalancedView(ClusterTestCase):
37
38 def setUp(self):
39 ClusterTestCase.setUp(self)
40 self.view = self.client.load_balanced_view()
41
42 @attr('crash')
43 def test_z_crash_task(self):
44 """test graceful handling of engine death (balanced)"""
45 # self.add_engines(1)
46 ar = self.view.apply_async(crash)
47 self.assertRaisesRemote(error.EngineError, ar.get, 10)
48 eid = ar.engine_id
49 tic = time.time()
50 while eid in self.client.ids and time.time()-tic < 5:
51 time.sleep(.01)
52 self.client.spin()
53 self.assertFalse(eid in self.client.ids, "Engine should have died")
54
55 def test_map(self):
56 def f(x):
57 return x**2
58 data = list(range(16))
59 r = self.view.map_sync(f, data)
60 self.assertEqual(r, list(map(f, data)))
61
62 def test_map_generator(self):
63 def f(x):
64 return x**2
65
66 data = list(range(16))
67 r = self.view.map_sync(f, iter(data))
68 self.assertEqual(r, list(map(f, iter(data))))
69
70 def test_map_short_first(self):
71 def f(x,y):
72 if y is None:
73 return y
74 if x is None:
75 return x
76 return x*y
77 data = list(range(10))
78 data2 = list(range(4))
79
80 r = self.view.map_sync(f, data, data2)
81 self.assertEqual(r, list(map(f, data, data2)))
82
83 def test_map_short_last(self):
84 def f(x,y):
85 if y is None:
86 return y
87 if x is None:
88 return x
89 return x*y
90 data = list(range(4))
91 data2 = list(range(10))
92
93 r = self.view.map_sync(f, data, data2)
94 self.assertEqual(r, list(map(f, data, data2)))
95
96 def test_map_unordered(self):
97 def f(x):
98 return x**2
99 def slow_f(x):
100 import time
101 time.sleep(0.05*x)
102 return x**2
103 data = list(range(16,0,-1))
104 reference = list(map(f, data))
105
106 amr = self.view.map_async(slow_f, data, ordered=False)
107 self.assertTrue(isinstance(amr, pmod.AsyncMapResult))
108 # check individual elements, retrieved as they come
109 # list comprehension uses __iter__
110 astheycame = [ r for r in amr ]
111 # Ensure that at least one result came out of order:
112 self.assertNotEqual(astheycame, reference, "should not have preserved order")
113 self.assertEqual(sorted(astheycame, reverse=True), reference, "result corrupted")
114
115 def test_map_ordered(self):
116 def f(x):
117 return x**2
118 def slow_f(x):
119 import time
120 time.sleep(0.05*x)
121 return x**2
122 data = list(range(16,0,-1))
123 reference = list(map(f, data))
124
125 amr = self.view.map_async(slow_f, data)
126 self.assertTrue(isinstance(amr, pmod.AsyncMapResult))
127 # check individual elements, retrieved as they come
128 # list(amr) uses __iter__
129 astheycame = list(amr)
130 # Ensure that results came in order
131 self.assertEqual(astheycame, reference)
132 self.assertEqual(amr.result, reference)
133
134 def test_map_iterable(self):
135 """test map on iterables (balanced)"""
136 view = self.view
137 # 101 is prime, so it won't be evenly distributed
138 arr = range(101)
139 # so that it will be an iterator, even in Python 3
140 it = iter(arr)
141 r = view.map_sync(lambda x:x, arr)
142 self.assertEqual(r, list(arr))
143
144
145 def test_abort(self):
146 view = self.view
147 ar = self.client[:].apply_async(time.sleep, .5)
148 ar = self.client[:].apply_async(time.sleep, .5)
149 time.sleep(0.2)
150 ar2 = view.apply_async(lambda : 2)
151 ar3 = view.apply_async(lambda : 3)
152 view.abort(ar2)
153 view.abort(ar3.msg_ids)
154 self.assertRaises(error.TaskAborted, ar2.get)
155 self.assertRaises(error.TaskAborted, ar3.get)
156
157 def test_retries(self):
158 self.minimum_engines(3)
159 view = self.view
160 def fail():
161 assert False
162 for r in range(len(self.client)-1):
163 with view.temp_flags(retries=r):
164 self.assertRaisesRemote(AssertionError, view.apply_sync, fail)
165
166 with view.temp_flags(retries=len(self.client), timeout=0.1):
167 self.assertRaisesRemote(error.TaskTimeout, view.apply_sync, fail)
168
169 def test_short_timeout(self):
170 self.minimum_engines(2)
171 view = self.view
172 def fail():
173 import time
174 time.sleep(0.25)
175 assert False
176 with view.temp_flags(retries=1, timeout=0.01):
177 self.assertRaisesRemote(AssertionError, view.apply_sync, fail)
178
179 def test_invalid_dependency(self):
180 view = self.view
181 with view.temp_flags(after='12345'):
182 self.assertRaisesRemote(error.InvalidDependency, view.apply_sync, lambda : 1)
183
184 def test_impossible_dependency(self):
185 self.minimum_engines(2)
186 view = self.client.load_balanced_view()
187 ar1 = view.apply_async(lambda : 1)
188 ar1.get()
189 e1 = ar1.engine_id
190 e2 = e1
191 while e2 == e1:
192 ar2 = view.apply_async(lambda : 1)
193 ar2.get()
194 e2 = ar2.engine_id
195
196 with view.temp_flags(follow=[ar1, ar2]):
197 self.assertRaisesRemote(error.ImpossibleDependency, view.apply_sync, lambda : 1)
198
199
200 def test_follow(self):
201 ar = self.view.apply_async(lambda : 1)
202 ar.get()
203 ars = []
204 first_id = ar.engine_id
205
206 self.view.follow = ar
207 for i in range(5):
208 ars.append(self.view.apply_async(lambda : 1))
209 self.view.wait(ars)
210 for ar in ars:
211 self.assertEqual(ar.engine_id, first_id)
212
213 def test_after(self):
214 view = self.view
215 ar = view.apply_async(time.sleep, 0.5)
216 with view.temp_flags(after=ar):
217 ar2 = view.apply_async(lambda : 1)
218
219 ar.wait()
220 ar2.wait()
221 self.assertTrue(ar2.started >= ar.completed, "%s not >= %s"%(ar.started, ar.completed))
@@ -1,374 +0,0 b''
1 # -*- coding: utf-8 -*-
2 """Test Parallel magics
3
4 Authors:
5
6 * Min RK
7 """
8 #-------------------------------------------------------------------------------
9 # Copyright (C) 2011 The IPython Development Team
10 #
11 # Distributed under the terms of the BSD License. The full license is in
12 # the file COPYING, distributed as part of this software.
13 #-------------------------------------------------------------------------------
14
15 #-------------------------------------------------------------------------------
16 # Imports
17 #-------------------------------------------------------------------------------
18
19 import re
20 import time
21
22
23 from IPython.testing import decorators as dec
24 from IPython.utils.io import capture_output
25
26 from IPython import parallel as pmod
27 from ipython_parallel import AsyncResult
28
29 from ipython_parallel.tests import add_engines
30
31 from .clienttest import ClusterTestCase, generate_output
32
33 def setup():
34 add_engines(3, total=True)
35
36 class TestParallelMagics(ClusterTestCase):
37
38 def test_px_blocking(self):
39 ip = get_ipython()
40 v = self.client[-1:]
41 v.activate()
42 v.block=True
43
44 ip.magic('px a=5')
45 self.assertEqual(v['a'], [5])
46 ip.magic('px a=10')
47 self.assertEqual(v['a'], [10])
48 # just 'print a' works ~99% of the time, but this ensures that
49 # the stdout message has arrived when the result is finished:
50 with capture_output() as io:
51 ip.magic(
52 'px import sys,time;print(a);sys.stdout.flush();time.sleep(0.2)'
53 )
54 self.assertIn('[stdout:', io.stdout)
55 self.assertNotIn('\n\n', io.stdout)
56 assert io.stdout.rstrip().endswith('10')
57 self.assertRaisesRemote(ZeroDivisionError, ip.magic, 'px 1/0')
58
59 def _check_generated_stderr(self, stderr, n):
60 expected = [
61 r'\[stderr:\d+\]',
62 '^stderr$',
63 '^stderr2$',
64 ] * n
65
66 self.assertNotIn('\n\n', stderr)
67 lines = stderr.splitlines()
68 self.assertEqual(len(lines), len(expected), stderr)
69 for line,expect in zip(lines, expected):
70 if isinstance(expect, str):
71 expect = [expect]
72 for ex in expect:
73 assert re.search(ex, line) is not None, "Expected %r in %r" % (ex, line)
74
75 def test_cellpx_block_args(self):
76 """%%px --[no]block flags work"""
77 ip = get_ipython()
78 v = self.client[-1:]
79 v.activate()
80 v.block=False
81
82 for block in (True, False):
83 v.block = block
84 ip.magic("pxconfig --verbose")
85 with capture_output(display=False) as io:
86 ip.run_cell_magic("px", "", "1")
87 if block:
88 assert io.stdout.startswith("Parallel"), io.stdout
89 else:
90 assert io.stdout.startswith("Async"), io.stdout
91
92 with capture_output(display=False) as io:
93 ip.run_cell_magic("px", "--block", "1")
94 assert io.stdout.startswith("Parallel"), io.stdout
95
96 with capture_output(display=False) as io:
97 ip.run_cell_magic("px", "--noblock", "1")
98 assert io.stdout.startswith("Async"), io.stdout
99
100 def test_cellpx_groupby_engine(self):
101 """%%px --group-outputs=engine"""
102 ip = get_ipython()
103 v = self.client[:]
104 v.block = True
105 v.activate()
106
107 v['generate_output'] = generate_output
108
109 with capture_output(display=False) as io:
110 ip.run_cell_magic('px', '--group-outputs=engine', 'generate_output()')
111
112 self.assertNotIn('\n\n', io.stdout)
113 lines = io.stdout.splitlines()
114 expected = [
115 r'\[stdout:\d+\]',
116 'stdout',
117 'stdout2',
118 r'\[output:\d+\]',
119 r'IPython\.core\.display\.HTML',
120 r'IPython\.core\.display\.Math',
121 r'Out\[\d+:\d+\]:.*IPython\.core\.display\.Math',
122 ] * len(v)
123
124 self.assertEqual(len(lines), len(expected), io.stdout)
125 for line,expect in zip(lines, expected):
126 if isinstance(expect, str):
127 expect = [expect]
128 for ex in expect:
129 assert re.search(ex, line) is not None, "Expected %r in %r" % (ex, line)
130
131 self._check_generated_stderr(io.stderr, len(v))
132
133
134 def test_cellpx_groupby_order(self):
135 """%%px --group-outputs=order"""
136 ip = get_ipython()
137 v = self.client[:]
138 v.block = True
139 v.activate()
140
141 v['generate_output'] = generate_output
142
143 with capture_output(display=False) as io:
144 ip.run_cell_magic('px', '--group-outputs=order', 'generate_output()')
145
146 self.assertNotIn('\n\n', io.stdout)
147 lines = io.stdout.splitlines()
148 expected = []
149 expected.extend([
150 r'\[stdout:\d+\]',
151 'stdout',
152 'stdout2',
153 ] * len(v))
154 expected.extend([
155 r'\[output:\d+\]',
156 'IPython.core.display.HTML',
157 ] * len(v))
158 expected.extend([
159 r'\[output:\d+\]',
160 'IPython.core.display.Math',
161 ] * len(v))
162 expected.extend([
163 r'Out\[\d+:\d+\]:.*IPython\.core\.display\.Math'
164 ] * len(v))
165
166 self.assertEqual(len(lines), len(expected), io.stdout)
167 for line,expect in zip(lines, expected):
168 if isinstance(expect, str):
169 expect = [expect]
170 for ex in expect:
171 assert re.search(ex, line) is not None, "Expected %r in %r" % (ex, line)
172
173 self._check_generated_stderr(io.stderr, len(v))
174
175 def test_cellpx_groupby_type(self):
176 """%%px --group-outputs=type"""
177 ip = get_ipython()
178 v = self.client[:]
179 v.block = True
180 v.activate()
181
182 v['generate_output'] = generate_output
183
184 with capture_output(display=False) as io:
185 ip.run_cell_magic('px', '--group-outputs=type', 'generate_output()')
186
187 self.assertNotIn('\n\n', io.stdout)
188 lines = io.stdout.splitlines()
189
190 expected = []
191 expected.extend([
192 r'\[stdout:\d+\]',
193 'stdout',
194 'stdout2',
195 ] * len(v))
196 expected.extend([
197 r'\[output:\d+\]',
198 r'IPython\.core\.display\.HTML',
199 r'IPython\.core\.display\.Math',
200 ] * len(v))
201 expected.extend([
202 (r'Out\[\d+:\d+\]', r'IPython\.core\.display\.Math')
203 ] * len(v))
204
205 self.assertEqual(len(lines), len(expected), io.stdout)
206 for line,expect in zip(lines, expected):
207 if isinstance(expect, str):
208 expect = [expect]
209 for ex in expect:
210 assert re.search(ex, line) is not None, "Expected %r in %r" % (ex, line)
211
212 self._check_generated_stderr(io.stderr, len(v))
213
214
215 def test_px_nonblocking(self):
216 ip = get_ipython()
217 v = self.client[-1:]
218 v.activate()
219 v.block=False
220
221 ip.magic('px a=5')
222 self.assertEqual(v['a'], [5])
223 ip.magic('px a=10')
224 self.assertEqual(v['a'], [10])
225 ip.magic('pxconfig --verbose')
226 with capture_output() as io:
227 ar = ip.magic('px print (a)')
228 self.assertIsInstance(ar, AsyncResult)
229 self.assertIn('Async', io.stdout)
230 self.assertNotIn('[stdout:', io.stdout)
231 self.assertNotIn('\n\n', io.stdout)
232
233 ar = ip.magic('px 1/0')
234 self.assertRaisesRemote(ZeroDivisionError, ar.get)
235
236 def test_autopx_blocking(self):
237 ip = get_ipython()
238 v = self.client[-1]
239 v.activate()
240 v.block=True
241
242 with capture_output(display=False) as io:
243 ip.magic('autopx')
244 ip.run_cell('\n'.join(('a=5','b=12345','c=0')))
245 ip.run_cell('b*=2')
246 ip.run_cell('print (b)')
247 ip.run_cell('b')
248 ip.run_cell("b/c")
249 ip.magic('autopx')
250
251 output = io.stdout
252
253 assert output.startswith('%autopx enabled'), output
254 assert output.rstrip().endswith('%autopx disabled'), output
255 self.assertIn('ZeroDivisionError', output)
256 self.assertIn('\nOut[', output)
257 self.assertIn(': 24690', output)
258 ar = v.get_result(-1)
259 self.assertEqual(v['a'], 5)
260 self.assertEqual(v['b'], 24690)
261 self.assertRaisesRemote(ZeroDivisionError, ar.get)
262
263 def test_autopx_nonblocking(self):
264 ip = get_ipython()
265 v = self.client[-1]
266 v.activate()
267 v.block=False
268
269 with capture_output() as io:
270 ip.magic('autopx')
271 ip.run_cell('\n'.join(('a=5','b=10','c=0')))
272 ip.run_cell('print (b)')
273 ip.run_cell('import time; time.sleep(0.1)')
274 ip.run_cell("b/c")
275 ip.run_cell('b*=2')
276 ip.magic('autopx')
277
278 output = io.stdout.rstrip()
279
280 assert output.startswith('%autopx enabled'), output
281 assert output.endswith('%autopx disabled'), output
282 self.assertNotIn('ZeroDivisionError', output)
283 ar = v.get_result(-2)
284 self.assertRaisesRemote(ZeroDivisionError, ar.get)
285 # prevent TaskAborted on pulls, due to ZeroDivisionError
286 time.sleep(0.5)
287 self.assertEqual(v['a'], 5)
288 # b*=2 will not fire, due to abort
289 self.assertEqual(v['b'], 10)
290
291 def test_result(self):
292 ip = get_ipython()
293 v = self.client[-1]
294 v.activate()
295 data = dict(a=111,b=222)
296 v.push(data, block=True)
297
298 for name in ('a', 'b'):
299 ip.magic('px ' + name)
300 with capture_output(display=False) as io:
301 ip.magic('pxresult')
302 self.assertIn(str(data[name]), io.stdout)
303
304 @dec.skipif_not_matplotlib
305 def test_px_pylab(self):
306 """%pylab works on engines"""
307 ip = get_ipython()
308 v = self.client[-1]
309 v.block = True
310 v.activate()
311
312 with capture_output() as io:
313 ip.magic("px %pylab inline")
314
315 self.assertIn("Populating the interactive namespace from numpy and matplotlib", io.stdout)
316
317 with capture_output(display=False) as io:
318 ip.magic("px plot(rand(100))")
319 self.assertIn('Out[', io.stdout)
320 self.assertIn('matplotlib.lines', io.stdout)
321
322 def test_pxconfig(self):
323 ip = get_ipython()
324 rc = self.client
325 v = rc.activate(-1, '_tst')
326 self.assertEqual(v.targets, rc.ids[-1])
327 ip.magic("%pxconfig_tst -t :")
328 self.assertEqual(v.targets, rc.ids)
329 ip.magic("%pxconfig_tst -t ::2")
330 self.assertEqual(v.targets, rc.ids[::2])
331 ip.magic("%pxconfig_tst -t 1::2")
332 self.assertEqual(v.targets, rc.ids[1::2])
333 ip.magic("%pxconfig_tst -t 1")
334 self.assertEqual(v.targets, 1)
335 ip.magic("%pxconfig_tst --block")
336 self.assertEqual(v.block, True)
337 ip.magic("%pxconfig_tst --noblock")
338 self.assertEqual(v.block, False)
339
340 def test_cellpx_targets(self):
341 """%%px --targets doesn't change defaults"""
342 ip = get_ipython()
343 rc = self.client
344 view = rc.activate(rc.ids)
345 self.assertEqual(view.targets, rc.ids)
346 ip.magic('pxconfig --verbose')
347 for cell in ("pass", "1/0"):
348 with capture_output(display=False) as io:
349 try:
350 ip.run_cell_magic("px", "--targets all", cell)
351 except pmod.RemoteError:
352 pass
353 self.assertIn('engine(s): all', io.stdout)
354 self.assertEqual(view.targets, rc.ids)
355
356
357 def test_cellpx_block(self):
358 """%%px --block doesn't change default"""
359 ip = get_ipython()
360 rc = self.client
361 view = rc.activate(rc.ids)
362 view.block = False
363 self.assertEqual(view.targets, rc.ids)
364 ip.magic('pxconfig --verbose')
365 for cell in ("pass", "1/0"):
366 with capture_output(display=False) as io:
367 try:
368 ip.run_cell_magic("px", "--block", cell)
369 except pmod.RemoteError:
370 pass
371 self.assertNotIn('Async', io.stdout)
372 self.assertEqual(view.block, False)
373
374
@@ -1,56 +0,0 b''
1 """Tests for mongodb backend
2
3 Authors:
4
5 * Min RK
6 """
7
8 #-------------------------------------------------------------------------------
9 # Copyright (C) 2011 The IPython Development Team
10 #
11 # Distributed under the terms of the BSD License. The full license is in
12 # the file COPYING, distributed as part of this software.
13 #-------------------------------------------------------------------------------
14
15 #-------------------------------------------------------------------------------
16 # Imports
17 #-------------------------------------------------------------------------------
18
19 import os
20
21 from unittest import TestCase
22
23 from nose import SkipTest
24
25 from pymongo import Connection
26 from ipython_parallel.controller.mongodb import MongoDB
27
28 from . import test_db
29
30 conn_kwargs = {}
31 if 'DB_IP' in os.environ:
32 conn_kwargs['host'] = os.environ['DB_IP']
33 if 'DBA_MONGODB_ADMIN_URI' in os.environ:
34 # On ShiningPanda, we need a username and password to connect. They are
35 # passed in a mongodb:// URI.
36 conn_kwargs['host'] = os.environ['DBA_MONGODB_ADMIN_URI']
37 if 'DB_PORT' in os.environ:
38 conn_kwargs['port'] = int(os.environ['DB_PORT'])
39
40 try:
41 c = Connection(**conn_kwargs)
42 except Exception:
43 c=None
44
45 class TestMongoBackend(test_db.TaskDBTest, TestCase):
46 """MongoDB backend tests"""
47
48 def create_db(self):
49 try:
50 return MongoDB(database='iptestdb', _connection=c)
51 except Exception:
52 raise SkipTest("Couldn't connect to mongodb")
53
54 def teardown(self):
55 if c is not None:
56 c.drop_database('iptestdb')
This diff has been collapsed as it changes many lines, (843 lines changed) Show them Hide them
@@ -1,843 +0,0 b''
1 # -*- coding: utf-8 -*-
2 """test View objects"""
3
4 # Copyright (c) IPython Development Team.
5 # Distributed under the terms of the Modified BSD License.
6
7 import base64
8 import sys
9 import platform
10 import time
11 from collections import namedtuple
12 from tempfile import NamedTemporaryFile
13
14 import zmq
15 from nose.plugins.attrib import attr
16
17 from IPython.testing import decorators as dec
18 from IPython.utils.io import capture_output
19 from IPython.utils.py3compat import unicode_type
20
21 from IPython import parallel as pmod
22 from ipython_parallel import error
23 from ipython_parallel import AsyncResult, AsyncHubResult, AsyncMapResult
24 from ipython_parallel.util import interactive
25
26 from ipython_parallel.tests import add_engines
27
28 from .clienttest import ClusterTestCase, crash, wait, skip_without
29
30 def setup():
31 add_engines(3, total=True)
32
33 point = namedtuple("point", "x y")
34
35 class TestView(ClusterTestCase):
36
37 def setUp(self):
38 # On Win XP, wait for resource cleanup, else parallel test group fails
39 if platform.system() == "Windows" and platform.win32_ver()[0] == "XP":
40 # 1 sec fails. 1.5 sec seems ok. Using 2 sec for margin of safety
41 time.sleep(2)
42 super(TestView, self).setUp()
43
44 @attr('crash')
45 def test_z_crash_mux(self):
46 """test graceful handling of engine death (direct)"""
47 # self.add_engines(1)
48 eid = self.client.ids[-1]
49 ar = self.client[eid].apply_async(crash)
50 self.assertRaisesRemote(error.EngineError, ar.get, 10)
51 eid = ar.engine_id
52 tic = time.time()
53 while eid in self.client.ids and time.time()-tic < 5:
54 time.sleep(.01)
55 self.client.spin()
56 self.assertFalse(eid in self.client.ids, "Engine should have died")
57
58 def test_push_pull(self):
59 """test pushing and pulling"""
60 data = dict(a=10, b=1.05, c=list(range(10)), d={'e':(1,2),'f':'hi'})
61 t = self.client.ids[-1]
62 v = self.client[t]
63 push = v.push
64 pull = v.pull
65 v.block=True
66 nengines = len(self.client)
67 push({'data':data})
68 d = pull('data')
69 self.assertEqual(d, data)
70 self.client[:].push({'data':data})
71 d = self.client[:].pull('data', block=True)
72 self.assertEqual(d, nengines*[data])
73 ar = push({'data':data}, block=False)
74 self.assertTrue(isinstance(ar, AsyncResult))
75 r = ar.get()
76 ar = self.client[:].pull('data', block=False)
77 self.assertTrue(isinstance(ar, AsyncResult))
78 r = ar.get()
79 self.assertEqual(r, nengines*[data])
80 self.client[:].push(dict(a=10,b=20))
81 r = self.client[:].pull(('a','b'), block=True)
82 self.assertEqual(r, nengines*[[10,20]])
83
84 def test_push_pull_function(self):
85 "test pushing and pulling functions"
86 def testf(x):
87 return 2.0*x
88
89 t = self.client.ids[-1]
90 v = self.client[t]
91 v.block=True
92 push = v.push
93 pull = v.pull
94 execute = v.execute
95 push({'testf':testf})
96 r = pull('testf')
97 self.assertEqual(r(1.0), testf(1.0))
98 execute('r = testf(10)')
99 r = pull('r')
100 self.assertEqual(r, testf(10))
101 ar = self.client[:].push({'testf':testf}, block=False)
102 ar.get()
103 ar = self.client[:].pull('testf', block=False)
104 rlist = ar.get()
105 for r in rlist:
106 self.assertEqual(r(1.0), testf(1.0))
107 execute("def g(x): return x*x")
108 r = pull(('testf','g'))
109 self.assertEqual((r[0](10),r[1](10)), (testf(10), 100))
110
111 def test_push_function_globals(self):
112 """test that pushed functions have access to globals"""
113 @interactive
114 def geta():
115 return a
116 # self.add_engines(1)
117 v = self.client[-1]
118 v.block=True
119 v['f'] = geta
120 self.assertRaisesRemote(NameError, v.execute, 'b=f()')
121 v.execute('a=5')
122 v.execute('b=f()')
123 self.assertEqual(v['b'], 5)
124
125 def test_push_function_defaults(self):
126 """test that pushed functions preserve default args"""
127 def echo(a=10):
128 return a
129 v = self.client[-1]
130 v.block=True
131 v['f'] = echo
132 v.execute('b=f()')
133 self.assertEqual(v['b'], 10)
134
135 def test_get_result(self):
136 """test getting results from the Hub."""
137 c = pmod.Client(profile='iptest')
138 # self.add_engines(1)
139 t = c.ids[-1]
140 v = c[t]
141 v2 = self.client[t]
142 ar = v.apply_async(wait, 1)
143 # give the monitor time to notice the message
144 time.sleep(.25)
145 ahr = v2.get_result(ar.msg_ids[0], owner=False)
146 self.assertIsInstance(ahr, AsyncHubResult)
147 self.assertEqual(ahr.get(), ar.get())
148 ar2 = v2.get_result(ar.msg_ids[0])
149 self.assertNotIsInstance(ar2, AsyncHubResult)
150 self.assertEqual(ahr.get(), ar2.get())
151 c.spin()
152 c.close()
153
154 def test_run_newline(self):
155 """test that run appends newline to files"""
156 with NamedTemporaryFile('w', delete=False) as f:
157 f.write("""def g():
158 return 5
159 """)
160 v = self.client[-1]
161 v.run(f.name, block=True)
162 self.assertEqual(v.apply_sync(lambda f: f(), pmod.Reference('g')), 5)
163
164 def test_apply_tracked(self):
165 """test tracking for apply"""
166 # self.add_engines(1)
167 t = self.client.ids[-1]
168 v = self.client[t]
169 v.block=False
170 def echo(n=1024*1024, **kwargs):
171 with v.temp_flags(**kwargs):
172 return v.apply(lambda x: x, 'x'*n)
173 ar = echo(1, track=False)
174 self.assertTrue(isinstance(ar._tracker, zmq.MessageTracker))
175 self.assertTrue(ar.sent)
176 ar = echo(track=True)
177 self.assertTrue(isinstance(ar._tracker, zmq.MessageTracker))
178 self.assertEqual(ar.sent, ar._tracker.done)
179 ar._tracker.wait()
180 self.assertTrue(ar.sent)
181
182 def test_push_tracked(self):
183 t = self.client.ids[-1]
184 ns = dict(x='x'*1024*1024)
185 v = self.client[t]
186 ar = v.push(ns, block=False, track=False)
187 self.assertTrue(isinstance(ar._tracker, zmq.MessageTracker))
188 self.assertTrue(ar.sent)
189
190 ar = v.push(ns, block=False, track=True)
191 self.assertTrue(isinstance(ar._tracker, zmq.MessageTracker))
192 ar._tracker.wait()
193 self.assertEqual(ar.sent, ar._tracker.done)
194 self.assertTrue(ar.sent)
195 ar.get()
196
197 def test_scatter_tracked(self):
198 t = self.client.ids
199 x='x'*1024*1024
200 ar = self.client[t].scatter('x', x, block=False, track=False)
201 self.assertTrue(isinstance(ar._tracker, zmq.MessageTracker))
202 self.assertTrue(ar.sent)
203
204 ar = self.client[t].scatter('x', x, block=False, track=True)
205 self.assertTrue(isinstance(ar._tracker, zmq.MessageTracker))
206 self.assertEqual(ar.sent, ar._tracker.done)
207 ar._tracker.wait()
208 self.assertTrue(ar.sent)
209 ar.get()
210
211 def test_remote_reference(self):
212 v = self.client[-1]
213 v['a'] = 123
214 ra = pmod.Reference('a')
215 b = v.apply_sync(lambda x: x, ra)
216 self.assertEqual(b, 123)
217
218
219 def test_scatter_gather(self):
220 view = self.client[:]
221 seq1 = list(range(16))
222 view.scatter('a', seq1)
223 seq2 = view.gather('a', block=True)
224 self.assertEqual(seq2, seq1)
225 self.assertRaisesRemote(NameError, view.gather, 'asdf', block=True)
226
227 @skip_without('numpy')
228 def test_scatter_gather_numpy(self):
229 import numpy
230 from numpy.testing.utils import assert_array_equal
231 view = self.client[:]
232 a = numpy.arange(64)
233 view.scatter('a', a, block=True)
234 b = view.gather('a', block=True)
235 assert_array_equal(b, a)
236
237 def test_scatter_gather_lazy(self):
238 """scatter/gather with targets='all'"""
239 view = self.client.direct_view(targets='all')
240 x = list(range(64))
241 view.scatter('x', x)
242 gathered = view.gather('x', block=True)
243 self.assertEqual(gathered, x)
244
245
246 @dec.known_failure_py3
247 @skip_without('numpy')
248 def test_push_numpy_nocopy(self):
249 import numpy
250 view = self.client[:]
251 a = numpy.arange(64)
252 view['A'] = a
253 @interactive
254 def check_writeable(x):
255 return x.flags.writeable
256
257 for flag in view.apply_sync(check_writeable, pmod.Reference('A')):
258 self.assertFalse(flag, "array is writeable, push shouldn't have pickled it")
259
260 view.push(dict(B=a))
261 for flag in view.apply_sync(check_writeable, pmod.Reference('B')):
262 self.assertFalse(flag, "array is writeable, push shouldn't have pickled it")
263
264 @skip_without('numpy')
265 def test_apply_numpy(self):
266 """view.apply(f, ndarray)"""
267 import numpy
268 from numpy.testing.utils import assert_array_equal
269
270 A = numpy.random.random((100,100))
271 view = self.client[-1]
272 for dt in [ 'int32', 'uint8', 'float32', 'float64' ]:
273 B = A.astype(dt)
274 C = view.apply_sync(lambda x:x, B)
275 assert_array_equal(B,C)
276
277 @skip_without('numpy')
278 def test_apply_numpy_object_dtype(self):
279 """view.apply(f, ndarray) with dtype=object"""
280 import numpy
281 from numpy.testing.utils import assert_array_equal
282 view = self.client[-1]
283
284 A = numpy.array([dict(a=5)])
285 B = view.apply_sync(lambda x:x, A)
286 assert_array_equal(A,B)
287
288 A = numpy.array([(0, dict(b=10))], dtype=[('i', int), ('o', object)])
289 B = view.apply_sync(lambda x:x, A)
290 assert_array_equal(A,B)
291
292 @skip_without('numpy')
293 def test_push_pull_recarray(self):
294 """push/pull recarrays"""
295 import numpy
296 from numpy.testing.utils import assert_array_equal
297
298 view = self.client[-1]
299
300 R = numpy.array([
301 (1, 'hi', 0.),
302 (2**30, 'there', 2.5),
303 (-99999, 'world', -12345.6789),
304 ], [('n', int), ('s', '|S10'), ('f', float)])
305
306 view['RR'] = R
307 R2 = view['RR']
308
309 r_dtype, r_shape = view.apply_sync(interactive(lambda : (RR.dtype, RR.shape)))
310 self.assertEqual(r_dtype, R.dtype)
311 self.assertEqual(r_shape, R.shape)
312 self.assertEqual(R2.dtype, R.dtype)
313 self.assertEqual(R2.shape, R.shape)
314 assert_array_equal(R2, R)
315
316 @skip_without('pandas')
317 def test_push_pull_timeseries(self):
318 """push/pull pandas.TimeSeries"""
319 import pandas
320
321 ts = pandas.TimeSeries(list(range(10)))
322
323 view = self.client[-1]
324
325 view.push(dict(ts=ts), block=True)
326 rts = view['ts']
327
328 self.assertEqual(type(rts), type(ts))
329 self.assertTrue((ts == rts).all())
330
331 def test_map(self):
332 view = self.client[:]
333 def f(x):
334 return x**2
335 data = list(range(16))
336 r = view.map_sync(f, data)
337 self.assertEqual(r, list(map(f, data)))
338
339 def test_map_empty_sequence(self):
340 view = self.client[:]
341 r = view.map_sync(lambda x: x, [])
342 self.assertEqual(r, [])
343
344 def test_map_iterable(self):
345 """test map on iterables (direct)"""
346 view = self.client[:]
347 # 101 is prime, so it won't be evenly distributed
348 arr = range(101)
349 # ensure it will be an iterator, even in Python 3
350 it = iter(arr)
351 r = view.map_sync(lambda x: x, it)
352 self.assertEqual(r, list(arr))
353
354 @skip_without('numpy')
355 def test_map_numpy(self):
356 """test map on numpy arrays (direct)"""
357 import numpy
358 from numpy.testing.utils import assert_array_equal
359
360 view = self.client[:]
361 # 101 is prime, so it won't be evenly distributed
362 arr = numpy.arange(101)
363 r = view.map_sync(lambda x: x, arr)
364 assert_array_equal(r, arr)
365
366 def test_scatter_gather_nonblocking(self):
367 data = list(range(16))
368 view = self.client[:]
369 view.scatter('a', data, block=False)
370 ar = view.gather('a', block=False)
371 self.assertEqual(ar.get(), data)
372
373 @skip_without('numpy')
374 def test_scatter_gather_numpy_nonblocking(self):
375 import numpy
376 from numpy.testing.utils import assert_array_equal
377 a = numpy.arange(64)
378 view = self.client[:]
379 ar = view.scatter('a', a, block=False)
380 self.assertTrue(isinstance(ar, AsyncResult))
381 amr = view.gather('a', block=False)
382 self.assertTrue(isinstance(amr, AsyncMapResult))
383 assert_array_equal(amr.get(), a)
384
385 def test_execute(self):
386 view = self.client[:]
387 # self.client.debug=True
388 execute = view.execute
389 ar = execute('c=30', block=False)
390 self.assertTrue(isinstance(ar, AsyncResult))
391 ar = execute('d=[0,1,2]', block=False)
392 self.client.wait(ar, 1)
393 self.assertEqual(len(ar.get()), len(self.client))
394 for c in view['c']:
395 self.assertEqual(c, 30)
396
397 def test_abort(self):
398 view = self.client[-1]
399 ar = view.execute('import time; time.sleep(1)', block=False)
400 ar2 = view.apply_async(lambda : 2)
401 ar3 = view.apply_async(lambda : 3)
402 view.abort(ar2)
403 view.abort(ar3.msg_ids)
404 self.assertRaises(error.TaskAborted, ar2.get)
405 self.assertRaises(error.TaskAborted, ar3.get)
406
407 def test_abort_all(self):
408 """view.abort() aborts all outstanding tasks"""
409 view = self.client[-1]
410 ars = [ view.apply_async(time.sleep, 0.25) for i in range(10) ]
411 view.abort()
412 view.wait(timeout=5)
413 for ar in ars[5:]:
414 self.assertRaises(error.TaskAborted, ar.get)
415
416 def test_temp_flags(self):
417 view = self.client[-1]
418 view.block=True
419 with view.temp_flags(block=False):
420 self.assertFalse(view.block)
421 self.assertTrue(view.block)
422
423 @dec.known_failure_py3
424 def test_importer(self):
425 view = self.client[-1]
426 view.clear(block=True)
427 with view.importer:
428 import re
429
430 @interactive
431 def findall(pat, s):
432 # this globals() step isn't necessary in real code
433 # only to prevent a closure in the test
434 re = globals()['re']
435 return re.findall(pat, s)
436
437 self.assertEqual(view.apply_sync(findall, '\w+', 'hello world'), 'hello world'.split())
438
439 def test_unicode_execute(self):
440 """test executing unicode strings"""
441 v = self.client[-1]
442 v.block=True
443 if sys.version_info[0] >= 3:
444 code="a='é'"
445 else:
446 code=u"a=u'é'"
447 v.execute(code)
448 self.assertEqual(v['a'], u'é')
449
450 def test_unicode_apply_result(self):
451 """test unicode apply results"""
452 v = self.client[-1]
453 r = v.apply_sync(lambda : u'é')
454 self.assertEqual(r, u'é')
455
456 def test_unicode_apply_arg(self):
457 """test passing unicode arguments to apply"""
458 v = self.client[-1]
459
460 @interactive
461 def check_unicode(a, check):
462 assert not isinstance(a, bytes), "%r is bytes, not unicode"%a
463 assert isinstance(check, bytes), "%r is not bytes"%check
464 assert a.encode('utf8') == check, "%s != %s"%(a,check)
465
466 for s in [ u'é', u'ßø®∫',u'asdf' ]:
467 try:
468 v.apply_sync(check_unicode, s, s.encode('utf8'))
469 except error.RemoteError as e:
470 if e.ename == 'AssertionError':
471 self.fail(e.evalue)
472 else:
473 raise e
474
475 def test_map_reference(self):
476 """view.map(<Reference>, *seqs) should work"""
477 v = self.client[:]
478 v.scatter('n', self.client.ids, flatten=True)
479 v.execute("f = lambda x,y: x*y")
480 rf = pmod.Reference('f')
481 nlist = list(range(10))
482 mlist = nlist[::-1]
483 expected = [ m*n for m,n in zip(mlist, nlist) ]
484 result = v.map_sync(rf, mlist, nlist)
485 self.assertEqual(result, expected)
486
487 def test_apply_reference(self):
488 """view.apply(<Reference>, *args) should work"""
489 v = self.client[:]
490 v.scatter('n', self.client.ids, flatten=True)
491 v.execute("f = lambda x: n*x")
492 rf = pmod.Reference('f')
493 result = v.apply_sync(rf, 5)
494 expected = [ 5*id for id in self.client.ids ]
495 self.assertEqual(result, expected)
496
497 def test_eval_reference(self):
498 v = self.client[self.client.ids[0]]
499 v['g'] = list(range(5))
500 rg = pmod.Reference('g[0]')
501 echo = lambda x:x
502 self.assertEqual(v.apply_sync(echo, rg), 0)
503
504 def test_reference_nameerror(self):
505 v = self.client[self.client.ids[0]]
506 r = pmod.Reference('elvis_has_left')
507 echo = lambda x:x
508 self.assertRaisesRemote(NameError, v.apply_sync, echo, r)
509
510 def test_single_engine_map(self):
511 e0 = self.client[self.client.ids[0]]
512 r = list(range(5))
513 check = [ -1*i for i in r ]
514 result = e0.map_sync(lambda x: -1*x, r)
515 self.assertEqual(result, check)
516
517 def test_len(self):
518 """len(view) makes sense"""
519 e0 = self.client[self.client.ids[0]]
520 self.assertEqual(len(e0), 1)
521 v = self.client[:]
522 self.assertEqual(len(v), len(self.client.ids))
523 v = self.client.direct_view('all')
524 self.assertEqual(len(v), len(self.client.ids))
525 v = self.client[:2]
526 self.assertEqual(len(v), 2)
527 v = self.client[:1]
528 self.assertEqual(len(v), 1)
529 v = self.client.load_balanced_view()
530 self.assertEqual(len(v), len(self.client.ids))
531
532
533 # begin execute tests
534
535 def test_execute_reply(self):
536 e0 = self.client[self.client.ids[0]]
537 e0.block = True
538 ar = e0.execute("5", silent=False)
539 er = ar.get()
540 self.assertEqual(str(er), "<ExecuteReply[%i]: 5>" % er.execution_count)
541 self.assertEqual(er.execute_result['data']['text/plain'], '5')
542
543 def test_execute_reply_rich(self):
544 e0 = self.client[self.client.ids[0]]
545 e0.block = True
546 e0.execute("from IPython.display import Image, HTML")
547 ar = e0.execute("Image(data=b'garbage', format='png', width=10)", silent=False)
548 er = ar.get()
549 b64data = base64.encodestring(b'garbage').decode('ascii')
550 self.assertEqual(er._repr_png_(), (b64data, dict(width=10)))
551 ar = e0.execute("HTML('<b>bold</b>')", silent=False)
552 er = ar.get()
553 self.assertEqual(er._repr_html_(), "<b>bold</b>")
554
555 def test_execute_reply_stdout(self):
556 e0 = self.client[self.client.ids[0]]
557 e0.block = True
558 ar = e0.execute("print (5)", silent=False)
559 er = ar.get()
560 self.assertEqual(er.stdout.strip(), '5')
561
562 def test_execute_result(self):
563 """execute triggers execute_result with silent=False"""
564 view = self.client[:]
565 ar = view.execute("5", silent=False, block=True)
566
567 expected = [{'text/plain' : '5'}] * len(view)
568 mimes = [ out['data'] for out in ar.execute_result ]
569 self.assertEqual(mimes, expected)
570
571 def test_execute_silent(self):
572 """execute does not trigger execute_result with silent=True"""
573 view = self.client[:]
574 ar = view.execute("5", block=True)
575 expected = [None] * len(view)
576 self.assertEqual(ar.execute_result, expected)
577
578 def test_execute_magic(self):
579 """execute accepts IPython commands"""
580 view = self.client[:]
581 view.execute("a = 5")
582 ar = view.execute("%whos", block=True)
583 # this will raise, if that failed
584 ar.get(5)
585 for stdout in ar.stdout:
586 lines = stdout.splitlines()
587 self.assertEqual(lines[0].split(), ['Variable', 'Type', 'Data/Info'])
588 found = False
589 for line in lines[2:]:
590 split = line.split()
591 if split == ['a', 'int', '5']:
592 found = True
593 break
594 self.assertTrue(found, "whos output wrong: %s" % stdout)
595
596 def test_execute_displaypub(self):
597 """execute tracks display_pub output"""
598 view = self.client[:]
599 view.execute("from IPython.core.display import *")
600 ar = view.execute("[ display(i) for i in range(5) ]", block=True)
601
602 expected = [ {u'text/plain' : unicode_type(j)} for j in range(5) ]
603 for outputs in ar.outputs:
604 mimes = [ out['data'] for out in outputs ]
605 self.assertEqual(mimes, expected)
606
607 def test_apply_displaypub(self):
608 """apply tracks display_pub output"""
609 view = self.client[:]
610 view.execute("from IPython.core.display import *")
611
612 @interactive
613 def publish():
614 [ display(i) for i in range(5) ]
615
616 ar = view.apply_async(publish)
617 ar.get(5)
618 expected = [ {u'text/plain' : unicode_type(j)} for j in range(5) ]
619 for outputs in ar.outputs:
620 mimes = [ out['data'] for out in outputs ]
621 self.assertEqual(mimes, expected)
622
623 def test_execute_raises(self):
624 """exceptions in execute requests raise appropriately"""
625 view = self.client[-1]
626 ar = view.execute("1/0")
627 self.assertRaisesRemote(ZeroDivisionError, ar.get, 2)
628
629 def test_remoteerror_render_exception(self):
630 """RemoteErrors get nice tracebacks"""
631 view = self.client[-1]
632 ar = view.execute("1/0")
633 ip = get_ipython()
634 ip.user_ns['ar'] = ar
635 with capture_output() as io:
636 ip.run_cell("ar.get(2)")
637
638 self.assertTrue('ZeroDivisionError' in io.stdout, io.stdout)
639
640 def test_compositeerror_render_exception(self):
641 """CompositeErrors get nice tracebacks"""
642 view = self.client[:]
643 ar = view.execute("1/0")
644 ip = get_ipython()
645 ip.user_ns['ar'] = ar
646
647 with capture_output() as io:
648 ip.run_cell("ar.get(2)")
649
650 count = min(error.CompositeError.tb_limit, len(view))
651
652 self.assertEqual(io.stdout.count('ZeroDivisionError'), count * 2, io.stdout)
653 self.assertEqual(io.stdout.count('by zero'), count, io.stdout)
654 self.assertEqual(io.stdout.count(':execute'), count, io.stdout)
655
656 def test_compositeerror_truncate(self):
657 """Truncate CompositeErrors with many exceptions"""
658 view = self.client[:]
659 msg_ids = []
660 for i in range(10):
661 ar = view.execute("1/0")
662 msg_ids.extend(ar.msg_ids)
663
664 ar = self.client.get_result(msg_ids)
665 try:
666 ar.get()
667 except error.CompositeError as _e:
668 e = _e
669 else:
670 self.fail("Should have raised CompositeError")
671
672 lines = e.render_traceback()
673 with capture_output() as io:
674 e.print_traceback()
675
676 self.assertTrue("more exceptions" in lines[-1])
677 count = e.tb_limit
678
679 self.assertEqual(io.stdout.count('ZeroDivisionError'), 2 * count, io.stdout)
680 self.assertEqual(io.stdout.count('by zero'), count, io.stdout)
681 self.assertEqual(io.stdout.count(':execute'), count, io.stdout)
682
683 @dec.skipif_not_matplotlib
684 def test_magic_pylab(self):
685 """%pylab works on engines"""
686 view = self.client[-1]
687 ar = view.execute("%pylab inline")
688 # at least check if this raised:
689 reply = ar.get(5)
690 # include imports, in case user config
691 ar = view.execute("plot(rand(100))", silent=False)
692 reply = ar.get(5)
693 self.assertEqual(len(reply.outputs), 1)
694 output = reply.outputs[0]
695 self.assertTrue("data" in output)
696 data = output['data']
697 self.assertTrue("image/png" in data)
698
699 def test_func_default_func(self):
700 """interactively defined function as apply func default"""
701 def foo():
702 return 'foo'
703
704 def bar(f=foo):
705 return f()
706
707 view = self.client[-1]
708 ar = view.apply_async(bar)
709 r = ar.get(10)
710 self.assertEqual(r, 'foo')
711 def test_data_pub_single(self):
712 view = self.client[-1]
713 ar = view.execute('\n'.join([
714 'from IPython.kernel.zmq.datapub import publish_data',
715 'for i in range(5):',
716 ' publish_data(dict(i=i))'
717 ]), block=False)
718 self.assertTrue(isinstance(ar.data, dict))
719 ar.get(5)
720 self.assertEqual(ar.data, dict(i=4))
721
722 def test_data_pub(self):
723 view = self.client[:]
724 ar = view.execute('\n'.join([
725 'from IPython.kernel.zmq.datapub import publish_data',
726 'for i in range(5):',
727 ' publish_data(dict(i=i))'
728 ]), block=False)
729 self.assertTrue(all(isinstance(d, dict) for d in ar.data))
730 ar.get(5)
731 self.assertEqual(ar.data, [dict(i=4)] * len(ar))
732
733 def test_can_list_arg(self):
734 """args in lists are canned"""
735 view = self.client[-1]
736 view['a'] = 128
737 rA = pmod.Reference('a')
738 ar = view.apply_async(lambda x: x, [rA])
739 r = ar.get(5)
740 self.assertEqual(r, [128])
741
742 def test_can_dict_arg(self):
743 """args in dicts are canned"""
744 view = self.client[-1]
745 view['a'] = 128
746 rA = pmod.Reference('a')
747 ar = view.apply_async(lambda x: x, dict(foo=rA))
748 r = ar.get(5)
749 self.assertEqual(r, dict(foo=128))
750
751 def test_can_list_kwarg(self):
752 """kwargs in lists are canned"""
753 view = self.client[-1]
754 view['a'] = 128
755 rA = pmod.Reference('a')
756 ar = view.apply_async(lambda x=5: x, x=[rA])
757 r = ar.get(5)
758 self.assertEqual(r, [128])
759
760 def test_can_dict_kwarg(self):
761 """kwargs in dicts are canned"""
762 view = self.client[-1]
763 view['a'] = 128
764 rA = pmod.Reference('a')
765 ar = view.apply_async(lambda x=5: x, dict(foo=rA))
766 r = ar.get(5)
767 self.assertEqual(r, dict(foo=128))
768
769 def test_map_ref(self):
770 """view.map works with references"""
771 view = self.client[:]
772 ranks = sorted(self.client.ids)
773 view.scatter('rank', ranks, flatten=True)
774 rrank = pmod.Reference('rank')
775
776 amr = view.map_async(lambda x: x*2, [rrank] * len(view))
777 drank = amr.get(5)
778 self.assertEqual(drank, [ r*2 for r in ranks ])
779
780 def test_nested_getitem_setitem(self):
781 """get and set with view['a.b']"""
782 view = self.client[-1]
783 view.execute('\n'.join([
784 'class A(object): pass',
785 'a = A()',
786 'a.b = 128',
787 ]), block=True)
788 ra = pmod.Reference('a')
789
790 r = view.apply_sync(lambda x: x.b, ra)
791 self.assertEqual(r, 128)
792 self.assertEqual(view['a.b'], 128)
793
794 view['a.b'] = 0
795
796 r = view.apply_sync(lambda x: x.b, ra)
797 self.assertEqual(r, 0)
798 self.assertEqual(view['a.b'], 0)
799
800 def test_return_namedtuple(self):
801 def namedtuplify(x, y):
802 from ipython_parallel.tests.test_view import point
803 return point(x, y)
804
805 view = self.client[-1]
806 p = view.apply_sync(namedtuplify, 1, 2)
807 self.assertEqual(p.x, 1)
808 self.assertEqual(p.y, 2)
809
810 def test_apply_namedtuple(self):
811 def echoxy(p):
812 return p.y, p.x
813
814 view = self.client[-1]
815 tup = view.apply_sync(echoxy, point(1, 2))
816 self.assertEqual(tup, (2,1))
817
818 def test_sync_imports(self):
819 view = self.client[-1]
820 with capture_output() as io:
821 with view.sync_imports():
822 import IPython
823 self.assertIn("IPython", io.stdout)
824
825 @interactive
826 def find_ipython():
827 return 'IPython' in globals()
828
829 assert view.apply_sync(find_ipython)
830
831 def test_sync_imports_quiet(self):
832 view = self.client[-1]
833 with capture_output() as io:
834 with view.sync_imports(quiet=True):
835 import IPython
836 self.assertEqual(io.stdout, '')
837
838 @interactive
839 def find_ipython():
840 return 'IPython' in globals()
841
842 assert view.apply_sync(find_ipython)
843
@@ -1,389 +0,0 b''
1 """Some generic utilities for dealing with classes, urls, and serialization."""
2
3 # Copyright (c) IPython Development Team.
4 # Distributed under the terms of the Modified BSD License.
5
6 import logging
7 import os
8 import re
9 import stat
10 import socket
11 import sys
12 import warnings
13 from signal import signal, SIGINT, SIGABRT, SIGTERM
14 try:
15 from signal import SIGKILL
16 except ImportError:
17 SIGKILL=None
18 from types import FunctionType
19
20 try:
21 import cPickle
22 pickle = cPickle
23 except:
24 cPickle = None
25 import pickle
26
27 import zmq
28 from zmq.log import handlers
29
30 from IPython.utils.log import get_logger
31 from decorator import decorator
32
33 from IPython.config.application import Application
34 from IPython.utils.localinterfaces import localhost, is_public_ip, public_ips
35 from IPython.utils.py3compat import string_types, iteritems, itervalues
36 from IPython.kernel.zmq.log import EnginePUBHandler
37
38
39 #-----------------------------------------------------------------------------
40 # Classes
41 #-----------------------------------------------------------------------------
42
43 class Namespace(dict):
44 """Subclass of dict for attribute access to keys."""
45
46 def __getattr__(self, key):
47 """getattr aliased to getitem"""
48 if key in self:
49 return self[key]
50 else:
51 raise NameError(key)
52
53 def __setattr__(self, key, value):
54 """setattr aliased to setitem, with strict"""
55 if hasattr(dict, key):
56 raise KeyError("Cannot override dict keys %r"%key)
57 self[key] = value
58
59
60 class ReverseDict(dict):
61 """simple double-keyed subset of dict methods."""
62
63 def __init__(self, *args, **kwargs):
64 dict.__init__(self, *args, **kwargs)
65 self._reverse = dict()
66 for key, value in iteritems(self):
67 self._reverse[value] = key
68
69 def __getitem__(self, key):
70 try:
71 return dict.__getitem__(self, key)
72 except KeyError:
73 return self._reverse[key]
74
75 def __setitem__(self, key, value):
76 if key in self._reverse:
77 raise KeyError("Can't have key %r on both sides!"%key)
78 dict.__setitem__(self, key, value)
79 self._reverse[value] = key
80
81 def pop(self, key):
82 value = dict.pop(self, key)
83 self._reverse.pop(value)
84 return value
85
86 def get(self, key, default=None):
87 try:
88 return self[key]
89 except KeyError:
90 return default
91
92 #-----------------------------------------------------------------------------
93 # Functions
94 #-----------------------------------------------------------------------------
95
96 @decorator
97 def log_errors(f, self, *args, **kwargs):
98 """decorator to log unhandled exceptions raised in a method.
99
100 For use wrapping on_recv callbacks, so that exceptions
101 do not cause the stream to be closed.
102 """
103 try:
104 return f(self, *args, **kwargs)
105 except Exception:
106 self.log.error("Uncaught exception in %r" % f, exc_info=True)
107
108
109 def is_url(url):
110 """boolean check for whether a string is a zmq url"""
111 if '://' not in url:
112 return False
113 proto, addr = url.split('://', 1)
114 if proto.lower() not in ['tcp','pgm','epgm','ipc','inproc']:
115 return False
116 return True
117
118 def validate_url(url):
119 """validate a url for zeromq"""
120 if not isinstance(url, string_types):
121 raise TypeError("url must be a string, not %r"%type(url))
122 url = url.lower()
123
124 proto_addr = url.split('://')
125 assert len(proto_addr) == 2, 'Invalid url: %r'%url
126 proto, addr = proto_addr
127 assert proto in ['tcp','pgm','epgm','ipc','inproc'], "Invalid protocol: %r"%proto
128
129 # domain pattern adapted from http://www.regexlib.com/REDetails.aspx?regexp_id=391
130 # author: Remi Sabourin
131 pat = re.compile(r'^([\w\d]([\w\d\-]{0,61}[\w\d])?\.)*[\w\d]([\w\d\-]{0,61}[\w\d])?$')
132
133 if proto == 'tcp':
134 lis = addr.split(':')
135 assert len(lis) == 2, 'Invalid url: %r'%url
136 addr,s_port = lis
137 try:
138 port = int(s_port)
139 except ValueError:
140 raise AssertionError("Invalid port %r in url: %r"%(port, url))
141
142 assert addr == '*' or pat.match(addr) is not None, 'Invalid url: %r'%url
143
144 else:
145 # only validate tcp urls currently
146 pass
147
148 return True
149
150
151 def validate_url_container(container):
152 """validate a potentially nested collection of urls."""
153 if isinstance(container, string_types):
154 url = container
155 return validate_url(url)
156 elif isinstance(container, dict):
157 container = itervalues(container)
158
159 for element in container:
160 validate_url_container(element)
161
162
163 def split_url(url):
164 """split a zmq url (tcp://ip:port) into ('tcp','ip','port')."""
165 proto_addr = url.split('://')
166 assert len(proto_addr) == 2, 'Invalid url: %r'%url
167 proto, addr = proto_addr
168 lis = addr.split(':')
169 assert len(lis) == 2, 'Invalid url: %r'%url
170 addr,s_port = lis
171 return proto,addr,s_port
172
173
174 def disambiguate_ip_address(ip, location=None):
175 """turn multi-ip interfaces '0.0.0.0' and '*' into a connectable address
176
177 Explicit IP addresses are returned unmodified.
178
179 Parameters
180 ----------
181
182 ip : IP address
183 An IP address, or the special values 0.0.0.0, or *
184 location: IP address, optional
185 A public IP of the target machine.
186 If location is an IP of the current machine,
187 localhost will be returned,
188 otherwise location will be returned.
189 """
190 if ip in {'0.0.0.0', '*'}:
191 if not location:
192 # unspecified location, localhost is the only choice
193 ip = localhost()
194 elif is_public_ip(location):
195 # location is a public IP on this machine, use localhost
196 ip = localhost()
197 elif not public_ips():
198 # this machine's public IPs cannot be determined,
199 # assume `location` is not this machine
200 warnings.warn("IPython could not determine public IPs", RuntimeWarning)
201 ip = location
202 else:
203 # location is not this machine, do not use loopback
204 ip = location
205 return ip
206
207
208 def disambiguate_url(url, location=None):
209 """turn multi-ip interfaces '0.0.0.0' and '*' into connectable
210 ones, based on the location (default interpretation is localhost).
211
212 This is for zeromq urls, such as ``tcp://*:10101``.
213 """
214 try:
215 proto,ip,port = split_url(url)
216 except AssertionError:
217 # probably not tcp url; could be ipc, etc.
218 return url
219
220 ip = disambiguate_ip_address(ip,location)
221
222 return "%s://%s:%s"%(proto,ip,port)
223
224
225 #--------------------------------------------------------------------------
226 # helpers for implementing old MEC API via view.apply
227 #--------------------------------------------------------------------------
228
229 def interactive(f):
230 """decorator for making functions appear as interactively defined.
231 This results in the function being linked to the user_ns as globals()
232 instead of the module globals().
233 """
234
235 # build new FunctionType, so it can have the right globals
236 # interactive functions never have closures, that's kind of the point
237 if isinstance(f, FunctionType):
238 mainmod = __import__('__main__')
239 f = FunctionType(f.__code__, mainmod.__dict__,
240 f.__name__, f.__defaults__,
241 )
242 # associate with __main__ for uncanning
243 f.__module__ = '__main__'
244 return f
245
246 @interactive
247 def _push(**ns):
248 """helper method for implementing `client.push` via `client.apply`"""
249 user_ns = globals()
250 tmp = '_IP_PUSH_TMP_'
251 while tmp in user_ns:
252 tmp = tmp + '_'
253 try:
254 for name, value in ns.items():
255 user_ns[tmp] = value
256 exec("%s = %s" % (name, tmp), user_ns)
257 finally:
258 user_ns.pop(tmp, None)
259
260 @interactive
261 def _pull(keys):
262 """helper method for implementing `client.pull` via `client.apply`"""
263 if isinstance(keys, (list,tuple, set)):
264 return [eval(key, globals()) for key in keys]
265 else:
266 return eval(keys, globals())
267
268 @interactive
269 def _execute(code):
270 """helper method for implementing `client.execute` via `client.apply`"""
271 exec(code, globals())
272
273 #--------------------------------------------------------------------------
274 # extra process management utilities
275 #--------------------------------------------------------------------------
276
277 _random_ports = set()
278
279 def select_random_ports(n):
280 """Selects and return n random ports that are available."""
281 ports = []
282 for i in range(n):
283 sock = socket.socket()
284 sock.bind(('', 0))
285 while sock.getsockname()[1] in _random_ports:
286 sock.close()
287 sock = socket.socket()
288 sock.bind(('', 0))
289 ports.append(sock)
290 for i, sock in enumerate(ports):
291 port = sock.getsockname()[1]
292 sock.close()
293 ports[i] = port
294 _random_ports.add(port)
295 return ports
296
297 def signal_children(children):
298 """Relay interupt/term signals to children, for more solid process cleanup."""
299 def terminate_children(sig, frame):
300 log = get_logger()
301 log.critical("Got signal %i, terminating children..."%sig)
302 for child in children:
303 child.terminate()
304
305 sys.exit(sig != SIGINT)
306 # sys.exit(sig)
307 for sig in (SIGINT, SIGABRT, SIGTERM):
308 signal(sig, terminate_children)
309
310 def generate_exec_key(keyfile):
311 import uuid
312 newkey = str(uuid.uuid4())
313 with open(keyfile, 'w') as f:
314 # f.write('ipython-key ')
315 f.write(newkey+'\n')
316 # set user-only RW permissions (0600)
317 # this will have no effect on Windows
318 os.chmod(keyfile, stat.S_IRUSR|stat.S_IWUSR)
319
320
321 def integer_loglevel(loglevel):
322 try:
323 loglevel = int(loglevel)
324 except ValueError:
325 if isinstance(loglevel, str):
326 loglevel = getattr(logging, loglevel)
327 return loglevel
328
329 def connect_logger(logname, context, iface, root="ip", loglevel=logging.DEBUG):
330 logger = logging.getLogger(logname)
331 if any([isinstance(h, handlers.PUBHandler) for h in logger.handlers]):
332 # don't add a second PUBHandler
333 return
334 loglevel = integer_loglevel(loglevel)
335 lsock = context.socket(zmq.PUB)
336 lsock.connect(iface)
337 handler = handlers.PUBHandler(lsock)
338 handler.setLevel(loglevel)
339 handler.root_topic = root
340 logger.addHandler(handler)
341 logger.setLevel(loglevel)
342
343 def connect_engine_logger(context, iface, engine, loglevel=logging.DEBUG):
344 logger = logging.getLogger()
345 if any([isinstance(h, handlers.PUBHandler) for h in logger.handlers]):
346 # don't add a second PUBHandler
347 return
348 loglevel = integer_loglevel(loglevel)
349 lsock = context.socket(zmq.PUB)
350 lsock.connect(iface)
351 handler = EnginePUBHandler(engine, lsock)
352 handler.setLevel(loglevel)
353 logger.addHandler(handler)
354 logger.setLevel(loglevel)
355 return logger
356
357 def local_logger(logname, loglevel=logging.DEBUG):
358 loglevel = integer_loglevel(loglevel)
359 logger = logging.getLogger(logname)
360 if any([isinstance(h, logging.StreamHandler) for h in logger.handlers]):
361 # don't add a second StreamHandler
362 return
363 handler = logging.StreamHandler()
364 handler.setLevel(loglevel)
365 formatter = logging.Formatter("%(asctime)s.%(msecs).03d [%(name)s] %(message)s",
366 datefmt="%Y-%m-%d %H:%M:%S")
367 handler.setFormatter(formatter)
368
369 logger.addHandler(handler)
370 logger.setLevel(loglevel)
371 return logger
372
373 def set_hwm(sock, hwm=0):
374 """set zmq High Water Mark on a socket
375
376 in a way that always works for various pyzmq / libzmq versions.
377 """
378 import zmq
379
380 for key in ('HWM', 'SNDHWM', 'RCVHWM'):
381 opt = getattr(zmq, key, None)
382 if opt is None:
383 continue
384 try:
385 sock.setsockopt(opt, hwm)
386 except zmq.ZMQError:
387 pass
388
389
General Comments 0
You need to be logged in to leave comments. Login now