SciSharp / SciSharp/NumSharp

[NEP05 NEP20] Generalized Universal Functions

Open
#551 0 comments 0 reactions 1 assignee View on GitHub

@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
  1. Core dimensions mapped to last axes
  2. Remaining dimensions are "loop dimensions"
  3. 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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.