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/graphs.py
ADDED
|
@@ -0,0 +1,556 @@
|
|
|
1
|
+
"""Shortest paths on directed networks from ``Numerics.Mathematics.Optimization``.
|
|
2
|
+
|
|
3
|
+
:class:`Edge` and :class:`Network` describe a directed network; the ``dijkstra_*`` functions and
|
|
4
|
+
:meth:`Network.solve` return a routing table with one row per node: column 0 is the next node
|
|
5
|
+
toward the destination, column 1 the index of the edge to take and column 2 the cumulative cost
|
|
6
|
+
(float32; the costs accumulate in float64 and are rounded once). The destination row is
|
|
7
|
+
``(itself, -1, 0)`` and an unreachable node's row is ``(-1, -1, inf)``. :func:`shortest_distances` gives
|
|
8
|
+
float64 distances from several sources on a CSR graph, as ``scipy.sparse.csgraph.dijkstra`` does.
|
|
9
|
+
|
|
10
|
+
:class:`BinaryHeap` is the keyed min-heap the shortest-path solvers are built on. The knapsack
|
|
11
|
+
and dynamic programming solvers of the same namespace are in :mod:`pynumerics.optimization`.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
from dataclasses import dataclass
|
|
17
|
+
from typing import Any
|
|
18
|
+
|
|
19
|
+
import numpy as np
|
|
20
|
+
|
|
21
|
+
from pynumerics._util import _opt
|
|
22
|
+
from pynumerics.errors import translate_public
|
|
23
|
+
from pynumerics.marshal import (
|
|
24
|
+
_net_rectangular,
|
|
25
|
+
_numpy_rectangular,
|
|
26
|
+
to_net_doubles,
|
|
27
|
+
to_net_ints,
|
|
28
|
+
to_net_singles,
|
|
29
|
+
to_numpy_doubles,
|
|
30
|
+
to_numpy_ints,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _table_to_numpy(table: Any) -> np.ndarray:
|
|
35
|
+
"""Copy a .NET ``float[,]`` routing table into a ``(nodes, 3)`` float32 array, bit for bit, in one copy."""
|
|
36
|
+
return _numpy_rectangular(table, np.float32)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _table_to_net(table: Any) -> Any:
|
|
40
|
+
"""Copy a ``(nodes, 3)`` array into a new .NET ``float[,]`` (rounded to float32) in one copy."""
|
|
41
|
+
array = np.asarray(table, dtype=np.float32)
|
|
42
|
+
if array.ndim != 2:
|
|
43
|
+
raise ValueError(f"Expected a 2-D routing table; got shape {array.shape}.")
|
|
44
|
+
return _net_rectangular(array)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _singles(values: Any) -> Any:
|
|
48
|
+
"""Copy edge weights into a ``System.Single[]`` (rounded to float32)."""
|
|
49
|
+
array = np.asarray(values, dtype=np.float32).reshape(1, 1, -1)
|
|
50
|
+
return to_net_singles(array)[0]
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _path(path: Any) -> np.ndarray | None:
|
|
54
|
+
"""Copy a .NET ``List<int>`` of edge indices, or keep None."""
|
|
55
|
+
return None if path is None else to_numpy_ints(path.ToArray())
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@dataclass(frozen=True, slots=True)
|
|
59
|
+
class Edge:
|
|
60
|
+
"""A directed, weighted edge of a network (``Numerics.Mathematics.Optimization.Edge``).
|
|
61
|
+
|
|
62
|
+
Parameters
|
|
63
|
+
----------
|
|
64
|
+
from_index : int
|
|
65
|
+
The start node.
|
|
66
|
+
to_index : int
|
|
67
|
+
The end node.
|
|
68
|
+
weight : float
|
|
69
|
+
The cost of the edge (stored as float32 by Numerics); infinity makes it impassable.
|
|
70
|
+
index : int
|
|
71
|
+
The edge index reported in paths and routing tables.
|
|
72
|
+
"""
|
|
73
|
+
|
|
74
|
+
from_index: int
|
|
75
|
+
to_index: int
|
|
76
|
+
weight: float
|
|
77
|
+
index: int
|
|
78
|
+
|
|
79
|
+
@classmethod
|
|
80
|
+
def from_net(cls, net: Any) -> Edge:
|
|
81
|
+
"""Copy a .NET ``Edge``.
|
|
82
|
+
|
|
83
|
+
Parameters
|
|
84
|
+
----------
|
|
85
|
+
net : Numerics.Mathematics.Optimization.Edge
|
|
86
|
+
The .NET edge.
|
|
87
|
+
|
|
88
|
+
Returns
|
|
89
|
+
-------
|
|
90
|
+
Edge
|
|
91
|
+
The copy.
|
|
92
|
+
"""
|
|
93
|
+
return cls(int(net.FromIndex), int(net.ToIndex), float(net.Weight), int(net.Index))
|
|
94
|
+
|
|
95
|
+
def to_net(self) -> Any:
|
|
96
|
+
"""Return the .NET ``Edge``.
|
|
97
|
+
|
|
98
|
+
Returns
|
|
99
|
+
-------
|
|
100
|
+
Numerics.Mathematics.Optimization.Edge
|
|
101
|
+
A new .NET edge.
|
|
102
|
+
"""
|
|
103
|
+
return _opt().Edge(int(self.from_index), int(self.to_index), float(self.weight), int(self.index))
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def _edges(edges: Any) -> Any:
|
|
107
|
+
"""Return the edges as a .NET ``Edge[]``.
|
|
108
|
+
|
|
109
|
+
Accepts :class:`Edge` values, ``(from, to, weight)`` or ``(from, to, weight, index)`` tuples (a
|
|
110
|
+
missing index is the position), or a .NET ``Edge[]`` passed through.
|
|
111
|
+
"""
|
|
112
|
+
opt = _opt()
|
|
113
|
+
import System # noqa: PLC0415 - the runtime is running
|
|
114
|
+
|
|
115
|
+
if isinstance(edges, System.Array):
|
|
116
|
+
return edges
|
|
117
|
+
items = []
|
|
118
|
+
for position, edge in enumerate(edges):
|
|
119
|
+
if isinstance(edge, Edge):
|
|
120
|
+
items.append(edge.to_net())
|
|
121
|
+
continue
|
|
122
|
+
fields = tuple(edge)
|
|
123
|
+
index = int(fields[3]) if len(fields) > 3 else position
|
|
124
|
+
items.append(opt.Edge(int(fields[0]), int(fields[1]), float(fields[2]), index))
|
|
125
|
+
return System.Array[opt.Edge](items)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def _destinations(destinations: Any) -> Any:
|
|
129
|
+
"""Return one destination as an int or several as a ``System.Int32[]``."""
|
|
130
|
+
if np.ndim(destinations) == 0:
|
|
131
|
+
return int(destinations)
|
|
132
|
+
return to_net_ints(destinations)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def dijkstra_solve(edges: Any, destinations: Any, node_count: int = -1) -> np.ndarray:
|
|
136
|
+
"""Return the shortest-path routing table toward one or more destinations (``Dijkstra.Solve``).
|
|
137
|
+
|
|
138
|
+
Parameters
|
|
139
|
+
----------
|
|
140
|
+
edges : sequence
|
|
141
|
+
:class:`Edge` values or ``(from, to, weight[, index])`` tuples.
|
|
142
|
+
destinations : int or array_like of int
|
|
143
|
+
One destination node, or several; each node routes to its cheapest one, earlier destinations
|
|
144
|
+
winning exact ties.
|
|
145
|
+
node_count : int, optional
|
|
146
|
+
Number of nodes; -1, the default, derives it from the edges.
|
|
147
|
+
|
|
148
|
+
Returns
|
|
149
|
+
-------
|
|
150
|
+
numpy.ndarray
|
|
151
|
+
The ``(nodes, 3)`` float32 routing table.
|
|
152
|
+
"""
|
|
153
|
+
return _table_to_numpy(_opt().Dijkstra.Solve(_edges(edges), _destinations(destinations), int(node_count)))
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def dijkstra_solve_nearest(edges: Any, destinations: Any, node_count: int = -1) -> np.ndarray:
|
|
157
|
+
"""Return the routing table to the nearest destination in one multi-source pass (``Dijkstra.SolveNearest``).
|
|
158
|
+
|
|
159
|
+
Costs equal those of :func:`dijkstra_solve`; exact ties may route differently.
|
|
160
|
+
|
|
161
|
+
Parameters
|
|
162
|
+
----------
|
|
163
|
+
edges : sequence
|
|
164
|
+
:class:`Edge` values or ``(from, to, weight[, index])`` tuples.
|
|
165
|
+
destinations : array_like of int
|
|
166
|
+
The destination nodes, at least one.
|
|
167
|
+
node_count : int, optional
|
|
168
|
+
Number of nodes; -1, the default, derives it from the edges.
|
|
169
|
+
|
|
170
|
+
Returns
|
|
171
|
+
-------
|
|
172
|
+
numpy.ndarray
|
|
173
|
+
The ``(nodes, 3)`` float32 routing table.
|
|
174
|
+
"""
|
|
175
|
+
return _table_to_numpy(_opt().Dijkstra.SolveNearest(_edges(edges), to_net_ints(np.atleast_1d(destinations)),
|
|
176
|
+
int(node_count)))
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def dijkstra_path_exists(table: Any, node: int) -> bool:
|
|
180
|
+
"""Return whether a node reaches the destination in a routing table (``Dijkstra.PathExists``).
|
|
181
|
+
|
|
182
|
+
Parameters
|
|
183
|
+
----------
|
|
184
|
+
table : array_like
|
|
185
|
+
A ``(nodes, 3)`` routing table.
|
|
186
|
+
node : int
|
|
187
|
+
The node.
|
|
188
|
+
|
|
189
|
+
Returns
|
|
190
|
+
-------
|
|
191
|
+
bool
|
|
192
|
+
True when the node's cost is finite.
|
|
193
|
+
"""
|
|
194
|
+
return bool(_opt().Dijkstra.PathExists(_table_to_net(table), int(node)))
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def dijkstra_get_path(table: Any, start: int) -> np.ndarray | None:
|
|
198
|
+
"""Return the edge indices from a node to the destination (``Dijkstra.GetPath``).
|
|
199
|
+
|
|
200
|
+
Parameters
|
|
201
|
+
----------
|
|
202
|
+
table : array_like
|
|
203
|
+
A ``(nodes, 3)`` routing table.
|
|
204
|
+
start : int
|
|
205
|
+
The start node.
|
|
206
|
+
|
|
207
|
+
Returns
|
|
208
|
+
-------
|
|
209
|
+
numpy.ndarray or None
|
|
210
|
+
The ordered int32 edge indices (empty at the destination), or None when unreachable.
|
|
211
|
+
"""
|
|
212
|
+
return _path(_opt().Dijkstra.GetPath(_table_to_net(table), int(start)))
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def dijkstra_try_get_path(table: Any, start: int) -> tuple[bool, np.ndarray, float]:
|
|
216
|
+
"""Walk a routing table from a node, returning the path and its cost (``Dijkstra.TryGetPath``).
|
|
217
|
+
|
|
218
|
+
Parameters
|
|
219
|
+
----------
|
|
220
|
+
table : array_like
|
|
221
|
+
A ``(nodes, 3)`` routing table.
|
|
222
|
+
start : int
|
|
223
|
+
The start node.
|
|
224
|
+
|
|
225
|
+
Returns
|
|
226
|
+
-------
|
|
227
|
+
tuple
|
|
228
|
+
``(found, edge_indices, total_cost)``; an unreachable node gives ``(False, [], inf)``.
|
|
229
|
+
"""
|
|
230
|
+
found, path, cost = _opt().Dijkstra.TryGetPath(_table_to_net(table), int(start), None, 0.0)
|
|
231
|
+
return bool(found), to_numpy_ints(path.ToArray()), float(cost)
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def shortest_distances(graph: Any, sources: Any, limit: float = np.inf, *, return_predecessors: bool = False,
|
|
235
|
+
return_nearest_source: bool = False) -> Any:
|
|
236
|
+
"""Return the double-precision distance of every node from its nearest source (``Dijkstra.ShortestDistances``).
|
|
237
|
+
|
|
238
|
+
The graph is directed, in compressed sparse row form: row ``n`` holds the edges leaving node ``n``. The
|
|
239
|
+
result matches ``scipy.sparse.csgraph.dijkstra(graph, indices=sources, min_only=True, limit=limit)``;
|
|
240
|
+
where SciPy reports -9999 for a missing predecessor or source, this function reports -1.
|
|
241
|
+
|
|
242
|
+
Parameters
|
|
243
|
+
----------
|
|
244
|
+
graph : sparse matrix or tuple
|
|
245
|
+
A square SciPy sparse matrix or array (converted with ``tocsr()``; any other object with ``shape``,
|
|
246
|
+
``indptr``, ``indices`` and ``data`` is read as CSR), or a ``(row_start, to_node, weight)`` tuple of
|
|
247
|
+
the CSR arrays. Weights must be non-negative; infinity makes
|
|
248
|
+
an edge impassable.
|
|
249
|
+
sources : int or array_like of int
|
|
250
|
+
The source nodes, each at distance zero. Integral floats are accepted; other floats raise ValueError.
|
|
251
|
+
limit : float, optional
|
|
252
|
+
The largest distance computed; farther nodes are reported unreachable. Default infinity.
|
|
253
|
+
return_predecessors : bool, optional
|
|
254
|
+
Also return the predecessor of each node on its shortest path (-1 for a source or an unreachable node).
|
|
255
|
+
return_nearest_source : bool, optional
|
|
256
|
+
Also return the source each shortest path starts from (-1 for an unreachable node).
|
|
257
|
+
|
|
258
|
+
Returns
|
|
259
|
+
-------
|
|
260
|
+
numpy.ndarray or tuple
|
|
261
|
+
The float64 distances, infinity where unreachable; with either flag, a tuple ``(distances, ...)``
|
|
262
|
+
holding the requested int32 arrays in the order predecessors, nearest source.
|
|
263
|
+
|
|
264
|
+
Raises
|
|
265
|
+
------
|
|
266
|
+
ValueError
|
|
267
|
+
If the matrix is not square, a source is not integral, or the CSR arrays are inconsistent (raised by
|
|
268
|
+
Numerics).
|
|
269
|
+
TypeError
|
|
270
|
+
If the sources are not numbers.
|
|
271
|
+
"""
|
|
272
|
+
dijkstra = _opt().Dijkstra
|
|
273
|
+
import System # noqa: PLC0415 - the namespace import above starts the runtime
|
|
274
|
+
|
|
275
|
+
if isinstance(graph, tuple):
|
|
276
|
+
row_start, to_node, weight = graph
|
|
277
|
+
node_count = len(row_start) - 1
|
|
278
|
+
else:
|
|
279
|
+
rows, cols = graph.shape
|
|
280
|
+
if rows != cols:
|
|
281
|
+
raise ValueError(f"The graph matrix must be square; got shape {graph.shape}.")
|
|
282
|
+
if hasattr(graph, "tocsr"):
|
|
283
|
+
# A CSC (or other) sparse matrix also has indptr/indices/data, which would read as the transpose.
|
|
284
|
+
graph = graph.tocsr()
|
|
285
|
+
row_start, to_node, weight, node_count = graph.indptr, graph.indices, graph.data, rows
|
|
286
|
+
source_array = np.atleast_1d(np.asarray(sources))
|
|
287
|
+
if source_array.dtype.kind == "f":
|
|
288
|
+
if not np.all(np.isfinite(source_array) & (source_array == np.floor(source_array))):
|
|
289
|
+
raise ValueError("The sources must be integral node indices.")
|
|
290
|
+
elif source_array.dtype.kind not in "iu":
|
|
291
|
+
raise TypeError(f"The sources must be integer node indices; got dtype {source_array.dtype}.")
|
|
292
|
+
# The typed array constructor binds once; Array.CreateInstance resolves its overloads on every call.
|
|
293
|
+
ints = System.Array[System.Int32]
|
|
294
|
+
predecessors = ints(node_count) if return_predecessors else None
|
|
295
|
+
nearest = ints(node_count) if return_nearest_source else None
|
|
296
|
+
distances = to_numpy_doubles(dijkstra.ShortestDistances(
|
|
297
|
+
int(node_count), to_net_ints(row_start), to_net_ints(to_node), to_net_doubles(weight),
|
|
298
|
+
to_net_ints(source_array), float(limit), predecessors, nearest))
|
|
299
|
+
if not (return_predecessors or return_nearest_source):
|
|
300
|
+
return distances
|
|
301
|
+
result = [distances]
|
|
302
|
+
if return_predecessors:
|
|
303
|
+
result.append(to_numpy_ints(predecessors))
|
|
304
|
+
if return_nearest_source:
|
|
305
|
+
result.append(to_numpy_ints(nearest))
|
|
306
|
+
return tuple(result)
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
class Network:
|
|
310
|
+
"""A compiled directed network with fixed destinations (``Numerics.Mathematics.Optimization.Network``).
|
|
311
|
+
|
|
312
|
+
Parameters
|
|
313
|
+
----------
|
|
314
|
+
edges : sequence
|
|
315
|
+
:class:`Edge` values or ``(from, to, weight[, index])`` tuples.
|
|
316
|
+
destination_indices : array_like of int
|
|
317
|
+
The destination nodes.
|
|
318
|
+
|
|
319
|
+
Attributes
|
|
320
|
+
----------
|
|
321
|
+
net : Numerics.Mathematics.Optimization.Network
|
|
322
|
+
The wrapped .NET network.
|
|
323
|
+
"""
|
|
324
|
+
|
|
325
|
+
def __init__(self, edges: Any, destination_indices: Any) -> None:
|
|
326
|
+
self.net = _opt().Network(_edges(edges), to_net_ints(np.atleast_1d(destination_indices)))
|
|
327
|
+
self._table: Any = None
|
|
328
|
+
|
|
329
|
+
@property
|
|
330
|
+
def destination_indices(self) -> np.ndarray:
|
|
331
|
+
"""The destination nodes (``DestinationIndices``)."""
|
|
332
|
+
return to_numpy_ints(self.net.DestinationIndices)
|
|
333
|
+
|
|
334
|
+
@property
|
|
335
|
+
def incoming_edges(self) -> list[list[Edge]]:
|
|
336
|
+
"""The incoming edges of each node, in edge order (``IncomingEdges``)."""
|
|
337
|
+
return [[Edge.from_net(e) for e in node] for node in self.net.IncomingEdges]
|
|
338
|
+
|
|
339
|
+
@property
|
|
340
|
+
def outgoing_edges(self) -> list[list[Edge]]:
|
|
341
|
+
"""The outgoing edges of each node, in edge order (``OutgoingEdges``)."""
|
|
342
|
+
return [[Edge.from_net(e) for e in node] for node in self.net.OutgoingEdges]
|
|
343
|
+
|
|
344
|
+
@property
|
|
345
|
+
def node_count(self) -> int:
|
|
346
|
+
"""The number of nodes (``NodeCount``)."""
|
|
347
|
+
return int(self.net.NodeCount)
|
|
348
|
+
|
|
349
|
+
def _reused_table(self) -> Any:
|
|
350
|
+
"""Return the .NET table the reuse overloads fill, allocated once per network."""
|
|
351
|
+
if self._table is None:
|
|
352
|
+
import System # noqa: PLC0415 - the runtime is running
|
|
353
|
+
|
|
354
|
+
self._table = System.Array.CreateInstance(System.Single, self.node_count, 3)
|
|
355
|
+
return self._table
|
|
356
|
+
|
|
357
|
+
def solve(self, destinations: Any = None, *, edge_weights: Any = None, reuse: bool = False) -> np.ndarray:
|
|
358
|
+
"""Return the routing table toward the destinations (``Network.Solve``).
|
|
359
|
+
|
|
360
|
+
Parameters
|
|
361
|
+
----------
|
|
362
|
+
destinations : int or array_like of int, optional
|
|
363
|
+
Destinations to solve for; None uses the network's. Not allowed with ``edge_weights``.
|
|
364
|
+
edge_weights : array_like, optional
|
|
365
|
+
Custom weights, one per edge in constructor order, toward the network's destinations.
|
|
366
|
+
reuse : bool, optional
|
|
367
|
+
With ``edge_weights``, fill one .NET table kept by the network (the allocation-free
|
|
368
|
+
``Solve(float[], float[,])`` overload) instead of a new one; default False.
|
|
369
|
+
|
|
370
|
+
Returns
|
|
371
|
+
-------
|
|
372
|
+
numpy.ndarray
|
|
373
|
+
The ``(nodes, 3)`` float32 routing table.
|
|
374
|
+
|
|
375
|
+
Raises
|
|
376
|
+
------
|
|
377
|
+
ValueError
|
|
378
|
+
If both ``destinations`` and ``edge_weights`` are given.
|
|
379
|
+
"""
|
|
380
|
+
if edge_weights is not None:
|
|
381
|
+
if destinations is not None:
|
|
382
|
+
raise ValueError("Custom edge weights solve toward the network's destinations; omit destinations.")
|
|
383
|
+
if reuse:
|
|
384
|
+
table = self._reused_table()
|
|
385
|
+
self.net.Solve(_singles(edge_weights), table)
|
|
386
|
+
return _table_to_numpy(table)
|
|
387
|
+
return _table_to_numpy(self.net.Solve(_singles(edge_weights)))
|
|
388
|
+
if destinations is None:
|
|
389
|
+
destinations = self.net.DestinationIndices
|
|
390
|
+
return _table_to_numpy(self.net.Solve(destinations))
|
|
391
|
+
return _table_to_numpy(self.net.Solve(_destinations(destinations)))
|
|
392
|
+
|
|
393
|
+
def solve_nearest(self, *, edge_weights: Any = None, reuse: bool = False) -> np.ndarray:
|
|
394
|
+
"""Return the routing table to the nearest destination in one pass (``Network.SolveNearest``).
|
|
395
|
+
|
|
396
|
+
Parameters
|
|
397
|
+
----------
|
|
398
|
+
edge_weights : array_like, optional
|
|
399
|
+
Custom weights, one per edge; None uses the constructor's.
|
|
400
|
+
reuse : bool, optional
|
|
401
|
+
With ``edge_weights``, fill one .NET table kept by the network; default False.
|
|
402
|
+
|
|
403
|
+
Returns
|
|
404
|
+
-------
|
|
405
|
+
numpy.ndarray
|
|
406
|
+
The ``(nodes, 3)`` float32 routing table.
|
|
407
|
+
"""
|
|
408
|
+
if edge_weights is None:
|
|
409
|
+
return _table_to_numpy(self.net.SolveNearest())
|
|
410
|
+
table = self._reused_table() if reuse else _table_to_net(np.zeros((self.node_count, 3)))
|
|
411
|
+
self.net.SolveNearest(_singles(edge_weights), table)
|
|
412
|
+
return _table_to_numpy(table)
|
|
413
|
+
|
|
414
|
+
def get_path(self, edges_to_remove: Any, start: int, table: Any = None,
|
|
415
|
+
edge_weights: Any = None) -> np.ndarray | None:
|
|
416
|
+
"""Return the shortest path from a node avoiding some edges (``Network.GetPath``).
|
|
417
|
+
|
|
418
|
+
Parameters
|
|
419
|
+
----------
|
|
420
|
+
edges_to_remove : array_like of int
|
|
421
|
+
Edge indices to exclude.
|
|
422
|
+
start : int
|
|
423
|
+
The start node.
|
|
424
|
+
table : array_like, optional
|
|
425
|
+
A routing table solved on this network without exclusions; its route is reused when it
|
|
426
|
+
avoids every excluded edge.
|
|
427
|
+
edge_weights : array_like, optional
|
|
428
|
+
The custom weights ``table`` was solved with.
|
|
429
|
+
|
|
430
|
+
Returns
|
|
431
|
+
-------
|
|
432
|
+
numpy.ndarray or None
|
|
433
|
+
The ordered int32 edge indices. Without ``table`` an unreachable start gives None; with a
|
|
434
|
+
table it gives an empty array, as Numerics does.
|
|
435
|
+
"""
|
|
436
|
+
removed = to_net_ints(np.asarray(edges_to_remove, dtype=np.int64).ravel())
|
|
437
|
+
if table is None:
|
|
438
|
+
return _path(self.net.GetPath(removed, int(start)))
|
|
439
|
+
if edge_weights is None:
|
|
440
|
+
return _path(self.net.GetPath(removed, int(start), _table_to_net(table)))
|
|
441
|
+
return _path(self.net.GetPath(removed, int(start), _table_to_net(table), _singles(edge_weights)))
|
|
442
|
+
|
|
443
|
+
|
|
444
|
+
@dataclass(frozen=True, slots=True)
|
|
445
|
+
class HeapNode:
|
|
446
|
+
"""A node of a :class:`BinaryHeap` (``BinaryHeap<T>.Node``).
|
|
447
|
+
|
|
448
|
+
Parameters
|
|
449
|
+
----------
|
|
450
|
+
weight : float
|
|
451
|
+
The priority (stored as float32 by Numerics).
|
|
452
|
+
index : int
|
|
453
|
+
The key the keyed operations address, in [0, heap size).
|
|
454
|
+
value : object, optional
|
|
455
|
+
Any value carried with the node.
|
|
456
|
+
"""
|
|
457
|
+
|
|
458
|
+
weight: float
|
|
459
|
+
index: int
|
|
460
|
+
value: Any = None
|
|
461
|
+
|
|
462
|
+
|
|
463
|
+
class BinaryHeap:
|
|
464
|
+
"""A fixed-capacity keyed binary min-heap (``Numerics.Mathematics.Optimization.BinaryHeap<object>``).
|
|
465
|
+
|
|
466
|
+
Parameters
|
|
467
|
+
----------
|
|
468
|
+
heap_size : int
|
|
469
|
+
The capacity; node indices lie in [0, heap_size).
|
|
470
|
+
|
|
471
|
+
Attributes
|
|
472
|
+
----------
|
|
473
|
+
net : Numerics.Mathematics.Optimization.BinaryHeap
|
|
474
|
+
The wrapped .NET heap of ``object`` values.
|
|
475
|
+
"""
|
|
476
|
+
|
|
477
|
+
def __init__(self, heap_size: int) -> None:
|
|
478
|
+
import clr # noqa: PLC0415 - the namespace import starts the runtime
|
|
479
|
+
import System # noqa: PLC0415
|
|
480
|
+
|
|
481
|
+
heap_type = _opt().BinaryHeap[System.Object]
|
|
482
|
+
self.net = heap_type(int(heap_size))
|
|
483
|
+
node = clr.GetClrType(heap_type).GetNestedType("Node")
|
|
484
|
+
self._node_type = node.MakeGenericType(System.Array[System.Type]([clr.GetClrType(System.Object)]))
|
|
485
|
+
|
|
486
|
+
def _node(self, node: HeapNode) -> Any:
|
|
487
|
+
"""Build the .NET node."""
|
|
488
|
+
import System # noqa: PLC0415 - the runtime is running
|
|
489
|
+
|
|
490
|
+
arguments = System.Array[System.Object]([System.Single(float(node.weight)), System.Int32(int(node.index)),
|
|
491
|
+
node.value])
|
|
492
|
+
return System.Activator.CreateInstance(self._node_type, arguments)
|
|
493
|
+
|
|
494
|
+
@property
|
|
495
|
+
def count(self) -> int:
|
|
496
|
+
"""The number of nodes in the heap (``Count``)."""
|
|
497
|
+
return int(self.net.Count)
|
|
498
|
+
|
|
499
|
+
def __len__(self) -> int:
|
|
500
|
+
"""Return the number of nodes in the heap.
|
|
501
|
+
|
|
502
|
+
Returns
|
|
503
|
+
-------
|
|
504
|
+
int
|
|
505
|
+
The count.
|
|
506
|
+
"""
|
|
507
|
+
return self.count
|
|
508
|
+
|
|
509
|
+
def add(self, node: HeapNode) -> None:
|
|
510
|
+
"""Add a node (``Add``).
|
|
511
|
+
|
|
512
|
+
Parameters
|
|
513
|
+
----------
|
|
514
|
+
node : HeapNode
|
|
515
|
+
The node.
|
|
516
|
+
"""
|
|
517
|
+
self.net.Add(self._node(node))
|
|
518
|
+
|
|
519
|
+
def remove_min(self) -> HeapNode:
|
|
520
|
+
"""Remove and return the lightest node (``RemoveMin``).
|
|
521
|
+
|
|
522
|
+
Returns
|
|
523
|
+
-------
|
|
524
|
+
HeapNode
|
|
525
|
+
The node with the smallest weight.
|
|
526
|
+
"""
|
|
527
|
+
node = self.net.RemoveMin()
|
|
528
|
+
return HeapNode(float(node.Weight), int(node.Index), node.Value)
|
|
529
|
+
|
|
530
|
+
def decrease_key(self, node: HeapNode) -> None:
|
|
531
|
+
"""Lower the weight of the node with the same index, or add it when absent (``DecreaseKey``).
|
|
532
|
+
|
|
533
|
+
Parameters
|
|
534
|
+
----------
|
|
535
|
+
node : HeapNode
|
|
536
|
+
The index to address and the candidate weight; a larger weight is ignored.
|
|
537
|
+
"""
|
|
538
|
+
self.net.DecreaseKey(self._node(node))
|
|
539
|
+
|
|
540
|
+
def replace(self, node: HeapNode) -> None:
|
|
541
|
+
"""Replace the weight and value of the node with the same index (``Replace``).
|
|
542
|
+
|
|
543
|
+
Parameters
|
|
544
|
+
----------
|
|
545
|
+
node : HeapNode
|
|
546
|
+
The index to address and the replacement weight and value; an absent index does nothing.
|
|
547
|
+
"""
|
|
548
|
+
self.net.Replace(self._node(node))
|
|
549
|
+
|
|
550
|
+
|
|
551
|
+
__all__ = [
|
|
552
|
+
"BinaryHeap", "Edge", "HeapNode", "Network", "dijkstra_get_path", "dijkstra_path_exists", "dijkstra_solve",
|
|
553
|
+
"dijkstra_solve_nearest", "dijkstra_try_get_path", "shortest_distances",
|
|
554
|
+
]
|
|
555
|
+
|
|
556
|
+
translate_public(globals())
|