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:
- Is the public API present? The compiled assembly answers this through the generated NumPy API Coverage & Support dashboard.
- Does the behavior match? Unit tests and committed NumPy-oracle cases answer this per operation, dtype, layout, and edge case. See Unit Tests & Oracle.
- 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
complex64has no NumSharp storage dtype. A.npyc8array can be read, but its values widen toSystem.Numerics.Complex; writes usec16.datetime64andtimedelta64, 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.xStringDTypeare not implemented. float128/longdoubleandcomplex256/clongdoublehave 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.asfortranarrayis 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, orKmode 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/RandomStatesurface uses MT19937, including NumPy-compatible state, array seeding, cached Gaussian state, and legacy distribution algorithms. np.random.default_rng(seed)returns aGeneratorbacked byPCG64andSeedSequence, 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: Trueand 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/Charhave round-trip mappings.complex64loads by widening tocomplex128.- 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.npyfile 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:
choleskydetandslogdeteig,eigvals,eigh, andeigvalshinvandsolvelstsqandqrsvdandsvdvals
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_dlpackis missing); - missing floating-point handling APIs: error-state controls such as
geterrandseterr, plusnan_to_num; - missing histogram, window, and several numerical utility functions, including
histogram*,gradient,interp,hypot,nextafter, andsignbit; - partial mappings for NumPy's top-level
np.shapehelper and Python buffer-stylendarray.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:
- The NumPy 2.4.2 code and observed output or exception.
- The equivalent NumSharp code and observed output or exception.
- Input dtype, shape, strides/layout, and whether the input is a view or broadcast array.
- 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
- NumPy API Coverage & Support — generated compiled-API inventory
- Unit Tests & Oracle — generated correctness-evidence inventory
- Dtypes — NumSharp dtype and casting details
- NumPy 2.0 migration guide
- NEP 50: Promotion rules for Python scalars
- NEP 52: Python API cleanup for NumPy 2.0
- NEP 56: Array API compatible functions in the main namespace
- NEP 1: A simple file format for NumPy arrays
- NumPy source v2.4.2