Nombres complexes en Cython

Quelle est la bonne façon de travailler avec des nombres complexes en Cython?

je voudrais écrire une boucle C pure en utilisant un numpy.ndarray de dtype np.complex128. Dans Cython, le type C associé est défini dans Cython/Includes/numpy/__init__.pxd

ctypedef double complex complex128_t

il semble donc que ce soit juste un simple complexe double C.

Toutefois, il est facile d'obtenir des comportements étranges. En particulier, avec ces définitions

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.

la ligne

varc128 = varc128 * varf64

peut être compilé par Cython mais gcc ne peut pas compiler le code C produit (l'erreur est "testcplx.c:663:25: erreur: deux ou plus de deux types de données dans la déclaration des prescripteurs" et semble être due à la ligne typedef npy_float64 _Complex __pyx_t_npy_float64_complex;). Cette erreur a déjà été signalé (par exemple ici) mais je n'ai trouvé aucune bonne explication et/ou solution propre.

Sans l'inclusion de complex.h, il n'y a pas d'erreur (je suppose parce que le typedef n'est alors pas inclus).

cependant, il y a encore un problème puisque dans le fichier html produit par cython -a testcplx.pyx la ligne varc128 = varc128 * varf64 est jaune, ce qui signifie qu'il n'a pas été traduit en pur C. le code C correspondant est:

__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));

et __Pyx_CREAL et __Pyx_CIMAG sont orange (appels Python).

fait intéressant, la ligne

vardc = vardc * vard

ne produit aucune erreur et est traduit en pur C (__pyx_v_8testcplx_vardc = __Pyx_c_prod(__pyx_v_8testcplx_vardc, __pyx_t_double_complex_from_parts(__pyx_v_8testcplx_vard, 0));), alors qu'elle est très similaire à la première.

je peux éviter l'erreur en en utilisant des variables intermédiaires (et cela se traduit en C pur):

vardc = varc128
vard = varf64
varc128 = vardc * vard

ou tout simplement par le casting (mais il ne se traduit pas par pure C):

vardc = <double complex>varc128 * <double>varf64

Donc ce qui se passe? Quelle est la signification de l'erreur de compilation? Est-il un moyen propre à éviter? Pourquoi la multiplication d'un np.complex128_t et np.float64_t semble impliquer des appels Python?

les Versions

Cython version 0.22 (version la plus récente de Pypi lorsque la question était demandé) et GCC 4.9.2.

Dépôt

j'ai créé un dépôt minuscule avec l'exemple (hg clone https://bitbucket.org/paugier/test_cython_complex) et un petit Makefile avec 3 cibles (make clean,make build,make html) il est donc facile de tester n'importe quoi.

36
demandé sur paugier 2015-05-05 16:12:59

1 réponses

le moyen le plus simple que je puisse trouver pour travailler autour de cette question Est de simplement changer l'ordre de multiplication.

Si testcplx.pyx j'ai modifier

varc128 = varc128 * varf64

varc128 = varf64 * varc128

je passe de l'échec d'une situation décrite à l'un qui fonctionne correctement. Ce scénario est utile car il permet une différence directe du code C produit.

tl;dr

l'ordre de la multiplication change la traduction, ce qui signifie que dans le échec de la version la multiplication est tentée via __pyx_t_npy_float64_complex types, alors que dans la version de travail il est fait via __pyx_t_double_complex types. Ceci introduit à son tour la ligne typedef typedef npy_float64 _Complex __pyx_t_npy_float64_complex;, qui est invalide.

je suis assez sûr que c'est une cython bug (mise à Jour: rapportés ici). Bien que ceci est un très vieux rapport de bogue gcc, la réponse indique explicitement (en disant qu'il n'est pas, en fait, un gcc bug, mais code utilisateur d'erreur):

typedef R _Complex C;

Ce n'est pas un code valide; vous ne pouvez pas utiliser _Complex avec un typedef, seulement avec "float", "double" ou "long double" dans l'une des formes listé en C99.

Ils concluent que double _Complex est un spécificateur de type valide alors que ArbitraryType _Complex ne l'est pas. Ce rapport plus récent a le même type de réponse - en essayant d'utiliser _Complex non type fondamental est hors spec, et le GCC manuel indique que _Complex peut être utilisé uniquement avec float,double et long double

donc-nous pouvons hacker le code C généré par cython pour tester que: remplacer typedef npy_float64 _Complex __pyx_t_npy_float64_complex;typedef double _Complex __pyx_t_npy_float64_complex; et vérifier qu'il est bien valide et qu'il peut faire compiler le code de sortie.


petit trek à travers le code

L'échange de l'ordre de multiplication ne fait que mettre en évidence le problème dont nous parle le compilateur. Dans le premier cas, l' la ligne incriminée est celle qui dit typedef npy_float64 _Complex __pyx_t_npy_float64_complex; - il est en train d'affecter le type npy_float64et utilisez le mot-clé _Complex le type __pyx_t_npy_float64_complex.

float _Complex ou double _Complex est un type valide, alors que npy_float64 _Complex ne l'est pas. Pour voir l'effet, vous pouvez simplement supprimer npy_float64 à partir de cette ligne, ou la remplacer par double ou float et le code se compile bien. La question suivante est de savoir pourquoi cette ligne est produite en premier lieu...

Cela semble produit par cette ligne dans le code source Cython.

Pourquoi l'ordre de la multiplication de modifier le code de façon significative - tels que le type __pyx_t_npy_float64_complex est introduit et présenté d'une manière qui échoue?

dans l'instance défaillante, le code pour implémenter la multiplication tourne varf64 dans un __pyx_t_npy_float64_complex type, fait la multiplication sur les parties réelles et imaginaires et puis réassemble le nombre complexe. Dans la version de travail, il fait le produit directement via le __pyx_t_double_complex Tapez en utilisant la fonction __Pyx_c_prod

je suppose que c'est aussi simple que le code cython prenant son signal pour quel type utiliser pour la multiplication de la première variable qu'il rencontre. Dans le premier cas, il voit un float de 64, produit (valide) code C basé sur cela, alors que dans le second, il voit le type (double) complex128 et fonde sa traduction sur cela. Cette explication est un peu vague et j'espère revenir à un l'analyse de celui-ci si le temps le permet...

- ici, nous voyonstypedefnpy_float64double, donc dans ce cas particulier, un correctif pourrait consister à modifier le code ici utiliser double _Complextypenpy_float64, mais cela dépasse la portée d'une réponse SO et ne présente pas de solution générale.


code C diff résultat

au travail version

crée ce code C à partir de la ligne `varc128 = varf64 * varc128

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

à Défaut de la version

crée ce code C à partir de la ligne 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));

ce Qui nécessite ces importations et la ligne incriminée est celle qui dit typedef npy_float64 _Complex __pyx_t_npy_float64_complex; - il est en train d'affecter le type npy_float64et le type _Complex le type __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
18
répondu J Richard Snape 2015-05-13 15:04:09