numpy and extending python with c

Tom Loredo loredo at astro.cornell.edu
Fri Jan 11 19:07:38 EST 2002


Les-

If you look in the NumPy documentation, there is a lot of
info on how to do this.  As a simple example, the module
below shows how to return a vector and a matrix:

>>> from mkarray import *
>>> vec3()
array([1.0, 2.0, 3.0], 'd')
>>> mat3()
array([[0.0, 1.0, 2.0], [10.0, 11.0, 12.0], [20.0, 21.0, 22.0]], 'd')
>>> 

Note the use of the DDATA macro, which I stole from some of
the NumPy code.

Good luck!
Tom Loredo

--------- mkarraymodule.c -----------

/*******************************************************************************
* mkarray: "MaKe ARRAY"
*
* Simple functions for learning how to create NumPy arrays and set
* their contents in C.
*
* Created:  07 Apr 2000  Tom Loredo
* Last mod: 11 May 2000  Cleanup error handling
*******************************************************************************/

/*******************************************************************************
* Headers, prototypes, and macros.
*******************************************************************************/

/* Include Python headers; needed for *all* extensions. */
#include "Python.h"

/* Other includes needed for this extension. */
#include "arrayobject.h"  /* Header needed to use Numeric array types. */

/* We need the math standard C library; but according
   to the NumPy folks, for the Mac we have to wrap it up with a bit of
   protection; this is done in "mymath.h" which is in the NumPy include
   directory.  This may only be needed for CFM68k builds. */
#ifdef macintosh
#include "mymath.h"
#else
#include <math.h>
#endif

/* Prototypes for functions defined and used in this extension file that aren't 
   taken care of in an include file. */
/* None are needed for this example. */

/* Error handler macro for use in any extension; adapted from Dubois & Yang. */
#define Py_TRY(BOOLEAN) {if (!(BOOLEAN)) goto FAIL;}

/* Macro to cast NumPy array data to a 1-d double array. */
#define DDATA(p) ((double *) (((PyArrayObject *)p)->data))

/*------------------------------------------------------------------------------
* Some handy error handling functions, from lapack_litemodule.c in NumPy.
------------------------------------------------------------------------------*/
static PyObject *ErrorObject;

static PyObject *mkarrayError()
{  if (! ErrorObject)
      ErrorObject = PyString_FromString("mkarrayError");
   Py_INCREF(ErrorObject);
   return ErrorObject;
}

static PyObject *ErrorReturn(char *mes)
{  if (!ErrorObject)
      ErrorObject = PyString_FromString("mkarrayError");
   PyErr_SetString(ErrorObject,mes);
   return NULL;
}

#define TRY(E) if( ! (E)) return NULL

static int NumPy_CheckObject(PyObject *ob, int t, char *obname,
        char *tname, char *funname)
{       char buf[255];
        if (! PyArray_Check(ob))
        {       sprintf(buf,"Expected an array for parameter %s in %s",obname, funname);
        ErrorReturn(buf);
        return 0;
    }
    if (!(((PyArrayObject *)ob)->flags & CONTIGUOUS)) 
    {    sprintf(buf,"Parameter %s is not contiguous in %s",obname, funname);
        ErrorReturn(buf);
        return 0;
    }
    if (!(((PyArrayObject *)ob)->descr->type_num == t))
    {    sprintf(buf,"Parameter %s is not of type %s in %s",obname,tname, funname);
        ErrorReturn(buf);
        return 0;
    }
    return 1;
}

/*------------------------------------------------------------------------------
* Make a new 2D double array, and provide a reference to its data that can
* be used as a 2D C array (i.e., pointers to pointers).
*
* Declare the reference as "double **data" and pass it as "&data".
* When done with it, free its memory with "free(*data)".
*
* This is patterned after PyArray_As2D in "arrayobject.c" in the NumPy sources.
------------------------------------------------------------------------------*/
static int
PyArray_New2DD(int n1, int n2, PyArrayObject **app, double ***data)
{
    int dims[2], i;
    PyArrayObject *ap;

/** Make a new (thus contiguous) 2D array. */
    dims[0] = n1;
    dims[1] = n2;
    ap = (PyArrayObject *)PyArray_FromDims(2, dims, PyArray_DOUBLE);
    if (ap == NULL) return -1;

/** Make a 1D array of pointers to each row. */
	*data = (double **)malloc(n1*sizeof(double *));
	for(i=0; i<n1; i++) {
		*data[i] = (double *) (ap->data + i*ap->strides[0]);
	}

/** Make the correct cast so "data" is a 2D C array. */
    *app = ap;

	return 0;
}

