NEP 5 — 广义通用函数#

地位

最终的

通常不仅需要循环标量上的函数,还需要循环向量(或数组)上的函数,如 http://scipy.org/scipy/numpy/wiki/GeneralLoopingFunctions上所述。我们建议通过泛化通用函数 (ufuncs) 来实现这一概念,并提供一个 C 实现,为 numpy 代码库添加约 500 行。在当前(专用)ufunc 中,初等函数仅限于逐个元素操作,而广义版本支持“子数组”到“子数组”操作。 Perl 向量库 PDL 提供了类似的功能,并且下面重复使用其术语。

每个广义 ufunc 都有与之相关的信息,说明输入的“核心”维度是什么,以及输出的相应维度(元素级 ufunc 的核心维度为零)。所有参数的核心维度列表称为 ufunc 的“签名”。例如,ufunc numpy.add 具有(),()->()定义两个标量输入和一个标量输出的签名。

inner1d(a,b)另一个示例是(请参阅 GeneralLoopingFunctions 页面)签名为 的函数 (i),(i)->()。这将沿每个输入的最后一个轴应用内积,但保持其余索引不变。例如,其中ais of shape(3,5,N)bis of shape (5,N),这将返回 shape 的输出(3,5)。底层初等函数被调用 3*5 次。在签名中,我们(i)为每个输入指定一个核心维度,()为输出指定零个核心维度,因为它需要两个一维数组并返回一个标量。通过使用相同的名称i,我们指定两个对应的维度应该具有相同的大小(或者其中一个维度的大小为1并且将被广播)。

超出核心尺寸的尺寸称为“环”尺寸。在上面的示例中,这对应于(3,5).

应用通常的 numpy“广播”规则,其中签名确定每个输入/输出对象的维度如何分为核心维度和循环维度:

  1. 虽然输入数组的维数小于相应的核心维数,但其形状前会添加 1。

  2. 从所有输入中删除核心维度,并广播剩余维度;定义循环尺寸。

  3. 输出由环路尺寸加上输出核心尺寸给出。

定义#

初等函数

每个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 | <Dimension name> |
                           <Dimension name> "," <Core dimension list>
<Dimension name>       ::= valid Python variable name

笔记:

  1. 所有引用都是为了清晰起见。

  2. i共享相同名称的核心维度必须是可广播的,如上面示例中的两个。每个维度名称通常对应于基本函数实现中的一个循环级别。

  3. 空白将被忽略。

以下是一些签名示例:

添加

(),()->()

内1d

(i),(i)->()

总和1d

(i)->()

点二维

(m,n),(n,p)->(m,p)

矩阵乘法

外层_内层

(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)argsnargsstepsnargsdimensions

对于重要的签名,dimensions还将包含从第二个条目开始的核心尺寸的大小。仅为每个唯一维度名称提供一个尺寸,并且根据签名中维度名称的第一次出现给出尺寸。

nargs的第一个元素steps与标量 ufunc 保持相同。以下元素按顺序包含所有参数的所有核心维度的步幅。

例如,考虑带有签名的 ufunc (i,j),(i)->()。在这种情况下,args将包含三个指向输入/输出数组数据的指针a, b, c。此外,dimensions将 定义环路的尺寸以及 芯尺寸和 的尺寸。最后,will be ,包含所有必要的步骤。[N, I, J]NIJijsteps[a_N, b_N, c_N, a_i, a_j, b_i]