##// END OF EJS Templates
Document OpenMP in %%cython magic
Bradley M. Froehle -
Show More
@@ -1,279 +1,285
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 Cython related magics.
3 Cython related magics.
4
4
5 Author:
5 Author:
6 * Brian Granger
6 * Brian Granger
7
7
8 Parts of this code were taken from Cython.inline.
8 Parts of this code were taken from Cython.inline.
9 """
9 """
10 #-----------------------------------------------------------------------------
10 #-----------------------------------------------------------------------------
11 # Copyright (C) 2010-2011, IPython Development Team.
11 # Copyright (C) 2010-2011, IPython Development Team.
12 #
12 #
13 # Distributed under the terms of the Modified BSD License.
13 # Distributed under the terms of the Modified BSD License.
14 #
14 #
15 # The full license is in the file COPYING.txt, distributed with this software.
15 # The full license is in the file COPYING.txt, distributed with this software.
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17
17
18 from __future__ import print_function
18 from __future__ import print_function
19
19
20 import imp
20 import imp
21 import io
21 import io
22 import os
22 import os
23 import re
23 import re
24 import sys
24 import sys
25 import time
25 import time
26
26
27 try:
27 try:
28 import hashlib
28 import hashlib
29 except ImportError:
29 except ImportError:
30 import md5 as hashlib
30 import md5 as hashlib
31
31
32 from distutils.core import Distribution, Extension
32 from distutils.core import Distribution, Extension
33 from distutils.command.build_ext import build_ext
33 from distutils.command.build_ext import build_ext
34
34
35 from IPython.core import display
35 from IPython.core import display
36 from IPython.core import magic_arguments
36 from IPython.core import magic_arguments
37 from IPython.core.magic import Magics, magics_class, cell_magic
37 from IPython.core.magic import Magics, magics_class, cell_magic
38 from IPython.testing.skipdoctest import skip_doctest
38 from IPython.testing.skipdoctest import skip_doctest
39 from IPython.utils import py3compat
39 from IPython.utils import py3compat
40
40
41 import Cython
41 import Cython
42 from Cython.Compiler.Errors import CompileError
42 from Cython.Compiler.Errors import CompileError
43 from Cython.Build.Dependencies import cythonize
43 from Cython.Build.Dependencies import cythonize
44
44
45
45
46 @magics_class
46 @magics_class
47 class CythonMagics(Magics):
47 class CythonMagics(Magics):
48
48
49 def __init__(self, shell):
49 def __init__(self, shell):
50 super(CythonMagics,self).__init__(shell)
50 super(CythonMagics,self).__init__(shell)
51 self._reloads = {}
51 self._reloads = {}
52 self._code_cache = {}
52 self._code_cache = {}
53
53
54 def _import_all(self, module):
54 def _import_all(self, module):
55 for k,v in module.__dict__.items():
55 for k,v in module.__dict__.items():
56 if not k.startswith('__'):
56 if not k.startswith('__'):
57 self.shell.push({k:v})
57 self.shell.push({k:v})
58
58
59 @cell_magic
59 @cell_magic
60 def cython_inline(self, line, cell):
60 def cython_inline(self, line, cell):
61 """Compile and run a Cython code cell using Cython.inline.
61 """Compile and run a Cython code cell using Cython.inline.
62
62
63 This magic simply passes the body of the cell to Cython.inline
63 This magic simply passes the body of the cell to Cython.inline
64 and returns the result. If the variables `a` and `b` are defined
64 and returns the result. If the variables `a` and `b` are defined
65 in the user's namespace, here is a simple example that returns
65 in the user's namespace, here is a simple example that returns
66 their sum::
66 their sum::
67
67
68 %%cython_inline
68 %%cython_inline
69 return a+b
69 return a+b
70
70
71 For most purposes, we recommend the usage of the `%%cython` magic.
71 For most purposes, we recommend the usage of the `%%cython` magic.
72 """
72 """
73 locs = self.shell.user_global_ns
73 locs = self.shell.user_global_ns
74 globs = self.shell.user_ns
74 globs = self.shell.user_ns
75 return Cython.inline(cell, locals=locs, globals=globs)
75 return Cython.inline(cell, locals=locs, globals=globs)
76
76
77 @cell_magic
77 @cell_magic
78 def cython_pyximport(self, line, cell):
78 def cython_pyximport(self, line, cell):
79 """Compile and import a Cython code cell using pyximport.
79 """Compile and import a Cython code cell using pyximport.
80
80
81 The contents of the cell are written to a `.pyx` file in the current
81 The contents of the cell are written to a `.pyx` file in the current
82 working directory, which is then imported using `pyximport`. This
82 working directory, which is then imported using `pyximport`. This
83 magic requires a module name to be passed::
83 magic requires a module name to be passed::
84
84
85 %%cython_pyximport modulename
85 %%cython_pyximport modulename
86 def f(x):
86 def f(x):
87 return 2.0*x
87 return 2.0*x
88
88
89 The compiled module is then imported and all of its symbols are
89 The compiled module is then imported and all of its symbols are
90 injected into the user's namespace. For most purposes, we recommend
90 injected into the user's namespace. For most purposes, we recommend
91 the usage of the `%%cython` magic.
91 the usage of the `%%cython` magic.
92 """
92 """
93 module_name = line.strip()
93 module_name = line.strip()
94 if not module_name:
94 if not module_name:
95 raise ValueError('module name must be given')
95 raise ValueError('module name must be given')
96 fname = module_name + '.pyx'
96 fname = module_name + '.pyx'
97 with io.open(fname, 'w', encoding='utf-8') as f:
97 with io.open(fname, 'w', encoding='utf-8') as f:
98 f.write(cell)
98 f.write(cell)
99 if 'pyximport' not in sys.modules:
99 if 'pyximport' not in sys.modules:
100 import pyximport
100 import pyximport
101 pyximport.install(reload_support=True)
101 pyximport.install(reload_support=True)
102 if module_name in self._reloads:
102 if module_name in self._reloads:
103 module = self._reloads[module_name]
103 module = self._reloads[module_name]
104 reload(module)
104 reload(module)
105 else:
105 else:
106 __import__(module_name)
106 __import__(module_name)
107 module = sys.modules[module_name]
107 module = sys.modules[module_name]
108 self._reloads[module_name] = module
108 self._reloads[module_name] = module
109 self._import_all(module)
109 self._import_all(module)
110
110
111 @magic_arguments.magic_arguments()
111 @magic_arguments.magic_arguments()
112 @magic_arguments.argument(
112 @magic_arguments.argument(
113 '-c', '--compile-args', action='append', default=[],
113 '-c', '--compile-args', action='append', default=[],
114 help="Extra flags to pass to compiler via the `extra_compile_args` "
114 help="Extra flags to pass to compiler via the `extra_compile_args` "
115 "Extension flag (can be specified multiple times)."
115 "Extension flag (can be specified multiple times)."
116 )
116 )
117 @magic_arguments.argument(
117 @magic_arguments.argument(
118 '-la', '--link-args', action='append', default=[],
118 '-la', '--link-args', action='append', default=[],
119 help="Extra flags to pass to linker via the `extra_link_args` "
119 help="Extra flags to pass to linker via the `extra_link_args` "
120 "Extension flag (can be specified multiple times)."
120 "Extension flag (can be specified multiple times)."
121 )
121 )
122 @magic_arguments.argument(
122 @magic_arguments.argument(
123 '-l', '--lib', action='append', default=[],
123 '-l', '--lib', action='append', default=[],
124 help="Add a library to link the extension against (can be specified "
124 help="Add a library to link the extension against (can be specified "
125 "multiple times)."
125 "multiple times)."
126 )
126 )
127 @magic_arguments.argument(
127 @magic_arguments.argument(
128 '-L', dest='library_dirs', metavar='dir', action='append', default=[],
128 '-L', dest='library_dirs', metavar='dir', action='append', default=[],
129 help="Add a path to the list of libary directories (can be specified "
129 help="Add a path to the list of libary directories (can be specified "
130 "multiple times)."
130 "multiple times)."
131 )
131 )
132 @magic_arguments.argument(
132 @magic_arguments.argument(
133 '-I', '--include', action='append', default=[],
133 '-I', '--include', action='append', default=[],
134 help="Add a path to the list of include directories (can be specified "
134 help="Add a path to the list of include directories (can be specified "
135 "multiple times)."
135 "multiple times)."
136 )
136 )
137 @magic_arguments.argument(
137 @magic_arguments.argument(
138 '-+', '--cplus', action='store_true', default=False,
138 '-+', '--cplus', action='store_true', default=False,
139 help="Output a C++ rather than C file."
139 help="Output a C++ rather than C file."
140 )
140 )
141 @magic_arguments.argument(
141 @magic_arguments.argument(
142 '-f', '--force', action='store_true', default=False,
142 '-f', '--force', action='store_true', default=False,
143 help="Force the compilation of a new module, even if the source has been "
143 help="Force the compilation of a new module, even if the source has been "
144 "previously compiled."
144 "previously compiled."
145 )
145 )
146 @magic_arguments.argument(
146 @magic_arguments.argument(
147 '-a', '--annotate', action='store_true', default=False,
147 '-a', '--annotate', action='store_true', default=False,
148 help="Produce a colorized HTML version of the source."
148 help="Produce a colorized HTML version of the source."
149 )
149 )
150 @cell_magic
150 @cell_magic
151 def cython(self, line, cell):
151 def cython(self, line, cell):
152 """Compile and import everything from a Cython code cell.
152 """Compile and import everything from a Cython code cell.
153
153
154 The contents of the cell are written to a `.pyx` file in the
154 The contents of the cell are written to a `.pyx` file in the
155 directory `IPYTHONDIR/cython` using a filename with the hash of the
155 directory `IPYTHONDIR/cython` using a filename with the hash of the
156 code. This file is then cythonized and compiled. The resulting module
156 code. This file is then cythonized and compiled. The resulting module
157 is imported and all of its symbols are injected into the user's
157 is imported and all of its symbols are injected into the user's
158 namespace. The usage is similar to that of `%%cython_pyximport` but
158 namespace. The usage is similar to that of `%%cython_pyximport` but
159 you don't have to pass a module name::
159 you don't have to pass a module name::
160
160
161 %%cython
161 %%cython
162 def f(x):
162 def f(x):
163 return 2.0*x
163 return 2.0*x
164
165 To compile OpenMP codes, pass the required `--compile-args`
166 and `--link-args`. For example with gcc::
167
168 %%cython --compile-args=-fopenmp --link-args=-fopenmp
169 ...
164 """
170 """
165 args = magic_arguments.parse_argstring(self.cython, line)
171 args = magic_arguments.parse_argstring(self.cython, line)
166 code = cell if cell.endswith('\n') else cell+'\n'
172 code = cell if cell.endswith('\n') else cell+'\n'
167 lib_dir = os.path.join(self.shell.ipython_dir, 'cython')
173 lib_dir = os.path.join(self.shell.ipython_dir, 'cython')
168 quiet = True
174 quiet = True
169 key = code, sys.version_info, sys.executable, Cython.__version__
175 key = code, sys.version_info, sys.executable, Cython.__version__
170
176
171 if not os.path.exists(lib_dir):
177 if not os.path.exists(lib_dir):
172 os.makedirs(lib_dir)
178 os.makedirs(lib_dir)
173
179
174 if args.force:
180 if args.force:
175 # Force a new module name by adding the current time to the
181 # Force a new module name by adding the current time to the
176 # key which is hashed to determine the module name.
182 # key which is hashed to determine the module name.
177 key += time.time(),
183 key += time.time(),
178
184
179 module_name = "_cython_magic_" + hashlib.md5(str(key).encode('utf-8')).hexdigest()
185 module_name = "_cython_magic_" + hashlib.md5(str(key).encode('utf-8')).hexdigest()
180 module_path = os.path.join(lib_dir, module_name + self.so_ext)
186 module_path = os.path.join(lib_dir, module_name + self.so_ext)
181
187
182 have_module = os.path.isfile(module_path)
188 have_module = os.path.isfile(module_path)
183 need_cythonize = not have_module
189 need_cythonize = not have_module
184
190
185 if args.annotate:
191 if args.annotate:
186 html_file = os.path.join(lib_dir, module_name + '.html')
192 html_file = os.path.join(lib_dir, module_name + '.html')
187 if not os.path.isfile(html_file):
193 if not os.path.isfile(html_file):
188 need_cythonize = True
194 need_cythonize = True
189
195
190 if need_cythonize:
196 if need_cythonize:
191 c_include_dirs = args.include
197 c_include_dirs = args.include
192 if 'numpy' in code:
198 if 'numpy' in code:
193 import numpy
199 import numpy
194 c_include_dirs.append(numpy.get_include())
200 c_include_dirs.append(numpy.get_include())
195 pyx_file = os.path.join(lib_dir, module_name + '.pyx')
201 pyx_file = os.path.join(lib_dir, module_name + '.pyx')
196 pyx_file = py3compat.cast_bytes_py2(pyx_file, encoding=sys.getfilesystemencoding())
202 pyx_file = py3compat.cast_bytes_py2(pyx_file, encoding=sys.getfilesystemencoding())
197 with io.open(pyx_file, 'w', encoding='utf-8') as f:
203 with io.open(pyx_file, 'w', encoding='utf-8') as f:
198 f.write(code)
204 f.write(code)
199 extension = Extension(
205 extension = Extension(
200 name = module_name,
206 name = module_name,
201 sources = [pyx_file],
207 sources = [pyx_file],
202 include_dirs = c_include_dirs,
208 include_dirs = c_include_dirs,
203 library_dirs = args.library_dirs,
209 library_dirs = args.library_dirs,
204 extra_compile_args = args.compile_args,
210 extra_compile_args = args.compile_args,
205 extra_link_args = args.link_args,
211 extra_link_args = args.link_args,
206 libraries = args.lib,
212 libraries = args.lib,
207 language = 'c++' if args.cplus else 'c',
213 language = 'c++' if args.cplus else 'c',
208 )
214 )
209 build_extension = self._get_build_extension()
215 build_extension = self._get_build_extension()
210 try:
216 try:
211 opts = dict(
217 opts = dict(
212 quiet=quiet,
218 quiet=quiet,
213 annotate = args.annotate,
219 annotate = args.annotate,
214 force = True,
220 force = True,
215 )
221 )
216 build_extension.extensions = cythonize([extension], **opts)
222 build_extension.extensions = cythonize([extension], **opts)
217 except CompileError:
223 except CompileError:
218 return
224 return
219
225
220 if not have_module:
226 if not have_module:
221 build_extension.build_temp = os.path.dirname(pyx_file)
227 build_extension.build_temp = os.path.dirname(pyx_file)
222 build_extension.build_lib = lib_dir
228 build_extension.build_lib = lib_dir
223 build_extension.run()
229 build_extension.run()
224 self._code_cache[key] = module_name
230 self._code_cache[key] = module_name
225
231
226 module = imp.load_dynamic(module_name, module_path)
232 module = imp.load_dynamic(module_name, module_path)
227 self._import_all(module)
233 self._import_all(module)
228
234
229 if args.annotate:
235 if args.annotate:
230 try:
236 try:
231 with io.open(html_file, encoding='utf-8') as f:
237 with io.open(html_file, encoding='utf-8') as f:
232 annotated_html = f.read()
238 annotated_html = f.read()
233 except IOError as e:
239 except IOError as e:
234 # File could not be opened. Most likely the user has a version
240 # File could not be opened. Most likely the user has a version
235 # of Cython before 0.15.1 (when `cythonize` learned the
241 # of Cython before 0.15.1 (when `cythonize` learned the
236 # `force` keyword argument) and has already compiled this
242 # `force` keyword argument) and has already compiled this
237 # exact source without annotation.
243 # exact source without annotation.
238 print('Cython completed successfully but the annotated '
244 print('Cython completed successfully but the annotated '
239 'source could not be read.', file=sys.stderr)
245 'source could not be read.', file=sys.stderr)
240 print(e, file=sys.stderr)
246 print(e, file=sys.stderr)
241 else:
247 else:
242 return display.HTML(self.clean_annotated_html(annotated_html))
248 return display.HTML(self.clean_annotated_html(annotated_html))
243
249
244 @property
250 @property
245 def so_ext(self):
251 def so_ext(self):
246 """The extension suffix for compiled modules."""
252 """The extension suffix for compiled modules."""
247 try:
253 try:
248 return self._so_ext
254 return self._so_ext
249 except AttributeError:
255 except AttributeError:
250 self._so_ext = self._get_build_extension().get_ext_filename('')
256 self._so_ext = self._get_build_extension().get_ext_filename('')
251 return self._so_ext
257 return self._so_ext
252
258
253 def _get_build_extension(self):
259 def _get_build_extension(self):
254 dist = Distribution()
260 dist = Distribution()
255 config_files = dist.find_config_files()
261 config_files = dist.find_config_files()
256 try:
262 try:
257 config_files.remove('setup.cfg')
263 config_files.remove('setup.cfg')
258 except ValueError:
264 except ValueError:
259 pass
265 pass
260 dist.parse_config_files(config_files)
266 dist.parse_config_files(config_files)
261 build_extension = build_ext(dist)
267 build_extension = build_ext(dist)
262 build_extension.finalize_options()
268 build_extension.finalize_options()
263 return build_extension
269 return build_extension
264
270
265 @staticmethod
271 @staticmethod
266 def clean_annotated_html(html):
272 def clean_annotated_html(html):
267 """Clean up the annotated HTML source.
273 """Clean up the annotated HTML source.
268
274
269 Strips the link to the generated C or C++ file, which we do not
275 Strips the link to the generated C or C++ file, which we do not
270 present to the user.
276 present to the user.
271 """
277 """
272 r = re.compile('<p>Raw output: <a href="(.*)">(.*)</a>')
278 r = re.compile('<p>Raw output: <a href="(.*)">(.*)</a>')
273 html = '\n'.join(l for l in html.splitlines() if not r.match(l))
279 html = '\n'.join(l for l in html.splitlines() if not r.match(l))
274 return html
280 return html
275
281
276
282
277 def load_ipython_extension(ip):
283 def load_ipython_extension(ip):
278 """Load the extension in IPython."""
284 """Load the extension in IPython."""
279 ip.register_magics(CythonMagics)
285 ip.register_magics(CythonMagics)
General Comments 0
You need to be logged in to leave comments. Login now