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.
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_float64
et 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 voyonstypedef
npy_float64
double
, donc dans ce cas particulier, un correctif pourrait consister à modifier le code ici utiliser double _Complex
où type
npy_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_float64
et 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