Table of Contents

NumPy Compliance & Compatibility

NumSharp's compatibility target is NumPy 2.x, with the implementation and differential-test oracle currently pinned to NumPy 2.4.2. The target is both API compatibility and behavioral compatibility: dtype promotion, shape, strides, views, broadcasting, exceptions, special values, and output bytes all matter.

NumSharp is not claiming that every NumPy API is implemented or that the existence of a method proves exact behavior. This page separates three questions that used to be conflated:

  1. Is the public API present? The compiled assembly answers this through the generated NumPy API Coverage & Support dashboard.
  2. Does the behavior match? Unit tests and committed NumPy-oracle cases answer this per operation, dtype, layout, and edge case. See Unit Tests & Oracle.
  3. Does the operation need an optional backend? Most operations do not. Matrix factorizations do; the distinction is documented below.

The authoritative NumPy implementation is the pinned clone at refs/numpy/. Documentation and tests are derived from that source and from running NumPy 2.4.2, rather than from memory or from older NumSharp behavior.


Current API Snapshot

The checked-in coverage artifact reflects the compiled NumSharp assembly against NumPy 2.4.2. Its default scope is top-level np.*, public ndarray members, and the np.random, np.linalg, and np.fft callables.

Surface Available Partial Missing Total
np.* 312 1 77 390
ndarray.* 66 1 3 70
np.random.* 51 0 0 51
np.linalg.* 31 0 0 31
np.fft.* 18 0 0 18
Default scope 478 2 80 560

That is 85.4% public-API availability, not an 85.4% behavioral-parity score. A row is "available" when a compiled public mapping exists; exact dtype, signature, layout, exception, and numeric parity still require tests. NumSharp-only extensions are catalogued separately and do not inflate the denominator.

The dashboard is generated by coverage/generate_coverage.py; it is the exhaustive source for the current 80 missing APIs and the two reviewed partial mappings. This page summarizes capability boundaries instead of maintaining a second hand-written function inventory.


NumPy 2.x Type Promotion (NEP 50)

NumPy 1.x sometimes inspected a Python scalar's value when choosing a dtype. The historical NEP 50 example is:

# NumPy 1.x behavior
np.result_type(np.int8, 1)    # int8
np.result_type(np.int8, 255)  # int16: the value does not fit in int8

NumPy 2.x treats Python int, float, and complex values as weakly typed. Precision is chosen from the strongly typed array or NumPy scalar, subject to kind promotion and an in-range check. For example, NumPy 2.4.2 and NumSharp both keep this operation at uint8:

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

// b.dtype == np.uint8
// b is [0, 1, 2] because uint8 arithmetic wraps

The corresponding NumPy array operation does not emit an overflow warning. NumPy's scalar expression np.uint8(1) + 255 does emit a RuntimeWarning; that is a different scalar execution path.

NumSharp's binary-operator and result_type paths implement NEP 50 promotion. C# primitive scalar arguments are the analogue of weak Python literals; an NDArray or explicit dtype remains strongly typed. Reductions also apply NumPy's accumulator rules, such as sum(int32) -> int64, while width-preserving operations such as abs(int32) remain int32.

Promotion is still verified per operation. The repository contains dedicated NEP 50 matrices and NumPy-generated oracle cases; the statement above does not imply that an untested API is correct merely because it calls a shared promotion helper.


NumPy 2.x API Additions and C# Spellings

The additions that the previous version of this page listed as missing are implemented:

NumPy 2.x API NumSharp mapping
np.concat Public np.concat overloads
np.permute_dims Public np.permute_dims API
np.isdtype Public overloads for NPTypeCode, Type, and NDArray
np.unique_values Returns an NDArray
np.unique_counts Returns values and counts
np.unique_inverse Returns values and inverse indices
np.unique_all Returns values, indices, inverse, and counts
ndarray.mT View that swaps the last two axes
ndarray.device Returns "cpu"
ndarray.to_device Accepts only the CPU device

Some reviewed mappings deliberately use an established NumSharp or C# spelling. np.acos, np.asin, and np.atan map to np.arccos, np.arcsin, and np.arctan; np.pow maps to np.power; and NumSharp's np.round_ maps to current NumPy's np.round. The coverage dashboard labels these as aliases instead of pretending that their names are exact.

Removed NumPy 1.x aliases are not counted as current NumPy 2.4.2 requirements. Ported code should prefer the current names such as prod, any, and all.


Dtypes

NumSharp's array engine has 15 core dtypes. Twelve map directly to real NumPy dtypes, one maps to complex128, and two are .NET-specific extensions.

