Show More
@@ -1,430 +1,412 b'' | |||
|
1 | 1 | { |
|
2 | 2 | "metadata": { |
|
3 | "name": "InteractiveMPI-publish-data" | |
|
3 | "name": "" | |
|
4 | 4 | }, |
|
5 | 5 | "nbformat": 3, |
|
6 | 6 | "nbformat_minor": 0, |
|
7 | 7 | "worksheets": [ |
|
8 | 8 | { |
|
9 | 9 | "cells": [ |
|
10 | 10 | { |
|
11 | 11 | "cell_type": "heading", |
|
12 | 12 | "level": 1, |
|
13 | 13 | "metadata": {}, |
|
14 | 14 | "source": [ |
|
15 | 15 | "Interactive visualization of MPI simulaitons" |
|
16 | 16 | ] |
|
17 | 17 | }, |
|
18 | 18 | { |
|
19 | 19 | "cell_type": "markdown", |
|
20 | 20 | "metadata": {}, |
|
21 | 21 | "source": [ |
|
22 | 22 | "In this example, which builds on our previous one of interactive MPI monitoring, we now demonstrate how to use the IPython data publication APIs." |
|
23 | 23 | ] |
|
24 | 24 | }, |
|
25 | 25 | { |
|
26 | 26 | "cell_type": "heading", |
|
27 | 27 | "level": 2, |
|
28 | 28 | "metadata": {}, |
|
29 | 29 | "source": [ |
|
30 | 30 | "Load IPython support for working with MPI tasks" |
|
31 | 31 | ] |
|
32 | 32 | }, |
|
33 | 33 | { |
|
34 | 34 | "cell_type": "markdown", |
|
35 | 35 | "metadata": {}, |
|
36 | 36 | "source": [ |
|
37 | 37 | "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", |
|
38 | 38 | "\n", |
|
39 | 39 | "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", |
|
40 | 40 | "\n", |
|
41 | 41 | "**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." |
|
42 | 42 | ] |
|
43 | 43 | }, |
|
44 | 44 | { |
|
45 | 45 | "cell_type": "code", |
|
46 | 46 | "collapsed": false, |
|
47 | 47 | "input": [ |
|
48 | 48 | "from IPython.parallel import Client, error\n", |
|
49 | 49 | "cluster = Client(profile=\"mpi\")\n", |
|
50 | 50 | "view = cluster[:]\n", |
|
51 | 51 | "view.block = True" |
|
52 | 52 | ], |
|
53 | 53 | "language": "python", |
|
54 | 54 | "metadata": {}, |
|
55 | 55 | "outputs": [], |
|
56 | 56 | "prompt_number": 1 |
|
57 | 57 | }, |
|
58 | 58 | { |
|
59 | 59 | "cell_type": "markdown", |
|
60 | 60 | "metadata": {}, |
|
61 | 61 | "source": [ |
|
62 | 62 | "Let's also load the plotting and numerical libraries so we have them ready for visualization later on." |
|
63 | 63 | ] |
|
64 | 64 | }, |
|
65 | 65 | { |
|
66 | 66 | "cell_type": "code", |
|
67 | 67 | "collapsed": false, |
|
68 | 68 | "input": [ |
|
69 |
"% |
|
|
69 | "%matplotlib inline\n", | |
|
70 | "import numpy as np\n", | |
|
71 | "import matplotlib.pyplot as plt" | |
|
70 | 72 | ], |
|
71 | 73 | "language": "python", |
|
72 | 74 | "metadata": {}, |
|
73 | "outputs": [ | |
|
74 | { | |
|
75 | "output_type": "stream", | |
|
76 | "stream": "stdout", | |
|
77 | "text": [ | |
|
78 | "\n", | |
|
79 | "Welcome to pylab, a matplotlib-based Python environment [backend: module://IPython.kernel.zmq.pylab.backend_inline].\n", | |
|
80 | "For more information, type 'help(pylab)'.\n" | |
|
81 | ] | |
|
82 | } | |
|
83 | ], | |
|
75 | "outputs": [], | |
|
84 | 76 | "prompt_number": 2 |
|
85 | 77 | }, |
|
86 | 78 | { |
|
87 | 79 | "cell_type": "markdown", |
|
88 | 80 | "metadata": {}, |
|
89 | 81 | "source": [ |
|
90 | 82 | "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", |
|
91 | 83 | "\n", |
|
92 | 84 | "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:" |
|
93 | 85 | ] |
|
94 | 86 | }, |
|
95 | 87 | { |
|
96 | 88 | "cell_type": "code", |
|
97 | 89 | "collapsed": false, |
|
98 | 90 | "input": [ |
|
99 | 91 | "%%px\n", |
|
100 | 92 | "# MPI initialization, library imports and sanity checks on all engines\n", |
|
101 | 93 | "from mpi4py import MPI\n", |
|
102 | 94 | "# Load data publication API so engines can send data to notebook client\n", |
|
103 | 95 | "from IPython.kernel.zmq.datapub import publish_data\n", |
|
104 | 96 | "import numpy as np\n", |
|
105 | 97 | "import time\n", |
|
106 | 98 | "\n", |
|
107 | 99 | "mpi = MPI.COMM_WORLD\n", |
|
108 | 100 | "bcast = mpi.bcast\n", |
|
109 | 101 | "barrier = mpi.barrier\n", |
|
110 | 102 | "rank = mpi.rank\n", |
|
111 | 103 | "print \"MPI rank: %i/%i\" % (mpi.rank,mpi.size)" |
|
112 | 104 | ], |
|
113 | 105 | "language": "python", |
|
114 | 106 | "metadata": {}, |
|
115 | 107 | "outputs": [ |
|
116 | 108 | { |
|
117 | 109 | "output_type": "stream", |
|
118 | 110 | "stream": "stdout", |
|
119 | 111 | "text": [ |
|
120 | 112 | "[stdout:0] MPI rank: 2/4\n", |
|
121 |
"[stdout:1] MPI rank: |
|
|
122 |
"[stdout:2] MPI rank: |
|
|
123 |
"[stdout:3] MPI rank: |
|
|
113 | "[stdout:1] MPI rank: 0/4\n", | |
|
114 | "[stdout:2] MPI rank: 3/4\n", | |
|
115 | "[stdout:3] MPI rank: 1/4\n" | |
|
124 | 116 | ] |
|
125 | 117 | } |
|
126 | 118 | ], |
|
127 | 119 | "prompt_number": 3 |
|
128 | 120 | }, |
|
129 | 121 | { |
|
130 | 122 | "cell_type": "markdown", |
|
131 | 123 | "metadata": {}, |
|
132 | 124 | "source": [ |
|
133 | 125 | "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:" |
|
134 | 126 | ] |
|
135 | 127 | }, |
|
136 | 128 | { |
|
137 | 129 | "cell_type": "code", |
|
138 | 130 | "collapsed": false, |
|
139 | 131 | "input": [ |
|
140 | 132 | "ranks = view['rank']\n", |
|
141 | 133 | "engine_mpi = np.argsort(ranks)\n", |
|
142 | 134 | "\n", |
|
143 | 135 | "def mpi_order(seq):\n", |
|
144 | 136 | " \"\"\"Return elements of a sequence ordered by MPI rank.\n", |
|
145 | 137 | "\n", |
|
146 | 138 | " The input sequence is assumed to be ordered by engine ID.\"\"\"\n", |
|
147 | 139 | " return [seq[x] for x in engine_mpi]" |
|
148 | 140 | ], |
|
149 | 141 | "language": "python", |
|
150 | 142 | "metadata": {}, |
|
151 | 143 | "outputs": [], |
|
152 | 144 | "prompt_number": 4 |
|
153 | 145 | }, |
|
154 | 146 | { |
|
155 | 147 | "cell_type": "heading", |
|
156 | 148 | "level": 2, |
|
157 | 149 | "metadata": {}, |
|
158 | 150 | "source": [ |
|
159 | 151 | "MPI simulation example" |
|
160 | 152 | ] |
|
161 | 153 | }, |
|
162 | 154 | { |
|
163 | 155 | "cell_type": "markdown", |
|
164 | 156 | "metadata": {}, |
|
165 | 157 | "source": [ |
|
166 | 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", |
|
167 | 159 | "\n", |
|
168 | 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)." |
|
169 | 161 | ] |
|
170 | 162 | }, |
|
171 | 163 | { |
|
172 | 164 | "cell_type": "code", |
|
173 | 165 | "collapsed": false, |
|
174 | 166 | "input": [ |
|
175 | 167 | "%%px\n", |
|
176 | 168 | "\n", |
|
177 | 169 | "# Global flag in the namespace\n", |
|
178 | 170 | "stop = False\n", |
|
179 | 171 | "\n", |
|
180 | 172 | "def simulation(nsteps=100, delay=0.1):\n", |
|
181 | 173 | " \"\"\"Toy simulation code, computes sin(f*(x**2+y**2)) for a slowly increasing f\n", |
|
182 | 174 | " over an increasingly fine mesh.\n", |
|
183 | 175 | "\n", |
|
184 | 176 | " The purpose of this code is simply to illustrate the basic features of a typical\n", |
|
185 | 177 | " MPI code: spatial domain decomposition, a solution which is evolving in some \n", |
|
186 | 178 | " sense, and local per-node computation. In this case the nodes only communicate when \n", |
|
187 | 179 | " gathering results for publication.\"\"\"\n", |
|
188 | 180 | " # Problem geometry\n", |
|
189 | 181 | " xmin, xmax = 0, np.pi\n", |
|
190 | 182 | " ymin, ymax = 0, 2*np.pi\n", |
|
191 | 183 | " dy = (ymax-ymin)/mpi.size\n", |
|
192 | 184 | "\n", |
|
193 | 185 | " freqs = np.linspace(0.6, 1, nsteps)\n", |
|
194 | 186 | " for j in range(nsteps):\n", |
|
195 | 187 | " nx, ny = 2+j/4, 2+j/2/mpi.size\n", |
|
196 | 188 | " nyt = mpi.size*ny\n", |
|
197 | 189 | " Xax = np.linspace(xmin, xmax, nx)\n", |
|
198 | 190 | " Yax = np.linspace(ymin+rank*dy, ymin+(rank+1)*dy, ny, endpoint=rank==mpi.size)\n", |
|
199 | 191 | " X, Y = np.meshgrid(Xax, Yax)\n", |
|
200 | 192 | " f = freqs[j]\n", |
|
201 | 193 | " Z = np.cos(f*(X**2 + Y**2))\n", |
|
202 | 194 | " \n", |
|
203 | 195 | " # We are now going to publish data to the clients. We take advantage of fast\n", |
|
204 | 196 | " # MPI communications and gather the Z mesh at the rank 0 node in the Zcat variable:\n", |
|
205 | 197 | " Zcat = mpi.gather(Z, root=0)\n", |
|
206 | 198 | " if mpi.rank == 0:\n", |
|
207 | 199 | " # Then we use numpy's concatenation to construct a single numpy array with the\n", |
|
208 | 200 | " # full mesh that can be sent to the client for visualization:\n", |
|
209 | 201 | " Zcat = np.concatenate(Zcat)\n", |
|
210 | 202 | " # We now can send a dict with the variables we want the client to have access to:\n", |
|
211 | 203 | " publish_data(dict(Z=Zcat, nx=nx, nyt=nyt, j=j, nsteps=nsteps))\n", |
|
212 | 204 | " \n", |
|
213 | 205 | " # We add a small delay to simulate that a real-world computation\n", |
|
214 | 206 | " # would take much longer, and we ensure all nodes are synchronized\n", |
|
215 | 207 | " time.sleep(delay)\n", |
|
216 | 208 | " # The stop flag can be set remotely via IPython, allowing the simulation to be\n", |
|
217 | 209 | " # cleanly stopped from the outside\n", |
|
218 | 210 | " if stop:\n", |
|
219 | 211 | " break" |
|
220 | 212 | ], |
|
221 | 213 | "language": "python", |
|
222 | 214 | "metadata": {}, |
|
223 | 215 | "outputs": [], |
|
224 |
"prompt_number": |
|
|
216 | "prompt_number": 5 | |
|
225 | 217 | }, |
|
226 | 218 | { |
|
227 | 219 | "cell_type": "heading", |
|
228 | 220 | "level": 2, |
|
229 | 221 | "metadata": {}, |
|
230 | 222 | "source": [ |
|
231 | 223 | "IPython tools to interactively monitor and plot the MPI results" |
|
232 | 224 | ] |
|
233 | 225 | }, |
|
234 | 226 | { |
|
235 | 227 | "cell_type": "markdown", |
|
236 | 228 | "metadata": {}, |
|
237 | 229 | "source": [ |
|
238 | 230 | "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:" |
|
239 | 231 | ] |
|
240 | 232 | }, |
|
241 | 233 | { |
|
242 | 234 | "cell_type": "code", |
|
243 | 235 | "collapsed": false, |
|
244 | 236 | "input": [ |
|
245 | "from IPython.display import clear_output\n", | |
|
237 | "from IPython.display import display, clear_output\n", | |
|
246 | 238 | "\n", |
|
247 | 239 | "def plot_current_results(ar, in_place=True):\n", |
|
248 | 240 | " \"\"\"Makes a blocking call to retrieve remote data and displays the solution mesh\n", |
|
249 | 241 | " as a contour plot.\n", |
|
250 | 242 | " \n", |
|
251 | 243 | " Parameters\n", |
|
252 | 244 | " ----------\n", |
|
253 | 245 | " ar : async result object\n", |
|
254 | 246 | "\n", |
|
255 | 247 | " in_place : bool\n", |
|
256 | 248 | " By default it calls clear_output so that new plots replace old ones. Set\n", |
|
257 | 249 | " to False to allow keeping of all previous outputs.\n", |
|
258 | 250 | " \"\"\"\n", |
|
259 | 251 | " # Read data from MPI rank 0 engine\n", |
|
260 | 252 | " data = ar.data[engine_mpi[0]]\n", |
|
261 | 253 | " \n", |
|
262 | 254 | " try:\n", |
|
263 | 255 | " nx, nyt, j, nsteps = [data[k] for k in ['nx', 'nyt', 'j', 'nsteps']]\n", |
|
264 | 256 | " Z = data['Z']\n", |
|
265 | 257 | " except KeyError:\n", |
|
266 | 258 | " # This can happen if we read from the engines so quickly that the data \n", |
|
267 | 259 | " # hasn't arrived yet.\n", |
|
268 | 260 | " fig, ax = plt.subplots()\n", |
|
269 | 261 | " ax.plot([])\n", |
|
270 | 262 | " ax.set_title(\"No data yet\")\n", |
|
271 | 263 | " display(fig)\n", |
|
272 | 264 | " return fig\n", |
|
273 | 265 | " else:\n", |
|
274 | 266 | " \n", |
|
275 | 267 | " fig, ax = plt.subplots()\n", |
|
276 | 268 | " ax.contourf(Z)\n", |
|
277 | 269 | " ax.set_title('Mesh: %i x %i, step %i/%i' % (nx, nyt, j+1, nsteps))\n", |
|
278 | " axis('off')\n", | |
|
270 | " plt.axis('off')\n", | |
|
279 | 271 | " # We clear the notebook output before plotting this if in-place \n", |
|
280 | 272 | " # plot updating is requested\n", |
|
281 | 273 | " if in_place:\n", |
|
282 | 274 | " clear_output()\n", |
|
283 | 275 | " display(fig)\n", |
|
284 | 276 | " \n", |
|
285 | 277 | " return fig" |
|
286 | 278 | ], |
|
287 | 279 | "language": "python", |
|
288 | 280 | "metadata": {}, |
|
289 | 281 | "outputs": [], |
|
290 |
"prompt_number": |
|
|
282 | "prompt_number": 6 | |
|
291 | 283 | }, |
|
292 | 284 | { |
|
293 | 285 | "cell_type": "markdown", |
|
294 | 286 | "metadata": {}, |
|
295 | 287 | "source": [ |
|
296 | 288 | "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:" |
|
297 | 289 | ] |
|
298 | 290 | }, |
|
299 | 291 | { |
|
300 | 292 | "cell_type": "code", |
|
301 | 293 | "collapsed": false, |
|
302 | 294 | "input": [ |
|
303 | 295 | "def monitor_simulation(ar, refresh=5.0, plots_in_place=True):\n", |
|
304 | 296 | " \"\"\"Monitor the simulation progress and call plotting routine.\n", |
|
305 | 297 | "\n", |
|
306 | 298 | " Supress KeyboardInterrupt exception if interrupted, ensure that the last \n", |
|
307 | 299 | " figure is always displayed and provide basic timing and simulation status.\n", |
|
308 | 300 | "\n", |
|
309 | 301 | " Parameters\n", |
|
310 | 302 | " ----------\n", |
|
311 | 303 | " ar : async result object\n", |
|
312 | 304 | "\n", |
|
313 | 305 | " refresh : float\n", |
|
314 | 306 | " Refresh interval between calls to retrieve and plot data. The default\n", |
|
315 | 307 | " is 5s, adjust depending on the desired refresh rate, but be aware that \n", |
|
316 | 308 | " very short intervals will start having a significant impact.\n", |
|
317 | 309 | "\n", |
|
318 | 310 | " plots_in_place : bool\n", |
|
319 | 311 | " If true, every new figure replaces the last one, producing a (slow)\n", |
|
320 | 312 | " animation effect in the notebook. If false, all frames are plotted\n", |
|
321 | 313 | " in sequence and appended in the output area.\n", |
|
322 | 314 | " \"\"\"\n", |
|
323 | 315 | " import datetime as dt, time\n", |
|
324 | 316 | " \n", |
|
325 | 317 | " if ar.ready():\n", |
|
326 | 318 | " plot_current_results(ar, in_place=plots_in_place)\n", |
|
327 | 319 | " plt.close('all')\n", |
|
328 | 320 | " print 'Simulation has already finished, no monitoring to do.'\n", |
|
329 | 321 | " return\n", |
|
330 | 322 | " \n", |
|
331 | 323 | " t0 = dt.datetime.now()\n", |
|
332 | 324 | " fig = None\n", |
|
333 | 325 | " try:\n", |
|
334 | 326 | " while not ar.ready():\n", |
|
335 | 327 | " fig = plot_current_results(ar, in_place=plots_in_place)\n", |
|
336 | 328 | " plt.close('all') # prevent re-plot of old figures\n", |
|
337 | 329 | " time.sleep(refresh)\n", |
|
338 | 330 | " except (KeyboardInterrupt, error.TimeoutError):\n", |
|
339 | 331 | " msg = 'Monitoring interrupted, simulation is ongoing!'\n", |
|
340 | 332 | " else:\n", |
|
341 | 333 | " msg = 'Simulation completed!'\n", |
|
342 | 334 | " tmon = dt.datetime.now() - t0\n", |
|
343 | 335 | " if plots_in_place and fig is not None:\n", |
|
344 | 336 | " clear_output()\n", |
|
345 | 337 | " plt.close('all')\n", |
|
346 | 338 | " display(fig)\n", |
|
347 | 339 | " print msg\n", |
|
348 | 340 | " print 'Monitored for: %s.' % tmon" |
|
349 | 341 | ], |
|
350 | 342 | "language": "python", |
|
351 | 343 | "metadata": {}, |
|
352 | 344 | "outputs": [], |
|
353 |
"prompt_number": |
|
|
345 | "prompt_number": 7 | |
|
354 | 346 | }, |
|
355 | 347 | { |
|
356 | 348 | "cell_type": "heading", |
|
357 | 349 | "level": 2, |
|
358 | 350 | "metadata": {}, |
|
359 | 351 | "source": [ |
|
360 | 352 | "Interactive monitoring in the client of the published data" |
|
361 | 353 | ] |
|
362 | 354 | }, |
|
363 | 355 | { |
|
364 | 356 | "cell_type": "markdown", |
|
365 | 357 | "metadata": {}, |
|
366 | 358 | "source": [ |
|
367 | 359 | "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." |
|
368 | 360 | ] |
|
369 | 361 | }, |
|
370 | 362 | { |
|
371 | 363 | "cell_type": "code", |
|
372 | 364 | "collapsed": false, |
|
373 | 365 | "input": [ |
|
374 | 366 | "# Create the local client that controls our IPython cluster with MPI support\n", |
|
375 | 367 | "from IPython.parallel import Client\n", |
|
376 | 368 | "cluster = Client(profile=\"mpi\")\n", |
|
377 | 369 | "# We make a view that encompasses all the engines\n", |
|
378 | 370 | "view = cluster[:]\n", |
|
379 | 371 | "# And now we call on all available nodes our simulation routine,\n", |
|
380 | 372 | "# as an asynchronous task\n", |
|
381 | "nsteps = 10\n", | |
|
382 | "delay = 0.1\n", | |
|
383 | "ar = view.apply_async(lambda : simulation(nsteps, delay))" | |
|
373 | "ar = view.apply_async(lambda : simulation(nsteps=10, delay=0.1))" | |
|
384 | 374 | ], |
|
385 | 375 | "language": "python", |
|
386 | 376 | "metadata": {}, |
|
387 | 377 | "outputs": [], |
|
388 |
"prompt_number": |
|
|
378 | "prompt_number": 8 | |
|
389 | 379 | }, |
|
390 | 380 | { |
|
391 | 381 | "cell_type": "code", |
|
392 | 382 | "collapsed": false, |
|
393 | 383 | "input": [ |
|
394 | 384 | "monitor_simulation(ar, refresh=1)" |
|
395 | 385 | ], |
|
396 | 386 | "language": "python", |
|
397 | 387 | "metadata": {}, |
|
398 | 388 | "outputs": [ |
|
399 | 389 | { |
|
400 | 390 | "metadata": {}, |
|
401 | 391 | "output_type": "display_data", |
|
402 | "png": "iVBORw0KGgoAAAANSUhEUgAAAXIAAAEICAYAAABCnX+uAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAIABJREFUeJztnXusVcX1x78HG1C5CNeKcHk/TB+IUDXWRhStTayPFFsb\nH1SgVVOtta/EalVSmbQYtT8fjW1slT7EirQa+UNFCipVq1QaX6CiERBB0MpTuSiCyvz+oOf0Ps45\ne8/Mmpk1e69PYtJ79z6zN3TOZy3Wnj2rorXWEARBEJKlR+wbEARBENwQkQuCICSOiFwQBCFxROSC\nIAiJIyIXBEFIHBG5IAhC4ojIhW6MGDECjz76aOzbEAQhJyLyBBkxYgR69eqFLVu2dPr94Ycfjh49\nemDdunVO41cqFVQqFavPzp8/H8ceeyxaW1vR1taG7373u9ixY0encx555BEcccQRaGlpwdChQ3Hv\nvfc63S8AvPHGG+jRowf69OlT+++aa67pdM5zzz2HiRMnok+fPhg4cCBuueUW5+v26NEDr7/+uvM4\njbjmmmswfPhw9O3bF5MnT0Z7e3vt2IYNG3D66afj05/+NIYOHYrbbrut2+fnzp2Lc889FwBw4YUX\n4nOf+xz22WcfzJ49u9u5N998M9ra2tC3b19ccMEF2L17d+3Y1q1b8Y1vfAMtLS0YMWIE5s6d6+FP\nK9giIk+QSqWCUaNGdfoyvfjii9i5c6e1gKnYvn07rr76arz99tt45ZVXsGHDBlx22WW14ytWrMC5\n556La6+9Ftu3b8fy5ctx5JFHkl6/vb0d7e3tmD59eu33mzdvximnnIKLL74YW7duxerVq3HSSSeR\nXNPXO3WzZ8/GXXfdhSVLluCtt97Czp078cMf/rB2fMqUKRg9ejQ2btyI+fPn46qrrsJjjz3WaYz5\n8+fjtNNOAwB84QtfwK233oojjjii2zxZuHAhrr/+eixevBhr167F66+/jhkzZtSOX3LJJdh3332x\nceNGzJkzBxdffDFWrFjh5c8tWKCF5BgxYoSeOXOmPuqoo2q/u/TSS/U111yjK5WKXrt2rdZa6w8/\n/FBfeumletiwYXrAgAH6e9/7nt65c6fWWutNmzbp0047Tffr108feOCB+rjjjus0/g033KDHjRun\n+/btq88++2z94YcfWt3rvHnz9GGHHVb7efLkyfrqq6/O9dnrrrtOH3300frjjz/WWmt966236kMP\nPVTv2rWr27lr1qzRlUqldm5XrrzySj1t2jSLP4HWK1eu1BMnTtR9+/bVBx10kD7nnHO01lofd9xx\nulKp6N69e+uWlhZ9zz33aK21fuCBB/T48eN1v3799DHHHKOXL19eG2v48OH62muv1WPGjNGtra36\nvPPOa/h3+81vflP/3//9X+3nJUuW6H333Vfv3LlTt7e360qlojdt2lQ7fuGFF+qpU6fWfv7kk0/0\ngAED9JYtWzqNe+yxx+rZs2d3+t3kyZP19OnTaz8vXrxYDxw4UGut9Y4dO3TPnj31ypUra8enTZum\nr7jiinx/gYJ3JCNPlC996UvYvn07Xn31VXzyySf429/+hilTpnQ654orrsCqVauwbNkyrFq1Chs2\nbMAvfvELAMCNN96IoUOHYvPmzdi4cSOuvfba2ue01rj33nuxcOFCrFmzBsuXL8cdd9xRO97a2ool\nS5bkus/HH38cY8eOrf28dOlSaK0xbtw4DBo0CFOnTsW2bdvqfvbyyy9Hr169MHPmTKxcuRLTp0/H\nnDlz0LNnz4bXGz58OIYOHYrzzz+/U+lp6dKlaG1txYQJEzBgwABMmjQJb775Zq4/w89//nOcfPLJ\nePfdd7Fhw4ZaVvzEE08AAJYvX4729naceeaZeP7553HBBRdg1qxZ2Lp1Ky666CJMmjQJH330UW28\nu+++G4sWLcLq1avx2muvYebMmXWvW6lUOmX7e/bswa5du7By5cra77sef+mll2o///vf/8aoUaNw\n4IEHZv4ZV6xYgfHjx9d+HjduHN555x1s27YNr732Gj71qU/hkEMOqR0fP348Xn755cxxhTCIyBNm\n6tSpuPPOO/Hwww9jzJgxGDx4cO2Y1hqzZs3CTTfdhH79+qGlpQVXXnkl/vrXvwIAevbsibfffhtv\nvPEG9tlnH0yYMKH22Uqlgh/96EcYOHAgWltb8bWvfQ0vvPBC7fi2bdtwzDHHZN7fww8/jDvvvLMW\nPADgzTffxF133YV58+Zh5cqV3coFHalUKrjzzjtxyy234PTTT8fPfvazTrLpSP/+/fHMM89g3bp1\nePbZZ9He3l6rDVevO3v2bNxyyy1Yt24dRo4cicmTJ2f+GYC9f1dvvPEGNmzYgJ49ezb9s99+++24\n6KKLcNRRR6FSqWDatGno1asXnn766dqf6Qc/+AEGDx6M1tZWTJ8+vWG9+eSTT8Yf/vAHrF27Fu+9\n9x6uv/56AMAHH3yAPn36YMKECfjlL3+JXbt24bnnnsO8efOwc+fO2uc7llWy2LFjB/r27Vv7+YAD\nDgAAtLe3Y8eOHbWfq/Tp06dTvV6Ii4g8USqVCqZOnYo5c+Zg9uzZmDZtWqfsbNOmTfjggw9w5JFH\norW1Fa2trTjllFOwefNmAMBll12GQw45BCeddBJGjx5dk0SVgQMH1v73fvvt1+2BZRZPP/00zj33\nXNx3332dMrn9998f5513Hg455BD07t0bV111FR566KGG4wwfPhwnnHAC1q5di0suuaTheb1798YR\nRxyBHj164OCDD8Zvf/tbLFq0CO+//37tumeccQaOPPJI9OrVCzNmzMCSJUtyyehXv/oVtNb44he/\niLFjx+LPf/5zw3PXrl2LG2+8sfZ33traivXr1+Ott96qnTN06NDa/x42bFinYx05//zzMXnyZJxw\nwgk47LDDcOKJJwIAhgwZAgCYM2cO1qxZg6FDh+KSSy7BlClTOgXzBQsW4NRTT8388wFAS0sLtm/f\nXvv5vffeA7BX2F2PVY/36dMn19iCf0TkCTNs2DCMGjUKCxYswBlnnNHp2EEHHYT99tsPK1aswLZt\n27Bt2za8++67tS9kS0sLbrjhBqxevRr3338/brrpJvzjH/+oex3TB6jPP/88Tj/9dNxxxx348pe/\n3OnYuHHjjMaaP38+nn76aXzlK1/BT3/6U6PPAnvLDTbX7ciAAQNw++23Y8OGDbjtttvw/e9/v+FK\nlWHDhmH69Om1v/Nt27Zhx44dOPvss2vndFxVtG7dOgwaNKjuWJVKBUoprFmzBuvWrcOYMWMwZMiQ\nmqyHDRuGBx54ABs3bsS//vUvbNq0CUcffTQA4D//+Q/efvttHH744bn+jIceeminf3UtW7YMAwYM\nQGtrKz7zmc/g448/xqpVqzod71gyEyITrzwv2DJixAj96KOPaq21Xr16tX722We11lp/9NFHnR52\n/vjHP9ZnnXWW3rhxo9Za6/Xr1+uFCxdqrbV+8MEH9cqVK/WePXv0unXrdFtbm37ssce6ja+11jNm\nzNBTpkzJdW8vvviiPvjgg2sP/rrypz/9SY8cOVK//vrr+v3339dnnnlmw4eQmzZt0m1tbXrBggV6\ny5YtetCgQfqhhx6qe+7SpUv1q6++qj/55BO9efNmfdZZZ+kTTzyxdnzx4sW6tbVVv/DCC3r37t36\nJz/5iZ44cWLt+PHHH6+VUnXHvueee/Sbb76ptdb6pZde0vvtt59es2aN1lrrgQMH6kWLFtXOfeaZ\nZ/TQoUP10qVL9Z49e/SOHTv0gw8+qNvb27XWex92jhs3Tq9fv15v2bJFT5gwodNDxo5s3bpVr1q1\nSu/Zs0e//PLLeuzYsXrWrFm146+88orevn273rVrl/7LX/6iDzroIL158+ba3/MFF1zQabzdu3fr\nnTt36mOOOUbPmjVL79y5U+/Zs0drrfXf//53PXDgQL1ixQq9detWffzxx+srr7yy9tlzzjlHT548\nWb///vv6n//8p+7bt69esWJF3fsWwiMiT5Cuoq3y0Ucf6R49enRatXLVVVfpUaNG6QMOOEB//vOf\n17/5zW+01lrffPPNesSIEbp37956yJAheubMmQ3HV0p1Wg3R0tKin3zyybr3dt555+l99tlHt7S0\n1P4bO3Zsp3NmzJih+/fvr/v376+nTZum33333bpjnXHGGfriiy+u/bxgwQI9aNAgvXXr1m7nzp07\nV48cOVL37t1bt7W16W9/+9v6nXfe6XTO7373Oz148GDd2tqqJ02apNevX187Nnr0aP3II4/UvY/L\nL79cDx48WLe0tOjRo0d3kunvf/973dbWpvv166fvvfderfVeKR511FG6X79+uq2tTZ911ll6x44d\nWuu9f7fXXXedHjNmjO7Xr5/+zne+U1tJ1JXXXntNf/azn9X777+/Hj58uL755ps7Hf/1r3+t+/fv\nr3v37q2PO+64WkDXeu+Kl/vuu6/T+ccff7yuVCq6R48eulKp6Eqloh9//PHa8ZtuukkPGDBAH3DA\nAfr888/Xu3fvrh3bunWr/vrXv6579+6thw8frufOnVv3noU4VLSWxhJCuVm/fj3OOeccPPnkk96v\nNXLkSPzxj3+s1bt98PHHH6OtrQ1r1qxBS0uLt+sIfJAauVB6hgwZEkTiodi2bRtmzpwpEi8RInJB\nKBj9+/fHRRddFPs2hIBIaUUQBCFxPuVr4Kci7/mRIhPyvZ9iz8/cPn7/ePO9SX6P/JnhgifOyD6p\nI8rsdADAP5ZafMgzXz4633mq8aFTJs5reOx76L6ZFgBMWrao8YDXNz4EAE9F2jMr13ekwTyvN38b\nzc+6c1FlXJdobmmdcz50wFtGLiJ3x7vY8xBB/oBZAAACBYF6UHx584q8imp+OLTUAf9id/4uNJnH\njeZovTnYdJ6pJtc3mCciciGTKMHBIRiYBgJ2/wIAmn+JTSWehWp+uJnkgcaiBzJkD+QSfkfqyd9k\nfjYLHkbz3FDyRll8FdXg93XmBiuR41vFF3msf16mCEkAMQwIeYJAXvHnlr7Kd1o0VPYpNhl9FdfM\nPg+U3zvbUo3PMo1enOOeuiAiD4RI3x7rIJBT/BTCzyV6le9+APit5UeoyVNl86G+R74F30zuvES+\nrCAiJ8oibJEAUJ/QGX7eEk+eDJ9c+h3JEwAoyjmq8aFGkjeuz0d64Oo0t3LKHWgseD3R/LLeRH4/\nvupjWHZkZhrURAgsqQYT0ucBEaTvVfg+UNmnkEm+ikMmn3d+ONfiDWvwk7Awx6Cd8SbyyhP0Y2Y9\nqOFGVj3RBS8BJFCQSDEwWAUFgtKOk+xVvus3xEd2rxofMpU8YJ/N54VirjadO/XmyHhzJfsTub+t\nJOxQsW9gL9TBiCJYOAcFxy9NimLPQ275ZwjfVvTOkqeu0+eRvqr/a9KaPPfllHdzEnkl0osX1Mu5\nfKBoh3MJDraBwEr+DsIviuyNM3sHyQME5RvV/HAnfK+pV/V/bZLJm2bwPuZd5hwQkRuSgvS7ouiH\nLEMg6Ai3oED9cK0rXmWvsq/vLPhG39OMa9eb17FeiDL6/1hEngjcAohyH8I2GNgEAuMgUOB/Cfh8\n8aWK1QswANmbjnXxUKYxkjzh86Suc2yChZLTqJFz3B+jSAR+u7AepoEgbwAwkj6ztcyU+FhdAfh/\ny9GarDnd4B6cyzQGgm80j3iJ3MOqldyoiNemJMUA5hIUVP5T84o/j/Bzy75goneVe5Ugku+Kr3p8\ng+vnLdOYyr3uskhOIj8V/JYKGu+tEQIV+wb+C4c3CfOgzE6nFD5AL/2ucAgCMcs1gKPoXeaxRS3e\nSfBA/XnCqUaeygtBprvsUeA1oCji8UL/qyCi9AHemX5HkhF+6uUai1U0QPd5ZFR/57SOPKVX9G23\nWrWBMnA4BwRFcBOcyz+2QUHlO81lJ8EqlDsKcpB7PYxfiAHRroOqwTVDbD1c59q5s3dOb3ZG2TTL\nce9s3/gKGBTBgUVQ6EjsAEGwsRQQf9tYjnLPzOSJtpUFLCTfkUZzkPg5UNc58hDMv4vFEjlXAgUY\nqkDhEhicAoKy/2hdYryZ2BGVfYprVp+rfJNAVu/6FixZmUblvA9KulyT1aZZ0lgicocfguDBtrsP\n4Hd3QFOYl3BS20K2HtQt3gDDDkAqx/W7YtlQhNU2tiLy+MRo5lDFJAiQN3cA+Oz93QyTAKCaH7aV\nvWtGz6l003C+O9bhreTuMKdYdQgSkfOG/F8LAYQPeGzlpoxu43+ECgIObzICYft4cpG7zUNWIL7g\nReSCF7yViJjJP4j4gTCvp1dR2afYbB8L0LR1Cy192wetXlfRdOQfS3mJvGwPO7lkISnAtX8nQNTs\nAaCvqZoQqGRjveKG4WqbKL07Vf3LsKqRs1hHHrlNW14kCNhBvSUsEKh/p8q+DwD+yzYJ9e7k8B0x\nqcHnEXyjvp2sRJ7nzc7gbdKoCRgoOEzkFPDdqBlw3xYWKFizZlX/1yT7hAPpyd2xPMNqHTnFXis+\nW6X5IEhgChA8OHxBfOBc0iHu2xmlZ2eI8o1q/lHSvp1EDZqbzQ2T7wPFyhlWb3b63v2Qe/9O30GI\nPGh4DBCpB4Yk+3UC4Us4jqUa0pU1jJqQSM9OG1Sk62bgI/AUoV8nkL7oG0HRszO5HQQb4UnyPvp1\nVonWt5PT7oesOgRx6sijaIcL3aYtVIu2Iso9ZL9Oa8kDcR/GctlKFhFXzojICeEkf0ACgAUpBAOO\nZRtWoifaRhagLc/4nFusGkskL3IfcAgOyn2IovbnrMI5AIRo+gB46tMJRNtCtopvwQPu80dELnTH\nV/BQ9h81CQQm8i96IwcTkmj6AITt0wnk3iccoO3TabTyhZXIYz3sDEHsvbK5wKg/J2mrNqavl7vg\nO5NP5sGrQQ0+ZJ/OjvASeczmyxSo2DeQAfdg4vovAWV2egot2riKn2J1DUAseZXznoCgfTp9yR34\n3/xgJXKOzZebEbUxswp0HS7yL1sz5sRLOb6aPgAeNp7yWYNvcu08gs9dc+e0aiWF5suhGy+TBwtF\nOxwb0WeRUEnHh+g5Sb4K5a6CAI99wTthsXrGWu6cXghisWlWB0I2WK6Hj6BBFhgUzTAA+AeDQFvA\nViniVrB58PGwNansvcH1c8md0yv6QbexZd50GSh442Ug7WBgk+GrfKfZbgML0LVoA/jInrrxMsBs\nJY3F0khpvpwSHoMNRZCwDQjWQUDZfawGp8489VDZp7iWborSeBkI15MTaLx9bCamc85yWwJpvswY\n742YHQOFTTDw2mRZmd0LgPhlHVPpq+xTXEo3qew5kgebTN65PKOaXM9jlydW+5GLyN0ou/iNM31l\ndjqANMSvmh8O2YuzShJiB3LLHTB4uKoyrkkwp1i1ehORh8W7+AEn+ftssgxIo+WulKVFW5VQpZkQ\nD1ZF5AIZ5IHBIgiYyJ9Ng2VfsufehxPwvgeJLb4bLlMvi2Ql8o4POzn8U0yIB4f2a0ABW7B1hKD/\nJhCnVAMwlbxD3d2oLNNljrAVOQckmPBGZB8I4p0DAX67B7pg3M0H9HJn9bDT+wtBARsfUyNBxQyn\nMg+x6IFIsgfilm1U40OhBA9EbPbQiJxyN1ktw2r5oe9X9IM0OqYgYsApY8AIIX3bvbw7Qta8AUiq\nNRuQvuB9L4Vk9UKQ702zfDc3tsV7gPEcGMoof4Bum1fAXfRO275W8VmuCZjB2z5gZVdzN5A7K5H7\n3sbWRzNjF3wEFrKgwKijeKr43uq1SvKSB1i2Z2MndqCh3FnttZL8fuQZ+AwkVEHBORBIAGgKdTNl\nwE30rMs1hHIHaJorA8xq7tX5wWn3w+Q7BKk4l6UMEK4BwSkQSBAgF703yavm160RUu6Acf091dUz\n3eYJp/3Io/bs5NDk2BRFP6RLULANAlbyd5B+yqKvEqJsQ75DIBDvIauq/2vn0gyTh6qsOgRJ8+Uc\n+A44yv6jNkHAVP5G0k+8y44tFJIvhOABki1iq4TovWmLiLxM+AgCyu5jptI3Eb5xhl+AbjvNoCzX\nkAse4NNMGXAuzcSqufMSuU2NPPZudEXEV9av7D7GRvoFzfCTbqTMvC0bEEbu6Ys8FhJAGhOxUTIQ\nqVlylQLK3kcLNsBDGzaA9nvJSO5Zc4SXyKmXHyri8UJT5mDhsctOFYpGyVWK2GIti5AdeoCAXXqa\nEbKhMpBb7KxE7vJmJ3m3+ZCoiNdOOVh4lj2V6Cnbq3GWvM+HrKyzdw/NlAHDB6mclh+einm1P5CP\nDvJcCBZ0lOfxuQYBl9KOyn8qVQknd+km4aw+ZHkmSgPljhCslDEux3ASue9Ns2LiMzCRBgZFNA5X\nyXclgay+6GUbyiYOAKHcGYkdyJA7pzc7vW9jGwCK7vRZUAYF5yCgSG6jmOJX2ae4NEoG0m7QkAXV\nPt9AmC49xjiWZDrODVZ7rXBrLFHDsekwBT4ChGtAcAoCyunS/4NbACDqugO4SZ4ii+cm+FJn7nWu\n3XF+sNr9kK3IY+AxeFAEBdsgYC1/ZfexGrGFzyiTL5Lko8pdZVzbdc4ZiJ1VY4kyNl8O0sm+ClFw\nsAkEXjvcA/y73DfCtEavsk+J1Si5CgfJUzVyAAj6a1ahmGsN5gurVm9lFDkl3oKCQwDwLf1kO9yb\n4rlRsrXgi5K9++itCQRbJSMiLylFkD4r4QPxpO/YfQcIL3gOci9S1q61+eorEXmJIQ8AFuL3JXyv\nsg8teYIM3kuJhrngg2ftKuOGcs4bEbkQFOdAYCj+4F3ugfhNkJsRSfAu9ffYcgfM+moC+eROWY4R\nkQtssZa+geypRE8uea5lGtX8cIhOPEUVO2CYtXeYI6xEnsryQw4Tqew4ZfbEoifL5lWOG6rCLZNX\njQ+VQe6xxc7qYWe0NzuJekWGIvakTYUQsmcp+lCSZy73KjG+LxS1dhOxsxK5614rzh3gYxEhkEgw\n6IyV9Ak63AOOXe4BPoJ3rL+TrZxhKHffYmf1QlBRN83yHmA8BQKRfWeMZE8geSfBq+zrA2CfvZs+\nWE1F7IC53JuJndUr+i77kcfEtnu8KeQBgTAAlFX6lP0wO2LTG7NKMpInlrtRWYbZMkjTNe1d5wer\nTbPIOwQRYtMhngLqIEEWDCQIZOJD8tGzeJ9yd3ixyUTutvX2kPPU+GUlTtvYBu3ZqQJeywAfAYMi\nGDgHAALxF1H4lOUa2yzeSfAhSjMW7dUA/3JnJXZOjSWSar5MhfI3tGtQsA0A1tK3lH0RBQ/4724P\nWLRQq6KaHItdd1eNPxKrOTI13eYGK5FXmGxQ5AJlB3lTFM0wtgHAVPxWwi9gl3oTqDJ4L9m7ynFP\nsUozqv6vCyN2Ts2XCyFyF0IHAeU+hI30bTJ9Y+mXLLuPmb0Djk2QAT+CJyzJuMrd97wSkRcN38FA\n2X/Ut/RDyD5F0cfobg8w6XDfEcJVMtzELiIX6kMZEJT5R0ykn1f2RqI3lHwZBR9U7r5KMiEaI3fF\ng9hF5AIdrvJXZqfnlT256BPvWt+MXHLnkrkzydqdxE5UY+cl8iKtWuHSWYYLFBm+Mjs9iugLlsnH\nKMtYP1Atsdh5iZz6hSBFPF5oyhgMAmb1lKKnljx3wQP2jY+BQHIvkdiLLfIYqEjXFennR+U7LY/o\nsyRPXa5JXvBEcmcjdsB4dYwPsbNaR57KXivGHd6pUAGuUcSAELhTfRVnyYvcc+/bDURugNwRD2LP\nlDonkXPY/dCkoa8vvAUK5WfYZOXvQfBAtuRJsvjEJV+qrN3gDVTrbJ3TXiu+GkuYNusNha+gQRYI\nFM0wANKSvU3JRmWf4ip4wK2xcRWOcqeut5Nk7aFLMXXuIbfYOe1+GK1DkGd8BhKKYOAsfuV4AylJ\nvoqJ7FX2KS6SpyjRFEnuzmJXDa7HWOy8RM69Z6dhB3dKKIOBq/ydxK+cLp2G9Aklb9OxHnCXu4i9\nC6rBNUOVYupcv+PcYNVYgr3IfeE5QFAEAVv5W0tf2X2sBjfhB5I7UB7Bs8vYgWhiZ9Xq7alK2iJ3\navZLBVFQcJG/qfStZK/MPwKAl+CJH7bGyt4BHoK3fSs1KbE3kLqIPDG8BguHIGAqfq+yV0ZD85J7\nRxybGVchF3xCD1VtsvYUxa4Xm39URM4cjrK3yfBNZO9V9AA/2Tu0RQPs5O6yYoaD1AGabvaAwXJH\n1eR6hHNKa/OVViLygkEu/kCyF9F3wFHsgHlTYyB9ubt2sq8SW+wicsEKMvkbSt9E9nlFn1vyKvel\n95KC3AGyvpdVXEoyseVOIfbcZRiVcTMG80dELgTDSf45hZ9X9KSSV7mG2ksJ5Z5y1u5L7NTZuohc\niE4IwQO0ki+l4FX9X5PW2xmL3VcZhkLqrETuax157Kgu2JOa5MnLNLEE7+lhqoi9M87Z+n/nRylE\nzgEJJv6wlj1huYZE8Crf/QCII3jLnpdAuHIMS7EHkDqr5YfR9lqx7LgeCgkC7hjLnkjyzoJX+e6D\nbeauGh8ykbuN2GN/b6jF3kzqInJfRAgOsSduivgQfJAMXmXfBwB+mbtqfIhE7CXN1lm92clhP/Is\njDqxUxEgKEgQ+B9WpRoCwRdW7iL2TviQOqtNs0J3CMrbdNc3XoODpyBQZvEbid6z4EnknrDYqR6e\nppCtN5N6qUUeCp8BgzQIEEq/rKKn6DoPRJa7iD25bJ3VfuQpNl/O24ndFapg4Cx+AtmXTfLs5a6a\nXxdAWLlbPkB1amoMJC11Vq3eKif6GLULKsA1DPERDCjE7yR9R+EXWfaUpRlbuSeVtVtk7D7r6zHm\nZqbUSyfyEKhwl6IKAi7itxa+g+yLKPoQmbu3rD2U2CVbrz9P7uYk8kqAyWDTWDcmin5IF/nbCt9K\n9paiL5rkbRsmdMRG7klk7YzEHlXqpRN5bEIGEuU+hI30TWVvLPmSC9535p501m4h9iJk6xMslCwi\nD4lv8Su3jxdF9KlL3mfWnqzYiaQOGIg9ktRF5EXCp/SV/UdNZW8ieiPJl0zwrnInL8eojHtJROwc\npS4iLzuU8ld2HzMRPQfJF1ruocSuMu4D8Ct2JiUYqrkkIhfMoBC/Mjs9r+jzSj634Esgd5esPajY\nY2brqvtzoyLsAAANEklEQVSvuEldRC7Q4iJ6ZXY6dSYvgvcjdqsau8q4h1jZuur+Kw5S5yXyWOvI\nY3dnKQOumbwyO50yiy+z4EOKvQhSB7rPvRBSF5FTIgHBnkCZfHDBF0zupRZ7SKkbPijlJXKKvVYU\nwRixkYBQHxvZq3yn5RE8idxF7DVMxJ6U1IFcJRhKqRdP5DFRka9f1gDAXPBlkTv7bJ1Jpu5jSSOr\nNztjbGObu1luKFSAa5RB+CWRu4g9oWxddf9VHqnnqqeXXeS+CBIglKdxiy56U8mr7FO4yD1psfvM\n1lXGtVOW+vXgJXJurd6yWm/5wksQUMTjFVH2xIJ3lXuRxU6drbOXuofyS6f5wWkb2/vxVUxatihX\n89oU8RkYyOSvaIYBUBzZmwheNT+cJXffWXuyYhep16hbT+fUIQjLKl6G9UHIYEMZAEiEr9yHSFry\nhGIH3OReRLGL1GGcpfMS+bc8iDxH89uY+AgIruJ3kr1yunS6gmeStRdN7LYlGOeHpSrjmsykzqr5\nsheRhyBgsKASv4vsrUWvrC+5l9QkH0juIvYOhJZ6yIekDe7jlInzROTR8RgEXKVvK3sr0SurS+0l\nJcHnlbtqftiL2BN7eEoldbLyS8QsXU80H9qbyJ+q8BO5UaPcUBDK30X2wUSvrC6zF+6SF7GTwErq\nEZYyisgjEDQ4EEjfVvamog8ieBG7N7GL1OsQKEvXi82HEZFHwKv8HWVvKnqvgldGQ++lCHJXzQ/b\niL0I2bpPqRu/UepR6iLyAuFN9g6i9yl5r4LnLHePYi9ytt70++G4pDF2lq61+ZYUIvKEIZe9peRZ\nCF4Z3QJfuTuKPXS2nqLUuWfpInKhLmTCtxC9ieTzCt6L3FMVu2p+mDRbZy714KUX1eA6jnNJRC5Y\nQSJ6Q8nnFbzIvQsOYifN1lOWukPpJUSWLiIXSHEWfNHkzk3snsow1CWY1KQeO0sXkQvBcJK8geAp\n5V74rN1Dtl4aqVNn6arJTWTMGRG5EB1OgieTu8pxQ1W4iF2knomJ1ENm6SJygS3Wgs8pd6rMvZRi\nV40PhZB6SkIHus816n1eWImcw14rsSeI0BwOcg8q9hSkDuTuIl+F8kFpzO+sj1q6jdBF5J6RwBAG\nK8HnkLuIvQ4i9brELLuwerOTtLGEQWdyboj8aTCWO1HWXiqxi9S7EUPoxRW5TyIHCRG9PT7k7l3s\nKvseAPCWump8yETqqdXT6843D3V0EXlIAgYAkX0+ROyeIJS6ZOnZdXRW29jej6/6GNaJzA4rPvEs\nfpF9d7iJvaxS9116KZrQReSe8R4IPMleJL+X0GIPkq3HknqAenoKWbqPlS6sWr2diuZNZ0PRbHMg\nn3iRPrHoyy54arFHz9Y5Sl01PuRcemG034vphl3NhC4i94DvQEAqfCLRl1XwRmL3mK2XTeqlydJz\nZuiTsND4mt5EXnnCx6jZNNvhLQQ+xE8ie5G8MZRiL53UCUsvZRO6iJyIEMGASvgcJF8WuecWe6wS\njGp+XQD8pK7q/9pn2YW70HmJ/ETCwRThWJ7wIX9X2TtJXuTeFCqpA56ydZV93eBSt8jSi1R2yS30\n8eZKTkPkPlFhL0clfBfJxxJ8keUeIls36gTfEZVxTwkIHShO2SVT6CLyACi/w1OI3lby1oK3lHtR\nxc5W6irjfgpWdklW6HdzEnnFYVLk6XzCHUU/pKvkgwpe5A7Av9S9lV44Zemq/q+LUEevOz8KI/IQ\ncAkWim4oW9HbCD6U3IskdgqpB6+ncxI6kLuOTiH0aNm5iDwAoQOAch8ilOCN5V5isceSepGzdOcH\no1yELiJnQgjZK/chbATvVe4lFXsuqXMqvYjQvTLBQski8hj4Fr2y/6hvuYvYm+Mq9UJn6SURuoi8\nSPiQvbL7mKncRezusCu9qIz74JKlq+6/8iV0wM/8EpGXBWrJK/OP+JK7T7EXWupcsvSiCj3gBl0i\n8rJDJXhl9zETuccWu0i9M0mXXQhKLpyycxG50J2Ico8q9hJk66Zbp3aE9OGoyrgPEboRInIhPxSC\nV2anpyL21KTukqUXso7OQOguc0hELrjjInhldjq12MsudTZlF5VxDyGE7vHForxCt507InKBHoZi\nF6lnE7LskqzQVfdf+djHxXTeiMgF/wQSO2epi9BF6FV8lFtE5EJ4GImdtK5eNqnHXukS+8UiVf9U\na6E7PAzlJfJQ29jG6k0o1CeA2INn6wWTenShqybXj/1AVNX/ddc553N1SzlF7ooEAr/Yil3lO41K\n6mUTuo+VLskJnWm5RUTuC5E9HTZiV/lPzRK7ZOmdEaEjenbedY6IyGMhorejCFIXodMIXWVc2/d3\njFF2zkvkT/gYtQsqwDUoENHnw1TsKv+pIvV8UK9ySWqFC8HacwqZl0/kLqjYNwARfDM8SZ1i9YsI\nHcUut8TOzlk1luAu8jyoSNcVwXcmktRDPSAtu9DZLld03FnRWuacRH4q8q0maPp/YgqoQNcRue/F\ng9QlS8+GcsliUtk5QC70TJmnKHIfsAkOyvP4ZZd7BKmXWehsyy0x156r7r9yfolIRG5HcPErj2OX\nUe4i9KBELbeoJteNlZ2r+qdaPQi9HiJyXwQRvfI0btnEzkzqFEJPUuZAXaEXdqmiqn+qlczHMxL5\n/fiqj2GbbnwfC2+iVx7GLJPYTaSusk8RoXentNm5z7p5GURuQyz5exG8Ih6vDGInztJF6N2xeRia\nfHbuS+YicjdCCZ9U8IpuqMJLXYTuFcrsPHmZA93uK7fMReR+8Sl6lnIvstjzSl1lnyJC74zv7Jxd\nqcXxBaJuMsdC41vwJnIsq3gZtkqzXoOh8SV4Erkr9yEAFFfqKQg90RUuDYVOsO68yDIvlchNiSF+\nasGzEXsRpR5I6E7LFkucnSex5pxI5iJyB0KJnlLuzmJXBDdRNKkzEHrRyi3sSi2MV7R8D7eJyH3i\nU/QUchepE0Mk9JjllmRkDjivOS+SzB+C+XfZn8i/ZSHyJrUzrvgQfHSxK8eLi9C7EUvoqcscSLhu\nDli91p++yF1hEgio5e4qdpG6I4QvFsUqt3AROssliszWmovIs4goekq5u4g9mtTLJHTV/HDZs3OR\n+X9R3X91ysR5InJnAoqeSuxRpK6sLylC74CX7Dyh2nmUFS2qyTVDy1zVP1VPNB9eRJ4Xz5KPLfbg\nUi+L0FX2KY2EXvpSS0llLiIPjUe5u4o9eKauLC+WutAlO3emNDLPWWIRkcfGk9hjSV2EbgBBdl5m\nmQPmb4ImuZolh8xZifypCr3Ic+2BzA1iuceQugg9JwTZue2DUJH5/0hd5oUXuQlspU8odhepB8vS\nldVl0hU611JLIjL3WWZh89JQxqv8IvKcsJF8wlIXoWfgOTsv8hLFsstcLzYfrpQib0RUwTOQOluh\ni8zrIjLvTFFkLiL3QBS5E0ldhM4AkbkVVHuzkKxkCbwkUUQeiKByJ5B6KKFLuaUBInMrypqVi8gj\nkJLUCyX0Esoc8PDyUBOZxxY5wEjmAbNyEXlkgkm9iEJXZvcCQGTehSLKPMr2t6rBtQLJXETODO9i\njyB0yc4JcHx5KHSZpSgyT6VeLiJnjFepF03oyuxeAIjMO+DjpaEiy5xNvRwAvny0iDwFyix0kXkX\nROa5YVViAbzONa0N9r//Lz083IfQhKfmevxCXI/cHdfrkbmjXh2ymgl3JOv1804o41sxawDBAUcZ\nNAuMjQJs02CdkQjEfM8i8ztTZ97bzOdOKLePh0REHomq0L1I3UHok5YtMv4CiMw9opofttkHx+Ut\nYNYyz0m9+Wo0LwF280xEzgCvQrfEt8xzf3EUzIXO7EvWlDxZubIbulnZq6HMc5Tn2Gxx0ZXQWTmj\neSYiZ4QXoTtm5yaYyBzwnJ0z+pJl4ihzmxJLU5j0vq0Hq6ycESJyhnDKzlnJvOyoxofKVGIxxTkr\nbwaThEFEzhRv2bkFvmWeG2V4PpMvWS48roIoVVZuMMdzZ+Uq/5ixEJEzpwwylxKLAarxoTJl5abf\nC69ZOQNE5AlAnp2LzHkiWTkNDg/5rWAwv0TkZUVkni6q8SHJyhtTb+4WpbwiIk+IMpRZSo9k5YIF\nIvLESFXmeZGsPCeq8aHQWXlMYu8BUyPy3BKRJ0iKMpes3ADJyt1pMKedyiuMEZEnCptMxAOSladF\nSmvKnVCxb6AxIvKE4bCSxQTJysNhU14R0kVELjjBolZeVhTtcKnWyQURefJIVo5illekTi4YICIX\nBEFIHBG5IAhCXlTsG6iPiFxwpuj7WAi8aVheNFiCSELEkp23np2CIAhCGCQjFwRBSBwRuSAIQuKI\nyAVBEBJHRC4IgpA4InJBEITEEZELgiAkjohcEAQhcUTkgiAIiSMiFwRBSBwRuSAIQuKIyAVBEBJH\nRC4IgpA4InJBEITEEZELgiAkjohcEAQhcUTkgiAIiSMiFwRBSBwRuSAIQuKIyAVBEBLn/wFL+aQx\n5FczVAAAAABJRU5ErkJggg==\n", | |
|
392 | "png": "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\n17/+Na6++uqYO3dudOvWLebNmxe33XZb3HvvvW0u//nPfz4OO+ywmDBhQqxcuTLWrl0bN9xwQ9x/\n//1x0UUXtS43ffr0GDZsWJtjNDc3x4EHHhjTp0+PDRs2xAsvvBBPP/107LPPPq3L9O/fP5588slY\ns2ZNrF+/Ps4666zo06dPnHzyyfHCCy/Ev//977jpppti9OjR8fLLL7eu9/7778cJJ5wQzz33XPzn\nP/+Jxx9/PMaOHRuTJ0+OPffcM+l7sqU33ngjFi9eHKeccso2l2vvaNdll10WkydPjqeffjo2bNgQ\nDz74YFx11VXR2NjYqfkA2yacoAsVCoU2jzp9/etfj1122aXd5Q4++OB44okn4u67744999wzRowY\nEXvssUfcddddEfHBEZMzzzwzTjzxxOjdu3dcc801cd9990W/fv1ax2tvPh/6xz/+0XprflsWL14c\nL7/8cgwYMGCra4Hmzp271fLr16+Pr33ta3H55Ze3PpdoyJAhcd1118U555wTr7/+epvbmT9/fgwe\nPDgOP/zwGDx4cNx3333x+OOPR0NDQ+syjz32WLt3n+28885xww03xK233hq77757fPGLX4wxY8bE\n5MmTW5e59NJL4/jjj49Pf/rT8eabb0ZdXV0sWrQoBg0aFCNHjoz+/fvH/Pnz45FHHom999679Xs1\nYcKEGDduXJx22mnRp0+fOO+88+KCCy6Ia6+9tt3v25a2/FnMmTMnxo8fv92jQ239txMRcdppp8UV\nV1zRGn/Tpk2LefPmxaGHHpo8JyBdIevoRQ4AJdTS0hJ9+/aNhQsXxoEHHthl27300kujubk5Lrvs\nslzHXbt2bWzatCn69OmT67hAcexc6gkAdMRf/vKXqK+v79JoKqb6+vpSTwHoAKfqgIpyyCGHbHbd\nEUBXEk4AiXbkkQNAdXCNEwBAotyucXqqg/8SGzk+ry0DABXtou0vkrvhnTtulNsRp46Ek2gCAHLX\nkQCrlHASTQBAyd3eufzp0ovDRRMAUMm65DlOggkAqAZFP+IkmgCAalHUcBJNAEA1KVo4iSYAoNoU\nJZxEEwBQjXK9OFwwAQDVLLcjTqIJAKh2PuQXACCRcAIASCScAAASCScAgETCCQAgkXACAEgknAAA\nEgknAIBEwgkAIJFwAgBIJJwAABIJJwCARMIJACCRcAIASCScAAASCScAgETCCQAgkXACAEgknAAA\n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\n", | |
|
403 | 393 | "text": [ |
|
404 |
"<matplotlib.figure.Figure at 0x5 |
|
|
394 | "<matplotlib.figure.Figure at 0x10b00b350>" | |
|
405 | 395 | ] |
|
406 | 396 | }, |
|
407 | 397 | { |
|
408 | 398 | "output_type": "stream", |
|
409 | 399 | "stream": "stdout", |
|
410 | 400 | "text": [ |
|
411 | 401 | "Simulation completed!\n", |
|
412 |
"Monitored for: 0:00: |
|
|
402 | "Monitored for: 0:00:01.229672.\n" | |
|
413 | 403 | ] |
|
414 | 404 | } |
|
415 | 405 | ], |
|
416 |
"prompt_number": |
|
|
417 | }, | |
|
418 | { | |
|
419 | "cell_type": "code", | |
|
420 | "collapsed": false, | |
|
421 | "input": [], | |
|
422 | "language": "python", | |
|
423 | "metadata": {}, | |
|
424 | "outputs": [] | |
|
406 | "prompt_number": 9 | |
|
425 | 407 | } |
|
426 | 408 | ], |
|
427 | 409 | "metadata": {} |
|
428 | 410 | } |
|
429 | 411 | ] |
|
430 | 412 | } No newline at end of file |
@@ -1,555 +1,543 b'' | |||
|
1 | 1 | { |
|
2 | 2 | "metadata": { |
|
3 |
"name": " |
|
|
3 | "name": "" | |
|
4 | 4 | }, |
|
5 | 5 | "nbformat": 3, |
|
6 | 6 | "nbformat_minor": 0, |
|
7 | 7 | "worksheets": [ |
|
8 | 8 | { |
|
9 | 9 | "cells": [ |
|
10 | 10 | { |
|
11 | 11 | "cell_type": "heading", |
|
12 | 12 | "level": 1, |
|
13 | 13 | "metadata": { |
|
14 | 14 | "slideshow": { |
|
15 | 15 | "slide_start": false |
|
16 | 16 | } |
|
17 | 17 | }, |
|
18 | 18 | "source": [ |
|
19 | 19 | "Interactive monitoring of a parallel MPI simulation with the IPython Notebook" |
|
20 | 20 | ] |
|
21 | 21 | }, |
|
22 | 22 | { |
|
23 | 23 | "cell_type": "code", |
|
24 | 24 | "collapsed": false, |
|
25 | 25 | "input": [ |
|
26 |
"% |
|
|
26 | "%matplotlib inline\n", | |
|
27 | "import numpy as np\n", | |
|
28 | "import matplotlib.pyplot as plt\n", | |
|
29 | "\n", | |
|
30 | "from IPython.display import display\n", | |
|
27 | 31 | "from IPython.parallel import Client, error\n", |
|
32 | "\n", | |
|
28 | 33 | "cluster = Client(profile=\"mpi\")\n", |
|
29 | 34 | "view = cluster[:]\n", |
|
30 | 35 | "view.block = True" |
|
31 | 36 | ], |
|
32 | 37 | "language": "python", |
|
33 | 38 | "metadata": { |
|
34 | 39 | "slideshow": { |
|
35 | 40 | "slide_start": false |
|
36 | 41 | } |
|
37 | 42 | }, |
|
38 | "outputs": [ | |
|
39 | { | |
|
40 | "output_type": "stream", | |
|
41 | "stream": "stdout", | |
|
42 | "text": [ | |
|
43 | "\n", | |
|
44 | "Welcome to pylab, a matplotlib-based Python environment [backend: module://IPython.kernel.zmq.pylab.backend_inline].\n", | |
|
45 | "For more information, type 'help(pylab)'.\n" | |
|
46 | ] | |
|
47 | } | |
|
48 | ], | |
|
49 | "prompt_number": 12 | |
|
43 | "outputs": [], | |
|
44 | "prompt_number": 1 | |
|
50 | 45 | }, |
|
51 | 46 | { |
|
52 | 47 | "cell_type": "code", |
|
53 | 48 | "collapsed": false, |
|
54 | 49 | "input": [ |
|
55 | 50 | "cluster.ids" |
|
56 | 51 | ], |
|
57 | 52 | "language": "python", |
|
58 | 53 | "metadata": {}, |
|
59 | 54 | "outputs": [ |
|
60 | 55 | { |
|
61 | 56 | "metadata": {}, |
|
62 | 57 | "output_type": "pyout", |
|
63 |
"prompt_number": |
|
|
58 | "prompt_number": 2, | |
|
64 | 59 | "text": [ |
|
65 | 60 | "[0, 1, 2, 3]" |
|
66 | 61 | ] |
|
67 | 62 | } |
|
68 | 63 | ], |
|
69 |
"prompt_number": |
|
|
64 | "prompt_number": 2 | |
|
70 | 65 | }, |
|
71 | 66 | { |
|
72 | 67 | "cell_type": "markdown", |
|
73 | 68 | "metadata": { |
|
74 | 69 | "slideshow": { |
|
75 | 70 | "slide_start": false |
|
76 | 71 | } |
|
77 | 72 | }, |
|
78 | 73 | "source": [ |
|
79 | 74 | "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", |
|
80 | 75 | "\n", |
|
81 | 76 | "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:" |
|
82 | 77 | ] |
|
83 | 78 | }, |
|
84 | 79 | { |
|
85 | 80 | "cell_type": "code", |
|
86 | 81 | "collapsed": false, |
|
87 | 82 | "input": [ |
|
88 | 83 | "%%px\n", |
|
89 | 84 | "# MPI initialization, library imports and sanity checks on all engines\n", |
|
90 | 85 | "from mpi4py import MPI\n", |
|
91 | 86 | "import numpy as np\n", |
|
92 | 87 | "import time\n", |
|
93 | 88 | "\n", |
|
94 | 89 | "mpi = MPI.COMM_WORLD\n", |
|
95 | 90 | "bcast = mpi.bcast\n", |
|
96 | 91 | "barrier = mpi.barrier\n", |
|
97 | 92 | "rank = mpi.rank\n", |
|
98 | 93 | "print \"MPI rank: %i/%i\" % (mpi.rank,mpi.size)" |
|
99 | 94 | ], |
|
100 | 95 | "language": "python", |
|
101 | 96 | "metadata": { |
|
102 | 97 | "slideshow": { |
|
103 | 98 | "slide_start": false |
|
104 | 99 | } |
|
105 | 100 | }, |
|
106 | 101 | "outputs": [ |
|
107 | 102 | { |
|
108 | 103 | "output_type": "stream", |
|
109 | 104 | "stream": "stdout", |
|
110 | 105 | "text": [ |
|
111 | 106 | "[stdout:0] MPI rank: 3/4\n", |
|
112 | 107 | "[stdout:1] MPI rank: 2/4\n", |
|
113 | 108 | "[stdout:2] MPI rank: 0/4\n", |
|
114 | 109 | "[stdout:3] MPI rank: 1/4\n" |
|
115 | 110 | ] |
|
116 | 111 | } |
|
117 | 112 | ], |
|
118 |
"prompt_number": |
|
|
113 | "prompt_number": 3 | |
|
119 | 114 | }, |
|
120 | 115 | { |
|
121 | 116 | "cell_type": "markdown", |
|
122 | 117 | "metadata": { |
|
123 | 118 | "slideshow": { |
|
124 | 119 | "slide_start": false |
|
125 | 120 | } |
|
126 | 121 | }, |
|
127 | 122 | "source": [ |
|
128 | 123 | "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:" |
|
129 | 124 | ] |
|
130 | 125 | }, |
|
131 | 126 | { |
|
132 | 127 | "cell_type": "code", |
|
133 | 128 | "collapsed": false, |
|
134 | 129 | "input": [ |
|
135 | 130 | "ranks = view['rank']\n", |
|
136 | 131 | "rank_indices = np.argsort(ranks)\n", |
|
137 | 132 | "\n", |
|
138 | 133 | "def mpi_order(seq):\n", |
|
139 | 134 | " \"\"\"Return elements of a sequence ordered by MPI rank.\n", |
|
140 | 135 | "\n", |
|
141 | 136 | " The input sequence is assumed to be ordered by engine ID.\"\"\"\n", |
|
142 | 137 | " return [seq[x] for x in rank_indices]" |
|
143 | 138 | ], |
|
144 | 139 | "language": "python", |
|
145 | 140 | "metadata": { |
|
146 | 141 | "slideshow": { |
|
147 | 142 | "slide_start": false |
|
148 | 143 | } |
|
149 | 144 | }, |
|
150 | 145 | "outputs": [], |
|
151 |
"prompt_number": |
|
|
146 | "prompt_number": 4 | |
|
152 | 147 | }, |
|
153 | 148 | { |
|
154 | 149 | "cell_type": "heading", |
|
155 | 150 | "level": 2, |
|
156 | 151 | "metadata": { |
|
157 | 152 | "slideshow": { |
|
158 | 153 | "slide_start": false |
|
159 | 154 | } |
|
160 | 155 | }, |
|
161 | 156 | "source": [ |
|
162 | 157 | "MPI simulation example" |
|
163 | 158 | ] |
|
164 | 159 | }, |
|
165 | 160 | { |
|
166 | 161 | "cell_type": "markdown", |
|
167 | 162 | "metadata": { |
|
168 | 163 | "slideshow": { |
|
169 | 164 | "slide_start": false |
|
170 | 165 | } |
|
171 | 166 | }, |
|
172 | 167 | "source": [ |
|
173 | 168 | "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", |
|
174 | 169 | "\n", |
|
175 | 170 | "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)." |
|
176 | 171 | ] |
|
177 | 172 | }, |
|
178 | 173 | { |
|
179 | 174 | "cell_type": "code", |
|
180 | 175 | "collapsed": false, |
|
181 | 176 | "input": [ |
|
182 | 177 | "%%px\n", |
|
183 | 178 | "\n", |
|
184 | 179 | "stop = False\n", |
|
185 | 180 | "nsteps = 100\n", |
|
186 | 181 | "delay = 0.1\n", |
|
187 | 182 | "\n", |
|
188 | 183 | "xmin, xmax = 0, np.pi\n", |
|
189 | 184 | "ymin, ymax = 0, 2*np.pi\n", |
|
190 | 185 | "dy = (ymax-ymin)/mpi.size\n", |
|
191 | 186 | "\n", |
|
192 | 187 | "def simulation():\n", |
|
193 | 188 | " \"\"\"Toy simulation code, computes sin(f*(x**2+y**2)) for a slowly increasing f\n", |
|
194 | 189 | " over an increasingly fine mesh.\n", |
|
195 | 190 | "\n", |
|
196 | 191 | " The purpose of this code is simply to illustrate the basic features of a typical\n", |
|
197 | 192 | " MPI code: spatial domain decomposition, a solution which is evolving in some \n", |
|
198 | 193 | " sense, and local per-node computation. In this case the nodes don't really\n", |
|
199 | 194 | " communicate at all.\n", |
|
200 | 195 | " \"\"\"\n", |
|
201 | 196 | " # By making these few variables global, we allow the IPython client to access them\n", |
|
202 | 197 | " # remotely for interactive introspection\n", |
|
203 | 198 | " global j, Z, nx, nyt\n", |
|
204 | 199 | " freqs = np.linspace(0.6, 1, nsteps)\n", |
|
205 | 200 | " for j in range(nsteps):\n", |
|
206 | 201 | " nx, ny = 2+j/4, 2+j/2/mpi.size\n", |
|
207 | 202 | " nyt = mpi.size*ny\n", |
|
208 | 203 | " Xax = np.linspace(xmin, xmax, nx)\n", |
|
209 | 204 | " Yax = np.linspace(ymin+rank*dy, ymin+(rank+1)*dy, ny, endpoint=rank==mpi.size)\n", |
|
210 | 205 | " X, Y = np.meshgrid(Xax, Yax)\n", |
|
211 | 206 | " f = freqs[j]\n", |
|
212 | 207 | " Z = np.cos(f*(X**2 + Y**2))\n", |
|
213 | 208 | " # We add a small delay to simulate that a real-world computation\n", |
|
214 | 209 | " # would take much longer, and we ensure all nodes are synchronized\n", |
|
215 | 210 | " time.sleep(delay)\n", |
|
216 | 211 | " # The stop flag can be set remotely via IPython, allowing the simulation to be\n", |
|
217 | 212 | " # cleanly stopped from the outside\n", |
|
218 | 213 | " if stop:\n", |
|
219 | 214 | " break" |
|
220 | 215 | ], |
|
221 | 216 | "language": "python", |
|
222 | 217 | "metadata": { |
|
223 | 218 | "slideshow": { |
|
224 | 219 | "slide_start": false |
|
225 | 220 | } |
|
226 | 221 | }, |
|
227 | 222 | "outputs": [], |
|
228 |
"prompt_number": |
|
|
223 | "prompt_number": 5 | |
|
229 | 224 | }, |
|
230 | 225 | { |
|
231 | 226 | "cell_type": "heading", |
|
232 | 227 | "level": 2, |
|
233 | 228 | "metadata": { |
|
234 | 229 | "slideshow": { |
|
235 | 230 | "slide_start": false |
|
236 | 231 | } |
|
237 | 232 | }, |
|
238 | 233 | "source": [ |
|
239 | 234 | "IPython tools to interactively monitor and plot the MPI results" |
|
240 | 235 | ] |
|
241 | 236 | }, |
|
242 | 237 | { |
|
243 | 238 | "cell_type": "markdown", |
|
244 | 239 | "metadata": { |
|
245 | 240 | "slideshow": { |
|
246 | 241 | "slide_start": false |
|
247 | 242 | } |
|
248 | 243 | }, |
|
249 | 244 | "source": [ |
|
250 | 245 | "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:" |
|
251 | 246 | ] |
|
252 | 247 | }, |
|
253 | 248 | { |
|
254 | 249 | "cell_type": "code", |
|
255 | 250 | "collapsed": false, |
|
256 | 251 | "input": [ |
|
257 | 252 | "from IPython.display import clear_output\n", |
|
258 | 253 | "\n", |
|
259 | 254 | "def plot_current_results(in_place=True):\n", |
|
260 | 255 | " \"\"\"Makes a blocking call to retrieve remote data and displays the solution mesh\n", |
|
261 | 256 | " as a contour plot.\n", |
|
262 | 257 | " \n", |
|
263 | 258 | " Parameters\n", |
|
264 | 259 | " ----------\n", |
|
265 | 260 | " in_place : bool\n", |
|
266 | 261 | " By default it calls clear_output so that new plots replace old ones. Set\n", |
|
267 | 262 | " to False to allow keeping of all previous outputs.\n", |
|
268 | 263 | " \"\"\"\n", |
|
269 | 264 | " \n", |
|
270 | 265 | " # We make a blocking call to load the remote data from the simulation into simple named \n", |
|
271 | 266 | " # variables we can read from the engine namespaces\n", |
|
272 | 267 | " #view.apply_sync(load_simulation_globals)\n", |
|
273 | 268 | " # And now we can use the view to read these variables from all the engines. Then we\n", |
|
274 | 269 | " # concatenate all of them into single arrays for local plotting\n", |
|
275 | 270 | " try:\n", |
|
276 | 271 | " Z = np.concatenate(mpi_order(view['Z']))\n", |
|
277 | 272 | " except ValueError:\n", |
|
278 | 273 | " print \"dimension mismatch in Z, not plotting\"\n", |
|
279 | 274 | " ax = plt.gca()\n", |
|
280 | 275 | " return ax.figure\n", |
|
281 | 276 | " \n", |
|
282 | 277 | " nx, nyt, j, nsteps = view.pull(['nx', 'nyt', 'j', 'nsteps'], targets=0)\n", |
|
283 | 278 | " fig, ax = plt.subplots()\n", |
|
284 | 279 | " ax.contourf(Z)\n", |
|
285 | 280 | " ax.set_title('Mesh: %i x %i, step %i/%i' % (nx, nyt, j+1, nsteps))\n", |
|
286 | " axis('off')\n", | |
|
281 | " plt.axis('off')\n", | |
|
287 | 282 | " # We clear the notebook output before plotting this if in-place plot updating is requested\n", |
|
288 | 283 | " if in_place:\n", |
|
289 | 284 | " clear_output()\n", |
|
290 | 285 | " display(fig)\n", |
|
291 | 286 | " return fig" |
|
292 | 287 | ], |
|
293 | 288 | "language": "python", |
|
294 | 289 | "metadata": { |
|
295 | 290 | "slideshow": { |
|
296 | 291 | "slide_start": false |
|
297 | 292 | } |
|
298 | 293 | }, |
|
299 | 294 | "outputs": [], |
|
300 |
"prompt_number": |
|
|
295 | "prompt_number": 6 | |
|
301 | 296 | }, |
|
302 | 297 | { |
|
303 | 298 | "cell_type": "markdown", |
|
304 | 299 | "metadata": { |
|
305 | 300 | "slideshow": { |
|
306 | 301 | "slide_start": false |
|
307 | 302 | } |
|
308 | 303 | }, |
|
309 | 304 | "source": [ |
|
310 | 305 | "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:" |
|
311 | 306 | ] |
|
312 | 307 | }, |
|
313 | 308 | { |
|
314 | 309 | "cell_type": "code", |
|
315 | 310 | "collapsed": false, |
|
316 | 311 | "input": [ |
|
317 | 312 | "def simulation_alive():\n", |
|
318 | 313 | " \"\"\"Return True if the simulation thread is still running on any engine.\n", |
|
319 | 314 | " \"\"\"\n", |
|
320 | 315 | " return any(view.apply_sync(lambda : simulation_thread.is_alive()))" |
|
321 | 316 | ], |
|
322 | 317 | "language": "python", |
|
323 | 318 | "metadata": { |
|
324 | 319 | "slideshow": { |
|
325 | 320 | "slide_start": false |
|
326 | 321 | } |
|
327 | 322 | }, |
|
328 | 323 | "outputs": [], |
|
329 |
"prompt_number": |
|
|
324 | "prompt_number": 7 | |
|
330 | 325 | }, |
|
331 | 326 | { |
|
332 | 327 | "cell_type": "markdown", |
|
333 | 328 | "metadata": { |
|
334 | 329 | "slideshow": { |
|
335 | 330 | "slide_start": false |
|
336 | 331 | } |
|
337 | 332 | }, |
|
338 | 333 | "source": [ |
|
339 | 334 | "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:" |
|
340 | 335 | ] |
|
341 | 336 | }, |
|
342 | 337 | { |
|
343 | 338 | "cell_type": "code", |
|
344 | 339 | "collapsed": false, |
|
345 | 340 | "input": [ |
|
346 | 341 | "def monitor_simulation(refresh=5.0, plots_in_place=True):\n", |
|
347 | 342 | " \"\"\"Monitor the simulation progress and call plotting routine.\n", |
|
348 | 343 | "\n", |
|
349 | 344 | " Supress KeyboardInterrupt exception if interrupted, ensure that the last \n", |
|
350 | 345 | " figure is always displayed and provide basic timing and simulation status.\n", |
|
351 | 346 | "\n", |
|
352 | 347 | " Parameters\n", |
|
353 | 348 | " ----------\n", |
|
354 | 349 | " refresh : float\n", |
|
355 | 350 | " Refresh interval between calls to retrieve and plot data. The default\n", |
|
356 | 351 | " is 5s, adjust depending on the desired refresh rate, but be aware that \n", |
|
357 | 352 | " very short intervals will start having a significant impact.\n", |
|
358 | 353 | "\n", |
|
359 | 354 | " plots_in_place : bool\n", |
|
360 | 355 | " If true, every new figure replaces the last one, producing a (slow)\n", |
|
361 | 356 | " animation effect in the notebook. If false, all frames are plotted\n", |
|
362 | 357 | " in sequence and appended in the output area.\n", |
|
363 | 358 | " \"\"\"\n", |
|
364 | 359 | " import datetime as dt, time\n", |
|
365 | 360 | " \n", |
|
366 | 361 | " if not simulation_alive():\n", |
|
367 | 362 | " plot_current_results(in_place=plots_in_place)\n", |
|
368 | 363 | " plt.close('all')\n", |
|
369 | 364 | " print 'Simulation has already finished, no monitoring to do.'\n", |
|
370 | 365 | " return\n", |
|
371 | 366 | " \n", |
|
372 | 367 | " t0 = dt.datetime.now()\n", |
|
373 | 368 | " fig = None\n", |
|
374 | 369 | " try:\n", |
|
375 | 370 | " while simulation_alive():\n", |
|
376 | 371 | " fig = plot_current_results(in_place=plots_in_place)\n", |
|
377 | 372 | " plt.close('all') # prevent re-plot of old figures\n", |
|
378 | 373 | " time.sleep(refresh) # so we don't hammer the server too fast\n", |
|
379 | 374 | " except (KeyboardInterrupt, error.TimeoutError):\n", |
|
380 | 375 | " msg = 'Monitoring interrupted, simulation is ongoing!'\n", |
|
381 | 376 | " else:\n", |
|
382 | 377 | " msg = 'Simulation completed!'\n", |
|
383 | 378 | " tmon = dt.datetime.now() - t0\n", |
|
384 | 379 | " if plots_in_place and fig is not None:\n", |
|
385 | 380 | " clear_output()\n", |
|
386 | 381 | " plt.close('all')\n", |
|
387 | 382 | " display(fig)\n", |
|
388 | 383 | " print msg\n", |
|
389 | 384 | " print 'Monitored for: %s.' % tmon" |
|
390 | 385 | ], |
|
391 | 386 | "language": "python", |
|
392 | 387 | "metadata": { |
|
393 | 388 | "slideshow": { |
|
394 | 389 | "slide_start": false |
|
395 | 390 | } |
|
396 | 391 | }, |
|
397 | 392 | "outputs": [], |
|
398 |
"prompt_number": |
|
|
393 | "prompt_number": 8 | |
|
399 | 394 | }, |
|
400 | 395 | { |
|
401 | 396 | "cell_type": "heading", |
|
402 | 397 | "level": 2, |
|
403 | 398 | "metadata": { |
|
404 | 399 | "slideshow": { |
|
405 | 400 | "slide_start": false |
|
406 | 401 | } |
|
407 | 402 | }, |
|
408 | 403 | "source": [ |
|
409 | 404 | "Making a simulation object that can be monitored interactively" |
|
410 | 405 | ] |
|
411 | 406 | }, |
|
412 | 407 | { |
|
413 | 408 | "cell_type": "code", |
|
414 | 409 | "collapsed": false, |
|
415 | 410 | "input": [ |
|
416 | 411 | "%%px\n", |
|
417 | 412 | "from threading import Thread\n", |
|
418 | 413 | "stop = False\n", |
|
419 | 414 | "nsteps = 100\n", |
|
420 | 415 | "delay=0.5\n", |
|
421 | 416 | "# Create a thread wrapper for the simulation. The target must be an argument-less\n", |
|
422 | 417 | "# function so we wrap the call to 'simulation' in a simple lambda:\n", |
|
423 | 418 | "simulation_thread = Thread(target = lambda : simulation())\n", |
|
424 | 419 | "# Now we actually start the simulation\n", |
|
425 | 420 | "simulation_thread.start()" |
|
426 | 421 | ], |
|
427 | 422 | "language": "python", |
|
428 | 423 | "metadata": { |
|
429 | 424 | "slideshow": { |
|
430 | 425 | "slide_start": false |
|
431 | 426 | } |
|
432 | 427 | }, |
|
433 | 428 | "outputs": [], |
|
434 |
"prompt_number": |
|
|
429 | "prompt_number": 9 | |
|
435 | 430 | }, |
|
436 | 431 | { |
|
437 | 432 | "cell_type": "code", |
|
438 | 433 | "collapsed": false, |
|
439 | 434 | "input": [ |
|
440 | 435 | "monitor_simulation(refresh=1);" |
|
441 | 436 | ], |
|
442 | 437 | "language": "python", |
|
443 | 438 | "metadata": { |
|
444 | 439 | "slideshow": { |
|
445 | 440 | "slide_start": false |
|
446 | 441 | } |
|
447 | 442 | }, |
|
448 | 443 | "outputs": [ |
|
449 | 444 | { |
|
450 | 445 | "metadata": {}, |
|
451 | 446 | "output_type": "display_data", |
|
452 | "png": "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\n", | |
|
447 | "png": "iVBORw0KGgoAAAANSUhEUgAAAlMAAAGKCAYAAAAomMSSAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAIABJREFUeJztvXvwnkV5//9+UkNOQAgQKAxnQjgIhFA5lNgQUvyGgA0e\nIAK1lFP40VoYEAsjh2GnxVq0RenUEUJQUQtKZ9IhQGxEAgQDgi0QkEkrIVBAwikRwYSEQPb3R/g8\n+Tyf53Tvfe/huvZ+v2Yc5Xn23t1g9vq8Ptde927DWmtBCCGEEEJKMSz1BAghhBBCNEOZIoQQQgip\nAGWKEEIIIaQClClCCCGEkApQpgghhBBCKkCZIoQQQgipAGWKEGW88MIL2HvvvVNPgwhk06ZNqadA\nSC2hTBFSAWMMhg0bhm9+85td28yfPx/Dhg3D2WefHXFmxXj22Wdx8sknY9y4cdhll11w1lln4bXX\nXuvY9oknnsCIESPwq1/9yvs8pk2bhmHDhrX85w/+4A86tn3llVewzTbb4Cc/+Yn3ecRm3rx52G+/\n/TBixAhMnDgRt956a8v3GzduxD/+4z9izz33xMiRIzFp0iTcc889Xfu77rrr8NWvfrXlsx/96EcY\nOXIkFi9e3Nb+nXfewbnnnovx48dj3LhxOOWUU/Dqq6+2tFm1ahU++9nPYty4cRg/fjzOO+88rF27\ntsKfmpD8oEwRUpFRo0bhlltu6fr9vHnzMHr0aDQajYiz6s/bb7+N448/HrvtthtWrFiBJ554Au+/\n/z4+9alPdWx/0UUX4eyzz8bBBx/sfS6NRgPz5s3Dpk2bmv/54IMPOra97LLLMGXKFMycOdP7PGJy\n3XXX4brrrsPNN9+MNWvWYO7cubj22mvxr//6r802F110EebPn4/58+dj9erV+Pu//3ucc845uPvu\nu9v6s9billtuwcknn9z87Otf/zr+5m/+BiNHjuw4h1NPPRW///3v8fTTT2PlypXYddddccIJJzT/\n3b/33nuYMWMGdt99dzz//PN46qmn8Pbbb2P27Nme/20QohxLCCmNMcZ++tOftmPHjrWPPPJI2/cv\nvfSS3WqrreyZZ55pzzrrLC9jPv/883avvfaq3M+9995rp06d2vLZ+vXr7YgRI+yyZctaPr/tttvs\ntttua19//fXK43Zi2rRp9pZbbunbbunSpXb48OH2mWee8T6H+++/306bNs17v51477337NZbb22X\nLFnS8vmDDz5ot9tuO/v+++/b3/zmN3bYsGH2xRdfbGlz66232oMOOqitz5/+9Kd2ypQpzX/+/ve/\nb3fZZRf71FNP2b322sved999Le0feughu/POO9v169e3fH7wwQfb22+/3Vpr7Q9+8AN72GGHtXy/\nbt06O378+I5/3wmpK8xMEVKRMWPG4IwzzsC8efPavvvOd76D448/HnvssUeCmfXm+OOPx4MPPtjy\n2YgRIzBq1KiW2pu1a9fisssuwxVXXIHx48cHm4/tc7PVpk2bcNFFF+Hcc8/FQQcdFGweMXjjjTew\ndu1aTJ48ueXzww8/HL/73e/w6quv4oUXXsB2222H3Xffva3N//7v/7b1OXfuXJx//vnNf549ezYe\nfvhhHHLIIR3nsGjRIhx//PEYMWJEy+czZ85sbqEuWrQIJ510Usv3o0aNwrRp07LYZiXEF5QpQjww\nZ84c3HHHHS21JNZafO9732v5ATeYX/7yl5gyZQpGjRqF3XbbDddee23L1tY999yDQw89FKNHj8ah\nhx6K++67r+X5JUuW4GMf+xi23nprfOxjH8PDDz/c8v2RRx6JL37xi05/joHtowMPPLD52Ve/+lV8\n5CMfwcUXX1yoj9NPPx3Tpk1rytH69etxwAEH4Oabb+75nDEGo0ePxvjx43H55Zfj/fffb/n+O9/5\nDp599ln83d/9ncsfqYVHH30Uf/zHf4wxY8Zg4sSJ+NGPfgQA2GuvvTB9+nQ8+OCDGDZsGPbZZ5/m\nM88++yxmzpyJMWPGYKeddsIll1yCdevWNb+fNm0a7r77blx++eXYbbfdMHr0aEydOhWPPvpo13ns\nuOOOGD16NJ588smWz5944gkAwLbbbos99tgDb731Fl5++eW2NmPHjm357PXXX8eSJUvwuc99rvnZ\niBEjsNdee3Wdw8qVKzFp0qS2zydNmoTnnnuuZ5vDDjus2YYQQpkipBLWWjQaDUyePBkTJ07E7bff\n3vzu3nvvxYYNG/DJT36yLevyi1/8AqeddhquvPJKrF69GkuXLsXy5cvxhS98AQCwZs0anHrqqfin\nf/onvPXWWzj33HNbZOTNN9/EVVddhVtuuQWrVq3CKaecgtmzZ+O9995rttl///2dMmIvvfQS5syZ\ngy9/+cvNbMVrr72Gf/7nf8aIESMwYcIE7L777vjSl76Ed999t2s/c+fOxUsvvYTrr78eAHDNNddg\nv/32w5w5c7o+c/rpp+Oee+7Bm2++ifnz5+POO+/EBRdc0Px+w4YNuPrqq7H99tvj6KOPxi677IJz\nzz0Xa9asKfzns9Zi1qxZOP/887FmzRp85StfwY033ohNmzbhhRdewP33349jjz0WmzZtwsqVKwFs\nfnPy+OOPx5lnnolXX30VTz/9NBqNBj7zmc80+200GrjwwguxYcMGPPTQQ3jllVdw3nnnYebMmbjz\nzjs7zmWrrbbCZZddhjlz5uDhhx/GunXr8Mgjj2DOnDk4+uijsc0222C33XbDX/7lX2L27Nl4+umn\nsXbtWixatAiXXXYZZsyY0dLfd7/7XXzuc59ryzL14re//S223Xbbts+322675r/Xbm3Gjh3r9O+e\nkOxJucdIiHauueYa+/nPf95aa+2NN95ojzrqqOZ3p556qr3iiiustdZeeeWVLTVTRx99tL3//vtb\n+tqwYYPdeuut7erVq+2yZcvsmDFj7FtvvdU25vPPP28bjYZdvnx5y+e77rpr6VqiN9980x544IH2\nz/7sz1o+v/LKK+3w4cOtMcY+/vjjduHChfbwww+3J510Us/+Hn30UbvNNtvYW265xf7hH/6hfe21\n15zm86tf/coOHz7cPvfcc9Zaa2+++WbbaDTsRRddZB977DG7ePFi+6d/+qf2sMMOsxs3bizU51tv\nvWWHDRtmn3322Y7fd6qZOu200+x3v/vdtrb77bef/e///m9rrbXHHnusPf3009va3HTTTXbixIld\n57Np0yZ7/fXX2913392OGDHC7r///rbRaNj58+c322zYsMFeccUVdqeddrKjRo2yEyZMsMOGDbOP\nP/54Sz/77befffrpp7uO1almaubMmfamm25qa3vPPffYAw880Fpr7YEHHmgXLVrU1uZb3/qWnTlz\nZtfxCKkbzEwR4onTTz8dzzzzDJ555hm8+eabuOuuu3Deeee1tdu4cSMeffRRTJ8+veUogJEjR2Ld\nunVYvnw5DjnkEEyfPh0TJkzA2WefjR/+8IfYsGFDs48dd9wRBxxwQEu/e++9N1avXu0877Vr1+Kk\nk07CjjvuiDvuuKPlu0WLFuGSSy7BNddcg8mTJ2PmzJn4z//8Tzz44IN45JFHuvZ55JFH4gtf+ALO\nO+88XHfdddhpp52c5vTRj34U++yzDx577LHmPE499VTccMMNOOKII3Dcccfh7rvvxm9/+1v8+7//\ne6E+x44diwsuuACHH344TjvtNMydOxdvv/12z2ceeughnHPOOW3HNjz33HNYvnw5gM2ZqdNPP73t\n2U996lN49tln8cYbb3Tsu9Fo4JJLLsGLL76I9evXY+rUqTjmmGPw6U9/utlmq622wle+8hW89tpr\nWLduHfbZZx+cdtppLbVWixcvxo477uj8luW4cePwu9/9ru3zt956CzvssEPfNttvv73TeITkDGWK\nEE9su+22mD17Nm6++WZ8//vfx8c//vHm4ZpDj0VoNBp4+umnW44CGDgOYMqUKWg0GliwYAHuuusu\n7LvvvviHf/gHTJ06tVlHNGbMmLbxhw8f3reIeygbN27EKaecgk2bNmHhwoVtr9C//fbb+PjHP97y\n2fjx43HAAQf0rJl59913sWDBAhxyyCH4t3/7N6c5DbDVVls1z5rqNI+RI0fiiCOOwIoVKwr3+a1v\nfQs///nPccQRR2DevHmYPHlyR1kYYNiwYbjrrrs6/v/053/+5812rv/eh/LUU0/he9/7Hv7lX/6l\na5u7774bS5cuxde//vWWz+fOndtzC7Ub++67L5YtW9b2+ZNPPol99923b5sJEyY4j0lIrlCmCKnA\nUEmaM2cOfvCDH2DevHldf8ANHz4cxxxzDH74wx+2fP7BBx/gmWeeaf7z+++/j6OPPhpXXXUVli1b\nhuXLl+Opp57yNndrLc466yz85je/waJFi7D11lu3tdl///1b5gQA69atw8qVK7Hnnnt27fvSSy/F\nRz/6UTz00EP49a9/ja997Wtd2z733HP49re/3fLZypUrsWLFChx11FFd57Fp0yY888wzPYush/L+\n++/j0EMPxaWXXorHHnsMw4cPbxb2NxqNthPEjz322Lb/nwC0CIa1Fj/+8Y/b2tx5552YOHFioTcg\nL774Ypx11lk4/PDDO36/ceNGfOlLX8IVV1yBXXfdtfn5G2+8gfvvvx+nnXZa3zGGcsIJJ+BnP/sZ\n1q9f3/JnWbhwYfMMrxNOOKHtTKt169bhgQceUH/OFyFeSbrJSIhyBtdMDXDwwQfb8ePH2/fee6/5\n2dCaqV/+8pd2zJgx9pvf/KZdvXq1ff755+3s2bPtjBkzrLXWPvDAA3a//fazTz75pH333XftHXfc\nYUePHm1fe+21rudMTZs2zT7wwAPNf/785z9vv/GNb3Sd+5e//GU7YcIEu2rVqq5tHn/8cbv99tvb\nH//4x/btt9+2K1assCeffLL9xCc+0fWZBQsW2F122cWuXr3aWmvtz3/+cztq1Cj72GOPdWz/i1/8\nwm611Vb2G9/4hn3nnXfs448/bidPnmwvv/zyZpuXX37Z7rDDDvbb3/62XbNmjX355Zft+eefbw8+\n+GC7YcOGZrtTTz3V/tVf/VXHcX7961/b3XbbzT7wwAN2/fr19r777rPbbLONffLJJ6211v7P//yP\n3Xnnne3rr79u/+///s9aa+2LL75ox44da6+44gr76quv2lWrVtkLL7zQHnTQQfaDDz6w1m6umdpz\nzz3txRdfbFeuXGnXrFljb731Vrv99tvbO++8s+u/pwHmz59vt9tuO/vGG290bXP99dfbffbZp+XP\naq21X/va1+xf//Vf9x2jU82UtZvrpmbPnm1XrVplV69ebS+88EI7efLk5p9t48aN9tBDD7UXXXSR\nXbNmjX3llVfsKaecYj/5yU/2HZOQOkGZIqQCxhj7F3/xFy2f3XDDDfbSSy9t+eyqq66yZ599dstn\n//Vf/2X/5E/+xI4cOdLuvPPO9sILL7TvvPNO8/trr73W7rHHHnbkyJH2j/7oj+y9995rrd1cgL73\n3nu3zWXatGn2wQcfbP7zEUccYS+55JKuc582bZodNmyYbTQabf+59dZbm+0efvhhe8wxx9iRI0fa\nnXbayV5yySUt8xzMK6+8YnfaaSd7zz33tHx+9dVX2wkTJtjf//73HZ+799577ZFHHmlHjRpl99xz\nT3vDDTe0tVm+fLmdMWOGHT16tB03bpw9++yzWwrbN23aZHfccUf7s5/9rOufee7cuXbixIl25MiR\n9qCDDmoeTjnAOeecY0eMGGEPP/zw5mcrVqywJ554oh0zZozdfvvt7ZlnnmlfffXVln+Pd911l/3i\nF79od955Zzty5Eg7ZcqUQodabtiwwU6YMKGn9L755pt23Lhx9j/+4z/avtt///2bMtiLbjL1zjvv\n2HPPPdfusMMOduzYsfaUU05pe1lg1apV9rOf/awdO3as3WGHHex5551n165d23dMQupEw9qKm/2E\nECKAxx9/HJ/4xCfwxhtvYNiweBUMxx13HK6++mpMnz492pgDrFixgrVLhAiANVOEkCy47777MGvW\nrKgilRqKFCEy+EjqCRBCiA/+9m//NvUUCCE1pT6/whFCCCGEBIA1U4QQQgghFYiyzbd0yFk8pH5M\n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\n", | |
|
453 | 448 | "text": [ |
|
454 |
"<matplotlib.figure.Figure at 0x5 |
|
|
449 | "<matplotlib.figure.Figure at 0x1141a1450>" | |
|
455 | 450 | ] |
|
456 | 451 | }, |
|
457 | 452 | { |
|
458 | 453 | "output_type": "stream", |
|
459 | 454 | "stream": "stdout", |
|
460 | 455 | "text": [ |
|
461 | "Simulation has already finished, no monitoring to do.\n" | |
|
456 | "Simulation completed!\n", | |
|
457 | "Monitored for: 0:00:50.653178.\n" | |
|
462 | 458 | ] |
|
463 | 459 | } |
|
464 | 460 | ], |
|
465 |
"prompt_number": |
|
|
461 | "prompt_number": 10 | |
|
466 | 462 | }, |
|
467 | 463 | { |
|
468 | 464 | "cell_type": "markdown", |
|
469 | 465 | "metadata": { |
|
470 | 466 | "slideshow": { |
|
471 | 467 | "slide_start": false |
|
472 | 468 | } |
|
473 | 469 | }, |
|
474 | 470 | "source": [ |
|
475 | 471 | "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:" |
|
476 | 472 | ] |
|
477 | 473 | }, |
|
478 | 474 | { |
|
479 | 475 | "cell_type": "code", |
|
480 | 476 | "collapsed": false, |
|
481 | 477 | "input": [ |
|
482 | 478 | "view['stop'] = True" |
|
483 | 479 | ], |
|
484 | 480 | "language": "python", |
|
485 | 481 | "metadata": { |
|
486 | 482 | "slideshow": { |
|
487 | 483 | "slide_start": false |
|
488 | 484 | } |
|
489 | 485 | }, |
|
490 | 486 | "outputs": [], |
|
491 |
"prompt_number": |
|
|
487 | "prompt_number": 11 | |
|
492 | 488 | }, |
|
493 | 489 | { |
|
494 | 490 | "cell_type": "code", |
|
495 | 491 | "collapsed": false, |
|
496 | 492 | "input": [ |
|
497 | 493 | "%%px --target 0\n", |
|
498 | 494 | "from IPython.parallel import bind_kernel; bind_kernel()\n", |
|
499 | 495 | "%connect_info" |
|
500 | 496 | ], |
|
501 | 497 | "language": "python", |
|
502 | 498 | "metadata": {}, |
|
503 | 499 | "outputs": [ |
|
504 | 500 | { |
|
505 | 501 | "output_type": "stream", |
|
506 | 502 | "stream": "stdout", |
|
507 | 503 | "text": [ |
|
508 | 504 | "{\n", |
|
509 |
" \"stdin_port\": 6 |
|
|
505 | " \"stdin_port\": 65310, \n", | |
|
510 | 506 | " \"ip\": \"127.0.0.1\", \n", |
|
511 |
" \"control_port\": |
|
|
512 |
" \"hb_port\": |
|
|
513 |
" \"key\": \" |
|
|
514 |
" \"shell_port\": 60 |
|
|
507 | " \"control_port\": 58188, \n", | |
|
508 | " \"hb_port\": 58187, \n", | |
|
509 | " \"key\": \"e4f5cda8-faa8-48d3-a62c-dbde67db9827\", \n", | |
|
510 | " \"shell_port\": 65083, \n", | |
|
515 | 511 | " \"transport\": \"tcp\", \n", |
|
516 |
" \"iopub_port\": 5 |
|
|
512 | " \"iopub_port\": 54934\n", | |
|
517 | 513 | "}\n", |
|
518 | 514 | "\n", |
|
519 | 515 | "Paste the above JSON into a file, and connect with:\n", |
|
520 | 516 | " $> ipython <app> --existing <file>\n", |
|
521 | 517 | "or, if you are local, you can connect with just:\n", |
|
522 |
" $> ipython <app> --existing kernel- |
|
|
518 | " $> ipython <app> --existing kernel-64604.json \n", | |
|
523 | 519 | "or even just:\n", |
|
524 | 520 | " $> ipython <app> --existing \n", |
|
525 | 521 | "if this is the most recent IPython session you have started.\n" |
|
526 | 522 | ] |
|
527 | 523 | } |
|
528 | 524 | ], |
|
529 |
"prompt_number": 1 |
|
|
525 | "prompt_number": 12 | |
|
530 | 526 | }, |
|
531 | 527 | { |
|
532 | 528 | "cell_type": "code", |
|
533 | 529 | "collapsed": false, |
|
534 | 530 | "input": [ |
|
535 | 531 | "%%px --target 0\n", |
|
536 | 532 | "%qtconsole" |
|
537 | 533 | ], |
|
538 | 534 | "language": "python", |
|
539 | 535 | "metadata": {}, |
|
540 | 536 | "outputs": [], |
|
541 |
"prompt_number": 1 |
|
|
542 | }, | |
|
543 | { | |
|
544 | "cell_type": "code", | |
|
545 | "collapsed": false, | |
|
546 | "input": [], | |
|
547 | "language": "python", | |
|
548 | "metadata": {}, | |
|
549 | "outputs": [] | |
|
537 | "prompt_number": 13 | |
|
550 | 538 | } |
|
551 | 539 | ], |
|
552 | 540 | "metadata": {} |
|
553 | 541 | } |
|
554 | 542 | ] |
|
555 | 543 | } No newline at end of file |
@@ -1,475 +1,487 b'' | |||
|
1 | 1 | { |
|
2 | 2 | "metadata": { |
|
3 |
"name": " |
|
|
3 | "name": "" | |
|
4 | 4 | }, |
|
5 | 5 | "nbformat": 3, |
|
6 | 6 | "nbformat_minor": 0, |
|
7 | 7 | "worksheets": [ |
|
8 | 8 | { |
|
9 | 9 | "cells": [ |
|
10 | 10 | { |
|
11 | 11 | "cell_type": "heading", |
|
12 | 12 | "level": 1, |
|
13 | 13 | "metadata": {}, |
|
14 | 14 | "source": [ |
|
15 | 15 | "Using Parallel Magics" |
|
16 | 16 | ] |
|
17 | 17 | }, |
|
18 | 18 | { |
|
19 | 19 | "cell_type": "markdown", |
|
20 | 20 | "metadata": {}, |
|
21 | 21 | "source": [ |
|
22 | 22 | "IPython has a few magics for working with your engines.\n", |
|
23 | 23 | "\n", |
|
24 | 24 | "This assumes you have started an IPython cluster, either with the notebook interface,\n", |
|
25 | 25 | "or the `ipcluster/controller/engine` commands." |
|
26 | 26 | ] |
|
27 | 27 | }, |
|
28 | 28 | { |
|
29 | 29 | "cell_type": "code", |
|
30 | 30 | "collapsed": false, |
|
31 | 31 | "input": [ |
|
32 | 32 | "from IPython import parallel\n", |
|
33 | 33 | "rc = parallel.Client()\n", |
|
34 | 34 | "dv = rc[:]\n", |
|
35 | 35 | "rc.ids" |
|
36 | 36 | ], |
|
37 | 37 | "language": "python", |
|
38 | 38 | "metadata": {}, |
|
39 | 39 | "outputs": [] |
|
40 | 40 | }, |
|
41 | 41 | { |
|
42 | 42 | "cell_type": "markdown", |
|
43 | 43 | "metadata": {}, |
|
44 | 44 | "source": [ |
|
45 | 45 | "Creating a Client registers the parallel magics `%px`, `%%px`, `%pxresult`, `pxconfig`, and `%autopx`. \n", |
|
46 | 46 | "These magics are initially associated with a DirectView always associated with all currently registered engines." |
|
47 | 47 | ] |
|
48 | 48 | }, |
|
49 | 49 | { |
|
50 | 50 | "cell_type": "markdown", |
|
51 | 51 | "metadata": {}, |
|
52 | 52 | "source": [ |
|
53 | 53 | "Now we can execute code remotely with `%px`:" |
|
54 | 54 | ] |
|
55 | 55 | }, |
|
56 | 56 | { |
|
57 | 57 | "cell_type": "code", |
|
58 | 58 | "collapsed": false, |
|
59 | 59 | "input": [ |
|
60 | 60 | "%px a=5" |
|
61 | 61 | ], |
|
62 | 62 | "language": "python", |
|
63 | 63 | "metadata": {}, |
|
64 | 64 | "outputs": [] |
|
65 | 65 | }, |
|
66 | 66 | { |
|
67 | 67 | "cell_type": "code", |
|
68 | 68 | "collapsed": false, |
|
69 | 69 | "input": [ |
|
70 | 70 | "%px print a" |
|
71 | 71 | ], |
|
72 | 72 | "language": "python", |
|
73 | 73 | "metadata": {}, |
|
74 | 74 | "outputs": [] |
|
75 | 75 | }, |
|
76 | 76 | { |
|
77 | 77 | "cell_type": "code", |
|
78 | 78 | "collapsed": false, |
|
79 | 79 | "input": [ |
|
80 | 80 | "%px a" |
|
81 | 81 | ], |
|
82 | 82 | "language": "python", |
|
83 | 83 | "metadata": {}, |
|
84 | 84 | "outputs": [] |
|
85 | 85 | }, |
|
86 | 86 | { |
|
87 | 87 | "cell_type": "code", |
|
88 | 88 | "collapsed": false, |
|
89 | 89 | "input": [ |
|
90 | 90 | "with dv.sync_imports():\n", |
|
91 | 91 | " import sys" |
|
92 | 92 | ], |
|
93 | 93 | "language": "python", |
|
94 | 94 | "metadata": {}, |
|
95 | 95 | "outputs": [] |
|
96 | 96 | }, |
|
97 | 97 | { |
|
98 | 98 | "cell_type": "code", |
|
99 | 99 | "collapsed": false, |
|
100 | 100 | "input": [ |
|
101 | 101 | "%px print >> sys.stderr, \"ERROR\"" |
|
102 | 102 | ], |
|
103 | 103 | "language": "python", |
|
104 | 104 | "metadata": {}, |
|
105 | 105 | "outputs": [] |
|
106 | 106 | }, |
|
107 | 107 | { |
|
108 | 108 | "cell_type": "markdown", |
|
109 | 109 | "metadata": {}, |
|
110 | 110 | "source": [ |
|
111 | 111 | "You don't have to wait for results. The `%pxconfig` magic lets you change the default blocking/targets for the `%px` magics:" |
|
112 | 112 | ] |
|
113 | 113 | }, |
|
114 | 114 | { |
|
115 | 115 | "cell_type": "code", |
|
116 | 116 | "collapsed": false, |
|
117 | 117 | "input": [ |
|
118 | 118 | "%pxconfig --noblock" |
|
119 | 119 | ], |
|
120 | 120 | "language": "python", |
|
121 | 121 | "metadata": {}, |
|
122 | 122 | "outputs": [] |
|
123 | 123 | }, |
|
124 | 124 | { |
|
125 | 125 | "cell_type": "code", |
|
126 | 126 | "collapsed": false, |
|
127 | 127 | "input": [ |
|
128 | 128 | "%px import time\n", |
|
129 | 129 | "%px time.sleep(5)\n", |
|
130 | 130 | "%px time.time()" |
|
131 | 131 | ], |
|
132 | 132 | "language": "python", |
|
133 | 133 | "metadata": {}, |
|
134 | 134 | "outputs": [] |
|
135 | 135 | }, |
|
136 | 136 | { |
|
137 | 137 | "cell_type": "markdown", |
|
138 | 138 | "metadata": {}, |
|
139 | 139 | "source": [ |
|
140 | 140 | "But you will notice that this didn't output the result of the last command.\n", |
|
141 | 141 | "For this, we have `%pxresult`, which displays the output of the latest request:" |
|
142 | 142 | ] |
|
143 | 143 | }, |
|
144 | 144 | { |
|
145 | 145 | "cell_type": "code", |
|
146 | 146 | "collapsed": false, |
|
147 | 147 | "input": [ |
|
148 | 148 | "%pxresult" |
|
149 | 149 | ], |
|
150 | 150 | "language": "python", |
|
151 | 151 | "metadata": {}, |
|
152 | 152 | "outputs": [] |
|
153 | 153 | }, |
|
154 | 154 | { |
|
155 | 155 | "cell_type": "markdown", |
|
156 | 156 | "metadata": {}, |
|
157 | 157 | "source": [ |
|
158 | 158 | "Remember, an IPython engine is IPython, so you can do magics remotely as well!" |
|
159 | 159 | ] |
|
160 | 160 | }, |
|
161 | 161 | { |
|
162 | 162 | "cell_type": "code", |
|
163 | 163 | "collapsed": false, |
|
164 | 164 | "input": [ |
|
165 | 165 | "%pxconfig --block\n", |
|
166 |
"%px % |
|
|
166 | "%px %matplotlib inline" | |
|
167 | ], | |
|
168 | "language": "python", | |
|
169 | "metadata": {}, | |
|
170 | "outputs": [] | |
|
171 | }, | |
|
172 | { | |
|
173 | "cell_type": "code", | |
|
174 | "collapsed": false, | |
|
175 | "input": [ | |
|
176 | "%%px\n", | |
|
177 | "import numpy as np\n", | |
|
178 | "import matplotlib.pyplot as plt" | |
|
167 | 179 | ], |
|
168 | 180 | "language": "python", |
|
169 | 181 | "metadata": {}, |
|
170 | 182 | "outputs": [] |
|
171 | 183 | }, |
|
172 | 184 | { |
|
173 | 185 | "cell_type": "markdown", |
|
174 | 186 | "metadata": {}, |
|
175 | 187 | "source": [ |
|
176 | 188 | "`%%px` can also be used as a cell magic, for submitting whole blocks.\n", |
|
177 | 189 | "This one acceps `--block` and `--noblock` flags to specify\n", |
|
178 | 190 | "the blocking behavior, though the default is unchanged.\n" |
|
179 | 191 | ] |
|
180 | 192 | }, |
|
181 | 193 | { |
|
182 | 194 | "cell_type": "code", |
|
183 | 195 | "collapsed": false, |
|
184 | 196 | "input": [ |
|
185 | 197 | "dv.scatter('id', dv.targets, flatten=True)\n", |
|
186 | 198 | "dv['stride'] = len(dv)" |
|
187 | 199 | ], |
|
188 | 200 | "language": "python", |
|
189 | 201 | "metadata": {}, |
|
190 | 202 | "outputs": [] |
|
191 | 203 | }, |
|
192 | 204 | { |
|
193 | 205 | "cell_type": "code", |
|
194 | 206 | "collapsed": false, |
|
195 | 207 | "input": [ |
|
196 | 208 | "%%px --noblock\n", |
|
197 | "x = linspace(0,pi,1000)\n", | |
|
209 | "x = np.linspace(0,np.pi,1000)\n", | |
|
198 | 210 | "for n in range(id,12, stride):\n", |
|
199 | 211 | " print n\n", |
|
200 | " plt.plot(x,sin(n*x))\n", | |
|
212 | " plt.plot(x,np.sin(n*x))\n", | |
|
201 | 213 | "plt.title(\"Plot %i\" % id)" |
|
202 | 214 | ], |
|
203 | 215 | "language": "python", |
|
204 | 216 | "metadata": {}, |
|
205 | 217 | "outputs": [] |
|
206 | 218 | }, |
|
207 | 219 | { |
|
208 | 220 | "cell_type": "code", |
|
209 | 221 | "collapsed": false, |
|
210 | 222 | "input": [ |
|
211 | 223 | "%pxresult" |
|
212 | 224 | ], |
|
213 | 225 | "language": "python", |
|
214 | 226 | "metadata": {}, |
|
215 | 227 | "outputs": [] |
|
216 | 228 | }, |
|
217 | 229 | { |
|
218 | 230 | "cell_type": "markdown", |
|
219 | 231 | "metadata": {}, |
|
220 | 232 | "source": [ |
|
221 | 233 | "It also lets you choose some amount of the grouping of the outputs with `--group-outputs`:\n", |
|
222 | 234 | "\n", |
|
223 | 235 | "The choices are:\n", |
|
224 | 236 | "\n", |
|
225 | 237 | "* `engine` - all of an engine's output is collected together\n", |
|
226 | 238 | "* `type` - where stdout of each engine is grouped, etc. (the default)\n", |
|
227 | 239 | "* `order` - same as `type`, but individual displaypub outputs are interleaved.\n", |
|
228 | 240 | " That is, it will output the first plot from each engine, then the second from each,\n", |
|
229 | 241 | " etc." |
|
230 | 242 | ] |
|
231 | 243 | }, |
|
232 | 244 | { |
|
233 | 245 | "cell_type": "code", |
|
234 | 246 | "collapsed": false, |
|
235 | 247 | "input": [ |
|
236 | 248 | "%%px --group-outputs=engine\n", |
|
237 | "x = linspace(0,pi,1000)\n", | |
|
249 | "x = np.linspace(0,np.pi,1000)\n", | |
|
238 | 250 | "for n in range(id+1,12, stride):\n", |
|
239 | 251 | " print n\n", |
|
240 | 252 | " plt.figure()\n", |
|
241 | " plt.plot(x,sin(n*x))\n", | |
|
253 | " plt.plot(x,np.sin(n*x))\n", | |
|
242 | 254 | " plt.title(\"Plot %i\" % n)" |
|
243 | 255 | ], |
|
244 | 256 | "language": "python", |
|
245 | 257 | "metadata": {}, |
|
246 | 258 | "outputs": [] |
|
247 | 259 | }, |
|
248 | 260 | { |
|
249 | 261 | "cell_type": "markdown", |
|
250 | 262 | "metadata": {}, |
|
251 | 263 | "source": [ |
|
252 | 264 | "When you specify 'order', then individual display outputs (e.g. plots) will be interleaved.\n", |
|
253 | 265 | "\n", |
|
254 | 266 | "`%pxresult` takes the same output-ordering arguments as `%%px`, \n", |
|
255 | 267 | "so you can view the previous result in a variety of different ways with a few sequential calls to `%pxresult`:" |
|
256 | 268 | ] |
|
257 | 269 | }, |
|
258 | 270 | { |
|
259 | 271 | "cell_type": "code", |
|
260 | 272 | "collapsed": false, |
|
261 | 273 | "input": [ |
|
262 | 274 | "%pxresult --group-outputs=order" |
|
263 | 275 | ], |
|
264 | 276 | "language": "python", |
|
265 | 277 | "metadata": {}, |
|
266 | 278 | "outputs": [] |
|
267 | 279 | }, |
|
268 | 280 | { |
|
269 | 281 | "cell_type": "heading", |
|
270 | 282 | "level": 2, |
|
271 | 283 | "metadata": {}, |
|
272 | 284 | "source": [ |
|
273 | 285 | "Single-engine views" |
|
274 | 286 | ] |
|
275 | 287 | }, |
|
276 | 288 | { |
|
277 | 289 | "cell_type": "markdown", |
|
278 | 290 | "metadata": {}, |
|
279 | 291 | "source": [ |
|
280 | 292 | "When a DirectView has a single target, the output is a bit simpler (no prefixes on stdout/err, etc.):" |
|
281 | 293 | ] |
|
282 | 294 | }, |
|
283 | 295 | { |
|
284 | 296 | "cell_type": "code", |
|
285 | 297 | "collapsed": false, |
|
286 | 298 | "input": [ |
|
287 | 299 | "def generate_output():\n", |
|
288 | 300 | " \"\"\"function for testing output\n", |
|
289 | 301 | " \n", |
|
290 | 302 | " publishes two outputs of each type, and returns something\n", |
|
291 | 303 | " \"\"\"\n", |
|
292 | 304 | " \n", |
|
293 | 305 | " import sys,os\n", |
|
294 |
" from IPython. |
|
|
306 | " from IPython.display import display, HTML, Math\n", | |
|
295 | 307 | " \n", |
|
296 | 308 | " print \"stdout\"\n", |
|
297 | 309 | " print >> sys.stderr, \"stderr\"\n", |
|
298 | 310 | " \n", |
|
299 | 311 | " display(HTML(\"<b>HTML</b>\"))\n", |
|
300 | 312 | " \n", |
|
301 | 313 | " print \"stdout2\"\n", |
|
302 | 314 | " print >> sys.stderr, \"stderr2\"\n", |
|
303 | 315 | " \n", |
|
304 | 316 | " display(Math(r\"\\alpha=\\beta\"))\n", |
|
305 | 317 | " \n", |
|
306 | 318 | " return os.getpid()\n", |
|
307 | 319 | "\n", |
|
308 | 320 | "dv['generate_output'] = generate_output" |
|
309 | 321 | ], |
|
310 | 322 | "language": "python", |
|
311 | 323 | "metadata": {}, |
|
312 | 324 | "outputs": [] |
|
313 | 325 | }, |
|
314 | 326 | { |
|
315 | 327 | "cell_type": "markdown", |
|
316 | 328 | "metadata": {}, |
|
317 | 329 | "source": [ |
|
318 | 330 | "You can also have more than one set of parallel magics registered at a time.\n", |
|
319 | 331 | "\n", |
|
320 | 332 | "The `View.activate()` method takes a suffix argument, which is added to `'px'`." |
|
321 | 333 | ] |
|
322 | 334 | }, |
|
323 | 335 | { |
|
324 | 336 | "cell_type": "code", |
|
325 | 337 | "collapsed": false, |
|
326 | 338 | "input": [ |
|
327 | 339 | "e0 = rc[-1]\n", |
|
328 | 340 | "e0.block = True\n", |
|
329 | 341 | "e0.activate('0')" |
|
330 | 342 | ], |
|
331 | 343 | "language": "python", |
|
332 | 344 | "metadata": {}, |
|
333 | 345 | "outputs": [] |
|
334 | 346 | }, |
|
335 | 347 | { |
|
336 | 348 | "cell_type": "code", |
|
337 | 349 | "collapsed": false, |
|
338 | 350 | "input": [ |
|
339 | 351 | "%px0 generate_output()" |
|
340 | 352 | ], |
|
341 | 353 | "language": "python", |
|
342 | 354 | "metadata": {}, |
|
343 | 355 | "outputs": [] |
|
344 | 356 | }, |
|
345 | 357 | { |
|
346 | 358 | "cell_type": "code", |
|
347 | 359 | "collapsed": false, |
|
348 | 360 | "input": [ |
|
349 | 361 | "%px generate_output()" |
|
350 | 362 | ], |
|
351 | 363 | "language": "python", |
|
352 | 364 | "metadata": {}, |
|
353 | 365 | "outputs": [] |
|
354 | 366 | }, |
|
355 | 367 | { |
|
356 | 368 | "cell_type": "markdown", |
|
357 | 369 | "metadata": {}, |
|
358 | 370 | "source": [ |
|
359 | 371 | "As mentioned above, we can redisplay those same results with various grouping:" |
|
360 | 372 | ] |
|
361 | 373 | }, |
|
362 | 374 | { |
|
363 | 375 | "cell_type": "code", |
|
364 | 376 | "collapsed": false, |
|
365 | 377 | "input": [ |
|
366 | 378 | "%pxresult --group-outputs order" |
|
367 | 379 | ], |
|
368 | 380 | "language": "python", |
|
369 | 381 | "metadata": {}, |
|
370 | 382 | "outputs": [] |
|
371 | 383 | }, |
|
372 | 384 | { |
|
373 | 385 | "cell_type": "code", |
|
374 | 386 | "collapsed": false, |
|
375 | 387 | "input": [ |
|
376 | 388 | "%pxresult --group-outputs engine" |
|
377 | 389 | ], |
|
378 | 390 | "language": "python", |
|
379 | 391 | "metadata": {}, |
|
380 | 392 | "outputs": [] |
|
381 | 393 | }, |
|
382 | 394 | { |
|
383 | 395 | "cell_type": "heading", |
|
384 | 396 | "level": 2, |
|
385 | 397 | "metadata": {}, |
|
386 | 398 | "source": [ |
|
387 | 399 | "Parallel Exceptions" |
|
388 | 400 | ] |
|
389 | 401 | }, |
|
390 | 402 | { |
|
391 | 403 | "cell_type": "markdown", |
|
392 | 404 | "metadata": {}, |
|
393 | 405 | "source": [ |
|
394 | 406 | "When you raise exceptions with the parallel exception,\n", |
|
395 | 407 | "the CompositeError raised locally will display your remote traceback." |
|
396 | 408 | ] |
|
397 | 409 | }, |
|
398 | 410 | { |
|
399 | 411 | "cell_type": "code", |
|
400 | 412 | "collapsed": false, |
|
401 | 413 | "input": [ |
|
402 | 414 | "%%px\n", |
|
403 | 415 | "from numpy.random import random\n", |
|
404 | 416 | "A = random((100,100,'invalid shape'))" |
|
405 | 417 | ], |
|
406 | 418 | "language": "python", |
|
407 | 419 | "metadata": {}, |
|
408 | 420 | "outputs": [] |
|
409 | 421 | }, |
|
410 | 422 | { |
|
411 | 423 | "cell_type": "heading", |
|
412 | 424 | "level": 2, |
|
413 | 425 | "metadata": {}, |
|
414 | 426 | "source": [ |
|
415 | 427 | "Remote Cell Magics" |
|
416 | 428 | ] |
|
417 | 429 | }, |
|
418 | 430 | { |
|
419 | 431 | "cell_type": "markdown", |
|
420 | 432 | "metadata": {}, |
|
421 | 433 | "source": [ |
|
422 | 434 | "Remember, Engines are IPython too, so the cell that is run remotely by %%px can in turn use a cell magic." |
|
423 | 435 | ] |
|
424 | 436 | }, |
|
425 | 437 | { |
|
426 | 438 | "cell_type": "code", |
|
427 | 439 | "collapsed": false, |
|
428 | 440 | "input": [ |
|
429 | 441 | "%%px\n", |
|
430 | 442 | "%%timeit\n", |
|
431 | 443 | "from numpy.random import random\n", |
|
432 | 444 | "from numpy.linalg import norm\n", |
|
433 | 445 | "A = random((100,100))\n", |
|
434 |
"norm(A, 2) |
|
|
446 | "norm(A, 2)" | |
|
435 | 447 | ], |
|
436 | 448 | "language": "python", |
|
437 | 449 | "metadata": {}, |
|
438 | 450 | "outputs": [] |
|
439 | 451 | }, |
|
440 | 452 | { |
|
441 | 453 | "cell_type": "heading", |
|
442 | 454 | "level": 2, |
|
443 | 455 | "metadata": {}, |
|
444 | 456 | "source": [ |
|
445 | 457 | "Local Execution" |
|
446 | 458 | ] |
|
447 | 459 | }, |
|
448 | 460 | { |
|
449 | 461 | "cell_type": "markdown", |
|
450 | 462 | "metadata": {}, |
|
451 | 463 | "source": [ |
|
452 | 464 | "As of IPython 0.14, you can instruct `%%px` to also execute the cell locally.\n", |
|
453 | 465 | "This is useful for interactive definitions,\n", |
|
454 | 466 | "or if you want to load a data source everywhere,\n", |
|
455 | 467 | "not just on the engines." |
|
456 | 468 | ] |
|
457 | 469 | }, |
|
458 | 470 | { |
|
459 | 471 | "cell_type": "code", |
|
460 | 472 | "collapsed": false, |
|
461 | 473 | "input": [ |
|
462 | 474 | "%%px --local\n", |
|
463 | 475 | "import os\n", |
|
464 | 476 | "thispid = os.getpid()\n", |
|
465 | 477 | "print thispid" |
|
466 | 478 | ], |
|
467 | 479 | "language": "python", |
|
468 | 480 | "metadata": {}, |
|
469 | 481 | "outputs": [] |
|
470 | 482 | } |
|
471 | 483 | ], |
|
472 | 484 | "metadata": {} |
|
473 | 485 | } |
|
474 | 486 | ] |
|
475 | 487 | } No newline at end of file |
General Comments 0
You need to be logged in to leave comments.
Login now