これは少しばかげているかもしれませんが、私はctypesを使用して、パラメーターとして複雑なベクトルを受け取る関数を呼び出そうとしています。しかし、ctypesにはクラスc_complexはありません。誰かがこれを解決する方法を知っていますか?
編集:他にある場合に備えて、Pythonのctypeを参照しています...。
これは少しばかげているかもしれませんが、私はctypesを使用して、パラメーターとして複雑なベクトルを受け取る関数を呼び出そうとしています。しかし、ctypesにはクラスc_complexはありません。誰かがこれを解決する方法を知っていますか?
編集:他にある場合に備えて、Pythonのctypeを参照しています...。
OPが@ArminRigoの回答に対するコメントで指摘しているように、これを行う正しい方法はラッパー関数を使用することです。また、元の質問に対するコメント(私による)に記載されているように、これはC++およびFortranでも可能です。ただし、これを3つの言語すべてで機能させる方法は、必ずしも明白ではありません。したがって、この回答は、各言語の実用的な例を示しています。
スカラー複素数の引数を取るC/C ++/Fortranプロシージャがあるとします。次に、2つのfloat / double(実数部と虚数部)を取り、それらを複素数に結合してから元のプロシージャを呼び出すラッパープロシージャを作成する必要があります。明らかに、これは複素数の配列に拡張できますが、今のところ、単一の複素数で単純に保ちましょう。
たとえば、フォーマットされた複素数を出力するC関数があるとします。だから私たちは持っていますmy_complex.c
:
#include <stdio.h>
#include <complex.h>
void print_complex(double complex z)
{
printf("%.1f + %.1fi\n", creal(z), cimag(z));
}
次に、次のようなラッパー関数を(同じファイルの最後に)追加する必要があります。
void print_complex_wrapper(double z_real, double z_imag)
{
double complex z = z_real + z_imag * I;
print_complex(z);
}
通常の方法でライブラリにコンパイルします。
gcc -shared -fPIC -o my_complex_c.so my_complex.c
次に、Pythonからラッパー関数を呼び出します。
from ctypes import CDLL, c_double
c = CDLL('./my_complex_c.so')
c.print_complex_wrapper.argtypes = [c_double, c_double]
z = complex(1.0 + 1j * 2.0)
c.print_complex_wrapper(c_double(z.real), c_double(z.imag))
extern "C"
名前のマングリングを回避するためにインターフェイスを定義する必要があり、Pythonでクラスを処理する必要があるため(このSO Q&Aのように) 、C++でも同じことが少し厄介です。
これで、my_complex.cpp
(ラッパー関数をすでに追加しました)次のようになりました。
#include <stdio.h>
#include <complex>
class ComplexPrinter
{
public:
void printComplex(std::complex<double> z)
{
printf("%.1f + %.1fi\n", real(z), imag(z));
}
void printComplexWrapper(double z_real, double z_imag)
{
std::complex<double> z(z_real, z_imag);
printComplex(z);
}
};
extern "C"
また、次のようなインターフェイスを(同じファイルの最後に)追加する必要があります。
extern "C"
{
ComplexPrinter* ComplexPrinter_new()
{
return new ComplexPrinter();
}
void ComplexPrinter_printComplexWrapper(ComplexPrinter* printer, double z_real, double z_imag)
{
printer->printComplexWrapper(z_real, z_imag);
}
}
通常の方法でライブラリにコンパイルします。
g++ -shared -fPIC -o my_complex_cpp.so my_complex.cpp
そして、Pythonからラッパーを呼び出します。
from ctypes import CDLL, c_double
cpp = CDLL('./my_complex_cpp.so')
cpp.ComplexPrinter_printComplexWrapper.argtypes = [c_double, c_double]
class ComplexPrinter:
def __init__(self):
self.obj = cpp.ComplexPrinter_new()
def printComplex(self, z):
cpp.ComplexPrinter_printComplexWrapper(c_double(z.real), c_double(z.imag))
printer = ComplexPrinter()
z = complex(1.0 + 1j * 2.0)
printer.printComplex(z)
そして最後に、Fortranには元のサブルーチンがありmy_complex.f90
ます:
subroutine print_complex(z)
use iso_c_binding, only: c_double_complex
implicit none
complex(c_double_complex), intent(in) :: z
character(len=16) :: my_format = "(f4.1,a3,f4.1,a)"
print my_format, real(z), " + ", aimag(z), "i"
end subroutine print_complex
(同じファイルの最後に)ラッパー関数を追加します。
subroutine print_complex_wrapper(z_real, z_imag) bind(c, name="print_complex_wrapper")
use iso_c_binding, only: c_double, c_double_complex
implicit none
real(c_double), intent(in) :: z_real, z_imag
complex(c_double_complex) :: z
z = cmplx(z_real, z_imag)
call print_complex(z)
end subroutine print_complex_wrapper
次に、通常の方法でライブラリにコンパイルします。
gfortran -shared -fPIC -o my_complex_f90.so my_complex.f90
そしてPythonから呼び出します(POINTERの使用に注意してください):
from ctypes import CDLL, c_double, POINTER
f90 = CDLL('./my_complex_f90.so')
f90.print_complex_wrapper.argtypes = [POINTER(c_double), POINTER(c_double)]
z = complex(1.0 + 1j * 2.0)
f90.print_complex_wrapper(c_double(z.real), c_double(z.imag))
上記の@Biggsyの回答は、c / fortranで記述された追加のラッパーが必要なため、あまり好きではありません。単純なケースでは問題ではありませんが、外部ライブラリ(lapackなど)を使用する場合は、呼び出す関数/ライブラリごとにプロキシdllを作成する必要があり、おそらく面白くなく、多くのことがあります。うまくいかない可能性があります(リンケージ、再利用性など)。私のアプローチは、ctypesとnumpyを使用したPythonのみに依存しています。これがお役に立てば幸いです。
以下の例では、Python、numpy(どちらも基本的にC)、純粋なCまたはFortran(Fortranを使用していますが、Cの場合はPython側でもまったく同じです)の間で複素数/配列を渡す方法を示しています。
まず、Cインターフェイスを使用してダミーのFortran(f90)ライブラリ(f.f90など)を作成します。
! print complex number
subroutine print_c(c) bind(c)
use iso_c_binding
implicit none
complex(c_double_complex), intent(in) :: c
print "(A, 2F20.16)", "@print_c, c=", c
end subroutine print_c
! multiply real numbers
real(c_double) function mtp_rr(r1,r2) bind(c)
use iso_c_binding
implicit none
real(c_double), intent(in) :: r1,r2
print "(A)", "@mpt_rr" ! make sure its fortran
mtp_rr = r1 * r2
end function mtp_rr
! multiply complex numbers
complex(c_double_complex) function mtp_cc(c1,c2) bind(c)
use iso_c_binding
implicit none
complex(c_double_complex), intent(in) :: c1,c2
print "(A)", "@mpt_cc" ! make sure its fortran
mtp_cc = c1 * c2
return
end function mtp_cc
! print array of complex numbers
subroutine print_carr(n, carr) bind(c)
use iso_c_binding
implicit none
integer(c_long), intent(in) :: n
integer(c_long) :: i
complex(c_double_complex), intent(in) :: carr(n)
print "(A)", "@print_carr"
do i=1,n
print "(I5,2F20.16)", i, carr(i)
end do
end subroutine print_carr
ライブラリは通常どおり簡単にコンパイルできます(この例ではlibf.soにコンパイルします)。各サブルーチンには独自の「use」ステートメントが含まれていることに注意してください。モジュールレベルで「useiso_c_binding」を宣言すると、これを回避できます。(gfortranがiso_c_bindingをグローバルに使用せずに関数のタイプを理解する理由はわかりませんが、機能し、私にとっては問題ありません。)
gfortran -shared -fPIC f.f90 -o libf.so
次に、次の内容のPythonスクリプト(p.pyなど)を作成しました。
#!/usr/bin/env python3
import ctypes as ct
import numpy as np
## ctypes 1.1.0
## numpy 1.19.5
# print("ctypes", ct.__version__)
# print("numpy", np.__version__)
from numpy.ctypeslib import ndpointer
## first we prepare some datatypes
c_double = ct.c_double
class c_double_complex(ct.Structure):
"""complex is a c structure
https://docs.python.org/3/library/ctypes.html#module-ctypes suggests
to use ctypes.Structure to pass structures (and, therefore, complex)
"""
_fields_ = [("real", ct.c_double),("imag", ct.c_double)]
@property
def value(self):
return self.real+1j*self.imag # fields declared above
c_double_complex_p = ct.POINTER(c_double_complex) # pointer to our complex
## pointers
c_long_p = ct.POINTER(ct.c_long) # pointer to long (python `int`)
c_double_p = ct.POINTER(ct.c_double) # similar to ctypes.c_char_p, i guess?
# ndpointers work well with unknown dimension and shape
c_double_complex_ndp = ndpointer(np.complex128, ndim=None)
### ct.c_double_complex
### > AttributeError: module 'ctypes' has no attribute 'c_double_complex'
## then we prepare some dummy functions to simplify argument passing
b_long = lambda i: ct.byref(ct.c_long(i))
b_double = lambda d: ct.byref(ct.c_double(d))
b_double_complex = lambda c: ct.byref(c_double_complex(c.real, c.imag))
## now we load the library
libf = ct.CDLL("libf.so")
## and define IO types of its functions/routines
print_c = libf.print_c
print_c.argtypes = [c_double_complex_p] # fortran accepes only references
print_c.restype = None # fortran subroutine (c void)
mtp_rr = libf.mtp_rr
mtp_rr.argtypes = [c_double_p, c_double_p]
mtp_rr.restype = c_double # ctypes.c_double
mtp_cc = libf.mtp_cc
mtp_cc.argtypes = [c_double_complex_p, c_double_complex_p]
mtp_cc.restype = c_double_complex # custom c_double_complex
print_carr = libf.print_carr
print_carr.argtypes = [c_long_p, c_double_complex_ndp]
print_carr.restype = None
## finally we can prepare some data and test what we got
print("test print_c")
c = 5.99j+3.1234567890123456789
print_c(b_double_complex(c)) # directly call c/fortran function
print(c)
print("\ntest mtp_rr")
d1 = 2.2
d2 = 3.3
res_d = mtp_rr(b_double(d1),b_double(d2))
print("d1*d2 =", res_d)
print("\ntest mtp_cc")
c1 = 2j+3
c2 = 3j
res = mtp_cc(b_double_complex(c1), b_double_complex(c2))
print(res.value)
print("\ntest print_carr")
n = 10
arr = np.arange(n) + 10j * np.arange(10)
print("arr.shape =",arr.shape)
print_carr(b_long(n), arr)
arr = arr.reshape((5,2)) # still contiguous chunk of memory
print("arr.shape =",arr.shape)
print_carr(b_long(n), arr) # same result
print("done")
そしてすべてが機能します。このようなものがctypesにまだ実装されていない理由がわかりません。
ここで他の提案にも関連しています:新しいctypesタイプを作成することで複素数を渡すことができます
__c_double_complex_p = ct.POINTER(2*ct.c_double)
dll.some_function.argtypes = [__c_double_complex_p, ...]
しかし、その方法で結果を取得することはできません!クラスを介して適切なrestypeのみのメソッドを設定することは私のために働いた。
「_Complexdouble」などのC99複合型を意味していると思います。これらのタイプは、ctypesによってネイティブにサポートされていません。たとえば、そこでの議論を参照してください:https ://bugs.python.org/issue16899
c_complexがC構造体であり、ドキュメントまたはヘッダーファイルに定義がある場合は、ctypesを使用して互換性のある型を作成できます。可能性は低いですが、c_complexがctypesがすでにサポートしているタイプのtypdefである可能性もあります。
より良い答えを提供するには、より多くの情報が必要になります...
c_doubleまたはc_floatを2回使用します。1回は実数、もう1回は虚数です。例えば:
from ctypes import c_double, c_int
dimType = c_int * 1
m = dimType(2)
n = dimType(10)
complexArrayType = (2 * c_double) * ( n * m )
complexArray = complexArrayType()
status = yourLib.libcall(m,n,complexArray)
ライブラリ側(Fortranの例):
subroutine libcall(m,n,complexArrayF) bind(c, name='libcall')
use iso_c_binding, only: c_double_complex,c_int
implicit none
integer(c_int) :: m, n
complex(c_double_complex), dimension(m,n) :: complexArrayF
integer :: ii, jj
do ii = 1,m
do jj = 1,n
complexArrayF(ii,jj) = (1.0, 0.0)
enddo
enddo
end subroutine