##// END OF EJS Templates
Octave magic based on oct2py.
Stefan van der Walt -
Show More
@@ -0,0 +1,287 b''
1 # -*- coding: utf-8 -*-
2 """
3 ===========
4 octavemagic
5 ===========
6
7 Magic command interface for interactive work with Octave via oct2py
8
9 Usage
10 =====
11
12 ``%octave``
13
14 {OCTAVE_DOC}
15
16 ``%octave_push``
17
18 {OCTAVE_PUSH_DOC}
19
20 ``%octave_pull``
21
22 {OCTAVE_PULL_DOC}
23
24 """
25
26 #-----------------------------------------------------------------------------
27 # Copyright (C) 2012 The IPython Development Team
28 #
29 # Distributed under the terms of the BSD License. The full license is in
30 # the file COPYING, distributed as part of this software.
31 #-----------------------------------------------------------------------------
32
33 import sys
34 import tempfile
35 from glob import glob
36 from shutil import rmtree
37 from getopt import getopt
38
39 import numpy as np
40 import oct2py
41
42 from IPython.core.displaypub import publish_display_data
43 from IPython.core.magic import (Magics, magics_class, cell_magic, line_magic,
44 line_cell_magic)
45 from IPython.testing.skipdoctest import skip_doctest
46 from IPython.core.magic_arguments import (
47 argument, magic_arguments, parse_argstring
48 )
49 from IPython.utils.py3compat import unicode_to_str
50
51
52 class OctaveMagicError(oct2py.Oct2PyError):
53 pass
54
55
56 @magics_class
57 class OctaveMagics(Magics):
58 """A set of magics useful for interactive work with Octave via oct2py.
59 """
60 def __init__(self, shell):
61 """
62 Parameters
63 ----------
64
65 shell : IPython shell
66
67 """
68 super(OctaveMagics, self).__init__(shell)
69 self.oct = oct2py.Oct2Py()
70
71
72 @skip_doctest
73 @line_magic
74 def octave_push(self, line):
75 '''
76 Line-level magic that pushes a variable to Octave.
77
78 `line` should be made up of whitespace separated variable names in the
79 IPython namespace::
80
81 In [7]: import numpy as np
82
83 In [8]: X = np.arange(5)
84
85 In [9]: X.mean()
86 Out[9]: 2.0
87
88 In [10]: %octave_push X
89
90 In [11]: %octave mean(X)
91 Out[11]: 2.0
92
93 '''
94 inputs = line.split(' ')
95 for input in inputs:
96 self.oct.put(input, self.shell.user_ns[input])
97
98
99 @skip_doctest
100 @line_magic
101 def octave_pull(self, line):
102 '''
103 Line-level magic that pulls a variable from Octave.
104
105 In [18]: _ = %octave x = [1 2; 3 4]; y = 'hello'
106
107 In [19]: %octave_pull x y
108
109 In [20]: x
110 Out[20]:
111 array([[ 1., 2.],
112 [ 3., 4.]])
113
114 In [21]: y
115 Out[21]: 'hello'
116
117 '''
118 outputs = line.split(' ')
119 for output in outputs:
120 self.shell.push({output: self.oct.get(output)})
121
122
123 @skip_doctest
124 @magic_arguments()
125 @argument(
126 '-i', '--input', action='append',
127 help='Names of input variables to be pushed to Octave. Multiple names can be passed, separated by commas with no whitespace.'
128 )
129 @argument(
130 '-o', '--output', action='append',
131 help='Names of variables to be pulled from Octave after executing cell body. Multiple names can be passed, separated by commas with no whitespace.'
132 )
133 @argument(
134 'code',
135 nargs='*',
136 )
137 @line_cell_magic
138 def octave(self, line, cell=None):
139 '''
140 Execute code in Octave, and pull some of the results back into the
141 Python namespace.
142
143 In [9]: %octave X = [1 2; 3 4]; mean(X)
144 Out[9]: array([[ 2., 3.]])
145
146 As a cell, this will run a block of Octave code, without returning any
147 value::
148
149 In [10]: %%octave
150 ....: p = [-2, -1, 0, 1, 2]
151 ....: polyout(p, 'x')
152
153 -2*x^4 - 1*x^3 + 0*x^2 + 1*x^1 + 2
154
155 In the notebook, plots are published as the output of the cell, e.g.
156
157 %octave plot([1 2 3], [4 5 6])
158
159 will create a line plot.
160
161 Objects can be passed back and forth between Octave and IPython via the
162 -i and -o flags in line::
163
164 In [14]: Z = np.array([1, 4, 5, 10])
165
166 In [15]: %octave -i Z mean(Z)
167 Out[15]: array([ 5.])
168
169
170 In [16]: %octave -o W W = Z * mean(Z)
171 Out[16]: array([ 5., 20., 25., 50.])
172
173 In [17]: W
174 Out[17]: array([ 5., 20., 25., 50.])
175
176 '''
177 args = parse_argstring(self.octave, line)
178
179 # arguments 'code' in line are prepended to the cell lines
180 if not cell:
181 code = ''
182 return_output = True
183 line_mode = True
184 else:
185 code = cell
186 return_output = False
187 line_mode = False
188
189 code = ' '.join(args.code) + code
190
191 if args.input:
192 for input in ','.join(args.input).split(','):
193 self.oct.put(input, self.shell.user_ns[input])
194
195 # generate plots in a temporary directory
196 plot_dir = tempfile.mkdtemp()
197
198 pre_call = '''
199 global __ipy_figures = [];
200 page_screen_output(0);
201
202 function fig_create(src, event)
203 global __ipy_figures;
204 __ipy_figures(size(__ipy_figures) + 1) = src;
205 end
206
207 set(0, 'DefaultFigureCreateFcn', @fig_create);
208
209 clear ans;
210 '''
211
212 post_call = '''
213
214 # Save output of the last execution
215 if exist("ans") == 1
216 _ = ans;
217 else
218 _ = nan;
219 end
220
221 for f = __ipy_figures
222 outfile = sprintf('%s/__ipy_oct_fig_%%03d.png', f)
223 print(f, outfile, '-dpng')
224 end
225
226 ''' % plot_dir
227
228 code = ' '.join((pre_call, code, post_call))
229 try:
230 text_output = self.oct.run(code, verbose=False)
231 except (oct2py.Oct2PyError) as exception:
232 raise OctaveMagicError('Octave could not complete execution. '
233 'Traceback (currently broken in oct2py): %s'
234 % exception.message)
235
236 key = 'OctaveMagic.Octave'
237 display_data = []
238
239 # Publish text output
240 if text_output:
241 display_data.append((key, {'text/plain': text_output}))
242
243 # Publish images
244 fmt = 'png'
245 mimetypes = {'png' : 'image/png',
246 'svg' : 'image/svg+xml'}
247 mime = mimetypes[fmt]
248
249 images = [open(imgfile, 'rb').read() for imgfile in \
250 glob("%s/*.png" % plot_dir)]
251 rmtree(plot_dir)
252
253 for image in images:
254 display_data.append((key, {mime: image}))
255
256 if args.output:
257 for output in ','.join(args.output).split(','):
258 output = unicode_to_str(output)
259 self.shell.push({output: self.oct.get(output)})
260
261 for tag, data in display_data:
262 publish_display_data(tag, data)
263
264 if return_output:
265 ans = self.oct.get('_')
266
267 # Unfortunately, Octave doesn't have a "None" object,
268 # so we can't return any NaN outputs
269 if np.isnan(np.nan):
270 ans = None
271
272 return ans
273
274 __doc__ = __doc__.format(
275 OCTAVE_DOC = ' '*8 + OctaveMagics.octave.__doc__,
276 OCTAVE_PUSH_DOC = ' '*8 + OctaveMagics.octave_push.__doc__,
277 OCTAVE_PULL_DOC = ' '*8 + OctaveMagics.octave_pull.__doc__
278 )
279
280
281 _loaded = False
282 def load_ipython_extension(ip):
283 """Load the extension in IPython."""
284 global _loaded
285 if not _loaded:
286 ip.register_magics(OctaveMagics)
287 _loaded = True
General Comments 0
You need to be logged in to leave comments. Login now