ナンクル力学系

学んだ事を書き連ねていこう。

Numpy 配列の添字チェックをCのマクロに埋め込んでon/offする方法

leave a comment »

こんなものを作って見ましたよ,という話:

  • デバッグ時だけ添字チェック出来る
    • 添字チェックのon/off切り替えはマクロで一発
  • マクロを使う方のソースでは,デバッグをあまり意識しないで使える
    • ...って言うのは無理があるかもw

マクロのフルバージョンは http://gist.github.com/119216 で.

ソース

 #include <Python.h>
 #include "structmember.h"
 #include <numpy/arrayobject.h>

 #ifdef NUMPY_DEBUG
/* ========================================================== Debugging NumPy */
static void *NumPyDebug_CheckAndGetPtr(PyArrayObject *aobj,
                                       char *name, int ndim, int *ind)
{
  int i;
  void *dptr = PyArray_DATA(aobj);
  PyGILState_STATE gstate;

  if(ndim != PyArray_NDIM(aobj)){
    /* specified number of dimentions is not same of its of array */
    gstate = PyGILState_Ensure();/* get GIL to raise IndexError */
    PyErr_Format(PyExc_IndexError,
                 "degree of array is not correct. %s->nd = %d != ndim =%d\n",
                 name, (int)PyArray_NDIM(aobj), ndim);
    PyGILState_Release(gstate);
  }
  for(i=0; i<ndim; ++i){
    if(ind&#91;i&#93; >= PyArray_DIM(aobj,i)){
      /* index ind[i] is out of range */
      gstate = PyGILState_Ensure();/* get GIL to raise IndexError */
      PyErr_Format(PyExc_IndexError,
                   "array %s's %d-th index %d is out of bounds(=%d).\n",
                   name, i, ind[i], (int)PyArray_DIM(aobj,i));
      PyGILState_Release(gstate);
      break;
    }
    dptr += ind[i] * PyArray_STRIDE(aobj, i);
  }
  return dptr;
}
static void *NumPyDebug_CheckAndGetPtr2(PyArrayObject *aobj, char *name,
                                        int i, int j)
{
  int ind[] = {i,j};
  return NumPyDebug_CheckAndGetPtr(aobj, name, 2, ind);
}
static int NumPyDebug_CheckError(void)
{
  int return_val=0;
  PyGILState_STATE gstate;
  gstate = PyGILState_Ensure();/* get GIL to check IndexError */
  if(PyErr_Occurred()!=NULL){/* error is occured */
    return_val = -1;
  }
  PyGILState_Release(gstate);
  return return_val;
}
 #define NPD_IF_ERR_OCCURRED_DO(dothis) \
  if(NumPyDebug_CheckError()<0){ dothis; }
 #define NPD_CHECKANDGETPTR2(a,i,j) NumPyDebug_CheckAndGetPtr2(a,#a,i,j)
 #define Da2(a, i, j) (*((double *) NPD_CHECKANDGETPTR2(a, i, j)))

 #else
/* ============================================================== Using NumPy */
 #define NPD_IF_ERR_OCCURRED_DO(dothis)
 #define Da2(a, i, j) (*((double *) PyArray_GETPTR2(a, i, j)))
 #endif

