Repository navigation
We are at 95.2% NumPy API coverage, and everything except datetime, complex64 and other exotic dtypes matches NumPy byte for byte.
This release brings NumPy's text dtypes with numpy.strings and numpy.char, numpy.ma, numpy.polynomial, np.emath and 48 new np.* functions.
Integration rather than competition goes on, this time inside .NET: an NDArray now runs through ONNX Runtime models, serves as an ML.NET IDataView and shares its memory with System.Numerics.Tensors.
🧭 Overview
- NumPy 2.x API coverage around 95.2%, everything except datetime, complex64 and other exotic dtypes is fully supported, with byte-to-byte equality and compatibility to NumPy.
- Text - NumPy's fixed-width
bytes_(S) andstr_(U) dtypes and NEP 55's variable-widthStringDType, with every computation NumPy runs on text, all 46numpy.stringsfunctions andnumpy.charwithchararray. np.polynomial.*- the wholenumpy.polynomialpackage, all 193 functions and the six series classes for all six bases, bit-exact.np.*- +48 functions, the ten most used on GitHub:np.hypot,np.histogram,np.logspace,np.gradient,np.vectorize,np.hanning,np.geomspace,np.apply_along_axis,np.trapezoidandnp.histogram2d.np.random.*- every NumPy bit generator, the wholeGeneratordistribution surface, and a legacyRandomStatethat is NumPy's own sampler code line for line, all byte-identical streams.np.ma.*- the masked-array module;np.emath.*- the automatic-domain math module;np.matrix-numpy.matrixas the newNDMatrixtype.
- Text - NumPy's fixed-width
DTypeis NumPy's two-level dtype model -NDArray.dtypereturns anumpy.dtype-shaped descriptor, withnp.dtypes, byte order anddatetime64/timedelta64descriptors, and NumPy's scalar classes (np.integer,np.floating,np.number, ...) withnp.typing.- Reductions on NumPy's own schedules -
max,min,ptp,nanmax,nanmin,argmax, the flatsum/meanand the fusednp.evaluatereductions match NumPy bit for bit, down to the sign of a ±0 tie and a NaN's payload;mean,var,stdand their NaN-aware forms are NumPy's own statements; and most of them run faster. - NumPy's structure for the engine - every computing
np.*function is aTensorEnginemember named after its NumPy source file,numpy.linalgandnumpy.fftare replaceable sub-engines, and the default engine is settable. - The 2026-11-02 parity QA - 53 of its 55 findings fixed on the owner's decisions (one kept as a documented divergence, one deferred), and NumPy's own dot functions behind
np.dot/np.vdot/np.vecdot. - 86 NumPy-parity, memory-safety and tooling fixes, 40 measured speedups and 44 breaking changes, across the release's 1,670 commits.
- 3 new NuGet packages - NumSharp.Interop.System.Numerics.Tensors, NumSharp.Interop.OnnxRuntime and NumSharp.Interop.MLNet.
- 1,119,305 NumPy 2.4.2 oracle test cases (0.70.0: 119,517) and 20,506 test methods (0.70.0: 14,416).
📦 New NuGet Packages
- NumSharp.Interop.System.Numerics.Tensors - zero-copy interop between
NDArrayand the BCL'sTensor<T>/TensorSpan<T>(System.Numerics.Tensors), all 15 dtypes. - NumSharp.Interop.OnnxRuntime - run ONNX Runtime models on
NDArrays, fed from the arrays' own memory, with the outputs read back asNDArrays. - NumSharp.Interop.MLNet - an
NDArrayas an ML.NETIDataView, and a transformer's output column back as anNDArray.
Detailed Breakdown
Show All
📦 New NuGet Packages
Three optional companion packages ship for the first time, co-versioned with NumSharp (each depends on NumSharp). None computes NumSharp operations: each converts arrays between NumSharp and another library, with no engine backend and no native asset of its own, so Core stays 100% managed without them.
- NumSharp.Interop.System.Numerics.Tensors - zero-copy interop between
NDArrayand System.Numerics.Tensors (Tensor<T>,TensorSpan<T>,ReadOnlyTensorSpan<T>); all 15 NumSharp dtypes cross as their own CLR type,Half,Complex,decimalandcharincluded.- Export -
AsTensorSpan<T>()views anNDArrayof any non-negative-stride layout (contiguous, sliced, transposed, strided, broadcast) as aTensorSpan<T>over the same unmanaged buffer, through a disposable handle that keeps the buffer pinned;ToTensor<T>()copies into a denseTensor<T>. - Import -
AsNDArray()views aTensor<T>as anNDArraywithout copying (writes go through to the tensor, and the pinned array counts toward NumSharp's memory accounting until the last view is disposed);ToNDArray()copies aTensor<T>,TensorSpan<T>orReadOnlyTensorSpan<T>. - Text -
S/U/StringDTypearrays cross as aTensor<string>copy in both directions (ToStringTensor()/ToNDArray(Tensor<string>)); the view verbs refuse text with one message, because aTensor<string>holds managed strings. - The exported spans feed the BCL's SIMD
TensorPrimitivesdirectly, with no fill loop. - Limits - a negative-stride view is refused, because the BCL forbids negative strides (copy it first); the copies stop at the managed-array size limit; live export and import counters help leak checks.
- Depends - System.Numerics.Tensors 10.x only, a managed BCL package. Its tensor types are experimental, so the bridge marks its own API experimental too and you opt in once. No engine backend and no native asset: Core stays 100% managed.
- Export -
- NumSharp.Interop.OnnxRuntime - feed an
NDArrayto an ONNX RuntimeInferenceSessionfrom the array's own memory and read the outputs back asNDArrays; no Python.- Modes - every entry that can share memory takes
Auto(a view when ONNX Runtime can read the buffer in place, else a copy the handle owns),View(refuses to copy) orCopy. - Export -
AsOrtValue/ToOrtValue, andAsDenseTensor/ToDenseTensorfor ONNX Runtime's legacy tensor tier; import -ToNDArraycopies (a legacyTensor<T>too),AsNDArrayviews and can own the value. - Running a model -
session.Run(...)binds the inputs by the model's names, checks each dtype with NumPy's casting rules and each declared shape, and refuses a missing required input (with onnxruntime's own message) or an unknown output name before the run;RunIntofills pre-allocated outputs;RunAsync/RunIntoAsyncrun ONNX Runtime's asynchronous inference (1.30.0 or later) and size a dynamic dimension (batch_size,N) from the fed inputs; the outputs ofRunandRunAsyncleave anNDScope(scope.ReturnsTask(session.RunAsync(...))). - Threads and disposal - every
OrtValuethe bridge touches is released once, after its last use: aDisposefrom any thread, during a verb or while views of the value live, neither crashes nor hands out another value's data, and a disposed session or value is refused withObjectDisposedException;NDInferenceSession, anInferenceSessionwith ONNX Runtime's constructors, releases a session once, after the bridge's runs on it. - Memory -
ToOrtValue's copy and every view the bridge hands out count toward NumSharp's memory accounting, so forgotten ones get collected: 5,000 forgottenToOrtValuecopies of a 602 KB array peak at +68 MB instead of +2,898 MB. - Text and non-tensor values - fixed-width
U/Sarrays cross as string tensors (a copy;ReadStringTensorreads one asstring[]), and aStringDTypearray is refused with its sized-Uremedy;ToNDArrays,ToMap,ToMapsandGetMapKeysread sequence and map outputs. - Post-processing -
Softmax,LogSoftmax,Sigmoid,Argmax,TopK. - Dtypes - 12 cross without a copy, float16 by bit reinterpretation;
charexports as uint16 anddecimalas float64 through a copy the handle owns; complex and bfloat16 are refused, each refusal naming the route that works. - Depends - Microsoft.ML.OnnxRuntime.Managed 1.16.0 or later, the C# API only: ONNX Runtime's native flavours exclude each other, so you reference the one you run. Verified on 1.16.0 and 1.31.1.
- Modes - every entry that can share memory takes
- NumSharp.Interop.MLNet - expose an
NDArrayto an ML.NET pipeline as anIDataViewand read a transformer's output column back as anNDArray, with no POCO class and no row-by-row fill.- Export -
AsDataViewis a lazyIDataViewover the buffer's strides (one vector column, or one scalar column per feature, a repeated name refused);ToDataViewtakes a snapshot;ToVBuffercopies into aVBuffer<T>. - Tables -
AsDataViewof a dictionary of named arrays is one view with a column per array, each in its own dtype ({"Features": X, "Label": y}, as pandas'DataFrame), so a model trains and scores from arrays with nohstackand no conversion step. - Shuffling - a view reports
CanShuffleand walks a full permutation of its rows when a trainer asks for a shuffled cursor, so SDCA trained on class-ordered rows passes 0.65 accuracy (ML.NET's pool shuffle left it at 0.50). - Import -
ToNDArrayreads a scalar column as(R,)and a fixed-size vector column as(R, C);AsNDArrayviews a denseVBuffer<T>'s array without copying, and the pinned array counts toward NumSharp's memory accounting until the last view is disposed. - Feeding a model -
transformer.Transform(nd.AsDataView("Features")).ToNDArray("Score"): theIDataViewis the whole bridge. - Post-processing - the ONNX Runtime bridge's
Postprocess(Softmax,LogSoftmax,Sigmoid,Argmax,TopK), which this package references, so an app that uses both bridges has one. - Dtypes - 11 column types directly,
charone way as uint16, float16 as float32 anddecimalas float64 through a copy the view owns; complex and text arrays are refused, and a text, time or ragged column's refusal names the ML.NET route that reads it (GetColumn<string>, int64 ticks,GetColumn<T[]>). - Depends - Microsoft.ML.DataView 2.0.0 or later, and NumSharp.Interop.OnnxRuntime for
Postprocess(bringing ONNX Runtime's C# API, whichPostprocessnever loads); theMicrosoft.MLyou reference brings the trainers and transforms. Verified on ML.NET 2.0.1 and 5.0.1.
- Export -
📊 Dashboards & Docs
- Supported Features Dashboard - now reports, for every API, whether its behavior is oracle-verified (committed NumPy 2.4.2 contracts resolve to it, directly or through a reviewed alias) next to whether it is available, and whether its C# call is NumPy's (one overload with exactly NumPy's parameters under NumPy's name). Headline 95.2% available (533/560, 4 more partial; 0.70.0: 87.1%, 488/560), 95.9% oracle-verified (537/560), 244 of 560 NumPy-identical calls.
- An oracle filter in the API explorer and an evidence pane per API: contract and error-contract counts, dtypes, layouts, corpus files and host pins; a filter per kind of signature difference.
- The catalog also covers what lies beyond the headline - every module NumPy lists as public (
numpy.ma,numpy.emath,numpy.polynomial,numpy.char,numpy.strings,numpy.dtypes,numpy.exceptions,numpy.typing, ...), the members of its objects (nditer,dtype,finfo, the random classes), every Python protocol its classes define (ndarray.__add__,ndarray.__getitem__,nditer.__enter__, ...), the members of NumPy's scalar types, and every method of every ufunc (np.add.reduce,np.maximum.accumulate,np.add.at, ...): 2,017 of 4,844 APIs available in that expanded scope and 1,615 oracle-verified, the dashboard's default view.
- Tests & Oracle Dashboard - 1,119,305 committed NumPy 2.4.2 oracle test cases over 1,448 operations (0.70.0: 119,517 over 372), and 20,506 test declarations (0.70.0: 14,416).
- Benchmark Dashboard - benchmark evidence for 452 of 518 benchmarkable APIs (0.70.0: 455 of 471), and
benchmark/strings: 442 text cells at geomean x6.61 NumPy's speed.- The 47 APIs this release makes benchmarkable have no benchmark yet, and
np.remainder,np.random.ranfandnp.random.sample, which 0.70.0 credited throughnp.modandnp.random.random_sample, are methods of their own now, with no benchmark of their own.
- The 47 APIs this release makes benchmarkable have no benchmark yet, and
- Documentation site - new guides, whose code samples run as tests:
- The absolute basics, Quickstart, Iterating & Enumerating, Strings, bytes & chararrays, and the interop pages for System.Numerics.Tensors, ONNX Runtime and ML.NET.
- Fundamentals and usage - array creation, indexing, getting & setting values, I/O, copies and views, strings and bytes, structured arrays, ufunc basics.
- Advanced usage - extending NumSharp, native code & backends, under the hood.
- Every page reviewed against the code and NumPy 2.4.2: DocFX warnings 463 -> 286, none left in the docs, 62 new doc tests.
✨ New APIs & Modules
- NumPy's fixed-width string dtypes
bytes_(S) andstr_(U) - real dtypes whose descriptor carries the class and the width (<U5,|S3), byte-identical to NumPy 2.4.2.np.array(string[]),"U5"/"S3"in every dtype parameter,NDArray.Scalar(string), a zero-copynp.frombuffer(bytes, "S3")- creation.item(),tolist(),NDArray.AsStringArray, stores cut to the width; promotion,can_castand thenp.dtypegrammar; views, joins, gathers and indexing in every layout;astypebetween widths and classes; printing and.npy- the dtype end to end.- A number written as text is its NumPy scalar's
str(), a text read as a number is CPython'sint()/float()/complex(), through every route that converts.
- NEP 55
StringDType-np.dtypes.StringDType(na_object, coerce)andnp.dtype("T"): NumPy's variable-width UTF-8 text in 16-byte cells over a per-buffer arena, with its NA object, coercion rules, promotion and casting table, NumPy's cells byte for byte, andnp.ndarray(shape, StringDType, buffer:)views of another array's cells; a maskednp.copytointo it converts only the elements the mask selects. - Computing on text - every computation NumPy runs on
S/U/StringDTypetext computes with NumPy's results, NA rules and refusals in NumPy's order -- the six comparisons and their operators, the truth readers (
astype(bool),np.any/np.all,np.nonzero,np.where, the logical ufuncs), concatenation (+) and repetition. np.sort/np.argsort/np.partition(NumPy's order of equal strings for everykind),np.argmax/np.argmin,np.searchsorted/np.digitize,np.uniqueand the Array-API unique family,np.isin, the set routines,np.lexsort, the histograms and the element-picking quantiles.np.sum/np.cumsum/np.max/np.min/np.traceofStringDType, reductions into a textout=ordtype=,np.padover text, and numpy.ma over text.
- the six comparisons and their operators, the truth readers (
np.strings- all 46 ofnumpy.strings' functions overS/U/StringDTypewith NumPy's arguments, results and errors: CPython 3.12's Unicode 15.0.0 tables for the predicates and case maps on every .NET, its codecs and error handlers forencode/decode.str_len,find,rfind,index,rindex,count,startswith,endswith- lengths and searches.strip,lstrip,rstrip,center,ljust,rjust,zfill,expandtabs,replace,partition,rpartition,slice- editing.isalpha,isdigit,isspace,isalnum,islower,isupper,istitle,isdecimal,isnumeric- the predicates, without=/where=/dtype=.upper,lower,swapcase,capitalize,title,translate- case maps and translation.add,multiply,mod,encode,decode, andequal...greater_equalunder theirnumpy.stringsnames.
np.@char- NumPy'snumpy.charmodule (charis a C# keyword) withchararrayasNDCharArray.- The comparisons that strip trailing whitespace,
compare_chararrays, and everynp.stringsfunction re-exported. chararray- construction, element access that strips as Python'srstrip()does, the+/*/%operators, its string members, and thenp.*rules that keep, wrap or drop the class as NumPy's do.split,rsplit,splitlines,join- the object path, whose results areNDListArray, NumPy's object array of lists with 65 of its 70 ndarray members.
- The comparisons that strip trailing whitespace,
- Text I/O -
np.loadtxt/np.savetxt/ndarray.tofileoverStringDTypewith Python's codec names (encoding:),np.save/np.loadofStringDTypeas NumPy's own pickle (allow_pickle), andnp.fromstring(byte[]). np.evaluateover text -S/U/StringDTypearrays and a C#stringareNDExprleaves: the comparisons, truth readers,IsNaN, casts to and from text,NDExpr.StrLen/Find/StartsWith/ the predicates, concatenation,Wherewith text branches and the root reductions compute as NumPy's statements do.np.polynomial- thenumpy.polynomialpackage for all six bases (polynomial,chebyshev,legendre,laguerre,hermite,hermite_e) pluspolyutils: all 193 of its functions and the six series classes, bit-exact with NumPy 2.4.2 against generated oracles (179,118 cases, 0 excused);{p}below ispoly/cheb/leg/lag/herm/herme.{p}val,{p}val2d,{p}val3d,{p}grid2d,{p}grid3d,{p}valnd,polyvalfromroots- evaluation.{p}add,{p}sub,{p}mulx,{p}mul,{p}div,{p}pow,{p}fromroots- series arithmetic.{p}der,{p}int- calculus, with everym/k/lbnd/scl/axisform.{p}vander,{p}vander2d,{p}vander3d- Vandermonde matrices.{p}fit(everydeg/rcond/full/wform) andchebinterpolate- fitting and interpolation.{p}companion,{p}roots- companion matrices and roots (the roots compute through the OpenBLAS backend'seigvals).chebweight...hermeweight,chebpts1,chebpts2,chebgauss,leggauss,laggauss,hermgauss,hermegauss- weights, points and Gauss quadrature.{p}trim,{p}line,{p}domain,{p}zero,{p}one,{p}x, the fiveX2poly/poly2Xpairs - helpers, module constants and basis conversions.as_series,trimseq,trimcoef,getdomain,mapparms,mapdomain,format_float,set_default_printstyle-polyutilsand the printing helpers.Polynomial,Chebyshev,Legendre,Laguerre,Hermite,HermiteE- the series classes (NDPolynomial...NDHermiteEoverNDPolyBase) under NumPy's names: construction, Python's operator protocol, evaluation and composition,fit/fromroots/roots/convert/integ/derivand the rest, the printers and pickling.- Speed - x1.78-x37.8 NumPy's speed by benchmark geomean per family (evaluation x7.5, additive x8.5, calculus x7.6, series algebra x37.8, Vandermonde x10.2, roots x4.65, evaluation from roots x13.9, fitting x1.96, interpolation x7.20, points x3.37, Gauss quadrature x4.54, weights x1.78, printing x11.45, the series classes x8.22).
np.randombit-generator family - NumPy's engines, seeding and stream splitting, byte-identical to NumPy 2.4.2 and replayed overload by overload (493 members, 5 engines x 10 seeds).PCG64DXSM,Philox,SFC64- new bit generators;PCG64.advance,jumped,random_raw,state- engine controls;MT19937is now a NumPyBitGeneratortoo.SeedSequence.spawn,spawn_key,pool_size,state,BitGenerator.spawn,Generator.spawn,ISeedSequence- seeding and independent streams.default_rng- every NumPy seed form (integers,BigInteger, arrays,SeedSequence, a bit generator, aGenerator, aRandomState,nullfor fresh entropy).np.random.PCG64(42),np.random.Generator(...),np.random.SeedSequence(...)and the other classes - NumPy's module spellings;get_bit_generator,set_bit_generator.
Generatordistributions - NumPy's wholenumpy.random.Generatorsurface on NumPy's modern algorithms, 29 samplers byte-identical todefault_rng.beta,chisquare,f,noncentral_chisquare,noncentral_f,standard_t,standard_cauchy,vonmises- the gamma family and friends.pareto,weibull,power,laplace,gumbel,logistic,lognormal,rayleigh,wald,triangular- one transform per draw.binomial,negative_binomial,poisson,zipf,geometric,hypergeometric,logseries- discrete.multinomial,dirichlet,multivariate_normal(svd/eigh/cholesky),multivariate_hypergeometric(marginals/count) - multivariate.pareto/powerstay within 2 ULP /ceil(2/a)+3ULP of NumPy, whose C runtimeexpm1has no portable twin;multivariate_normalis byte-identical with the OpenBLAS backend and may pick the other eigenvector sign without it.
- Array-valued distribution parameters - the 29 parameterized samplers of both
RandomStateandGeneratortakeNDArrayparameters, broadcast againstsizeas NumPy does, byte-identical streams.integers,randint- array bounds andBigIntegerbounds, sog.integers(0, BigInteger.Pow(2, 64), dtype: np.uint64)ports verbatim.
- Legacy
RandomStategains NumPy's form over any bit generator (np.random.RandomState(new PCG64(42))), the dictget_state(legacy: false)/set_state,tomaxint,ranfandsample. np.ma- NumPy's masked-array module on a newNDMaskedArraytype, bit-exact vs NumPy 2.4.2 and gated by 109,961 differential cases in 13 tiers plus a 27,240-case surface tier.- The ufunc wrappers, masked reductions (
mean/var/std/anomas numpy.ma's own statements), operators, creation, extrema and manipulation - the core. average,median,dot, the set operations,sort,unique,cov,corrcoef,convolve,correlate,polyfit,apply_along_axis,apply_over_axes- the extras.- The masked indexer, hard masks and byte-exact masked
str/repr; only the structured/record-dtype functions are missing.
- The ufunc wrappers, masked reductions (
np.emath- automatic-domainsqrt,log,log2,log10,logn,power,arccos,arcsin,arctanh(an out-of-domain input gives the complex result instead of NaN).np.matrix- NumPy'snumpy.matrixas the typeNDMatrix(80 of its 83 members), held to NumPy 2.4.2's values, class and shape by two generated oracles (26,869 and 4,645 cases).np.matrix,np.asmatrix,np.bmat- construction from arrays, sequences and"1 2; 3 4"literals.T,H,I,A,A1- the transposes, the inverse and the plain-array views.*,**- the matrix product and the matrix power; an index keeps rows and columns two-dimensional.sum,prod,mean,var,std,any,all,max,min,argmax,argmin,ptp- reductions that keep two axes.np.*functions return NumPy's class and shape for a matrix operand:np.sum(m, 0)is a 1 x N matrix,np.linalg.inv(m)a matrix,np.ravel(m)a plain array, andnp.stackraises as in NumPy;np.max/np.mintake the method's keyword andout=rules (np.max(m, initial=1)raises itsTypeError),np.nanmax/np.nanminNumPy's subclass route.
- 48 new
np.*functions, NumPy 2.4.2 parity, each gated by the differential-fuzz oracle -hypot(correctly rounded: float32/float16 bit-exact, float64 bit-exact on ~91% of inputs and 1 ULP more accurate on the rest),divmod,fmod,remainder,float_power,heaviside,spacing,frexp,ldexp,gcd,lcm,fabs,signbit,fix- elementwise math ufuncs.sinc,i0,real_if_close,unwrap- special functions and signal helpers.bartlett,blackman,hamming,hanning,kaiser- window functions, bit-identical.trapezoid,gradient- numerical integration and differentiation, bit-exact on every layout.histogram,histogram_bin_edges,histogramdd,histogram2d- histograms.packbits,unpackbits,bitwise_count- bit packing and population count.piecewise,putmask,put_along_axis- indexed and conditional assignment.vectorize,frompyfunc,apply_along_axis,apply_over_axes,broadcast_shapes- functional and shape helpers.array_equiv,may_share_memory,shares_memory(withTooHardError) - array predicates.logspace,geomspace- log-scale ranges, bit-exact.binary_repr,base_repr,typename- string helpers.
- Existing APIs gain NumPy's parameters -
maximum,minimum,fmax,fmin-out=/where=.left_shift,right_shift,logical_and,logical_or,logical_xor,logical_not,modf-out=/where=/dtype=.array_equal-equal_nan=.indices,indices_sparse-long[]/Shapedimensions (np.indices(arr.shape)), andmgridpast 2^31 elements.
- The
max/minfamily's keywords -np.max/np.min/np.amax/np.amin/np.nanmax/np.nanminandndarray.max/ndarray.mintake NumPy'sout=,initial=andwhere=in NumPy's order, through a port of NumPy's reduction iterator: the loop ispromote_types(out, a),initialis packed into it as NumPy packs a Python scalar, and a mask withoutinitialraises NumPy's no-identity error; bit-exact over a 35,847-caseminmax_kwargsoracle tier with zero excuses. Plainnp.max/np.minof complex128 along a reversed axis and of a broadcast float input keep NumPy's first NaN and ±0, and an axis result over a permuted input keeps NumPy's layout (237 cells, geomean x3.98;np.max(bool, axis=1)at 10M x0.19 -> x2). - The reductions' keywords -
np.sum/np.prodtakeinitial=andwhere=,np.meanwhere=, andnp.var/np.stdwhere=,mean=andcorrection=, bit for bit with NumPy. weights=fornp.quantile/np.percentile/np.nanquantile/np.nanpercentile(inverted_cdf), over numbers and text.np.evaluate/NDExpr- the fused-expression DSL covers most of the ufunc surface and every reduction, NumPy-typed and bit-exact (29,893-case oracle tier).Maximum,Minimum,FMax,FMin,Fmod,CopySign,NextAfter,LogAddExp,LogAddExp2,Hypot,Heaviside,Gcd,Lcm,LeftShift,RightShift- binary nodes.Positive,Conjugate,Fabs,Spacing,SignBit,IsPosInf,IsNegInf,BitwiseCount,Rint,Real,Imag,Angle,Cast,Round(decimals)- unary nodes.LogicalAnd,LogicalOr,LogicalXor,Select,Clip, and the</>/<=/>=operators onNDExpr- logic and selection.Any,All,CountNonzero,NanSum,NanProd,Ptp,NanMin,NanMax,ArgMax,ArgMin,NanMean,Var,Std(ddof), the weightedAverage, axis forms andkeepdims- reductions.dtype=,casting=,order=,where=- keywords;expr.Compile()returns a reusableCompiledExpression.If,Switch,Mux,Saturate,NanTo,Coalesce,Relu,LeakyRelu,Elu,Sigmoid,Swish,Softplus,Gelu,Step,Nand,Implies,IsClose,Between,Bucketize,Lerpand 18 more - 38 decision and activation combinators that fuse like any node.
DTypesystem - NumPy's NEP 40-50 two-level dtype model (dtype classes and dtype instances), the descriptor every dtype parameter andNDArray.dtypenow use.np.dtypes(Float64DTypeand the other dtype classes),np.datetime_data- the class module.num,kind,char,itemsize,str,descr,byteorder,newbyteorder,isbuiltin,isnative,flags,type_num(NPY_TYPES) - the fullnumpy.dtypesurface.isalignedstruct,metadata,len(dtype)/dtype[key](__len__/__getitem__) andnp.dtype(dtype, align, copy, metadata); the dtype classes under NumPy's own names (type,_abstract,_parametric,_legacy,_is_numeric,singleton,type_num,default_descr(),common_dtype(),ensure_canonical(),castingimplswith each class pair's strided loop (get_strided_loop),DTypeFlags.NPY_DT_*,DType.NPY_NATIVEand the other byte-order macros).- Non-native byte orders (
np.dtype(">i4")) anddatetime64/timedelta64descriptors that parse, print, promote and cast-check (no datetime storage yet). promote_types,result_type,can_cast,issubdtype,isdtype-DTypeoverloads with NEP 50 weak scalars.
np.integer,np.floating,np.numberand the rest of NumPy's scalar-type hierarchy - the abstract classes as the newNDScalarType, the class behind every dtype (a.dtype.type) andnp.typing, held to NumPy 2.4.2 by a 6,123-case oracle section (the most-used NumPy names NumSharp lacked:np.integeralone appears in ~203K GitHub files).generic,number,integer,signedinteger,unsignedinteger,inexact,floating,complexfloating,flexible,character- the abstract classes.np.issubdtype(a.dtype, np.integer),np.isdtype(dt, np.floating),np.dtype(cls),np.finfo(cls),np.iinfo(cls)- the type tests and conversions take classes, with NumPy's identity rules and error texts.__name__,__mro__,__bases__,__subclasses__(),__instancecheck__(isinstance),__subclasscheck__(issubclass) - the class surface.np.complex64/np.csingle,np.object_,np.void- the NumPy classes NumSharp has no dtype for, in their NumPy places, and Python's built-in classes in every__mro__(numpy.float64derives fromfloat).np.longdouble,np.clongdouble,np.intc/np.uintc(Windows),np.longlong/np.ulonglong(Linux and macOS) - the C-type classes NumPy keeps beside the sized class of the same storage, classes of their own (np.issubdtype(np.float64, np.longdouble)is False, as in NumPy).np.typecodes,np.sctypeDict,np.ScalarType,np.True_,np.False_- the relatives.np.typing.NDArray,ArrayLike,DTypeLike,NBitBase- each names the C# type that plays the alias's role (npt.NDArray[np.float64]isNDArray<double>).
TensorEngine.Linalg,TensorEngine.Fft,TensorEngine.Default-numpy.linalgandnumpy.fftas sub-engines whoseprotected virtualkernels a native package overrides, and a settable default engine that every new array binds.- Typed iteration and .NET interop -
np.flat<T>(a)/a.flatiter.AsTyped<T>()- typed by-refC-order flat iteration.np.ndenumerate<T>(a).AsRef(),np.ndindex(...).AsSpans()- allocation-free enumeration.nd.Unsafe.Span<T>(),Memory<T>(),Bytes(),Pointer<T>(),TryGetSpan<T>(),TryGetMemory<T>()- zero-copy BCL views of the buffer (they do not keep it alive: hold theNDArray);nd.AsEnumerable<T>()and the typed iterators'ToArray()/CopyTo(Span<T>)- safe copies.
Py.RegisterNumSharpCodec()andPy.RegisterNumSharpArrayAdapter(adapter)(NumSharp.Interop.pythonnet) - the one-call codec and array-adapter registrations, spelled on pythonnet's ownPy(C# 14 extension members).- Text crosses NumSharp.Interop.pythonnet -
S/Uarrays share memory with numpy in both directions,StringDTypecrosses as an exact copy through numpy'sNpyStringC API, and an object array ofstr(a pandas text column) copies asStringDType. NumSharp.Collections-ConcurrentOrderedDictionary<TKey,TValue>andConcurrentOrderedCompactDictionary<TKey,TValue>(about a third of the memory): insertion-ordered, index-addressable concurrent dictionaries.- Extension points - the surface NumSharp's own subclasses, engines and bridges build on is open to a package of yours:
__array_priority__and theprotected internalsubclass hooks__array_wrap__,__array_finalize__and_collapse, under NumPy's names, whichNDMatrixandNDCharArrayoverride; thenp.*functions route only those two classes through them, so a subclass of your own gets plain arrays back from them (np.nanmax/np.nanminexcepted: they dispatch virtually).- Public constructors for
NDMatrix,NDCharArray,NDMaskedArray,NDMaskedConstant,NDPolyFitResult,NDArray<T>(Shape, bool, TensorEngine)andNDArray(DType, Shape, bool, TensorEngine). BitGeneratoris a public abstract base (NextUInt64,NextUInt32, the state hooks), andGenerator,MT19937,PCG64,PCG64DXSM,PhiloxandSFC64can be subclassed, as NumPy's can.Shape's raw layout (dimensions,strides,offset,size,bufferSize) and its view constructor,TensorEngine.Evaluate(program, ...),DotFunction1D/VdotFunction1D, and the bridge surface:SizeBucketedBufferPool.BookForeign/ReturnForeign,ArraySlice.WrapSharedandVStringAllocatorwith NumPy'sNpyStringC API.
🧩 ndarray surface
ToArray<T>()- aTother than the dtype converts likeastype(it threwArrayTypeMismatchException), and every layout copies in one pass (~5.8x faster than before on the measured set).ToMuliDimArray<T>()/(Array)nd- one pass over any layout; float16, complex128, aTother than the dtype (converted likeastype), 0-d and empty decimal arrays now work (they threw).ToJaggedArray<T>()- any rank (rank 6 came back wrong, deeper ranks threw), any layout (a transposed view gave wrong values, a column slice read out of bounds) and aTother than the dtype; 10.7x faster with 7.7x less memory.
⚡ Performance
Ratios are NumPy ÷ NumSharp - higher is better (x2 = twice NumPy's speed); xLOW->xHIGH spans the worst→best measured cell across sizes and dtypes.
x0.70->x3.1-np.max/np.min/np.ptpon NumPy's exact reduction schedules, slab reductions folding eight reduced indices per pass (axis cells on non-C layouts were x0.28-x1.0; L3-bound 4M cells of 1- and 4-byte lanes still trail).x0.41->x4.7-np.nanmax/np.nanmin(float32/float64 axis cells were x0.15-x0.50, flat C now x2.89 / float32 x3.33; float16/complex128 non-C axes run NumPy's scalar loop at x0.41-x0.69).x0.77->x106-np.argmax/np.argminalong an axis walk the strides instead of a per-output div/mod walk (axis 0: float64 x4.4-x8.8, integers x10-x16, bool x95-x106; memory-bound contiguous rows at 4M x0.77-x1.04), and the 64-bit flat argmax runs NumPy's single-pass tournament.x0.89->x2.65-np.mean/np.sumalong an axis with a large output: the identity seed and the mean's division run as typed block passes (np.mean(a, axis=0)of 3x1M float32 was x0.29;mean(axis=1)over 3-wide rows stays below NumPy).x1.43->x1.83-np.convolvelong products without the OpenBLAS backend (1000x1000 float64 was ~x0.9, complex128 x0.25).x1.15->x5.9- complex128divide,multiplyand integer-exponentpower, now on NumPy's own formulas.x1.26->x4.0-np.modfplain,out=and promoted calls (out=at 10M was x0.40).x1.0->x16-np.array_equal/np.array_equivfused early-exit compare (x15-x16 at N=1000, parity at 100K, instant on an early mismatch).x1.05->x6.4- everynp.randombit generator fills through a bulk layer: all 105 engine x op x size cells beat NumPy.x1.08->x5.58- legacyRandomStatesamplers (hypergeometric was x0.96, zipf x1.12, multinomial x0.97; the samplers bound by the C runtime'spow/log/expstay at x1.17-x1.49).x0.65->x17.6-np.evaluatecomparison, logical,whereand bool trees vectorize instead of running the whole tree scalar (a > 0.5was x0.16).x1.0->x22.2-np.evaluatemixed-dtype trees vectorize through lane groups (i4*2+f8x14.2 andf4*f8+f4x22.2 at 100K; 1K cells sit at the per-call floor).x0.86->x53-np.evaluatereductions stream block by block instead of materializing their child, andAny/Allstop at the deciding block (any(a > b, axis=0)was x0.37;count_nonzero(a > b)at 100K sits at the read-bandwidth ceiling).x2.06- a smallnp.evaluatecall skips the iterator like NumPy's trivial loop:a*b+cat n=8 runs in 255 ns vs NumPy's 526 (out=: 75 ns).x1.2->x10^6-Generator.multivariate_hypergeometric(method='count')keeps a large, lightly sampled urn sparse instead of fillingsum(colors)entries before the first draw as NumPy does, byte-identical:[10**9, 1]with one item 3.3 s -> 3 µs (it allocated 8 GB), a 1.05M-item urn sampled 10 items x 20 variates x81; dense urns x1.2-x1.45, unchanged.x0.87->x5.5- the comparisons==/!=/</<=/>/>=on every operand layout and dtype pair: a stepped, reversed, cast or broadcast chunk runs the contiguous SIMD kernels, a matrix against a row, a column or an outer product skips the iterator, an F operand against a C one is compared tile by tile, an array against a scalar runs a vector loop, and complex128 and int64-against-uint64 get vector kernels (1,279 cells: geomean x1.15 -> x2.12, cells under x1.0 445 -> 22, the lowest x0.03 -> x0.87;m > 0.75on a 512x512 float64 matrix x0.25 -> x1.0); buffers of one page or more start on a cache line, which alone makes two-array comparisons of 100,000 elements 12-25 % faster.x1.05->x8.8-np.sum/np.meanon NumPy's add.reduce schedule, every result bit-exact (272 cells, geomean x3.73; was x1.54 with 87 cells under x1.0, and the four complex128 -> float64dtype=cells crashed the process): the pairwise folds run SIMD per run kind (contiguous, reversed, stride 2) and a buffered fill is folded in place, never materialized.x2.33->x79- float16np.max/np.min/np.nanmax/np.nanmin/np.ptpon every layout, flat and per axis, through bit-level SIMD walks of NumPy's sequential HALF loops (276 cells, geomean x26.1: x5.6 at 1K, x41 at 100K, x79 at 1M; was geomean x1.60 with 169 cells under x1.5).x0.8->x41-np.clipwith array bounds,out=andwhere=: the ternary kernels gain broadcast-pattern stages and broadcast a stride-0 bound instead of gathering it (row / column bounds at 100K were x0.2-x0.7 and run x5.1-x19.9;where=with a row bound x19.6-x40.9 at 100K); the plain contiguous clip at 100K is L2-bound on both sides (x0.8-x1.4).x1.51->x38- maskednp.copytois a bit-copy kernel per element width (165 masked cells, geomean x12.4: a 0-d source x38, an array or row source x23, a row mask x10.6), which also lifts numpy.ma's masked reductions from geomean x2.83 to x4.60.x1.55->x7.9- float16add/subtract/multiply/dividewith a row, column, strided, reversed or outer-product operand, and intoout=, run the SIMD widen-compute-narrow kernels (100K cells were x0.30-x0.50 and a broadcast F set x0.23 -> x1.99; numpy.ma's float16anomx0.86 -> x5.97).x0.71->x80- movingS/UandStringDTypetext: copies, gathers,np.where,np.repeat,np.arrayand the casts (StringDTypegeomean x5.35).x1.14->x221- comparisons and truth values ofS/Utext (geomean x12.3), concatenation and repetition (x1.14-x44), text <-> number (x2.0-x7.9).x0.66->x45- sorting, searching, set routines and statistics overS/Utext (family geomeans x3.05-x4.86; the low cells wait on memory on both sides).x1.54->x47- computing on and orderingStringDTypetext (geomeans x3.31 and x7.80, every cell over x1.5).x1.65->x540-np.strings: searches, strips, pads andreplacex1.65-x15.5, predicates x3.99-x33, case maps x4.50-x237,translatex10.8-x540,modx10.7-x39.7,encode/decodex30.7-x317.x1.03->x459-np.@charandchararray(the stripping comparisons x2.6-x10.5, the member families' geomeans x2.70-x57.1, the object path x4.6-x48) andNDListArray's members (x1.92-x459).x1.4->x30.8- the text readers and writers over every dtype (219 cells, geomean x3.81), andStringDTypethroughnp.save/np.load(x2.1-x9.5) andnp.loadtxt/np.savetxt(x2.9-x8.1).x1.52->x46- the quantile family over numbers (70 cells, geomean x5.45) and weighted (x1.52-x24).x0.42->x34.7-np.isinover numbers, its table method in one pass (26 cells, geomean x4.21).x0.91->x15.6-np.argsortandnp.uniqueof float16 and complex128 sort by radix in NumPy's stable order (float16 was x0.40-x1.02).x0.79->x30.1-np.logical_and/np.logical_or/np.logical_xor/np.logical_notin one pass (72 cells, geomean x3.04).x1.84->x10.6-np.evaluatetrees whose dtypes share one element size vectorize (Where(int64 >= 0, float64, 0)x0.74 -> x4.73).x0.88->x9.91-np.indicesof a dtype other than int64 (x7.01 at (1000, 1000)) andnp.mgrid/np.ogridof an imaginary step (x9.91).x0.34->x2.40- the sorts take their per-call scratch from the buffer pool (np.argsortint32 x1.73 -> x2.40).x1.33->x4.34-np.correlate/np.convolveof short and view operands (np.correlateof 3 by 8 values was x0.57).x2.48->x7.0- text -> float64 casts take Clinger's exact fast path.x0.97->x1.92-ndarray.byteswap(inplace)of a strided view (int32 was x0.17).x0.88->x2.33- pythonnetStringDTypecopies; every small pythonnet import runs 2.45x faster than before.x1.07->x1.32-np.dotof an N-D operand without the OpenBLAS backend (int64 was x0.026).
🎯 Parity & Fixes
np.max/np.min/np.ptp, flat and along an axis, run NumPy's own reduction schedules - the sign of a ±0 tie and a canonical-vs-payload NaN now match on every non-broadcast layout.np.nanmax/np.nanminare NumPy'sfmax.reduce/fmin.reducebit for bit: complex128 skips NaN (it propagated it), float16 on non-C layouts read the wrong elements, and an all-NaN float16 slice returns its first NaN.- complex128 arithmetic bit-exact with NumPy -
divide(1 ULP off on ~33% of operands),multiply(NumPy's fusedsimd_cmul),powerwith an integer exponent (up to ~1e14 ULP off) andabs(NumPy's fusedsimd_cabsolute; 35.5% of values were off). - The quantile family -
percentile/quantile/medianare NumPy's two-branch_lerpin each dtype's arithmetic (medianthe mean of the middle pair, a view at a non-zero offset read from its own start), the arguments are checked in NumPy's order,qfollows NumPy 2.4.2 (an intqpicks the element, a 2-Dq, arithmetic inq's dtype), the nan forms return NumPy's axis order and positive NaN, complexmedianof data holding a NaN returns the element NumPy's complex order puts last (_median_nancheck), and uint64 rounds once on .NET 8. np.round/np.aroundwithdecimals != 0portPyArray_Round- negative decimals threw, complex values stayed unrounded, float32 rounded in double, and the result dtypes follow NumPy.sum/prodof a size-0 or size-1 array widen the accumulator by NEP 50 like any other size.np.array_equal(a, a)with a NaN inareturnedTrue; NumPy returnsFalseunlessequal_nan.- int64-vs-uint64 comparisons are exact on every path (they went through float64).
- float16
mod/floor_divide/fmodby zero give ±inf like NumPy (they gave NaN), and float16add/subtract/multiply/divideon theout=/where=/ strided paths compute in float32 like NumPy's HALF loops (they double-rounded through double). - float16 signalling NaNs stay signalling across every float16 cast route, uint64 at or above 2^63 converts to float64/float32 with one rounding on .NET 8 (it rounded twice, inside mixed uint64/float arithmetic and comparisons too), and
maximum/minimumof float16 with float32/float64 keep the NaN's bits. np.linalg.eig/eigvalsof a float32 matrix with complex eigenvalues return NumPy's complex64 values (they kept double precision).- Use-after-free fixed:
np.real/np.imag/.real/.imag,view(dtype)andgetfielddid not keep their owner's buffer alive, so after the owner was disposed (as insideeigvals) the next allocation overwrote them;resize(refcheck: true)now sees such views. np.convolve/np.correlatewithout the backend are byte-exact on float32 positions under 32 terms and complex128 positions under 8 (NumPy's scalarsdot/zdoturegimes), and complex products with inf/NaN follow NumPy (convolve([1+0j], [inf+0j])isnan+nanj).np.arctan2(complex)corrupted the heap and crashed the process; it raises NumPy'sTypeError.np.dtype(str)follows NumPy's parser where it differed:'cdouble'and the intp letters'n'/'N'parse, a single control character is a type number ('\x0c'is float64), only C whitespace counts inside a sized code ('f\xa08'was accepted), and error texts quote the string like Python'srepr(data type "a'b" not understood).np.min_scalar_typeof an array returnednull; it answers the array's dtype, and a 0-d array by its value.np.common_type()with no array is float16,np.mintypecoderaises NumPy'sValueErrorfor a code outside its size table,np.result_typeof oneulongpast int64 is uint64, andNonearguments tocan_cast/promote_typesraise NumPy'sTypeErrors.- A race could make the first
np.can_cast(float64, int32, 'no')of a process answer True: the cached cast safety was read in two loads. np.dtype(null)is float64, NumPy'snp.dtype(None)(it threwArgumentNullException), and a nullnp.result_typeoperand is NumPy'sNone, a float64 operand (np.result_type((DType)null, np.int8)is float64).np.find_common_typeis NumPy 1.26.4's, the last NumPy that has it: each list's type is the first of?bBhHiIlLqQefdgFDGOthat every entry casts to safely, and a scalar type of a higher kind goes through_find_common_coerce.find_common_type([uint8], [int16])is int16 (it was uint8), four or more types are all counted ([int8, int8, float64, int8]was int8), a decimal is no longer searched as int64, and the NDArray form no longer throwsOverflowExceptionfor a uint16 array with an int16 0-d array.np.asanyarrayof mixed scalars promotes in element order like NumPy ([int8, uint8, float16]is float32; it was float16), and a float16 among other types converts (it threwInvalidCastException).- Complex input to
cbrt,floor,ceil,trunc,deg2rad,rad2deg,floor_divideandmodraises NumPy'sTypeError(it raisedNotSupportedException, and a zero-size complex input returned an empty array);IncorrectTypeExceptionnow derives fromTypeError. np.modfaccepts int, float16 and bool input like NumPy (it rejected them), float16 byte-exact.np.traceof uint8/uint16/char accumulates in uint64 like NumPy;ndarray.flatof a 0-d array has shape(1,);np.take,ndarray.item()andlen()of a 0-d array raise NumPy's verbatim texts.np.argsortof an empty array with sort axis >= 2 overran the heap.- A boolean-mask selection owns its result (
flags.owndata), like NumPy. !(logical not) on a non-contiguous array returned wrong values.- Memory leaks fixed in
np.choose,tofile,tobytes,ogrid,poly1d,polyfit,multivariate_normal,apply_along_axis/apply_over_axes,flatiterslice/fancy access, cross-dtypeSetDataand the float16sum/meantemporaries;NDArray.ReplaceDatacould free another array's buffer. Generatorparity (it shipped in 0.70.0).integerswith ahighpast the dtype returned constant data (integers(0, 257, dtype: np.uint8)was all zeros); it raises NumPy's error.- Concurrent draws on one bit generator tore its state (72-99% of 4-thread values belonged to no seed's stream); draws hold the generator's
lock, as in NumPy. choicewithout asizereturns one element (it returned shape(1,));permutedfollows memory order and casts intoout=safely; a-0.0scale, shape or range is rejected; a()size keeps float32.
- Legacy
RandomStateparity.- An unseeded
RandomState()took its seed fromEnvironment.TickCount, so instances made in the same millisecond were identical; it uses OS entropy. choiceignoredreplace=Falseand searched the probability CDF from the wrong side;randint(dtype: bool)crashed.- Every sampler is NumPy's
legacy-distributions.cline for line:gamma(shape<1),binomial,negative_binomial,poisson,f,pareto,power,standard_cauchy,multinomial,hypergeometric,zipf,geometricandvonmisesused other algorithms.
- An unseeded
SeedSequence- a typednullseed array seeded the empty sequence (the same stream on every run; now fresh entropy, NumPy'sNone), andSeedSequence(new[] { -1 })silently seeded0xFFFFFFFFinstead of raising.result_typeof four or more operands dropped every other pair (result_type(int8, int8, float64, int8)was int8; it is float64).np.evaluate- flat and axisSum/Mean/Prod/Min/Maxfollow NumPy's own schedules bit for bit (they matched the value only), a flatMeanof an integer child is NumPy's buffered float64 sum, NEP 50 typing gaps are closed, two closures over one lambda or two literals that print alike (two NaN payloads; 2^-25 on .NET 10) no longer share a kernel, and results take NumPy's layout.- .NET 8: string slicing (
nd["1:3"]) re-parsed its pattern on every call because the staticRegexcache thrashed - 9-18 µs -> 0.6-2.8 µs per parse. NDArray.Scalarstalled for seconds after a multi-threaded burst: the scalar pool's trim removed each freed slot with a linear search under its lock (7-12 s with a few hundred thousand slots).np.clipis NumPy's_clipand its ufunc call: an integer bound outside the dtype no longer wraps (np.clip(uint8, 0, 256)returned all 0 andnp.clip(int8, -1000, 1000)all -24; a Python-int bound at or past the dtype's limit is dropped, and a kept one that does not fit raisesOverflowError),min=/max=/where=exist with fromnumeric's keyword rule, the bounds,outandwherebroadcast together (a bound larger than the array, a largerout), anoutthat overlaps an input is computed as if copied (it gave wrong values), the const float loop keeps NumPy's signed zeros (np.clip(-0.0, 0, 1)is-0.0) and a NaN bound its own bits, and a givenoutis returned itself; the 4,170-caseclip_pyintand 2,302-caseclip_ufuncoracle tiers.- A ufunc with
where=and anoutthat overlaps an input no longer writes heap bytes into the masked-off positions (np.add(x[:-1], 1, out=x[1:], where=m)): the maskedoutis read-write as in NumPy, on everyout=/where=route and innp.evaluate. - An array allocated from a view's
Shapeis laid out fresh, as NumPy'snp.empty(shape)is:new NDArray(dtype, shape),np.empty/np.zeros/np.ones/np.fulland everynp.randomsize:kept a stepped, offset or broadcast view's strides over a buffer ofsizeelements (default_rng(42).integers(0, 100, arange(20)["::4"].Shape)returned heap values andnp.polyval(p, a["::4"])crashed the CLR), and a transposed view'sShapeput a sampler's stream in column order. new NDArray(Array | IArraySlice, Shape, order)is NumPy'snp.ndarray(shape, buffer=, order=)and reads only the shape's dimensions: a view'sShaperead past the caller's buffer (stepped), backwards (reversed), in F order (transposed) or as a read-only repeated row (broadcast);'A'/'K'and lowercase orders are accepted, and an invalid order raises NumPy'sValueError.- A copy or cast of a stepped one- or two-byte view reads no element past the view:
np.zeros(163840, np.uint8)["1::2"].copy()ended the process with an access violation, because seven SIMD deinterleave loops loaded one element beyond a view that ends at its buffer's last element. NDArray<T>.reshape(Shape)reads only its argument's dimensions, as the untypedreshapedoes: theShapeof a view, a transposed or a broadcast array put that array's strides over this array's buffer, reading and writing outside it.np.append(a, (object)v)and the six object-valuenp.insertoverloads no longer dispose the caller'sNDArray(its buffer went back to the pool, and a second call returned wrong values), andnp.choosereleases its cast choices and itsout=temporary instead of leaving them to the finalizer (a 1K choose 3.33 -> 1.63 us).np.add/np.multiply(+,*,np.evaluate) of two float NaNs return NumPy's NaN: the second operand's on every win-amd64 loop path and the broadcast operand's in its AVX2 scalar-broadcast blocks (NumSharp returned the first operand's); the 2,678-casenan_ordertier compares NaN payloads raw.- float16
np.max/np.min/np.nanmax/np.nanmin/np.ptpreturn NumPy's exact bits on every layout, flat and per axis: the first NaN verbatim (payload, sign, signalling) and the first ±0 in NumPy's visiting order (a later NaN replaced an earlier one, and a strided flat walk returned .NET's canonical NaN); the raw-byte 5,900-casehalf_minmaxtier. np.sortwrites NumPy's positive quiet NaN into a float NaN tail (it wrote .NET's negative NaN), the stable sort keeps NaN bits and the float sorts keep ±0 in input order;np.uniquereads an offset view from its own start (it returned the parent's rows), writes NumPy's NaN and keeps NumPy's order among equal values;np.isinfollows NumPy's three paths;np.searchsortedchecks its sorter and reads offset views.np.sum/np.meanstart from the add identity, as NumPy's add.reduce does: a sum of only-0.0is+0.0on every route (one element, a length-1 axis;np.mean,np.average,np.nansum,np.nanmean,np.trapezoid, einsum's sums andnp.evaluatetoo). The flat float sums and means are NumPy's add.reduce bit for bit (NpyIter's call schedule withnp.getbufsize()fills and a pairwise sum per call; 106 of 268 benchmark results had other bits than NumPy's), adtype=loop casts every element first (float -> int truncated the total, complex -> real crashed the process), a complex mean divides as NumPy's complex division does, an empty mean is NaN in the mean's dtype, and the nan reductions read a strided, reversed or transposed view's own elements; the zero-excuse 23,953-casesum_identitytier.- The statistics reductions are NumPy's Python functions statement for statement, bit-exact over zero-excuse oracle tiers -
np.var/np.std(_var/_std) -ddof=anddtype=work at every size: 8,284 of 22,110 probed cases were grossly wrong, and 936 emptydtype=calls wrote past the result buffer; the 16,108-casevarstdtier, geomean x3.07.np.nanvar/np.nanstd- a 1-D axis gives a 0-d result (it was(1,)),keepdimsholds for every input, addofat or past the non-NaN count and an infinite element give NaN (they gave 0), float16 / float32 sum in their own dtype, a complex input isx * conj(x), anddtype=exists; the 13,382-casenanvarstdtier, geomean x3.71.np.mean(_mean) - everydtype=loop casts each element first (the axis loops summed doubles), a complex sum is divided as NumPy's complex loop divides, float16 sums in float32, integer and bool loops wrap, an empty integer input no longer throws, and a flat decimal mean is decimal; the 6,550-casemean_loopstier, geomean x2.62 (a bool mean along axis 0 at 10M x0.08 -> x28).np.nanmean-keepdimsholds for integer inputs, float16 / float32 sum in their own dtype (float16 can overflow toinf), a complex sum divides as NumPy's complex loop does, anddtype=exists; the 5,017-casenanmeantier, geomean x5.82.
np.all/np.anywithout=orwhere=run NumPy's reduce prologue before any work: a non-boolwhere=mask raises NumPy's safe-castTypeError(it was read by truth value), a read-onlyoutis reported first, then the mask, the axis andout's rank, and the reduction iterator's shape checks use NumPy's texts (a mask may not stretch the input or add axes); thereduce_kwargstier starts here with 4,752 cases.np.copytois NumPy'sarray_copyto: an overlapping masked copy no longer reads values it already wrote (251 of 1,500 overlap probes gave silent wrong values), an overlapping slice assignment walks NumPy's raw order (a[3:19:2] = a[6:14]), a mask or a source that does not broadcast is refused, a source with leading unit axes and a self-copy of a read-only array are accepted, and the checks run in NumPy's order with its texts; the 13,462-casecopyto_fulltier.np.power's float loops take NumPy's scalar-exponent fast path (1/x,1,sqrt(x),x,x*xfor -1, 0, 0.5, 1, 2) exactly where NumPy's calls see a zero exponent step: theout=/where=routes calledpowfor a 0-d exponent,(1,) ** (1,)took the fast path where NumPy callspow, a column exponent never took it, and float16 power ispowfwith no fast path (7,161 of 1,114,112 values differed); the 7,902-casepower_fasttier, fast-path cells geomean x6.- Two F-ordered operands where one broadcasts give the right values:
np.add/subtract/multiply/true_divide,maximum/minimum/fmax/fmin, the bitwise functions and the C# operators paired the elements wrongly for every 3-D or 4-D pair with a broadcast axis (each SIMD dtype under 8,192 elements, float16 at any size: 824 of a 2,814-case probe); 3,074 newpp_fbcast_*cases in the binary oracle tiers. np.randomreturns NumPy's kind of array:Generator.choicewithout replacement,RandomState.choicewithout replacement but withp, andmultivariate_normalown their data (they were reshape views),Generator.permutation(x, axis > 0)is NumPy's fancy-index view (F-contiguous for 2-D), and the transpose of an empty array is a view;choice(1_000_000, 100_000, replace: false)x2.03 -> x5.04.NpzFileis safe to share between threads: concurrent first loads of its members decoded each other's bytes; itsZipArchivereads run under one gate, with lock-free cache hits.NDW012, the dropped-NDArraywarning of the analyzer the NumSharp package ships, reads as a fix procedure in one short paragraph (611 characters, was 3,034): not a leak -> suppress it with a reason; every call already runs under a scope ->[NDScopedCovered]; an entry point that keeps no array past the call ->[NDScoped]/[NDScopedAsync]; otherwise dispose it. It no longer claims an array is "never scoped" (a caller's scope reclaims it), and where the build declares that NumSharp.Build is not weaving it never offers[NDScoped].- The analyzer and the NumSharp.Build weaver agree on which interface member a method implements (the CLR's interface map): the analyzer missed every override below a base that lists an interface without declaring the slot, and the weaver wove a public virtual beside an explicit implementation of the same signature (its scope disposed the arrays that method keeps); a class over a k-deep generic interface chain cost the weaver 2^k searches per method (56 s at 26 levels), a type of 50,000 auto-properties 12 s (now 1 s), and
NDW012costs a project that never namesNDArrayone memoized lookup per operation. - Build cost - the three analyzers share one fact set per compilation and skip every body and type that cannot involve an
NDArray(the holder analysis of a 2,500-file project that never namesNDArray13 s -> under 0.01 s,NDW012over NumSharp's own 1,016 files 3.5 s -> 1.5 s), and the NumSharp.Build weaver reads property and event accessors in linear time instead of Cecil's quadratic scan (a 50,000-property type with a scoped method 8.2 s -> 1.8 s). - C# literals are NumPy's weak Python scalars (NEP 50): a float32 array compares with
0.1in float32, an out-of-range integer literal raisesOverflowErrorin a binary ufunc and in every store (it wrapped), and only a literal's 0-d array is weak (an element read ornp.array(x)promotes as a NumPy scalar). Convertsspeaks culture-invariant NumPy text (under de-DE"1.5"read as 15), and every internal promotion is NumPy's order-independentresult_type.- Indexing is NumPy's
prepare_indexandarray_assign_subscript: a float entry is refused (it was rounded), a Python bool is a 0-d mask, a 0-d integer array a view index, the advanced block lands where NumPy puts it, errors come in NumPy's order, a store converts its value with the array's own descriptor (a list item by item),Nonestores NumPy's value, and the key array is never written. - View assignment and
np.insertfollowPyArray_AssignArrayand NumPy's shifted positions; a zero slice step raises when used; the element accessors check every coordinate and convert another element type with NumPy's cast (in Release they read and wrote past the buffer). - Axis arguments are NumPy's
normalize_axis_index(34 functions raised other exceptions, eight wrapped-ndim - 1, a negative axis of a 0-d input never returned). np.put/np.takearePyArray_PutTo/PyArray_TakeFrom(a values view read slots the put had written),np.argwhere/np.flatnonzero/np.unravel_indexreturn NumPy's views, andnp.placeconverts its values as a Python list.np.nditer- NumPy's descriptors end to end, its buffered iteration ported (a buffered reduction lost partial sums with a short buffer), its checks in NumPy's order, element views that do not own their data, and NumPy's store in the setter.- Reductions with
dtype=andout=are NumPy's loops:np.cumsum/np.cumprod/np.prodcast the input first (a float16 or complex128dtype=crashed or read past the buffer), anout=reduces inpromote_types(out, a),np.cumsum/np.cumprodstore into everyout=NumPy's accumulate accepts (more trailing axes, an extent-1 input against a longer out, an empty input or out returned untouched),dtype=charruns uint16's loop anddtype=boolthe logical one, a reversed or broadcast axis adds in NumPy's order, and a 0-d result is NumPy's read-only scalar. np.nansum/np.nanprodarenp.sum/np.prodover_replace_nan(float16 was several ULP off) and no longer read freed memory or divide by zero over an empty axis; complexnp.prod/np.cumprodand float16np.prodmultiply in NumPy's forms.np.dot/np.vdot/np.vecdotcompute with NumPy's dot functions (models of scipy-openblas'sdot/ddot/zdotu/zdotc, NaN priority included): an N-D product read every element as a double (a complex one lost its imaginary parts) and annp.splitchild from its parent's start; the matmul family checks calls, casts andout=in NumPy's order and returns NumPy's output object; float16 products are NumPy's HALF loops.np.correlate/np.convolvecompute complex128 positions with NumPy'szdotuand readmodewith NumPy's converter.np.linalgruns NumPy's statements innorm/vector_norm/matrix_norm,cond,tensorsolve,tensorinv,cross, thehermitian=Trueforms,qr's modes,matrix_power,solve,multi_dotand the scalars ofdet/slogdet/lstsq, and answers empty operands without the OpenBLAS backend.- Ufuncs - a fresh result takes NumPy's K-order layout,
out=/where=are checked before the loop with NumPy's shape errors, an empty operand gives NumPy's empty result,NDArray.negative()wraps unsigned values, unary+of bool raises, andcharreads as uint16. - NumPy's bits in more ufuncs:
np.fmax/np.fminat NumPy's SIMD and C-library positions, float16arctan2/log1p/expm1, float64hypot, models of the UCRT'slog1p/expm1/logaddexp, complexmaximum/fmaxties and the two-bound complexnp.clip. - Creation -
np.arangeisPyArray_ArangeObj(a float ramp in the dtype's own arithmetic, exactlongbounds),np.linspacetakes NumPy's edge cases and text dtypes,np.full/np.eye/*_like/np.indices/np.mgrid/np.ogrid/np.r_/np.c_/np.ix_follow NumPy's arithmetic and checks, a C# object list or jagged array reads as a Python list (a ragged one raises), andnp.frombufferisPyArray_FromBuffer. np.pad,np.delete,np.select,np.where,np.choose,np.compress,np.fill_diagonaland the broadcast family check and convert as NumPy does, with its texts (np.whereread an offset view from its buffer's start;np.broadcast_arraysmade a read-only operand writeable).np.averageis NumPy's statements (2,998 of 3,000 random cases byte-identical, 1,482 before; an empty axis crashed the process), andnp.cov/np.corrcoef,np.einsumand the legacynp.poly*functions answer text, integerdtype=,out=and N-D operands as NumPy does.- Text I/O -
np.fromstring/np.fromfileare NumPy's C scanners (630 of 2,000 random cases differed),np.loadtxtreads Python'sfloat()and NumPy'sDataSource,np.savetxt/ndarray.tofileformat with CPython's%, files open with Python's share mode, and a binarynp.fromfilepast 2 GB reads. - Printing - the array printer is a linear-time walk (it overflowed the stack from about 2,000 dimensions),
np.format_float_positional/np.format_float_scientificare NumPy'sdragon4.c, a float64 prints NumPy's digits on .NET 10, and a text repr escapes as CPython 3.12 does. - Memory safety - an operand stays alive until the call that reads it returns (28 entry points; the NumSharp.Build weaver roots them), and
tolist()/AsStringArrayof a temporary,np.uniquepastArray.MaxLengthelements and a float16 element at the end of mapped memory on .NET 8 no longer read freed or unmapped memory. - NumSharp.Interop.pythonnet - a view of a numpy buffer counts toward NumSharp's memory accounting until its last view is released, instead of one
GC.AddMemoryPressureper view: 5,000 views disposed as they go ran 8-15 full collections, now none. - From the parity QA -
(bool)of a one-element array is NumPy's truth test, a leading boolean mask over trailing extents of 1 selects rows,np.isclosetakes an infinite tolerance,view/getfieldrefuse a non-native descriptor,np.interp's complex NaN isnan+0j,np.nan_to_numrefuses a text fill,ndarray.astypechecks its order and casting first,np.mintypecodereads arrays, and exception texts and thenp.finfo/np.iinforeprs read the same under every culture, NumPy's reprs byte for byte (a number in a message took the culture's minus sign or decimal comma,finfoprintedmin=-3.402823E+038and uint64'siinfomaxlong.MaxValue). np.einsum/np.nested_iterscheck their arguments in NumPy's order:np.nested_itersconvertsflags/order/castingbefore it readsaxesor the operands, andnp.einsumrefuses anout=of the wrong extent before any cast check and plans an explicit path first (an incomplete one raises NumPy'sRuntimeError: Invalid einsum_path is specified; it was contracted anyway).np.polyvaltakes its first product inx's dtype, as NumPy'szeros_like(x)start does:np.polyval([1+1j], [inf])isnan+1j(wasnan+nanj) andnp.polyval([-0.0], int [-3])is+0.0(was-0.0).
🧰 Testing & Tooling
- Differential-fuzz oracle 117,917 -> 836,054 corpus cases: new tiers for every
numpy.polynomialunit (179,118 cases),np.ma(109,961),np.evaluate(29,893),np.emath,ndarrayinstance members, and complex128 / Decimal coverage, with a dtype-spread gate (at least 4 dtypes per op) and an applicability gate. - The array-object, relayout and surface tiers - 9,677 NumPy 2.4.2 cases gate the object an API returns (a view, a copy or the operand itself,
out=identity, the C / F / OWNDATA / WRITEABLE / ALIGNED flags, byte strides, every C# form of a shape argument), 16,862 replay every corpus cell in the layouts it never saw (F, permuted, stepped and reversed views at an offset, broadcast, 0-d, empty), and 25,729 + 27,240 gate the members, attributes and numpy.ma functions the operand corpus cannot express; every gap they found is carved under an id. - The random-API oracle replays every public
np.randomoverload against NumPy 2.4.2 by its exact C# signature (493 members, 5 engines x 10 seeds, the full post-call state), with coverage gates and a nightly soak under 10 fresh seeds. - The coverage join credits each API from the oracle contracts that name it, strictly: a new op key or corpus file that resolves to nothing fails the docs build.
- The coverage inventory lists every
numpy.ufuncmethod (reduce,accumulate,reduceat,outer,at,resolve_dtypes, read from the type) for every ufunc, including those ofnumpy.stringsandnumpy.char, and the methods numpy.ma's masked ufunc wrappers implement: 323 more API contracts. - The coverage inventory credits the
numpy.ma.MaskedArrayandnumpy.vectorizemembers to their NumSharp ownersNDMaskedArrayandVectorized(79 rows had read missing). - The coverage inventory catalogs every module NumPy lists as public and every name its documentation indexes:
numpy.dtypes,numpy.exceptions,numpy.typing,numpy.versionandnumpy.ma.mrecordshad no rows, and neither had the constants and classes of the scanned modules (numpy.ma.masked, the polynomial domain constants), so 66 NumSharp APIs read as NumSharp-only. Two CI guards now hold the catalog to NumPy's own public-module list and documentation index. - The coverage inventory detects every public NumPy API but
numpy.testingandnumpy.lib: the 1,793 Python protocols NumPy's classes define (a + b,a[i], casts, iteration,with, pickling; 158 provided, by members of the protocol's name, C# operators, indexers and casts, or reviewed adapters such asToString()forstr()), the 923 members of NumPy's scalar types and the members of the remaining classes, withnumpy.ctypeslib,numpy.__array_namespace_info__andnumpy.lib.NumpyVersionnow shown. NumPy 1.x compatibility APIs NumPy itself deprecated (numpy.matlib,numpy.typing.NBitBase) are left out, and two CI guards resolve every row on NumPy 2.4.2 and fail on any API NumPy deprecates. - The
numpy.dtype/ dtype-class oracle replays 9,566 / 8,526 NumPy 2.4.2 records (Windows / Linux NumPy, one file per Clongmodel): the member names (the C slots included), every attribute of 1,483 / 1,351 descriptors, comparisons, promotions with each result's class, the dtype classes (each model's C-type classes included), 2,993 / 2,439 casting resolutions and 2,012 dtype strings throughnp.dtype(str)- comma strings included, whose errors compare type and text strictly - and holds the C enums NumSharp mirrors (NPY_TYPES,NPY_TYPECHAR, ...) to the members, values and order of NumPy's header. np.find_common_typereplays 11,291 NumPy 1.26.4 cases, andnp.asanyarrayof mixed scalars 2,917 NumPy 2.4.2 orderings.- NumSharp.Tests runs in parallel, one test per worker: 108 s -> 22-27 s on net10.0 and 117 s -> 22-28 s on net8.0 (Release, a 32-thread desktop; 48 s on 4 CPUs); tests that touch process-wide state are
[DoNotParallelize], and VSTest's result batch is 1000 (NumSharp.Tests.runsettings). - The string oracle - 281,297 NumPy 2.4.2 cases in 66 sections over
S/U/StringDType, strict (no case passes as a deferral), with a randomized soak and mutation passes over the text families. benchmark/strings- the sixth subsystem ofrun_benchmark.py: 442 cells over the text dtypes at 1K / 100K / 1M, geomean x6.61, min x1.44.- The text I/O oracle replays 54,741 Linux NumPy 2.4.2 cases on every host.
- The coverage inventory compares every credited API's C# overloads with NumPy's signature and joins the API diff gap map onto its rows.
tools/facade-move/snapshot.csrecords NumSharp's own outcome for every overload of the named functions over 18 dtypes x 13 layouts, so a refactor can be shown to change nothing; two ratchets keep dtype logic out of the public facades.- Analyzer rule
NDW020keeps exception texts culture-independent: a number,objector type parameter formatted without a format provider inside an exception message (an interpolation hole, a+operand,ToString(),string.Format/Join, aStringBuilderappend, anArgumentOutOfRangeExceptionactual value, and the locals,: base(...)calls and helpers a message reads) is a build error in NumSharp.Core whose text lists the fixes in order; it ships in the NumSharp package off by default (dotnet_diagnostic.NDW020.severity = errorin an.editorconfigturns it on). - Build warning
NDW021- a project that references NumSharp withPublishAot=true(the default of adotnet run app.csfile-based app) is told at build time that NumSharp generates its kernels at run time and does not support Native AOT, with the one-line fix (#:property PublishAot=false), instead of building clean and throwingPlatformNotSupportedExceptionat the first array arithmetic;NumSharpDisableAotWarning=truesilences it. - Packages publish to nuget.org through Trusted Publishing: the release workflow trades its GitHub OIDC token for a one-hour API key (
NuGet/login), so no long-lived API key is stored.
💥 Breaking Changes
DTypereplacesSystem.Typeas the dtype currency.NDArray.dtypereturnsDType(wasType), andnp.float64,np.int32and the rest areDTypedescriptors (wereType);DType->Typeis explicit (a.dtype.type), anda.dtype.Nameisa.dtype.name.- Each dtype parameter takes one
DType: theType/NPTypeCodetwins are gone (aType,NPTypeCodeor dtype string still converts implicitly), andlinspace/nancumsum'stypeCode:isdtype:. - The promotion family (
result_type,promote_types,find_common_type,common_type, ...) returnsDType, andisdtyperejectsissubdtype's vocabulary ("floating"), as NumPy does.
finfo.dtype/iinfo.dtypereturnDType(wereNPTypeCode), and the pythonnet dtype maps takeDType.np.issubdtype's string argument is a dtype string, as in NumPy: the category names are gone (np.issubdtype(x, "integer")raisesdata type 'integer' not understood, andnp.issubdtype(np.float32, "float")is False because"float"is float64); pass the class,np.issubdtype(x, np.integer).- Casting-rule strings are case-sensitive like NumPy (
"Safe"raisesValueError). - A byte-order prefix before a dtype NAME is refused, as in NumPy, which looks names up by the whole string:
np.dtype("<float64"),np.dtype("|bool")and NumSharp's own"<Int32"raisedata type '<float64' not understood(they were accepted). Before a code the prefix still works ("<f8",">i4"). - On Windows,
np.int32/np.int64report the local NumPy's LLP64 type numbers and chars (7/'l'and9/'q'; they were hard-coded to Linux's5/'i'and7/'l'). NPY_TYPECHARis NumPy 2.4.2's:NPY_STRINGLTR2isNPY_DEPRECATED_STRINGLTR2,NPY_INTPLTR/NPY_UINTPLTRare gone (cast the character,(NPY_TYPECHAR)'n'), andNPY_VSTRINGLTR('T') is new.NPTypeCode.ToTypeCode(NPY_TYPECHAR)reads a character asnp.dtypedoes:'D'is complex128 (it was float64),'l'is int32 on Windows (it was int64), and'F','S','U','V','c'and the kind letter'u'are refused (they read as float32, char, char,Empty, complex and uint64).ToTYPECHARgives the dtype's own character ('D'for complex128, it was'c';'q'for int64 on Windows) and refuses decimal and char (they gave'q'and'c'), and theirdtype.charis'A'/'u', characters of their own thatnp.dtyperefuses as NumPy refuses a user dtype's (they were int64's'q'and S1's'c', sonp.dtype(d.char)built another dtype).- A write to a read-only array raises NumPy's
ValueError(wasNumSharpException); it derives fromArgumentException, socatch (NumSharpException)no longer catches it. np.maximum/np.minimum/np.fmax/np.fmintakeoutas the third positional argument (NumPy's order), notdtype; passdtype:by name.np.logical_and/logical_or/logical_xor/logical_notreturnNDArray(wasNDArray<bool>), andnp.modf's tuple fieldIntergralisIntegral.ndarray.shape = ...raises NumPy'sAttributeErrorwhen the layout cannot be reshaped in place (it silently copied); usereshape.np.nanmax/np.nanminof an empty reduction raise NumPy's "zero-size array to reduction operation fmax which has no identity" (they returned a 0-d NaN).new MT19937(42)seeds like NumPy'snp.random.MT19937(42)(throughSeedSequence), not likeRandomState(42);mt._legacy_seeding(42)is the old stream.MT19937's non-NumPy members (NextUInt32,NextDouble,NextInt,NextLong,Next,NextBytes,Seed,SeedByArray,SetState,Key,Pos,Clone) are gone, andPcg64StateDataisPCG64.State.- Seeded legacy
RandomStatestreams ofgamma(shape<1),binomial,negative_binomial,poisson,f,pareto,power,standard_cauchy,multinomial,hypergeometric,zipf,geometric,vonmises(andgumbel/logistic/rayleighat scale 0) change to NumPy's values;binomial(int n)isbinomial(long n); inputs NumPy accepts (dirichlet([]), a NaN alpha,negative_binomial(p: 0)) no longer throw. - Legacy integers are NumPy's LP64 C
long:randintdefaults to int64 (was int32),random_integers/permutation/choice/multinomialreturn int64,NumPyRandom.Seedis auint(wasint), andSeedSequence.generate_statereturns anNDArray(wasArray). - NumPy's parameter names:
Generator(bit_generator)(wasbitGenerator),PCG64(seed)and the other bit generators (wasseedSeq, now anyISeedSequence), anddefault_rng(seed)(wasbitGenerator/generator) - named-argument calls must be renamed. - The non-NumPy
NumPyRandom.uniform(NDArray low, NDArray high, DType dtype)is removed; two-array calls bind NumPy's broadcasting overload (float64;.astype()the result for another dtype). np.asmatrix/np.bmatreturnNDMatrix(was a plain 2-DNDArray):*is the matrix product, an index keeps two axes, andnp.*functions return NumPy's matrix results for it (np.sum(m, 0)has shape(1, N)); take.Afor the plain array.- An index-array assignment follows NumPy's value rule: a value that does not fit raises
ValueError"shape mismatch: value array of shape (1,4) could not be broadcast to indexing result of shape (2,)" before anything is written (wasIncorrectShapeException), and a(1, N)value now fits an(N,)selection. - A boolean-mask assignment takes a 0-d or 1-d value: a 2-d value raises NumPy's
TypeError, and a 1-d value whose length is neither 1 nor the number of true elements raises NumPy'sValueError. Shapeis immutable and the shape accessors return copies, as NumPy's tuples are: theShapeindexer has no setter (s[i] = vno longer compiles; build a newShape),a.shape,Shape.DimensionsandShape.Stridesreturn a copy on every read, andnew Shape(long[]),(Shape)long[]and every public method taking along[]shape copy the caller's array. A write through any of them changed an array's dimensions or strides without its size or buffer, so later reads went past the buffer; a read in a hot loop now allocates (hoist it, or useshape[i]/NDim), and newparams ReadOnlySpan<long>overloads ofreshape/reshape_unsafe/resizekeep inline calls at one allocation.Shape(long[] dims, char order = 'C', bool copy = true)andShape(long[] dims, long[] strides, bool copy = true)replace the two-parameter constructors (recompile: the old signatures are gone), andcopy: false, NumPy's keyword passed by name, keeps an array nothing will write into again instead of copying it.np.ndarray(shape, dtype, buffer, offset, strides, order)is NumPy'sndarray.__new__: float64 and C order by default (they were float32 and'F'),orderis honored and validated without a buffer, the buffer is any bytes-likeobjectwhose bytes are read asdtype(its element type was the dtype),offsetandstridescount bytes, and the fourth positional argument isoffset(acharthere is a compile error: nameorder:). The wrap constructors read a larger buffer's prefix and raiseTypeErrorfor a smaller one (both raisedArgumentException), andflags.alignedis false for misaligned memory (np.frombuffer/getfieldat odd offsets too), sosetflags(align: true)can raise andnp.require(..., 'A')can copy; 167 of 185 probe cases match NumPy (21 did), gated by a 5,919-casendarray_ctortier.np.var/np.std/np.nanvar/np.nanstd/np.mean/np.nanmeanreturn NumPy's dtypes, shapes and values for every loop: a 1-D-axisnanvar/nanstdis 0-d (it was(1,)), float16 / float32 inputs are summed in their own dtype (a float16nanmeancan overflow toinf), an integer or boolmeanloop wraps, and a flat decimal mean is decimal (it was float64).np.medianinto an integer or boolout=stores NumPy's value, cast unsafely as NumPy'smean(out=out)does ([1, 5, 9]forarange(12.).reshape(3, 4)along axis 1; a nonzero median is True), andnp.ptpof a float input into an integer out raises subtract'sTypeError: Cannot cast ufunc 'subtract' output from dtype('float64') to dtype('int64') with casting rule 'same_kind'with the maxima already in the out (both raised np.copyto'sInvalidCastException); integernp.ptpwraps in its dtype and widens exactly (np.ptp(int8 [127, -128], out=int64)is 255; it was -1).np.float_power(x, 2.0)ispow, no longerx*x; float16np.powerispowf(-0.0 ** 0.5is+0.0and-inf ** 0.5is+inf, as in NumPy); and a one-element operand with more dimensions than the other gives the broadcast shape in every binary ufunc ((7,) + (1, 1)is(1, 7); it lost the leading axes).- On C# 12, the default language of a
net8.0project,np.random.seed(null)andnew PCG64(null)no longer compile (CS0121; both compiled against 0.70.0): the overload that takes anullseed is chosen by[OverloadResolutionPriority], which only C# 13 and later honor. Callnp.random.seed()/new PCG64(), or set<LangVersion>13</LangVersion>;net9.0and later projects are not affected. - A C#
stringis NumPy'sstr: the implicitstring->NDArrayconversion andnp.array(string)give a 0-dUarray (the text was parsed as numbers; parse withastype(np.float64)), a.npy<U1loads asU1(it loaded asChar), andnp.@charis thenumpy.charmodule (it still converts to the char dtype). - C# number literals are weak Python scalars: a float16 / float32 array compared with a
doubleliteral compares in its own precision, an out-of-range integer literal raisesOverflowExceptionin a binary ufunc and in every store (a[0] = 1000on int8,np.full(3, -1, dtype: np.uint8),np.copyto(int8, 1000); they wrapped), a NaN literal into an integer array raises, aComplexone into a real arrayTypeError,'equiv'refuses an integer into int32, and a 0-d array that is not a literal promotes as a NumPy scalar (uint8 + int64[1]is int64). - NumPy's exception types:
AxisErrorfor an out-of-range axis,IndexErrorfromnp.put/np.take/np.choose, the indexers andnp.einsum_path(an ellipsis without its operand, an explicit path past the operands),TypeErrorfor a ufunc cast a casting rule refuses, a dtype string NumPy rejects, a numeric cast of a non-0-d array,np.ediff1d's refused ends and anullstored into an integer array,ValueErrorfromnp.nditer's construction errors andnp.nested_iterswithout an operand, a zero slice step andnp.nonzeroof a 0-d array (they wereArgumentException,IndexOutOfRangeException,ArgumentOutOfRangeException,NotSupportedException,InvalidOperationExceptionorIncorrectShapeException);AxisError.ActualValueis the axis as invariant text ("-3";AxisError.Axiskeeps the number). - One NumPy signature:
np.sum/np.prod/np.mean/np.var/np.stdand themax/minfamily (and theirndarraymethods) have one overload each in NumPy's order, so a positional third argument isdtype/out(namekeepdims:); the genericnp.amax<T>/a.max<T>()andNDArray.amax/aminare gone;np.evaluate(expr, x)passesxas an operand (name@out:). - Each array class has one indexer, NumPy's
__getitem__/__setitem__:NDArray's other indexers bindthis[params object[]]with the same results, anint[]key to__getitem__is an index array, andNDArray<T>'s indexer returnsNDArray<T>(its typed scalar indexers are gone:(double)a[0], ora.GetValue<double>(0)in a hot loop). TensorEnginesubclasses implement the new abstract members that carry each computing function;np.sum/np.meancallReduceAdd/ReduceMean; the LAPACK members (Cholesky...Svd) areDefaultLinalgEngine's kernels underTensorEngine.Linalg;BackendFactory.GetEngine()returnsTensorEngine.Default, results and views bind the engine that made them, and a null array operand raisesArgumentNullExceptionwhen the call starts.- A fresh ufunc result takes NumPy's K-order layout (its strides and flags change for permuted, F-ordered and flipped operands), and
np.fftkeeps the input's memory order. - The print options and
np.setbufsizeare context-local under Python 3.12'sContextVarrules: a new thread starts at the defaults,Task.Runandawaitcontinuations see the caller's. NDScope.Disposeon a thread other than the one that opened the scope throwsInvalidOperationExceptionin every build (only a Debug assert guarded it): a hand-written scope held across anawaitwas reused on the continuation's thread, where its close freed the opening thread's later arrays. Close the scope before theawaitand open another after it, or mark the async method[NDScopedAsync]; a refused scope's arrays are left to the finalizer.- Text I/O follows NumPy's encodings and CPython's formatter:
np.loadtxt(Stream)/np.savetxt(Stream)default to Latin-1 (was UTF-8), a named file no longer detects a BOM,encoding:takes Python's codec names,np.savetxt/ndarray.tofileraise CPython's errors where they were lenient, andnp.fromstring/np.fromfileread text as NumPy does. a[true]returns a copy (it was a view), and(bool)of a one-element array answers its truth value (it threw).- Creation:
np.arangeraises NumPy's errors (a zero stepDivideByZeroException, an out-of-range boundOverflowException; it wrapped),np.asanyarrayof a C# object list reads integers as int64 and floats as float64,np.where/np.selectread C# literals as Python scalars (np.where(c, 7, 7)is int64), andnp.frombuffer's range checks raiseValueError. np.padreads its widths before its mode (a null mode isValueError; oneint[]entry per axis is refused, write anint[,]), a value that does not fit a view assignment ornp.insertraisesValueError(wasIncorrectShapeException), andnp.average/np.isin(assume_unique: true)answer as NumPy does.np.complex64/np.csingleare scalar classes (np.zeros(3, np.complex64)no longer compiles), andnp.longdouble/np.clongdouble/np.intc/np.longlongand their unsigned twins are classes of their own (equal to their twin, not the same object).np.sort/np.argsortvalidatekindand takeorder/stable,np.argsort's first parameter isa(wasnd), andnp.format_float_positional/np.format_float_scientifictakeobject xand astring trim.- pythonnet: numpy's
<U1imports as aU1view (was aCharcopy),np.bytes_as a 0-dS<n>, andFromNumpyDtypeStr/FromBufferFormatreturnDType. np.polyderis NumPy's recursion: a 2-Dpis differentiated along its first axis (np.polyder(np.ones((3, 2)))is[[2, 1], [2, 1]]; it raised), and a 0-dpraisesTypeError: len() of unsized objectat every order (m = 0returned it).np.isscalaranswers as NumPy does: aTypeis not a scalar (np.isscalar(typeof(int))wasTrue), andHalf,BigInteger,Char8andDateTime64are (they wereFalse).
Install via NuGet
dotnet add package NumSharp --version 0.80.0
dotnet add package NumSharp.Bitmap --version 0.80.0
dotnet add package NumSharp.Interop.MLNet --version 0.80.0
dotnet add package NumSharp.Interop.OnnxRuntime --version 0.80.0
dotnet add package NumSharp.Interop.OpenBLAS --version 0.80.0
dotnet add package NumSharp.Interop.System.Numerics.Tensors --version 0.80.0
dotnet add package NumSharp.Interop.pythonnet --version 0.80.0
dotnet add package NumSharp.Build --version 0.80.0
Packages
| Package | NuGet |
|---|---|
| NumSharp | |
| NumSharp.Bitmap | |
| NumSharp.Interop.MLNet | |
| NumSharp.Interop.OnnxRuntime | |
| NumSharp.Interop.OpenBLAS | |
| NumSharp.Interop.System.Numerics.Tensors | |
| NumSharp.Interop.pythonnet | |
| NumSharp.Build |