如何在 'language=c++' 模式下使用 C 复数?

How to use C complex numbers in 'language=c++' mode?

我的大部分库都是用 "normal" C 模式下的 Cython 编写的。到目前为止,我很少需要任何 C++ 功能,但总是假设(有时确实如此!)如果我愿意,我可以只为一个模块切换到 C++ 模式。

所以我有 10 多个 C 模式模块和 1 个 C++ 模式模块。

现在的问题是 Cython 似乎 如何处理复数定义。在 C 模式下,它假设我认为是 C 复数,而在 C++ 模式下,它假设我认为是 C++ 复数。我读过它们现在可能是一样的,但无论如何 Cython 抱怨它们不是:

openChargeState/utility/cheb.cpp:2895:35: error: cannot convert ‘__pyx_t_double_complex {aka std::complex<double>}’ to ‘__complex__ double’ for argument ‘1’ to ‘double cabs(__complex__ double)’
 __pyx_t_5 = ((cabs(__pyx_v_num) == INFINITY) != 0);

在这种情况下,我尝试使用在 C 模式模块中定义的出租车,并从 C++ 模式模块调用它。

我知道有一些明显的解决方法(现在我只是不使用 C++ 模式;我想使用向量,而不是现在使用较慢的 Python 列表)。

有没有办法告诉我的 C++ 模式模块使用 C 复数,或者告诉它它们是相同的?如果我在我的 C++ 模式模块中找不到 ctypedef C 复数的工作方法...或者还有其他解决方案吗?


编辑: DavidW 和 ead 的评论提出了一些合理的建议。首先是最小工作示例。

setup.py

from distutils.core import setup
from distutils.extension import Extension
from Cython.Distutils import build_ext
from Cython.Build import cythonize

extra_compile_args=['-O3']
compdir = {'language_level' : '3'}

extensions = cythonize([
                        Extension("cmod", ["cmod.pyx"]),
                        Extension("cppmod", ["cppmod.pyx"], language='c++')
                       ], 
                       compiler_directives = compdir
                      )

setup(cmdclass = {'build_ext': build_ext},
      ext_modules = extensions
     )

import cppmod

cmod.pyx

cdef double complex c_complex_fun(double complex xx):
    return xx**2    

cmod.pxd

cdef double complex c_complex_fun(double complex xx)

cdef extern from "complex.h":
    double cabs(double complex zz) nogil

cppmod.pyx

cimport cmod

cdef double complex cpp_complex_fun(double complex xx):
    return cmod.c_complex_fun(xx)*abs(xx)   # cmod.cabs(xx) doesn't work here

print(cpp_complex_fun(5.5))

然后用 python3 setup.py build_ext --inplace.

编译

现在有趣的部分是(如代码中所写)只有 "indirectly" 导入的 c 函数有问题,在我的例子中是 cabs。所以只使用 abs 的建议实际上确实有帮助,但我仍然不明白底层逻辑。我希望我不会在另一个问题中遇到这个问题。我暂时不提这个问题。也许有人知道发生了什么。

您的问题与以下事实无关:一个模块编译为 C 扩展,另一个模块编译为 C++ 扩展 - 仅在 C++ 扩展中即可轻松重现该问题:

%%cython -+ 
cdef extern from "complex.h": 
    double cabs(double complex zz) nogil

def cpp_complex_fun(double complex xx):
    return cabs(xx)

导致您的错误消息:

error: cannot convert __pyx_t_double_complex {aka std::complex<double>} to __complex__ double for argument 1 to double cabs(__complex__ double)

问题是复数……好吧,很复杂。 Cython 处理复数的策略(可以查找 here and here)是使用 C/CPP 中的可用实现,如果找到 none,则使用手写回退:

#if !defined(CYTHON_CCOMPLEX)
  #if defined(__cplusplus)
    #define CYTHON_CCOMPLEX 1
  #elif defined(_Complex_I)
    #define CYTHON_CCOMPLEX 1
  #else
    #define CYTHON_CCOMPLEX 0
  #endif
#endif
....
#if CYTHON_CCOMPLEX
  #ifdef __cplusplus
    typedef ::std::complex< double > __pyx_t_double_complex;
  #else
    typedef double _Complex __pyx_t_double_complex;
  #endif
#else
    typedef struct { double real, imag; } __pyx_t_double_complex;
#endif

在 C++ 扩展的情况下,Cython 的 double complex 被翻译成 std::complex<double>,因此不能用 cabs( double complex z ) 调用,因为 std::complex<double> 不是 [=16] =].

所以实际上,这是你的 "fault":你对 Cython 撒谎并告诉他,cabs 具有签名 double cabs(std::complex<double> z),但这不足以欺骗 c++-编译器。

这意味着,在 c++ 模块中可以使用 std::abs(std::complex<double>),或者只是 Cython's/Python 的 abs,即 automatically translated 到正确的函数(然而,这对于所有标准功能都是不可能的。


在 C 扩展的情况下,因为您已经将 complex.h 作为所谓的 "early include" 包含在 cdef extern from "complex.h" 中,因此对于上述定义 _Complex_I 已定义并且 Cython 的复合体成为 double complex 的别名,因此可以使用 cabs


对于 Cython 来说,正确的做法可能是始终默认使用回退,并且用户应该能够明确选择所需的实现(double complexstd::complex<double>)。