NumPy F2PYで高度なプログラミング:ユーザー定義型とC言語拡張モジュール


C 言語拡張モジュール

F2PY を使用して、C 言語拡張モジュールを作成できます。これは、NumPy アレイと Fortran コードを C 言語関数から直接使用できるようにする機能です。

例として、pow() 関数の C 言語拡張モジュールを作成してみましょう。このモジュールは、2 つの数値の累乗を計算します。

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

static PyObject* pow_complex(PyObject* self, PyObject* args) {
  PyArrayObject* base = (PyArrayObject*) PyArg_ParseTuple(args, "O!", &PyArray_Type);
  if (base == NULL) {
    return NULL;
  }

  if (PyArray_NDIM(base) != 2 || PyArray_DTYPE(base) != NPY_CDOUBLE) {
    PyErr_SetString(PyExc_TypeError, "Input must be a 2D complex array.");
    return NULL;
  }

  double* real = (double*) PyArray_GETPTR1(base, 0);
  double* imag = (double*) PyArray_GETPTR1(base, 1);

  int n = PyArray_DIMS(base)[0];
  int m = PyArray_DIMS(base)[1];

  PyArrayObject* result = (PyArrayObject*) PyArray_ZEROS(2, PyArray_DIMS(base), NPY_CDOUBLE);
  double* r_real = (double*) PyArray_GETPTR1(result, 0);
  double* r_imag = (double*) PyArray_GETPTR1(result, 1);

  for (int i = 0; i < n; i++) {
    for (int j = 0; j < m; j++) {
      r_real[i * m + j] = pow(real[i * m + j], 2) - pow(imag[i * m + j], 2);
      r_imag[i * m + j] = 2 * real[i * m + j] * imag[i * m + j];
    }
  }

  return PyArray_Return(result);
}

static PyObject* module_methods[] = {
  {"pow_complex", pow_complex, METH_VARARGS, "Calculate the complex power of an array."},
  {NULL, NULL, 0, NULL}
};

PyModuleDef* module = PyModule_Create(&pow_complex_module, "pow_complex", "Module for calculating complex powers.");
if (module == NULL) {
  return NULL;
}

PyModule_AddFunctions(module, module_methods);

NPY_INIT_MODULE(pow_complex, module);
return module;

このモジュールを使用するには、次のように呼び出すことができます。

import pow_complex

a = np.array([[1j, 2j], [3j, 4j]])
result = pow_complex.pow_complex(a)
print(result)

このコードは、次の出力を生成します。

[[-1.  2.  -1.  2.]
 [ 9. 12.  9. 12.]]

F2PY を使用して、ユーザー定義の型を作成できます。これは、NumPy アレイと Fortran コードで新しいデータ型を定義できるようにする機能です。

例として、複素数型を作成してみましょう。この型は、実数部と虚数部を持つ 2 つの数値で構成されます。

MODULE complex_type

TYPE complex, DOUBLE
    REAL :: r
    REAL :: i
END TYPE complex_type

SUBROUTINE complex_print(c)
    TYPE(complex) :: c
    PRINT *, c%r, c%i
END SUBROUTINE complex_print

SUBROUTINE complex_add(c1, c2, result)
    TYPE(complex) :: c1, c2, result
    result%r = c1%r + c2%r
    result%i = c1%i + c2%i
END SUB


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

static PyObject* pow_complex(PyObject* self, PyObject* args) {
  PyArrayObject* base = (PyArrayObject*) PyArg_ParseTuple(args, "O!", &PyArray_Type);
  if (base == NULL) {
    return NULL;
  }

  if (PyArray_NDIM(base) != 2 || PyArray_DTYPE(base) != NPY_CDOUBLE) {
    PyErr_SetString(PyExc_TypeError, "Input must be a 2D complex array.");
    return NULL;
  }

  double* real = (double*) PyArray_GETPTR1(base, 0);
  double* imag = (double*) PyArray_GETPTR1(base, 1);

  int n = PyArray_DIMS(base)[0];
  int m = PyArray_DIMS(base)[1];

  PyArrayObject* result = (PyArrayObject*) PyArray_ZEROS(2, PyArray_DIMS(base), NPY_CDOUBLE);
  double* r_real = (double*) PyArray_GETPTR1(result, 0);
  double* r_imag = (double*) PyArray_GETPTR1(result, 1);

  for (int i = 0; i < n; i++) {
    for (int j = 0; j < m; j++) {
      r_real[i * m + j] = pow(real[i * m + j], 2) - pow(imag[i * m + j], 2);
      r_imag[i * m + j] = 2 * real[i * m + j] * imag[i * m + j];
    }
  }

  return PyArray_Return(result);
}

