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 | <Dimension name> |
<Dimension name> "," <Core dimension list>
<Dimension name> ::= valid Python variable name
Примечания:
- Все кавычки добавлены для большей ясности.
- Ядерные размерности, имеющие одинаковое имя, должны иметь точно одинаковый размер. Каждое имя размерности, как правило, соответствует одному уровню циклических операций в реализации элементарной функции.
- Пробелы игнорируются.
Вот несколько примеров сигнатур:
| add | (),()->() | |
| inner1d | (i),(i)->() | |
| sum1d | (i)->() | |
| dot2d | (m,n),(n,p)->(m,p) | умножение матриц |
| outer_inner | (i,t),(j,t)->(i,j) | внутреннее произведение по последней размерности, внешнее произведение по предпоследней и циклы/вещание по остальным. |
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], содержащим все необходимые шаги.
© 2008–2016 NumPy Developers
Licensed under the NumPy License.
https://docs.scipy.org/doc/numpy-1.11.0/reference/c-api.generalized-ufuncs.html