LoginSignup
14
13

More than 5 years have passed since last update.

Python C/C++ 拡張パターン - データを np.array として Python に渡す

Last updated at Posted at 2016-05-18

C/C++ のデータを np.array として Python から見る。

struct Particle {
  float x[3], v[3];
};

vector<Particle> v;

Particle* のようなデータが C/C++ 側にあるとする。

numpy.array from C/C++

まずは numpy/arrayobject.h をインクルードして、初期化する。これは static 変数を初期化しているようで、ソースコードが複数ある場合は numpy を使う全てのソースコードで import_array() を呼ばないといけない。さもないと、segmentation fault になる。

#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION
#include "numpy/arrayobject.h"

PyMODINIT_FUNC
PyInit_cext07(void) {
  import_array();
  return PyModule_Create(&module);
}

NumPy のヘッダを読むために setup.py では include_dirs を設定する。

import numpy as np
from distutils.core import setup, Extension

setup(name='cext07',
      version='0.0.1',
      description='c_ext07 read data as np.array',
      author='Jun Koda',
      url='https://github.com/junkoda/python_c_ext',
      ext_modules=[
          Extension('cext07', ['cext07.c'],
                    include_dirs = [np.get_include()]),
      ],
)

データのありかをPythonに

Python側から参照できるように vector<Particle> のポインタを渡す関数:

static void py_particles_free(PyObject *obj);

static PyObject* py_particles_alloc(PyObject *self, PyObject *args)
{
  // Allocate a new C++ pointer as a Python object "_Particles"
  vector<Particle>* const p= new vector<Particle>();

  p->resize(10); // 仮にデータは粒子が10個

  return PyCapsule_New(p, "_Particles", py_particles_free);
}

void py_particles_free(PyObject *obj)
{
  // Destructor function for _Particles pointer, called automatically from Python
  vector<Particle>* const p=
    (vector<Particle>*) PyCapsule_GetPointer(obj, "_Particles");

  delete p;
}

データを np.array として Python に渡す

vector<Particle>np.array として渡す関数:

PyObject* py_as_array(PyObject *self, PyObject *args)
{
  PyObject* py_particles;
  if(!PyArg_ParseTuple(args, "O", &py_particles))
    return NULL;

  // Get vector<Particle>* pointer back from _Particles
  vector<Particle>* const p=
    (vector<Particle>*) PyCapsule_GetPointer(py_particles, "_Particles");
  if(!p) return NULL;

  int nd=2; // 行と列がある2次元 array
  int ncol= sizeof(Particle)/sizeof(float); // ncol=6 x[3], v[3] の6列
  npy_intp dims[]= {(npy_intp) p->size(), ncol};

  return PyArray_SimpleNewFromData(nd, dims, NPY_FLOAT, &(p->front()));
}

いつものC/C++拡張の関数

static PyMethodDef methods[] = {
  {"_particles_alloc", py_particles_alloc, METH_VARARGS, "new Particles"},
  {"_as_array", py_as_array, METH_VARARGS, "vector<Particle> as an array"},
  {NULL, NULL, 0, NULL}
};

static struct PyModuleDef module = {
  PyModuleDef_HEAD_INIT,
  "cext07",                // name of this module
  "Use vector<Particle>",  // Doc String
  -1,
  methods
};

Pythonのクラスでラッピングする

import numpy as np
import cext07

class Particles:
    def __init__(self):
        self._p= cext07._particles_alloc() # _Particle ポインタ

    def __repr__(self):
        return cext07._as_array(self._p).__repr__()

    def __getitem__(self, index):
        return cext07._as_array(self._p)[index]

    def asarray(self):
        return cext07._as_array(self._p)        
    #などなど

import cext07
p = Particles()
p        # __repr__ により array が見られる
p[0]     # __getitem__; 最初の粒子
p[2:5,2] # slice も np.array が扱ってくれる
p[:,0]   # x[0]
p[:,4]   # v[1]
p[:]     # 全データ

追記

この例なら、Python のクラスは不要で、as_array関数だけでよかった。あとで簡略化します。

全コード

全コードは GitHub にあります。

14
13
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
14
13