Cythonで複素数を操作する正しい方法は何ですか?
Dtypenp.complex128のnumpy.ndarrayを使用して純粋なCループを記述したいと思います。 Cythonでは、関連するCタイプはCython/Includes/numpy/__init__.pxd
で次のように定義されています。
ctypedef double complex complex128_t
したがって、これは単純なC二重複素数のようです。
ただし、奇妙な動作をするのは簡単です。特に、これらの定義では
cimport numpy as np
import numpy as np
np.import_array()
cdef extern from "complex.h":
pass
cdef:
np.complex128_t varc128 = 1j
np.float64_t varf64 = 1.
double complex vardc = 1j
double vard = 1.
この線
varc128 = varc128 * varf64
cythonでコンパイルできますが、gccは生成されたCコードをコンパイルできません(エラーは「testcplx.c:663:25:エラー:宣言指定子の2つ以上のデータ型」であり、行typedef npy_float64 _Complex __pyx_t_npy_float64_complex;
が原因のようです)。このエラーはすでに報告されていますが(たとえば ここ )、適切な説明やクリーンな解決策が見つかりませんでした。
complex.h
を含めなくても、エラーは発生しません(typedef
が含まれていないためだと思います)。
ただし、cython -a testcplx.pyx
によって生成されたhtmlファイルでは、行varc128 = varc128 * varf64
が黄色であり、純粋なCに変換されていないことを意味するため、まだ問題があります。対応するCコードは次のとおりです。
__pyx_t_2 = __Pyx_c_prod_npy_float64(__pyx_t_npy_float64_complex_from_parts(__Pyx_CREAL(__pyx_v_8testcplx_varc128), __Pyx_CIMAG(__pyx_v_8testcplx_varc128)), __pyx_t_npy_float64_complex_from_parts(__pyx_v_8testcplx_varf64, 0));
__pyx_v_8testcplx_varc128 = __pyx_t_double_complex_from_parts(__Pyx_CREAL(__pyx_t_2), __Pyx_CIMAG(__pyx_t_2));
__Pyx_CREAL
と__Pyx_CIMAG
はオレンジ色です(Python呼び出し)。
興味深いことに、ライン
vardc = vardc * vard
エラーは発生せず、純粋なC(__pyx_v_8testcplx_vardc = __Pyx_c_prod(__pyx_v_8testcplx_vardc, __pyx_t_double_complex_from_parts(__pyx_v_8testcplx_vard, 0));
のみ)に変換されますが、最初のCと非常によく似ています。
中間変数を使用することでエラーを回避できます(そしてそれは純粋なCに変換されます):
vardc = varc128
vard = varf64
varc128 = vardc * vard
または単にキャストすることによって(ただし、純粋なCには変換されません):
vardc = <double complex>varc128 * <double>varf64
では、どうなるのでしょうか。コンパイルエラーの意味は何ですか?それを回避するためのクリーンな方法はありますか? np.complex128_tとnp.float64_tの乗算にPython呼び出しが含まれているように見えるのはなぜですか?
Cythonバージョン0.22(質問されたときのPypiの最新バージョン)およびGCC4.9.2。
例(hg clone https://bitbucket.org/paugier/test_cython_complex
)を使用して小さなリポジトリを作成し、3つのターゲット(make clean
、make build
、make html
)を使用して小さなMakefileを作成したので、何でも簡単にテストできます。
この問題を回避するために私が見つけることができる最も簡単な方法は、単に乗算の順序を切り替えることです。
testcplx.pyx
の場合は変更します
varc128 = varc128 * varf64
に
varc128 = varf64 * varc128
失敗した状況から説明された状況から正しく機能する状況に変更します。このシナリオは、生成されたCコードの直接差分を可能にするので便利です。
乗算の順序によって変換が変更されます。つまり、失敗したバージョンでは__pyx_t_npy_float64_complex
タイプを介して乗算が試行されますが、作業バージョンでは__pyx_t_double_complex
タイプを介して乗算が試行されます。これにより、typedef行typedef npy_float64 _Complex __pyx_t_npy_float64_complex;
が導入されますが、これは無効です。
これはcythonのバグであると確信しています(更新: ここで報告 )。 これは非常に古いgccバグレポートです ですが、応答には明示的に記載されています(実際には、gccではありません。 バグ、ただしユーザーコードエラー):
typedef R _Complex C;
これは有効なコードではありません。 _Complexをtypedefと一緒に使用することはできません。C99にリストされている形式のいずれかで、「float」、「double」、または「longdouble」と一緒にのみ使用できます。
彼らは、double _Complex
は有効な型指定子であるのに対し、ArbitraryType _Complex
はそうではないと結論付けています。 この最新のレポート 同じタイプの応答があります-非基本タイプで_Complex
を使用しようとすると仕様外になり、 GCCマニュアル は_Complex
は、float
、double
、およびlong double
でのみ使用できます。
つまり、cythonで生成されたCコードをハックして、次のことをテストできます。typedef npy_float64 _Complex __pyx_t_npy_float64_complex;
をtypedef double _Complex __pyx_t_npy_float64_complex;
に置き換え、それが実際に有効であり、出力コードをコンパイルできることを確認します。
乗算の順序を入れ替えると、コンパイラーから通知された問題が浮き彫りになります。最初のケースでは、問題のある行はtypedef npy_float64 _Complex __pyx_t_npy_float64_complex;
と書かれています-タイプnpy_float64
andキーワード_Complex
をタイプ__pyx_t_npy_float64_complex
に使用します。
float _Complex
またはdouble _Complex
は有効な型ですが、npy_float64 _Complex
は無効です。効果を確認するには、その行からnpy_float64
を削除するか、double
またはfloat
に置き換えるだけで、コードが正常にコンパイルされます。次の質問は、なぜそのラインが最初に生産されるのかということです...
これは、Cythonソースコードの この行 によって生成されているようです。
乗算の順序によってコードが大幅に変更されるのはなぜですか?タイプ__pyx_t_npy_float64_complex
が導入され、失敗する方法で導入されるようになりますか?
失敗したインスタンスでは、乗算を実装するコードがvarf64
を__pyx_t_npy_float64_complex
型に変換し、実数部と虚数部で乗算を実行してから、複素数を再構成します。作業バージョンでは、関数__pyx_t_double_complex
を使用して__Pyx_c_prod
タイプを介して製品を直接実行します。
これは、最初に遭遇した変数からの乗算にどのタイプを使用するかをキューに入れるcythonコードと同じくらい簡単だと思います。最初のケースでは、float 64を確認するため、それに基づいて(invalid)Cコードを生成しますが、2番目のケースでは、(double)complex128タイプを確認し、それに基づいて変換を行います。この説明は少し波打っています。時間が許せば、分析に戻りたいと思います...
これに関する注記-- ここに表示されますnpy_float64
のtypedef
はdouble
であるため、この特定のケースでは、修正は-の変更で構成される場合があります。 ここのコードdouble _Complex
を使用します。ここでtype
はnpy_float64
ですが、これはSO回答の範囲を超えています。一般的な解決策は示していません。
`varc128 = varf64 * varc128の行からこのCコードを作成します
__pyx_v_8testcplx_varc128 = __Pyx_c_prod(__pyx_t_double_complex_from_parts(__pyx_v_8testcplx_varf64, 0), __pyx_v_8testcplx_varc128);
varc128 = varc128 * varf64
行からこのCコードを作成します
__pyx_t_2 = __Pyx_c_prod_npy_float64(__pyx_t_npy_float64_complex_from_parts(__Pyx_CREAL(__pyx_v_8testcplx_varc128), __Pyx_CIMAG(__pyx_v_8testcplx_varc128)), __pyx_t_npy_float64_complex_from_parts(__pyx_v_8testcplx_varf64, 0));
__pyx_v_8testcplx_varc128 = __pyx_t_double_complex_from_parts(__Pyx_CREAL(__pyx_t_2), __Pyx_CIMAG(__pyx_t_2));
これらの追加のインポートが必要になります-そして問題の行はtypedef npy_float64 _Complex __pyx_t_npy_float64_complex;
と書かれています-タイプnpy_float64
andタイプ_Complex
からタイプ__pyx_t_npy_float64_complex
へ
#if CYTHON_CCOMPLEX
#ifdef __cplusplus
typedef ::std::complex< npy_float64 > __pyx_t_npy_float64_complex;
#else
typedef npy_float64 _Complex __pyx_t_npy_float64_complex;
#endif
#else
typedef struct { npy_float64 real, imag; } __pyx_t_npy_float64_complex;
#endif
/*... loads of other stuff the same ... */
static CYTHON_INLINE __pyx_t_npy_float64_complex __pyx_t_npy_float64_complex_from_parts(npy_float64, npy_float64);
#if CYTHON_CCOMPLEX
#define __Pyx_c_eq_npy_float64(a, b) ((a)==(b))
#define __Pyx_c_sum_npy_float64(a, b) ((a)+(b))
#define __Pyx_c_diff_npy_float64(a, b) ((a)-(b))
#define __Pyx_c_prod_npy_float64(a, b) ((a)*(b))
#define __Pyx_c_quot_npy_float64(a, b) ((a)/(b))
#define __Pyx_c_neg_npy_float64(a) (-(a))
#ifdef __cplusplus
#define __Pyx_c_is_zero_npy_float64(z) ((z)==(npy_float64)0)
#define __Pyx_c_conj_npy_float64(z) (::std::conj(z))
#if 1
#define __Pyx_c_abs_npy_float64(z) (::std::abs(z))
#define __Pyx_c_pow_npy_float64(a, b) (::std::pow(a, b))
#endif
#else
#define __Pyx_c_is_zero_npy_float64(z) ((z)==0)
#define __Pyx_c_conj_npy_float64(z) (conj_npy_float64(z))
#if 1
#define __Pyx_c_abs_npy_float64(z) (cabs_npy_float64(z))
#define __Pyx_c_pow_npy_float64(a, b) (cpow_npy_float64(a, b))
#endif
#endif
#else
static CYTHON_INLINE int __Pyx_c_eq_npy_float64(__pyx_t_npy_float64_complex, __pyx_t_npy_float64_complex);
static CYTHON_INLINE __pyx_t_npy_float64_complex __Pyx_c_sum_npy_float64(__pyx_t_npy_float64_complex, __pyx_t_npy_float64_complex);
static CYTHON_INLINE __pyx_t_npy_float64_complex __Pyx_c_diff_npy_float64(__pyx_t_npy_float64_complex, __pyx_t_npy_float64_complex);
static CYTHON_INLINE __pyx_t_npy_float64_complex __Pyx_c_prod_npy_float64(__pyx_t_npy_float64_complex, __pyx_t_npy_float64_complex);
static CYTHON_INLINE __pyx_t_npy_float64_complex __Pyx_c_quot_npy_float64(__pyx_t_npy_float64_complex, __pyx_t_npy_float64_complex);
static CYTHON_INLINE __pyx_t_npy_float64_complex __Pyx_c_neg_npy_float64(__pyx_t_npy_float64_complex);
static CYTHON_INLINE int __Pyx_c_is_zero_npy_float64(__pyx_t_npy_float64_complex);
static CYTHON_INLINE __pyx_t_npy_float64_complex __Pyx_c_conj_npy_float64(__pyx_t_npy_float64_complex);
#if 1
static CYTHON_INLINE npy_float64 __Pyx_c_abs_npy_float64(__pyx_t_npy_float64_complex);
static CYTHON_INLINE __pyx_t_npy_float64_complex __Pyx_c_pow_npy_float64(__pyx_t_npy_float64_complex, __pyx_t_npy_float64_complex);
#endif
#endif