API обобщённых универсальных функций
Существует общая потребность в итерации не только по функциям над скалярами, но и по функциям над векторами (или массивами). Эта концепция реализована в NumPy путём обобщения универсальных функций (ufuncs). В обычных ufunc элементарная функция ограничена операциями по элементу, в то время как обобщённая версия (gufuncs) поддерживает операции «подмассива» по «подмассиву». Библиотека векторов Perl PDL предоставляет аналогичную функциональность, и её терминология используется далее.
Каждая обобщённая ufunc содержит информацию о «ядерной» размерности входных данных, а также соответствующей размерности выходных данных (элементные ufuncs имеют нулевую ядерную размерность). Список ядерных размерностей для всех аргументов называется «сигнатурой» 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.16.1/reference/c-api.generalized-ufuncs.html