2. Simplified Wrapper Interface Generator (SWIG)

The SWIG Project develops the swig software tool which simplifies the task interfacing different languages to C/C++ programs. SWIG is a compiler that takes C/C++ declarations and creates the wrappers needed to access those declarations from other languages including including Python, Perl, Tcl, Ruby, Guile, and Java.

OpenMOC uses SWIG to wrap C/C++ source code to create a user interface for Python. This section serves as a brief introduction to SWIG with a focus on those features most applicable to OpenMOC. The following sections describe how to write SWIG interface files, the output generated by SWIG, the use of typemaps, and more. The online documentation for SWIG is extensive and the reader is recommended to the following tutorials for more in-depth information:

2.1. SAXPY in SWIG Example

This introduces SWIG through an example of “Single-Precision A \cdot X Plus Y” (“saxpy” for for short). The example uses SWIG to wrap a C/C++ file to create a C/C++ extension module for Python.

2.1.1. C/C++ Source Code

The following is the C++ header file for the SAXPY source code (download). The function prototypes are defined and will be wrapped by SWIG as described in the next section.

/* File saxpy.h */
#include <stdlib.h>
#include <stdio.h>

/* Define function prototypes */
void set_array_length(int n);
void initialize_data();
void free_data();
void print_data();
void saxpy();

The corresponding function implementations for the SAXPY example are given in the C++ source file saxpy.cpp below (download).

/* File saxpy.cpp */
#include "saxpy.h"

/* Define global variables */
int length;
double a;
double* x;
double* y;

void set_array_length(int n) {
  length = n;
}

void initialize_data() {

  /* Allocate memory for arrays */
  x = (double*)malloc(length*sizeof(double));
  y = (double*)malloc(length*sizeof(double));

  /* Initialize data with random numbers in [0,1] */
  a = float(rand()) / RAND_MAX;

  for (int i=0; i < length; i++) {
    x[i] = float(rand()) / RAND_MAX;
    y[i] = float(rand()) / RAND_MAX;
  }
}

void free_data() {
  free(x);
  free(y);
}

void print_data() {
  printf("a = %f\n", a);

  for (int i=0; i < length; i++)
    printf("x[%d] = %f\ty[%d] = %f\n", i, x[i], i, y[i]);
}

void saxpy() {
  for (int i=0; i < length; i++)
    y[i] = a * x[i] + y[i];
}

2.1.2. SWIG Interface File

SWIG requires the use of interface files for input. A SWIG interface file is required for each C/C++ extension module generated for Python. The primary purpose for SWIG interface file(s) in OpenMOC is to expose the C/C++ source code to SWIG. This is done by including the header files which contain all of the function prototypes, class definitions, etc. for SWIG to wrap. In addition, the name of the module must be included at the top of the interface file. The following illustrates saxpy.i interface file (download) for the SAXPY example.

%module saxpy
%{
  #include SWIG_FILE_WITH_INIT
  #include "saxpy.h"
%}

%include "saxpy.h"

Note

The reader is encouraged to reference the online documentation for the many options which may be used in SWIG interface files.

2.1.3. Wrapping the C/C++ Source Code

SWIG is provided as the swig executable and is called on the command line to wrap C/C++ source code given a SWIG interface file. The call to swig produces a SWIG wrap file, which is designated following the -o argument. The language used for the input source files (e.g., C/C++) as well as the language targeted for the bindings (e.g., Python) must be specified as flags to the swig executable. The following is an example of how to issue a command from the shell to wrap the source code in the saxpy.i SWIG interface file:

$ swig -python -c++ -o saxpy_wrap.cpp saxpy.i

One output from the swig command to wrap C/C++ source code is a new C/C++ source file which contains all of the wrapper code needed to build an extension module. For example, the interface file saxpy.i will be transformed into an output saxpy_wrap.cpp file. To build the final extension module, the SWIG output file is compiled and linked with the rest of the C/C++ program to create a shared library as discussed in the following sections.

Another output from a swig command to wrap C/C++ source code is a new file in the scripting language targeted by SWIG. In the case of OpenMOC, the target scripting language is Python and hence a Python file containing the wrapped routines is created. For example, the interface file saxpy.i will be transformed into an output saxpy.py file. This is the file which is imported when a user imports the saxpy module into Python and which interfaces with the shared library created for the module.

2.1.4. Creating the Extension Module