NPTypeCode C# storage type NumPy analogue Important boundary
Boolean bool bool_ Boolean is not in NumPy's numeric type hierarchy
SByte sbyte int8 Signed 8-bit integer
Byte byte uint8 Unsigned 8-bit integer
Int16 short int16
UInt16 ushort uint16
Int32 int int32
UInt32 uint uint32
Int64 long int64
UInt64 ulong uint64
Half System.Half float16 Scalar arithmetic where the BCL has no vector arithmetic
Single float float32 SIMD-capable on supported operations
Double double float64 SIMD-capable on supported operations
Complex System.Numerics.Complex complex128 No native complex64 storage dtype
Char char no general analogue Treated as an unsigned 16-bit value; .npy maps it to <U1
Decimal decimal none .NET extension; cannot be written as a NumPy .npy dtype

This table describes the dtype matrix used by numerical operations. It does not promise that every operation accepts all 15 dtypes; support is tested and documented per operation. NPTypeCode.String remains as a legacy compatibility code used by older string-conversion helpers, but it is outside that 15-dtype operation matrix and does not provide a NumPy string-array dtype.

NumPy dtype families not represented natively

  • complex64 has no NumSharp storage dtype. A .npy c8 array can be read, but its values widen to System.Numerics.Complex; writes use c16.
  • datetime64 and timedelta64, including their unit metadata, are not implemented.
  • Object, structured, subarray, and void dtypes are not implemented.
  • Byte strings, fixed-width Unicode strings wider than <U1, and NumPy 2.x StringDType are not implemented.
  • float128/longdouble and complex256/clongdouble have no corresponding .NET storage type.

See Dtypes for scalar conversion, promotion, special-value, and casting details.


Memory Layout, Strides, and Views

NumSharp is not C-order-only. Shape records dimensions, element strides, a base offset, and cached C-contiguous, F-contiguous, ownership, alignment, writeability, and broadcast flags.

  • Fresh arrays default to C order, and APIs that accept order: 'F' can allocate Fortran-order arrays. np.asfortranarray is implemented.
  • Transposes, negative-stride slices, stepped slices, and other non-contiguous views retain their strides and offset instead of being silently densified.
  • Slicing returns a view that shares the underlying buffer. Call .copy() when independent storage is required.
  • Broadcast views use zero strides and are read-only, matching NumPy's protection against writing one physical element through many logical coordinates.
  • Order-aware creation and conversion paths resolve the relevant C, F, A, or K mode to physical C or F storage. Arbitrary non-contiguous stride patterns remain views; they are not a third allocation order.

Kernel paths distinguish contiguous, strided, broadcast, and offset views. Correctness tests cover C- and F-contiguous arrays, transposes, positive and negative strides, sliced offsets, and broadcast reads. See Broadcasting and Buffering & Memory.


Python Array API Features

NumSharp uses the Array API features that NumPy exposes in its main namespace as compatibility targets, but it does not claim independent certification as a Python Array API implementation. The C# public surface cannot be substituted for a Python namespace object without an interop layer.

The formerly missing Array API additions isdtype, concat, permute_dims, the four unique_* functions, mT, device, and to_device are present. NumSharp is CPU-only: device is "cpu", creation functions that expose a device argument accept only null or "cpu", and to_device cannot transfer to a GPU.

The generated NumPy coverage dashboard is the current inventory. The old hand-maintained claim of "74% Array API coverage" and its 133-function denominator have been removed because they mixed a particular specification revision with NumPy API aliases and became stale.


Random Number Generation

NumSharp implements both NumPy random APIs:

  • The legacy np.random/RandomState surface uses MT19937, including NumPy-compatible state, array seeding, cached Gaussian state, and legacy distribution algorithms.
  • np.random.default_rng(seed) returns a Generator backed by PCG64 and SeedSequence, with the modern ziggurat and bounded-integer algorithms used by NumPy 2.4.2.

The familiar legacy example matches NumPy:

np.random.seed(42);
var a = np.random.rand(5);
// [0.37454012, 0.95071431, 0.73199394, 0.59865848, 0.15601864]

The oracle suite separates portable byte-parity cases from distributions whose transforms depend on the platform math library. MT19937/PCG64 bit streams and purely integer/exactly-rounded paths are hard-gated across hosts. Host-libm cases are byte-pinned on the matching Windows AMD64 host and are inconclusive elsewhere. Therefore, "same seed" is a tested stream guarantee for the corresponding NumPy API, but it is not a blanket promise that every transformed distribution has identical last bits on every operating system.


.npy and .npz Interoperability

The .npy implementation is a port of NumPy 2.4.2's format code and is tested against files written by real NumPy. For supported dtypes and layouts, NumSharp's writer produces the same header and data bytes as NumPy's writer.

  • Format versions 1.0, 2.0, and 3.0 are read and written. Automatic writes choose the oldest format that can represent the header.
  • C- and F-order files are supported. A strictly F-contiguous array is written with fortran_order: True and loads as F-contiguous.
  • Little- and big-endian numeric files load. Big-endian data is converted to native byte order because NumSharp has no byte-swapped dtype metadata.
  • bool, all eight integer widths, float16, float32, float64, complex128, and <U1/Char have round-trip mappings. complex64 loads by widening to complex128.
  • Decimal cannot be saved because NumPy has no decimal dtype. Object/pickle, structured, subarray, datetime/timedelta, byte-string, void, and unsupported extended-precision descriptors are rejected with a capability-specific error.
  • np.load(..., mmap_mode: ...) supports read-only, read-write, and copy-on-write mapping for a .npy file path. Streams and byte arrays cannot be memory-mapped.

