Cython中的复数

pau*_*ier 39 c python numpy cython complex-numbers

在Cython中处理复杂数字的正确方法是什么?

我想用dtype np.complex128的numpy.ndarray编写一个纯C循环.在Cython中,关联的C类型定义 Cython/Includes/numpy/__init__.pxd

ctypedef double complex complex128_t
Run Code Online (Sandbox Code Playgroud)

所以看起来这只是一个简单的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.
Run Code Online (Sandbox Code Playgroud)

这条线

varc128 = varc128 * varf64
Run Code Online (Sandbox Code Playgroud)

可以通过Cython编译,但gcc无法编译生成的C代码(错误是"testcplx.c:663:25:错误:声明说明符中的两个或更多数据类型",似乎是由于该行typedef npy_float64 _Complex __pyx_t_npy_float64_complex;).此错误已经报告(例如此处)但我没有找到任何好的解释和/或清洁解决方案.

没有包含complex.h,没有错误(我猜因为typedef那时不包括在内).

但是,仍然存在一个问题,因为在生成的html文件中cython -a testcplx.pyx,该行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));
Run Code Online (Sandbox Code Playgroud)

__Pyx_CREAL__Pyx_CIMAG是橙色(Python的调用).

有趣的是,这条线

vardc = vardc * vard
Run Code Online (Sandbox Code Playgroud)

不产生任何错误并被转换为纯C(只是__pyx_v_8testcplx_vardc = __Pyx_c_prod(__pyx_v_8testcplx_vardc, __pyx_t_double_complex_from_parts(__pyx_v_8testcplx_vard, 0));),而它与第一个非常相似.

我可以通过使用中间变量来避免错误(并将其转换为纯C):

vardc = varc128
vard = varf64
varc128 = vardc * vard
Run Code Online (Sandbox Code Playgroud)

或者只是通过铸造(但它不会转化为纯C):

vardc = <double complex>varc128 * <double>varf64
Run Code Online (Sandbox Code Playgroud)

那会发生什么?编译错误的含义是什么?有一个干净的方法来避免它吗?为什么np.complex128_t和np.float64_t的乘法似乎涉及Python调用?

版本

Cython版本0.22(询问问题时Pypi的最新版本)和GCC 4.9.2.

知识库

我创建的例子(一个小库hg clone https://bitbucket.org/paugier/test_cython_complex),并用3个目标(一个微小的Makefile make clean,make build,make html),所以很容易测试任何东西.

J R*_*ape 20

我可以找到解决这个问题的最简单方法是简单地切换乘法的顺序.

如果在testcplx.pyx我改变

varc128 = varc128 * varf64
Run Code Online (Sandbox Code Playgroud)

varc128 = varf64 * varc128
Run Code Online (Sandbox Code Playgroud)

我从失败的情况改为描述为正常的情况.这种情况很有用,因为它允许生成的C代码的直接差异.

TL;博士

乘法的顺序改变了转换,这意味着在失败的版本中,乘法是通过__pyx_t_npy_float64_complex类型尝试的,而在工作版本中,它是通过__pyx_t_double_complex类型完成的.这又引入了typedef行typedef npy_float64 _Complex __pyx_t_npy_float64_complex;,该行无效.

我很确定这是一个cython bug(更新:在这里报道).虽然这是一个非常古老的gcc错误报告,但响应明确指出(事实上,它不是gcc错误,而是用户代码错误):

typedef R _Complex C;
Run Code Online (Sandbox Code Playgroud)

这不是有效的代码; 您不能将_Complex与typedef一起使用,只能与C99中列出的其中一种形式的"float","double"或"long double"一起使用.

他们得出结论,这double _Complex是一个有效的类型说明符,而ArbitraryType _Complex不是. 这个更新的报告具有相同类型的响应 - 尝试_Complex在非基本类型上使用是在规范之外,并且GCC手册指示_Complex只能使用float,doublelong 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 使用关键字_Complex到类型__pyx_t_npy_float64_complex.

float _Complex或者double _Complex是有效类型,而npy_float64 _Complex不是.要看到效果,你可以直接删除npy_float64从该行,或是更换doublefloat和代码编译罚款.接下来的问题是为什么这条生产线首先生产出来......

这似乎是由Cython源代码中的这一行产生的.

为什么乘法的顺序会显着改变代码 - 这样__pyx_t_npy_float64_complex引入类型,并以失败的方式引入?

在失败的实例中,实现乘法的代码变为varf64一种__pyx_t_npy_float64_complex类型,对实部和虚部进行乘法,然后重新组合复数.在工作版本中,它通过__pyx_t_double_complex使用该功能的类型直接执行产品__Pyx_c_prod

我想这就像cython代码一样简单,它从哪个类型用于从它遇到的第一个变量中进行乘法.在第一种情况下,它会看到一个浮点数64,因此根据它生成(无效的)C代码,而在第二种情况下,它会看到(double)complex128类型并将其转换基于该类型.这个解释有点波动,如果时间允许,我希望回到它的分析......

基于此的一个说明- 在这里我们看到的是typedefnpy_float64double,所以在这种特殊情况下,修复可能包括修改的这里的代码使用的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);
Run Code Online (Sandbox Code Playgroud)

失败的版本

从该行创建此C代码 varc128 = varc128 * varf64

__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));
Run Code Online (Sandbox Code Playgroud)

这需要这些额外的导入 - 并且违规行是说typedef npy_float64 _Complex __pyx_t_npy_float64_complex;- 它试图将类型npy_float64 类型分配给_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
Run Code Online (Sandbox Code Playgroud)

  • @taleinat 你启发了我创建一张票 - 引用一个旧的,它对同一个问题有不同的角度。我也编辑了一个链接到答案中。http://trac.cython.org/ticket/850#ticket (2认同)