web-dev-qa-db-ja.com

Cythonの複素数

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 cleanmake buildmake html)を使用して小さなMakefileを作成したので、何でも簡単にテストできます。

40
paugier

この問題を回避するために私が見つけることができる最も簡単な方法は、単に乗算の順序を切り替えることです。

testcplx.pyxの場合は変更します

varc128 = varc128 * varf64

varc128 = varf64 * varc128

失敗した状況から説明された状況から正しく機能する状況に変更します。このシナリオは、生成されたCコードの直接差分を可能にするので便利です。

tl; dr

乗算の順序によって変換が変更されます。つまり、失敗したバージョンでは__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は、floatdouble、および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_float64andキーワード_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_float64typedefdoubleであるため、この特定のケースでは、修正は-の変更で構成される場合があります。 ここのコードdouble _Complexを使用します。ここでtypenpy_float64ですが、これはSO回答の範囲を超えています。一般的な解決策は示していません。


Cコードの差分結果

作業バージョン

`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_float64andタイプ_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
21
J Richard Snape