Cython

时间:2015-05-05 13:12:59

标签: python c numpy cython complex-numbers

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

我想使用dtype np.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:错误:声明说明符中的两个或更多数据类型”,似乎是由于行{ {1}})。此错误已经报告(例如here),但我没有找到任何好的解释和/或清洁解决方案。

如果不包含typedef npy_float64 _Complex __pyx_t_npy_float64_complex;,则没有错误(我猜是因为complex.h不包括在内)。

但是,由于在typedef生成的html文件中,行cython -a testcplx.pyx为黄色,意味着它尚未转换为纯C,因此仍然存在问题。相应的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)); __Pyx_CREAL是橙色的(Python调用)。

有趣的是,该行

__Pyx_CIMAG

不会产生任何错误并被转换为纯C(只是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):

vardc = varc128
vard = varf64
varc128 = vardc * vard

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

版本

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

存储库

我创建了一个包含示例(vardc = <double complex>varc128 * <double>varf64 )和一个包含3个目标的小Makefile(hg clone https://bitbucket.org/paugier/test_cython_complexmake cleanmake build)的小型存储库,因此很容易测试任何东西。

1 个答案:

答案 0 :(得分:19)

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

如果在testcplx.pyx我改变了

varc128 = varc128 * varf64

varc128 = varf64 * varc128

我从失败的情况转变为正确的情况。这种情况很有用,因为它允许直接区分生成的C代码。

TL;博士

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

我很确定这是一个cython bug(更新:reported here)。虽然this is a very old gcc bug report,但响应明确指出(事实上,它不是 gcc 错误,但用户代码错误):

typedef R _Complex C;
     

这不是有效的代码;你不能将_Complex与typedef一起使用,   只与&#34; float&#34;,&#34; double&#34;或者&#34;长双&#34;其中一种形式   列于C99。

他们得出结论:double _Complex是有效的类型说明符,而ArbitraryType _Complex则不是。{1}}。 This more recent report具有相同类型的响应 - 尝试在非基本类型上使用_Complex超出规范,而GCC manual表示_Complex只能与{float一起使用1}},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 使用关键字{ {1}}到_Complex类型。

__pyx_t_npy_float64_complexfloat _Complex是有效类型,而double _Complex则不是。要查看效果,您只需从该行中删除npy_float64 _Complex,或将其替换为npy_float64double,代码就可以正常编译。接下来的问题是为什么这条线首先产生......

这似乎是由Cython源代码中的this line产生的。

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

在失败的实例中,实现乘法的代码将__pyx_t_npy_float64_complex转换为varf64类型,对实部和虚部进行乘法运算,然后重新组合复数。在工作版本中,它使用函数__pyx_t_npy_float64_complex

直接通过__pyx_t_double_complex类型执行产品

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

关于此问题的说明 - here we see __Pyx_c_prod typedefnpy_float64,因此在此特定情况下,修正可能包括修改the code here使用double double _Complex type的{​​{1}},但这超出了SO答案的范围,并没有提供一般解决方案。

C代码差异结果

工作版

从`varc128 = varf64 * varc128

行创建此C代码
npy_float64

版本失败

从行__pyx_v_8testcplx_varc128 = __Pyx_c_prod(__pyx_t_double_complex_from_parts(__pyx_v_8testcplx_varf64, 0), __pyx_v_8testcplx_varc128);

创建此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)); - 它正在尝试分配类型typedef npy_float64 _Complex __pyx_t_npy_float64_complex; npy_float64类型_Complex

__pyx_t_npy_float64_complex