Struct NDIterRef
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
HasExternalLoop
Whether iterator has external loop.
public bool HasExternalLoop { get; }
Property Value
HasGrowInner
Whether iterator uses GROWINNER optimization for buffering.
public bool HasGrowInner { get; }
Property Value
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
HasIndex
Check if iterator is tracking a flat index.
public bool HasIndex { get; }
Property Value
HasMultiIndex
Check if iterator is tracking multi-index.
public bool HasMultiIndex { get; }
Property Value
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
HasWriteMaskedOperand
True if any operand is flagged WRITEMASKED (and a corresponding ARRAYMASK exists).
public bool HasWriteMaskedOperand { get; }
Property Value
IsContiguous
Whether all operands are contiguous.
public bool IsContiguous { get; }
Property Value
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
IsRanged
Check if iterator is using ranged iteration.
public bool IsRanged { get; }
Property Value
IsReduction
Check if iteration includes reduction operands.
public bool IsReduction { get; }
Property Value
IterEnd
Get the current iteration range end.
public long IterEnd { get; }
Property Value
IterIndex
Current iteration index.
public long IterIndex { get; }
Property Value
IterRange
Get the current iteration range as (start, end) tuple.
public (long Start, long End) IterRange { get; }
Property Value
IterSize
Total iteration count.
public long IterSize { get; }
Property Value
IterStart
Get the current iteration range start.
public long IterStart { get; }
Property Value
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
NDim
Number of dimensions after coalescing.
public int NDim { get; }
Property Value
NOp
Number of operands.
public int NOp { get; }
Property Value
RawState
public NDIterState* RawState { get; }
Property Value
RequiresBuffering
Whether iterator requires buffering.
public bool RequiresBuffering { get; }
Property Value
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
nopintThe number of operands.
opNDArray[]The operands; a null entry is allocated (ALLOCATE / VIRTUAL).
flagsNDIterGlobalFlagsThe global flags.
orderNPY_ORDERThe iteration order.
castingNPY_CASTINGThe casting rule every operand's cast must satisfy.
opFlagsNDIterPerOpFlags[]The per-operand flags.
opDtypesNPTypeCode[]The per-operand iteration type codes (Empty or null = the operand's).
opAxesNDimintThe op_axes rank, or -1 when
opAxesis unused.opAxesint[][]The per-operand axis maps.
iterShapelong[]An explicit iteration shape, or null to broadcast the operands.
bufferSizelongThe buffer size in elements (0 =
np.getbufsize()).opDescrsDType[]The per-operand iteration DESCRIPTORS — NumPy's resolved
op_dtypearray (a null entry means the operand's own descriptor; for an operand the iterator allocates, the type-code rules ofopDtypesdecide 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, thennpyiter_allocate_arrays' copy routes (a read-only 0-d operand copied into its iteration dtype, acopy/updateifcopyoperand 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 violatescasting(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
kernelTKernelThe kernel; returning false ends the walk early (the held buffer is written back at Dispose).
Type Parameters
TKernelThe inner-loop kernel.
TAccumThe 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
StringDTypefill 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
itemsizelongBase stride (typically element size in bytes).
outStridesSpan<long>Output span of length ≥ NDim, one stride per axis in original array order (C-order).
Returns
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
writerTextWriter
DebugPrintToString()
Returns the debug dump as a string.
public string DebugPrintToString()
Returns
DetectExecutionPath()
Pick the right ExecutionPath for MixedType/Comparison kernel selection by scanning the post-coalesce stride picture.
public ExecutionPath DetectExecutionPath()
Returns
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
StringDTypestring.
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
opBinaryOp
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
opComparisonOp
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
operandTypesNPTypeCode[]scalarBodyAction<ILGenerator>vectorBodyAction<ILGenerator>keyInnerLoopKernelKey
ExecuteElementWise(NPTypeCode[], Action<ILGenerator>, Action<ILGenerator>?, in InnerLoopKernelKey, void*)
Packed-key ExecuteElementWise(NPTypeCode[], Action<ILGenerator>, Action<ILGenerator>?, in InnerLoopKernelKey)
that also hands the 1-D inner-loop kernel an auxdata cookie — the binary ufunc routes pass a
NumSharp.Backends.Kernels.BroadcastNaNRule there (see EmitBroadcastNaNRegion(ILGenerator, NPTypeCode, LocalBuilder, LocalBuilder, NPTypeCode, LocalBuilder, LocalBuilder, LocalBuilder, Action<ILGenerator>, bool, Action<ILGenerator>, Action<ILGenerator>, bool, Label)).
public void ExecuteElementWise(NPTypeCode[] operandTypes, Action<ILGenerator> scalarBody, Action<ILGenerator>? vectorBody, in InnerLoopKernelKey key, void* auxdata)
Parameters
operandTypesNPTypeCode[]Data-operand dtypes (inputs then output; a trailing ARRAYMASK operand excluded).
scalarBodyAction<ILGenerator>Per-element IL body.
vectorBodyAction<ILGenerator>Optional per-vector IL body.
keyInnerLoopKernelKeyThe kernel's packed cache key.
auxdatavoid*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
operandTypesis 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
operandTypesNPTypeCode[][input0, input1, ..., output] — one entry per iterator operand. Length must equal NOp.
scalarBodyAction<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.
vectorBodyAction<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
scalarBodybut withVector{W}<T>in place of scalar values.cacheKeystringUnique 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
lhsNPTypeCoderhsNPTypeCodeoutTypeNPTypeCodescalarBodyAction<ILGenerator>vectorBodyAction<ILGenerator>keyInnerLoopKernelKey
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
lhsNPTypeCodeThe first input's dtype.
rhsNPTypeCodeThe second input's dtype.
outTypeNPTypeCodeThe output's dtype.
scalarBodyAction<ILGenerator>Per-element IL body.
vectorBodyAction<ILGenerator>Optional per-vector IL body.
keyInnerLoopKernelKeyThe kernel's packed cache key.
auxdatavoid*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
lhsNPTypeCoderhsNPTypeCodeoutTypeNPTypeCodescalarBodyAction<ILGenerator>vectorBodyAction<ILGenerator>cacheKeystring
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
aNPTypeCodebNPTypeCodecNPTypeCodeoutTypeNPTypeCodescalarBodyAction<ILGenerator>vectorBodyAction<ILGenerator>cacheKeystring
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
inTypeNPTypeCodeoutTypeNPTypeCodescalarBodyAction<ILGenerator>vectorBodyAction<ILGenerator>keyInnerLoopKernelKey
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
inTypeNPTypeCodeoutTypeNPTypeCodescalarBodyAction<ILGenerator>vectorBodyAction<ILGenerator>cacheKeystring
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
expressionNDExprRoot of the expression tree.
inputTypesNPTypeCode[]Dtypes of the first N operands (all inputs). Length must equal NOp - 1.
outputTypeNPTypeCodeDtype of the last operand (the output).
cacheKeystringOptional 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
kernelTKernelThe kernel.
Type Parameters
TKernelThe 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
emitBodyAction<ILGenerator>cacheKeystringauxdatavoid*
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
kernelTKernelThe kernel.
initTAccumThe accumulator's starting value.
Returns
- TAccum
The accumulator after the last call:
inititself for an empty iteration, which makes no call (see ForEach(NDInnerLoopFunc, void*)).
Type Parameters
TKernelThe inner-loop kernel type.
TAccumThe 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
opReductionOpThe reduction.
accumTypeNPTypeCode?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
TResultThe 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
opReductionOp
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
opUnaryOp
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
kernelNDInnerLoopFuncThe inner-loop kernel.
auxdatavoid*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
kernelis 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
stateNDIterState*
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
axisintAxis index (0-based). With HASMULTIINDEX: original-array axis. Without: fastest-changing-first (Fortran) ordering.
outStridesSpan<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
operandint
Returns
- void*
GetDataPtrArray()
Get array of current data pointers.
public void** GetDataPtrArray()
Returns
- void**
GetDescrArray()
Get operand dtypes.
public NPTypeCode[] GetDescrArray()
Returns
GetIndex()
Get the current flat index. Requires C_INDEX or F_INDEX flag to be set during construction.
public long GetIndex()
Returns
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
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
operandintThe 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
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
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):
- IDENTPERM — direct copy of internal coords
- Positive perm — apply perm[] mapping
- 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
errmsgstringSet 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
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
operandint
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
flatIndexlongThe flat index in C or F order (depending on flags)
Exceptions
- InvalidOperationException
No flat index is tracked.
- IndexOutOfRangeException
flatIndexis 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
iterindexlongThe 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
StringDTypetransfer 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
coordsReadOnlySpan<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
fnNDIterGetMultiIndexFuncoutCoordslong*
InvokeMultiIndex(NDIterGetMultiIndexFunc, scoped Span<long>)
Span overload of InvokeMultiIndex(NDIterGetMultiIndexFunc, long*).
public void InvokeMultiIndex(NDIterGetMultiIndexFunc fn, scoped Span<long> outCoords)
Parameters
fnNDIterGetMultiIndexFuncoutCoordsSpan<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
operandintThe 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
operandis 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
operandint
Returns
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
StringDTypetransfer 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
nopintopNDArray[]flagsNDIterGlobalFlagsorderNPY_ORDERcastingNPY_CASTINGopFlagsNDIterPerOpFlags[]opDtypesNPTypeCode[]
Returns
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
opNDArrayflagsNDIterGlobalFlagsorderNPY_ORDERcastingNPY_CASTINGdtypeNPTypeCode?
Returns
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
inputNDArrayRead-only source operand.
outputNDArrayRead-write destination; reduced shape, identity-seeded.
axisintNormalized (non-negative) axis to reduce.
extraFlagsNDIterGlobalFlagsExtra global flags OR-ed onto REDUCE_OK|EXTERNAL_LOOP.
Returns
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
inputNDArrayoutputNDArrayaxesint[]extraFlagsNDIterGlobalFlags
Returns
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
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
axisintThe 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
axisis 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
StringDTypetransfer 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
newOperandsNDArray[]
Returns
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
baseptrsReadOnlySpan<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
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
valueToperandint
Type Parameters
T