Archive for the ‘SWIG’ Category.

Using additional term structure interpolation methods with QuantLib’s Python bindings

The amazingly comprehensive open-source quantitative finance library QuantLib supplies a set of Python bindings generated with SWIG. Unfortunately much of QuantLib’s adaptability is made available via C++ templates. With the current SWIG wrapper it’s difficult to expose the entirety of QuantLib’s functionality without compiling every permutation of template parameter.

I discovered this quirk whilst trying to apply cubic spline interpolation to a zero curve. It turns out that by default the SWIG interface only exposes a linearly interpolated zero curve class. Fortunately there are some nice macros within the SWIG interface that ease the exposure of additional interpolation schemes, albeit with a recompile of the Python module.

Zero curves with additional interpolation methods can be added to the end of QuantLib-SWIG-0.9.7/SWIG/zerocurve.i using the export_zero_curve macro as follows:

export_zero_curve(ZeroCurve,Linear);
export_zero_curve(CubicZeroCurve,Cubic);

After recompiling the Python bindings you’ll now have a CubicZeroCurve class that performs cubic spline interpolation between data points.

This approach can be used throughout much of the SWIG interface files to expose template customized QuantLib classes.

Passing Python file objects across a SWIG interface

SWIG works great out of the box when you’re dealing with basic types. Once you start to wrap more complicated APIs the type conversion magic can only go so far.

I have been writing a Python SWIG interface for a library that accepts stdio FILE* pointers for input/output. Here I’ll present a minimal example where a Python file object is passed as a FILE* to a simple C function. All source is available here if you wish to follow along.

char buffer[1024];
 
char*
message(FILE *input)
{
    memset(buffer, 0, sizeof(buffer));
    fread(buffer, sizeof(buffer), 1, input);
    return buffer;
}

This simply reads from a file stream until it’s closed and returns what was read. I preemptively apologise for it not being thread safe.

We’ll create a module called test wrapping the C function message such that the following code (example.py) will output “Hello World”.

import os
import test
 
# Create and open pipe as file objects
r,w = os.pipe()
fr, fw = os.fdopen(r, 'r'), os.fdopen(w, 'w')
 
# Write and close
fw.write('Hello World')
fw.close()
 
# Read data from pipe using test module
print test.message(fr)

The first cut at a SWIG interface file yields

%module test
%{
#include "test.h"
%}
char *message(FILE *input);

Unfortunately when we build and run example.py we get a TypeError indicating the argument we passed couldn’t be converted to a FILE*.

$ python setup.py build_ext --inplace
$ python example.py 
Traceback (most recent call last):
  File "example.py", line 17, in <module>
    print test.message(fr)
TypeError: in method 'message', argument 1 of type 'FILE *'

How do we make SWIG correctly convert the Python file type to a FILE*? The solution lies in a SWIG feature called a typemap.

Typemaps allow you to write short stubs in C to convert objects from Python to C and vice-versa. Writing them requires you to have some knowledge of the Python API, for simple conversions however you can make good progress simply by digging through the Python include directory.

Adding the following typemap to our SWIG interface file tells squid how to convert incoming Python objects into FILE* pointers. Conveniently the Python C API provides the PyFile_Check function that checks if a PyObject* is of the PyFile type and PyFile_AsFile functions that returns a FILE* given a PyFile instance.

/* Converts a PyFile instance to a stdio FILE* */
%typemap(in) FILE* {
    if ( PyFile_Check($input) ){
        $1 = PyFile_AsFile($input);
    } else {
        PyErr_SetString(PyExc_TypeError, "$1_name must be a file type.");
        return NULL;
    }
}

Now when we build test and run example.py we get the desired result.

$ python example.py 
Hello World

Notice that if we pass a non-file type to message a TypeError will be raised.

>>> import test
>>> test.message('not a file')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: input must be a file type.

This simple example demonstrates one powerful feature of SWIG that can be expanded to wrap complex C and even C++ APIs.