Numpy передающий входной массив как аргумент `out` для ufunc

Как правило, безопасно предоставлять входной массив как необязательный аргумент out для ufunc в numpy, если тип правильный? Например, я проверил, что следующие работы:

>>> import numpy as np
>>> arr = np.array([1.2, 3.4, 4.5])
>>> np.floor(arr, arr)
array([ 1.,  3.,  4.])

Тип массива должен быть либо совместимым, либо идентичным с выходом (который является float для numpy.floor()), или это происходит:

>>> arr2 = np.array([1, 3, 4], dtype = np.uint8)
>>> np.floor(arr2, arr2)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: ufunc 'floor' output (typecode 'e') could not be coerced to provided output parameter (typecode 'B') according to the casting rule ''same_kind''

Итак, учитывая, что массив подходящего типа, вообще ли безопасно применять ufuncs на месте? Или floor() исключительный случай? Документация не дает понять, и ни один из следующих двух потоков, имеющих касательное отношение к вопросу:

EDIT:

Как предположение первого порядка, я бы предположил, что это часто, но не всегда безопасно, на основе учебника на http://docs.scipy.org/doc/numpy/user/c-info.ufunc-tutorial.html. По-видимому, не существует ограничений на использование выходного массива в качестве временного владельца для промежуточных результатов во время вычисления. Хотя что-то вроде floor() и ciel() может не требовать временного хранения, более сложные функции могут. При этом вся существующая библиотека может быть написана с учетом этого.

Ответ 1

Параметр out функции numpy - это массив, в котором записывается результат. Основным преимуществом использования out является исключение выделения новой памяти там, где это необязательно.

Безопасно ли использовать запись функции в том же массиве, который передается как вход? Нет общего ответа, это зависит от того, что делает функция.

Два примера

Вот два примера ufunc-подобных функций:

In [1]: def plus_one(x, out=None):
   ...:     if out is None:
   ...:         out = np.zeros_like(x)
   ...: 
   ...:     for i in range(x.size):
   ...:         out[i] = x[i] + 1
   ...:     return out
   ...: 

In [2]: x = np.arange(5)

In [3]: x
Out[3]: array([0, 1, 2, 3, 4])

In [4]: y = plus_one(x)

In [5]: y
Out[5]: array([1, 2, 3, 4, 5])

In [6]: z = plus_one(x, x)

In [7]: z
Out[7]: array([1, 2, 3, 4, 5])

Функция shift_one:

In [11]: def shift_one(x, out=None):
    ...:     if out is None:
    ...:         out = np.zeros_like(x)
    ...: 
    ...:     n = x.size
    ...:     for i in range(n):
    ...:         out[(i+1) % n] = x[i]
    ...:     return out
    ...: 

In [12]: x = np.arange(5)

In [13]: x
Out[13]: array([0, 1, 2, 3, 4])

In [14]: y = shift_one(x)

In [15]: y
Out[15]: array([4, 0, 1, 2, 3])

In [16]: z = shift_one(x, x)

In [17]: z
Out[17]: array([0, 0, 0, 0, 0])

Для функции plus_one нет проблемы: ожидаемый результат получается, когда параметры x и out являются одним и тем же массивом. Но функция shift_one дает удивительный результат, когда параметры x и out являются одним и тем же массивом, потому что массив

Обсуждение

Для функции формы out[i] := some_operation(x[i]), такой как plus_one выше, но также функции floor, ceil, sin, cos, tan, log, conj и т.д., насколько я знаю, безопасно, чтобы записать результат на вход, используя параметр out.

Он также безопасен для функций, принимающих два входных параметра формы `` out [i]: = some_operation (x [i], y [i]), такие как функция numpy add, умножить, вычесть.

Для других функций это зависит от конкретного случая. Как показано ниже, матричное умножение небезопасно:

In [18]: a = np.arange(4).reshape((2,2))

In [19]: a
Out[19]: 
array([[0, 1],
       [2, 3]])

In [20]: b = (np.arange(4) % 2).reshape((2,2))

In [21]: b
Out[21]: 
array([[0, 1],
       [0, 1]], dtype=int32)

In [22]: c = np.dot(a, b)

In [23]: c
Out[23]: 
array([[0, 1],
       [0, 5]])

In [24]: d = np.dot(a, b, out=a)

In [25]: d
Out[25]: 
array([[0, 1],
       [0, 3]])

Последнее замечание:, если реализация многопоточная, результат небезопасной функции может даже быть недетерминированным, поскольку он зависит от порядка обработки элементов массива.