##// END OF EJS Templates
Added wait=True to a couple more clear_output calls.
Jonathan Frederic -
Show More
@@ -1,706 +1,706 b''
1 1 """AsyncResult objects for the client
2 2
3 3 Authors:
4 4
5 5 * MinRK
6 6 """
7 7 #-----------------------------------------------------------------------------
8 8 # Copyright (C) 2010-2011 The IPython Development Team
9 9 #
10 10 # Distributed under the terms of the BSD License. The full license is in
11 11 # the file COPYING, distributed as part of this software.
12 12 #-----------------------------------------------------------------------------
13 13
14 14 #-----------------------------------------------------------------------------
15 15 # Imports
16 16 #-----------------------------------------------------------------------------
17 17
18 18 from __future__ import print_function
19 19
20 20 import sys
21 21 import time
22 22 from datetime import datetime
23 23
24 24 from zmq import MessageTracker
25 25
26 26 from IPython.core.display import clear_output, display, display_pretty
27 27 from IPython.external.decorator import decorator
28 28 from IPython.parallel import error
29 29
30 30 #-----------------------------------------------------------------------------
31 31 # Functions
32 32 #-----------------------------------------------------------------------------
33 33
34 34 def _raw_text(s):
35 35 display_pretty(s, raw=True)
36 36
37 37 #-----------------------------------------------------------------------------
38 38 # Classes
39 39 #-----------------------------------------------------------------------------
40 40
41 41 # global empty tracker that's always done:
42 42 finished_tracker = MessageTracker()
43 43
44 44 @decorator
45 45 def check_ready(f, self, *args, **kwargs):
46 46 """Call spin() to sync state prior to calling the method."""
47 47 self.wait(0)
48 48 if not self._ready:
49 49 raise error.TimeoutError("result not ready")
50 50 return f(self, *args, **kwargs)
51 51
52 52 class AsyncResult(object):
53 53 """Class for representing results of non-blocking calls.
54 54
55 55 Provides the same interface as :py:class:`multiprocessing.pool.AsyncResult`.
56 56 """
57 57
58 58 msg_ids = None
59 59 _targets = None
60 60 _tracker = None
61 61 _single_result = False
62 62
63 63 def __init__(self, client, msg_ids, fname='unknown', targets=None, tracker=None):
64 64 if isinstance(msg_ids, basestring):
65 65 # always a list
66 66 msg_ids = [msg_ids]
67 67 self._single_result = True
68 68 else:
69 69 self._single_result = False
70 70 if tracker is None:
71 71 # default to always done
72 72 tracker = finished_tracker
73 73 self._client = client
74 74 self.msg_ids = msg_ids
75 75 self._fname=fname
76 76 self._targets = targets
77 77 self._tracker = tracker
78 78
79 79 self._ready = False
80 80 self._outputs_ready = False
81 81 self._success = None
82 82 self._metadata = [ self._client.metadata.get(id) for id in self.msg_ids ]
83 83
84 84 def __repr__(self):
85 85 if self._ready:
86 86 return "<%s: finished>"%(self.__class__.__name__)
87 87 else:
88 88 return "<%s: %s>"%(self.__class__.__name__,self._fname)
89 89
90 90
91 91 def _reconstruct_result(self, res):
92 92 """Reconstruct our result from actual result list (always a list)
93 93
94 94 Override me in subclasses for turning a list of results
95 95 into the expected form.
96 96 """
97 97 if self._single_result:
98 98 return res[0]
99 99 else:
100 100 return res
101 101
102 102 def get(self, timeout=-1):
103 103 """Return the result when it arrives.
104 104
105 105 If `timeout` is not ``None`` and the result does not arrive within
106 106 `timeout` seconds then ``TimeoutError`` is raised. If the
107 107 remote call raised an exception then that exception will be reraised
108 108 by get() inside a `RemoteError`.
109 109 """
110 110 if not self.ready():
111 111 self.wait(timeout)
112 112
113 113 if self._ready:
114 114 if self._success:
115 115 return self._result
116 116 else:
117 117 raise self._exception
118 118 else:
119 119 raise error.TimeoutError("Result not ready.")
120 120
121 121 def _check_ready(self):
122 122 if not self.ready():
123 123 raise error.TimeoutError("Result not ready.")
124 124
125 125 def ready(self):
126 126 """Return whether the call has completed."""
127 127 if not self._ready:
128 128 self.wait(0)
129 129 elif not self._outputs_ready:
130 130 self._wait_for_outputs(0)
131 131
132 132 return self._ready
133 133
134 134 def wait(self, timeout=-1):
135 135 """Wait until the result is available or until `timeout` seconds pass.
136 136
137 137 This method always returns None.
138 138 """
139 139 if self._ready:
140 140 self._wait_for_outputs(timeout)
141 141 return
142 142 self._ready = self._client.wait(self.msg_ids, timeout)
143 143 if self._ready:
144 144 try:
145 145 results = map(self._client.results.get, self.msg_ids)
146 146 self._result = results
147 147 if self._single_result:
148 148 r = results[0]
149 149 if isinstance(r, Exception):
150 150 raise r
151 151 else:
152 152 results = error.collect_exceptions(results, self._fname)
153 153 self._result = self._reconstruct_result(results)
154 154 except Exception as e:
155 155 self._exception = e
156 156 self._success = False
157 157 else:
158 158 self._success = True
159 159 finally:
160 160 if timeout is None or timeout < 0:
161 161 # cutoff infinite wait at 10s
162 162 timeout = 10
163 163 self._wait_for_outputs(timeout)
164 164
165 165
166 166 def successful(self):
167 167 """Return whether the call completed without raising an exception.
168 168
169 169 Will raise ``AssertionError`` if the result is not ready.
170 170 """
171 171 assert self.ready()
172 172 return self._success
173 173
174 174 #----------------------------------------------------------------
175 175 # Extra methods not in mp.pool.AsyncResult
176 176 #----------------------------------------------------------------
177 177
178 178 def get_dict(self, timeout=-1):
179 179 """Get the results as a dict, keyed by engine_id.
180 180
181 181 timeout behavior is described in `get()`.
182 182 """
183 183
184 184 results = self.get(timeout)
185 185 if self._single_result:
186 186 results = [results]
187 187 engine_ids = [ md['engine_id'] for md in self._metadata ]
188 188
189 189
190 190 rdict = {}
191 191 for engine_id, result in zip(engine_ids, results):
192 192 if engine_id in rdict:
193 193 raise ValueError("Cannot build dict, %i jobs ran on engine #%i" % (
194 194 engine_ids.count(engine_id), engine_id)
195 195 )
196 196 else:
197 197 rdict[engine_id] = result
198 198
199 199 return rdict
200 200
201 201 @property
202 202 def result(self):
203 203 """result property wrapper for `get(timeout=-1)`."""
204 204 return self.get()
205 205
206 206 # abbreviated alias:
207 207 r = result
208 208
209 209 @property
210 210 def metadata(self):
211 211 """property for accessing execution metadata."""
212 212 if self._single_result:
213 213 return self._metadata[0]
214 214 else:
215 215 return self._metadata
216 216
217 217 @property
218 218 def result_dict(self):
219 219 """result property as a dict."""
220 220 return self.get_dict()
221 221
222 222 def __dict__(self):
223 223 return self.get_dict(0)
224 224
225 225 def abort(self):
226 226 """abort my tasks."""
227 227 assert not self.ready(), "Can't abort, I am already done!"
228 228 return self._client.abort(self.msg_ids, targets=self._targets, block=True)
229 229
230 230 @property
231 231 def sent(self):
232 232 """check whether my messages have been sent."""
233 233 return self._tracker.done
234 234
235 235 def wait_for_send(self, timeout=-1):
236 236 """wait for pyzmq send to complete.
237 237
238 238 This is necessary when sending arrays that you intend to edit in-place.
239 239 `timeout` is in seconds, and will raise TimeoutError if it is reached
240 240 before the send completes.
241 241 """
242 242 return self._tracker.wait(timeout)
243 243
244 244 #-------------------------------------
245 245 # dict-access
246 246 #-------------------------------------
247 247
248 248 def __getitem__(self, key):
249 249 """getitem returns result value(s) if keyed by int/slice, or metadata if key is str.
250 250 """
251 251 if isinstance(key, int):
252 252 self._check_ready()
253 253 return error.collect_exceptions([self._result[key]], self._fname)[0]
254 254 elif isinstance(key, slice):
255 255 self._check_ready()
256 256 return error.collect_exceptions(self._result[key], self._fname)
257 257 elif isinstance(key, basestring):
258 258 # metadata proxy *does not* require that results are done
259 259 self.wait(0)
260 260 values = [ md[key] for md in self._metadata ]
261 261 if self._single_result:
262 262 return values[0]
263 263 else:
264 264 return values
265 265 else:
266 266 raise TypeError("Invalid key type %r, must be 'int','slice', or 'str'"%type(key))
267 267
268 268 def __getattr__(self, key):
269 269 """getattr maps to getitem for convenient attr access to metadata."""
270 270 try:
271 271 return self.__getitem__(key)
272 272 except (error.TimeoutError, KeyError):
273 273 raise AttributeError("%r object has no attribute %r"%(
274 274 self.__class__.__name__, key))
275 275
276 276 # asynchronous iterator:
277 277 def __iter__(self):
278 278 if self._single_result:
279 279 raise TypeError("AsyncResults with a single result are not iterable.")
280 280 try:
281 281 rlist = self.get(0)
282 282 except error.TimeoutError:
283 283 # wait for each result individually
284 284 for msg_id in self.msg_ids:
285 285 ar = AsyncResult(self._client, msg_id, self._fname)
286 286 yield ar.get()
287 287 else:
288 288 # already done
289 289 for r in rlist:
290 290 yield r
291 291
292 292 def __len__(self):
293 293 return len(self.msg_ids)
294 294
295 295 #-------------------------------------
296 296 # Sugar methods and attributes
297 297 #-------------------------------------
298 298
299 299 def timedelta(self, start, end, start_key=min, end_key=max):
300 300 """compute the difference between two sets of timestamps
301 301
302 302 The default behavior is to use the earliest of the first
303 303 and the latest of the second list, but this can be changed
304 304 by passing a different
305 305
306 306 Parameters
307 307 ----------
308 308
309 309 start : one or more datetime objects (e.g. ar.submitted)
310 310 end : one or more datetime objects (e.g. ar.received)
311 311 start_key : callable
312 312 Function to call on `start` to extract the relevant
313 313 entry [defalt: min]
314 314 end_key : callable
315 315 Function to call on `end` to extract the relevant
316 316 entry [default: max]
317 317
318 318 Returns
319 319 -------
320 320
321 321 dt : float
322 322 The time elapsed (in seconds) between the two selected timestamps.
323 323 """
324 324 if not isinstance(start, datetime):
325 325 # handle single_result AsyncResults, where ar.stamp is single object,
326 326 # not a list
327 327 start = start_key(start)
328 328 if not isinstance(end, datetime):
329 329 # handle single_result AsyncResults, where ar.stamp is single object,
330 330 # not a list
331 331 end = end_key(end)
332 332 return (end - start).total_seconds()
333 333
334 334 @property
335 335 def progress(self):
336 336 """the number of tasks which have been completed at this point.
337 337
338 338 Fractional progress would be given by 1.0 * ar.progress / len(ar)
339 339 """
340 340 self.wait(0)
341 341 return len(self) - len(set(self.msg_ids).intersection(self._client.outstanding))
342 342
343 343 @property
344 344 def elapsed(self):
345 345 """elapsed time since initial submission"""
346 346 if self.ready():
347 347 return self.wall_time
348 348
349 349 now = submitted = datetime.now()
350 350 for msg_id in self.msg_ids:
351 351 if msg_id in self._client.metadata:
352 352 stamp = self._client.metadata[msg_id]['submitted']
353 353 if stamp and stamp < submitted:
354 354 submitted = stamp
355 355 return (now-submitted).total_seconds()
356 356
357 357 @property
358 358 @check_ready
359 359 def serial_time(self):
360 360 """serial computation time of a parallel calculation
361 361
362 362 Computed as the sum of (completed-started) of each task
363 363 """
364 364 t = 0
365 365 for md in self._metadata:
366 366 t += (md['completed'] - md['started']).total_seconds()
367 367 return t
368 368
369 369 @property
370 370 @check_ready
371 371 def wall_time(self):
372 372 """actual computation time of a parallel calculation
373 373
374 374 Computed as the time between the latest `received` stamp
375 375 and the earliest `submitted`.
376 376
377 377 Only reliable if Client was spinning/waiting when the task finished, because
378 378 the `received` timestamp is created when a result is pulled off of the zmq queue,
379 379 which happens as a result of `client.spin()`.
380 380
381 381 For similar comparison of other timestamp pairs, check out AsyncResult.timedelta.
382 382
383 383 """
384 384 return self.timedelta(self.submitted, self.received)
385 385
386 386 def wait_interactive(self, interval=1., timeout=-1):
387 387 """interactive wait, printing progress at regular intervals"""
388 388 if timeout is None:
389 389 timeout = -1
390 390 N = len(self)
391 391 tic = time.time()
392 392 while not self.ready() and (timeout < 0 or time.time() - tic <= timeout):
393 393 self.wait(interval)
394 clear_output()
394 clear_output(wait=True)
395 395 print("%4i/%i tasks finished after %4i s" % (self.progress, N, self.elapsed), end="")
396 396 sys.stdout.flush()
397 397 print()
398 398 print("done")
399 399
400 400 def _republish_displaypub(self, content, eid):
401 401 """republish individual displaypub content dicts"""
402 402 try:
403 403 ip = get_ipython()
404 404 except NameError:
405 405 # displaypub is meaningless outside IPython
406 406 return
407 407 md = content['metadata'] or {}
408 408 md['engine'] = eid
409 409 ip.display_pub.publish(content['source'], content['data'], md)
410 410
411 411 def _display_stream(self, text, prefix='', file=None):
412 412 if not text:
413 413 # nothing to display
414 414 return
415 415 if file is None:
416 416 file = sys.stdout
417 417 end = '' if text.endswith('\n') else '\n'
418 418
419 419 multiline = text.count('\n') > int(text.endswith('\n'))
420 420 if prefix and multiline and not text.startswith('\n'):
421 421 prefix = prefix + '\n'
422 422 print("%s%s" % (prefix, text), file=file, end=end)
423 423
424 424
425 425 def _display_single_result(self):
426 426 self._display_stream(self.stdout)
427 427 self._display_stream(self.stderr, file=sys.stderr)
428 428
429 429 try:
430 430 get_ipython()
431 431 except NameError:
432 432 # displaypub is meaningless outside IPython
433 433 return
434 434
435 435 for output in self.outputs:
436 436 self._republish_displaypub(output, self.engine_id)
437 437
438 438 if self.pyout is not None:
439 439 display(self.get())
440 440
441 441 def _wait_for_outputs(self, timeout=-1):
442 442 """wait for the 'status=idle' message that indicates we have all outputs
443 443 """
444 444 if self._outputs_ready or not self._success:
445 445 # don't wait on errors
446 446 return
447 447
448 448 # cast None to -1 for infinite timeout
449 449 if timeout is None:
450 450 timeout = -1
451 451
452 452 tic = time.time()
453 453 while True:
454 454 self._client._flush_iopub(self._client._iopub_socket)
455 455 self._outputs_ready = all(md['outputs_ready']
456 456 for md in self._metadata)
457 457 if self._outputs_ready or \
458 458 (timeout >= 0 and time.time() > tic + timeout):
459 459 break
460 460 time.sleep(0.01)
461 461
462 462 @check_ready
463 463 def display_outputs(self, groupby="type"):
464 464 """republish the outputs of the computation
465 465
466 466 Parameters
467 467 ----------
468 468
469 469 groupby : str [default: type]
470 470 if 'type':
471 471 Group outputs by type (show all stdout, then all stderr, etc.):
472 472
473 473 [stdout:1] foo
474 474 [stdout:2] foo
475 475 [stderr:1] bar
476 476 [stderr:2] bar
477 477 if 'engine':
478 478 Display outputs for each engine before moving on to the next:
479 479
480 480 [stdout:1] foo
481 481 [stderr:1] bar
482 482 [stdout:2] foo
483 483 [stderr:2] bar
484 484
485 485 if 'order':
486 486 Like 'type', but further collate individual displaypub
487 487 outputs. This is meant for cases of each command producing
488 488 several plots, and you would like to see all of the first
489 489 plots together, then all of the second plots, and so on.
490 490 """
491 491 if self._single_result:
492 492 self._display_single_result()
493 493 return
494 494
495 495 stdouts = self.stdout
496 496 stderrs = self.stderr
497 497 pyouts = self.pyout
498 498 output_lists = self.outputs
499 499 results = self.get()
500 500
501 501 targets = self.engine_id
502 502
503 503 if groupby == "engine":
504 504 for eid,stdout,stderr,outputs,r,pyout in zip(
505 505 targets, stdouts, stderrs, output_lists, results, pyouts
506 506 ):
507 507 self._display_stream(stdout, '[stdout:%i] ' % eid)
508 508 self._display_stream(stderr, '[stderr:%i] ' % eid, file=sys.stderr)
509 509
510 510 try:
511 511 get_ipython()
512 512 except NameError:
513 513 # displaypub is meaningless outside IPython
514 514 return
515 515
516 516 if outputs or pyout is not None:
517 517 _raw_text('[output:%i]' % eid)
518 518
519 519 for output in outputs:
520 520 self._republish_displaypub(output, eid)
521 521
522 522 if pyout is not None:
523 523 display(r)
524 524
525 525 elif groupby in ('type', 'order'):
526 526 # republish stdout:
527 527 for eid,stdout in zip(targets, stdouts):
528 528 self._display_stream(stdout, '[stdout:%i] ' % eid)
529 529
530 530 # republish stderr:
531 531 for eid,stderr in zip(targets, stderrs):
532 532 self._display_stream(stderr, '[stderr:%i] ' % eid, file=sys.stderr)
533 533
534 534 try:
535 535 get_ipython()
536 536 except NameError:
537 537 # displaypub is meaningless outside IPython
538 538 return
539 539
540 540 if groupby == 'order':
541 541 output_dict = dict((eid, outputs) for eid,outputs in zip(targets, output_lists))
542 542 N = max(len(outputs) for outputs in output_lists)
543 543 for i in range(N):
544 544 for eid in targets:
545 545 outputs = output_dict[eid]
546 546 if len(outputs) >= N:
547 547 _raw_text('[output:%i]' % eid)
548 548 self._republish_displaypub(outputs[i], eid)
549 549 else:
550 550 # republish displaypub output
551 551 for eid,outputs in zip(targets, output_lists):
552 552 if outputs:
553 553 _raw_text('[output:%i]' % eid)
554 554 for output in outputs:
555 555 self._republish_displaypub(output, eid)
556 556
557 557 # finally, add pyout:
558 558 for eid,r,pyout in zip(targets, results, pyouts):
559 559 if pyout is not None:
560 560 display(r)
561 561
562 562 else:
563 563 raise ValueError("groupby must be one of 'type', 'engine', 'collate', not %r" % groupby)
564 564
565 565
566 566
567 567
568 568 class AsyncMapResult(AsyncResult):
569 569 """Class for representing results of non-blocking gathers.
570 570
571 571 This will properly reconstruct the gather.
572 572
573 573 This class is iterable at any time, and will wait on results as they come.
574 574
575 575 If ordered=False, then the first results to arrive will come first, otherwise
576 576 results will be yielded in the order they were submitted.
577 577
578 578 """
579 579
580 580 def __init__(self, client, msg_ids, mapObject, fname='', ordered=True):
581 581 AsyncResult.__init__(self, client, msg_ids, fname=fname)
582 582 self._mapObject = mapObject
583 583 self._single_result = False
584 584 self.ordered = ordered
585 585
586 586 def _reconstruct_result(self, res):
587 587 """Perform the gather on the actual results."""
588 588 return self._mapObject.joinPartitions(res)
589 589
590 590 # asynchronous iterator:
591 591 def __iter__(self):
592 592 it = self._ordered_iter if self.ordered else self._unordered_iter
593 593 for r in it():
594 594 yield r
595 595
596 596 # asynchronous ordered iterator:
597 597 def _ordered_iter(self):
598 598 """iterator for results *as they arrive*, preserving submission order."""
599 599 try:
600 600 rlist = self.get(0)
601 601 except error.TimeoutError:
602 602 # wait for each result individually
603 603 for msg_id in self.msg_ids:
604 604 ar = AsyncResult(self._client, msg_id, self._fname)
605 605 rlist = ar.get()
606 606 try:
607 607 for r in rlist:
608 608 yield r
609 609 except TypeError:
610 610 # flattened, not a list
611 611 # this could get broken by flattened data that returns iterables
612 612 # but most calls to map do not expose the `flatten` argument
613 613 yield rlist
614 614 else:
615 615 # already done
616 616 for r in rlist:
617 617 yield r
618 618
619 619 # asynchronous unordered iterator:
620 620 def _unordered_iter(self):
621 621 """iterator for results *as they arrive*, on FCFS basis, ignoring submission order."""
622 622 try:
623 623 rlist = self.get(0)
624 624 except error.TimeoutError:
625 625 pending = set(self.msg_ids)
626 626 while pending:
627 627 try:
628 628 self._client.wait(pending, 1e-3)
629 629 except error.TimeoutError:
630 630 # ignore timeout error, because that only means
631 631 # *some* jobs are outstanding
632 632 pass
633 633 # update ready set with those no longer outstanding:
634 634 ready = pending.difference(self._client.outstanding)
635 635 # update pending to exclude those that are finished
636 636 pending = pending.difference(ready)
637 637 while ready:
638 638 msg_id = ready.pop()
639 639 ar = AsyncResult(self._client, msg_id, self._fname)
640 640 rlist = ar.get()
641 641 try:
642 642 for r in rlist:
643 643 yield r
644 644 except TypeError:
645 645 # flattened, not a list
646 646 # this could get broken by flattened data that returns iterables
647 647 # but most calls to map do not expose the `flatten` argument
648 648 yield rlist
649 649 else:
650 650 # already done
651 651 for r in rlist:
652 652 yield r
653 653
654 654
655 655 class AsyncHubResult(AsyncResult):
656 656 """Class to wrap pending results that must be requested from the Hub.
657 657
658 658 Note that waiting/polling on these objects requires polling the Hubover the network,
659 659 so use `AsyncHubResult.wait()` sparingly.
660 660 """
661 661
662 662 def _wait_for_outputs(self, timeout=-1):
663 663 """no-op, because HubResults are never incomplete"""
664 664 self._outputs_ready = True
665 665
666 666 def wait(self, timeout=-1):
667 667 """wait for result to complete."""
668 668 start = time.time()
669 669 if self._ready:
670 670 return
671 671 local_ids = filter(lambda msg_id: msg_id in self._client.outstanding, self.msg_ids)
672 672 local_ready = self._client.wait(local_ids, timeout)
673 673 if local_ready:
674 674 remote_ids = filter(lambda msg_id: msg_id not in self._client.results, self.msg_ids)
675 675 if not remote_ids:
676 676 self._ready = True
677 677 else:
678 678 rdict = self._client.result_status(remote_ids, status_only=False)
679 679 pending = rdict['pending']
680 680 while pending and (timeout < 0 or time.time() < start+timeout):
681 681 rdict = self._client.result_status(remote_ids, status_only=False)
682 682 pending = rdict['pending']
683 683 if pending:
684 684 time.sleep(0.1)
685 685 if not pending:
686 686 self._ready = True
687 687 if self._ready:
688 688 try:
689 689 results = map(self._client.results.get, self.msg_ids)
690 690 self._result = results
691 691 if self._single_result:
692 692 r = results[0]
693 693 if isinstance(r, Exception):
694 694 raise r
695 695 else:
696 696 results = error.collect_exceptions(results, self._fname)
697 697 self._result = self._reconstruct_result(results)
698 698 except Exception as e:
699 699 self._exception = e
700 700 self._success = False
701 701 else:
702 702 self._success = True
703 703 finally:
704 704 self._metadata = map(self._client.metadata.get, self.msg_ids)
705 705
706 706 __all__ = ['AsyncResult', 'AsyncMapResult', 'AsyncHubResult']
@@ -1,412 +1,412 b''
1 1 {
2 2 "metadata": {
3 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 70 "import numpy as np\n",
71 71 "import matplotlib.pyplot as plt"
72 72 ],
73 73 "language": "python",
74 74 "metadata": {},
75 75 "outputs": [],
76 76 "prompt_number": 2
77 77 },
78 78 {
79 79 "cell_type": "markdown",
80 80 "metadata": {},
81 81 "source": [
82 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",
83 83 "\n",
84 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:"
85 85 ]
86 86 },
87 87 {
88 88 "cell_type": "code",
89 89 "collapsed": false,
90 90 "input": [
91 91 "%%px\n",
92 92 "# MPI initialization, library imports and sanity checks on all engines\n",
93 93 "from mpi4py import MPI\n",
94 94 "# Load data publication API so engines can send data to notebook client\n",
95 95 "from IPython.kernel.zmq.datapub import publish_data\n",
96 96 "import numpy as np\n",
97 97 "import time\n",
98 98 "\n",
99 99 "mpi = MPI.COMM_WORLD\n",
100 100 "bcast = mpi.bcast\n",
101 101 "barrier = mpi.barrier\n",
102 102 "rank = mpi.rank\n",
103 103 "print \"MPI rank: %i/%i\" % (mpi.rank,mpi.size)"
104 104 ],
105 105 "language": "python",
106 106 "metadata": {},
107 107 "outputs": [
108 108 {
109 109 "output_type": "stream",
110 110 "stream": "stdout",
111 111 "text": [
112 112 "[stdout:0] MPI rank: 2/4\n",
113 113 "[stdout:1] MPI rank: 0/4\n",
114 114 "[stdout:2] MPI rank: 3/4\n",
115 115 "[stdout:3] MPI rank: 1/4\n"
116 116 ]
117 117 }
118 118 ],
119 119 "prompt_number": 3
120 120 },
121 121 {
122 122 "cell_type": "markdown",
123 123 "metadata": {},
124 124 "source": [
125 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:"
126 126 ]
127 127 },
128 128 {
129 129 "cell_type": "code",
130 130 "collapsed": false,
131 131 "input": [
132 132 "ranks = view['rank']\n",
133 133 "engine_mpi = np.argsort(ranks)\n",
134 134 "\n",
135 135 "def mpi_order(seq):\n",
136 136 " \"\"\"Return elements of a sequence ordered by MPI rank.\n",
137 137 "\n",
138 138 " The input sequence is assumed to be ordered by engine ID.\"\"\"\n",
139 139 " return [seq[x] for x in engine_mpi]"
140 140 ],
141 141 "language": "python",
142 142 "metadata": {},
143 143 "outputs": [],
144 144 "prompt_number": 4
145 145 },
146 146 {
147 147 "cell_type": "heading",
148 148 "level": 2,
149 149 "metadata": {},
150 150 "source": [
151 151 "MPI simulation example"
152 152 ]
153 153 },
154 154 {
155 155 "cell_type": "markdown",
156 156 "metadata": {},
157 157 "source": [
158 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 159 "\n",
160 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 161 ]
162 162 },
163 163 {
164 164 "cell_type": "code",
165 165 "collapsed": false,
166 166 "input": [
167 167 "%%px\n",
168 168 "\n",
169 169 "# Global flag in the namespace\n",
170 170 "stop = False\n",
171 171 "\n",
172 172 "def simulation(nsteps=100, delay=0.1):\n",
173 173 " \"\"\"Toy simulation code, computes sin(f*(x**2+y**2)) for a slowly increasing f\n",
174 174 " over an increasingly fine mesh.\n",
175 175 "\n",
176 176 " The purpose of this code is simply to illustrate the basic features of a typical\n",
177 177 " MPI code: spatial domain decomposition, a solution which is evolving in some \n",
178 178 " sense, and local per-node computation. In this case the nodes only communicate when \n",
179 179 " gathering results for publication.\"\"\"\n",
180 180 " # Problem geometry\n",
181 181 " xmin, xmax = 0, np.pi\n",
182 182 " ymin, ymax = 0, 2*np.pi\n",
183 183 " dy = (ymax-ymin)/mpi.size\n",
184 184 "\n",
185 185 " freqs = np.linspace(0.6, 1, nsteps)\n",
186 186 " for j in range(nsteps):\n",
187 187 " nx, ny = 2+j/4, 2+j/2/mpi.size\n",
188 188 " nyt = mpi.size*ny\n",
189 189 " Xax = np.linspace(xmin, xmax, nx)\n",
190 190 " Yax = np.linspace(ymin+rank*dy, ymin+(rank+1)*dy, ny, endpoint=rank==mpi.size)\n",
191 191 " X, Y = np.meshgrid(Xax, Yax)\n",
192 192 " f = freqs[j]\n",
193 193 " Z = np.cos(f*(X**2 + Y**2))\n",
194 194 " \n",
195 195 " # We are now going to publish data to the clients. We take advantage of fast\n",
196 196 " # MPI communications and gather the Z mesh at the rank 0 node in the Zcat variable:\n",
197 197 " Zcat = mpi.gather(Z, root=0)\n",
198 198 " if mpi.rank == 0:\n",
199 199 " # Then we use numpy's concatenation to construct a single numpy array with the\n",
200 200 " # full mesh that can be sent to the client for visualization:\n",
201 201 " Zcat = np.concatenate(Zcat)\n",
202 202 " # We now can send a dict with the variables we want the client to have access to:\n",
203 203 " publish_data(dict(Z=Zcat, nx=nx, nyt=nyt, j=j, nsteps=nsteps))\n",
204 204 " \n",
205 205 " # We add a small delay to simulate that a real-world computation\n",
206 206 " # would take much longer, and we ensure all nodes are synchronized\n",
207 207 " time.sleep(delay)\n",
208 208 " # The stop flag can be set remotely via IPython, allowing the simulation to be\n",
209 209 " # cleanly stopped from the outside\n",
210 210 " if stop:\n",
211 211 " break"
212 212 ],
213 213 "language": "python",
214 214 "metadata": {},
215 215 "outputs": [],
216 216 "prompt_number": 5
217 217 },
218 218 {
219 219 "cell_type": "heading",
220 220 "level": 2,
221 221 "metadata": {},
222 222 "source": [
223 223 "IPython tools to interactively monitor and plot the MPI results"
224 224 ]
225 225 },
226 226 {
227 227 "cell_type": "markdown",
228 228 "metadata": {},
229 229 "source": [
230 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:"
231 231 ]
232 232 },
233 233 {
234 234 "cell_type": "code",
235 235 "collapsed": false,
236 236 "input": [
237 237 "from IPython.display import display, clear_output\n",
238 238 "\n",
239 239 "def plot_current_results(ar, in_place=True):\n",
240 240 " \"\"\"Makes a blocking call to retrieve remote data and displays the solution mesh\n",
241 241 " as a contour plot.\n",
242 242 " \n",
243 243 " Parameters\n",
244 244 " ----------\n",
245 245 " ar : async result object\n",
246 246 "\n",
247 247 " in_place : bool\n",
248 248 " By default it calls clear_output so that new plots replace old ones. Set\n",
249 249 " to False to allow keeping of all previous outputs.\n",
250 250 " \"\"\"\n",
251 251 " # Read data from MPI rank 0 engine\n",
252 252 " data = ar.data[engine_mpi[0]]\n",
253 253 " \n",
254 254 " try:\n",
255 255 " nx, nyt, j, nsteps = [data[k] for k in ['nx', 'nyt', 'j', 'nsteps']]\n",
256 256 " Z = data['Z']\n",
257 257 " except KeyError:\n",
258 258 " # This can happen if we read from the engines so quickly that the data \n",
259 259 " # hasn't arrived yet.\n",
260 260 " fig, ax = plt.subplots()\n",
261 261 " ax.plot([])\n",
262 262 " ax.set_title(\"No data yet\")\n",
263 263 " display(fig)\n",
264 264 " return fig\n",
265 265 " else:\n",
266 266 " \n",
267 267 " fig, ax = plt.subplots()\n",
268 268 " ax.contourf(Z)\n",
269 269 " ax.set_title('Mesh: %i x %i, step %i/%i' % (nx, nyt, j+1, nsteps))\n",
270 270 " plt.axis('off')\n",
271 271 " # We clear the notebook output before plotting this if in-place \n",
272 272 " # plot updating is requested\n",
273 273 " if in_place:\n",
274 " clear_output()\n",
274 " clear_output(wait=True)\n",
275 275 " display(fig)\n",
276 276 " \n",
277 277 " return fig"
278 278 ],
279 279 "language": "python",
280 280 "metadata": {},
281 281 "outputs": [],
282 282 "prompt_number": 6
283 283 },
284 284 {
285 285 "cell_type": "markdown",
286 286 "metadata": {},
287 287 "source": [
288 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:"
289 289 ]
290 290 },
291 291 {
292 292 "cell_type": "code",
293 293 "collapsed": false,
294 294 "input": [
295 295 "def monitor_simulation(ar, refresh=5.0, plots_in_place=True):\n",
296 296 " \"\"\"Monitor the simulation progress and call plotting routine.\n",
297 297 "\n",
298 298 " Supress KeyboardInterrupt exception if interrupted, ensure that the last \n",
299 299 " figure is always displayed and provide basic timing and simulation status.\n",
300 300 "\n",
301 301 " Parameters\n",
302 302 " ----------\n",
303 303 " ar : async result object\n",
304 304 "\n",
305 305 " refresh : float\n",
306 306 " Refresh interval between calls to retrieve and plot data. The default\n",
307 307 " is 5s, adjust depending on the desired refresh rate, but be aware that \n",
308 308 " very short intervals will start having a significant impact.\n",
309 309 "\n",
310 310 " plots_in_place : bool\n",
311 311 " If true, every new figure replaces the last one, producing a (slow)\n",
312 312 " animation effect in the notebook. If false, all frames are plotted\n",
313 313 " in sequence and appended in the output area.\n",
314 314 " \"\"\"\n",
315 315 " import datetime as dt, time\n",
316 316 " \n",
317 317 " if ar.ready():\n",
318 318 " plot_current_results(ar, in_place=plots_in_place)\n",
319 319 " plt.close('all')\n",
320 320 " print 'Simulation has already finished, no monitoring to do.'\n",
321 321 " return\n",
322 322 " \n",
323 323 " t0 = dt.datetime.now()\n",
324 324 " fig = None\n",
325 325 " try:\n",
326 326 " while not ar.ready():\n",
327 327 " fig = plot_current_results(ar, in_place=plots_in_place)\n",
328 328 " plt.close('all') # prevent re-plot of old figures\n",
329 329 " time.sleep(refresh)\n",
330 330 " except (KeyboardInterrupt, error.TimeoutError):\n",
331 331 " msg = 'Monitoring interrupted, simulation is ongoing!'\n",
332 332 " else:\n",
333 333 " msg = 'Simulation completed!'\n",
334 334 " tmon = dt.datetime.now() - t0\n",
335 335 " if plots_in_place and fig is not None:\n",
336 " clear_output()\n",
336 " clear_output(wait=True)\n",
337 337 " plt.close('all')\n",
338 338 " display(fig)\n",
339 339 " print msg\n",
340 340 " print 'Monitored for: %s.' % tmon"
341 341 ],
342 342 "language": "python",
343 343 "metadata": {},
344 344 "outputs": [],
345 345 "prompt_number": 7
346 346 },
347 347 {
348 348 "cell_type": "heading",
349 349 "level": 2,
350 350 "metadata": {},
351 351 "source": [
352 352 "Interactive monitoring in the client of the published data"
353 353 ]
354 354 },
355 355 {
356 356 "cell_type": "markdown",
357 357 "metadata": {},
358 358 "source": [
359 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."
360 360 ]
361 361 },
362 362 {
363 363 "cell_type": "code",
364 364 "collapsed": false,
365 365 "input": [
366 366 "# Create the local client that controls our IPython cluster with MPI support\n",
367 367 "from IPython.parallel import Client\n",
368 368 "cluster = Client(profile=\"mpi\")\n",
369 369 "# We make a view that encompasses all the engines\n",
370 370 "view = cluster[:]\n",
371 371 "# And now we call on all available nodes our simulation routine,\n",
372 372 "# as an asynchronous task\n",
373 373 "ar = view.apply_async(lambda : simulation(nsteps=10, delay=0.1))"
374 374 ],
375 375 "language": "python",
376 376 "metadata": {},
377 377 "outputs": [],
378 378 "prompt_number": 8
379 379 },
380 380 {
381 381 "cell_type": "code",
382 382 "collapsed": false,
383 383 "input": [
384 384 "monitor_simulation(ar, refresh=1)"
385 385 ],
386 386 "language": "python",
387 387 "metadata": {},
388 388 "outputs": [
389 389 {
390 390 "metadata": {},
391 391 "output_type": "display_data",
392 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\nEu1c6gkAAGzPU3fkO97I2zu3nnACADot76Apd8IJAKpQrQVNVxFOANCFBE1lE04AEIKGNMIJgLIn\naigXwgmAThM01BrhBFCFBA0Uh3AC6EKCBiqbcAIIQQOkEU5AWRM0QDkRTkCnCBqgFgknqEKiBqA4\nhBN0IUEDUNmEE4SgASCNcKKsCRoAyolwolMEDQC1SDhVGUEDAMUjnLqQqAGAyiacikAgAUB1Ek5F\nMHJ8qWdQPUQoAOVEOFHWRGj5ErVALRJOQKeI2vIlaqF4hBNAlRG15UvUVj7hBABdRNRWvrpSTwAA\noFLkd8TpotxG6rhrSrhtAKBmVMepulJGGx0ndAGoUNURTlQWoVt5xC5ARAgnIIXYrSxCF4pGOAFU\nG6FbWYRuRSlkWZblMlJTIZdhAACKbnjn8sfjCAAAEuV2qu7+4UclLXdi04K8NgkA0KW6/Bqn1MCi\n/IlgAGqNi8PpNBFcPUQwQBrhBIjgKiKCobiEE0AVEcHVQQCXL+EEAGVGABffiZ1cz+MIAAASCScA\ngES5naqbEefmNRRd5Ly4sdRTAICK4hqnGiZ2a4NABsiPcIIqJ5Brg0CGriGcAKqAQK4dIrm0hBMA\nVBCRnA+PIwAAKDLhBACQKLdTdQ8/OTavodiOY0fdU+opAEBNco1TBRKpRAhogFIQTlChBDQfEtHQ\ndYQTQIUT0UQI6K5SyLIsy2WgJ/MYBQCg+LJRnVvPXXUAAImEEwBAovyucWrMbaTq0ljqCQAAeXFx\neLE1lnoCVJTGUk8AgG0RTlBOGks9ASpKY6knALVHOAFUqsZST4CK0ljqCVSH/B5HMDqPUQAAii/7\nfefWc1cdAEAi4QQAkCi/a5wW/Sm3obrcFw8t9QwAgArg4vCIyo4+ypcgB6g6wgmKRZBTLKIcSkY4\nAVQaUU6xiPLtyu9xBAX/RwYAKkOWdS4S3VUHAJBIOAEAJBJOAACJhBMAQCJ31QEApVchd/QJJ4Ad\nUSG/7IF8CKda5Zc9AHRYfuFkRwwAVDkXhwMAJBJOAACJhBMAQCLhBACQyF11ALWosdQTgMqUXzg1\n5jYSAEBZcqoOACCRcAIASCScAAASCScAgETCCQAgkXACAEiU2+MIjh11T15DAQAU2dhOreWIEwBA\nIuEEAJBIOAEAJBJOAACJhBMAQKLc7qo7L27Mayiq0Iw4t9RTAIAdlls4wbYIawDKi8cRAAAUlXAC\nAEgknAAAEgknAIBEwgkAIFFud9Wd2LQgr6GgJtw//KhSTwGADvI4AigR/9iA6uEfQrWjkGVZlstI\nTYVchgEAKLrhncsf1zgBACQSTgAAiYQTAECi/C4Ovya3karHRaWeAACQJ3fVFZOYpBQEO0DRCCeo\nNoKdUhDs1AjhBMCOE+yUQgmCPb/nOJ3uOU4AQIW43XOcAACKKrdweuqOD/4AAFSr3K9xEk/VaeT4\nUs8AAErPxeEkEcTVRwwDdJxwgholhquPGIbiE04AVUIMVx8xXH5yexzBUwWPIwAAKsPITuaPI04A\nQElU4hE14QRARajEnSzVRzgBVclOFigG4QRhJwtAGuHUCXayAFCbcgsnMQEAVDsf8gsAkMipOgCg\nPF1U6glsTTgBUDpluGOEbRFOQPHYKQJVRjixY+wYAaghuX1WXTT5rDoAoEIM71z+uKsOACCRcAIA\nSOQaJwCg4t0//KgOLX9iJ7cjnADotI7urKDSCSeoMHZUAKUjnBLZWQEAuYWTsAAAqp276gAAEgkn\nAIBEwgkAIJGLwwGq3Iw4t9RTgLLjOU7UFDsCAEoht3CyIwMAqp1rnAAAEgknAIBEwgkAIJFwAgBI\n5K46gBw8/OTYUk8B6IhRnVtNOLXBL0AAoC2FLMuyXAZ6Mo9RAACKL+vkESfXOAEAJBJOAACJhBMA\nQCLhBACQyF11QNsaSz0BgCL6fedWyy+cGnMbCQCgLDlVBwCQSDgBACQSTgAAiYQTAEAi4QQAkKgy\nH0ew6E+lngEAUNEO7dRa+X3Ib0HMAACVIcs6F05O1QEAJBJOAACJhBMAQCLhBACQSDgBACQSTgAA\niYQTAEAi4QQAkEg4AQAkEk4AAImEEwBAIuEEAJBo51JPAIAy8sXOffAp1ArhRPnyCxyAMpNfONnJ\nAQBVzjVOAACJhBMAQCLhBACQSDgBACQSTgAAiTyOAACoHI2l3bxwAoAPNZZ6ApQ74QTQVRpLPQFg\nRwknqCaNpZ4AQHUTTqRrLPUEAKC0yiucGks9AQCA9uUXTo25jQQAUJY8xwkAIJFwAgBIVF7XOAEA\nVevYUfeUegr/Y2yn1hJOAFAk5RUK5EE4AZALkUAtEE4AnSQUoPYIJyCJSAAQTtAuoQDAloQTrYQC\nAGxbzYaTSAAAOiq3cBIiAEC1q9kjTgBAvs6LG0s9hQ7wAEwAqDiVFRsIJwAqjtigVIQTQI0QG7Dj\nhBPAdggO4EPCCSgKsQFUI+EEZURsAJQ34UTFExsAdBXhVKPEBgDV6sSmBdtfaHjnxhZOHSQ4AKgV\nSQFSY7oknMQGALVKfFSXQpZlWR4D3R9H5zEMALRJgJCr4Z3LH6fqAGqI+IAdI5wAOkGAQG0STkDJ\niA+g0ggnqGHCBaBjhBOUESEDUN6EE2yHmAHgQ8KJiiNkACgV4UQuxAwAtUA4VSkhAwD/zzVtvHZ7\n54YSTl1EyABQ89oKmApT0+EkZgCoOVUQL6VUVuEkZACoGQKmIuX2Ib/RVMhlGADoUgKmNt3uQ34B\nqFTihQohnAD4/wQMbJNwAihHAgbKknAC2BYBA/wP4QSUP/EClAnhBKQTMECNE05QiQQMQEkIJ+gs\n8QJQc4QTlU/AANBFhBP5ETAAVDnhVI0EDAAUhXAqFvECAFWn+sNJwAAAOem6cBIwAECFyy+chBEA\nUOXqSj0BAIBKIZwAABJV/8XhAEDVeeqOHVt/5O2dW084AUCN2dHoqGXCCQASCQ6EEwBFJzioFsIJ\noIwJDigvwgmoSoIDKAbhBGxGcAC0TzhBTgQHQPUTTpQF0QFAJRBOFU5wAEDXqdlwEhwAQEcVsizL\n8hjoqUIhj2EAAIpuZCfzx4f8AgAkEk4AAImEEwBAIuEEAJBIOAEAJBJOAACJhBMAQCLhBACQSDgB\nACQSTgAAiYQTAEAi4QQAkEg4AQAkEk4AAImEEwBAIuEEAJBIOAEAJBJOAACJhBMAQCLhBACQSDgB\nACQSTgAAiYQTAEAi4QQAkEg4AQAkEk4AAImEEwBAIuEEAJBIOAEAJBJOAACJhBMAQCLhBACQSDgB\nACQSTgAAiYQTAEAi4QQAkEg4AQAkEk4AAImEEwBAIuEEAJBIOAEAJBJOAACJhBMAQCLhBACQSDgB\nACQSTgAAiYQTAEAi4QQAkEg4AQAkEk4AAImEEwBAIuEEAJBIOAEAJBJOAACJhBMAQCLhBACQSDgB\nACQSTgAAiYQTAEAi4QQAkKiQZVlW6kkAAFQCR5wAABIJJwCARMIJACCRcAIASCScAAASCScAgETC\nCQAgkXACAEgknAAAEgknAIBEwgkAIJFwAgBIJJwAABIJJwCARMIJACCRcAIASCScAAASCScAgETC\nCQAgkXACAEgknAAAEgknAIBEwgkAINH/Acd8GCUQEYlkAAAAAElFTkSuQmCC\n",
393 393 "text": [
394 394 "<matplotlib.figure.Figure at 0x10b00b350>"
395 395 ]
396 396 },
397 397 {
398 398 "output_type": "stream",
399 399 "stream": "stdout",
400 400 "text": [
401 401 "Simulation completed!\n",
402 402 "Monitored for: 0:00:01.229672.\n"
403 403 ]
404 404 }
405 405 ],
406 406 "prompt_number": 9
407 407 }
408 408 ],
409 409 "metadata": {}
410 410 }
411 411 ]
412 412 } No newline at end of file
@@ -1,543 +1,543 b''
1 1 {
2 2 "metadata": {
3 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 27 "import numpy as np\n",
28 28 "import matplotlib.pyplot as plt\n",
29 29 "\n",
30 30 "from IPython.display import display\n",
31 31 "from IPython.parallel import Client, error\n",
32 32 "\n",
33 33 "cluster = Client(profile=\"mpi\")\n",
34 34 "view = cluster[:]\n",
35 35 "view.block = True"
36 36 ],
37 37 "language": "python",
38 38 "metadata": {
39 39 "slideshow": {
40 40 "slide_start": false
41 41 }
42 42 },
43 43 "outputs": [],
44 44 "prompt_number": 1
45 45 },
46 46 {
47 47 "cell_type": "code",
48 48 "collapsed": false,
49 49 "input": [
50 50 "cluster.ids"
51 51 ],
52 52 "language": "python",
53 53 "metadata": {},
54 54 "outputs": [
55 55 {
56 56 "metadata": {},
57 57 "output_type": "pyout",
58 58 "prompt_number": 2,
59 59 "text": [
60 60 "[0, 1, 2, 3]"
61 61 ]
62 62 }
63 63 ],
64 64 "prompt_number": 2
65 65 },
66 66 {
67 67 "cell_type": "markdown",
68 68 "metadata": {
69 69 "slideshow": {
70 70 "slide_start": false
71 71 }
72 72 },
73 73 "source": [
74 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",
75 75 "\n",
76 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:"
77 77 ]
78 78 },
79 79 {
80 80 "cell_type": "code",
81 81 "collapsed": false,
82 82 "input": [
83 83 "%%px\n",
84 84 "# MPI initialization, library imports and sanity checks on all engines\n",
85 85 "from mpi4py import MPI\n",
86 86 "import numpy as np\n",
87 87 "import time\n",
88 88 "\n",
89 89 "mpi = MPI.COMM_WORLD\n",
90 90 "bcast = mpi.bcast\n",
91 91 "barrier = mpi.barrier\n",
92 92 "rank = mpi.rank\n",
93 93 "print \"MPI rank: %i/%i\" % (mpi.rank,mpi.size)"
94 94 ],
95 95 "language": "python",
96 96 "metadata": {
97 97 "slideshow": {
98 98 "slide_start": false
99 99 }
100 100 },
101 101 "outputs": [
102 102 {
103 103 "output_type": "stream",
104 104 "stream": "stdout",
105 105 "text": [
106 106 "[stdout:0] MPI rank: 3/4\n",
107 107 "[stdout:1] MPI rank: 2/4\n",
108 108 "[stdout:2] MPI rank: 0/4\n",
109 109 "[stdout:3] MPI rank: 1/4\n"
110 110 ]
111 111 }
112 112 ],
113 113 "prompt_number": 3
114 114 },
115 115 {
116 116 "cell_type": "markdown",
117 117 "metadata": {
118 118 "slideshow": {
119 119 "slide_start": false
120 120 }
121 121 },
122 122 "source": [
123 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:"
124 124 ]
125 125 },
126 126 {
127 127 "cell_type": "code",
128 128 "collapsed": false,
129 129 "input": [
130 130 "ranks = view['rank']\n",
131 131 "rank_indices = np.argsort(ranks)\n",
132 132 "\n",
133 133 "def mpi_order(seq):\n",
134 134 " \"\"\"Return elements of a sequence ordered by MPI rank.\n",
135 135 "\n",
136 136 " The input sequence is assumed to be ordered by engine ID.\"\"\"\n",
137 137 " return [seq[x] for x in rank_indices]"
138 138 ],
139 139 "language": "python",
140 140 "metadata": {
141 141 "slideshow": {
142 142 "slide_start": false
143 143 }
144 144 },
145 145 "outputs": [],
146 146 "prompt_number": 4
147 147 },
148 148 {
149 149 "cell_type": "heading",
150 150 "level": 2,
151 151 "metadata": {
152 152 "slideshow": {
153 153 "slide_start": false
154 154 }
155 155 },
156 156 "source": [
157 157 "MPI simulation example"
158 158 ]
159 159 },
160 160 {
161 161 "cell_type": "markdown",
162 162 "metadata": {
163 163 "slideshow": {
164 164 "slide_start": false
165 165 }
166 166 },
167 167 "source": [
168 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",
169 169 "\n",
170 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)."
171 171 ]
172 172 },
173 173 {
174 174 "cell_type": "code",
175 175 "collapsed": false,
176 176 "input": [
177 177 "%%px\n",
178 178 "\n",
179 179 "stop = False\n",
180 180 "nsteps = 100\n",
181 181 "delay = 0.1\n",
182 182 "\n",
183 183 "xmin, xmax = 0, np.pi\n",
184 184 "ymin, ymax = 0, 2*np.pi\n",
185 185 "dy = (ymax-ymin)/mpi.size\n",
186 186 "\n",
187 187 "def simulation():\n",
188 188 " \"\"\"Toy simulation code, computes sin(f*(x**2+y**2)) for a slowly increasing f\n",
189 189 " over an increasingly fine mesh.\n",
190 190 "\n",
191 191 " The purpose of this code is simply to illustrate the basic features of a typical\n",
192 192 " MPI code: spatial domain decomposition, a solution which is evolving in some \n",
193 193 " sense, and local per-node computation. In this case the nodes don't really\n",
194 194 " communicate at all.\n",
195 195 " \"\"\"\n",
196 196 " # By making these few variables global, we allow the IPython client to access them\n",
197 197 " # remotely for interactive introspection\n",
198 198 " global j, Z, nx, nyt\n",
199 199 " freqs = np.linspace(0.6, 1, nsteps)\n",
200 200 " for j in range(nsteps):\n",
201 201 " nx, ny = 2+j/4, 2+j/2/mpi.size\n",
202 202 " nyt = mpi.size*ny\n",
203 203 " Xax = np.linspace(xmin, xmax, nx)\n",
204 204 " Yax = np.linspace(ymin+rank*dy, ymin+(rank+1)*dy, ny, endpoint=rank==mpi.size)\n",
205 205 " X, Y = np.meshgrid(Xax, Yax)\n",
206 206 " f = freqs[j]\n",
207 207 " Z = np.cos(f*(X**2 + Y**2))\n",
208 208 " # We add a small delay to simulate that a real-world computation\n",
209 209 " # would take much longer, and we ensure all nodes are synchronized\n",
210 210 " time.sleep(delay)\n",
211 211 " # The stop flag can be set remotely via IPython, allowing the simulation to be\n",
212 212 " # cleanly stopped from the outside\n",
213 213 " if stop:\n",
214 214 " break"
215 215 ],
216 216 "language": "python",
217 217 "metadata": {
218 218 "slideshow": {
219 219 "slide_start": false
220 220 }
221 221 },
222 222 "outputs": [],
223 223 "prompt_number": 5
224 224 },
225 225 {
226 226 "cell_type": "heading",
227 227 "level": 2,
228 228 "metadata": {
229 229 "slideshow": {
230 230 "slide_start": false
231 231 }
232 232 },
233 233 "source": [
234 234 "IPython tools to interactively monitor and plot the MPI results"
235 235 ]
236 236 },
237 237 {
238 238 "cell_type": "markdown",
239 239 "metadata": {
240 240 "slideshow": {
241 241 "slide_start": false
242 242 }
243 243 },
244 244 "source": [
245 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:"
246 246 ]
247 247 },
248 248 {
249 249 "cell_type": "code",
250 250 "collapsed": false,
251 251 "input": [
252 252 "from IPython.display import clear_output\n",
253 253 "\n",
254 254 "def plot_current_results(in_place=True):\n",
255 255 " \"\"\"Makes a blocking call to retrieve remote data and displays the solution mesh\n",
256 256 " as a contour plot.\n",
257 257 " \n",
258 258 " Parameters\n",
259 259 " ----------\n",
260 260 " in_place : bool\n",
261 261 " By default it calls clear_output so that new plots replace old ones. Set\n",
262 262 " to False to allow keeping of all previous outputs.\n",
263 263 " \"\"\"\n",
264 264 " \n",
265 265 " # We make a blocking call to load the remote data from the simulation into simple named \n",
266 266 " # variables we can read from the engine namespaces\n",
267 267 " #view.apply_sync(load_simulation_globals)\n",
268 268 " # And now we can use the view to read these variables from all the engines. Then we\n",
269 269 " # concatenate all of them into single arrays for local plotting\n",
270 270 " try:\n",
271 271 " Z = np.concatenate(mpi_order(view['Z']))\n",
272 272 " except ValueError:\n",
273 273 " print \"dimension mismatch in Z, not plotting\"\n",
274 274 " ax = plt.gca()\n",
275 275 " return ax.figure\n",
276 276 " \n",
277 277 " nx, nyt, j, nsteps = view.pull(['nx', 'nyt', 'j', 'nsteps'], targets=0)\n",
278 278 " fig, ax = plt.subplots()\n",
279 279 " ax.contourf(Z)\n",
280 280 " ax.set_title('Mesh: %i x %i, step %i/%i' % (nx, nyt, j+1, nsteps))\n",
281 281 " plt.axis('off')\n",
282 282 " # We clear the notebook output before plotting this if in-place plot updating is requested\n",
283 283 " if in_place:\n",
284 " clear_output()\n",
284 " clear_output(wait=True)\n",
285 285 " display(fig)\n",
286 286 " return fig"
287 287 ],
288 288 "language": "python",
289 289 "metadata": {
290 290 "slideshow": {
291 291 "slide_start": false
292 292 }
293 293 },
294 294 "outputs": [],
295 295 "prompt_number": 6
296 296 },
297 297 {
298 298 "cell_type": "markdown",
299 299 "metadata": {
300 300 "slideshow": {
301 301 "slide_start": false
302 302 }
303 303 },
304 304 "source": [
305 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:"
306 306 ]
307 307 },
308 308 {
309 309 "cell_type": "code",
310 310 "collapsed": false,
311 311 "input": [
312 312 "def simulation_alive():\n",
313 313 " \"\"\"Return True if the simulation thread is still running on any engine.\n",
314 314 " \"\"\"\n",
315 315 " return any(view.apply_sync(lambda : simulation_thread.is_alive()))"
316 316 ],
317 317 "language": "python",
318 318 "metadata": {
319 319 "slideshow": {
320 320 "slide_start": false
321 321 }
322 322 },
323 323 "outputs": [],
324 324 "prompt_number": 7
325 325 },
326 326 {
327 327 "cell_type": "markdown",
328 328 "metadata": {
329 329 "slideshow": {
330 330 "slide_start": false
331 331 }
332 332 },
333 333 "source": [
334 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:"
335 335 ]
336 336 },
337 337 {
338 338 "cell_type": "code",
339 339 "collapsed": false,
340 340 "input": [
341 341 "def monitor_simulation(refresh=5.0, plots_in_place=True):\n",
342 342 " \"\"\"Monitor the simulation progress and call plotting routine.\n",
343 343 "\n",
344 344 " Supress KeyboardInterrupt exception if interrupted, ensure that the last \n",
345 345 " figure is always displayed and provide basic timing and simulation status.\n",
346 346 "\n",
347 347 " Parameters\n",
348 348 " ----------\n",
349 349 " refresh : float\n",
350 350 " Refresh interval between calls to retrieve and plot data. The default\n",
351 351 " is 5s, adjust depending on the desired refresh rate, but be aware that \n",
352 352 " very short intervals will start having a significant impact.\n",
353 353 "\n",
354 354 " plots_in_place : bool\n",
355 355 " If true, every new figure replaces the last one, producing a (slow)\n",
356 356 " animation effect in the notebook. If false, all frames are plotted\n",
357 357 " in sequence and appended in the output area.\n",
358 358 " \"\"\"\n",
359 359 " import datetime as dt, time\n",
360 360 " \n",
361 361 " if not simulation_alive():\n",
362 362 " plot_current_results(in_place=plots_in_place)\n",
363 363 " plt.close('all')\n",
364 364 " print 'Simulation has already finished, no monitoring to do.'\n",
365 365 " return\n",
366 366 " \n",
367 367 " t0 = dt.datetime.now()\n",
368 368 " fig = None\n",
369 369 " try:\n",
370 370 " while simulation_alive():\n",
371 371 " fig = plot_current_results(in_place=plots_in_place)\n",
372 372 " plt.close('all') # prevent re-plot of old figures\n",
373 373 " time.sleep(refresh) # so we don't hammer the server too fast\n",
374 374 " except (KeyboardInterrupt, error.TimeoutError):\n",
375 375 " msg = 'Monitoring interrupted, simulation is ongoing!'\n",
376 376 " else:\n",
377 377 " msg = 'Simulation completed!'\n",
378 378 " tmon = dt.datetime.now() - t0\n",
379 379 " if plots_in_place and fig is not None:\n",
380 " clear_output()\n",
380 " clear_output(wait=True)\n",
381 381 " plt.close('all')\n",
382 382 " display(fig)\n",
383 383 " print msg\n",
384 384 " print 'Monitored for: %s.' % tmon"
385 385 ],
386 386 "language": "python",
387 387 "metadata": {
388 388 "slideshow": {
389 389 "slide_start": false
390 390 }
391 391 },
392 392 "outputs": [],
393 393 "prompt_number": 8
394 394 },
395 395 {
396 396 "cell_type": "heading",
397 397 "level": 2,
398 398 "metadata": {
399 399 "slideshow": {
400 400 "slide_start": false
401 401 }
402 402 },
403 403 "source": [
404 404 "Making a simulation object that can be monitored interactively"
405 405 ]
406 406 },
407 407 {
408 408 "cell_type": "code",
409 409 "collapsed": false,
410 410 "input": [
411 411 "%%px\n",
412 412 "from threading import Thread\n",
413 413 "stop = False\n",
414 414 "nsteps = 100\n",
415 415 "delay=0.5\n",
416 416 "# Create a thread wrapper for the simulation. The target must be an argument-less\n",
417 417 "# function so we wrap the call to 'simulation' in a simple lambda:\n",
418 418 "simulation_thread = Thread(target = lambda : simulation())\n",
419 419 "# Now we actually start the simulation\n",
420 420 "simulation_thread.start()"
421 421 ],
422 422 "language": "python",
423 423 "metadata": {
424 424 "slideshow": {
425 425 "slide_start": false
426 426 }
427 427 },
428 428 "outputs": [],
429 429 "prompt_number": 9
430 430 },
431 431 {
432 432 "cell_type": "code",
433 433 "collapsed": false,
434 434 "input": [
435 435 "monitor_simulation(refresh=1);"
436 436 ],
437 437 "language": "python",
438 438 "metadata": {
439 439 "slideshow": {
440 440 "slide_start": false
441 441 }
442 442 },
443 443 "outputs": [
444 444 {
445 445 "metadata": {},
446 446 "output_type": "display_data",
447 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",
448 448 "text": [
449 449 "<matplotlib.figure.Figure at 0x1141a1450>"
450 450 ]
451 451 },
452 452 {
453 453 "output_type": "stream",
454 454 "stream": "stdout",
455 455 "text": [
456 456 "Simulation completed!\n",
457 457 "Monitored for: 0:00:50.653178.\n"
458 458 ]
459 459 }
460 460 ],
461 461 "prompt_number": 10
462 462 },
463 463 {
464 464 "cell_type": "markdown",
465 465 "metadata": {
466 466 "slideshow": {
467 467 "slide_start": false
468 468 }
469 469 },
470 470 "source": [
471 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:"
472 472 ]
473 473 },
474 474 {
475 475 "cell_type": "code",
476 476 "collapsed": false,
477 477 "input": [
478 478 "view['stop'] = True"
479 479 ],
480 480 "language": "python",
481 481 "metadata": {
482 482 "slideshow": {
483 483 "slide_start": false
484 484 }
485 485 },
486 486 "outputs": [],
487 487 "prompt_number": 11
488 488 },
489 489 {
490 490 "cell_type": "code",
491 491 "collapsed": false,
492 492 "input": [
493 493 "%%px --target 0\n",
494 494 "from IPython.parallel import bind_kernel; bind_kernel()\n",
495 495 "%connect_info"
496 496 ],
497 497 "language": "python",
498 498 "metadata": {},
499 499 "outputs": [
500 500 {
501 501 "output_type": "stream",
502 502 "stream": "stdout",
503 503 "text": [
504 504 "{\n",
505 505 " \"stdin_port\": 65310, \n",
506 506 " \"ip\": \"127.0.0.1\", \n",
507 507 " \"control_port\": 58188, \n",
508 508 " \"hb_port\": 58187, \n",
509 509 " \"key\": \"e4f5cda8-faa8-48d3-a62c-dbde67db9827\", \n",
510 510 " \"shell_port\": 65083, \n",
511 511 " \"transport\": \"tcp\", \n",
512 512 " \"iopub_port\": 54934\n",
513 513 "}\n",
514 514 "\n",
515 515 "Paste the above JSON into a file, and connect with:\n",
516 516 " $> ipython <app> --existing <file>\n",
517 517 "or, if you are local, you can connect with just:\n",
518 518 " $> ipython <app> --existing kernel-64604.json \n",
519 519 "or even just:\n",
520 520 " $> ipython <app> --existing \n",
521 521 "if this is the most recent IPython session you have started.\n"
522 522 ]
523 523 }
524 524 ],
525 525 "prompt_number": 12
526 526 },
527 527 {
528 528 "cell_type": "code",
529 529 "collapsed": false,
530 530 "input": [
531 531 "%%px --target 0\n",
532 532 "%qtconsole"
533 533 ],
534 534 "language": "python",
535 535 "metadata": {},
536 536 "outputs": [],
537 537 "prompt_number": 13
538 538 }
539 539 ],
540 540 "metadata": {}
541 541 }
542 542 ]
543 543 } No newline at end of file
General Comments 0
You need to be logged in to leave comments. Login now