[NEP05 NEP20] Generalized Universal Functions
Open
@Nucs is already working on this.
Since Feb 18, 2026.
core
documentation-needed
enhancement
NumPy 2.x Compliance
- Dominant language
- C#
- Stars
- 1.5k
- Forks
- 205
- Avg merge
- 7d 7h
- Merged PRs (30d)
- 2
Description
Overview
NumPy's generalized ufunc (gufunc) system for sub-array operations with defined core dimensions.
NEP 5: Generalized Universal Functions
Status: Final | Full Text
Concept
Regular ufuncs operate element-by-element (scalars). Gufuncs operate on sub-arrays with defined core dimensions.
Signature Syntax
<Inputs> "->" <Outputs>
Examples
| Function | Signature | Description |
|---|---|---|
add |
(),()->() |
Scalar + scalar |
inner |
(n),(n)->() |
Inner product |
matmul |
(m,n),(n,p)->(m,p) |
Matrix multiplication |
cross |
(3),(3)->(3) |
Cross product |
Broadcasting Rules
- Core dimensions mapped to last axes
- Remaining dimensions are "loop dimensions"
- Loop dimensions broadcast normally
# Signature: (i),(i)->()
a.shape = (3, 5, N) # Core i=N, loop (3, 5)
b.shape = (5, N) # Core i=N, loop (5,) → broadcast to (3, 5)
output.shape = (3, 5)
NEP 20: Signature Enhancements
Status: Final | Full Text
Frozen Dimensions
Use integer instead of variable to require specific size:
(3),(3)->(3) # Cross product: must be size 3
()->(2) # Output always size 2
Flexible Dimensions
Suffix with ? for optional dimensions:
(m?,n),(n,p?)->(m?,p?)
Covers four matmul variants:
| Case | Input Shapes | Output |
|---|---|---|
| matrix × matrix | (m,n),(n,p) |
(m,p) |
| vector × matrix | (n),(n,p) |
(p) |
| matrix × vector | (m,n),(n) |
(m) |
| vector × vector | (n),(n) |
() |
Behavior
When a flexible dimension is missing:
- Internal computation treats it as size 1
- Output array has that dimension removed (squeezed)
Suggested Implementation for NumSharp
Frozen Dimension Validation
public static void ValidateFrozenDimension(NDArray arr, int axis, int required) {
if (arr.Shape[axis] != required) {
throw new ValueError(
$"Axis {axis} must have size {required}, got {arr.Shape[axis]}");
}
}
// Usage
public static NDArray Cross(NDArray a, NDArray b) {
ValidateFrozenDimension(a, -1, 3);
ValidateFrozenDimension(b, -1, 3);
// ...
}
Flexible Dimension Handling
public static NDArray Matmul(NDArray a, NDArray b) {
bool aIsVector = a.NDim == 1;
bool bIsVector = b.NDim == 1;
// Expand vectors to 2D
var a2d = aIsVector ? a.reshape(1, -1) : a;
var b2d = bIsVector ? b.reshape(-1, 1) : b;
var result = MatrixProduct(a2d, b2d);
// Squeeze based on flexible dims
if (aIsVector && bIsVector) return result.squeeze();
if (aIsVector) return result.squeeze(axis: 0);
if (bIsVector) return result.squeeze(axis: -1);
return result;
}
Signature Parsing (Future)
public class GufuncSignature {
public CoreDimension[] Inputs { get; }
public CoreDimension[] Outputs { get; }
public static GufuncSignature Parse(string sig) {
// Parse "(m?,n),(n,p?)->(m?,p?)"
}
}
public class CoreDimension {
public string Name { get; }
public int? FrozenSize { get; } // null if variable, int if frozen
public bool IsFlexible { get; }
}
Documentation
See docs/neps/NEP05.md, docs/neps/NEP20.md
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.