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’s(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 | (i)->() | сведение |
| inner1d | (i),(i)->() | умножение вектор-вектор |
| matmat | (m,n),(n,p)->(m,p) | умножение матриц |
| vecmat | (n),(n,p)->(p) | умножение вектор-матрица |
| matvec | (m,n),(n)->(m) | умножение матрица-вектор |
| matmul | (m?,n),(n,p?)->(m?,p?) | комбинация четырёх вышеперечисленных |
| outer_inner | (i,t),(j,t)->(i,j) | внутреннее произведение по последней размерности, внешнее произведение по предпоследней и цикл/вещание по остальным |
| cross1d | (3),(3)->(3) | векторное произведение, где последняя размерность заморожена и должна быть равна 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–2019 NumPy Developers
Licensed under the 3-clause BSD License.
https://docs.scipy.org/doc/numpy-1.17.0/reference/c-api.generalized-ufuncs.html