static int _check_array(PyObject *array, char *array_name,
                        int ndim, int type, char *type_name)
{
  if ( PyArray_Check(array) != 1 ){
    PyErr_Format( PyExc_ValueError, "%s is not of type numpy array",
                  array_name);
    return -1;
  }
  if ( PyArray_NDIM(array) != ndim ){
    PyErr_Format( PyExc_ValueError, "%s is not %d-dim array",
                  array_name, ndim);
    return -1;
  }
  if ( PyArray_TYPE(array) != type ){
    PyErr_Format( PyExc_ValueError, "%s is not of type %s",
                  array_name, type_name);
    return -1;
  }
  return 0;
}

 #define CHECK_ARRAY(array, ndim, type)         \
  _check_array(array, #array, ndim, type, #type)

static PyObject *
mult2d(PyObject *dummy, PyObject *args)
{
  int i, j, *dims;
  PyObject *o1, *o2;
  PyArrayObject *a1, *a2, *a3;

  if (PyArg_ParseTuple(args, "OO:mult2d", &o1, &o2) < 0) {
    PyErr_SetString( PyExc_TypeError,  "bad arguments");
    return NULL;
  }
  if ( CHECK_ARRAY(o1, 2, NPY_DOUBLE) < 0 ){
    return NULL;
  }
  if ( CHECK_ARRAY(o2, 2, NPY_DOUBLE) < 0 ){
    return NULL;
  }
  a1 = (PyArrayObject *)o1;
  a2 = (PyArrayObject *)o2;

  dims = PyArray_DIMS(o1);
  a3 = (PyArrayObject *)PyArray_SimpleNew(2, dims, NPY_DOUBLE);
  if (a3 == NULL) {
    return NULL;
  }

  for (i = 0; i < dims&#91;0&#93;; ++i){
    for (j = 0; j < dims&#91;1&#93;; ++j){
      Da2(a3,i,j) = Da2(a1,i,j) * Da2(a2,i,j);
      /* check if error occured when the macro NUMPY_DEBUG is set */
      NPD_IF_ERR_OCCURRED_DO( Py_XDECREF(a3);
                              a3 = NULL;
                              goto _post_process )
    }
  }

 _post_process:
  /* some post process here */
  return (PyObject *)a3;
}

static PyMethodDef module_methods&#91;&#93; = {
  {"mult2d", (PyCFunction)mult2d, METH_VARARGS, "2d array multiplication"},
  {NULL}  /* Sentinel */
};

 #ifndef PyMODINIT_FUNC /* declarations for DLL import/export */
 #define PyMODINIT_FUNC void
 #endif
PyMODINIT_FUNC
initarraydebug(void)
{
  (void) Py_InitModule("arraydebug", module_methods);
  import_array();   /* required NumPy initialization */
}
&#91;/sourcecode&#93;</div>
</div>
<div id="outline-container-9.1.2">
<h4>setup.py</h4>
<div id="text-9.1.2">
from distutils.core import setup, Extension

module1 = Extension(
        'arraydebug',
        sources = ['arraydebugmodule.c']
)

setup(
        name = 'my',
        ext_modules = [module1],
)

コンパイル

  • 使う時
    • python setup.py build_ext -i -f
  • デバッグする時
    • python setup.py build_ext -i -f -DNUMPY_DEBUG

ちなみに, -i は–inplaceの略でその場に*.soを作る, -f はソースに変更が無くてもコンパイルする, -D[MACRO] は [MACRO] をCのマクロにセットする というオプション.

実行用のスクリプト:

import numpy
import arraydebug

a1 = numpy.arange( 10, dtype=float ).reshape(2,5)
a2 = a1
print "a1"
print a1
print "a2"
print a2
print

print "a3"
print arraydebug.mult2d(a1, a2)

print "---"
a2 = a2.reshape(5,2)

print "a1"
print a1
print "a2"
print a2
print

# トータルの要素数は同じなので,普通のコンパイルだとエラーは起きない
print "a3"
print arraydebug.mult2d(a1, a2)

普通にコンパイル (-DNUMPY_DEBUG なし) した時の実行結果

a1
[[ 0.  1.  2.  3.  4.]
 [ 5.  6.  7.  8.  9.]]
a2
[[ 0.  1.  2.  3.  4.]
 [ 5.  6.  7.  8.  9.]]

a3
[[  0.   1.   4.   9.  16.]
 [ 25.  36.  49.  64.  81.]]
---
a1
[[ 0.  1.  2.  3.  4.]
 [ 5.  6.  7.  8.  9.]]
a2
[[ 0.  1.]
 [ 2.  3.]
 [ 4.  5.]
 [ 6.  7.]
 [ 8.  9.]]

a3
[[  0.   1.   4.   9.  16.]
 [ 10.  18.  28.  40.  54.]]

デバッグ時 (-DNUMPY_DEBUG あり) の実行結果

a1
[[ 0.  1.  2.  3.  4.]
 [ 5.  6.  7.  8.  9.]]
a2
[[ 0.  1.  2.  3.  4.]
 [ 5.  6.  7.  8.  9.]]

a3
[[  0.   1.   4.   9.  16.]
 [ 25.  36.  49.  64.  81.]]
---
a1
[[ 0.  1.  2.  3.  4.]
 [ 5.  6.  7.  8.  9.]]
a2
[[ 0.  1.]
 [ 2.  3.]
 [ 4.  5.]
 [ 6.  7.]
 [ 8.  9.]]

a3
Traceback (most recent call last):
  File "mult2d.py", line 25, in <module>
    print arraydebug.mult2d(a1, a2)
IndexError: array a2's 1-th index 4 is out of bounds(=2).

Written by tkf

May 28, 2009 at 7:41 pm

Posted in PC

Tagged with ,

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: