##// END OF EJS Templates
Merge pull request #1770 from ellisonbg/cython_magic...
Fernando Perez -
r7103:42f1e817 merge
parent child Browse files
Show More
@@ -0,0 +1,181 b''
1 # -*- coding: utf-8 -*-
2 """
3 Cython related magics.
4
5 Author:
6 * Brian Granger
7
8 Parts of this code were taken from Cython.inline.
9 """
10 #-----------------------------------------------------------------------------
11 # Copyright (C) 2010-2011, IPython Development Team.
12 #
13 # Distributed under the terms of the Modified BSD License.
14 #
15 # The full license is in the file COPYING.txt, distributed with this software.
16 #-----------------------------------------------------------------------------
17
18 import io
19 import os, sys
20 from importlib import import_module
21 import imp
22
23 try:
24 import hashlib
25 except ImportError:
26 import md5 as hashlib
27
28 from distutils.core import Distribution, Extension
29 from distutils.command.build_ext import build_ext
30
31 from IPython.core.magic import Magics, magics_class, cell_magic
32 from IPython.testing.skipdoctest import skip_doctest
33 from IPython.core.magic_arguments import (
34 argument, magic_arguments, parse_argstring
35 )
36 from IPython.utils import py3compat
37
38 import Cython
39 from Cython.Compiler.Errors import CompileError
40 from Cython.Compiler.Main import Context, default_options
41 from Cython.Build.Dependencies import cythonize
42
43
44 @magics_class
45 class CythonMagics(Magics):
46
47 def __init__(self, shell):
48 super(CythonMagics,self).__init__(shell)
49 self._reloads = {}
50 self._code_cache = {}
51
52 def _import_all(self, module):
53 for k,v in module.__dict__.items():
54 if not k.startswith('__'):
55 self.shell.push({k:v})
56
57 @cell_magic
58 def cython_inline(self, line, cell):
59 """Compile and run a Cython code cell using Cython.inline.
60
61 This magic simply passes the body of the cell to Cython.inline
62 and returns the result. If the variables `a` and `b` are defined
63 in the user's namespace, here is a simple example that returns
64 their sum::
65
66 %%cython_inline
67 return a+b
68
69 For most purposes, we recommend the usage of the `%%cython` magic.
70 """
71 locs = self.shell.user_global_ns
72 globs = self.shell.user_ns
73 return Cython.inline(cell, locals=locs, globals=globs)
74
75 @cell_magic
76 def cython_pyximport(self, line, cell):
77 """Compile and import a Cython code cell using pyximport.
78
79 The contents of the cell are written to a `.pyx` file in the current
80 working directory, which is then imported using `pyximport`. This
81 magic requires a module name to be passed::
82
83 %%cython_pyximport modulename
84 def f(x):
85 return 2.0*x
86
87 The compiled module is then imported and all of its symbols are injected into
88 the user's namespace. For most purposes, we recommend the usage of the
89 `%%cython` magic.
90 """
91 module_name = line.strip()
92 if not module_name:
93 raise ValueError('module name must be given')
94 fname = module_name + '.pyx'
95 with io.open(fname, 'w', encoding='utf-8') as f:
96 f.write(cell)
97 if 'pyximport' not in sys.modules:
98 import pyximport
99 pyximport.install(reload_support=True)
100 if module_name in self._reloads:
101 module = self._reloads[module_name]
102 reload(module)
103 else:
104 module = import_module(module_name)
105 self._reloads[module_name] = module
106 self._import_all(module)
107
108 @magic_arguments()
109 @argument(
110 '-f', '--force', action='store_true', default=False,
111 help="Force the compilation of the pyx module even if it hasn't changed"
112 )
113 @cell_magic
114 def cython(self, line, cell):
115 """Compile and import everything from a Cython code cell.
116
117 The contents of the cell are written to a `.pyx` file in the
118 directory `IPYTHONDIR/cython` using a filename with the hash of the code.
119 This file is then cythonized and compiled. The resulting module
120 is imported and all of its symbols are injected into the user's
121 namespace. The usage is similar to that of `%%cython_pyximport` but
122 you don't have to pass a module name::
123
124 %%cython
125 def f(x):
126 return 2.0*x
127 """
128 args = parse_argstring(self.cython, line)
129 code = cell if cell.endswith('\n') else cell+'\n'
130 lib_dir=os.path.join(self.shell.ipython_dir, 'cython')
131 cython_include_dirs=['.']
132 force=args.force
133 quiet=True
134 ctx = Context(cython_include_dirs, default_options)
135 key = code, sys.version_info, sys.executable, Cython.__version__
136 module_name = "_cython_magic_" + hashlib.md5(str(key).encode('utf-8')).hexdigest()
137 so_ext = [ ext for ext,_,mod_type in imp.get_suffixes() if mod_type == imp.C_EXTENSION ][0]
138 module_path = os.path.join(lib_dir, module_name+so_ext)
139
140 if not os.path.exists(lib_dir):
141 os.makedirs(lib_dir)
142
143 if force or not os.path.isfile(module_path):
144 cflags = []
145 c_include_dirs = []
146 if 'numpy' in code:
147 import numpy
148 c_include_dirs.append(numpy.get_include())
149 pyx_file = os.path.join(lib_dir, module_name + '.pyx')
150 pyx_file = py3compat.unicode_to_str(pyx_file, encoding=sys.getfilesystemencoding())
151 with io.open(pyx_file, 'w', encoding='utf-8') as f:
152 f.write(code)
153 extension = Extension(
154 name = module_name,
155 sources = [pyx_file],
156 include_dirs = c_include_dirs,
157 extra_compile_args = cflags
158 )
159 build_extension = build_ext(Distribution())
160 build_extension.finalize_options()
161 try:
162 build_extension.extensions = cythonize([extension], ctx=ctx, quiet=quiet)
163 except CompileError:
164 return
165 build_extension.build_temp = os.path.dirname(pyx_file)
166 build_extension.build_lib = lib_dir
167 build_extension.run()
168 self._code_cache[key] = module_name
169
170 module = imp.load_dynamic(module_name, module_path)
171 self._import_all(module)
172
173
174 _loaded = False
175
176 def load_ipython_extension(ip):
177 """Load the extension in IPython."""
178 global _loaded
179 if not _loaded:
180 ip.register_magics(CythonMagics)
181 _loaded = True
@@ -0,0 +1,47 b''
1 # -*- coding: utf-8 -*-
2 """Tests for the Cython magics extension."""
3
4 import os
5 import nose.tools as nt
6
7 from IPython.utils import py3compat
8
9 code = py3compat.str_to_unicode("""def f(x):
10 return 2*x
11 """)
12
13 try:
14 import Cython
15 except:
16 __test__ = False
17
18 def setup():
19 ip = get_ipython()
20 ip.extension_manager.load_extension('cythonmagic')
21
22 def test_cython_inline():
23 ip = get_ipython()
24 ip.ex('a=10; b=20')
25 result = ip.run_cell_magic('cython_inline','','return a+b')
26 nt.assert_equals(result, 30)
27
28 def test_cython_pyximport():
29 module_name = '_test_cython_pyximport'
30 ip = get_ipython()
31 ip.run_cell_magic('cython_pyximport', module_name, code)
32 ip.ex('g = f(10)')
33 nt.assert_equals(ip.user_ns['g'], 20.0)
34 try:
35 os.remove(module_name+'.pyx')
36 except OSError:
37 pass
38
39 def test_cython():
40 ip = get_ipython()
41 ip.run_cell_magic('cython', '', code)
42 ip.ex('g = f(10)')
43 nt.assert_equals(ip.user_ns['g'], 20.0)
44
45
46
47
@@ -0,0 +1,226 b''
1 {
2 "metadata": {
3 "name": "cython_extension"
4 },
5 "nbformat": 3,
6 "worksheets": [
7 {
8 "cells": [
9 {
10 "cell_type": "heading",
11 "level": 1,
12 "source": [
13 "Cython Magic Functions Extension"
14 ]
15 },
16 {
17 "cell_type": "heading",
18 "level": 2,
19 "source": [
20 "Loading the extension"
21 ]
22 },
23 {
24 "cell_type": "markdown",
25 "source": [
26 "IPtyhon has a `cythonmagic` extension that contains a number of magic functions for working with Cython code. This extension can be loaded using the `%load_ext` magic as follows:"
27 ]
28 },
29 {
30 "cell_type": "code",
31 "collapsed": false,
32 "input": [
33 "%load_ext cythonmagic"
34 ],
35 "language": "python",
36 "outputs": [],
37 "prompt_number": 1
38 },
39 {
40 "cell_type": "heading",
41 "level": 2,
42 "source": [
43 "The %cython_inline magic"
44 ]
45 },
46 {
47 "cell_type": "markdown",
48 "source": [
49 "The `%%cython_inline` magic uses `Cython.inline` to compile a Cython expression. This allows you to enter and run a function body with Cython code. Use a bare `return` statement to return values. "
50 ]
51 },
52 {
53 "cell_type": "code",
54 "collapsed": true,
55 "input": [
56 "a = 10",
57 "b = 20"
58 ],
59 "language": "python",
60 "outputs": [],
61 "prompt_number": 8
62 },
63 {
64 "cell_type": "code",
65 "collapsed": false,
66 "input": [
67 "%%cython_inline",
68 "return a+b"
69 ],
70 "language": "python",
71 "outputs": [
72 {
73 "output_type": "pyout",
74 "prompt_number": 9,
75 "text": [
76 "30"
77 ]
78 }
79 ],
80 "prompt_number": 9
81 },
82 {
83 "cell_type": "heading",
84 "level": 2,
85 "source": [
86 "The %cython_pyximport magic"
87 ]
88 },
89 {
90 "cell_type": "markdown",
91 "source": [
92 "The `%%cython_pyximport` magic allows you to enter arbitrary Cython code into a cell. That Cython code is written as a `.pyx` file in the current working directory and then imported using `pyximport`. You have the specify the name of the module that the Code will appear in. All symbols from the module are imported automatically by the magic function."
93 ]
94 },
95 {
96 "cell_type": "code",
97 "collapsed": false,
98 "input": [
99 "%%cython_pyximport foo",
100 "def f(x):",
101 " return 4.0*x"
102 ],
103 "language": "python",
104 "outputs": [],
105 "prompt_number": 18
106 },
107 {
108 "cell_type": "code",
109 "collapsed": false,
110 "input": [
111 "f(10)"
112 ],
113 "language": "python",
114 "outputs": [
115 {
116 "output_type": "pyout",
117 "prompt_number": 19,
118 "text": [
119 "40.0"
120 ]
121 }
122 ],
123 "prompt_number": 19
124 },
125 {
126 "cell_type": "heading",
127 "level": 2,
128 "source": [
129 "The %cython magic"
130 ]
131 },
132 {
133 "cell_type": "markdown",
134 "source": [
135 "Probably the most important magic is the `%cython` magic. This is similar to the `%%cython_pyximport` magic, but doesn't require you to specify a module name. Instead, the `%%cython` magic uses manages everything using temporary files in the `~/.cython/magic` directory. All of the symbols in the Cython module are imported automatically by the magic.",
136 "",
137 "Here is a simple example of a Black-Scholes options pricing algorithm written in Cython:"
138 ]
139 },
140 {
141 "cell_type": "code",
142 "collapsed": false,
143 "input": [
144 "%%cython",
145 "cimport cython",
146 "from libc.math cimport exp, sqrt, pow, log, erf",
147 "",
148 "@cython.cdivision(True)",
149 "cdef double std_norm_cdf(double x) nogil:",
150 " return 0.5*(1+erf(x/sqrt(2.0)))",
151 "",
152 "@cython.cdivision(True)",
153 "def black_scholes(double s, double k, double t, double v,",
154 " double rf, double div, double cp):",
155 " \"\"\"Price an option using the Black-Scholes model.",
156 " ",
157 " s : initial stock price",
158 " k : strike price",
159 " t : expiration time",
160 " v : volatility",
161 " rf : risk-free rate",
162 " div : dividend",
163 " cp : +1/-1 for call/put",
164 " \"\"\"",
165 " cdef double d1, d2, optprice",
166 " with nogil:",
167 " d1 = (log(s/k)+(rf-div+0.5*pow(v,2))*t)/(v*sqrt(t))",
168 " d2 = d1 - v*sqrt(t)",
169 " optprice = cp*s*exp(-div*t)*std_norm_cdf(cp*d1) - \\",
170 " cp*k*exp(-rf*t)*std_norm_cdf(cp*d2)",
171 " return optprice"
172 ],
173 "language": "python",
174 "outputs": [],
175 "prompt_number": 6
176 },
177 {
178 "cell_type": "code",
179 "collapsed": false,
180 "input": [
181 "black_scholes(100.0, 100.0, 1.0, 0.3, 0.03, 0.0, -1)"
182 ],
183 "language": "python",
184 "outputs": [
185 {
186 "output_type": "pyout",
187 "prompt_number": 7,
188 "text": [
189 "10.327861752731728"
190 ]
191 }
192 ],
193 "prompt_number": 7
194 },
195 {
196 "cell_type": "code",
197 "collapsed": false,
198 "input": [
199 "%timeit black_scholes(100.0, 100.0, 1.0, 0.3, 0.03, 0.0, -1)"
200 ],
201 "language": "python",
202 "outputs": [
203 {
204 "output_type": "stream",
205 "stream": "stdout",
206 "text": [
207 "1000000 loops, best of 3: 621 ns per loop",
208 ""
209 ]
210 }
211 ],
212 "prompt_number": 14
213 },
214 {
215 "cell_type": "code",
216 "collapsed": true,
217 "input": [
218 ""
219 ],
220 "language": "python",
221 "outputs": []
222 }
223 ]
224 }
225 ]
226 } No newline at end of file
@@ -0,0 +1,7 b''
1 .. _extensions_cythonmagic:
2
3 ===========
4 cythonmagic
5 ===========
6
7 .. automodule:: IPython.extensions.cythonmagic
@@ -149,6 +149,7 b" have['wx'] = test_for('wx')"
149 have['wx.aui'] = test_for('wx.aui')
149 have['wx.aui'] = test_for('wx.aui')
150 have['qt'] = test_for('IPython.external.qt')
150 have['qt'] = test_for('IPython.external.qt')
151 have['sqlite3'] = test_for('sqlite3')
151 have['sqlite3'] = test_for('sqlite3')
152 have['cython'] = test_for('Cython')
152
153
153 have['tornado'] = test_for('tornado.version_info', (2,1,0), callback=None)
154 have['tornado'] = test_for('tornado.version_info', (2,1,0), callback=None)
154
155
@@ -269,6 +270,9 b' def make_exclude():'
269 ipjoin('zmq', 'pylab'),
270 ipjoin('zmq', 'pylab'),
270 ])
271 ])
271
272
273 if not have['cython']:
274 exclusions.extend([ipjoin('extensions', 'cythonmagic')])
275
272 if not have['tornado']:
276 if not have['tornado']:
273 exclusions.append(ipjoin('frontend', 'html'))
277 exclusions.append(ipjoin('frontend', 'html'))
274
278
@@ -71,6 +71,7 b' Extensions bundled with IPython'
71 :maxdepth: 1
71 :maxdepth: 1
72
72
73 autoreload
73 autoreload
74 cythonmagic
74 parallelmagic
75 parallelmagic
75 storemagic
76 storemagic
76 sympyprinting
77 sympyprinting
General Comments 0
You need to be logged in to leave comments. Login now