NumPy参考 >NumPy C-API > 通用函数API
通常,不仅需要遍历标量上的函数,还需要遍历矢量(或数组)上的函数。NumPy通过泛化通用函数(ufuncs)来实现此概念。在常规ufunc中,基本功能仅限于逐个元素的操作,而广义版本(gufuncs)通过“子数组”操作支持“子数组”。Perl矢量库PDL提供了类似的功能,其术语在下面重新使用。
每个广义ufunc都有与之关联的信息,该信息说明输入的“核心”维数是什么,以及输出的相应维数(逐个元素的ufunc具有零核心维)。所有参数的核心维度列表称为ufunc的“签名”。例如,ufunc numpy.add具有(),()->()
定义两个标量输入和一个标量输出的签名。
另一个示例是签名
为的函数。这将沿每个输入的最后一个轴应用内积,但保留其余索引不变。例如,where 为shape 且为shape
,这将返回shape的输出。基本的基本功能称为时间。在签名中,我们为每个输入指定一个核心尺寸,为输出指定零核心尺寸,因为它需要两个一维数组并返回一个标量。通过使用相同的名称,我们指定两个相应的尺寸应具有相同的大小。inner1d(a, b)
(i),(i)->()
a
(3, 5, N)
b
(5, N)
(3,5)
3 * 5
(i)
()
i
超出核心尺寸的尺寸称为“环形”尺寸。在上面的示例中,这对应于。(3, 5)
签名确定如何将每个输入/输出数组的维度划分为核心和循环维度:
从形状元组的末端开始,签名中的每个维度都与相应的传入数组的维度匹配。这些是核心尺寸,它们必须存在于数组中,否则会产生错误。
在签名分配到相同的标签核心尺寸(如
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)
;标量输入/输出用表示()
。除了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的尺寸)。
以下是一些签名示例:
名称 |
签名 |
常用用法 |
---|---|---|
加 |
|
二进制ufunc |
总和 |
|
减少 |
内部1d |
|
向量-向量乘法 |
垫子 |
|
矩阵乘法 |
Vecmat |
|
向量矩阵乘法 |
Matvec |
|
矩阵向量乘法 |
Matmul |
|
以上四种的结合 |
external_inner |
|
在最后一个维度上是内部,在倒数第二个维度上是外部,在其余维度上循环/广播。 |
跨度 |
|
最终产品的冻结尺寸必须为3的叉积 |
最后一个是冻结核心维度的实例,可用于提高ufunc性能
当前接口保持不变,并且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 (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]