广义通用函数 API #
通常不仅需要循环标量上的函数,还需要循环向量(或数组)上的函数。这个概念是通过泛化通用函数 (ufuncs) 在 NumPy 中实现的。在常规 ufunc 中,初等函数仅限于逐个元素的操作,而广义版本 (gufuncs) 支持“子数组”到“子数组”的操作。 Perl 向量库 PDL 提供了类似的功能,并且下面重复使用其术语。
每个广义 ufunc 都有与之相关的信息,说明输入的“核心”维度是什么,以及输出的相应维度(元素级 ufunc 的核心维度为零)。所有参数的核心维度列表称为 ufunc 的“签名”。例如,ufunc numpy.add 具有(),()->()
定义两个标量输入和一个标量输出的签名。
另一个例子是签名为 的
函数。这将沿每个输入的最后一个轴应用内积,但保持其余索引不变。例如,其中is of shape且is of shape
,这将返回 shape 的输出。底层的基本函数称为时间。在签名中,我们为每个输入指定一个核心维度,为输出指定零个核心维度,因为它需要两个一维数组并返回一个标量。通过使用相同的名称,我们指定两个相应的维度应该具有相同的大小。inner1d(a, b)
(i),(i)->()
a
(3, 5, N)
b
(5, N)
(3,5)
3 * 5
(i)
()
i
超出核心尺寸的尺寸称为“环”尺寸。在上面的示例中,这对应于.(3, 5)
签名确定每个输入/输出数组的维度如何分为核心维度和循环维度:
签名中的每个维度都与相应传入数组的维度相匹配,从形状元组的末尾开始。这些是核心维度,它们必须存在于数组中,否则将引发错误。
分配给签名中相同标签的核心维度(例如 in
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)
;标量输入/输出用 表示()
。可以使用任何有效的 Python 变量名称,而不是i_1
、等。i_2
不同参数的维度列表由 分隔
","
。输入/输出参数由 分隔"->"
。如果在多个位置使用相同的维度名称,则会强制相应维度具有相同的大小。
签名的正式语法如下:
<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 的维度替换)。
以下是一些签名示例:
姓名 |
签名 |
常见用法 |
---|---|---|
添加 |
|
二进制ufunc |
总和1d |
|
减少 |
内1d |
|
向量-向量乘法 |
马特马特 |
|
矩阵乘法 |
韦克马特 |
|
向量矩阵乘法 |
马特韦克 |
|
矩阵向量乘法 |
矩阵相乘 |
|
以上四种的组合 |
外层_内层 |
|
内部覆盖最后一个维度,外部覆盖倒数第二个维度,循环/广播覆盖其余维度。 |
交叉1d |
|
最后一个维度被冻结且必须为 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
将
定义环路的尺寸以及
芯尺寸和 的尺寸。最后,will be
,包含所有必要的步骤。[N, I, J]
N
I
J
i
j
steps
[a_N, b_N, c_N, a_i, a_j, b_i]