The last example is about approximating factorials with Cython. We will use two approximation methods. First, we will use the Stirling approximation method (see http://en.wikipedia.org/wiki/Stirling%27s_approximation for more information). The formula for the Stirling approximation is:
Secondly, we will be using the approximation due to Ramanujan, with the following formula:
This section describes how to approximate factorials using Cython. In this recipe, we will be using types, which as you may remember, is optional in Cython. In theory, declaring static types should speed things up. Static typing offers interesting challenges that you may not encounter when writing Python code, but don't worry, we will try to keep it simple.
The Cython code that we will write looks like regular Python code, except that we declare function parameters and a local variable to be an ndarray
array. In order to get the static types to work, we need to import NumPy using the cimport
statement. Also we have to use the cdef
keyword to declare the type of the local variable.
import numpy cimport numpy def ramanujan_factorial(numpy.ndarray n): sqrt_pi = numpy.sqrt(numpy.pi, dtype=numpy.float64) cdef numpy.ndarray root = (8 * n + 4) * n + 1 root = root * n + 1/30. root = root ** (1/6.) return sqrt_pi * calc_eton(n) * root def stirling_factorial(numpy.ndarray n): return numpy.sqrt(2 * numpy.pi * n) * calc_eton(n) def calc_eton(numpy.ndarray n): return (n/numpy.e) ** n
Building requires us to create a setup.py
file, as in the previous tutorials, but we now need to include NumPy-related directories by calling the get_include
function.
With this amendment, the setup.py
file will have the following content:
from distutils.core import setup from distutils.extension import Extension from Cython.Distutils import build_ext import numpy ext_modules = [Extension("factorial", ["factorial.pyx"], include_dirs = [numpy.get_include()])] setup( name = 'Factorial app', cmdclass = {'build_ext': build_ext}, ext_modules = ext_modules )
Let's plot the relative error for both approximation methods. We will calculate the error relative to the factorial values that we will compute with the NumPy cumprod
function, as we have done throughout the book.
from factorial import ramanujan_factorial from factorial import stirling_factorial import numpy import matplotlib.pyplot N = 50 numbers = numpy.arange(1, N) factorials = numpy.cumprod(numbers, dtype=float) def error(approximations): return (factorials - approximations)/factorials matplotlib.pyplot.plot(error(ramanujan_factorial(numbers)), 'b-') matplotlib.pyplot.plot(error(stirling_factorial(numbers)), 'ro') matplotlib.pyplot.show()
The following plot shows the relative error for the Ramanujan approximation (dots) and the Stirling approximation (line). As you can see, the Ramanujan method is more accurate: