API обобщенных универсальных функций
Существует общая потребность в итерации не только по функциям скаляров, но и по функциям векторов (или массивов). Эта концепция реализована в NumPy путём обобщения универсальных функций (ufunc). В обычных ufunc элементарная функция ограничена операциями по элементу, в то время как обобщенная версия (gufunc) поддерживает операции «подмассива» по «подмассиву». Библиотека векторов Perl PDL предоставляет аналогичную функциональность, и её термины используются далее.
Каждая обобщенная ufunc имеет связанную с ней информацию, указывающую на «ядерную» размерность входов, а также соответствующую размерность выходов (у элементарных ufunc нулевая ядерная размерность). Список ядерных размерностей для всех аргументов называется «подписью» ufunc. Например, ufunc numpy.add имеет подпись (),()->() , определяющую два скалярных входа и один скалярный выход.
Другим примером является функция inner1d(a, b) с подписью (i),(i)->(). Она применяет скалярное произведение по последней оси каждого входа, но сохраняет остальные индексы неизменными. Например, где a имеет форму (3, 5, N) и b имеет форму (5, N), это вернёт выходной массив формы (3,5). Подлежащая элементарная функция вызывается 3 * 5 раз. В подписи мы указываем одну ядерную размерность (i) для каждого входа и ноль ядерных размерностей () для выхода, так как она принимает два одномерных массива и возвращает скаляр. Используя то же имя i, мы указываем, что две соответствующие размерности должны иметь одинаковый размер.
Размерности, выходящие за пределы ядерных размерностей, называются «цикловыми» размерностями. В приведённом выше примере это соответствует (3, 5).
Подпись определяет, как размерности каждого массива входа/выхода разбиваются на ядерные и цикловые размерности:
- Каждая размерность в подписи сопоставляется с размерностью соответствующего входного массива, начиная с конца кортежа формы. Это ядерные размерности, и они должны присутствовать в массивах, иначе будет выброшено исключение.
- Ядерные размерности, назначенные одному и тому же имени в подписи (например,
iв подписиinner1dдля(i),(i)->()) должны иметь точно совпадающие размеры; векторизация не выполняется. - Ядерные размерности удаляются из всех входов, а оставшиеся размерности объединяются с помощью векторизации, определяя цикловые размерности.
- Форма каждого выхода определяется цикловыми размерностями плюс ядерными размерностями выхода.
Как правило, размер всех ядерных размерностей на выходе определяется размером ядерной размерности с тем же именем в массиве входных данных. Это не требование, и можно определить подпись, где имя появляется впервые на выходе, хотя при вызове такой функции необходимо соблюдать некоторые предосторожности. Примером является функция euclidean_pdist(a), с подписью (n,d)->(p), которая, принимая массив n d-мерных векторов, вычисляет все уникальные парные евклидовы расстояния между ними. Размерность выхода p должна, следовательно, быть равна n * (n - 1) / 2, но ответственность за передачу массива выхода с правильным размером лежит на вызывающей стороне. Если размер ядерной размерности выхода не может быть определён из переданных массивов ввода или вывода, будет выброшено исключение.
Примечание: До NumPy 1.10.0 применялись менее строгие проверки: отсутствующие ядерные размерности создавались путём добавления 1 в начало формы по мере необходимости, ядерные размерности с одинаковым именем векторизовались, а неопределённые размерности создавались с размером 1.
Определения
- Элементарная функция
-
Каждая ufunc состоит из элементарной функции, которая выполняет наиболее базовую операцию на самой малой части аргументов массивов (например, сложение двух чисел является наиболее базовой операцией в суммировании двух массивов). Ufunc применяет элементарную функцию многократно к различным частям массивов. Вход/выход элементарных функций может быть векторами; например, элементарная функция inner1d принимает два вектора в качестве входных данных.
- Подпись
-
Подпись — это строка, описывающая размерности входа/выхода элементарной функции ufunc. Подробнее см. раздел ниже.
- Ядерная размерность
-
Размерность каждого входа/выхода элементарной функции определяется её ядерными размерностями (нулевые ядерные размерности соответствуют скалярному входу/выходу). Ядерные размерности сопоставляются с последними размерностями массивов входа/выхода.
- Имя размерности
-
Имя размерности представляет собой ядерную размерность в подписи. Разные размерности могут иметь одинаковое имя, указывающее на одинаковый размер.
- Индекс размерности
-
Индекс размерности — это целое число, представляющее имя размерности. Он перечисляет имена размерностей в порядке первого появления каждого имени в подписи.
Подробности о подписи
Подпись определяет «ядерную» размерность входных и выходных переменных, а также определяет свёртку размерностей. Подпись представлена строкой следующего формата:
- Ядерные размерности каждого массива входа или выхода представлены списком имён размерностей в скобках,
(i_1,...,i_N); скалярный вход/выход обозначается как(). Вместоi_1,i_2, и т.д., можно использовать любое допустимое имя переменной Python. - Списки размерностей для разных аргументов разделены
",". Аргументы ввода/вывода разделены"->". - Если одно и то же имя размерности используется в нескольких местах, это накладывает ограничение на одинаковый размер соответствующих размерностей.
Формальный синтаксис подписей следующий:
<Signature> ::= <Input arguments> "->" <Output arguments>
<Input arguments> ::= <Argument list>
<Output arguments> ::= <Argument list>
<Argument list> ::= nil | <Argument> | <Argument> "," <Argument list>
<Argument> ::= "(" <Core dimension list> ")"
<Core dimension list> ::= nil | <Core dimension> |
<Core dimension> "," <Core dimension list>
<Core dimension> ::= <Dimension name> <Dimension modifier>
<Dimension name> ::= valid Python variable name | valid integer
<Dimension modifier> ::= nil | "?"
Примечания:
- Все кавычки приведены для ясности.
- Неизменённые ядерные размерности, имеющие одинаковое имя, должны иметь одинаковый размер. Каждое имя размерности, как правило, соответствует одному уровню цикла в реализации элементарной функции.
- Пробелы игнорируются.
- Целое число в качестве имени размерности замораживает эту размерность до значения.
- Если имя размерности завершается модификатором «?», размерность является ядерной только в том случае, если она существует во всех входах и выходах, которые её используют; в противном случае она игнорируется (и заменяется размерностью с размером 1 для элементарной функции).
Вот некоторые примеры подписей:
Имя | Подпись | Общее использование |
|---|---|---|
add |
| бинарная ufunc |
sum1d |
| сведение |
inner1d |
| умножение вектор-вектор |
matmat |
| умножение матриц |
vecmat |
| умножение вектор-матрица |
matvec |
| умножение матрица-вектор |
matmul |
| комбинация четырёх вышеперечисленных |
outer_inner |
| внутреннее произведение по последней размерности, внешнее произведение по предпоследней и цикловая/векторизуемая по остальным. |
cross1d |
| векторное произведение, где последняя размерность заморожена и должна быть равна 3 |
Последний пример иллюстрирует заморозку ядерной размерности, что может улучшить производительность ufunc
C-API для реализации элементарных функций
Текущий интерфейс остаётся неизменным, и PyUFunc_FromFuncAndData по-прежнему можно использовать для реализации (специализированных) ufunc, состоящих из скалярных элементарных функций.
Можно использовать PyUFunc_FromFuncAndDataAndSignature для объявления более общей ufunc. Список аргументов такой же, как у PyUFunc_FromFuncAndData, с дополнительным аргументом, определяющим подпись как строку C.
Кроме того, функция обратного вызова имеет тот же тип, что и прежде, void (*foo)(char **args, intp *dimensions, intp *steps, void *func). При вызове args — это список длины nargs, содержащий данные всех аргументов ввода/вывода. Для скалярной элементарной функции steps также имеет длину nargs, обозначающую смещения, используемые для аргументов. dimensions — указатель на целое число, определяющее размер оси, по которой выполняется цикл.
Для нетривиальной подписи dimensions также будет содержать размеры ядерных размерностей, начиная со второго элемента. Для каждого уникального имени размерности предоставляется только один размер, а размеры указываются в соответствии с первым появлением имени размерности в подписи.
Первые nargs элементов steps остаются такими же, как для скалярных ufunc. Следующие элементы содержат смещения всех ядерных размерностей для всех аргументов в порядке.
Например, рассмотрим ufunc с подписью (i,j),(i)->(). В этом случае args будет содержать три указателя на данные массивов входа/выхода a, b, c. Кроме того, dimensions будет [N, I, J] для определения размера N цикла и размеров I и J для ядерных размерностей i и j. Наконец, steps будет [a_N, b_N, c_N, a_i, a_j, b_i], содержащим все необходимые смещения.
© 2005–2021 NumPy Developers
Licensed under the 3-clause BSD License.
https://numpy.org/doc/1.20/reference/c-api/generalized-ufuncs.html