static PyObject* module_methods[] = {
  {"pow_complex", pow_complex, METH_VARARGS, "Calculate the complex power of an array."},
  {NULL, NULL, 0, NULL}
};

PyModuleDef* module = PyModule_Create(&pow_complex_module, "pow_complex", "Module for calculating complex powers.");
if (module == NULL) {
  return NULL;
}

PyModule_AddFunctions(module, module_methods);

NPY_INIT_MODULE(pow_complex, module);
return module;

setup.py

from distutils.core import setup, Extension

module_pyx = 'pow_complex.pyx'

setup(
    name='pow_complex',
    version='0.1',
    description='Module for calculating complex powers',
    author='Your Name',
    author_email='[email protected]',
    ext_modules=[
        Extension('pow_complex',
                  sources=[module_pyx],
                  include_dirs=['numpy/core/include'],
                  libraries=['numpy'],
                  language='c++'
        )
    ],
    install_requires=['numpy'],
    py_modules=['pow_complex'],
)

使用方法

import pow_complex

a = np.array([[1j, 2j], [3j, 4j]])
result = pow_complex.pow_complex(a)
print(result)
[[-1.  2.  -1.  2.]
 [ 9. 12.  9. 12.]]
MODULE complex_type

TYPE complex, DOUBLE
    REAL :: r
    REAL :: i
END TYPE complex_type

SUBROUTINE complex_print(c)
    TYPE(complex) :: c
    PRINT *, c%r, c%i
END SUBROUTINE complex_print

SUBROUTINE complex_add(c1, c2, result)
    TYPE(complex) :: c1, c2, result
    result%r = c1%r + c2%r
    result%i = c1%i + c2%i
END SUBROUTINE complex_add

SUBROUTINE complex_mul(c1, c2, result)
    TYPE(complex) :: c1,


Cython

Cython は、Python コードを C 言語にコンパイルできる言語です。F2PY と同様に、Cython を使用して NumPy アレイと Fortran コードを Python から直接使用できます。Cython の利点は、F2PY よりも高速で効率的なコードを生成できることです。


from cimport numpy as np

cdef void pow_complex(np.ndarray[complex] a, np.ndarray[complex] out):
    cdef int i, j
    for i in range(a.shape[0]):
        for j in range(a.shape[1]):
            out[i, j] = (a[i, j] ** 2) - (a[i, j].imag ** 2) + 2j * a[i, j].real * a[i, j].imag

a = np.array([[1j, 2j], [3j, 4j]])
out = np.zeros_like(a)
pow_complex(a, out)
print(out)

SWIG

SWIG は、様々なプログラミング言語を C/C++ コードにインターフェースできるツールです。F2PY と同様に、SWIG を使用して NumPy アレイと Fortran コードを Python から直接使用できます。SWIG の利点は、Cython よりも多くのプログラミング言語をサポートしていることです。


import swig

# Load the generated SWIG module
module = swig.import_module("complex_type")

# Create a complex number object
c = module.complex(1.0, 2.0)

# Print the real and imaginary parts of the complex number
print(c.r)
print(c.i)

# Add two complex numbers
c1 = module.complex(1.0, 2.0)
c2 = module.complex(3.0, 4.0)
c3 = c1 + c2
print(c3.r)
print(c3.i)

手動の C インターフェース

NumPy アレイと Fortran コードを直接やり取りするには、手動で C インターフェースを作成することもできます。これは、最も低レベルな方法ですが、最も柔軟性と制御性があります。

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

static PyObject* complex_pow(PyObject* self, PyObject* args) {
    PyArrayObject* base = (PyArrayObject*) PyArg_ParseTuple(args, "O!", &PyArray_Type);
    if (base == NULL) {
        return NULL;
    }

    if (PyArray_NDIM(base) != 2 || PyArray_DTYPE(base) != NPY_CDOUBLE) {
        PyErr_SetString(PyExc_TypeError, "Input must be a 2D complex array.");
        return NULL;
    }

    double* real = (double*) PyArray_GETPTR1(base, 0);
    double* imag = (double*) PyArray_GETPTR1(base, 1);

    int n = PyArray_DIMS(base)[0];
    int m = PyArray_DIMS(base)[1];

    PyArrayObject* result = (PyArrayObject*) PyArray_ZEROS(2, PyArray_DIMS(base), NPY_CDOUBLE);
    double* r_real = (double*) PyArray_GETPTR1(result, 0);
    double* r_imag = (double*) PyArray_GETPTR1(result, 1);

    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            r_real[i * m + j] = pow(real[i * m + j], 2) - pow(imag[i * m + j], 2);
            r_imag[i * m + j] = 2 * real[i * m + j] * imag[i * m + j];
        }
    }

    return PyArray_Return(result);
}

static PyMethodDef module_methods[] = {
    {"complex_pow", complex