Numpy: comportamento gufunc na forma incorreta

Criado em 2 jun. 2020  ·  5Comentários  ·  Fonte: numpy/numpy

Da discussão aqui: https://github.com/numpy/numpy/pull/15162#discussion_r434122175

O exemplo a seguir não parece gerar um erro quando a forma de saída está incorreta ou transmite as entradas para a saída, mas preenche silenciosamente a saída com resultados + valores lixo.

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)

Resultado:

[[ 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]]
00 - Bug numpy.core numpy.ufunc

Comentários muito úteis

Ah, é muito simples:

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;

é provavelmente tudo que há para fazer. Em seguida, obtemos a transmissão correta de entradas para saídas, e qualquer pensamento de descontinuar isso pode ser adiado para as futuras selves, se isso ocorrer.

EDIT: Além da verificação nula de op ...

Todos 5 comentários

Hehe, estava pensando sobre isso por um tempo… .Também pingue em garantir .

O sinalizador NPY_ITER_NO_BROADCAST , curiosamente, parece quase sempre usado de uma maneira em que você poderia simplesmente retirar aquele operando totalmente para a descoberta de forma (por exemplo, como uma operação de saída). Mas, seria uma quebra incompatível se apenas o usássemos para resolver esse problema.

Portanto, estou pensando em adicionar um novo NPY_ITER_OUTPUT_OPERAND . Ou faça um NPY_ITER_OUTPUT_OPERAND e outro sinalizador NPY_ITER_DOES_NOT_AFFECT_SHAPE para que o sinalizador do operando de saída possa incluir os sinalizadores "alocar" e "sem transmissão".

Parece uma combinação de alocar e não transmitir, provavelmente já implica isso de alguma forma. Mas a questão é se queremos fazer essa mudança, em teoria, ela pode afetar bibliotecas externas usando o NpyIter. (A menos que o tornemos um VisibleDeprecationWarning quando ele entrar em ação, mas não temos certeza de que seja muito legal de implementar)

Acho que isso pode resolver o problema. Existem casos colaterais interessantes, como eixos que são usados apenas por um (ou mais) vetores de saída. Alguém poderia tentar oferecer suporte a isso (em algum momento?), Mas provavelmente adiciona muita complexidade para nenhum ganho real.
Eu simplesmente não vejo muito caso de uso para (),()->(k) gufunc. Algo como (),()->(3,3) ou ->(3,3) faz sentido, é claro, mas não é problema.

Um pouco confuso com todas as bandeiras, devo admitir. Mas um gufunc moments com assinatura ()->(k) talvez pudesse calcular momentos até k na saída. Claro, incrivelmente rebuscado, mas meu sentido seria não excluí-lo explicitamente (nem codificar explicitamente para permiti-lo!).

Um pouco mais diretamente relevante para o problema aqui, onde a saída tem uma forma externa inconsistente, é que há precedente dos ufuncs regulares:

np.add(1, 1, out=np.empty((3,)))
# array([2., 2., 2.])

Isso sugere também para gufuncs, a forma externa deve ser determinada a partir de todas as entradas e saídas. Obviamente, causará cálculos desnecessários, mas que assim seja ...

Oh, de alguma forma eu não esperava que isso realmente funcionasse. Suponho que, nesse caso, podemos simplesmente permitir que a saída cause a transmissão nas entradas (e, assim, ajustar a forma). Basicamente, simplesmente mantendo o comportamento atual.

portanto, não faça nada aqui, exceto certificar-se de que (g) ufunc seja chamado várias vezes (será um modo um pouco lento de obter o resultado em muitos casos, mas esse é o problema dos chamadores). Embora eu não ache importante apoiar isso, também não vejo problemas em fazê-lo.

Mas ainda precisamos corrigir esse problema no gufuncs, obviamente!

Ah, é muito simples:

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;

é provavelmente tudo que há para fazer. Em seguida, obtemos a transmissão correta de entradas para saídas, e qualquer pensamento de descontinuar isso pode ser adiado para as futuras selves, se isso ocorrer.

EDIT: Além da verificação nula de op ...

Esta página foi útil?
0 / 5 - 0 avaliações