Table of Contents

Struct NDIterRef

Namespace
NumSharp.Backends.Iteration
Assembly
NumSharp.dll

High-performance multi-operand iterator matching NumPy's nditer API.

public ref struct NDIterRef
Inherited Members

Remarks

Why the routes are split in two. NumPy checks every cast after the axis data is filled (so a broadcast or reduction error wins over a cast error) and makes the copies only after the iteration order is final (each temporary is dense in that order, so the coalescing that follows merges it like a fresh array). PlanDescriptorRoutes(int, NDArray[], NDIterPerOpFlags[], DType[], bool[], bool[], NPY_CASTING) therefore decides everything up front without touching memory, the casting error it found is raised after the axis data is filled, and MaterializeDescriptorRoutes(DescriptorPlan, NDIterPerOpFlags[], NDIterGlobalFlags, int[][], bool) runs between the axis reorder and the coalescing — the same seam NumPy's npyiter_allocate_arrays occupies.

Write-backs are registered only on success. NumPy discards a WRITEBACKIFCOPY temporary when the construction fails (NpyIter_Deallocate under a pending error); here a temporary becomes a write-back only after every route of every operand succeeded, and a failure releases the temporaries made so far.

Properties

Finished

Check if iteration is finished.

public bool Finished { get; }

Property Value

bool

HasExternalLoop

Whether iterator has external loop.

public bool HasExternalLoop { get; }

Property Value

bool

HasGrowInner

Whether iterator uses GROWINNER optimization for buffering.

public bool HasGrowInner { get; }

Property Value

bool

HasIdentPerm

Check if the axis permutation is identity (no reordering). Mutually exclusive with NEGPERM - if NEGPERM is set, IDENTPERM is cleared.

public bool HasIdentPerm { get; }

Property Value

bool

HasIndex

Check if iterator is tracking a flat index.

public bool HasIndex { get; }

Property Value

bool

HasMultiIndex

Check if iterator is tracking multi-index.

public bool HasMultiIndex { get; }

Property Value

bool

HasNegPerm

Check if any axes have negative permutation entries (flipped for memory-order iteration). When NEGPERM is set, GetMultiIndex reverses indices for those axes.

public bool HasNegPerm { get; }

Property Value

bool

HasWriteMaskedOperand

True if any operand is flagged WRITEMASKED (and a corresponding ARRAYMASK exists).

public bool HasWriteMaskedOperand { get; }

Property Value

bool

IsContiguous

Whether all operands are contiguous.

public bool IsContiguous { get; }

Property Value

bool

IsParallelSafe

Whether the iteration range can be split across parallel workers without write hazards (see PARALLEL_SAFE): no REDUCE operand, and either no WRITE operands or exactly one whose potential overlap with inputs was resolved by COPY_IF_OVERLAP.

public bool IsParallelSafe { get; }

Property Value

bool

IsRanged

Check if iterator is using ranged iteration.

public bool IsRanged { get; }

Property Value

bool

IsReduction

Check if iteration includes reduction operands.

public bool IsReduction { get; }

Property Value

bool

IterEnd

Get the current iteration range end.

public long IterEnd { get; }

Property Value

long

IterIndex

Current iteration index.

public long IterIndex { get; }

Property Value

long

IterRange

Get the current iteration range as (start, end) tuple.

public (long Start, long End) IterRange { get; }

Property Value

(long Start, long End)

IterSize

Total iteration count.

public long IterSize { get; }

Property Value

long

IterStart

Get the current iteration range start.

public long IterStart { get; }

Property Value

long

MaskOp

Index of the ARRAYMASK operand (used by WRITEMASKED operands), or -1 if none. Matches NumPy's NIT_MASKOP(iter).

public int MaskOp { get; }

Property Value

int

NDim

Number of dimensions after coalescing.

public int NDim { get; }

Property Value

int

NOp

Number of operands.

public int NOp { get; }

Property Value

int

RawState

public NDIterState* RawState { get; }

Property Value

NDIterState*

RequiresBuffering

Whether iterator requires buffering.

public bool RequiresBuffering { get; }

Property Value

bool

Shape

Get the current iterator shape in original axis order. When MULTI_INDEX is set, returns shape in original axis order. When NEGPERM is set, handles flipped axes correctly. Otherwise returns internal (possibly coalesced) shape.

public long[] Shape { get; }

Property Value

long[]

Methods

AdvancedNew(int, NDArray[], NDIterGlobalFlags, NPY_ORDER, NPY_CASTING, NDIterPerOpFlags[], NPTypeCode[]?, int, int[][]?, long[]?, long, DType[]?)

Create iterator with full control over all parameters. Equivalent to NumPy's NDIter_AdvancedNew.

public static NDIterRef AdvancedNew(int nop, NDArray[] op, NDIterGlobalFlags flags, NPY_ORDER order, NPY_CASTING casting, NDIterPerOpFlags[] opFlags, NPTypeCode[]? opDtypes = null, int opAxesNDim = -1, int[][]? opAxes = null, long[]? iterShape = null, long bufferSize = 0, DType[]? opDescrs = null)

Parameters

nop int

The number of operands.

op NDArray[]

The operands; a null entry is allocated (ALLOCATE / VIRTUAL).

flags NDIterGlobalFlags

The global flags.

order NPY_ORDER

The iteration order.

casting NPY_CASTING

The casting rule every operand's cast must satisfy.

opFlags NDIterPerOpFlags[]

The per-operand flags.

opDtypes NPTypeCode[]

The per-operand iteration type codes (Empty or null = the operand's).

opAxesNDim int

The op_axes rank, or -1 when opAxes is unused.

opAxes int[][]

The per-operand axis maps.

iterShape long[]

An explicit iteration shape, or null to broadcast the operands.

bufferSize long

The buffer size in elements (0 = np.getbufsize()).

opDescrs DType[]

The per-operand iteration DESCRIPTORS — NumPy's resolved op_dtype array (a null entry means the operand's own descriptor; for an operand the iterator allocates, the type-code rules of opDtypes decide its dtype instead). When given, the iterator runs NumPy's descriptor-level construction: a fixed-width string operand is sized by its descriptor (<U5 = 20 bytes), an allocated output takes its descriptor, and the cast decisions are NumPy's — npyiter_check_casting's read and write-back checks with their TypeError texts, then npyiter_allocate_arrays' copy routes (a read-only 0-d operand copied into its iteration dtype, a copy / updateifcopy operand copied and written back at close) and its "required copying or buffering" TypeError. Null (every internal caller) keeps the type-code construction and its legacy cast errors.