/*	ptrs = (double **)malloc(n1*sizeof(double *));
	for(i=0; i<n1; i++) {
		ptrs[i] = (double *) (ap->data + i*ap->strides[0]);
	}

    *data = ptrs;
    *app = ap;
*/

/*******************************************************************************
* Methods for this module.
*
* The functions that will actually be called by Python to implement the methods
* are defined here, as well as any global variables they may use to communicate
* with each other and any other "helper" functions not defined elsewhere.  The
* functions that will actually be called by Python must return PyObject 
* pointers.
*
* Be sure to define a documentation string for each method.
*
* The functions and global variables here should all be static (i.e., accessible
* only from within this module) to prevent side effects from name collisions
* outside this extension.  They can have any names (the names Python will
* use to access them are defined separately, below); they here follow a 
* convention that should be self-explanatory.  
*******************************************************************************/

/*------------------------------------------------------------------------------
* Return a 3-vector of doubles (Python floats).
------------------------------------------------------------------------------*/
static char mkarray_vec3_doc[] =
"vec3():  Return a filled 3-vector.";

static PyObject *
mkarray_vec3 (PyObject *self, PyObject *args) {
    int dims[1];
    PyArrayObject *result;
    double *data;

/** Create a 3-vector for the result. */
    dims[0] = 3;
    /* printf("creating vector of size %d...\n",dims[0]); */
    result = (PyArrayObject *)PyArray_FromDims(1, dims, PyArray_DOUBLE);
    if (result == NULL) return NULL;
    /* printf("vector created\n"); */

/** Assign values to its 3 elements. */
    if (NumPy_CheckObject((PyObject *)result, PyArray_DOUBLE, "result", "PyArray_DOUBLE", "vec3") ==
0) goto FAIL;
    data = DDATA(result);
    data[0] = 1.;
    data[1] = 2.;
    data[2] = 3.;
    

/** Return a NumPy array. */
    return PyArray_Return(result);

/** Misbehavior ends up here! */
FAIL:
    Py_XDECREF(result);
    return NULL;
}


/*------------------------------------------------------------------------------
* Return a 3x3 matrix of doubles (Python floats).
------------------------------------------------------------------------------*/
static char mkarray_mat3_doc[] =
"mat3():  Return a filled 3x3 matrix.";

static PyObject *
mkarray_mat3 (PyObject *self, PyObject *args) {
    int dims[2] = {3, 3};
    int i, j;
    PyArrayObject *result;
    double *data;

/** Create a 3x3 matrix for the result. */
    result = (PyArrayObject *)PyArray_FromDims(2, dims, PyArray_DOUBLE);
    if (result == NULL) goto FAIL;

/** Assign values to its 3 elements. */
    if (NumPy_CheckObject((PyObject *)result, PyArray_DOUBLE, "result", "PyArray_DOUBLE", "mat3") ==
0) goto FAIL;
    data = DDATA(result);
    for (i=0; i<3; i++) {
        for (j=0; j<3; j++) {
             *(data+i*3+j) = 10*i + j;
        }
    }

/** Return a NumPy array. */
    return PyArray_Return(result);

/** Misbehavior ends up here! */
FAIL:
    Py_XDECREF(result);
    return NULL;
}


/*******************************************************************************
* The methods table defining how Python accesses this module's methods.  This
* must have an element for each new method and end with the "sentinel" element.
* It should have 4 entries for each method (except the sentinel): 
*         {call name, function to call, argument type, doc string},
* with each entry ending with a comma.
*******************************************************************************/

static PyMethodDef methods[] = {
  {"vec3", mkarray_vec3, METH_VARARGS, mkarray_vec3_doc},
  {"mat3", mkarray_mat3, METH_VARARGS, mkarray_mat3_doc},
  {NULL,		NULL, 0}		/* sentinel */
};


/*******************************************************************************
* The initialization function---it must be named for the module ("init" followed
* by the module name).
*
* This should be the only non-static global object in this file.
*******************************************************************************/

void initmkarray() {
  PyObject *m;
  
  /* Create the module and add the functions */
  m = Py_InitModule("mkarray", methods);
  import_array();

  /* 
  /* Check for errors */
  if (PyErr_Occurred())
    Py_FatalError("can't initialize module mkarray");
}



More information about the Python-list mailing list