This is one of the 100 recipes of the IPython Cookbook, the definitive guide to high-performance scientific computing and data science in Python.

5.3. Wrapping a C library in Python with ctypes

This example shows:

  • How to write and compile C code defining functions that are accessible from Python, and
  • How to call C functions from Python using the native ctypes module.

This notebook has been written for Windows systems and Microsoft's C compiler (shipped with Visual Studio).

Note: on Windows, for the C compiler to run, you need to execute a sequence of magic incantations before launching the IPython notebook. See the _launch_notebook.bat file in this repository.

Let's write the generation of the Mandelbrot fractal in C.

In [ ]:
%%writefile mandelbrot.c

// Needed when creating a DLL.
#define EXPORT __declspec(dllexport)

#include "stdio.h"
#include "stdlib.h"

// This function will be available in the DLL.
EXPORT void __stdcall mandelbrot(int size,
                                 int iterations,
                                 int *col) 
{
    // Variable declarations.
    int i, j, n, index;
    double cx, cy;
    double z0, z1, z0_tmp, z0_2, z1_2;
    
    // Loop within the grid.
    for (i = 0; i < size; i++)
    {
        cy = -1.5 + (double)i / size * 3;
        for (j = 0; j < size; j++)
        {
            // We initialize the loop of the system.
            cx = -2.0 + (double)j / size * 3;
            index = i * size + j;
            // Let's run the system.
            z0 = 0.0;
            z1 = 0.0;
            for (n = 0; n < iterations; n++)
            {
                z0_2 = z0 * z0;
                z1_2 = z1 * z1;
                if (z0_2 + z1_2 <= 100)
                {
                    // Update the system.
                    z0_tmp = z0_2 - z1_2 + cx;
                    z1 = 2 * z0 * z1 + cy;
                    z0 = z0_tmp;
                    col[index] = n;
                }
                else
                {
                    break;
                }
            }
        }
    }
}

Now, let's build this C source file into a DLL with Microsoft Visual Studio's cl.exe. The /LD option specifies that a DLL has to be created.

In [ ]:
!cl /LD mandelbrot.c

Wrapping the C library with NumPy and ctypes

Let's access the library with ctypes.

In [ ]:
import ctypes
In [ ]:
lb = ctypes.CDLL('mandelbrot.dll')
In [ ]:
lib = ctypes.WinDLL(None, handle=lb._handle)
In [ ]:
# Access the mandelbrot function.
mandelbrot = lib.mandelbrot

NumPy and ctypes allow us to wrap the C function defined in the DLL.

In [ ]:
from numpy.ctypeslib import ndpointer
In [ ]:
# Define the types of the output and arguments of this function.
mandelbrot.restype = None
mandelbrot.argtypes = [ctypes.c_int, ctypes.c_int,
                       ndpointer(ctypes.c_int)]

Now, we can execute the mandelbrot function.

In [ ]:
import numpy as np
# We initialize an empty array.
size = 200
iterations = 100
col = np.empty((size, size), dtype=np.int32)
# We execute the C function, which will update the array.
mandelbrot(size, iterations, col)

The simulation has finished, let's display the fractal.

In [ ]:
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
In [ ]:
plt.imshow(np.log(col), cmap=plt.cm.hot,);
plt.xticks([]);
plt.yticks([]);
In [ ]:
%timeit mandelbrot(size, iterations, col)

We free the library handle at the end.

In [ ]:
lb._handle
In [ ]:
from ctypes.wintypes import HMODULE
ctypes.windll.kernel32.FreeLibrary.argtypes = [HMODULE]
ctypes.windll.kernel32.FreeLibrary(lb._handle);

You'll find all the explanations, figures, references, and much more in the book (to be released later this summer).

IPython Cookbook, by Cyrille Rossant, Packt Publishing, 2014 (500 pages).