Returns

NDIterRef

The iterator (owns its state; dispose it).

Exceptions

TypeError

With opDescrs: an operand's cast violates casting (read or write-back direction), or a cast operand is neither buffered nor copied.

ArgumentException

Inconsistent arguments, a broadcast mismatch, a refused reduction, or (without opDescrs) a cast without BUFFERED.

BufferedReduce<TKernel, TAccum>(TKernel)

Reduction variant over a BUFFER + REDUCE iterator: one kernel call per inner loop — a core of NumPy's reduce double loop when the buffering setup chose it (the reduce operand's element repeats along the core, or its core repeats per outer step), else a plain fill — with IsFirstVisit(int) telling the kernel when an output slot starts. The NumPy-parity path for axis reductions that span multiple output elements. An empty iteration makes no call (see ForEach(NDInnerLoopFunc, void*)).

public void BufferedReduce<TKernel, TAccum>(TKernel kernel) where TKernel : struct, INDReducingInnerLoop<TAccum> where TAccum : unmanaged

Parameters

kernel TKernel

The kernel; returning false ends the walk early (the held buffer is written back at Dispose).

Type Parameters

TKernel

The inner-loop kernel.

TAccum

The kernel's accumulator type (threaded through every call).

Remarks

An exception that leaves it marks the iteration failed (NumSharp.Backends.Iteration.NDIterRef.MarkFailed()), so Dispose() clears the held buffers and discards the COPY_IF_OVERLAP write-backs instead of writing them into the operands, as NumPy's NpyIter_Deallocate does when an error is set.

Exceptions

InvalidOperationException

The iterator is not buffered or has no reduction operand.

ValueError

A string operand's conversion fails during a buffer transfer.

Copy()

Creates an independent copy of the iterator at its current position — NumPy's NpyIter_Copy. The copy has its own state and buffers and is advanced independently (dispose it separately).

public NDIterRef Copy()

Returns

NDIterRef

The copy (it owns its state; it shares the operand arrays).

Remarks

A buffered copy gets fresh buffers FILLED FROM THE ARRAYS at the current position, as NumPy's does (npyiter_goto_iterindex + npyiter_copy_to_buffers on the copy): it starts a new buffer at the current element (inside a core when the position is), and it does not see what the original wrote into its buffer and has not written back yet. A copy taken before the first fill or after the end holds no buffer either.

Exceptions

OutOfMemoryException

The copy's state or buffers could not be allocated, or a StringDType fill cannot allocate a string.

ValueError

A string operand's conversion fails while the copy's buffers are filled.

CreateCompatibleStrides(long, scoped Span<long>)

Builds a set of strides that match the iterator's axis ordering for a hypothetical contiguous array (like the result of NPY_ITER_ALLOCATE). Matches NumPy's NDIter_CreateCompatibleStrides (nditer_api.c:1058).

Use case: match the shape/layout of an iterator while tacking on extra dimensions (e.g., gradient vector per element, Hessian matrix). If an array is created with these strides, adding itemsize each iteration traverses the array matching the iterator.

Requirements:

  • Iterator must be tracking a multi-index (HASMULTIINDEX flag).
  • No axis may be flipped (NPY_ITER_DONT_NEGATE_STRIDES must have been used, or the iterator must have no negative-stride axes to flip).
public bool CreateCompatibleStrides(long itemsize, scoped Span<long> outStrides)

Parameters

itemsize long

Base stride (typically element size in bytes).

outStrides Span<long>

Output span of length ≥ NDim, one stride per axis in original array order (C-order).

Returns

bool

DebugPrint()

Dumps iterator state to standard output. See DebugPrint(TextWriter).

public void DebugPrint()

DebugPrint(TextWriter)

Dumps a verbose textual representation of the iterator's internal state to the specified TextWriter. Matches NumPy's NDIter_DebugPrint (nditer_api.c:1402) format as closely as possible.

Output includes: ItFlags (decoded), NDim, NOp, IterSize/Start/End/Index, Perm, DTypes, DataPtrs, BaseOffsets, OpItFlags, BufferData, and per-axis data.

public void DebugPrint(TextWriter writer)

Parameters

writer TextWriter

DebugPrintToString()

Returns the debug dump as a string.

public string DebugPrintToString()

Returns

string

DetectExecutionPath()

Pick the right ExecutionPath for MixedType/Comparison kernel selection by scanning the post-coalesce stride picture.

public ExecutionPath DetectExecutionPath()

Returns

ExecutionPath

Dispose()

