De la discussion ici: https://github.com/numpy/numpy/pull/15162#discussion_r434122175
L'exemple suivant ne semble pas générer une erreur lorsque la forme de sortie est incorrecte ou diffuser les entrées vers la sortie, mais remplit silencieusement la sortie avec les valeurs résultat + garbage.
import numpy as np
from numpy.core import _umath_tests as umt
from numpy.testing import assert_raises
a = np.arange(6).reshape(3, 2)
b = np.ones(2)
out = np.empty((5, 3))
umt.inner1d(a, b, out)
print(out)
Résultat:
[[ 1.00000000e+000 5.00000000e+000 9.00000000e+000]
[ 6.91217735e-310 -1.45681599e+144 -1.45681599e+144]
[-1.45681599e+144 -1.45681599e+144 -1.45681599e+144]
[-1.45681599e+144 -1.45681599e+144 -1.45681599e+144]
[-1.45681599e+144 -1.45681599e+144 6.32404027e-322]]
Hehe, y pensait pendant un moment… .Prenez également @mhvk au cas où.
Le drapeau NPY_ITER_NO_BROADCAST
, assez curieusement, semble presque toujours utilisé d'une manière où vous pouvez simplement retirer complètement cet opérande pour la découverte de forme (par exemple comme une opération de sortie). Mais ce serait une rupture incompatible si nous l’utilisions simplement pour résoudre ce problème.
J'envisage donc d'ajouter un nouveau NPY_ITER_OUTPUT_OPERAND
. Ou créez un NPY_ITER_OUTPUT_OPERAND
et un autre drapeau NPY_ITER_DOES_NOT_AFFECT_SHAPE
afin que le drapeau d'opérande de sortie puisse inclure à la fois les indicateurs "allocate" et "no broadcast".
Cela ressemble à une combinaison d'allocation et de non-diffusion, ce qui implique probablement déjà quelque peu cela. Mais la question est de savoir si nous voulons faire un tel changement, en théorie cela peut affecter les bibliothèques externes utilisant NpyIter. (Sauf si nous en faisons un VisibleDeprecationWarning quand il entre en jeu, mais pas sûr que ce soit super sympa à mettre en œuvre.)
Je pense que cela pourrait régler le problème. Il y a des cas secondaires intéressants, comme un axe qui est utilisé uniquement par un des réseaux de sortie (ou plus). On pourrait essayer de soutenir cela (à un moment donné?), Mais cela ajoute probablement trop de complexité sans réel gain.
Je ne vois tout simplement pas vraiment de cas d'utilisation pour un gufunc (),()->(k)
. Quelque chose comme pas (),()->(3,3)
ou ->(3,3)
sens bien sûr, mais ce n'est pas un problème.
Un peu confus à propos de tous les drapeaux, je dois l'admettre. Mais un gufunc moments
avec la signature ()->(k)
pourrait peut-être calculer des moments jusqu'à k
dans la sortie. Bien sûr, incroyablement tiré par les cheveux, mais mon sens serait de ne pas l'exclure explicitement (ni de coder explicitement pour le permettre!).
Un peu plus directement pertinent pour le problème ici, où la sortie a une forme extérieure incohérente, est qu'il existe un précédent des ufuncs réguliers:
np.add(1, 1, out=np.empty((3,)))
# array([2., 2., 2.])
Cela suggère également que pour les gufuncs, la forme extérieure doit être déterminée à partir de toutes les entrées et sorties. Évidemment, cela entraînera des calculs inutiles, mais qu'il en soit ainsi ...
Oh, je ne m'attendais pas à ce que cela fonctionne réellement. Je suppose que dans ce cas, nous pouvons simplement permettre à la sortie de provoquer une diffusion dans les entrées (et donc d'ajuster la forme). Fondamentalement, il suffit de conserver le comportement actuel.
donc, ne faites rien ici, sauf pour vous assurer que le (g) ufunc est appelé plusieurs fois (ce sera un moyen un peu lent d'obtenir le résultat dans de nombreux cas, mais c'est le problème des appelants). Bien que je ne pense pas qu'il soit important de soutenir cela, je ne vois pas non plus de problème à le faire.
Mais nous devons toujours résoudre ce problème dans les gufuncs évidemment!
Ah, c'est très simple:
diff --git a/numpy/core/src/umath/ufunc_object.c b/numpy/core/src/umath/ufunc_object.c
index 19876d641..85820e3a0 100644
--- a/numpy/core/src/umath/ufunc_object.c
+++ b/numpy/core/src/umath/ufunc_object.c
@@ -2614,7 +2614,7 @@ PyUFunc_GeneralizedFunction(PyUFuncObject *ufunc,
* dimensions.
*/
broadcast_ndim = 0;
- for (i = 0; i < nin; ++i) {
+ for (i = 0; i < nop; ++i) {
int n = PyArray_NDIM(op[i]) - op_core_num_dims[i];
if (n > broadcast_ndim) {
broadcast_ndim = n;
est probablement tout ce qu'il y a à faire. Ensuite, nous obtenons une diffusion correcte des entrées vers les sorties, et toute idée de déprécier cela peut simplement être reportée pour les futurs moi si jamais cela se produit.
EDIT: Plus le contrôle nul de op ...
Commentaire le plus utile
Ah, c'est très simple:
est probablement tout ce qu'il y a à faire. Ensuite, nous obtenons une diffusion correcte des entrées vers les sorties, et toute idée de déprécier cela peut simplement être reportée pour les futurs moi si jamais cela se produit.
EDIT: Plus le contrôle nul de op ...