np.savez and np.savez_compressed write uncompressed and deflated ZIP archives. np.load_npz opens members lazily and caches loaded arrays; the returned NpzFile owns resources and should be disposed. np.load detects .npy versus .npz from magic bytes and returns either NDArray or NpzFile, while np.load_npy and np.load_npz provide typed entry points.

allow_pickle defaults to false. NumSharp cannot execute Python pickle data even when allow_pickle: true; the flag changes validation/error behavior but does not add an object dtype or an unpickler.


Linear Algebra and Optional OpenBLAS

NumSharp's numerical kernels in NumSharp.Core are implemented in managed C# and do not require a third-party native library. Core does contain a Windows-only P/Invoke to VirtualAlloc/VirtualFree for large zero-initialized buffer allocation; that is a memory-management fast path, not a numerical backend. Matrix products such as dot, matmul, inner, vdot, vecdot, matvec, and vecmat have managed implementations and remain available without an optional package.

The separate NumSharp.Interop.OpenBLAS package installs an IBlasBackend. It accelerates and, for supported floating/complex routes, can reproduce NumPy's BLAS accumulation order. Referencing the package is the normal opt-in; removing or disabling the backend leaves managed matrix products available.

Matrix factorizations are different. Core does not carry managed LU, QR, SVD, or eigensolver kernels. These public APIs validate inputs and then require a backend implementation:

  • cholesky
  • det and slogdet
  • eig, eigvals, eigh, and eigvalsh
  • inv and solve
  • lstsq and qr
  • svd and svdvals

The OpenBLAS package implements those operations through LAPACK. Without a serving backend they throw OpenBlasMissingBackendException; they are not null-returning stubs. APIs built from an SVD or solve, such as pinv, matrix_rank, negative matrix_power, and spectral/nuclear matrix norms, inherit that backend requirement.

Pure compositions such as non-negative matrix_power, multi_dot, ordinary matrix products, and non-SVD norm orders work in Core. See OpenBLAS interoperability for supported dtypes, delivery, parity, and configuration details.


FFT

The complete 18-callable np.fft inventory is implemented in Core: complex, real, Hermitian, 1-D, 2-D, and N-D transforms, frequency helpers, and shift helpers. Compute runs through a managed port of NumPy 2.4.2's vendored pocketfft engine; it does not require OpenBLAS.

The double/complex128 path preserves pocketfft's operation order and is covered by byte-parity oracles across contiguous, transposed, strided, negative-stride, and broadcast-read inputs. NumSharp also has a single-precision pocketfft engine, but it has no complex64 storage dtype: float16/float32 transform results are therefore exposed as complex128, where NumPy exposes complex64. The FFT oracle treats that as a documented dtype divergence while still checking the values. As elsewhere, public API availability does not replace the per-dtype oracle evidence.


Execution Engine and SIMD

SIMD is not future roadmap work. Core emits runtime-specialized IL kernels and selects V128, V256, or V512 paths when the operation, dtype, hardware, and layout permit it. Contiguous numeric arrays take the fast paths; strided and broadcast operands use general iterator paths. Half, Complex, and Decimal use scalar arithmetic where the .NET BCL has no suitable vector operation.

The optional OpenBLAS package is the only native numerical backend. The Windows virtual-memory calls described above only allocate and release buffers. Installing OpenBLAS does not replace the tensor engine; it fills the engine's settable backend seam, and unsupported operand combinations fall back to Core for matrix products.


Known Gaps

The generated dashboard is the exhaustive API list. The important capability-level gaps are:

  • no native complex64, datetime/timedelta, object, structured, or general string dtype;
  • no GPU device or DLPack import (np.from_dlpack is missing);
  • missing floating-point handling APIs: error-state controls such as geterr and seterr, plus nan_to_num;
  • missing histogram, window, and several numerical utility functions, including histogram*, gradient, interp, hypot, nextafter, and signbit;
  • partial mappings for NumPy's top-level np.shape helper and Python buffer-style ndarray.data;
  • matrix factorizations require the optional OpenBLAS/LAPACK backend.

When exact parity matters, search the coverage dashboard for API presence and the test dashboard for oracle depth. Do not infer support from this summary alone.


Reporting a Compatibility Difference

A useful report includes:

  1. The NumPy 2.4.2 code and observed output or exception.
  2. The equivalent NumSharp code and observed output or exception.
  3. Input dtype, shape, strides/layout, and whether the input is a view or broadcast array.
  4. Raw floating-point or complex bytes when the difference is below display precision.

The preferred fix is a test generated from real NumPy output, followed by an implementation that matches NumPy's source structure and handles contiguous, strided, broadcast, sliced-offset, empty, scalar, and dtype edge cases.


References