[ Web Proxy ]
URL:
Viewing: https://scisharp.github.io/NumSharp/docs/NDArray.html [Back]  [Original]

NumSharp's ndarray is NDArray! | NumSharp Documentation
NumSharp [NumSharp] NumSharp
Table of Contents

NumSharp's ndarray is NDArray!

NumPy's central type is numpy.ndarray. NumSharp's is NDArray. If you know one, you know the other same concept, same memory model, same semantics, same operator behavior, ported to .NET idioms. This page is the quick tour: what NDArray is, how to make one, how to read and modify it, how it compares to numpy.ndarray, and where the two diverge because C# is not Python.


Anatomy

An NDArray is three things glued together:

NDArray               user-facing handle (the type you work with)
 Storage           UnmanagedStorage: raw pointer to native memory
 Shape             dimensions, strides, offset, flags
 TensorEngine      dispatches operations (DefaultEngine by default)

You rarely touch Storage or TensorEngine directly NDArray exposes everything.


Creating an NDArray

The usual ways, with their numpy counterparts:

np.array(new[] {1, 2, 3});                 // np.array([1, 2, 3])
np.array(new int[,] {{1, 2}, {3, 4}});     // np.array([[1, 2], [3, 4]])

np.zeros((3, 4));                          // np.zeros((3, 4))
np.ones(5);                                // np.ones(5)
np.full((2, 2), 7);                        // np.full((2, 2), 7)
np.full(new Shape(2, 2), 7);               // same thing, explicit Shape form
np.empty((3, 3));                          // np.empty((3, 3))
np.eye(4);                                 // np.eye(4)
np.identity(4);                            // np.identity(4)

np.arange(10);                             // np.arange(10)
np.arange(0, 1, 0.1);                      // np.arange(0, 1, 0.1)
np.linspace(0, 1, 11);                     // np.linspace(0, 1, 11)

np.random.rand(3, 4);                      // np.random.rand(3, 4)
np.random.randn(100);                      // np.random.randn(100)

Where (3, 4) comes from. NumSharp's Shape struct has implicit conversions from int, long, int[], long[], and value tuples of 26 dimensions. So these four calls all produce the same (3, 4) array:

np.zeros((3, 4));              // tuple  Shape
np.zeros(new[] {3, 4});        // int[]  Shape
np.zeros(new Shape(3, 4));     // explicit Shape
np.zeros(new Shape(new[] {3L, 4L}));

A bare np.zeros(5) creates a 1-D length-5 array it hits the int shape overload, not a tuple.

Scalars (0-d arrays) flow in implicitly:

NDArray a = 42;                          // 0-d int32
NDArray b = 3.14;                        // 0-d double
NDArray c = Half.One;                    // 0-d float16
NDArray d = NDArray.Scalar(100.123m);    // 0-d decimal
NDArray e = NDArray.Scalar<long>(1);     // 0-d with explicit dtype

Implicit scalar NDArray exists for all 15 dtypes (bool, sbyte, byte, short, ushort, int, uint, long, ulong, char, Half, float, double, decimal, Complex). Use NDArray.Scalar<T>(value) to force a specific dtype the C# literal wouldn't pick e.g. NDArray.Scalar<short>(1) instead of NDArray x = 1; (which would be int32).

See also: Dtypes for how to pick element types, Broadcasting for shape rules.


Wrapping Existing Buffers np.frombuffer

When you already have memory a byte[] read from a file, a network packet, a pointer from a native library, or even a typed T[] you want to reinterpret np.frombuffer wraps it as an NDArray without copying whenever possible. Same contract as NumPy's numpy.frombuffer.

// From a byte[]  creates a view (pins the array)
byte[] buffer = File.ReadAllBytes("sensor_data.bin");
var readings = np.frombuffer(buffer, typeof(float));

// Skip a header
var data = np.frombuffer(buffer, typeof(float), offset: 16);

// Read only part of the buffer
var subset = np.frombuffer(buffer, typeof(float), count: 1000, offset: 16);

// Reinterpret a typed array as a different dtype (view)
int[] ints = { 1, 2, 3, 4 };
var bytes = np.frombuffer<int>(ints, typeof(byte));   // 16 bytes: [1,0,0,0, 2,0,0,0, ...]

