pynumerics 2.3.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pynumerics/__init__.py +73 -0
- pynumerics/_bin/Microsoft.CodeAnalysis.CSharp.dll +0 -0
- pynumerics/_bin/Microsoft.CodeAnalysis.LICENSE.txt +27 -0
- pynumerics/_bin/Microsoft.CodeAnalysis.dll +0 -0
- pynumerics/_bin/Numerics.dll +0 -0
- pynumerics/_bin/pynumerics.runtimeconfig.json +15 -0
- pynumerics/_coverage.py +3825 -0
- pynumerics/_util.py +237 -0
- pynumerics/_version.py +40 -0
- pynumerics/bootstrap.py +593 -0
- pynumerics/callbacks.py +791 -0
- pynumerics/copulas.py +910 -0
- pynumerics/data.py +2233 -0
- pynumerics/differentiation.py +348 -0
- pynumerics/distributions.py +4043 -0
- pynumerics/download.py +623 -0
- pynumerics/enums.py +119 -0
- pynumerics/errors.py +245 -0
- pynumerics/functions.py +1142 -0
- pynumerics/goodness_of_fit.py +699 -0
- pynumerics/graphs.py +556 -0
- pynumerics/hypothesis_tests.py +445 -0
- pynumerics/identity.py +144 -0
- pynumerics/integration.py +677 -0
- pynumerics/linalg.py +1408 -0
- pynumerics/machine_learning.py +1335 -0
- pynumerics/marshal.py +733 -0
- pynumerics/mathematics.py +1053 -0
- pynumerics/mcmc.py +1406 -0
- pynumerics/multivariate.py +1539 -0
- pynumerics/ode.py +156 -0
- pynumerics/optimization.py +1736 -0
- pynumerics/probability.py +966 -0
- pynumerics/py.typed +0 -0
- pynumerics/regional.py +522 -0
- pynumerics/roots.py +274 -0
- pynumerics/runtime.py +384 -0
- pynumerics/sampling.py +1758 -0
- pynumerics/special.py +1079 -0
- pynumerics/statistics.py +1958 -0
- pynumerics/timeseries.py +1326 -0
- pynumerics-2.3.0.dist-info/METADATA +142 -0
- pynumerics-2.3.0.dist-info/RECORD +46 -0
- pynumerics-2.3.0.dist-info/WHEEL +5 -0
- pynumerics-2.3.0.dist-info/licenses/LICENSE +10 -0
- pynumerics-2.3.0.dist-info/top_level.txt +1 -0
pynumerics/marshal.py
ADDED
|
@@ -0,0 +1,733 @@
|
|
|
1
|
+
"""Copy arrays between NumPy and .NET in bulk, preserving every bit.
|
|
2
|
+
|
|
3
|
+
Each non-empty transfer is one bulk copy through the Python buffer protocol, never
|
|
4
|
+
element-by-element indexing, so values keep their exact bit patterns (NaN payloads, signed
|
|
5
|
+
zeros, infinities and subnormals included) and large arrays copy at memory speed.
|
|
6
|
+
|
|
7
|
+
pythonnet exports a .NET array of a primitive type (``Double``, ``Single``, ``Int32``,
|
|
8
|
+
``Int64``, ``Byte``, ``SByte``, of any rank) through the buffer protocol. Acquiring the buffer
|
|
9
|
+
pins the array with a ``GCHandle`` and releasing it frees the handle; every copy here holds the
|
|
10
|
+
buffer in a ``with memoryview(...)`` block, so the handle is freed when the block ends, also
|
|
11
|
+
when the copy raises. A write allocates the .NET array and assigns the NumPy values into a
|
|
12
|
+
``numpy.frombuffer`` view of its storage; a read copies such a view into a new NumPy array.
|
|
13
|
+
Both are one ``memcpy`` for contiguous data, the same bits as
|
|
14
|
+
``System.Runtime.InteropServices.Marshal.Copy`` at less than half of its call cost. Callbacks
|
|
15
|
+
rely on this, since every evaluation copies its array arguments.
|
|
16
|
+
|
|
17
|
+
Two-dimensional arrays (``double[,]``, which Numerics returns from samplers such as
|
|
18
|
+
``LatinHypercube.Random`` and takes as covariance matrices, and the ``int[,]`` and ``float[,]``
|
|
19
|
+
tables of the graph, optimization and probability wrappers) copy directly into or out of the
|
|
20
|
+
rectangular .NET array: a .NET rectangular array is stored in row major order, the same layout
|
|
21
|
+
as a C-order NumPy array, so element ``[i, j]`` maps exactly.
|
|
22
|
+
|
|
23
|
+
Space-time blocks cross as a flat ``System.Single[]`` in C order together with explicit
|
|
24
|
+
``(steps, rows, columns)`` dimensions: ``flat[(t * rows + r) * columns + c]`` equals
|
|
25
|
+
``block[t, r, c]``.
|
|
26
|
+
|
|
27
|
+
pythonnet describes a ``System.Boolean[]`` buffer with the four-byte marshalled size of
|
|
28
|
+
``Boolean`` rather than its one-byte storage, so ``Boolean[]`` arrays cross as one buffer copy
|
|
29
|
+
into a ``System.Byte[]`` followed by one ``System.Buffer.BlockCopy``, which reinterprets the
|
|
30
|
+
bytes without conversion (a .NET ``Boolean`` occupies one byte, as a NumPy ``bool`` does).
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
from __future__ import annotations
|
|
34
|
+
|
|
35
|
+
import math
|
|
36
|
+
from typing import Any
|
|
37
|
+
|
|
38
|
+
import numpy as np
|
|
39
|
+
|
|
40
|
+
from pynumerics.runtime import initialize
|
|
41
|
+
|
|
42
|
+
_INT32_MIN = -(2**31)
|
|
43
|
+
_INT32_MAX = 2**31 - 1
|
|
44
|
+
_INT64_MIN = -(2**63)
|
|
45
|
+
_INT64_MAX = 2**63 - 1
|
|
46
|
+
_INT8_MIN = -(2**7)
|
|
47
|
+
_INT8_MAX = 2**7 - 1
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _clr() -> tuple[Any, Any]:
|
|
51
|
+
"""Return the (System, Marshal) handles after guaranteeing runtime initialization."""
|
|
52
|
+
initialize()
|
|
53
|
+
import System # noqa: PLC0415 - resolvable only after runtime initialization
|
|
54
|
+
from System.Runtime.InteropServices import Marshal # noqa: PLC0415
|
|
55
|
+
|
|
56
|
+
return System, Marshal
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
_BOUND: dict[str, Any] = {}
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _bound() -> dict[str, Any]:
|
|
63
|
+
"""Return the array constructors and the ``Marshal.Copy`` read overloads, resolved once per process.
|
|
64
|
+
|
|
65
|
+
pythonnet resolves an overloaded method group on every call; ``Marshal.Copy`` has more than a
|
|
66
|
+
dozen overloads and ``Array.CreateInstance`` six, and resolving them costs many times the copy
|
|
67
|
+
of a short array. Binding the exact overloads once keeps a small transfer in the microseconds.
|
|
68
|
+
The ``Marshal.Copy`` overloads serve only reads of an argument that is not an array of the
|
|
69
|
+
expected element type, which they convert or refuse as they always have.
|
|
70
|
+
"""
|
|
71
|
+
if not _BOUND:
|
|
72
|
+
System, Marshal = _clr()
|
|
73
|
+
import clr # noqa: PLC0415 - resolvable only after runtime initialization
|
|
74
|
+
|
|
75
|
+
clr_type = clr.GetClrType
|
|
76
|
+
pointer, length = System.IntPtr, System.Int32
|
|
77
|
+
doubles, ints, singles = System.Array[System.Double], System.Array[System.Int32], System.Array[System.Single]
|
|
78
|
+
longs, raw = System.Array[System.Int64], System.Array[System.Byte]
|
|
79
|
+
create_matrix = System.Array.CreateInstance.Overloads[System.Type, length, length]
|
|
80
|
+
_BOUND.update({
|
|
81
|
+
"doubles": doubles, "ints": ints, "singles": singles, "pointer": pointer,
|
|
82
|
+
"longs": longs, "bytes": raw, "sbytes": System.Array[System.SByte], "bools": System.Array[System.Boolean],
|
|
83
|
+
"block_copy": System.Buffer.BlockCopy, "double_type": clr_type(System.Double),
|
|
84
|
+
"create_matrix": create_matrix, "objects": System.Array[System.Object],
|
|
85
|
+
"element_types": {np.dtype(np.float64): clr_type(System.Double), np.dtype(np.int32): clr_type(System.Int32),
|
|
86
|
+
np.dtype(np.float32): clr_type(System.Single)},
|
|
87
|
+
"doubles_matrix": type(create_matrix(clr_type(System.Double), 0, 0)),
|
|
88
|
+
"doubles_out": Marshal.Copy.Overloads[doubles, length, pointer, length],
|
|
89
|
+
"ints_out": Marshal.Copy.Overloads[ints, length, pointer, length],
|
|
90
|
+
"singles_out": Marshal.Copy.Overloads[singles, length, pointer, length],
|
|
91
|
+
"longs_out": Marshal.Copy.Overloads[longs, length, pointer, length],
|
|
92
|
+
})
|
|
93
|
+
return _BOUND
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _fill(target: Any, array: np.ndarray) -> Any:
|
|
97
|
+
"""Copy ``array`` into the storage of ``target``, a new .NET primitive array of the same size, in C order.
|
|
98
|
+
|
|
99
|
+
``array`` must already have the native NumPy dtype of the element type of ``target`` (float64
|
|
100
|
+
for ``Double``, int32 for ``Int32`` and so on), and ``target`` must hold ``array.size``
|
|
101
|
+
elements, at any rank. Acquiring the buffer pins ``target``; the ``with`` block releases it,
|
|
102
|
+
also when the copy raises. A same-dtype NumPy assignment copies the bits unchanged: one
|
|
103
|
+
``memcpy`` for contiguous input, a C-order strided copy otherwise. Returns ``target``.
|
|
104
|
+
"""
|
|
105
|
+
if array.size:
|
|
106
|
+
with memoryview(target) as view:
|
|
107
|
+
if array.ndim == 1:
|
|
108
|
+
np.frombuffer(view, dtype=array.dtype)[:] = array
|
|
109
|
+
else:
|
|
110
|
+
np.frombuffer(view, dtype=array.dtype).reshape(array.shape)[...] = array
|
|
111
|
+
return target
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def _read(values: Any, dtype: Any) -> np.ndarray:
|
|
115
|
+
"""Copy a .NET primitive array whose element type matches ``dtype`` into a new NumPy array of its shape.
|
|
116
|
+
|
|
117
|
+
One ``memcpy`` of the pinned .NET storage; the ``with`` block releases the buffer. The caller
|
|
118
|
+
guarantees the element type, since the buffer bytes are read as ``dtype``.
|
|
119
|
+
"""
|
|
120
|
+
with memoryview(values) as view:
|
|
121
|
+
shape = view.shape
|
|
122
|
+
if len(shape) == 1:
|
|
123
|
+
return np.frombuffer(view, dtype=dtype).copy()
|
|
124
|
+
return np.frombuffer(view, dtype=dtype).reshape(shape).copy()
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def _flat_doubles(array: np.ndarray) -> Any:
|
|
128
|
+
"""Copy a float64 array of any shape (contiguous, strided or broadcast) into a new ``System.Double[]`` in C order.
|
|
129
|
+
|
|
130
|
+
The values cross in one assignment, with no intermediate contiguous copy; the elements are
|
|
131
|
+
those of ``to_net_doubles(array.ravel())``, bit for bit.
|
|
132
|
+
"""
|
|
133
|
+
return _fill(_bound()["doubles"](array.size), array)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def _net_rectangular(array: np.ndarray) -> Any:
|
|
137
|
+
"""Copy a two-dimensional float64, int32 or float32 array into a new .NET rectangular array of that element type."""
|
|
138
|
+
bound = _bound()
|
|
139
|
+
rows, cols = (int(dimension) for dimension in array.shape)
|
|
140
|
+
return _fill(bound["create_matrix"](bound["element_types"][array.dtype], rows, cols), array)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
_BUFFER_FORMATS = {np.dtype(np.float64): "d", np.dtype(np.int32): "i", np.dtype(np.float32): "f"}
|
|
144
|
+
"""The buffer-protocol format pythonnet reports for a .NET ``Double``, ``Int32`` and ``Single`` array."""
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def _numpy_rectangular(values: Any, dtype: Any) -> np.ndarray:
|
|
148
|
+
"""Copy a .NET rectangular array (``double[,]``, ``int[,]`` or ``float[,]``) into a new 2-D array of ``dtype``.
|
|
149
|
+
|
|
150
|
+
Raises
|
|
151
|
+
------
|
|
152
|
+
TypeError
|
|
153
|
+
If ``values`` is not a rank-2 array whose element type matches ``dtype``.
|
|
154
|
+
"""
|
|
155
|
+
with memoryview(values) as view:
|
|
156
|
+
if view.ndim != 2 or view.format != _BUFFER_FORMATS[np.dtype(dtype)]:
|
|
157
|
+
raise TypeError(f"Expected a rank-2 .NET array of {np.dtype(dtype)}; got format {view.format!r} "
|
|
158
|
+
f"and rank {view.ndim}.")
|
|
159
|
+
return np.frombuffer(view, dtype=dtype).reshape(view.shape).copy()
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def to_net_doubles(values: Any) -> Any:
|
|
163
|
+
"""Copy a one-dimensional array into a new ``System.Double[]``, preserving every bit.
|
|
164
|
+
|
|
165
|
+
The input is converted with ``numpy.ascontiguousarray(values, dtype=numpy.float64)``
|
|
166
|
+
first. That conversion is exact for float32 values and for integers up to 2**53 in
|
|
167
|
+
magnitude, and it turns a scalar into a one-element array. Starts the .NET runtime if
|
|
168
|
+
it is not already running.
|
|
169
|
+
|
|
170
|
+
Parameters
|
|
171
|
+
----------
|
|
172
|
+
values : array_like
|
|
173
|
+
Values to copy, one-dimensional after conversion.
|
|
174
|
+
|
|
175
|
+
Returns
|
|
176
|
+
-------
|
|
177
|
+
System.Double[]
|
|
178
|
+
A new .NET array holding the float64 values.
|
|
179
|
+
|
|
180
|
+
Raises
|
|
181
|
+
------
|
|
182
|
+
ValueError
|
|
183
|
+
If ``values`` is not one-dimensional after conversion.
|
|
184
|
+
"""
|
|
185
|
+
bound = _bound()
|
|
186
|
+
array = np.ascontiguousarray(values, dtype=np.float64)
|
|
187
|
+
if array.ndim == 0:
|
|
188
|
+
array = array.reshape(1)
|
|
189
|
+
if array.ndim != 1:
|
|
190
|
+
raise ValueError(f"Expected a 1-D array; got shape {array.shape}.")
|
|
191
|
+
return _fill(bound["doubles"](array.size), array)
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def _optional_doubles(values: Any) -> Any:
|
|
195
|
+
"""Copy values into a ``System.Double[]``, passing None through as a null reference."""
|
|
196
|
+
return None if values is None else to_net_doubles(values)
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
_LINEAR_ALGEBRA: list[Any] = []
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def _linear_algebra() -> Any:
|
|
203
|
+
"""Return the ``Numerics.Mathematics.LinearAlgebra`` namespace after starting the runtime, imported once."""
|
|
204
|
+
if not _LINEAR_ALGEBRA:
|
|
205
|
+
initialize()
|
|
206
|
+
import importlib # noqa: PLC0415
|
|
207
|
+
|
|
208
|
+
_LINEAR_ALGEBRA.append(importlib.import_module("Numerics.Mathematics.LinearAlgebra"))
|
|
209
|
+
return _LINEAR_ALGEBRA[0]
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
_CONSTRUCTORS: dict[str, Any] = {}
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def _construct(name: str, argument: Any) -> Any:
|
|
216
|
+
"""Construct a Numerics ``Matrix`` from a ``double[,]`` or a ``Vector`` from a ``double[]`` (``name``).
|
|
217
|
+
|
|
218
|
+
pythonnet resolves a constructor overload on every call, about 30 microseconds for the
|
|
219
|
+
``Matrix`` constructors; invoking the one ``ConstructorInfo``, found once per process, runs
|
|
220
|
+
the same constructor at about a quarter of that cost. Both constructors throw only for a null
|
|
221
|
+
argument, and the callers pass a new array, so no exception reaches the reflection wrapper.
|
|
222
|
+
"""
|
|
223
|
+
invoke = _CONSTRUCTORS.get(name)
|
|
224
|
+
if invoke is None:
|
|
225
|
+
bound = _bound() # starts the runtime before the System import
|
|
226
|
+
import clr # noqa: PLC0415
|
|
227
|
+
import System # noqa: PLC0415
|
|
228
|
+
|
|
229
|
+
owner = getattr(_linear_algebra(), name)
|
|
230
|
+
argument_type = clr.GetClrType(bound["doubles_matrix"] if name == "Matrix" else bound["doubles"])
|
|
231
|
+
constructor = clr.GetClrType(owner).GetConstructor(System.Array[System.Type]([argument_type]))
|
|
232
|
+
invoke = _CONSTRUCTORS[name] = constructor.Invoke.Overloads[bound["objects"]]
|
|
233
|
+
return invoke(_bound()["objects"]([argument]))
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def _numerics_vector(values: Any, *, unwrap: bool = False, pass_through: bool = False) -> Any:
|
|
237
|
+
"""Return a Numerics ``Vector`` for ``values``, by default a new one copied from a 1-D array.
|
|
238
|
+
|
|
239
|
+
With ``unwrap``, a wrapper whose ``net`` attribute holds a Numerics ``Vector`` (such as
|
|
240
|
+
:class:`pynumerics.linalg.Vector`) gives that vector, not a copy. With ``pass_through``, a
|
|
241
|
+
Numerics ``Vector`` is returned as it is, None gives None (a null reference), and any other
|
|
242
|
+
input is flattened before the copy. Otherwise the values are copied with :func:`to_net_doubles`.
|
|
243
|
+
"""
|
|
244
|
+
linear_algebra = _linear_algebra()
|
|
245
|
+
if unwrap and isinstance(getattr(values, "net", None), linear_algebra.Vector):
|
|
246
|
+
return values.net
|
|
247
|
+
if pass_through:
|
|
248
|
+
if values is None or isinstance(values, linear_algebra.Vector):
|
|
249
|
+
return values
|
|
250
|
+
values = np.ravel(np.asarray(values, dtype=np.float64))
|
|
251
|
+
return _construct("Vector", to_net_doubles(values))
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def to_numpy_doubles(values: Any) -> np.ndarray:
|
|
255
|
+
"""Copy a ``System.Double[]`` into a new float64 NumPy array, preserving every bit.
|
|
256
|
+
|
|
257
|
+
The result is always a copy, so it never aliases .NET state. A ``System.Double[]`` is read
|
|
258
|
+
with one copy through the buffer protocol. A .NET collection of doubles that is not an array
|
|
259
|
+
(``List<double>``, ``ReadOnlyCollection<double>``) is first copied into a ``System.Double[]``
|
|
260
|
+
with one ``ICollection<double>.CopyTo`` call, never element by element.
|
|
261
|
+
|
|
262
|
+
Parameters
|
|
263
|
+
----------
|
|
264
|
+
values : System.Double[] or System.Collections.Generic.ICollection[System.Double]
|
|
265
|
+
.NET array or collection to copy.
|
|
266
|
+
|
|
267
|
+
Returns
|
|
268
|
+
-------
|
|
269
|
+
numpy.ndarray
|
|
270
|
+
A new one-dimensional float64 array with one element per value.
|
|
271
|
+
"""
|
|
272
|
+
bound = _bound()
|
|
273
|
+
doubles = bound["doubles"]
|
|
274
|
+
if type(values) is not doubles:
|
|
275
|
+
# An interface wrapper (IList<double>) exposes the object behind it.
|
|
276
|
+
values = getattr(values, "__implementation__", values)
|
|
277
|
+
if type(values) is doubles:
|
|
278
|
+
with memoryview(values) as view:
|
|
279
|
+
return np.frombuffer(view, dtype=np.float64).copy()
|
|
280
|
+
if not hasattr(values, "Length"):
|
|
281
|
+
array = doubles(int(values.Count))
|
|
282
|
+
values.CopyTo(array, 0)
|
|
283
|
+
return _read(array, np.float64)
|
|
284
|
+
length = int(values.Length)
|
|
285
|
+
out = np.empty(length, dtype=np.float64)
|
|
286
|
+
if length:
|
|
287
|
+
bound["doubles_out"](values, 0, bound["pointer"](out.ctypes.data), length)
|
|
288
|
+
return out
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
def to_net_matrix(values: Any) -> Any:
|
|
292
|
+
"""Copy a two-dimensional array into a new ``System.Double[,]``, preserving every bit.
|
|
293
|
+
|
|
294
|
+
Parameters
|
|
295
|
+
----------
|
|
296
|
+
values : array_like
|
|
297
|
+
Values to copy, two-dimensional after conversion to C-order float64.
|
|
298
|
+
|
|
299
|
+
Returns
|
|
300
|
+
-------
|
|
301
|
+
System.Double[,]
|
|
302
|
+
A new rectangular .NET array with the same shape.
|
|
303
|
+
|
|
304
|
+
Raises
|
|
305
|
+
------
|
|
306
|
+
ValueError
|
|
307
|
+
If ``values`` is not two-dimensional after conversion.
|
|
308
|
+
"""
|
|
309
|
+
array = np.ascontiguousarray(values, dtype=np.float64)
|
|
310
|
+
if array.ndim != 2:
|
|
311
|
+
raise ValueError(f"Expected a 2-D array; got shape {array.shape}.")
|
|
312
|
+
return _net_rectangular(array)
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
def _numerics_matrix(values: Any, *, unwrap: bool = False, pass_through: bool = False) -> Any:
|
|
316
|
+
"""Return a Numerics ``Matrix`` for ``values``, by default a new one copied from a 2-D array.
|
|
317
|
+
|
|
318
|
+
With ``unwrap``, a wrapper whose ``net`` attribute holds a Numerics ``Matrix`` (such as
|
|
319
|
+
:class:`pynumerics.linalg.Matrix`) gives that matrix, not a copy. With ``pass_through``, a
|
|
320
|
+
Numerics ``Matrix`` is returned as it is and any other input is promoted to at least two
|
|
321
|
+
dimensions before the copy, so a scalar gives a 1 x 1 matrix. Otherwise the values are copied
|
|
322
|
+
with :func:`to_net_matrix`.
|
|
323
|
+
"""
|
|
324
|
+
linear_algebra = _linear_algebra()
|
|
325
|
+
if unwrap and isinstance(getattr(values, "net", None), linear_algebra.Matrix):
|
|
326
|
+
return values.net
|
|
327
|
+
if pass_through:
|
|
328
|
+
if isinstance(values, linear_algebra.Matrix):
|
|
329
|
+
return values
|
|
330
|
+
values = np.atleast_2d(np.asarray(values, dtype=np.float64))
|
|
331
|
+
return _construct("Matrix", to_net_matrix(values))
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
def _parameter_set_arrays(sets: Any) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
|
|
335
|
+
"""Read a .NET ``ParameterSet[]`` into ``(values, fitness, weight)`` arrays, one row or entry per set.
|
|
336
|
+
|
|
337
|
+
``values`` has one row per set and as many columns as the longest set. A set without values
|
|
338
|
+
(a null ``Values`` array) gives a row of NaN, and a shorter set is padded with NaN, so every
|
|
339
|
+
value Numerics returned is kept. Numerics fills a failed replicate with NaN values of the full
|
|
340
|
+
length, so such a row reads as NaN throughout.
|
|
341
|
+
"""
|
|
342
|
+
count = len(sets)
|
|
343
|
+
width = max((len(item.Values) for item in sets if item.Values is not None), default=0)
|
|
344
|
+
values = np.full((count, width), np.nan)
|
|
345
|
+
fitness, weight = np.empty(count), np.empty(count)
|
|
346
|
+
for index, item in enumerate(sets):
|
|
347
|
+
if item.Values is not None:
|
|
348
|
+
row = to_numpy_doubles(item.Values)
|
|
349
|
+
values[index, :row.size] = row
|
|
350
|
+
fitness[index], weight[index] = float(item.Fitness), float(item.Weight)
|
|
351
|
+
return values, fitness, weight
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
def to_numpy_matrix(values: Any) -> np.ndarray:
|
|
355
|
+
"""Copy a ``System.Double[,]`` into a new two-dimensional float64 array, preserving every bit.
|
|
356
|
+
|
|
357
|
+
Parameters
|
|
358
|
+
----------
|
|
359
|
+
values : System.Double[,]
|
|
360
|
+
Rectangular .NET array to copy.
|
|
361
|
+
|
|
362
|
+
Returns
|
|
363
|
+
-------
|
|
364
|
+
numpy.ndarray
|
|
365
|
+
A new C-order float64 array of shape ``(values.GetLength(0), values.GetLength(1))``.
|
|
366
|
+
|
|
367
|
+
Raises
|
|
368
|
+
------
|
|
369
|
+
ValueError
|
|
370
|
+
If ``values`` is not a rank-2 array.
|
|
371
|
+
"""
|
|
372
|
+
bound = _bound()
|
|
373
|
+
if type(values) is bound["doubles_matrix"]:
|
|
374
|
+
return _read(values, np.float64)
|
|
375
|
+
System, _ = _clr()
|
|
376
|
+
if int(values.Rank) != 2:
|
|
377
|
+
raise ValueError(f"Expected a rank-2 .NET array; got rank {int(values.Rank)}.")
|
|
378
|
+
rows, cols = int(values.GetLength(0)), int(values.GetLength(1))
|
|
379
|
+
size = rows * cols
|
|
380
|
+
if size == 0:
|
|
381
|
+
return np.empty((rows, cols), dtype=np.float64)
|
|
382
|
+
if values.GetType().GetElementType() == bound["double_type"]:
|
|
383
|
+
return _read(values, np.float64)
|
|
384
|
+
flat = System.Array.CreateInstance(System.Double, size)
|
|
385
|
+
System.Buffer.BlockCopy(values, 0, flat, 0, size * 8)
|
|
386
|
+
return to_numpy_doubles(flat).reshape((rows, cols))
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
def _range_error(label: str, minimum: int, maximum: int, low: object, high: object) -> OverflowError:
|
|
390
|
+
"""Return the error for values from ``low`` to ``high`` that leave the ``label`` range."""
|
|
391
|
+
return OverflowError(f"Values must lie in the {label} range [{minimum}, {maximum}]; got values from {low} to "
|
|
392
|
+
f"{high}.")
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
def _checked_integers(values: Any, dtype: type, label: str, minimum: int, maximum: int) -> np.ndarray:
|
|
396
|
+
"""Convert values to a contiguous integer array of ``dtype``, refusing any value outside its range.
|
|
397
|
+
|
|
398
|
+
Booleans cross as 0 and 1 and floating-point values truncate toward zero; a value outside
|
|
399
|
+
``[minimum, maximum]``, or a floating-point value that is not finite, raises ``OverflowError``
|
|
400
|
+
instead of wrapping. Complex, date, time, text and other values raise ``TypeError``.
|
|
401
|
+
"""
|
|
402
|
+
source = np.asarray(values)
|
|
403
|
+
kind = source.dtype.kind
|
|
404
|
+
if kind == "O":
|
|
405
|
+
items = source.ravel().tolist()
|
|
406
|
+
if not all(isinstance(item, (int, np.integer)) for item in items):
|
|
407
|
+
raise TypeError(f"Values must be booleans, integers, or floating-point numbers to cross as {label}; got "
|
|
408
|
+
"an object array holding another type.")
|
|
409
|
+
if items and (min(items) < minimum or max(items) > maximum):
|
|
410
|
+
raise _range_error(label, minimum, maximum, min(items), max(items))
|
|
411
|
+
return np.ascontiguousarray(np.array(items, dtype=np.int64).reshape(source.shape), dtype=dtype)
|
|
412
|
+
if kind not in "biuf":
|
|
413
|
+
raise TypeError(f"Values must be booleans, integers, or floating-point numbers to cross as {label}; got dtype "
|
|
414
|
+
f"{source.dtype}.")
|
|
415
|
+
if kind in "iuf" and source.size:
|
|
416
|
+
if kind == "f":
|
|
417
|
+
truncated = np.trunc(source)
|
|
418
|
+
low, high = float(truncated.min()), float(truncated.max())
|
|
419
|
+
if not (math.isfinite(low) and math.isfinite(high)):
|
|
420
|
+
raise OverflowError(f"Values must lie in the {label} range [{minimum}, {maximum}]; "
|
|
421
|
+
"got a value that is not finite.")
|
|
422
|
+
else:
|
|
423
|
+
low, high = int(source.min()), int(source.max())
|
|
424
|
+
if low < minimum or high > maximum:
|
|
425
|
+
raise _range_error(label, minimum, maximum, low, high)
|
|
426
|
+
return np.ascontiguousarray(source, dtype=dtype)
|
|
427
|
+
|
|
428
|
+
|
|
429
|
+
def _int32_array(values: Any) -> np.ndarray:
|
|
430
|
+
"""Convert values to a contiguous int32 array, refusing any value outside the int32 range."""
|
|
431
|
+
return _checked_integers(values, np.int32, "Int32", _INT32_MIN, _INT32_MAX)
|
|
432
|
+
|
|
433
|
+
|
|
434
|
+
def to_net_ints(values: Any) -> Any:
|
|
435
|
+
"""Copy a one-dimensional array into a new ``System.Int32[]``, preserving every bit.
|
|
436
|
+
|
|
437
|
+
A value outside the int32 range raises ``OverflowError`` instead of wrapping,
|
|
438
|
+
whether it arrives as a Python integer, a wider NumPy integer, or a floating-point
|
|
439
|
+
value. Floating-point values inside the range truncate toward zero. Booleans cross
|
|
440
|
+
as 0 and 1; complex, date, time, text and other values are refused.
|
|
441
|
+
|
|
442
|
+
Parameters
|
|
443
|
+
----------
|
|
444
|
+
values : array_like
|
|
445
|
+
Integer values to copy, one-dimensional after conversion.
|
|
446
|
+
|
|
447
|
+
Returns
|
|
448
|
+
-------
|
|
449
|
+
System.Int32[]
|
|
450
|
+
A new .NET array holding the int32 values.
|
|
451
|
+
|
|
452
|
+
Raises
|
|
453
|
+
------
|
|
454
|
+
OverflowError
|
|
455
|
+
If a value lies outside the int32 range or is not finite; raised before the
|
|
456
|
+
runtime starts.
|
|
457
|
+
TypeError
|
|
458
|
+
If the values are not booleans, integers, or floating-point numbers.
|
|
459
|
+
ValueError
|
|
460
|
+
If ``values`` is not one-dimensional after conversion.
|
|
461
|
+
"""
|
|
462
|
+
array = _int32_array(values)
|
|
463
|
+
bound = _bound()
|
|
464
|
+
if array.ndim != 1:
|
|
465
|
+
raise ValueError(f"Expected a 1-D array; got shape {array.shape}.")
|
|
466
|
+
return _fill(bound["ints"](array.size), array)
|
|
467
|
+
|
|
468
|
+
|
|
469
|
+
def to_numpy_ints(values: Any) -> np.ndarray:
|
|
470
|
+
"""Copy a ``System.Int32[]`` into a new int32 NumPy array, preserving every bit.
|
|
471
|
+
|
|
472
|
+
Parameters
|
|
473
|
+
----------
|
|
474
|
+
values : System.Int32[]
|
|
475
|
+
.NET array to copy.
|
|
476
|
+
|
|
477
|
+
Returns
|
|
478
|
+
-------
|
|
479
|
+
numpy.ndarray
|
|
480
|
+
A new one-dimensional int32 array with ``values.Length`` elements.
|
|
481
|
+
"""
|
|
482
|
+
bound = _bound()
|
|
483
|
+
if type(values) is bound["ints"]:
|
|
484
|
+
return _read(values, np.int32)
|
|
485
|
+
length = int(values.Length)
|
|
486
|
+
out = np.empty(length, dtype=np.int32)
|
|
487
|
+
if length:
|
|
488
|
+
bound["ints_out"](values, 0, bound["pointer"](out.ctypes.data), length)
|
|
489
|
+
return out
|
|
490
|
+
|
|
491
|
+
|
|
492
|
+
def to_net_longs(values: Any) -> Any:
|
|
493
|
+
"""Copy a one-dimensional array into a new ``System.Int64[]``, preserving every bit.
|
|
494
|
+
|
|
495
|
+
A value outside the int64 range raises ``OverflowError`` instead of wrapping, whether
|
|
496
|
+
it arrives as a Python integer, an unsigned 64-bit NumPy integer, or a floating-point
|
|
497
|
+
value. Floating-point values inside the range truncate toward zero. Booleans cross as
|
|
498
|
+
0 and 1; complex, date, time, text and other values are refused.
|
|
499
|
+
|
|
500
|
+
Parameters
|
|
501
|
+
----------
|
|
502
|
+
values : array_like
|
|
503
|
+
Integer values to copy, one-dimensional after conversion.
|
|
504
|
+
|
|
505
|
+
Returns
|
|
506
|
+
-------
|
|
507
|
+
System.Int64[]
|
|
508
|
+
A new .NET array holding the int64 values.
|
|
509
|
+
|
|
510
|
+
Raises
|
|
511
|
+
------
|
|
512
|
+
OverflowError
|
|
513
|
+
If a value lies outside the int64 range or is not finite; raised before the
|
|
514
|
+
runtime starts.
|
|
515
|
+
TypeError
|
|
516
|
+
If the values are not booleans, integers, or floating-point numbers.
|
|
517
|
+
ValueError
|
|
518
|
+
If ``values`` is not one-dimensional after conversion.
|
|
519
|
+
"""
|
|
520
|
+
array = _checked_integers(values, np.int64, "Int64", _INT64_MIN, _INT64_MAX)
|
|
521
|
+
bound = _bound()
|
|
522
|
+
if array.ndim != 1:
|
|
523
|
+
raise ValueError(f"Expected a 1-D array; got shape {array.shape}.")
|
|
524
|
+
return _fill(bound["longs"](array.size), array)
|
|
525
|
+
|
|
526
|
+
|
|
527
|
+
def to_numpy_longs(values: Any) -> np.ndarray:
|
|
528
|
+
"""Copy a ``System.Int64[]`` into a new int64 NumPy array, preserving every bit.
|
|
529
|
+
|
|
530
|
+
Parameters
|
|
531
|
+
----------
|
|
532
|
+
values : System.Int64[]
|
|
533
|
+
.NET array to copy, for example the ticks of a ``DateTime`` series.
|
|
534
|
+
|
|
535
|
+
Returns
|
|
536
|
+
-------
|
|
537
|
+
numpy.ndarray
|
|
538
|
+
A new one-dimensional int64 array with ``values.Length`` elements.
|
|
539
|
+
"""
|
|
540
|
+
bound = _bound()
|
|
541
|
+
if type(values) is bound["longs"]:
|
|
542
|
+
return _read(values, np.int64)
|
|
543
|
+
length = int(values.Length)
|
|
544
|
+
out = np.empty(length, dtype=np.int64)
|
|
545
|
+
if length:
|
|
546
|
+
bound["longs_out"](values, 0, bound["pointer"](out.ctypes.data), length)
|
|
547
|
+
return out
|
|
548
|
+
|
|
549
|
+
|
|
550
|
+
def _bytes_to_net(array: np.ndarray, kind: str) -> Any:
|
|
551
|
+
"""Copy a contiguous one-byte-per-element array into a new .NET array of ``kind`` through one byte copy."""
|
|
552
|
+
bound = _bound()
|
|
553
|
+
target = bound[kind](array.size)
|
|
554
|
+
if kind != "bools":
|
|
555
|
+
return _fill(target, array)
|
|
556
|
+
if array.size:
|
|
557
|
+
raw = _fill(bound["bytes"](array.size), array.view(np.uint8))
|
|
558
|
+
bound["block_copy"](raw, 0, target, 0, array.size)
|
|
559
|
+
return target
|
|
560
|
+
|
|
561
|
+
|
|
562
|
+
def _bytes_from_net(values: Any, dtype: type) -> np.ndarray:
|
|
563
|
+
"""Copy a one-byte-per-element .NET array into a new NumPy array of ``dtype`` through one byte copy."""
|
|
564
|
+
bound = _bound()
|
|
565
|
+
if type(values) is bound["bytes"] or type(values) is bound["sbytes"]:
|
|
566
|
+
return _read(values, dtype)
|
|
567
|
+
length = int(values.Length)
|
|
568
|
+
raw = bound["bytes"](length)
|
|
569
|
+
if length:
|
|
570
|
+
bound["block_copy"](values, 0, raw, 0, length)
|
|
571
|
+
return _read(raw, dtype)
|
|
572
|
+
|
|
573
|
+
|
|
574
|
+
def to_net_sbytes(values: Any) -> Any:
|
|
575
|
+
"""Copy a one-dimensional array into a new ``System.SByte[]``, preserving every bit.
|
|
576
|
+
|
|
577
|
+
A value outside the int8 range raises ``OverflowError`` instead of wrapping.
|
|
578
|
+
Floating-point values inside the range truncate toward zero. Booleans cross as 0 and 1;
|
|
579
|
+
complex, date, time, text and other values are refused.
|
|
580
|
+
|
|
581
|
+
Parameters
|
|
582
|
+
----------
|
|
583
|
+
values : array_like
|
|
584
|
+
Integer values to copy, one-dimensional after conversion.
|
|
585
|
+
|
|
586
|
+
Returns
|
|
587
|
+
-------
|
|
588
|
+
System.SByte[]
|
|
589
|
+
A new .NET array holding the int8 values.
|
|
590
|
+
|
|
591
|
+
Raises
|
|
592
|
+
------
|
|
593
|
+
OverflowError
|
|
594
|
+
If a value lies outside the int8 range or is not finite; raised before the
|
|
595
|
+
runtime starts.
|
|
596
|
+
TypeError
|
|
597
|
+
If the values are not booleans, integers, or floating-point numbers.
|
|
598
|
+
ValueError
|
|
599
|
+
If ``values`` is not one-dimensional after conversion.
|
|
600
|
+
"""
|
|
601
|
+
array = _checked_integers(values, np.int8, "SByte", _INT8_MIN, _INT8_MAX)
|
|
602
|
+
if array.ndim != 1:
|
|
603
|
+
raise ValueError(f"Expected a 1-D array; got shape {array.shape}.")
|
|
604
|
+
return _bytes_to_net(array, "sbytes")
|
|
605
|
+
|
|
606
|
+
|
|
607
|
+
def to_numpy_sbytes(values: Any) -> np.ndarray:
|
|
608
|
+
"""Copy a ``System.SByte[]`` into a new int8 NumPy array, preserving every bit.
|
|
609
|
+
|
|
610
|
+
Parameters
|
|
611
|
+
----------
|
|
612
|
+
values : System.SByte[]
|
|
613
|
+
.NET array to copy.
|
|
614
|
+
|
|
615
|
+
Returns
|
|
616
|
+
-------
|
|
617
|
+
numpy.ndarray
|
|
618
|
+
A new one-dimensional int8 array with ``values.Length`` elements.
|
|
619
|
+
"""
|
|
620
|
+
return _bytes_from_net(values, np.int8)
|
|
621
|
+
|
|
622
|
+
|
|
623
|
+
def to_net_bools(values: Any) -> Any:
|
|
624
|
+
"""Copy a one-dimensional boolean array into a new ``System.Boolean[]``.
|
|
625
|
+
|
|
626
|
+
Only boolean input is accepted: integers or floating-point values are refused rather
|
|
627
|
+
than interpreted as truth values. An empty input of any type gives an empty array.
|
|
628
|
+
|
|
629
|
+
Parameters
|
|
630
|
+
----------
|
|
631
|
+
values : array_like
|
|
632
|
+
Boolean values to copy, one-dimensional after conversion.
|
|
633
|
+
|
|
634
|
+
Returns
|
|
635
|
+
-------
|
|
636
|
+
System.Boolean[]
|
|
637
|
+
A new .NET array holding the values.
|
|
638
|
+
|
|
639
|
+
Raises
|
|
640
|
+
------
|
|
641
|
+
TypeError
|
|
642
|
+
If the values are not booleans; raised before the runtime starts.
|
|
643
|
+
ValueError
|
|
644
|
+
If ``values`` is not one-dimensional after conversion.
|
|
645
|
+
"""
|
|
646
|
+
source = np.asarray(values)
|
|
647
|
+
if source.dtype.kind != "b" and source.size:
|
|
648
|
+
raise TypeError(f"Values must be booleans to cross as Boolean; got dtype {source.dtype}.")
|
|
649
|
+
array = np.ascontiguousarray(source, dtype=np.bool_)
|
|
650
|
+
if array.ndim != 1:
|
|
651
|
+
raise ValueError(f"Expected a 1-D array; got shape {array.shape}.")
|
|
652
|
+
return _bytes_to_net(array, "bools")
|
|
653
|
+
|
|
654
|
+
|
|
655
|
+
def to_numpy_bools(values: Any) -> np.ndarray:
|
|
656
|
+
"""Copy a ``System.Boolean[]`` into a new boolean NumPy array.
|
|
657
|
+
|
|
658
|
+
Parameters
|
|
659
|
+
----------
|
|
660
|
+
values : System.Boolean[]
|
|
661
|
+
.NET array to copy.
|
|
662
|
+
|
|
663
|
+
Returns
|
|
664
|
+
-------
|
|
665
|
+
numpy.ndarray
|
|
666
|
+
A new one-dimensional bool array with ``values.Length`` elements; any nonzero
|
|
667
|
+
.NET byte reads as True.
|
|
668
|
+
"""
|
|
669
|
+
return _bytes_from_net(values, np.uint8) != 0
|
|
670
|
+
|
|
671
|
+
|
|
672
|
+
def to_net_singles(block: Any) -> tuple[Any, tuple[int, int, int]]:
|
|
673
|
+
"""Flatten a ``(steps, rows, columns)`` block into a ``System.Single[]`` and its shape.
|
|
674
|
+
|
|
675
|
+
The block is converted with ``numpy.ascontiguousarray(block, dtype=numpy.float32)``
|
|
676
|
+
first, which rounds float64 values to the nearest float32, and is then copied in C
|
|
677
|
+
order, so ``flat[(t * rows + r) * columns + c]`` equals ``block[t, r, c]``.
|
|
678
|
+
|
|
679
|
+
Parameters
|
|
680
|
+
----------
|
|
681
|
+
block : array_like
|
|
682
|
+
Block with shape ``(steps, rows, columns)``.
|
|
683
|
+
|
|
684
|
+
Returns
|
|
685
|
+
-------
|
|
686
|
+
flat : System.Single[]
|
|
687
|
+
A new .NET array holding the ``steps * rows * columns`` float32 values.
|
|
688
|
+
dims : tuple of int
|
|
689
|
+
The block shape ``(steps, rows, columns)``.
|
|
690
|
+
|
|
691
|
+
Raises
|
|
692
|
+
------
|
|
693
|
+
ValueError
|
|
694
|
+
If ``block`` is not three-dimensional after conversion.
|
|
695
|
+
"""
|
|
696
|
+
array = np.ascontiguousarray(block, dtype=np.float32)
|
|
697
|
+
if array.ndim != 3:
|
|
698
|
+
raise ValueError(f"Expected a (steps, rows, cols) block; got shape {array.shape}.")
|
|
699
|
+
steps, rows, cols = (int(dimension) for dimension in array.shape)
|
|
700
|
+
return _fill(_bound()["singles"](array.size), array), (steps, rows, cols)
|
|
701
|
+
|
|
702
|
+
|
|
703
|
+
def to_numpy_singles(values: Any, dims: tuple[int, int, int]) -> np.ndarray:
|
|
704
|
+
"""Copy a flat ``System.Single[]`` back into a ``(steps, rows, columns)`` float32 block.
|
|
705
|
+
|
|
706
|
+
Parameters
|
|
707
|
+
----------
|
|
708
|
+
values : System.Single[]
|
|
709
|
+
Flat C-order .NET array, as produced by :func:`to_net_singles`.
|
|
710
|
+
dims : tuple of int
|
|
711
|
+
Block shape ``(steps, rows, columns)``.
|
|
712
|
+
|
|
713
|
+
Returns
|
|
714
|
+
-------
|
|
715
|
+
numpy.ndarray
|
|
716
|
+
A new float32 array with shape ``dims``, preserving every bit.
|
|
717
|
+
|
|
718
|
+
Raises
|
|
719
|
+
------
|
|
720
|
+
ValueError
|
|
721
|
+
If ``values.Length`` differs from ``steps * rows * columns``.
|
|
722
|
+
"""
|
|
723
|
+
bound = _bound()
|
|
724
|
+
steps, rows, cols = (int(dimension) for dimension in dims)
|
|
725
|
+
length = int(values.Length)
|
|
726
|
+
if length != steps * rows * cols:
|
|
727
|
+
raise ValueError(f"Flat length {length} does not match dims {dims}.")
|
|
728
|
+
if type(values) is bound["singles"]:
|
|
729
|
+
return _read(values, np.float32).reshape((steps, rows, cols))
|
|
730
|
+
out = np.empty(length, dtype=np.float32)
|
|
731
|
+
if length:
|
|
732
|
+
bound["singles_out"](values, 0, bound["pointer"](out.ctypes.data), length)
|
|
733
|
+
return out.reshape((steps, rows, cols))
|