API обобщенных универсальных функций
Существует общая потребность в итерации не только по функциям над скалярами, но и по функциям над векторами (или массивами). Эта концепция реализована в NumPy путем обобщения универсальных функций (ufuncs). В обычных ufunc элементарная функция ограничена операциями по элементу, тогда как обобщенная версия (gufuncs) поддерживает операции «подмассива» по «подмассиву». Библиотека векторов Perl PDL предоставляет аналогичную функциональность, и ее термины используются далее.
Каждая обобщенная 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–2017 NumPy Developers
Licensed under the NumPy License.
https://docs.scipy.org/doc/numpy-1.12.0/reference/c-api.generalized-ufuncs.html