##// END OF EJS Templates
update parallel magics...
MinRK -
Show More
@@ -32,6 +32,7 b' import zmq'
32 32
33 33 from IPython.config.configurable import MultipleInstanceError
34 34 from IPython.core.application import BaseIPythonApplication
35 from IPython.core.profiledir import ProfileDir, ProfileDirError
35 36
36 37 from IPython.utils.coloransi import TermColors
37 38 from IPython.utils.jsonutil import rekey
@@ -50,7 +51,6 b' from IPython.parallel import util'
50 51 from IPython.zmq.session import Session, Message
51 52
52 53 from .asyncresult import AsyncResult, AsyncHubResult
53 from IPython.core.profiledir import ProfileDir, ProfileDirError
54 54 from .view import DirectView, LoadBalancedView
55 55
56 56 if sys.version_info[0] >= 3:
@@ -480,6 +480,18 b' class Client(HasTraits):'
480 480 self._queue_handlers = {'execute_reply' : self._handle_execute_reply,
481 481 'apply_reply' : self._handle_apply_reply}
482 482 self._connect(sshserver, ssh_kwargs, timeout)
483
484 # last step: setup magics, if we are in IPython:
485
486 try:
487 ip = get_ipython()
488 except NameError:
489 return
490 else:
491 if 'px' not in ip.magics_manager.magics:
492 # in IPython but we are the first Client.
493 # activate a default view for parallel magics.
494 self.activate()
483 495
484 496 def __del__(self):
485 497 """cleanup sockets, but _not_ context."""
@@ -905,6 +917,29 b' class Client(HasTraits):'
905 917 # always copy:
906 918 return list(self._ids)
907 919
920 def activate(self, targets='all', suffix=''):
921 """Create a DirectView and register it with IPython magics
922
923 Defines the magics `%px, %autopx, %pxresult, %%px`
924
925 Parameters
926 ----------
927
928 targets: int, list of ints, or 'all'
929 The engines on which the view's magics will run
930 suffix: str [default: '']
931 The suffix, if any, for the magics. This allows you to have
932 multiple views associated with parallel magics at the same time.
933
934 e.g. ``rc.activate(targets=0, suffix='0')`` will give you
935 the magics ``%px0``, ``%pxresult0``, etc. for running magics just
936 on engine 0.
937 """
938 view = self.direct_view(targets)
939 view.block = True
940 view.activate(suffix)
941 return view
942
908 943 def close(self):
909 944 if self._closed:
910 945 return
@@ -17,10 +17,14 b' Usage'
17 17
18 18 {PX_DOC}
19 19
20 ``%result``
20 ``%pxresult``
21 21
22 22 {RESULT_DOC}
23 23
24 ``%pxconfig``
25
26 {CONFIG_DOC}
27
24 28 """
25 29
26 30 #-----------------------------------------------------------------------------
@@ -38,7 +42,8 b' import ast'
38 42 import re
39 43
40 44 from IPython.core.error import UsageError
41 from IPython.core.magic import Magics, magics_class, line_magic, cell_magic
45 from IPython.core.magic import Magics
46 from IPython.core import magic_arguments
42 47 from IPython.testing.skipdoctest import skip_doctest
43 48
44 49 #-----------------------------------------------------------------------------
@@ -46,95 +51,164 b' from IPython.testing.skipdoctest import skip_doctest'
46 51 #-----------------------------------------------------------------------------
47 52
48 53
49 NO_ACTIVE_VIEW = "Use activate() on a DirectView object to use it with magics."
50 NO_LAST_RESULT = "%result recalls last %px result, which has not yet been used."
54 NO_LAST_RESULT = "%pxresult recalls last %px result, which has not yet been used."
51 55
56 def exec_args(f):
57 """decorator for adding block/targets args for execution
58
59 applied to %pxconfig and %%px
60 """
61 args = [
62 magic_arguments.argument('-b', '--block', action="store_const",
63 const=True, dest='block',
64 help="use blocking (sync) execution"
65 ),
66 magic_arguments.argument('-a', '--noblock', action="store_const",
67 const=False, dest='block',
68 help="use non-blocking (async) execution"
69 ),
70 magic_arguments.argument('-t', '--targets', type=str,
71 help="specify the targets on which to execute"
72 ),
73 ]
74 for a in args:
75 f = a(f)
76 return f
77
78 def output_args(f):
79 """decorator for output-formatting args
80
81 applied to %pxresult and %%px
82 """
83 args = [
84 magic_arguments.argument('-r', action="store_const", dest='groupby',
85 const='order',
86 help="collate outputs in order (same as group-outputs=order)"
87 ),
88 magic_arguments.argument('-e', action="store_const", dest='groupby',
89 const='engine',
90 help="group outputs by engine (same as group-outputs=engine)"
91 ),
92 magic_arguments.argument('--group-outputs', dest='groupby', type=str,
93 choices=['engine', 'order', 'type'], default='type',
94 help="""Group the outputs in a particular way.
95
96 Choices are:
97
98 type: group outputs of all engines by type (stdout, stderr, displaypub, etc.).
99
100 engine: display all output for each engine together.
101
102 order: like type, but individual displaypub output from each engine is collated.
103 For example, if multiple plots are generated by each engine, the first
104 figure of each engine will be displayed, then the second of each, etc.
105 """
106 ),
107 magic_arguments.argument('-o', '--out', dest='save_name', type=str,
108 help="""store the AsyncResult object for this computation
109 in the global namespace under this name.
110 """
111 ),
112 ]
113 for a in args:
114 f = a(f)
115 return f
52 116
53 @magics_class
54 117 class ParallelMagics(Magics):
55 118 """A set of magics useful when controlling a parallel IPython cluster.
56 119 """
57 120
121 # magic-related
122 magics = None
123 registered = True
124
125 # suffix for magics
126 suffix = ''
58 127 # A flag showing if autopx is activated or not
59 128 _autopx = False
60 129 # the current view used by the magics:
61 active_view = None
62 # last result cache for %result
130 view = None
131 # last result cache for %pxresult
63 132 last_result = None
64 133
65 @skip_doctest
66 @line_magic
67 def result(self, line=''):
68 """Print the result of the last asynchronous %px command.
69
70 Usage:
134 def __init__(self, shell, view, suffix=''):
135 self.view = view
136 self.suffix = suffix
71 137
72 %result [-o] [-e] [--group-options=type|engine|order]
138 # register magics
139 self.magics = dict(cell={},line={})
140 line_magics = self.magics['line']
73 141
74 Options:
142 px = 'px' + suffix
143 if not suffix:
144 # keep %result for legacy compatibility
145 line_magics['result'] = self.result
75 146
76 -o: collate outputs in order (same as group-outputs=order)
147 line_magics['pxresult' + suffix] = self.result
148 line_magics[px] = self.px
149 line_magics['pxconfig' + suffix] = self.pxconfig
150 line_magics['auto' + px] = self.autopx
77 151
78 -e: group outputs by engine (same as group-outputs=engine)
152 self.magics['cell'][px] = self.cell_px
79 153
80 --group-outputs=type [default behavior]:
81 each output type (stdout, stderr, displaypub) for all engines
82 displayed together.
83
84 --group-outputs=order:
85 The same as 'type', but individual displaypub outputs (e.g. plots)
86 will be interleaved, so it will display all of the first plots,
87 then all of the second plots, etc.
88
89 --group-outputs=engine:
90 All of an engine's output is displayed before moving on to the next.
91
92 To use this a :class:`DirectView` instance must be created
93 and then activated by calling its :meth:`activate` method.
154 super(ParallelMagics, self).__init__(shell=shell)
155
156 def _eval_target_str(self, ts):
157 if ':' in ts:
158 targets = eval("self.view.client.ids[%s]" % ts)
159 elif 'all' in ts:
160 targets = 'all'
161 else:
162 targets = eval(ts)
163 return targets
164
165 @magic_arguments.magic_arguments()
166 @exec_args
167 def pxconfig(self, line):
168 """configure default targets/blocking for %px magics"""
169 args = magic_arguments.parse_argstring(self.pxconfig, line)
170 if args.targets:
171 self.view.targets = self._eval_target_str(args.targets)
172 if args.block is not None:
173 self.view.block = args.block
174
175 @magic_arguments.magic_arguments()
176 @output_args
177 @skip_doctest
178 def result(self, line=''):
179 """Print the result of the last asynchronous %px command.
94 180
95 181 This lets you recall the results of %px computations after
96 asynchronous submission (view.block=False).
182 asynchronous submission (block=False).
97 183
98 Then you can do the following::
184 Examples
185 --------
186 ::
99 187
100 188 In [23]: %px os.getpid()
101 189 Async parallel execution on engine(s): all
102 190
103 In [24]: %result
191 In [24]: %pxresult
104 192 [ 8] Out[10]: 60920
105 193 [ 9] Out[10]: 60921
106 194 [10] Out[10]: 60922
107 195 [11] Out[10]: 60923
108 196 """
109 opts, _ = self.parse_options(line, 'oe', 'group-outputs=')
110
111 if 'group-outputs' in opts:
112 groupby = opts['group-outputs']
113 elif 'o' in opts:
114 groupby = 'order'
115 elif 'e' in opts:
116 groupby = 'engine'
117 else:
118 groupby = 'type'
119
120 if self.active_view is None:
121 raise UsageError(NO_ACTIVE_VIEW)
197 args = magic_arguments.parse_argstring(self.result, line)
122 198
123 199 if self.last_result is None:
124 200 raise UsageError(NO_LAST_RESULT)
125 201
126 202 self.last_result.get()
127 self.last_result.display_outputs(groupby=groupby)
203 self.last_result.display_outputs(groupby=args.groupby)
128 204
129 205 @skip_doctest
130 @line_magic
131 def px(self, parameter_s=''):
206 def px(self, line=''):
132 207 """Executes the given python command in parallel.
133 208
134 To use this a :class:`DirectView` instance must be created
135 and then activated by calling its :meth:`activate` method.
136
137 Then you can do the following::
209 Examples
210 --------
211 ::
138 212
139 213 In [24]: %px a = os.getpid()
140 214 Parallel execution on engine(s): all
@@ -145,27 +219,24 b' class ParallelMagics(Magics):'
145 219 [stdout:2] 1236
146 220 [stdout:3] 1237
147 221 """
148 return self.parallel_execute(parameter_s)
222 return self.parallel_execute(line)
149 223
150 224 def parallel_execute(self, cell, block=None, groupby='type', save_name=None):
151 225 """implementation used by %px and %%parallel"""
152 226
153 if self.active_view is None:
154 raise UsageError(NO_ACTIVE_VIEW)
155
156 227 # defaults:
157 block = self.active_view.block if block is None else block
228 block = self.view.block if block is None else block
158 229
159 230 base = "Parallel" if block else "Async parallel"
160 231
161 targets = self.active_view.targets
232 targets = self.view.targets
162 233 if isinstance(targets, list) and len(targets) > 10:
163 234 str_targets = str(targets[:4])[:-1] + ', ..., ' + str(targets[-4:])[1:]
164 235 else:
165 236 str_targets = str(targets)
166 237 print base + " execution on engine(s): %s" % str_targets
167 238
168 result = self.active_view.execute(cell, silent=False, block=False)
239 result = self.view.execute(cell, silent=False, block=False)
169 240 self.last_result = result
170 241
171 242 if save_name:
@@ -178,45 +249,16 b' class ParallelMagics(Magics):'
178 249 # return AsyncResult only on non-blocking submission
179 250 return result
180 251
252 @magic_arguments.magic_arguments()
253 @exec_args
254 @output_args
181 255 @skip_doctest
182 @cell_magic('px')
183 256 def cell_px(self, line='', cell=None):
184 """Executes the given python command in parallel.
185
186 Cell magic usage:
187
188 %%px [-o] [-e] [--group-options=type|engine|order] [--[no]block] [--out name]
189
190 Options:
191
192 --out <name>: store the AsyncResult object for this computation
193 in the global namespace.
194
195 -o: collate outputs in order (same as group-outputs=order)
196
197 -e: group outputs by engine (same as group-outputs=engine)
198
199 --group-outputs=type [default behavior]:
200 each output type (stdout, stderr, displaypub) for all engines
201 displayed together.
202
203 --group-outputs=order:
204 The same as 'type', but individual displaypub outputs (e.g. plots)
205 will be interleaved, so it will display all of the first plots,
206 then all of the second plots, etc.
207
208 --group-outputs=engine:
209 All of an engine's output is displayed before moving on to the next.
210
211 --[no]block:
212 Whether or not to block for the execution to complete
213 (and display the results). If unspecified, the active view's
257 """Executes the cell in parallel.
214 258
215
216 To use this a :class:`DirectView` instance must be created
217 and then activated by calling its :meth:`activate` method.
218
219 Then you can do the following::
259 Examples
260 --------
261 ::
220 262
221 263 In [24]: %%px --noblock
222 264 ....: a = os.getpid()
@@ -230,38 +272,29 b' class ParallelMagics(Magics):'
230 272 [stdout:3] 1237
231 273 """
232 274
233 block = None
234 groupby = 'type'
235 # as a cell magic, we accept args
236 opts, _ = self.parse_options(line, 'oe', 'group-outputs=', 'out=', 'block', 'noblock')
237
238 if 'group-outputs' in opts:
239 groupby = opts['group-outputs']
240 elif 'o' in opts:
241 groupby = 'order'
242 elif 'e' in opts:
243 groupby = 'engine'
244
245 if 'block' in opts:
246 block = True
247 elif 'noblock' in opts:
248 block = False
249
250 save_name = opts.get('out')
251
252 return self.parallel_execute(cell, block=block, groupby=groupby, save_name=save_name)
253
275 args = magic_arguments.parse_argstring(self.cell_px, line)
276
277 if args.targets:
278 save_targets = self.view.targets
279 self.view.targets = self._eval_target_str(args.targets)
280 try:
281 return self.parallel_execute(cell, block=args.block,
282 groupby=args.groupby,
283 save_name=args.save_name,
284 )
285 finally:
286 if args.targets:
287 self.view.targets = save_targets
288
254 289 @skip_doctest
255 @line_magic
256 def autopx(self, parameter_s=''):
290 def autopx(self, line=''):
257 291 """Toggles auto parallel mode.
258 292
259 To use this a :class:`DirectView` instance must be created
260 and then activated by calling its :meth:`activate` method. Once this
261 is called, all commands typed at the command line are send to
262 the engines to be executed in parallel. To control which engine
263 are used, set the ``targets`` attributed of the multiengine client
264 before entering ``%autopx`` mode.
293 Once this is called, all commands typed at the command line are send to
294 the engines to be executed in parallel. To control which engine are
295 used, the ``targets`` attribute of the view before
296 entering ``%autopx`` mode.
297
265 298
266 299 Then you can do the following::
267 300
@@ -290,9 +323,6 b' class ParallelMagics(Magics):'
290 323 """Enable %autopx mode by saving the original run_cell and installing
291 324 pxrun_cell.
292 325 """
293 if self.active_view is None:
294 raise UsageError(NO_ACTIVE_VIEW)
295
296 326 # override run_cell
297 327 self._original_run_cell = self.shell.run_cell
298 328 self.shell.run_cell = self.pxrun_cell
@@ -356,12 +386,12 b' class ParallelMagics(Magics):'
356 386 return False
357 387 else:
358 388 try:
359 result = self.active_view.execute(cell, silent=False, block=False)
389 result = self.view.execute(cell, silent=False, block=False)
360 390 except:
361 391 ipself.showtraceback()
362 392 return True
363 393 else:
364 if self.active_view.block:
394 if self.view.block:
365 395 try:
366 396 result.get()
367 397 except:
@@ -376,15 +406,6 b' class ParallelMagics(Magics):'
376 406 __doc__ = __doc__.format(
377 407 AUTOPX_DOC = ' '*8 + ParallelMagics.autopx.__doc__,
378 408 PX_DOC = ' '*8 + ParallelMagics.px.__doc__,
379 RESULT_DOC = ' '*8 + ParallelMagics.result.__doc__
409 RESULT_DOC = ' '*8 + ParallelMagics.result.__doc__,
410 CONFIG_DOC = ' '*8 + ParallelMagics.pxconfig.__doc__,
380 411 )
381
382 _loaded = False
383
384
385 def load_ipython_extension(ip):
386 """Load the extension in IPython."""
387 global _loaded
388 if not _loaded:
389 ip.register_magics(ParallelMagics)
390 _loaded = True
@@ -792,33 +792,37 b' class DirectView(View):'
792 792 return self.client.kill(targets=targets, block=block)
793 793
794 794 #----------------------------------------
795 # activate for %px,%autopx magics
795 # activate for %px, %autopx, etc. magics
796 796 #----------------------------------------
797 def activate(self):
798 """Make this `View` active for parallel magic commands.
799 797
800 IPython has a magic command syntax to work with `MultiEngineClient` objects.
801 In a given IPython session there is a single active one. While
802 there can be many `Views` created and used by the user,
803 there is only one active one. The active `View` is used whenever
804 the magic commands %px and %autopx are used.
805
806 The activate() method is called on a given `View` to make it
807 active. Once this has been done, the magic commands can be used.
798 def activate(self, suffix=''):
799 """Activate IPython magics associated with this View
800
801 Defines the magics `%px, %autopx, %pxresult, %%px, %pxconfig`
802
803 Parameters
804 ----------
805
806 suffix: str [default: '']
807 The suffix, if any, for the magics. This allows you to have
808 multiple views associated with parallel magics at the same time.
809
810 e.g. ``rc[::2].activate(suffix='_even')`` will give you
811 the magics ``%px_even``, ``%pxresult_even``, etc. for running magics
812 on the even engines.
808 813 """
809
814
815 from IPython.parallel.client.magics import ParallelMagics
816
810 817 try:
811 818 # This is injected into __builtins__.
812 819 ip = get_ipython()
813 820 except NameError:
814 print "The IPython parallel magics (%result, %px, %autopx) only work within IPython."
815 else:
816 pmagic = ip.magics_manager.registry.get('ParallelMagics')
817 if pmagic is None:
818 ip.magic('load_ext parallelmagic')
819 pmagic = ip.magics_manager.registry.get('ParallelMagics')
820
821 pmagic.active_view = self
821 print "The IPython parallel magics (%px, etc.) only work within IPython."
822 return
823
824 M = ParallelMagics(ip, self, suffix)
825 ip.magics_manager.register(M)
822 826
823 827
824 828 @skip_doctest
@@ -24,6 +24,7 b' from tempfile import mktemp'
24 24
25 25 import zmq
26 26
27 from IPython import parallel
27 28 from IPython.parallel.client import client as clientmod
28 29 from IPython.parallel import error
29 30 from IPython.parallel import AsyncResult, AsyncHubResult
@@ -420,3 +421,16 b' class TestClient(ClusterTestCase):'
420 421 "Shouldn't be spinning, but got wall_time=%f" % ar.wall_time
421 422 )
422 423
424 def test_activate(self):
425 ip = get_ipython()
426 magics = ip.magics_manager.magics
427 self.assertTrue('px' in magics['line'])
428 self.assertTrue('px' in magics['cell'])
429 v0 = self.client.activate(-1, '0')
430 self.assertTrue('px0' in magics['line'])
431 self.assertTrue('px0' in magics['cell'])
432 self.assertEquals(v0.targets, self.client.ids[-1])
433 v0 = self.client.activate('all', 'all')
434 self.assertTrue('pxall' in magics['line'])
435 self.assertTrue('pxall' in magics['cell'])
436 self.assertEquals(v0.targets, 'all')
@@ -301,20 +301,13 b' class TestParallelMagics(ClusterTestCase, ParametricTestCase):'
301 301 data = dict(a=111,b=222)
302 302 v.push(data, block=True)
303 303
304 ip.magic('px a')
305 ip.magic('px b')
306 for idx, name in [
307 ('', 'b'),
308 ('-1', 'b'),
309 ('2', 'b'),
310 ('1', 'a'),
311 ('-2', 'a'),
312 ]:
304 for name in ('a', 'b'):
305 ip.magic('px ' + name)
313 306 with capture_output() as io:
314 ip.magic('result ' + idx)
307 ip.magic('pxresult')
315 308 output = io.stdout
316 309 msg = "expected %s output to include %s, but got: %s" % \
317 ('%result '+idx, str(data[name]), output)
310 ('%pxresult', str(data[name]), output)
318 311 self.assertTrue(str(data[name]) in output, msg)
319 312
320 313 @dec.skipif_not_matplotlib
@@ -336,5 +329,17 b' class TestParallelMagics(ClusterTestCase, ParametricTestCase):'
336 329
337 330 self.assertTrue('Out[' in io.stdout, io.stdout)
338 331 self.assertTrue('matplotlib.lines' in io.stdout, io.stdout)
332
333 def test_pxconfig(self):
334 ip = get_ipython()
335 rc = self.client
336 v = rc.activate(-1, '_tst')
337 self.assertEquals(v.targets, rc.ids[-1])
338 ip.magic("%pxconfig_tst -t :")
339 self.assertEquals(v.targets, rc.ids)
340 ip.magic("%pxconfig_tst --block")
341 self.assertEquals(v.block, True)
342 ip.magic("%pxconfig_tst --noblock")
343 self.assertEquals(v.block, False)
339 344
340 345
General Comments 0
You need to be logged in to leave comments. Login now