NumSharp.Interop.pythonnet
0.80.0
dotnet add package NumSharp.Interop.pythonnet --version 0.80.0
NuGet\Install-Package NumSharp.Interop.pythonnet -Version 0.80.0
<PackageReference Include="NumSharp.Interop.pythonnet" Version="0.80.0" />
<PackageVersion Include="NumSharp.Interop.pythonnet" Version="0.80.0" />
<PackageReference Include="NumSharp.Interop.pythonnet" />
paket add NumSharp.Interop.pythonnet --version 0.80.0
#r "nuget: NumSharp.Interop.pythonnet, 0.80.0"
#:package NumSharp.Interop.pythonnet@0.80.0
#addin nuget:?package=NumSharp.Interop.pythonnet&version=0.80.0
#tool nuget:?package=NumSharp.Interop.pythonnet&version=0.80.0
NumSharp.Interop.pythonnet
Zero-copy interop between NumSharp NDArray and the Python ecosystem via Python.NET (pythonnet) — with no Numpy.NET dependency. NumPy and PEP 3118 buffers enter the memory bridge directly; registered IPythonArrayAdapter instances feed library objects such as torch.Tensor and Pandas containers through that same bridge. PyTorch and Pandas adapters are built in; neither duplicates the memory implementation.
using NumSharp;
using NumSharp.Interop.PythonNet;
using Python.Runtime;
Runtime.PythonDLL = "python312.dll"; // or the PYTHONNET_PYDLL env var
PythonEngine.Initialize();
Runnable tour:
examples/NumSharp.Interop.pythonnet.Examples— twelve single-filedotnet runtutorials covering every feature below, written for reading (adynamicPython namespace, oneNDScopeper script, the outcome of each line in a comment); their asserted twins live intest/NumSharp.Tests.Interop/Examples.Two projects to copy:
examples/NumSharpPythonnetLocalExampleis a console app that finds the Python on your machine, starts the engine inside a virtual environment and runs NumPy, pandas, SciPy, scikit-learn, Matplotlib and PyTorch over NumSharp arrays.examples/NumSharpPythonnetGhActionsCicdis its asserted twin: an MSTest project with the GitHub Actions workflow that runs it on Windows, Linux and macOS.
The four verbs
Everything is packaging over four operations on the static NDArrayPythonInterop engine:
| Direction | Verb | Semantics |
|---|---|---|
| NumSharp → Python | ToNumpy(nd) |
zero-copy numpy view of NumSharp's buffer — shared mutation, source rooted; full layout fidelity (slices, transposes, Fortran order, negative strides; broadcasts become read-only; scalars become 0-d) |
| NumSharp → Python | ToNumpyCopy(nd) |
independent numpy array (no shared memory, no lifetime coupling) |
| Python → NumSharp | ToNDArray(py) |
copy any PEP 3118 exporter or registered-adapter source into a fresh C-contiguous NDArray (honors logical strides/order; complex64 widens; adapter Copy may detach/transfer) |
| Python → NumSharp | ToNDArrayView(py[, allowReadonly]) |
zero-copy NDArray view over Python memory — adapters only select a canonical shareable object; the existing locked-buffer / __array_interface__ / strided-buffer routes make every memory and lifetime decision |
The lease buffer is always acquired through the exporter's memoryview, never the raw object: the memoryview is CPython's canonical, uniformly-behaved buffer exporter, so this sidesteps pythonnet 3.0.x's per-exporter GetBuffer bugs — a raw ctypes array hard-crashes obj.GetBuffer on every flag, while the memoryview over the same memory leases cleanly. Measured coverage across 55 exporter varieties: 50 view, 4 copy (complex64 widens, sub-item strides linearize, and a StringDType array or an object array of str copies as a NumSharp StringDType array; fixed-width S / U text views). Big-endian multi-byte data is a further copy — the view path declines it (a native-endian shared view is impossible), but ToNDArray byte-reverses each element on copy — so the only refusals left are element types with no NumSharp dtype at all (datetime64, structured, void) and object arrays that hold anything but str.
Plus ToMemoryView(nd) (a writable Python memoryview of raw bytes for non-numpy consumers) and the dtype maps ToNumpyDtypeStr / FromNumpyDtypeStr / ToBufferFormat / FromBufferFormat.
PyTorch through the existing bridge
The built-in TorchPythonArrayAdapter makes the ordinary verbs work directly; there is no TorchSharp
or compile-time PyTorch dependency:
using (Py.GIL())
{
using PyObject tensor = nd.ToTorch(); // shared CPU tensor
using PyObject owned = nd.ToTorch(copy: true); // independent tensor
using NDArray view = tensor.AsNDArray(); // existing shared bridge
using NDArray copy = tensor.ToNDArray(); // existing copy bridge (force permitted)
// Torch-specific aliases/policy controls remain available:
using NDArray sameView = tensor.AsTorchNDArray();
using NDArray strictCopy = tensor.ToTorchNDArray(); // no detach/device transfer
using NDArray forced = tensor.ToTorchNDArray(force: true); // detach + CPU transfer, then copy
}
The sharing path preserves positive strides and roots the owner on both sides. Decimal is the
documented exception: neither NumPy nor PyTorch has that dtype, so it converts to float64.
Negative-stride and non-writeable NumSharp arrays require copy:true; this avoids PyTorch's
documented undefined behavior for tensors over read-only NumPy memory. A Tensor requiring gradients
or living on CUDA/MPS cannot be shared directly with CPU NumSharp memory — use an explicit detach or
force:true copy.
Rare tensor families follow PyTorch's NumPy compatibility boundary: complex64 copy-widens to
NumSharp Complex; bfloat16, float8, sparse and quantized tensors cannot cross through NumPy;
meta tensors have no data even for force:true. PyTorch expand() tensors import safely as
non-writeable NumSharp broadcasts. Scalars, empty arrays, storage offsets, F-order/transposes,
conjugate/negative view bits, derived-view lifetimes, resize refusal, all dtype boundary values,
caller-owned GIL mode and concurrent churn are pinned by the live interop test matrix.
Pandas through the existing bridge
The built-in PandasPythonArrayAdapter recognizes DataFrame, Series, Index,
ExtensionArray, and their subclasses. Pandas → NumSharp uses the ordinary verbs:
using NDArray view = series.AsNDArray(allowReadonly: true); // verified shared projection
using NDArray copy = frame.ToNDArray(); // owning C-contiguous snapshot
NDArrayPythonInterop.RegisterCodec();
using NDArray automatic = frame.As<NDArray>(); // Auto: view, then copy fallback
Pandas documents that to_numpy(copy:false) does not guarantee a view. View mode therefore checks
that two independent official projections overlap before the existing bridge leases one. Homogeneous
NumPy-backed frames, numeric Series/Indexes, and some extension arrays share; mixed numeric blocks,
nullable arrays with missing values, categorical, and sparse arrays copy through Auto/Copy. Pandas 3
usually exposes shared projections read-only, so direct view callers pass allowReadonly:true and
receive a non-writeable NDArray. A text column of str (an object projection) copies as a NumSharp
StringDType() array, and fixed-width S / U projections cross like any NumPy text array; a text
projection holding a missing value (None, NaN, pd.NA) is refused until it is converted to a NumPy
StringDType with a representable NA object. Mixed object, datetime and timedelta outputs remain
unsupported because NumSharp has no corresponding dtype. Axis labels are metadata and do not enter the value
array.
NumSharp → Pandas is the existing NumPy encoder; request Pandas copy:false when sharing is intended:
NDArrayPythonInterop.RegisterCodec();
scope.Set("values", nd);
scope.Exec("series = pd.Series(values, copy=False)");
Adapter-aware pythonnet codec
After NDArrayPythonInterop.RegisterCodec(), pythonnet uses the same adapter registry implicitly:
dynamic torch = Py.Import("torch");
using PyObject tensor = (PyObject)torch.from_numpy(nd); // registered encoder: NDArray → NumPy
using NDArray a = tensor.As<NDArray>(); // registered decoder: Torch → shared bridge
using NDArray b = (NDArray)tensor.AsManagedObject(typeof(NDArray));
NumpyCodecOptions.DecodeArrayAdapters is on by default and independent of DecodeAnyBuffer and
DecodeArrayLike. View permits only share-preserving adaptation; Copy permits library-specific
materialization (including Pandas coercion or Torch detach/device transfer); Auto tries View and
falls back to Copy.
nd.ToPython() itself produces the bridge's NumPy view because pythonnet's encoder sees the managed
source type, not the eventual Python library target. That is also why torch.from_numpy(nd) works:
the callable receives the implicitly encoded NumPy view. On return, As<NDArray>() and
AsManagedObject(typeof(NDArray)) do carry an explicit CLR target and select the adapter-aware decoder.
Other libraries can register the same kind of front door without writing memory code:
PythonArrayAdapterRegistry.Register(myAdapter); // implements IPythonArrayAdapter
The adapter returns a canonical buffer/NumPy object. NDArrayPythonInterop still owns dtype, shape,
strides, writeability, leases, copy fallback, GIL and shutdown behavior. Adapters may decline an
instance by returning null; the registry then tries the next adapter. Disabling codec adapters does
not suppress a recognized object's independent NumPy/buffer capability.
Extension methods (the ergonomic default)
The verbs above double as extension methods — NDArrayPythonInterop provides them, so importing its
namespace lets you call them fluently on NDArray / PyObject as well as statically:
using NumSharp.Interop.PythonNet;
PyObject a = nd.ToNumpy(); // zero-copy view (also: nd.ToPython())
PyObject c = nd.ToNumpyCopy(); // independent copy
NDArray b = py.ToNDArray(); // copy (To… = copy)
NDArray v = py.AsNDArray(); // view (As… = share, like numpy array/asarray)
Codec (auto-marshaling)
NDArrayPythonInterop.RegisterCodec(); // once per engine session; idempotent
Py.RegisterNumSharpCodec(); // identical alias, reads beside Py.Import / Py.GIL
using (Py.GIL())
{
scope.Set("x", nd); // NDArray auto-encoded to a numpy view
dynamic np = Py.Import("numpy");
NDArray r = ((PyObject)np.matmul(a, b)).As<NDArray>(); // decoded back, no explicit calls
}
Py.RegisterNumSharpCodec() is a static extension member (C# 14) on pythonnet's Py, so it reads
beside Py.Import(...) / Py.GIL() at the call site. It forwards verbatim to
NDArrayPythonInterop.RegisterCodec() (both the no-arg and the NumpyCodecOptions overload) — an
alias, not a second registration.
This one call enables ALL of NumSharp's support; NumpyCodecOptions is the single control surface
and every support is a flag defaulting ON — NDArray⇄numpy (EncodeMode/DecodeMode), PEP 3118
buffers (DecodeAnyBuffer), the built-in torch/pandas (and any registered) adapters
(DecodeArrayAdapters), list/tuple/scalar array-likes (DecodeArrayLike), and C# tuples ⇄ Python
tuples (ConvertTuples). So torch and pandas support is part of this call — there is no separate
"register the adapters" step. Adding your OWN third-party adapter is the one thing outside it, a rare
extension point on NDArrayPythonInterop.RegisterArrayAdapter(IPythonArrayAdapter) (it takes an
adapter instance; it is not a support toggle).
Your project must compile with C# 14 to use the
Py.spelling. Static extension members are resolved at the call site by the C# 14+ compiler (the .NET 10 SDK); a consumer on C# 13 or earlier (the .NET 8/9 SDK default) getsCS9202and should callNDArrayPythonInterop.RegisterCodec()instead — it is identical and compiles on every C# version.
NumpyCodecOptions controls the policies via NumpyCodecMode — one enum for both directions (EncodeMode / DecodeMode):
| Mode | Meaning |
|---|---|
Auto (default) |
Zero-copy view when the dtype/layout permits, an independent copy only when a view is impossible — never a blanket copy. On decode the fallback is real (complex64 widens, sub-item strides linearize, big-endian multi-byte byteswaps, a StringDType array or an object array of str copies as StringDType — all as copies; everything else, fixed-width S / U text included, stays a view). On encode every array has a view except a StringDType array, which Auto copies (its text lives outside its buffer). |
View |
Always share; decline the conversion (return no value) if a view is impossible — a loud failure for callers who depend on shared memory. |
Copy |
Always an independent copy — no shared memory, no Py_buffer lock, total coverage. |
Plus DecodeArrayAdapters (default true, including PyTorch and Pandas), DecodeAnyBuffer (default true) and DecodeArrayLike (default true for numeric builtin containers). NumPy ndarray subclasses decode via an __mro__ walk.
Tuples. pythonnet has no tuple conversion of its own — through dynamic, numpy.zeros((2, 3)) used to hand numpy an opaque wrapped System.ValueTuple. RegisterCodec() therefore also registers TupleCodec (NumpyCodecOptions.ConvertTuples, default true): a C# ValueTuple/Tuple of any arity — nested, mixed, an NDArray element becoming a numpy view, null becoming None — crosses as a Python tuple, and a Python tuple (or a subclass such as a namedtuple or torch.Size) decodes into a C# tuple of the same arity; a length mismatch or a list is declined. So shapes, axes and multi-indices read the numpy way:
dynamic z = numpy.zeros((2, 3)); // a C# tuple is the shape
(long, long) strides = z.strides; // (24, 8)
double corner = z.item((1, 2)); // a tuple multi-index
Implicit encodes do not linger. pythonnet takes its own reference from the wrapper an encoder returns and never disposes that wrapper, which would leave every scope.Set("x", nd) / py.x = nd / numpy.sum(nd) export pinned until the CLR finalizer ran (a 150-iteration loop meant 150 live exports). The codec therefore hands its wrapper to a per-thread slot that is disposed at the next encode on that thread and at the next conversion verb, so a rebinding loop keeps exactly one export live. A dynamic read of a numpy array (py.x.sum()) is an ordinary pythonnet wrapper and still lives until the GC; where a reference count matters, read the object as using PyObject or read a scalar through scope.Eval(...).
Autodecode shares memory. Under the default,pyObj.As<NDArray>()on a contiguous/strided-numpy source is a zero-copy view: mutations flow both ways, read-only sources decode as non-writeable views, and the view holds aPy_bufferlock on the source (abytearraycannot be resized while it lives). UseDecodeMode = Copywhen you want a detached snapshot that never touches the Python object.
Lifetime & memory safety (the design)
Exports take their own atomic reference on the NumSharp buffer (NumSharp's ARC), so the memory survives even if every C# reference — including the returned PyObject wrapper — is disposed or collected. Release is owned by Python: a weakref.finalize on the exported array's base buffer object (which every derived numpy view chains to) fires when the last Python-side view dies. If the engine shuts down while Python still holds views, the pin is swept right after PythonEngine.Shutdown() completes — pythonnet runs no Python atexit pass, so the finalize callbacks cannot fire during shutdown (probed; the sweep waits until the interpreter is provably gone, so it can never race Python reads). While exported, ndarray.resize(refcheck: true) on the source refuses to reallocate — the same guard NumPy applies to referenced arrays.
Imports lease the Python buffer through NumSharp's memory-block reference counting: the lease is released when the last NumSharp view over the memory — including derived slices like nd["2:"] — is disposed or collected. C-contiguous leases hold a real Py_buffer, so resizable exporters (bytearray) are locked against reallocation, and numpy's resize refcheck refuses while the lease lives. The Python-side release is marshaled to the GIL safely (never raw on a finalizer thread); NDArrayPythonInterop.LiveExports / LiveImports expose the counters.
Read-only sources (bytes, non-writeable numpy arrays) are refused for views by default — writing through them would corrupt immutable Python objects. Pass allowReadonly: true to opt in: the view comes back non-writeable (Shape.IsWriteable == false, exactly numpy's writeable=False), so guarded write paths raise assignment destination is read-only instead of corrupting the source. Or use ToNDArray to copy. An empty read-only source (np.frombuffer(b''), an empty broadcast) is refused or non-writeable the same way, and an empty NumSharp broadcast exports as a read-only numpy array.
Import views also do not own their data (like np.frombuffer(...), whose flags.owndata is False): a size-changing ndarray.resize refuses with NumPy's cannot resize this array: it does not own its data instead of silently reallocating away from the shared Python memory, and np.require(..., "O") produces an owning copy.
Rules & limits
GIL: every method acquires the GIL itself (re-entrant). Your own
PyObjectusage still follows pythonnet's rules: dispose aPyObject(aToNumpyresult included) insidePy.GIL(). pythonnet'sDisposehas no guard of its own, so two threads disposing one without the GIL can both release it (1 crash in 397 races of a probe; 0 under the GIL).GIL opt-out: every conversion verb takes a nullable
requireGILparameter;null(the default) follows the process-wideNDArrayPythonInterop.RequireGIL(defaulttrue). An effectivefalsereplacesPy.GIL()with a shared no-op guard — the calling thread must already hold the GIL (an enclosingPy.GIL()block); converting GIL-less without holding it is an immediate access violation. Trap: a .NET method/delegate body invoked from Python does not hold the GIL — pythonnet's binder releases it around managed bodies (probed on 3.0.5/3.1.0:PyGILState_Check() == 0inside the body) — so keep GIL management on inside Python→.NET callbacks. The interop's background machinery (deferred lease disposal, shutdown drain) always manages the GIL itself, regardless of the policy.using (Py.GIL()) // ONE acquisition... for (int i = 0; i < n; i++) using (PyObject p = batch[i].ToNumpy(requireGIL: false)) // ...N conversions inside consumer.Invoke(p); NDArrayPythonInterop.RequireGIL = false; // or process-wide, when EVERY call site holds the GILEngine lifetime: import views die with the interpreter.
PythonEngine.Shutdown()releases all outstanding leases crash-free (a registered shutdown handler), but the NDArrays over that memory must not be touched afterwards (disposing them stays safe). Exports still held by Python are swept right after shutdown completes — no leak survives the engine.PythonEngine.Shutdownon .NET 8+: pythonnet crashes in its own BinaryFormatter state-stashing. Opt out first:RuntimeData.FormatterType = typeof(NoopFormatter);(NoopFormatterships from pythonnet 3.0.4 onward — guaranteed by the 3.0.5 floor below).dtypes: all NumSharp numeric dtypes map except
Decimal(text: next bullet) — it has no numpy dtype, so the array-producing verbs (ToNumpy/ToNumpyCopy, and the codec) auto-convert it tofloat64(theastype(Double)guidance, automated — lossy beyond ~16 digits), while the low-level maps (ToNumpyDtypeStr/ToBufferFormat/ToMemoryView) stay honest and still refuse it.Charis exported asuint16(UTF-16 code units) and imported from 2-byte wchar buffers (PEP 3118'u':array.array('u')on Windows,ctypes.c_wchar) as a zero-copy view. Big-endian multi-byte buffers cannot be viewed (a native-endian shared view is impossible), butToNDArraybyteswaps them on copy (>c16reverses each 8-byte half;>c8reverses then widens; single-byte>i1/|u1/|b1view regardless). complex64 imports copy-widen to complex128 — it has no zero-copy view.text: fixed-width
S<n>/U<n>arrays cross as views in both directions, the same bytes on both sides (numpy<U1is NumSharp'sU1, the same 4-byte code points, astral characters included; a 4-byte wchar buffer,'w', views asU1); big-endian>U<n>copies byte-reversed;np.str_/np.bytes_scalars are 0-dU<n>/S<n>. A NEP 55StringDTypearray has no buffer — numpy keeps each element's text in an allocator its descriptor owns — soToNDArrayandToNumpyCopycopy it exactly through numpy's ownNpyStringC API (every NA value, NULs, non-BMP text, any layout; the copy keepsna_objectandcoerce), and the view verbs refuse it. The missing-value object crosses when it has a C# form (None,bool,int,float,complex,str,bytes,np.float16, and numpy'snp.float64/np.complex128/np.str_/np.bytes_as the Python type they subclass); any other (pd.NA,np.int64) is refused. An object array whose every element is astr(a pandas text column) copies toStringDType()(astrsubclass element asstr(x), what numpy's ownStringDType()cast stores); any other object array is refused. PyTorch has no string tensors, soToTorchrefuses text.array-like decode (on by default):
py.FromArrayLike()materializes a Pythonlist/tuple/ nested sequence / scalar (int/float/bool/complex) into an owningNDArrayvianumpy.asarray, and the registered codec does it automatically (NumpyCodecOptions.DecodeArrayLike, defaulttrue) so a numpy call or plain Python that returns a list crosses without a manual conversion. It fires ONLY for genuine non-buffer builtins — a numpy array the view/copy paths declined is never rescued into a silent copy. SetDecodeArrayLike = falseto restrict decode to buffer exporters (fully numpy-agnostic).pythonnet versions: the dependency is
[3.0.5, 4.0.0).PyBufferis broken for shape/strides/format flags, so metadata is read through Python'smemoryviewand only the crash-freePyBUF.SIMPLE/PyBUF.WRITABLEare used — the same code path works on every v3. The source compiles clean against all of 3.0.0–3.1.0; 3.0.5 is the floor because it is the oldest release that covers the Python versions people actually run (see below).
Choosing a pythonnet version
Every pythonnet release hard-caps the Python it can drive. MinSupportedVersion has been 3.7 for the
entire v3 line, so the ceiling is the only thing that moves — which makes 3.0.5 a strict superset of
every earlier release. Read from each package's own PythonEngine.MaxSupportedVersion:
| pythonnet | Python supported | note |
|---|---|---|
| 3.0.0 | 3.7 – 3.10 | |
| 3.0.1 / 3.0.2 | 3.7 – 3.11 | |
| 3.0.3 / 3.0.4 | 3.7 – 3.12 | NoopFormatter added in 3.0.4 |
| 3.0.5 | 3.7 – 3.13 | the floor — what you get by default |
| 3.1.0 | 3.7 – 3.14 | release notes drop 3.7–3.9; opt in for Python 3.14 |
NuGet installs the floor, not the latest. Resolution is lowest-applicable, so a plain
dotnet add package NumSharp.Interop.pythonnet gives you pythonnet 3.0.5 even though newer
releases exist. On Python 3.14, ask for it explicitly:
<PackageReference Include="pythonnet" Version="3.1.0" />
Overshooting a release's ceiling usually kills PythonEngine.Initialize() with a bare
MissingMethodException: Failed to load symbol PyUnicode_AsUnicode (or similar). If the engine
initializes anyway, the interop checks the range itself on first use and raises an actionable error
naming the version to install.
What's new in 0.80.0
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.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
NuGet packages (3)
Showing the top 3 NuGet packages that depend on NumSharp.Interop.pythonnet:
| Package | Downloads |
|---|---|
|
OptunaSharp.Interop.PyCma
PyCmaSampler for OptunaSharp — a native OptunaSharp BaseSampler that drives the real pycma (Nikolaus Hansen's `cma` package: cma.CMAEvolutionStrategy + cma.BoundTransform) library in-process via Python.NET (pythonnet). A faithful C# port of optuna_integration's PyCmaSampler glue (the sampler protocol + the parameter/cma-space transforms + the ask/tell generation replay) whose CMA-ES numerics are direct pycma calls, so it is byte-exact with pycma. Distinct from OptunaSharp's native CmaEsSampler, which ports the separate `cmaes` package — this mirrors Optuna's own optuna.integration.PyCmaSampler vs optuna.samplers.CmaEsSampler split. Does NOT require the Python optuna_integration package — install only `cma`. The .NET analog of using optuna_integration.PyCmaSampler, built on OptunaSharp.Interop.pythonnet. Part of the SciSharp STACK. |
|
|
OptunaSharp.Interop.Pymoo
PymooSampler for OptunaSharp — a native OptunaSharp BaseSampler that drives the real pymoo (the multi-objective evolutionary-optimization library) in-process via Python.NET (pythonnet). One sampler exposes pymoo's whole multi-objective algorithm zoo — NSGA-II, NSGA-III, R-NSGA-II, R-NSGA-III, U-NSGA-III, AGE-MOEA, AGE-MOEA-2, RVEA, SMS-EMOA, SPEA2, C-TAEA — through a pythonnet-free PymooAlgorithm enum. The Optuna side (study, storage, ask/tell, search space) is native OptunaSharp; the operators, reference-direction machinery and HV/decomposition survival are genuine pymoo, reconstructed from the study's completed trials and driven through pymoo's native ask()/tell() — the same reconstruct-from-trials bridge as OptunaSharp.Interop.PyCma / .BoTorch. Configurable through PymooAlgorithmOptions: SBX crossover + PM mutation, duplicate elimination, RVEA alpha/adapt_freq, R-NSGA weights/mu/epsilon, and Das-Dennis / Riesz-energy reference directions. Supports constrained optimization (pymoo's G ≤ 0 feasibility, via Trial.SetConstraint or a constraintsFunc) and single-, two- and many-objective studies over Optuna's full parameter space (float/int with log/step, categoricals, dynamic spaces). Beyond Optuna core (Optuna ships only NSGA-II/III), a live-runtime complement to the native AgeMoea2Sampler. Does NOT require any Python glue package — install only `pymoo`. Built on OptunaSharp.Interop.pythonnet. Part of the SciSharp STACK. |
|
|
OptunaSharp.Interop.BoTorch
BoTorchSampler for OptunaSharp — a native OptunaSharp BaseSampler that drives the real BoTorch (PyTorch + GPyTorch) library in-process via Python.NET (pythonnet). A faithful C# port of optuna_integration's BoTorchSampler glue (sampler protocol + candidates_func selection) whose numerics are direct botorch calls, so it is byte-exact with BoTorch. You pick the acquisition function with the pythonnet-free BoTorchCandidatesFunc enum: LogEI, qEI, qLogEI and qNEI for one objective; qEHVI, EHVI, qNEHVI, qParEGO and qLogEI-ParEGO for several; the knowledge-gradient qKG and qHVKG; constrained optimization through Trial.SetConstraint. Does NOT require the Python optuna_integration package — install only `botorch`. The .NET analog of using optuna_integration.BoTorchSampler, built on OptunaSharp.Interop.pythonnet. Part of the SciSharp STACK. |
GitHub repositories
This package is not used by any popular GitHub repositories.