Using Cython¶
We show how to use Cython to accelerate the computation of a cost function and how to avoid some pitfalls.
If you do not care specifically about Cython and just want to make your code faster, try Numba to accelerate your cost function (see the corresponding tutorials for more details). Numba in particular is more powerful than Cython and you don’t have to learn the awkward Cython dialect. Also note that Cython does not fully support the C++ language, it was designed for C. iminuit also works with PyPy.
With that disclaimer out of the way, let’s try to use iminuit with a Cython-compiled function. We will only give a very brief glimps on how to write Cython code, please search the web for a proper Cython tutorial.
[1]:
# setup of the notebook
%load_ext Cython
from iminuit import Minuit, describe
from iminuit.util import make_func_code
import numpy as np
import traceback
---------------------------------------------------------------------------
ModuleNotFoundError Traceback (most recent call last)
Input In [1], in <cell line: 2>()
1 # setup of the notebook
----> 2 get_ipython().run_line_magic('load_ext', 'Cython')
3 from iminuit import Minuit, describe
4 from iminuit.util import make_func_code
File /usr/lib/python3.10/site-packages/IPython/core/interactiveshell.py:2294, in InteractiveShell.run_line_magic(self, magic_name, line, _stack_depth)
2292 kwargs['local_ns'] = self.get_local_scope(stack_depth)
2293 with self.builtin_trap:
-> 2294 result = fn(*args, **kwargs)
2295 return result
File /usr/lib/python3.10/site-packages/IPython/core/magics/extension.py:33, in ExtensionMagics.load_ext(self, module_str)
31 if not module_str:
32 raise UsageError('Missing module name.')
---> 33 res = self.shell.extension_manager.load_extension(module_str)
35 if res == 'already loaded':
36 print("The %s extension is already loaded. To reload it, use:" % module_str)
File /usr/lib/python3.10/site-packages/IPython/core/extensions.py:76, in ExtensionManager.load_extension(self, module_str)
69 """Load an IPython extension by its module name.
70
71 Returns the string "already loaded" if the extension is already loaded,
72 "no load function" if the module doesn't have a load_ipython_extension
73 function, or None if it succeeded.
74 """
75 try:
---> 76 return self._load_extension(module_str)
77 except ModuleNotFoundError:
78 if module_str in BUILTINS_EXTS:
File /usr/lib/python3.10/site-packages/IPython/core/extensions.py:92, in ExtensionManager._load_extension(self, module_str)
90 if module_str not in sys.modules:
91 with prepended_to_syspath(self.ipython_extension_dir):
---> 92 mod = import_module(module_str)
93 if mod.__file__.startswith(self.ipython_extension_dir):
94 print(("Loading extensions from {dir} is deprecated. "
95 "We recommend managing extensions like any "
96 "other Python packages, in site-packages.").format(
97 dir=compress_user(self.ipython_extension_dir)))
File /usr/lib/python3.10/importlib/__init__.py:126, in import_module(name, package)
124 break
125 level += 1
--> 126 return _bootstrap._gcd_import(name[level:], package, level)
File <frozen importlib._bootstrap>:1050, in _gcd_import(name, package, level)
File <frozen importlib._bootstrap>:1027, in _find_and_load(name, import_)
File <frozen importlib._bootstrap>:1004, in _find_and_load_unlocked(name, import_)
ModuleNotFoundError: No module named 'Cython'
The following cell is Cython code and will be compiled to machine code behind the scenes.
[2]:
%%cython
def cython_func(double x, double y, double z):
return (x - 1.) ** 2 + (y - 2.) ** 2 + (z - 3.) ** 2 + 1.
UsageError: Cell magic `%%cython` not found.
Unfortunately, if we try to pass starting values to Minuit via keywords, we get a failure.
[3]:
try:
m = Minuit(cython_func, x=1, y=2, z=3)
except:
traceback.print_exc()
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
Input In [3], in <cell line: 1>()
1 try:
----> 2 m = Minuit(cython_func, x=1, y=2, z=3)
3 except:
NameError: name 'Minuit' is not defined
During handling of the above exception, another exception occurred:
NameError Traceback (most recent call last)
Input In [3], in <cell line: 1>()
2 m = Minuit(cython_func, x=1, y=2, z=3)
3 except:
----> 4 traceback.print_exc()
NameError: name 'traceback' is not defined
What happened? Minuit
uses the describe
tool which uses introspection to read the function signature, but this failed here. Without that, Minuit does not know how many parameters this function accepts and their names.
Python built-in functions (like min
) normally do not have a function signature. Functions from cython and swig also do not have one.
There are a few ways to fix this.
One can pass parameter names explicitly to Minuit, then it works.
One can use positional arguments.
One can tell Cython to embed a signature.
[4]:
m = Minuit(cython_func, name=("x", "y", "z"), x=0, y=0, z=0)
m.errordef = Minuit.LEAST_SQUARES
m.migrad()
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
Input In [4], in <cell line: 1>()
----> 1 m = Minuit(cython_func, name=("x", "y", "z"), x=0, y=0, z=0)
2 m.errordef = Minuit.LEAST_SQUARES
3 m.migrad()
NameError: name 'Minuit' is not defined
Alternatively, one can use positional arguments without specifying parameter names.
[5]:
m = Minuit(cython_func, 0, 0, 0)
m.errordef = Minuit.LEAST_SQUARES
m.migrad()
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
Input In [5], in <cell line: 1>()
----> 1 m = Minuit(cython_func, 0, 0, 0)
2 m.errordef = Minuit.LEAST_SQUARES
3 m.migrad()
NameError: name 'Minuit' is not defined
A nicer solution is to ask Cython to add the missing function signature. This can be achieved with the embedsignature(true)
decorator.
[6]:
%%cython
cimport cython
@cython.embedsignature(True) # generate a signature that iminuit can extract
def cython_f(double x, double y, double z):
return (x - 1.) ** 2 + (y - 2.) ** 2 + (z - 3.) ** 2 + 1.
UsageError: Cell magic `%%cython` not found.
Now it works.
[7]:
m = Minuit(cython_f, x=0, y=0, z=0)
m.errordef = Minuit.LEAST_SQUARES
m.migrad()
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
Input In [7], in <cell line: 1>()
----> 1 m = Minuit(cython_f, x=0, y=0, z=0)
2 m.errordef = Minuit.LEAST_SQUARES
3 m.migrad()
NameError: name 'Minuit' is not defined