The next step is to compile both the the C/C++ source files as well as the wrap file generated by SWIG. The wrap file includes Python.h which is included with the Python development package provided by most package managers (i.e., python-dev for Ubuntu’s apt-get package manager). The C/C++ source MUST be compiled with the -fpic option to produce “position independent code” for the shared library. The following two commands may be used to compile the source saxpy.cpp and saxpy_wrap.cpp files for this example:

$ gcc -c saxpy.cpp -o saxpy.o -fpic -std=c++0x
$ gcc -I/usr/include/python2.7 -c saxpy_wrap.cpp -o saxpy_wrap.o -fpic -std=c++0x

The final step is to link the object files into a shared library which will serve as the extension module. It is standard practice for C/C++ extension modules to begin with an underscore “_” prefix. The object files for the SAXPY example can be linked into the _saxpy.so shared libary file as follows:

$ g++ saxpy_wrap.o saxpy.o -o _saxpy.so -shared -Wl,-soname,_saxpy.so

2.1.5. Using the Extension Module

Finally, the shared library extension module can be imported into Python and used as follows:

$ python
Python 2.7.3 (default, Sep 26 2013, 16:35:25)
[GCC 4.7.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import _saxpy as module
>>> module.set_array_length(10)
>>> module.initialize_data()
>>> module.saxpy()
>>> module.print_data()
a = 0.840188
x[0] = 0.394383     y[0] = 1.114455
x[1] = 0.798440     y[1] = 1.582487
x[2] = 0.197551     y[2] = 0.501203
x[3] = 0.768230     y[3] = 0.923232
x[4] = 0.553970     y[4] = 0.942836
x[5] = 0.628871     y[5] = 0.893154
x[6] = 0.513401     y[6] = 1.383583
x[7] = 0.916195     y[7] = 1.405488
x[8] = 0.717297     y[8] = 0.744267
x[9] = 0.606969     y[9] = 0.526268
>>> module.free_data()

2.2. NumPy Typemaps

It is often useful to input/return NumPy data structures to/from C/C++ routines. The NumPy C API makes this functionality possibility through array conversions. In addition, it is possible to automatically embed the NumPy C API directly into the source code with the use of NumPy typemaps. Typemaps are a mechanism to match function signatures through a list of of function arguments. When SWIG finds a function which matches the typemap, it will target and subsequently modify the function to include the NumPy C API in order to input/output NumPy data arrays. Two types of parameters must be specified in the C/C++ function(s) of interest in order to match a NumPy typemap:

  • Array Pointer - The data type and pointer to the array
  • Array Dimensions - An integer for each array dimension

The numpy.i interface file (download) defines the typemaps and is shipped with OpenMOC in the /OpenMOC/openmoc directory. In order to utilize NumPy typemaps, the following should be appended to the SWIG interface file used for the C/C++ extension module of interest:

%include "numpy.i"

%init %{
  import_array();
%}

The following sections overview the basic steps to utilize NumPy typemaps to input NumPy data from Python into C/C++ routines, and return data from C/C++ routines as NumPy arrays.

2.2.1. Input NumPy Data Arrays

The numpy.i interface file provides two particular typemaps for inputting a NumPy data array into a C/C++ routine. The IN_ARRAY* defines an array which is passed into a routine but is not modified in-place by the C/C++ function and is not returned to the user. The INPLACE_ARRAY* typemap defines arrays that are modified in-place. In each case, the * represents the number of dimensions for the input array. For example, in order to input a 3D array to be modified in-place, one would use the INPLACE_ARRAY3 typemap. The array dimension(s) are included in each typemap through the use of the DIM* parameter.

The following is an example C/C++ in which which we wish to wrap some function sum_array(...) with SWIG and provide the capability to input a NumPy array as a function parameter. Note that the function prototype includes a first paramter for the pointer to the input double array and a second parameter for the length of the array (which together form the function signature). The function prototype is given below in the sum_array.h file below file (download):

/* File sum_array.h */

/* Define function prototype to take in a NumPy array */
double sum_array(double* input_array, int length);

One possible implementation of the sum_array(...) routine is given in the sum_array.cpp file below (download):

/* File sum_array.cpp */

/* Define function implementation */
double sum_array(double* input_array, int length) {

  /* Initialize sum */
  double sum = 0.;

  /* Compute sum of array elements */
  for (int i=0; i < length; i++)
    sum += input_array[i];

  return sum;
}

The following would be the required SWIG interface file sum_array.i (download) to wrap sum_array.h into the _sum_array C/C++ extension module for Python. The second-to-last line defines the NumPy typemap - the first tuple is a pair of the typemap (array type and dimension) while the second is the function signature to match using that typemap.

%module sum_array

%{
  #define SWIG_FILE_WITH_INIT
  #include "sum_array.h"
%}

/* Include the NumPy typemaps library */
%include "numpy.i"

%init %{
  import_array();
%}

/* Typemap for the sum_list(double* input_array, int length) C/C++ routine */
%apply (double* IN_ARRAY1, int DIM1) {(double* input_array, int length)};

%include "sum_array.h"

The source code can be wrapped and compiled in similar fashion to that shown before using the following commands from the console:

$ swig -python -c++ -o sum_array_wrap.cpp sum_array.i
$ gcc -c sum_array.cpp -o sum_array.o -fpic -std=c++0x
$ gcc -I/usr/include/python2.7 -c sum_array_wrap.cpp -o sum_array.o -fpic -std=c++0x
$ g++ sum_array_wrap.o sum_array.o -o _sum_array.so -shared -Wl,-soname,_sum_array.so

Finally, the shared library extension module may be imported into Python and the routine used with a NumPy array as follows:

$ python
Python 2.7.3 (default, Sep 26 2013, 16:35:25)
[GCC 4.7.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> from numpy.random import rand
>>> from _sum_array import *
>>> input_array = rand(5)
>>> sum = sum_array(input_array)
>>> print 'The sum of the array is %d' % (sum)
The sum of the array is 2

Note

More detailed information on IN_ARRAY and INPLACE_ARRAY typemaps is provided in the official NumPy.i documetation.

2.2.2. Return NumPy Data Arrays

The numpy.i interface file (download) also provides two typemaps for returning a NumPy data array from a C/C++ routine. The ARGOUT_ARRAY* used in situations where you would allocate an array on the heap and call the function to fill the array values. In Python, the arrays are allocated for you and returned as new array objects. As was the case for array input, the * represents the number of dimensions for the input array. For example, in order to input a 3D array to be modified in-place, one would use the ARGOUT_ARRAY3 typemap. The array dimension(s) are included in each typemap through the use of the DIM* parameter.

The following is an example C/C++ in which which we wish to wrap some function get_rand_array(...) with SWIG and provide the capability to convert a C/C++ array into an output NumPy array. Based on the function signature, it would appear that the output array is input into the function and nothing is returned. Instead, SWIG will modify the source code with the NumPy C API such that a NumPy array is initialized and input as a C/C++ array and subsequently returned as a NumPy array.

The function prototype is given below in the get_rand_array.h file below (download):

/* File get_rand_array.h */
#include <stdlib.h>

/* Define function prototype to take in a NumPy array */
void get_rand_array(double* output_array, int length);

One possible implementation of the get_rand_array(...) routine is given in the get_rand_array.cpp file below (download):

/* File get_rand_array.cpp */
#include "get_rand_array.h"

/* Define function implementation */
void get_rand_array(double* output_array, int length) {

  /* Populate input NumPy array with random numbers */
  for (int i=0; i < length; i++)
    output_array[i] = ((double) rand()) / RAND_MAX;

  return;
}

The following would be the required SWIG interface file get_rand_array.i (download) to wrap get_rand_array.h into the _get_rand_array C/C++ extension module for Python. The second-to-last line defines the NumPy typemap - the first tuple is a pair of the typemap (array type and dimension) while the second is the function signature to match using that typemap.

%module get_rand_array

%{
  #define SWIG_FILE_WITH_INIT
  #include "get_rand_array.h"
%}

/* Include the NumPy typemaps library */
%include "numpy.i"

%init %{
  import_array();
%}

/* Typemap for the get_rand_array(double* output_array, int length) C/C++ routine */
%apply (double* ARGOUT_ARRAY1, int DIM1) {(double* output_array, int length)};

%include "get_rand_array.h"

The source code can be wrapped and compiled in similar fashion to that shown before using the following commands from the console:

$ swig -python -c++ -o get_rand_array_wrap.cpp get_rand_array.i
$ gcc -c get_rand_array.cpp -o get_rand_array.o -fpic -std=c++0x
$ gcc -I/usr/include/python2.7 -c get_rand_array_wrap.cpp -o get_rand_array.o -fpic -std=c++0x
$ g++ get_rand_array_wrap.o get_rand_array.o -o _get_rand_array.so -shared -Wl,-soname,_get_rand_array.so

Finally, the shared library extension module may be imported into Python and the routine used with a NumPy array as follows:

$ python
Python 2.7.3 (default, Sep 26 2013, 16:35:25)
[GCC 4.7.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> from _get_rand_array import *
>>> output_array = get_rand_array(10)
>>> print output_array
[ 0.84018773  0.39438292  0.78309923  0.79844004  0.91164738  0.19755137
  0.33522275  0.7682296   0.27777472  0.55396998]
>>> type(output_array)
<type 'numpy.ndarray'>

Note

More detailed information on the ARGOUT_ARRAY typemap is provided in the official NumPy.i documetation.

2.3. SWIG Typemaps

On some machines - such as some leadership class supercomputing clusters - NumPy may not be available. As a result it may be preferable to input/output data from Python using standard Python data structures (e.g., lists, tuples). SWIG provides it’s own library of typemaps for this purpose in the typemaps.i file which is included in the standard SWIG installation.

In order to utilize SWIG typemaps, the following include should be appended to the SWIG interface file used for the C/C++ extension module of interest:

%include "typemaps.i"

The following sections overview the basic steps to utilize SWIG typemaps to input data from Python lists into C/C++ routines, and return data from C/C++ routines as Python lists.

Note

The reader is encouraged to reference the online documentation for SWIG typemaps for more in-depth information and examples.

2.3.1. Input Python Lists

This section introduces the SWIG typemaps for inputting Python lists into C/C++ routines with a simple example. The example uses SWIG to wrap a C/C++ file to create a routine which takes in a list of floating point data and prints it to the console.

The following is the C++ header file sum_list.h for the example code (download).

/* File sum_list.h */
#include <stdio.h>

/* Define function prototype */
double sum_list(double* input_list, int length);

The corresponding function implementation for this example is given in the C++ source file sum_list.cpp below (download).

/* File sum_list.cpp */
#include "sum_list.h"

double sum_list(double* input_list, int length) {

  /* Initialize sum */
  double sum = 0.;

  /* Compute sum of array elements */
  for (int i=0; i < length; i++)
    sum += input_list[i];

  return sum;
}

The following would be the required SWIG interface file sum_list.i (download) to wrap sum_list.h into the _sum_list C/C++ extension module for Python. Writing an interface file using SWIG typemaps is more involved than it is for NumPy typemaps and requires some familiarity with the Python/C API.

%module sum_list
%{
  #define SWIG_FILE_WITH_INIT
  #include "sum_list.h"
%}

/* Include the SWIG typemaps library */
%include typemaps.i

/* Typemap the sum_list(double* input_list, int length) C/C++ routine */
%typemap(in) (double* input_list, int length) {

  /* Check that the input is a Python list data structure */
  if (!PyList_Check($input)) {
    PyErr_SetString(PyExc_ValueError,"Expected a Python list of values\n");
    return NULL;
  }

  /* Set the second parameter to the length of the Python list input */
  $2 = PySequence_Length($input);

  /* Allocate memory to convert the list into a C/C++ array */
  $1 = (double*) malloc($2 * sizeof(double));

  /* Loop over the values in the list */
  for (int i = 0; i < $2; i++) {

    /* Extract the value from the list at this location */
    PyObject *o = PySequence_GetItem($input,i);

    /* If the value is a number, cast it as an int and set the
     * input array value */
    if (PyNumber_Check(o)) {
      $1[i] = (double) PyFloat_AsDouble(o);
    }
    else {
      free($1);
      PyErr_SetString(PyExc_ValueError,"Expected a list of numbers\n");
      return NULL;
    }
  }
}

%include "sum_list.h"

The source code can be wrapped and compiled in similar fashion to that shown before using the following commands from the console:

$ swig -python -c++ -o sum_list_wrap.cpp sum_list.i
$ gcc -c sum_list.cpp -o sum_list.o -fpic -std=c++0x
$ gcc -I/usr/include/python2.7 -c sum_list_wrap.cpp -o sum_list_wrap.o -fpic -std=c++0x
$ g++ sum_list_wrap.o sum_list.o -o _sum_list.so -shared -Wl,-soname,_sum_list.so

Finally, the shared library extension module can be imported into Python and used as follows:

$ python
Python 2.7.3 (default, Sep 26 2013, 16:35:25)
[GCC 4.7.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> from _sum_list import *
>>> input_list = [2.,4.,8.,16.,32.]
>>> sum = sum_list(input_list)
>>> print 'The sum of the array is %d' % (sum)
The sum of the array is 62

2.3.2. Return Python Lists

The std_vector.i interface file provides SWIG templates for returning a Python tuple from a function returning a C++ STL std::vector data structure. The following is an example C/C++ in which we wish to wrap some function get_rand_list(...) with SWIG and provide the capability to convert a C++ std::vector into an output Python list. SWIG will modify the source code with the Python/C API such that a Python tuple is returned with the data, which will then be converted to a Python list.

The function prototype is given below in the get_rand_list.h file below (download):

/* File sum_list.h */
#include <stdio.h>
#include <vector>

/* Define function prototype */
std::vector<double> get_rand_list(int length);

One possible implementation of the get_rand_list(...) routine is given in the get_rand_list.cpp file below (download):

/* File get_rand_list.cpp */
#include "get_rand_list.h"

/* Define function implementation */
std::vector<double> get_rand_list(int length) {

  /* Allocate memory for the C++ STL vector */
  std::vector<double> output_list(length);

  /* Populate vector with random numbers */
  for (int i=0; i < length; i++)
    output_list[i] = ((double) rand()) / RAND_MAX;

  return output_list;
}

The following would be the required SWIG interface file get_rand_list.i (download) to wrap get_rand_list.h into the _get_rand_list C/C++ extension module for Python. The interface file includes the std_vector.i file and defines a SWIG template to match C++ STL vectors.

%module get_rand_list
%{
  #define SWIG_FILE_WITH_INIT
  #include "get_rand_list.h"
%}

%include "std_vector.i"

/* SWIG template for get_rand_list(int length) C++ routine */
namespace std {
  %template(DoubleVector) vector<double>;
}

%include "get_rand_list.h"

The source code can be wrapped and compiled in similar fashion to that shown before using the following commands from the console:

$ swig -python -c++ -o get_rand_list_wrap.cpp get_rand_list.i
$ gcc -c get_rand_list.cpp -o get_rand_list.o -fpic -std=c++0x
$ gcc -I/usr/include/python2.7 -c get_rand_list_wrap.cpp -o get_rand_list_wrap.o -fpic -std=c++0x
$ g++ get_rand_list_wrap.o get_rand_list.o -o _get_rand_list.so -shared -Wl,-soname,_get_rand_list.so

Finally, the shared library extension module can be imported into Python and used as follows:

$ python
Python 2.7.3 (default, Sep 26 2013, 16:35:25)
[GCC 4.7.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> from _get_rand_list import *
>>> output_list = list(get_rand_list(10))
>>> print output_list
[0.8401877171547095, 0.39438292681909304, 0.7830992237586059, 0.7984400334760733,
 0.9116473579367843, 0.19755136929338396, 0.335222755714889, 0.768229594811904,
 0.2777747108031878, 0.5539699557954305]
>>> type(output_list)
<type 'list'>

2.4. Default Parameters

It is highly recommended that developers make use of default parameters for routines when possible. Default arguments in a C++ routine are wrapped by swig given the -keyword command line option to provide keyword arguments (also known as named parameters) in the Python binding for that routine. There are several benefits for defining default arguments in the C/C++ source code:

  • Readability - Keyword arguments make code more readable, especially in example input files for users new to OpenMOC
  • Ordering - Keyword arguments can be provided in any order, lessening the burden to the user to remember a specific ordering
  • Flexibility - Function parameters with useful default values are not required at runtime, making Python scripts easier to comprehend

An example of function parameters with default values and the use of keyword arguments to override the default values in Python is given below:

# Define a function with two arguments with default values
def multiverseHelloWorld(count=5, greeting='Hello'):

  for i in range(count):
    print '%s from World %d!' % (greeting, i)

# Call routine and override default keyword arguments
# The keyword arguments can be provided in any order
multiverseHelloWorld(greeting='Hola', count=7)

Likewise, an example of how to define default values for function parameters - which will be provided through the Python interface as SWIG default arguments - in C/C++ is given below:

/* Define a function prototype with two arguments with default values */
void multiverseHelloWorld(int count=5, char* greeting="Hello");

/* Function implementation doesn't include default values in C++ */
void multiverseHelloWorld(int count, char* greeting) {

  for (int i=0; i < count; i++)
    printf("%s from World %d!", greeting, i)
}

2.5. Macros

SWIG provides preprocessing capabilities for interface files. Macro expansions may be defined in the interface file using the traditional syntax for C/C++:

#ifndef PI
#define PI 3.14159
#endif

SWIG also includes special SWIG macros with more enhanced capabilities for interface files.

2.6. Typedefs

SWIG provides functionality to define typedefs in interface files. SWIG typedefs can be defined using the same syntax as in C/C++. As discussed in the SWIG online documentation, the typedef must be defined twice in the interface file for in order for it to be propagated to the generated wrapper file:

%{
  /* Include in the generated wrapper file */
  typedef unsigned int size_t;
%}

/* Tell SWIG about it */
typedef unsigned int size_t;