// From .NET buffer types
var fromSegment = np.frombuffer(new ArraySegment<byte>(buffer, 0, 128), typeof(int));
var fromMemory  = np.frombuffer((Memory<byte>)buffer, typeof(float));
// ReadOnlySpan<byte> always copies (spans can't be pinned)
ReadOnlySpan<byte> span = stackalloc byte[16];
var fromSpan = np.frombuffer(span, typeof(int));

// From native memory  NumSharp takes ownership and frees on GC
IntPtr owned = Marshal.AllocHGlobal(1024);
var arr1 = np.frombuffer(owned, 1024, typeof(float),
    dispose: () => Marshal.FreeHGlobal(owned));

// Or just borrow  caller must keep it alive and free it later
IntPtr borrowed = NativeLib.GetData(out int size);
var arr2 = np.frombuffer(borrowed, size, typeof(float));
// ... use arr2 ...
NativeLib.FreeData(borrowed);                       // after arr2 is done

// Endianness via dtype strings (big-endian triggers a copy)
byte[] networkData = ReceivePacket();
var be = np.frombuffer(networkData, ">i4");         // big-endian int32 (copy)
var le = np.frombuffer(networkData, "<i4");         // little-endian int32 (view on x86/x64)

View or copy?

Source Behavior
byte[], ArraySegment<byte>, array-backed Memory<byte> view (array is pinned)
T[] via frombuffer<T>(T[], ) view (reinterpret bytes)
IntPtr view (optionally with dispose callback for ownership transfer)
ReadOnlySpan<byte> copy (spans can't be pinned)
Memory<byte> not backed by an array copy
Big-endian dtype string on a little-endian CPU copy (must swap bytes)

Key rules (same as NumPy)

See the Buffering & Memory page for the full story: memory architecture, ownership patterns (ArrayPool, COM, P/Invoke), endianness, and troubleshooting.


Core Properties

Property Type NumPy equivalent Description
shape long[] ndarray.shape Dimensions
ndim int ndarray.ndim Number of dimensions
size long ndarray.size Total element count
dtype Type ndarray.dtype C# element type
typecode NPTypeCode Compact enum form of dtype
strides long[] ndarray.strides Byte stride per dimension
T NDArray ndarray.T Transpose (view)
flat NDArray ndarray.flat 1-D iterator view
Shape Shape Full shape object (dimensions + strides + flags)
@base NDArray? ndarray.base Owner array if this is a view, else null
var a = np.arange(12).reshape(3, 4);   // arange defaults to int64 (NumPy 2.x)
a.shape;       // [3, 4]
a.ndim;        // 2
a.size;        // 12
a.dtype;       // typeof(long)
a.typecode;    // NPTypeCode.Int64
a.T.shape;     // [4, 3]
a.@base;       // the 1-D arange buffer  reshape returns a view (like NumPy)
var b = a["1:, :2"];
b.@base;       // wraps a's Storage (b is a view)

Indexing & Slicing

Python's slice notation is accepted as a string:

var a = np.arange(20).reshape(4, 5);

a[0];              // first row  reduces dim, returns (5,)
a[-1];             // last row
a[1, 2];           // single element at row 1, col 2
a["1:3"];          // rows 1-2  keeps dim, returns (2, 5)
a["1:3, :2"];      // rows 1-2, first two cols  (2, 2)
a["::2"];          // every other row
a["::-1"];         // reversed first axis
a["..., -1"];      // ellipsis + last column

Boolean and fancy indexing work like NumPy:

var arr = np.array(new[] {10, 20, 30, 40, 50});

var mask = arr > 20;           // NDArray<bool>
arr[mask];                     // [30, 40, 50]

var idx = np.array(new[] {0, 2, 4});
arr[idx];                      // [10, 30, 50]  fancy indexing

Assignment follows the same rules:

a[1, 2] = 99;               // scalar write
a[0] = np.zeros(5);         // row write (assign a full row)
a[a > 10] = -1;             // masked write

View / copy summary for indexing:


Views vs Copies Most Important Rule

Slicing returns a view, not a copy. The view shares memory with the parent. This matches NumPy and is the source of most "why did my array change?" questions.

var a = np.arange(10);
var v = a["2:5"];            // view  shares memory with a
v[0] = 999;                  // mutates a[2] as well!
a[2];                        // 999

var c = a["2:5"].copy();     // explicit copy  independent memory
c[0] = 0;
a[2];                        // still 999

Detect views with arr.@base is not null (use the C# is pattern, not != null NDArray's ==/!= operators are element-wise and return an array, not a bool). Force a copy with .copy() or np.copy(arr).

Broadcasted arrays are a special case: they're views with stride=0 dimensions, and they're read-only (Shape.IsWriteable == false) to prevent cross-row corruption. See Broadcasting.


Operators

Every NumPy operator that C# can express is defined on NDArray with matching semantics.

Arithmetic

NumPy NumSharp Broadcasts?
a + b a + b yes
a - b a - b yes
a * b a * b yes
a / b a / b yes returns float dtype for int inputs
a % b a % b yes result sign follows divisor (Python/NumPy convention)
-a -a
+a +a returns a copy

Each takes NDArray NDArray, NDArray object, and object NDArray so 10 - arr works just like arr - 10.

Bitwise & shift

NumPy NumSharp Notes
a & b a & b bool arrays: logical AND
a \| b a \| b bool arrays: logical OR
a ^ b a ^ b
~a ~a
a << b a << b integer dtypes only
a >> b a >> b integer dtypes only

Comparison

NumPy NumSharp Returns
a == b a == b NDArray<bool>
a != b a != b NDArray<bool>
a < b a < b NDArray<bool>
a <= b a <= b NDArray<bool>
a > b a > b NDArray<bool>
a >= b a >= b NDArray<bool>

Comparisons with NaN return False (IEEE 754), just like NumPy.

Logical

NumPy NumSharp Notes
np.logical_not(a) !a NDArray<bool> only

Operators NumPy has that C# doesn't

C# has no **, //, @ operators, and no __abs__/__divmod__ protocol. Use the functions:

NumPy NumSharp
a ** b np.power(a, b)
a // b np.floor_divide(a, b)
a @ b np.matmul(a, b) or np.dot(a, b)
abs(a) np.abs(a)
divmod(a, b) (np.floor_divide(a, b), a % b)

C# shift-operator quirk

C# requires the declaring type on the left of << / >>, so object << NDArray is a compile error. Use the named form:

object rhs = 2;
arr << 2;                     // OK  int RHS
arr << rhs;                   // OK  object RHS supported
2 << arr;                     // compile error
np.left_shift(2, arr);        // use the function instead

Compound assignment

+=, -=, *=, /=, %=, &=, |=, ^=, <<=, >>= all work. But: C# synthesizes them as a = a op b they produce a new array and reassign the variable. They are not in-place like NumPy's compound operators. Other references to the original array do not see the change:

var x = np.array(new[] {1, 2, 3});
var alias = x;
x += 10;                 // x    new array [11, 12, 13]
// alias                 // still [1, 2, 3]  different from NumPy!

This is a C# language constraint compound operators on reference types cannot be defined independently of the binary operator not a NumSharp choice.


Dtype Conversion

Three ways to change an array's type:

var a = np.array(new[] {1, 2, 3});

// astype  allocates a new array (default) or rewrites in place (copy: false)
var b = a.astype(np.float64);
var c = a.astype(NPTypeCode.Int64);

// explicit cast on 0-d arrays  matches NumPy's int(arr), float(arr), complex(arr)
NDArray scalar = NDArray.Scalar(42);        // 0-d
int i = (int)scalar;                        // 42
double d = (double)scalar;                  // 42.0
Half h = (Half)scalar;                      // (Half)42
Complex cx = (Complex)scalar;               // 42 + 0i

Rules (match NumPy 2.x):

See Dtypes for the full type table and conversion rules.


Scalars (0-d Arrays)

A 0-d array has no dimensions ndim == 0, shape == [], size == 1. Create one with NDArray.Scalar<T>(value) or implicit scalar conversion:

var s1 = NDArray.Scalar(42);       // explicit
NDArray s2 = 42;                   // implicit (same result)

s1.ndim;                           // 0
s1.size;                           // 1
(int)s1;                           // 42  explicit cast out

Integer indexing always reduces one dimension:

To unwrap a 0-d result to a raw C# scalar, cast: (int)a[i] or a.item<int>(i).


Reading & Writing Elements

Four ways to touch individual elements, picked based on how many indices you have and whether you already know the dtype:

var a = np.arange(12).reshape(3, 4);    // int64 (NumPy 2.x default integer)

// 1. Indexer  returns NDArray (0-d for a single element)
NDArray elem = a[1, 2];
long v = (long)elem;                    // explicit cast to scalar (converts)

// 2. .item<T>()  direct scalar extraction (NumPy parity; converts if T differs)
long v2 = a.item<long>(6);              // flat index 6  row 1, col 2
object box = a.item(6);                 // untyped form returns object (boxed long)

// 3. GetValue<T>  N-D coordinates, typed. T must match the dtype exactly.
long v3 = a.GetValue<long>(1, 2);

// 4. GetAtIndex<T>  flat index, typed, no Shape math (fastest). T must match the dtype.
long v4 = a.GetAtIndex<long>(6);

// Writes mirror the reads:
a[1, 2] = 99;                           // indexer assignment (converts to the array dtype)
a.SetValue(99L, 1, 2);                  // N-D coordinates (value must match the dtype)
a.SetAtIndex(99L, 6);                   // flat index

Rule of thumb: use .item<T>() when porting NumPy code, GetAtIndex<T> in a hot loop, and the indexer (a[i, j]) when you want NumPy-like ergonomics and don't mind the 0-d NDArray detour.

.item() without arguments works on any size-1 array (0-d, 1-element 1-d, 11 2-d) and throws IncorrectSizeException otherwise the NumPy 2.x replacement for the removed np.asscalar().


Iterating (foreach)

NDArray implements IEnumerable, so foreach works and it iterates along axis 0, matching NumPy:

var m = np.arange(6).reshape(2, 3);
foreach (NDArray row in m)
{
    Console.WriteLine(row);   // each `row` is shape (3,), a view of m
}

For a 1-D array, foreach yields individual elements (boxed). For higher-D arrays, each iteration yields a view of the subarray at that axis-0 index.

To iterate all elements flat, use .flat or index into .ravel():

foreach (var x in m.flat) { ... }

Common Patterns

Flatten to 1-D (view if possible)

a.ravel();        // view if contiguous, copy if not
a.flatten();      // always a copy

Reshape

a.reshape(3, 4);               // explicit dims
a.reshape(-1);                 // auto-size one dim  1-D flatten
a.reshape(-1, 4);              // infer first dim, second is 4

All three return a view when the source is contiguous and a copy otherwise.

Transpose / axis shuffle

a.T;                           // full transpose (view)
a.transpose(new[] {1, 0, 2});  // permute axes
np.swapaxes(a, 0, 1);
np.moveaxis(a, 0, -1);

Copy semantics at a glance

Operation Result
a["1:3"] view
a.T view
a.reshape(...) view if possible, else copy
a.ravel() view if contiguous, else copy
a.flatten() always copy
a.copy() always copy
a + b always new array
a[mask] with bool mask copy
a[idx] with int indices copy

Generic NDArray<T>

For type-safe element access, use NDArray<T>:

NDArray<double> a = np.zeros(10).MakeGeneric<double>();
double first = a[0];                  // T, not NDArray
a[0] = 3.14;

Three ways to get a typed wrapper:

Method Allocates? On dtype mismatch When to use
MakeGeneric<T>() never (same storage) throws ArgumentException You already know the dtype matches
AsGeneric<T>() never (same storage) returns null (like C# as) Defensive typing check for null
AsOrMakeGeneric<T>() only if dtype differs (then astype) converts via astype Accept any dtype, convert if needed

NDArray<T> wraps the same storage; use the untyped NDArray when dtype is dynamic.


Saving, Loading, and Interop

NumSharp reads and writes NumPy's .npy / .npz formats and raw binary. The .npy/.npz stack is a port of NumPy 2.4.2's format code (NEP-01), so a file written by np.save is byte-for-byte identical to what NumPy itself writes data moves between Python and C# in both directions, losslessly. To wrap an existing in-memory byte buffer (file bytes, a network packet, a native pointer) see np.frombuffer above.

// .npy  a single array (byte-identical to NumPy's np.save)
np.save("arr.npy", arr);
NDArray a = np.load_npy("arr.npy");                 // typed load

// .npz  many arrays in one archive
np.savez("bundle.npz", x, y);                       // positional  "arr_0", "arr_1"
np.savez("bundle.npz", new Dictionary<string, NDArray> { ["w"] = w, ["b"] = b });
np.savez_compressed("bundle.npz", w, b);            // same, Deflate-compressed

using NpzFile npz = np.load_npz("bundle.npz");      // lazy + cached; dispose it (holds the file handle)
NDArray w1 = npz["w"];                              // "w.npy" also works as a key
NDArray w2 = npz.f.w;                               // dot access, like NumPy's npz.f
foreach (string name in npz.Files) { }             // "w", "b"  the ".npy" is stripped

// np.load dispatches on the file's magic bytes and returns `object`
object any = np.load("bundle.npz");                 // NDArray for .npy, NpzFile for .npz

// Raw binary  element bytes only, no header
arr.tofile("data.bin");
var raw = np.fromfile("data.bin", np.float64);

Format versions 1.0 / 2.0 / 3.0, C- and Fortran-order, and big-endian files all load; the writer emits the byte-exact, 64-byte-aligned, mmap-ready layout NumPy produces. np.load returns object because like NumPy it yields an array for a .npy and an archive for a .npz, decided by the file's contents rather than its name; prefer the typed np.load_npy / np.load_npz when you know the kind. allow_pickle defaults to false (NumPy's security default), so object-array files are rejected with a clear message instead of executed. See NumPy Compliance for the full dtype map and the handful of unsupported types.

Interop with standard .NET arrays:

var arr = np.array(new[,] {{1, 2}, {3, 4}});

// To multi-dim array (preserves shape). Note the method name is "Muli", not "Multi" 
// a longstanding API typo preserved for backwards compatibility.
int[,] md = (int[,])arr.ToMuliDimArray<int>();

// To jagged array
int[][] jag = (int[][])arr.ToJaggedArray<int>();

// From .NET array back (np.array accepts any rank)
NDArray fromMd = np.array(md);

For unsafe interop with native code, use arr.Data<T>() (gets the ArraySlice<T> handle) or the underlying arr.Storage.Address pointer. Contiguous-only; check arr.Shape.IsContiguous first or copy with arr.copy().


Memory Layout

NumSharp is C-contiguous only row-major storage, like NumPy's default. The order parameter on reshape, ravel, flatten, and copy is accepted for API compatibility but ignored (there is no F-order path).

This means:

Views can be non-contiguous (sliced, transposed, broadcasted). Use arr.Shape.IsContiguous to detect; use arr.copy() to materialize contiguous memory when a kernel needs it.


When Two Arrays Are "The Same"

Comparison Returns Meaning
a == b NDArray<bool> element-wise equality (broadcasts)
np.array_equal(a, b) bool same shape AND all elements equal
np.allclose(a, b) bool same shape AND all elements within tolerance (good for floats)
ReferenceEquals(a, b) bool same C# object (rarely what you want)
a.@base is not null bool a is a view (shares memory with some owner); use is, not != null

Caveat: NumSharp does not expose a direct "do these two arrays share memory?" check from user code. a.@base returns a fresh wrapper on every call and the underlying Storage is protected internal, so strict memory-identity testing is only available inside the assembly.


Troubleshooting

"My array changed when I modified a slice!"

That's views. a["1:3"] shares memory with a. Force a copy: a["1:3"].copy().

"NumSharpException: assignment destination is read-only"

You're writing to a broadcasted view (stride=0 dimension), which is read-only. Copy first: b.copy()["..."] = value.

"ScalarConversionException on (int)arr"

The array isn't 0-d. (int) casts only work on scalars. Use arr.GetAtIndex<int>(0) or index first: (int)arr[0].

"10 << arr doesn't compile"

C# requires the declaring type on the left of shift operators. Use np.left_shift(10, arr).

"a += 1 didn't update another reference"

C# compound assignment reassigns the variable; it doesn't mutate. See Compound assignment above. For in-place modification, write directly: a["..."] = a + 1 (or a[":"] = a + 1).


API Reference

Properties

Member Type Description
shape long[] Dimensions
ndim int Rank
size long Total elements
dtype Type Element Type
typecode NPTypeCode Element type enum
strides long[] Byte strides
T NDArray Transpose (view)
flat NDArray 1-D view
Shape Shape Full shape struct
@base NDArray? Owning array if view, else null
Storage UnmanagedStorage Raw memory handle (internal)
TensorEngine TensorEngine Operation dispatcher

Instance Methods

Method Description
astype(type, copy) Cast to different dtype (copy by default)
copy() Deep copy
Clone() Same as copy() (ICloneable)
reshape(...) Reshape (view if possible)
ravel() Flatten to 1-D (view if contiguous)
flatten() Flatten to 1-D (always copy)
transpose(...) Permute axes
view(dtype) Reinterpret bytes as a different dtype (no copy)
item() / item<T>() Extract size-1 array as scalar
item(index) / item<T>(index) Extract element at flat index as scalar
GetAtIndex<T>(i) Read element at flat index (typed, fastest)
SetAtIndex<T>(value, i) Write element at flat index
GetValue<T>(indices) Read at N-D coordinates
SetValue<T>(value, indices) Write at N-D coordinates
MakeGeneric<T>() Wrap as NDArray<T> (same storage); throws if dtype differs
AsGeneric<T>() Wrap as NDArray<T>; returns null if dtype differs
AsOrMakeGeneric<T>() Wrap as NDArray<T>; astype if dtype differs
Data<T>() Get the underlying ArraySlice<T> handle
ToMuliDimArray<T>() Copy to a rank-N .NET array
ToJaggedArray<T>() Copy to a jagged .NET array
tofile(path) Write raw bytes to file

Operators

Operator Overloads
+, -, *, /, % (NDArray, NDArray), (NDArray, object), (object, NDArray)
unary -, unary + (NDArray)
&, \|, ^ (NDArray, NDArray), (NDArray, object), (object, NDArray)
~, ! (NDArray), (NDArray<bool>)
<<, >> (NDArray, NDArray), (NDArray, object) RHS only
==, !=, <, <=, >, >= (NDArray, NDArray), (NDArray, object), (object, NDArray)

Conversions

Direction Kind Notes
scalar NDArray implicit bool, sbyte, byte, short, ushort, int, uint, long, ulong, char, Half, float, double, decimal, Complex
NDArray scalar explicit same 15 types + string 0-d required; complex non-complex throws TypeError

Persistence & Buffers

Call Format View / copy Notes
np.save(path, arr) .npy Byte-identical to NumPy's np.save; Stream / byte[] overloads
np.savez(path, ) .npz Many arrays; positional (arr_0, arr_1, ) or IDictionary<string, NDArray>
np.savez_compressed(path, ) .npz Same as savez, Deflate-compressed
np.load(path) .npy / .npz Returns object (NDArray or NpzFile); Stream / byte[] overloads
np.load_npy(path) .npy copy Typed NDArray
np.load_npz(path) .npz lazy Typed NpzFile (IDisposable; .Files, ["w"] / .f.w access)
arr.tofile(path) raw Element bytes only, no header
np.fromfile(path, dtype) raw copy Pair with tofile
np.frombuffer(byte[], ) in-memory view (pins array) Endian-prefix dtype strings trigger a copy
np.frombuffer(ArraySegment<byte>, ) in-memory view Uses segment's offset
np.frombuffer(Memory<byte>, ) in-memory view if array-backed, else copy
np.frombuffer(ReadOnlySpan<byte>, ) in-memory copy Spans can't be pinned
np.frombuffer(IntPtr, byteLength, , dispose) native view (optional ownership) Pass dispose to transfer ownership
np.frombuffer<T>(T[], ) in-memory view Reinterpret typed array as different dtype

See also: Dtypes, Broadcasting, Exceptions, NumPy Compliance.

Edit this page
Copyright © 2025 SciSharp STACK. Built with DocFX.

Web Proxy Viewer  |  New URL  |  Original Page