##// END OF EJS Templates
Adding Cython extension and example notebook.
Brian Granger -
Show More
@@ -0,0 +1,145 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 os, sys
19 from importlib import import_module
20 import imp
21
22 try:
23 import hashlib
24 except ImportError:
25 import md5 as hashlib
26
27 from distutils.core import Distribution, Extension
28 from distutils.command.build_ext import build_ext
29
30 from IPython.core.magic import Magics, magics_class, cell_magic
31 from IPython.testing.skipdoctest import skip_doctest
32 from IPython.core.magic_arguments import (
33 argument, magic_arguments, parse_argstring
34 )
35
36 import Cython
37 from Cython.Compiler.Errors import CompileError
38 from Cython.Compiler.Main import Context, default_options
39 from Cython.Build.Dependencies import cythonize
40
41
42 @magics_class
43 class CythonMagics(Magics):
44
45 def __init__(self, shell):
46 super(CythonMagics,self).__init__(shell)
47 self._reloads = {}
48 self._code_cache = {}
49
50 def _import_all(self, module):
51 for k,v in module.__dict__.items():
52 if not k.startswith('__'):
53 self.shell.push({k:v})
54
55 @skip_doctest
56 @cell_magic
57 def cython_inline(self, line, cell):
58 """Compile and run a Cython code cell using Cython.inline."""
59 locs = self.shell.user_global_ns
60 globs = self.shell.user_ns
61 return Cython.inline(cell, locals=locs, globals=globs)
62
63 @skip_doctest
64 @cell_magic
65 def cython_pyximport(self, line, cell):
66 """Compile and import a Cython code cell using pyximport."""
67 module_name = line.strip()
68 if not module_name:
69 raise ValueError('module name must be given')
70 fname = module_name + '.pyx'
71 with open(fname, 'w') as f:
72 f.write(cell)
73 if 'pyximport' not in sys.modules:
74 import pyximport
75 pyximport.install(reload_support=True)
76 if module_name in self._reloads:
77 module = self._reloads[module_name]
78 reload(module)
79 else:
80 module = import_module(module_name)
81 self._reloads[module_name] = module
82 self._import_all(module)
83
84 @magic_arguments()
85 @argument(
86 '-f', '--force', action='store_true', default=False,
87 help="Force the compilation of the pyx module even if it hasn't changed"
88 )
89 @skip_doctest
90 @cell_magic
91 def cython(self, line, cell):
92 """Compile and import everything from a Cython code cell."""
93 args = parse_argstring(self.cython, line)
94 code = cell if cell.endswith('\n') else cell+'\n'
95 lib_dir=os.path.expanduser('~/.cython/magic')
96 cython_include_dirs=['.']
97 force=args.force
98 quiet=True
99 ctx = Context(cython_include_dirs, default_options)
100 key = code, sys.version_info, sys.executable, Cython.__version__
101 module_name = "_cython_magic_" + hashlib.md5(str(key).encode('utf-8')).hexdigest()
102 so_ext = [ ext for ext,_,mod_type in imp.get_suffixes() if mod_type == imp.C_EXTENSION ][0]
103 module_path = os.path.join(lib_dir, module_name+so_ext)
104
105 if not os.path.exists(lib_dir):
106 os.makedirs(lib_dir)
107
108 if force or not os.path.isfile(module_path):
109 cflags = []
110 c_include_dirs = []
111 if 'numpy' in code:
112 import numpy
113 c_include_dirs.append(numpy.get_include())
114 pyx_file = os.path.join(lib_dir, module_name + '.pyx')
115 with open(pyx_file, 'w') as f:
116 f.write(code)
117 extension = Extension(
118 name = module_name,
119 sources = [pyx_file],
120 include_dirs = c_include_dirs,
121 extra_compile_args = cflags
122 )
123 build_extension = build_ext(Distribution())
124 build_extension.finalize_options()
125 try:
126 build_extension.extensions = cythonize([extension], ctx=ctx, quiet=quiet)
127 except CompileError:
128 return
129 build_extension.build_temp = os.path.dirname(pyx_file)
130 build_extension.build_lib = lib_dir
131 build_extension.run()
132 self._code_cache[key] = module_name
133
134 module = imp.load_dynamic(module_name, module_path)
135 self._import_all(module)
136
137
138 _loaded = False
139
140 def load_ipython_extension(ip):
141 """Load the extension in IPython."""
142 global _loaded
143 if not _loaded:
144 ip.register_magics(CythonMagics)
145 _loaded = True
@@ -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
General Comments 0
You need to be logged in to leave comments. Login now