NEP 5 — 廣義通用函數#
- 狀態:
最終版本
對於不僅對純量函數進行迴圈,也對向量(或陣列)函數進行迴圈存在普遍需求,如 http://scipy.org/scipy/numpy/wiki/GeneralLoopingFunctions 中所述。我們建議透過廣義化通用函數 (ufuncs) 來實現此概念,並提供一個 C 語言實作,該實作向 numpy 程式碼庫新增約 500 行程式碼。在目前的(專用)ufuncs 中,基本函數僅限於逐元素操作,而廣義版本支援「子陣列」對「子陣列」操作。Perl 向量程式庫 PDL 提供了類似的功能,以下重新使用了其術語。
每個廣義 ufunc 都具有與之相關聯的資訊,該資訊說明輸入的「核心」維度,以及對應輸出的維度(逐元素 ufuncs 具有零核心維度)。所有引數的核心維度列表稱為 ufunc 的「簽名」。例如,ufunc numpy.add 的簽名為 (),()->()
,定義了兩個純量輸入和一個純量輸出。
另一個範例是(請參閱 GeneralLoopingFunctions 頁面)簽名為 (i),(i)->()
的函數 inner1d(a,b)
。這會沿著每個輸入的最後一個軸套用內積,但保持其餘索引不變。例如,當 a
的形狀為 (3,5,N)
且 b
的形狀為 (5,N)
時,這將傳回形狀為 (3,5)
的輸出。底層的基本函數會被呼叫 3*5 次。在簽名中,我們為每個輸入指定一個核心維度 (i)
,為輸出指定零個核心維度 ()
,因為它接受兩個 1 維陣列並傳回純量值。透過使用相同的名稱 i
,我們指定兩個對應的維度應具有相同的大小(或者其中一個維度的大小為 1 並將被廣播)。
超出核心維度的維度稱為「迴圈」維度。在上面的範例中,這對應於 (3,5)
。
常用的 numpy「廣播」規則適用,其中簽名決定了每個輸入/輸出物件的維度如何分割為核心維度和迴圈維度
當輸入陣列的維度小於對應的核心維度數量時,會在形狀前面加上 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 | <Dimension name> |
<Dimension name> "," <Core dimension list>
<Dimension name> ::= valid Python variable name
注意事項
所有引號僅為清晰起見。
共用相同名稱的核心維度必須是可廣播的,就像我們上面範例中的兩個
i
一樣。每個維度名稱通常對應於基本函數實作中的一個迴圈層級。空白字元會被忽略。
以下是一些簽名範例
add |
|
|
inner1d |
|
|
sum1d |
|
|
dot2d |
|
矩陣乘法 |
outer_inner |
|
最後一個維度的內積,倒數第二個維度的外積,以及其餘維度的迴圈/廣播。 |
用於實作基本函數的 C-API#
目前的介面保持不變,PyUFunc_FromFuncAndData
仍然可以用於實作(專用)ufuncs,其由純量基本函數組成。
可以使用 PyUFunc_FromFuncAndDataAndSignature
來宣告更通用的 ufunc。PyUFunc_FromFuncAndDataAndSignature
的引數列表與 PyUFunc_FromFuncAndData
相同,但額外有一個引數將簽名指定為 C 字串。
此外,回呼函數的型別與之前相同,void (*foo)(char **args, intp *dimensions, intp *steps, void *func)
。當被調用時,args
是長度為 nargs
的列表,其中包含所有輸入/輸出引數的資料。對於純量基本函數,steps
的長度也為 nargs
,表示用於引數的步幅。dimensions
是指向單一整數的指標,該整數定義要迴圈的軸的大小。
對於非平凡簽名,dimensions
也將包含核心維度的大小,從第二個條目開始。每個唯一維度名稱僅提供一個大小,並且大小會根據維度名稱在簽名中首次出現的順序給出。
steps
的前 nargs
個元素與純量 ufuncs 保持相同。後續元素包含所有引數的所有核心維度的步幅,依序排列。
例如,考慮簽名為 (i,j),(i)->()
的 ufunc。在這種情況下,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]
,其中包含所有必要的步幅。