在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_complex
,make clean
,make build
)的小型存储库,因此很容易测试任何东西。
答案 0 :(得分:19)
我可以找到解决此问题的最简单方法是简单地切换乘法的顺序。
如果在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 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}},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
和 使用关键字{ {1}}到_Complex
类型。
__pyx_t_npy_float64_complex
或float _Complex
是有效类型,而double _Complex
则不是。要查看效果,您只需从该行中删除npy_float64 _Complex
,或将其替换为npy_float64
或double
,代码就可以正常编译。接下来的问题是为什么这条线首先产生......
这似乎是由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
typedef
为npy_float64
,因此在此特定情况下,修正可能包括修改the code here使用double
double _Complex
type
的{{1}},但这超出了SO答案的范围,并没有提供一般解决方案。
从`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);
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