Deallocate iterator resources: NumPy's NpyIter_Deallocate. When the iteration succeeded, a held buffer is written back and the COPY_IF_OVERLAP temporaries are copied into the caller's arrays; when it failed (NumSharp.Backends.Iteration.NDIterState.Failed: one of this iterator's drivers threw) the buffers are cleared and the temporaries discarded, so the operands keep what the completed fills wrote and nothing of the failed one.

public void Dispose()

Remarks

A write-back that throws is a failure too, as in NumPy (success = NPY_FAIL): the buffers are cleared, the temporaries discarded and the memory released before the exception is raised again, where it used to leave the temporaries and the state block behind.

The flag is set only by the drivers (NumSharp.Backends.Iteration.NDIterRef.MarkFailed()). An exception in a loop the caller writes around the iterator (np.nditer's element walk) is not seen here, and the buffers are written back, as NumPy's with block writes them back when its body raises.

Exceptions

ValueError

The success path's write-back of the held buffer does not convert (a number into a text, or a text into a number); raised after the iterator's resources are released.

OverflowException

That write-back puts a text's number outside an integer dtype.

UnicodeEncodeError

That write-back puts a non-ASCII text into S.

OutOfMemoryException

That write-back cannot allocate a StringDType string.

EnableExternalLoop()

Switches the iterator to external-loop iteration and resets it — NumPy's NpyIter_EnableExternalLoop: each step then hands out a whole inner loop, starting again from the first one.

public bool EnableExternalLoop()

Returns

bool

True when the iterator was reset (false only when delayed buffers cannot be allocated).

Remarks

The reset is NumPy's, and it matters: an external loop starts at the beginning of an inner run, so keeping a position in the middle of one would hand out a chunk that crosses into the next row.

Exceptions

ValueError

A flat index or a multi-index is tracked ("Iterator flag EXTERNAL_LOOP cannot be used if an index or multi-index is being tracked"), or NumPy's ranged-without-buffering refusal.

EnsureBuffersReady()

Performs the reset a DELAY_BUFALLOC iterator needs before its first use — allocates the buffers and fills them at the current position. A no-op unless buffer allocation is still pending. NumPy requires an explicit reset there; NumSharp's execution entry points (ForEach, ExecuteGeneric, ExecuteReducing, ExecuteBinary) call this instead.

public void EnsureBuffersReady()

Exceptions

OutOfMemoryException

The buffers could not be allocated.

ValueError

A string operand's conversion fails during the fill.

OverflowException

A text converted into an integer holds a number outside it.

ExecuteBinary(BinaryOp)

Run a binary ufunc over three operands [in0, in1, out]. Picks SimdFull / SimdScalarRight / SimdScalarLeft / SimdChunk / General based on the iterator's stride picture after coalescing.

public void ExecuteBinary(BinaryOp op)

Parameters

op BinaryOp

Remarks

An exception that leaves it marks the iteration failed (NumSharp.Backends.Iteration.NDIterRef.MarkFailed()), so Dispose() clears the held buffers and discards the COPY_IF_OVERLAP write-backs instead of writing them into the operands, as NumPy's NpyIter_Deallocate does when an error is set.

ExecuteComparison(ComparisonOp)

Element-wise comparison → bool output. Same 3-operand shape as ExecuteBinary but the output is always Boolean.

public void ExecuteComparison(ComparisonOp op)

Parameters

op ComparisonOp

Remarks

An exception that leaves it marks the iteration failed (NumSharp.Backends.Iteration.NDIterRef.MarkFailed()), so Dispose() clears the held buffers and discards the COPY_IF_OVERLAP write-backs instead of writing them into the operands, as NumPy's NpyIter_Deallocate does when an error is set.

ExecuteCopy()

Same-type copy with broadcast. When both operands are contiguous the kernel collapses to cpblk.

public void ExecuteCopy()

Remarks

An exception that leaves it marks the iteration failed (NumSharp.Backends.Iteration.NDIterRef.MarkFailed()), so Dispose() clears the held buffers and discards the COPY_IF_OVERLAP write-backs instead of writing them into the operands, as NumPy's NpyIter_Deallocate does when an error is set.

ExecuteElementWise(NPTypeCode[], Action<ILGenerator>, Action<ILGenerator>?, in InnerLoopKernelKey)

Packed-key form of ExecuteElementWise(NPTypeCode[], Action<ILGenerator>, Action<ILGenerator>?, string) for the production ufunc routes: a cache hit runs the kernel without building a string or touching operandTypes beyond its length; a miss compiles under the equivalent string key (see InnerLoopKernelKey).

public void ExecuteElementWise(NPTypeCode[] operandTypes, Action<ILGenerator> scalarBody, Action<ILGenerator>? vectorBody, in InnerLoopKernelKey key)

Parameters

operandTypes NPTypeCode[]
scalarBody Action<ILGenerator>
vectorBody Action<ILGenerator>
key InnerLoopKernelKey

ExecuteElementWise(NPTypeCode[], Action<ILGenerator>, Action<ILGenerator>?, in InnerLoopKernelKey, void*)

public void ExecuteElementWise(NPTypeCode[] operandTypes, Action<ILGenerator> scalarBody, Action<ILGenerator>? vectorBody, in InnerLoopKernelKey key, void* auxdata)

Parameters

operandTypes NPTypeCode[]

Data-operand dtypes (inputs then output; a trailing ARRAYMASK operand excluded).

scalarBody Action<ILGenerator>

Per-element IL body.

vectorBody Action<ILGenerator>

Optional per-vector IL body.

key InnerLoopKernelKey

The kernel's packed cache key.

auxdata void*

Cookie passed to every inner-loop call, or null. It must stay valid for the whole call. The 2-D block kernels (narrow strided rows, masked or not) do not receive it: they serve multi-axis iterations, which NumPy mostly BUFFERS into one contiguous call (the second operand's NaN everywhere, which the kernel bodies already give). The multi-axis broadcasts NumPy runs one region-rule call per row (unbuffered) or per buffer fill (a 0-d or size-1 operand stays at stride 0 in its buffered loop) are open bugs, pinned with NumPy's bits in test/NumSharp.Tests/OpenBugs.NaNOperandOrder.cs.

Exceptions

ArgumentNullException

operandTypes is null.

ArgumentException

The operand-type count does not match the iterator.

ExecuteElementWise(NPTypeCode[], Action<ILGenerator>, Action<ILGenerator>?, string)

Compile and run an element-wise kernel using user-supplied scalar and optional vector emit bodies. The factory wraps the bodies in a 4×-unrolled SIMD loop (when the operand types allow) plus a scalar-strided fallback for non-contiguous inner axes.

public void ExecuteElementWise(NPTypeCode[] operandTypes, Action<ILGenerator> scalarBody, Action<ILGenerator>? vectorBody, string cacheKey)

Parameters

operandTypes NPTypeCode[]

[input0, input1, ..., output] — one entry per iterator operand. Length must equal NOp.

scalarBody Action<ILGenerator>

Per-element IL body. On entry, stack holds the N input values (operand 0 deepest, operand N-1 on top). On exit, stack must hold exactly one value of the output dtype.

vectorBody Action<ILGenerator>

Per-vector IL body (optional). When supplied AND all operand dtypes are identical AND SIMD-capable, emitted as the fast path. Stack contract mirrors scalarBody but with Vector{W}<T> in place of scalar values.

cacheKey string

Unique identifier for this kernel.

ExecuteElementWiseBinary(NPTypeCode, NPTypeCode, NPTypeCode, Action<ILGenerator>, Action<ILGenerator>?, in InnerLoopKernelKey)

Packed-key binary form: a cache hit allocates nothing.

public void ExecuteElementWiseBinary(NPTypeCode lhs, NPTypeCode rhs, NPTypeCode outType, Action<ILGenerator> scalarBody, Action<ILGenerator>? vectorBody, in InnerLoopKernelKey key)

Parameters

lhs NPTypeCode
rhs NPTypeCode
outType NPTypeCode
scalarBody Action<ILGenerator>
vectorBody Action<ILGenerator>
key InnerLoopKernelKey

ExecuteElementWiseBinary(NPTypeCode, NPTypeCode, NPTypeCode, Action<ILGenerator>, Action<ILGenerator>?, in InnerLoopKernelKey, void*)

Packed-key binary form that also hands the 1-D inner-loop kernel an auxdata cookie (the binary ufunc routes pass a NumSharp.Backends.Kernels.BroadcastNaNRule); a cache hit allocates nothing.

public void ExecuteElementWiseBinary(NPTypeCode lhs, NPTypeCode rhs, NPTypeCode outType, Action<ILGenerator> scalarBody, Action<ILGenerator>? vectorBody, in InnerLoopKernelKey key, void* auxdata)

Parameters

lhs NPTypeCode

The first input's dtype.

rhs NPTypeCode

The second input's dtype.

outType NPTypeCode

The output's dtype.

scalarBody Action<ILGenerator>

Per-element IL body.

vectorBody Action<ILGenerator>

Optional per-vector IL body.

key InnerLoopKernelKey

The kernel's packed cache key.

auxdata void*

Cookie passed to every inner-loop call, or null; it must stay valid for the whole call. The 2-D block kernel (narrow strided rows) does not receive it, because NumPy mostly buffers such multi-axis iterations into one contiguous call; the ones it runs one region-rule call per row (unbuffered) or per buffer fill (a 0-d or size-1 operand stays at stride 0) are open bugs, pinned in test/NumSharp.Tests/OpenBugs.NaNOperandOrder.cs.

Exceptions

ArgumentException

The iterator does not have three data operands.

ExecuteElementWiseBinary(NPTypeCode, NPTypeCode, NPTypeCode, Action<ILGenerator>, Action<ILGenerator>?, string)

Convenience: 2-input + 1-output (binary).

public void ExecuteElementWiseBinary(NPTypeCode lhs, NPTypeCode rhs, NPTypeCode outType, Action<ILGenerator> scalarBody, Action<ILGenerator>? vectorBody, string cacheKey)

Parameters

lhs NPTypeCode
rhs NPTypeCode
outType NPTypeCode
scalarBody Action<ILGenerator>
vectorBody Action<ILGenerator>
cacheKey string

ExecuteElementWiseTernary(NPTypeCode, NPTypeCode, NPTypeCode, NPTypeCode, Action<ILGenerator>, Action<ILGenerator>?, string)

Convenience: 3-input + 1-output (ternary, FMA-shaped).

public void ExecuteElementWiseTernary(NPTypeCode a, NPTypeCode b, NPTypeCode c, NPTypeCode outType, Action<ILGenerator> scalarBody, Action<ILGenerator>? vectorBody, string cacheKey)

Parameters

a NPTypeCode
b NPTypeCode
c NPTypeCode
outType NPTypeCode
scalarBody Action<ILGenerator>
vectorBody Action<ILGenerator>
cacheKey string

ExecuteElementWiseUnary(NPTypeCode, NPTypeCode, Action<ILGenerator>, Action<ILGenerator>?, in InnerLoopKernelKey)

Packed-key unary form: a cache hit allocates nothing.

public void ExecuteElementWiseUnary(NPTypeCode inType, NPTypeCode outType, Action<ILGenerator> scalarBody, Action<ILGenerator>? vectorBody, in InnerLoopKernelKey key)

Parameters

inType NPTypeCode
outType NPTypeCode
scalarBody Action<ILGenerator>
vectorBody Action<ILGenerator>
key InnerLoopKernelKey

ExecuteElementWiseUnary(NPTypeCode, NPTypeCode, Action<ILGenerator>, Action<ILGenerator>?, string)

Convenience: 1-input + 1-output (unary).

public void ExecuteElementWiseUnary(NPTypeCode inType, NPTypeCode outType, Action<ILGenerator> scalarBody, Action<ILGenerator>? vectorBody, string cacheKey)

Parameters

inType NPTypeCode
outType NPTypeCode
scalarBody Action<ILGenerator>
vectorBody Action<ILGenerator>
cacheKey string

ExecuteExpression(NDExpr, NPTypeCode[], NPTypeCode, string?)

Compile and run an expression tree over the iterator's operands. The tree's leaves reference inputs by position (NDExpr.Input(i)) and constants; interior nodes combine them via primitive ops. The compiler produces the same style of kernel as ExecuteElementWise(NPTypeCode[], Action<ILGenerator>, Action<ILGenerator>?, string).

public void ExecuteExpression(NDExpr expression, NPTypeCode[] inputTypes, NPTypeCode outputType, string? cacheKey = null)

Parameters

expression NDExpr

Root of the expression tree.

inputTypes NPTypeCode[]

Dtypes of the first N operands (all inputs). Length must equal NOp - 1.

outputType NPTypeCode

Dtype of the last operand (the output).

cacheKey string

Optional cache key; if null, a key is derived from the tree's structural signature.

ExecuteGeneric<TKernel>(TKernel)

Struct-generic overload — the JIT devirtualizes and inlines the kernel call through the TKernel type parameter. Preferred when the kernel is known at call site.

Performance note: the single-iteration fast path (coalesced + EXLOOP or ONEITERATION) avoids the do/while + delegate call so the JIT can autovectorize the kernel body. An empty iteration (IterSize 0) makes no call, as in ForEach(NDInnerLoopFunc, void*).

public void ExecuteGeneric<TKernel>(TKernel kernel) where TKernel : struct, INDInnerLoop

Parameters

kernel TKernel

The kernel.

Type Parameters

TKernel

The inner-loop kernel type.

Remarks

An exception that leaves it marks the iteration failed (NumSharp.Backends.Iteration.NDIterRef.MarkFailed()), so Dispose() clears the held buffers and discards the COPY_IF_OVERLAP write-backs instead of writing them into the operands, as NumPy's NpyIter_Deallocate does when an error is set.

ExecuteRawIL(Action<ILGenerator>, string, void*)

Compile and run a user-authored inner-loop kernel. The delegate signature is NDInnerLoopFunc; the body must emit its own ret. Cached by cacheKey, so the IL generator is invoked exactly once per key.

public void ExecuteRawIL(Action<ILGenerator> emitBody, string cacheKey, void* auxdata = null)

Parameters

emitBody Action<ILGenerator>
cacheKey string
auxdata void*

Remarks

The caller is responsible for cacheKey uniqueness: two different IL bodies compiled under the same key will silently alias.

ExecuteReducing<TKernel, TAccum>(TKernel, TAccum)

Reducing variant. The accumulator is passed by reference; return false from the kernel to abort (used by All/Any early exit).

public TAccum ExecuteReducing<TKernel, TAccum>(TKernel kernel, TAccum init) where TKernel : struct, INDReducingInnerLoop<TAccum> where TAccum : unmanaged

Parameters

kernel TKernel

The kernel.

init TAccum

The accumulator's starting value.

Returns

TAccum

The accumulator after the last call: init itself for an empty iteration, which makes no call (see ForEach(NDInnerLoopFunc, void*)).

Type Parameters

TKernel

The inner-loop kernel type.

TAccum

The accumulator type.

Remarks

An exception that leaves it marks the iteration failed (NumSharp.Backends.Iteration.NDIterRef.MarkFailed()), so Dispose() clears the held buffers and discards the COPY_IF_OVERLAP write-backs instead of writing them into the operands, as NumPy's NpyIter_Deallocate does when an error is set.

ExecuteReduction<TResult>(ReductionOp, NPTypeCode?)

Reduce a single operand to a scalar of type TResult: one typed element-reduction IL kernel walks the whole operand by its strides (the iterator's buffering flags are not consulted). For NumPy's double-loop reduction schedule over a BUFFER + REDUCE iterator, call BufferedReduce<TKernel, TAccum>(TKernel) instead.

public TResult ExecuteReduction<TResult>(ReductionOp op, NPTypeCode? accumType = null) where TResult : unmanaged

Parameters

op ReductionOp

The reduction.

accumType NPTypeCode?

The accumulator (loop) dtype the caller resolved, which must be TResult's dtype; null derives the op's default (the NEP 50 accumulator for sum / prod). The caller's dtype matters: np.sum(int8, dtype=int8) over an F-contiguous view asked for an int8 result, and the derived int64 accumulator generated an int8-typed kernel under the (int8, int64) cache key — a wrong sum, and every later default int8 sum found that kernel and failed its delegate cast ("IL kernel not available for Sum(SByte) -> Int64").

Returns

TResult

The reduced value.

Type Parameters

TResult

The CLR type of the result — the accumulator's own type.

Remarks

An exception that leaves it marks the iteration failed (NumSharp.Backends.Iteration.NDIterRef.MarkFailed()), so Dispose() clears the held buffers and discards the COPY_IF_OVERLAP write-backs instead of writing them into the operands, as NumPy's NpyIter_Deallocate does when an error is set.

Exceptions

InvalidOperationException

The iterator does not hold exactly one operand.

ExecuteScan(ReductionOp)

Cumulative scan (CumSum, CumProd) over [in, out].

public void ExecuteScan(ReductionOp op)

Parameters

op ReductionOp

Remarks

An exception that leaves it marks the iteration failed (NumSharp.Backends.Iteration.NDIterRef.MarkFailed()), so Dispose() clears the held buffers and discards the COPY_IF_OVERLAP write-backs instead of writing them into the operands, as NumPy's NpyIter_Deallocate does when an error is set.

ExecuteUnary(UnaryOp)

Run a unary op over [in, out].

public void ExecuteUnary(UnaryOp op)

Parameters

op UnaryOp

Remarks

An exception that leaves it marks the iteration failed (NumSharp.Backends.Iteration.NDIterRef.MarkFailed()), so Dispose() clears the held buffers and discards the COPY_IF_OVERLAP write-backs instead of writing them into the operands, as NumPy's NpyIter_Deallocate does when an error is set.

ForEach(NDInnerLoopFunc, void*)

Drive the iterator with a user-supplied inner-loop kernel. Matches the pattern used by NumPy ufuncs in C:

do { inner(dataptrs, strides, count, aux); } while (iternext);

The iterator decides the semantics: • Fully coalesced + contiguous → 1 call covering IterSize elements. • EXTERNAL_LOOP → 1 call per outer index, count = inner dim size. • Buffered → 1 call per buffer fill, count = BufIterEnd. • Otherwise → 1 call per element, count = 1. • Empty (IterSize 0) → no call at all, as NumPy's callers never enter the loop: a kernel can rely on every call covering elements that exist. Outputs keep whatever the caller seeded (a reduction's identity).

public void ForEach(NDInnerLoopFunc kernel, void* auxdata = null)

Parameters

kernel NDInnerLoopFunc

The inner-loop kernel.

auxdata void*

The kernel's extra argument, passed through to every call.

Remarks

An exception that leaves it marks the iteration failed (NumSharp.Backends.Iteration.NDIterRef.MarkFailed()), so Dispose() clears the held buffers and discards the COPY_IF_OVERLAP write-backs instead of writing them into the operands, as NumPy's NpyIter_Deallocate does when an error is set.

Exceptions

ArgumentNullException

kernel is null.

FreeState(NDIterState*)

Tear down a state pointer previously obtained from ReleaseState(). Mirrors Dispose()'s cleanup path but operates on a bare pointer so long-lived owners can free the state without reconstructing an NDIterRef.

public static void FreeState(NDIterState* state)

Parameters

state NDIterState*

GetAxisStrideArray(int, scoped Span<long>)

Gets the array of strides for the specified axis, one stride per operand. Matches NumPy's NDIter_GetAxisStrideArray (nditer_api.c:1309).

If the iterator is tracking a multi-index, returns strides for the user-supplied axis in original-array coordinates (perm is walked to locate the internal axis). Otherwise returns strides for iteration axis axis in Fortran order (fastest-changing axis first).

Strides are returned in BYTES (multiplying NumSharp's internal element-count strides by the operand's element size) to match NumPy's byte-stride convention.

public void GetAxisStrideArray(int axis, scoped Span<long> outStrides)

Parameters

axis int

Axis index (0-based). With HASMULTIINDEX: original-array axis. Without: fastest-changing-first (Fortran) ordering.

outStrides Span<long>

Output span of length ≥ NOp; filled with byte strides.

GetDataPtr(int)

Get pointer to current data for operand. When buffering is enabled, returns pointer to buffer position. Otherwise returns pointer to source array position. Matches NumPy's dataptrs[i] access.

public void* GetDataPtr(int operand)

Parameters

operand int

Returns

void*

GetDataPtrArray()

Get array of current data pointers.

public void** GetDataPtrArray()

Returns

void**

GetDescrArray()

Get operand dtypes.

public NPTypeCode[] GetDescrArray()

Returns

NPTypeCode[]

GetIndex()

Get the current flat index. Requires C_INDEX or F_INDEX flag to be set during construction.

public long GetIndex()

Returns

long

GetInnerFixedStrideArray(scoped Span<long>)

Copies the array of strides that are fixed during iteration into outStrides. Matches NumPy's NDIter_GetInnerFixedStrideArray (nditer_api.c:1357).

Buffered iterators copy BufStrides. Non-buffered iterators copy the innermost-axis stride from Strides and convert it to bytes.

public void GetInnerFixedStrideArray(scoped Span<long> outStrides)

Parameters

outStrides Span<long>

Output span of length at least NOp.

GetInnerLoopSizePtr()

A pointer to the current inner loop's element count — NumPy's NpyIter_GetInnerLoopSizePtr: the buffer's inner loop size for a buffered iterator (BufTransferSize, one core inside the reduce double loop), the innermost extent otherwise.

public long* GetInnerLoopSizePtr()

Returns

long*

The pointer (it stays valid for the iterator's life; the value changes as the iteration moves).

Remarks

Do not call it on an unbuffered 0-d iteration: there is no innermost extent (it would read Shape[-1]).

GetInnerStrideArray()

Get inner loop stride array.

public long* GetInnerStrideArray()

Returns

long*

GetIterNext()

The iteration-advance function for the iterator's flags — NumPy's NpyIter_GetIterNext: a single iteration, the buffered reduce double loop (npyiter_buffered_reduce_iternext), plain buffering (npyiter_buffered_iternext), an external loop, or one element at a time.

public NDIterNextFunc GetIterNext()

Returns

NDIterNextFunc

The advance function (cached until a flag change clears the cache).

Remarks

The buffered functions honor the external-loop flag themselves, as NumPy's do: with it a step hands out the next inner loop, without it the next element of the current inner loop (the buffer is written back and refilled at its end). The reduce double loop is chosen by the buffering setup (BUFREDUCE), not by the presence of a reduction operand: NumPy walks a reduction whose split does not use the double loop with the plain buffered steps.

GetIterView(int)

Returns a view of the i-th operand with the iterator's internal axes ordering. A C-order iteration of this view is equivalent to the iterator's iteration order.

For example, if a 3D array was coalesced to 1D, this returns a 1D view. If axes were reordered for memory efficiency, this reflects that reordering.

Not available when buffering is enabled. Matches NumPy's NDIter_GetIterView behavior.

public NDArray GetIterView(int operand)

Parameters

operand int

The operand index (0 to NOp-1)

Returns

NDArray

An NDArray view with the iterator's internal shape and strides

GetMultiIndex(scoped Span<long>)

Get the current multi-index (coordinates) in original axis order. Uses the Perm array to map internal coordinates to original array coordinates. When NEGPERM is set, flipped axes have negative perm entries and their coordinates are reversed (shape - coord - 1). Requires MULTI_INDEX flag to be set during construction.

public void GetMultiIndex(scoped Span<long> outCoords)

Parameters

outCoords Span<long>

GetMultiIndexFunc()

Returns a specialized delegate for computing multi-index. Matches NumPy's NDIter_GetGetMultiIndex. Throws on failure instead of returning null (thin wrapper over the out-errmsg overload).

public NDIterGetMultiIndexFunc GetMultiIndexFunc()

Returns

NDIterGetMultiIndexFunc

GetMultiIndexFunc(out string?)

Returns a specialized delegate for computing multi-index based on iterator flags. Matches NumPy's NDIter_GetGetMultiIndex (nditer_templ.c.src:481).

NumPy generates 12 specializations on (HASINDEX × IDENTPERM × NEGPERM × BUFFER). NumSharp dispatches to 3 variants (BUFFER and HASINDEX don't affect coords):

  1. IDENTPERM — direct copy of internal coords
  2. Positive perm — apply perm[] mapping
  3. NEGPERM — apply perm[] with flip decoding

The returned delegate takes raw NDIterState and a pointer to output coords.

public NDIterGetMultiIndexFunc? GetMultiIndexFunc(out string? errmsg)

Parameters

errmsg string

Set on failure; null on success.

Returns

NDIterGetMultiIndexFunc

Delegate, or null if iterator is not tracking multi-index.

GetOperandArray()

Get operand arrays.

public NDArray[]? GetOperandArray()

Returns

NDArray[]

GetTransferFlags()

Fetch the NDArrayMethodFlags (runtime) flags for all transfer functions (i.e. copy to buffer/casts). Matches NumPy's NDIter_GetTransferFlags (nditer_api.c:903). Decoded from the top 8 bits of ItFlags.

In .NET context, REQUIRES_PYAPI is never set — included for API parity only.

public NDArrayMethodFlags GetTransferFlags()

Returns

NDArrayMethodFlags

GetValue<T>(int)

Get current value for operand as T. When buffering with casting is enabled, reads from buffer (which has target dtype).

public T GetValue<T>(int operand = 0) where T : unmanaged

Parameters

operand int

Returns

T

Type Parameters

T

GotoIndex(long)

Jump to a specific flat index position (C or F order based on construction flags). Requires C_INDEX or F_INDEX flag to be set during construction. Matches NumPy's NDIter_GotoIndex behavior: the position is read off the index strides, so flipped and coalesced axes need no special case.

public void GotoIndex(long flatIndex)

Parameters

flatIndex long

The flat index in C or F order (depending on flags)

Exceptions

InvalidOperationException

No flat index is tracked.

IndexOutOfRangeException

flatIndex is outside [0, IterSize).

GotoIterIndex(long)

Moves the iterator to iterindex — NumPy's NpyIter_GotoIterIndex: inside the current buffer's inner loop only the pointers move (outside the reduce double loop), anywhere else a buffered iterator writes its buffer back and fills a new one there.

public void GotoIterIndex(long iterindex)

Parameters

iterindex long

The target iteration index (the callers check it against the range).

Remarks

A position inside a core starts the next fill there: it covers only the rest of that core (NumPy's core offset), and in the reduce double loop such a fill writes every buffered operand's partial result back as zero elements, which loses it — NumPy 2.4.2's behavior, kept (it reads and writes nothing outside the operands).

Exceptions

ValueError

A string operand's conversion fails during the write-back or the fill.

OverflowException

A text converted into an integer holds a number outside it.

OutOfMemoryException

A StringDType transfer cannot allocate a string.

GotoMultiIndex(scoped ReadOnlySpan<long>)

Jump to a specific multi-index (coordinates) given in original axis order. Uses the Perm array to map original coordinates to internal iteration order. When NEGPERM is set, flipped axes have negative perm entries and their coordinates are reversed when mapping to internal coordinates. Requires MULTI_INDEX flag to be set during construction.

public void GotoMultiIndex(scoped ReadOnlySpan<long> coords)

Parameters

coords ReadOnlySpan<long>

InvokeMultiIndex(NDIterGetMultiIndexFunc, long*)

Invokes the specialized multi-index delegate with this iterator's internal state. This mirrors NumPy's pattern: fn(iter, outcoords), where NumSharp's iterator handle is a ref struct and the state is held internally.

public void InvokeMultiIndex(NDIterGetMultiIndexFunc fn, long* outCoords)

Parameters

fn NDIterGetMultiIndexFunc
outCoords long*

InvokeMultiIndex(NDIterGetMultiIndexFunc, scoped Span<long>)

public void InvokeMultiIndex(NDIterGetMultiIndexFunc fn, scoped Span<long> outCoords)

Parameters

fn NDIterGetMultiIndexFunc
outCoords Span<long>

IsFirstVisit(int)

Check if this is the first visit to the current element of a reduction operand. This is used for initialization (e.g., set to 0 before summing).

For reduction operands (stride=0 on some axes), returns true when all coordinates on reduction axes are 0. Returns false when any coordinate on a reduction axis is non-zero (meaning we've already visited this output element from another input element).

For non-reduction operands, always returns true (every visit is "first").

Matches NumPy's NDIter_IsFirstVisit behavior.

public bool IsFirstVisit(int operand)

Parameters

operand int

The operand index.

Returns

bool

Whether the operand's current element is visited for the first time.

Remarks

Buffered, the coordinates are the current buffer's start (NumPy's), and the reduce double loop adds NumPy's outer-level test: after the first outer step of a fill, an operand whose core repeats (outer stride 0) is not seen first. NumPy meant the function for the external-loop mode, where a caller also checks the inner stride; without an external loop NumSharp applies that check itself (an inner stride of 0 repeats the element along the inner loop, so only its first position is a first visit) — NumPy's per-element answers there "may be incorrect", NumSharp's are exact.

Exceptions

ArgumentOutOfRangeException

operand is not an operand index.

IsOperandReduction(int)

Check if a specific operand is a reduction operand (has stride=0 for READWRITE).

public bool IsOperandReduction(int operand)

Parameters

operand int

Returns

bool

Iternext()

Advances to the next position and returns whether the iteration continues — NumPy's iternext(): the next element, or with an external loop the next inner loop. A buffered iterator writes its buffer back and fills the next one at a buffer's end; in the reduce double loop it first steps through the fill's outer steps (BufferedReduceIternext(ref NDIterState)).

public bool Iternext()

Returns

bool

True while elements remain; false when the iteration is complete (the last buffer written back).

Remarks

A buffered reduction can run past the range end, as NumPy's does: when a range is set (or a reset kept the buffer) while the current fill already reaches past the new end in its outer steps, the iteration ends with that fill, not at the range end. Every element it visits lies in the operands.

Exceptions

ValueError

A string operand's conversion fails during a buffer transfer.

OverflowException

A text converted into an integer holds a number outside it.

OutOfMemoryException

A StringDType transfer cannot allocate a string.

MultiNew(int, NDArray[], NDIterGlobalFlags, NPY_ORDER, NPY_CASTING, NDIterPerOpFlags[], NPTypeCode[]?)

Create multi-operand iterator. Equivalent to NumPy's NDIter_MultiNew.

public static NDIterRef MultiNew(int nop, NDArray[] op, NDIterGlobalFlags flags, NPY_ORDER order, NPY_CASTING casting, NDIterPerOpFlags[] opFlags, NPTypeCode[]? opDtypes = null)

Parameters

nop int
op NDArray[]
flags NDIterGlobalFlags
order NPY_ORDER
casting NPY_CASTING
opFlags NDIterPerOpFlags[]
opDtypes NPTypeCode[]

Returns

NDIterRef

New(NDArray, NDIterGlobalFlags, NPY_ORDER, NPY_CASTING, NPTypeCode?)

Create single-operand iterator. Equivalent to NumPy's NDIter_New.

public static NDIterRef New(NDArray op, NDIterGlobalFlags flags = NDIterGlobalFlags.None, NPY_ORDER order = NPY_ORDER.NPY_KEEPORDER, NPY_CASTING casting = NPY_CASTING.NPY_SAFE_CASTING, NPTypeCode? dtype = null)

Parameters

op NDArray
flags NDIterGlobalFlags
order NPY_ORDER
casting NPY_CASTING
dtype NPTypeCode?

Returns

NDIterRef

NewReduce(NDArray, NDArray, int, NDIterGlobalFlags)

Build a 2-operand REDUCE iterator over input reducing the single axis axis into output. output must already have the reduced shape (input shape with axis removed) and be pre-seeded with the reduction identity.

public static NDIterRef NewReduce(NDArray input, NDArray output, int axis, NDIterGlobalFlags extraFlags = NDIterGlobalFlags.None)

Parameters

input NDArray

Read-only source operand.

output NDArray

Read-write destination; reduced shape, identity-seeded.

axis int

Normalized (non-negative) axis to reduce.

extraFlags NDIterGlobalFlags

Extra global flags OR-ed onto REDUCE_OK|EXTERNAL_LOOP.

Returns

NDIterRef

NewReduce(NDArray, NDArray, int[], NDIterGlobalFlags)

Multi-axis variant of NewReduce(NDArray, NDArray, int, NDIterGlobalFlags). Every axis in axes is reduced (stride 0 in the output); the remaining axes map to the output in ascending order. axes must be normalized (non-negative) and contain no duplicates.

public static NDIterRef NewReduce(NDArray input, NDArray output, int[] axes, NDIterGlobalFlags extraFlags = NDIterGlobalFlags.None)

Parameters

input NDArray
output NDArray
axes int[]
extraFlags NDIterGlobalFlags

Returns

NDIterRef

ReleaseState()

Transfer ownership of the underlying NDIterState pointer out of this NDIterRef. After the call, this instance's Dispose() is a no-op and the returned pointer becomes the caller's responsibility to free via FreeState(NDIterState*) (or equivalent manual teardown: FreeBuffers(ref NDIterState) when BUFFER is set, FreeDimArrays(), and Free(void*)).

Intended for callers that need to hold the iterator state across a non-ref-struct boundary (class fields, long-lived objects) where a ref struct can't live.

public NDIterState* ReleaseState()

Returns

NDIterState*

RemoveAxis(int)

Removes ORIGINAL axis axis from the iteration — NumPy's NpyIter_RemoveAxis: the iterator then walks every other axis with that axis fixed at logical index 0, so a caller can walk it itself (the IterAllButAxis pattern of NumPy's reductions and scans), and the iterator is reset to its start.

public bool RemoveAxis(int axis)

Parameters

axis int

The axis in the operands' own (original) numbering, not the iterator's internal order.

Returns

bool

True (failures raise NumPy's errors).

Remarks

The axis is located through the permutation, so it is the right one whatever order the iterator settled on (an F-ordered or transposed operand under K order, a reversed axis). A REVERSED axis (one the construction flipped to walk memory forward) first moves every operand's base back to its logical index 0. Removing the last axis leaves a 0-d iteration of one element.

Exceptions

RuntimeError

The iterator does not track a multi-index, tracks a flat index, or is buffered (NumPy's three texts).

ValueError

axis is outside [0, ndim): "axis out of bounds in iterator RemoveAxis".

RemoveMultiIndex()

Stops tracking the multi-index and merges the axes that can be walked as one — NumPy's NpyIter_RemoveMultiIndex: the iterator is reset first (a buffer written back, delayed buffers allocated), then coalesced, and starts again at the range start.

public bool RemoveMultiIndex()

Returns

bool

True when a multi-index was removed; false when none was tracked (the reset still happened) or delayed buffers could not be allocated.

Remarks

A buffered iterator keeps NumPy's buffering setup — the one chosen for the axes before the merge — wherever it still walks the right elements: when nothing merged, when everything merged into one axis (the old cores then run along it: an external loop hands out the OLD core's chunks, as NumPy's does), when the merges happened above the setup's outer position, and when the setup fills one core at a time (no reduce loop). Otherwise NumPy's kept setup walks wrong elements or reads stale axis data, and NumSharp runs the setup again over the merged axes (RebuildBufferingSetup(ref NDIterState)).

Exceptions

OutOfMemoryException

A partly merged buffered iteration's new buffers cannot be allocated.

NotSupportedException

A WRITEMASKED operand the rerun buffering setup buffers has a mask that is not bool or uint8.

ValueError

A string operand's conversion fails during the write-back or a fill.

Reset()

Moves the iterator to the start of its range — NumPy's NpyIter_Reset. A buffered iterator first allocates delayed buffers (DELAY_BUFALLOC) or writes the current buffer back, then fills the buffers at the start.

public bool Reset()

Returns

bool

True; false only when delayed buffers could not be allocated.

Remarks

NumPy leaves a buffer that already starts at the range start (and lies inside the range) alone: writing it back and refilling it would not be a no-op, because a write-only operand's buffer is never read from its array — after a first reset it still holds the previous buffer's data, and a second write-back would put that data over the range's first elements.

Exceptions

ValueError

A string operand's conversion fails during the write-back or the fill.

OverflowException

A text converted into an integer holds a number outside it.

OutOfMemoryException

A StringDType transfer cannot allocate a string.

ResetBasePointers(NDArray[])

Convenience overload: resets base pointers using the data pointers of new NDArray operands. The new arrays must have the same shape, dtype, and layout as the original operands.

public bool ResetBasePointers(NDArray[] newOperands)

Parameters

newOperands NDArray[]

Returns

bool

ResetBasePointers(scoped ReadOnlySpan<nint>)

Resets the iterator to its initial state with new base data pointers. Matches NumPy's NDIter_ResetBasePointers (nditer_api.c:314).

For each operand, sets resetdataptr[iop] = baseptrs[iop] + baseoffsets[iop], where baseoffsets is the cumulative byte offset recorded by FlipNegativeStrides. Then repositions the iterator to IterStart.

The new arrays pointed to by baseptrs MUST have the exact same shape, dtype, and memory layout as the original operands. This is typically used in nested iteration (ufunc-style) where one iterator feeds data pointers to another.

Throws ArgumentException if baseptrs.Length != NOp.

public bool ResetBasePointers(scoped ReadOnlySpan<nint> baseptrs)

Parameters

baseptrs ReadOnlySpan<nint>

Array of new base data pointers, one per operand.

Returns

bool

True on success.

ResetToIterIndexRange(long, long)

Reset iterator to a specific iteration range. Enables ranged iteration for parallel chunking.

public bool ResetToIterIndexRange(long start, long end)

Parameters

start long

Start index (inclusive)

end long

End index (exclusive)

Returns

bool

True if range is valid (and the reset succeeded), false otherwise

Remarks

Ends with Reset(), as NumPy's NpyIter_ResetToIterIndexRange ends with NpyIter_Reset: the position moves to start, a buffered window is written back and primed for the new range, and delayed buffers are allocated (false when that allocation fails).

SetValue<T>(T, int)

Set current value for operand. When buffering with casting is enabled, writes to buffer (which has target dtype).

public void SetValue<T>(T value, int operand = 0) where T : unmanaged

Parameters

value T
operand int

Type Parameters

T