-
Notifications
You must be signed in to change notification settings - Fork 164
/
Copy path__init__.py
2022 lines (1606 loc) · 83.5 KB
/
__init__.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
#
# Any modifications or derivative works of this code must retain this
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.
import importlib
import sys
import functools
from .rustworkx import *
# flake8: noqa
import rustworkx.visit
__rustworkx_mod__ = importlib.import_module(".rustworkx", package="rustworkx")
sys.modules["rustworkx.generators"] = __rustworkx_mod__.generators
class PyDAG(PyDiGraph):
"""A class for creating direct acyclic graphs.
PyDAG is just an alias of the PyDiGraph class and behaves identically to
the :class:`~rustworkx.PyDiGraph` class and can be used interchangeably
with ``PyDiGraph``. It currently exists solely as a backwards
compatibility alias for users of rustworkx from prior to the
0.4.0 release when there was no PyDiGraph class.
The PyDAG class is used to create a directed graph. It can be a
multigraph (have multiple edges between nodes). Each node and edge
(although rarely used for edges) is indexed by an integer id. These ids
are stable for the lifetime of the graph object and on node or edge
deletions you can have holes in the list of indices for the graph.
Node indices will be reused on additions after removal. For example:
.. jupyter-execute::
import rustworkx as rx
graph = rx.PyDAG()
graph.add_nodes_from(list(range(5)))
graph.add_nodes_from(list(range(2)))
graph.remove_node(2)
print("After deletion:", graph.node_indices())
res_manual = graph.add_parent(6, None, None)
print("After adding a new node:", graph.node_indices())
Additionally, each node and edge contains an arbitrary Python object as a
weight/data payload.
You can use the index for access to the data payload as in the
following example:
.. jupyter-execute::
import rustworkx as rx
graph = rx.PyDAG()
data_payload = "An arbitrary Python object"
node_index = graph.add_node(data_payload)
print("Node Index: %s" % node_index)
print(graph[node_index])
The PyDAG class implements the Python mapping protocol for nodes so in
addition to access you can also update the data payload with:
.. jupyter-execute::
import rustworkx as rx
graph = rx.PyDAG()
data_payload = "An arbitrary Python object"
node_index = graph.add_node(data_payload)
graph[node_index] = "New Payload"
print("Node Index: %s" % node_index)
print(graph[node_index])
The PyDAG class has an option for real time cycle checking which can
be used to ensure any edges added to the graph does not introduce a cycle.
By default the real time cycle checking feature is disabled for
performance, however you can enable it by setting the ``check_cycle``
attribute to True. For example::
import rustworkx as rx
dag = rx.PyDAG()
dag.check_cycle = True
or at object creation::
import rustworkx as rx
dag = rx.PyDAG(check_cycle=True)
With check_cycle set to true any calls to :meth:`PyDAG.add_edge` will
ensure that no cycles are added, ensuring that the PyDAG class truly
represents a directed acyclic graph. Do note that this cycle checking on
:meth:`~PyDAG.add_edge`, :meth:`~PyDigraph.add_edges_from`,
:meth:`~PyDAG.add_edges_from_no_data`,
:meth:`~PyDAG.extend_from_edge_list`, and
:meth:`~PyDAG.extend_from_weighted_edge_list` comes with a performance
penalty that grows as the graph does. If you're adding a node and edge at
the same time, leveraging :meth:`PyDAG.add_child` or
:meth:`PyDAG.add_parent` will avoid this overhead.
By default a ``PyDAG`` is a multigraph (meaning there can be parallel
edges between nodes) however this can be disabled by setting the
``multigraph`` kwarg to ``False`` when calling the ``PyDAG`` constructor.
For example::
import rustworkx as rx
dag = rx.PyDAG(multigraph=False)
This can only be set at ``PyDiGraph`` initialization and not adjusted after
creation. When :attr:`~rustworkx.PyDiGraph.multigraph` is set to ``False``
if a method call is made that would add a parallel edge it will instead
update the existing edge's weight/data payload.
The maximum number of nodes and edges allowed on a ``PyGraph`` object is
:math:`2^{32} - 1` (4,294,967,294) each. Attempting to add more nodes or
edges than this will result in an exception being raised.
:param bool check_cycle: When this is set to ``True`` the created
``PyDAG`` has runtime cycle detection enabled.
:param bool multgraph: When this is set to ``False`` the created
``PyDAG`` object will not be a multigraph. When ``False`` if a method
call is made that would add parallel edges the the weight/weight from
that method call will be used to update the existing edge in place.
"""
pass
def _rustworkx_dispatch(func):
"""Decorator to dispatch rustworkx universal functions to the correct typed function"""
func_name = func.__name__
wrapped_func = functools.singledispatch(func)
wrapped_func.register(PyDiGraph, vars(__rustworkx_mod__)[f"digraph_{func_name}"])
wrapped_func.register(PyGraph, vars(__rustworkx_mod__)[f"graph_{func_name}"])
return wrapped_func
@_rustworkx_dispatch
def distance_matrix(graph, parallel_threshold=300, as_undirected=False, null_value=0.0):
"""Get the distance matrix for a graph
This differs from functions like :func:`~rustworkx.floyd_warshall_numpy` in
that the edge weight/data payload is not used and each edge is treated as a
distance of 1.
This function is also multithreaded and will run in parallel if the number
of nodes in the graph is above the value of ``parallel_threshold`` (it
defaults to 300). If the function will be running in parallel the env var
``RAYON_NUM_THREADS`` can be used to adjust how many threads will be used.
:param graph: The graph to get the distance matrix for, can be either a
:class:`~rustworkx.PyGraph` or :class:`~rustworkx.PyDiGraph`.
:param int parallel_threshold: The number of nodes to calculate the
the distance matrix in parallel at. It defaults to 300, but this can
be tuned
:param bool as_undirected: If set to ``True`` the input directed graph
will be treat as if each edge was bidirectional/undirected in the
output distance matrix.
:param float null_value: An optional float that will treated as a null
value. This is the default value in the output matrix and it is used
to indicate the absence of an edge between 2 nodes. By default this
is ``0.0``.
:returns: The distance matrix
:rtype: numpy.ndarray
"""
raise TypeError("Invalid Input Type %s for graph" % type(graph))
@_rustworkx_dispatch
def unweighted_average_shortest_path_length(graph, parallel_threshold=300, disconnected=False):
r"""Return the average shortest path length with unweighted edges.
The average shortest path length is calculated as
.. math::
a =\sum_{s,t \in V, s \ne t} \frac{d(s, t)}{n(n-1)}
where :math:`V` is the set of nodes in ``graph``, :math:`d(s, t)` is the
shortest path length from :math:`s` to :math:`t`, and :math:`n` is the
number of nodes in ``graph``. If ``disconnected`` is set to ``True``,
the average will be taken only between connected nodes.
This function is also multithreaded and will run in parallel if the number
of nodes in the graph is above the value of ``parallel_threshold`` (it
defaults to 300). If the function will be running in parallel the env var
``RAYON_NUM_THREADS`` can be used to adjust how many threads will be used.
By default it will use all available CPUs if the environment variable is
not specified.
:param graph: The graph to compute the average shortest path length for,
can be either a :class:`~rustworkx.PyGraph` or :class:`~rustworkx.PyDiGraph`.
:param int parallel_threshold: The number of nodes to calculate the
the distance matrix in parallel at. It defaults to 300, but this can
be tuned to any number of nodes.
:param bool as_undirected: If set to ``True`` the input directed graph
will be treated as if each edge was bidirectional/undirected while
finding the shortest paths. Default: ``False``.
:param bool disconnected: If set to ``True`` only connected vertex pairs
will be included in the calculation. If ``False``, infinity is returned
for disconnected graphs. Default: ``False``.
:returns: The average shortest path length. If no vertex pairs can be included
in the calculation this will return NaN.
:rtype: float
"""
raise TypeError("Invalid Input Type %s for graph" % type(graph))
@_rustworkx_dispatch
def adjacency_matrix(graph, weight_fn=None, default_weight=1.0, null_value=0.0):
"""Return the adjacency matrix for a graph object
In the case where there are multiple edges between nodes the value in the
output matrix will be the sum of the edges' weights.
:param graph: The graph used to generate the adjacency matrix from. Can
either be a :class:`~rustworkx.PyGraph` or :class:`~rustworkx.PyDiGraph`
:param callable weight_fn: A callable object (function, lambda, etc) which
will be passed the edge object and expected to return a ``float``. This
tells rustworkx/rust how to extract a numerical weight as a ``float``
for edge object. Some simple examples are::
adjacency_matrix(graph, weight_fn: lambda x: 1)
to return a weight of 1 for all edges. Also::
adjacency_matrix(graph, weight_fn: lambda x: float(x))
to cast the edge object as a float as the weight. If this is not
specified a default value (either ``default_weight`` or 1) will be used
for all edges.
:param float default_weight: If ``weight_fn`` is not used this can be
optionally used to specify a default weight to use for all edges.
:param float null_value: An optional float that will treated as a null
value. This is the default value in the output matrix and it is used
to indicate the absence of an edge between 2 nodes. By default this is
``0.0``.
:return: The adjacency matrix for the input dag as a numpy array
:rtype: numpy.ndarray
"""
raise TypeError("Invalid Input Type %s for graph" % type(graph))
@_rustworkx_dispatch
def all_simple_paths(graph, from_, to, min_depth=None, cutoff=None):
"""Return all simple paths between 2 nodes in a PyGraph object
A simple path is a path with no repeated nodes.
:param graph: The graph to find the path in. Can either be a
class:`~rustworkx.PyGraph` or :class:`~rustworkx.PyDiGraph`
:param int from_: The node index to find the paths from
:param int to: The node index to find the paths to
:param int min_depth: The minimum depth of the path to include in the
output list of paths. By default all paths are included regardless of
depth, setting to 0 will behave like the default.
:param int cutoff: The maximum depth of path to include in the output list
of paths. By default includes all paths regardless of depth, setting to
0 will behave like default.
:returns: A list of lists where each inner list is a path of node indices
:rtype: list
"""
raise TypeError("Invalid Input Type %s for graph" % type(graph))
@_rustworkx_dispatch
def floyd_warshall(
graph,
weight_fn=None,
default_weight=1.0,
parallel_threshold=300,
):
"""Find all-pairs shortest path lengths using Floyd's algorithm
Floyd's algorithm is used for finding shortest paths in dense graphs
or graphs with negative weights (where Dijkstra's algorithm fails).
This function is multithreaded and will launch a pool with threads equal
to the number of CPUs by default if the number of nodes in the graph is
above the value of ``parallel_threshold`` (it defaults to 300).
You can tune the number of threads with the ``RAYON_NUM_THREADS``
environment variable. For example, setting ``RAYON_NUM_THREADS=4`` would
limit the thread pool to 4 threads if parallelization was enabled.
:param graph: The graph to run Floyd's algorithm on. Can
either be a :class:`~rustworkx.PyGraph` or :class:`~rustworkx.PyDiGraph`
:param callable weight_fn: A callable object (function, lambda, etc) which
will be passed the edge object and expected to return a ``float``. This
tells rustworkx/rust how to extract a numerical weight as a ``float``
for edge object. Some simple examples are::
floyd_warshall(graph, weight_fn= lambda x: 1)
to return a weight of 1 for all edges. Also::
floyd_warshall(graph, weight_fn=float)
to cast the edge object as a float as the weight. If this is not
specified a default value (either ``default_weight`` or 1) will be used
for all edges.
:param float default_weight: If ``weight_fn`` is not used this can be
optionally used to specify a default weight to use for all edges.
:param int parallel_threshold: The number of nodes to execute
the algorithm in parallel at. It defaults to 300, but this can
be tuned
:return: A read-only dictionary of path lengths. The keys are the source
node indices and the values are a dict of the target node and the
length of the shortest path to that node. For example::
{
0: {0: 0.0, 1: 2.0, 2: 2.0},
1: {1: 0.0, 2: 1.0},
2: {0: 1.0, 2: 0.0},
}
:rtype: AllPairsPathLengthMapping
"""
raise TypeError("Invalid Input Type %s for graph" % type(graph))
@_rustworkx_dispatch
def floyd_warshall_numpy(
graph,
weight_fn=None,
default_weight=1.0,
parallel_threshold=300,
):
"""Find all-pairs shortest path lengths using Floyd's algorithm
Floyd's algorithm is used for finding shortest paths in dense graphs
or graphs with negative weights (where Dijkstra's algorithm fails).
This function is multithreaded and will launch a pool with threads equal
to the number of CPUs by default if the number of nodes in the graph is
above the value of ``parallel_threshold`` (it defaults to 300).
You can tune the number of threads with the ``RAYON_NUM_THREADS``
environment variable. For example, setting ``RAYON_NUM_THREADS=4`` would
limit the thread pool to 4 threads if parallelization was enabled.
:param graph: The graph to run Floyd's algorithm on. Can
either be a :class:`~rustworkx.PyGraph` or :class:`~rustworkx.PyDiGraph`
:param callable weight_fn: A callable object (function, lambda, etc) which
will be passed the edge object and expected to return a ``float``. This
tells rustworkx/rust how to extract a numerical weight as a ``float``
for edge object. Some simple examples are::
floyd_warshall_numpy(graph, weight_fn: lambda x: 1)
to return a weight of 1 for all edges. Also::
floyd_warshall_numpy(graph, weight_fn: lambda x: float(x))
to cast the edge object as a float as the weight. If this is not
specified a default value (either ``default_weight`` or 1) will be used
for all edges.
:param float default_weight: If ``weight_fn`` is not used this can be
optionally used to specify a default weight to use for all edges.
:param int parallel_threshold: The number of nodes to execute
the algorithm in parallel at. It defaults to 300, but this can
be tuned
:returns: A matrix of shortest path distances between nodes. If there is no
path between two nodes then the corresponding matrix entry will be
``np.inf``.
:rtype: numpy.ndarray
"""
raise TypeError("Invalid Input Type %s for graph" % type(graph))
@_rustworkx_dispatch
def astar_shortest_path(graph, node, goal_fn, edge_cost_fn, estimate_cost_fn):
"""Compute the A* shortest path for a graph
:param graph: The input graph to use. Can
either be a :class:`~rustworkx.PyGraph` or :class:`~rustworkx.PyDiGraph`
:param int node: The node index to compute the path from
:param goal_fn: A python callable that will take in 1 parameter, a node's
data object and will return a boolean which will be True if it is the
finish node.
:param edge_cost_fn: A python callable that will take in 1 parameter, an
edge's data object and will return a float that represents the cost
of that edge. It must be non-negative.
:param estimate_cost_fn: A python callable that will take in 1 parameter, a
node's data object and will return a float which represents the
estimated cost for the next node. The return must be non-negative. For
the algorithm to find the actual shortest path, it should be
admissible, meaning that it should never overestimate the actual cost
to get to the nearest goal node.
:returns: The computed shortest path between node and finish as a list
of node indices.
:rtype: NodeIndices
"""
raise TypeError("Invalid Input Type %s for graph" % type(graph))
@_rustworkx_dispatch
def dijkstra_shortest_paths(
graph,
source,
target=None,
weight_fn=None,
default_weight=1.0,
as_undirected=False,
):
"""Find the shortest path from a node
This function will generate the shortest path from a source node using
Dijkstra's algorithm.
:param graph: The input graph to use. Can either be a
:class:`~rustworkx.PyGraph` or :class:`~rustworkx.PyDiGraph`
:param int source: The node index to find paths from
:param int target: An optional target to find a path to
:param weight_fn: An optional weight function for an edge. It will accept
a single argument, the edge's weight object and will return a float
which will be used to represent the weight/cost of the edge
:param float default_weight: If ``weight_fn`` isn't specified this optional
float value will be used for the weight/cost of each edge.
:param bool as_undirected: If set to true the graph will be treated as
undirected for finding the shortest path. This only works with a
:class:`~rustworkx.PyDiGraph` input for ``graph``
:return: Dictionary of paths. The keys are destination node indices and
the dict values are lists of node indices making the path.
:rtype: dict
"""
raise TypeError("Invalid Input Type %s for graph" % type(graph))
@_rustworkx_dispatch
def has_path(
graph,
source,
target,
as_undirected=False,
):
"""Checks if a path exists between a source and target node
:param graph: The input graph to use. Can either be a
:class:`~rustworkx.PyGraph` or :class:`~rustworkx.PyDiGraph`
:param int source: The node index to find paths from
:param int target: The index of the target node
:param bool as_undirected: If set to true the graph will be treated as
undirected for finding existence of a path. This only works with a
:class:`~rustworkx.PyDiGraph` input for ``graph``
:return: True if a path exists, False if not
:rtype: bool
"""
raise TypeError("Invalid Input Type %s for graph" % type(graph))
@_rustworkx_dispatch
def all_pairs_dijkstra_shortest_paths(graph, edge_cost_fn):
"""For each node in the graph, finds the shortest paths to all others.
This function will generate the shortest path from all nodes in the graph
using Dijkstra's algorithm. This function is multithreaded and will run
launch a thread pool with threads equal to the number of CPUs by default.
You can tune the number of threads with the ``RAYON_NUM_THREADS``
environment variable. For example, setting ``RAYON_NUM_THREADS=4`` would
limit the thread pool to 4 threads.
:param graph: The input graph to use. Can either be a
:class:`~rustworkx.PyGraph` or :class:`~rustworkx.PyDiGraph`
:param edge_cost_fn: A callable object that acts as a weight function for
an edge. It will accept a single positional argument, the edge's weight
object and will return a float which will be used to represent the
weight/cost of the edge
:return: A read-only dictionary of paths. The keys are source node
indices and the values are a dict of target node indices and a list
of node indices making the path. For example::
{
0: {1: [0, 1], 2: [0, 1, 2]},
1: {2: [1, 2]},
2: {0: [2, 0]},
}
:rtype: AllPairsPathMapping
"""
raise TypeError("Invalid Input Type %s for graph" % type(graph))
@_rustworkx_dispatch
def all_pairs_all_simple_paths(graph, min_depth=None, cutoff=None):
"""Return all the simple paths between all pairs of nodes in the graph
This function is multithreaded and will launch a thread pool with threads
equal to the number of CPUs by default. You can tune the number of threads
with the ``RAYON_NUM_THREADS`` environment variable. For example, setting
``RAYON_NUM_THREADS=4`` would limit the thread pool to 4 threads.
:param graph: The graph to find all simple paths in. This can be a :class:`~rustworkx.PyGraph`
or a :class:`~rustworkx.PyDiGraph`
:param int min_depth: The minimum depth of the path to include in the output
list of paths. By default all paths are included regardless of depth,
setting to 0 will behave like the default.
:param int cutoff: The maximum depth of path to include in the output list
of paths. By default includes all paths regardless of depth, setting to
0 will behave like default.
:returns: A mapping of source node indices to a mapping of target node
indices to a list of paths between the source and target nodes.
:rtype: AllPairsMultiplePathMapping
"""
raise TypeError("Invalid Input Type %s for graph" % type(graph))
@_rustworkx_dispatch
def all_pairs_dijkstra_path_lengths(graph, edge_cost_fn):
"""For each node in the graph, calculates the lengths of the shortest paths to all others.
This function will generate the shortest path lengths from all nodes in the
graph using Dijkstra's algorithm. This function is multithreaded and will
launch a thread pool with threads equal to the number of CPUs by
default. You can tune the number of threads with the ``RAYON_NUM_THREADS``
environment variable. For example, setting ``RAYON_NUM_THREADS=4`` would
limit the thread pool to 4 threads.
:param graph: The input graph to use. Can either be a
:class:`~rustworkx.PyGraph` or :class:`~rustworkx.PyDiGraph`
:param edge_cost_fn: A callable object that acts as a weight function for
an edge. It will accept a single positional argument, the edge's weight
object and will return a float which will be used to represent the
weight/cost of the edge
:return: A read-only dictionary of path lengths. The keys are the source
node indices and the values are a dict of the target node and the
length of the shortest path to that node. For example::
{
0: {1: 2.0, 2: 2.0},
1: {2: 1.0},
2: {0: 1.0},
}
:rtype: AllPairsPathLengthMapping
"""
raise TypeError("Invalid Input Type %s for graph" % type(graph))
@_rustworkx_dispatch
def dijkstra_shortest_path_lengths(graph, node, edge_cost_fn, goal=None):
"""Compute the lengths of the shortest paths for a graph object using
Dijkstra's algorithm.
:param graph: The input graph to use. Can either be a
:class:`~rustworkx.PyGraph` or :class:`~rustworkx.PyDiGraph`
:param int node: The node index to use as the source for finding the
shortest paths from
:param edge_cost_fn: A python callable that will take in 1 parameter, an
edge's data object and will return a float that represents the
cost/weight of that edge. It must be non-negative
:param int goal: An optional node index to use as the end of the path.
When specified the traversal will stop when the goal is reached and
the output dictionary will only have a single entry with the length
of the shortest path to the goal node.
:returns: A dictionary of the shortest paths from the provided node where
the key is the node index of the end of the path and the value is the
cost/sum of the weights of path
:rtype: dict
"""
raise TypeError("Invalid Input Type %s for graph" % type(graph))
@_rustworkx_dispatch
def k_shortest_path_lengths(graph, start, k, edge_cost, goal=None):
"""Compute the length of the kth shortest path
Computes the lengths of the kth shortest path from ``start`` to every
reachable node.
Computes in :math:`O(k * (|E| + |V|*log(|V|)))` time (average).
:param graph: The graph to find the shortest paths in. Can either be a
:class:`~rustworkx.PyGraph` or :class:`~rustworkx.PyDiGraph`
:param int start: The node index to find the shortest paths from
:param int k: The kth shortest path to find the lengths of
:param edge_cost: A python callable that will receive an edge payload and
return a float for the cost of that eedge
:param int goal: An optional goal node index, if specified the output
dictionary
:returns: A dict of lengths where the key is the destination node index and
the value is the length of the path.
:rtype: dict
"""
raise TypeError("Invalid Input Type %s for graph" % type(graph))
@_rustworkx_dispatch
def dfs_edges(graph, source=None):
"""Get an edge list of the tree edges from a depth-first traversal
The pseudo-code for the DFS algorithm is listed below. The output
contains the tree edges found by the procedure.
::
DFS(G, v)
let S be a stack
label v as discovered
PUSH(S, (v, iterator of G.neighbors(v)))
while (S != Ø)
let (v, iterator) := LAST(S)
if hasNext(iterator) then
w := next(iterator)
if w is not labeled as discovered then
label w as discovered # (v, w) is a tree edge
PUSH(S, (w, iterator of G.neighbors(w)))
else
POP(S)
end while
.. note::
If the input is an undirected graph with a single connected component,
the output of this function is a spanning tree.
:param graph: The graph to get the DFS edge list from. Can either be a
:class:`~rustworkx.PyGraph` or :class:`~rustworkx.PyDiGraph`
:param int source: An optional node index to use as the starting node
for the depth-first search. The edge list will only return edges in
the components reachable from this index. If this is not specified
then a source will be chosen arbitrarily and repeated until all
components of the graph are searched.
:returns: A list of edges as a tuple of the form ``(source, target)`` in
depth-first order
:rtype: EdgeList
"""
raise TypeError("Invalid Input Type %s for graph" % type(graph))
@_rustworkx_dispatch
def is_isomorphic(
first,
second,
node_matcher=None,
edge_matcher=None,
id_order=True,
call_limit=None,
):
"""Determine if 2 graphs are isomorphic
This checks if 2 graphs are isomorphic both structurally and also
comparing the node and edge data using the provided matcher functions.
The matcher functions take in 2 data objects and will compare them. A
simple example that checks if they're just equal would be::
graph_a = rustworkx.PyGraph()
graph_b = rustworkx.PyGraph()
rustworkx.is_isomorphic(graph_a, graph_b,
lambda x, y: x == y)
.. note::
For better performance on large graphs, consider setting
`id_order=False`.
:param first: The first graph to compare. Can either be a
:class:`~rustworkx.PyGraph` or :class:`~rustworkx.PyDiGraph`.
:param second: The second graph to compare. Can either be a
:class:`~rustworkx.PyGraph` or :class:`~rustworkx.PyDiGraph`.
It should be the same type as the first graph.
:param callable node_matcher: A python callable object that takes 2
positional one for each node data object. If the return of this
function evaluates to True then the nodes passed to it are viewed
as matching.
:param callable edge_matcher: A python callable object that takes 2
positional one for each edge data object. If the return of this
function evaluates to True then the edges passed to it are viewed
as matching.
:param bool id_order: If set to ``False`` this function will use a
heuristic matching order based on [VF2]_ paper. Otherwise it will
default to matching the nodes in order specified by their ids.
:param int call_limit: An optional bound on the number of states that VF2
algorithm visits while searching for a solution. If it exceeds this limit,
the algorithm will stop and return ``False``.
:returns: ``True`` if the 2 graphs are isomorphic, ``False`` if they are
not.
:rtype: bool
.. [VF2] VF2++ An Improved Subgraph Isomorphism Algorithm
by Alpár Jüttner and Péter Madarasi
"""
raise TypeError("Invalid Input Type %s for graph" % type(first))
def is_isomorphic_node_match(first, second, matcher, id_order=True):
"""Determine if 2 graphs are isomorphic
This checks if 2 graphs are isomorphic both structurally and also
comparing the node data using the provided matcher function. The matcher
function takes in 2 node data objects and will compare them. A simple
example that checks if they're just equal would be::
graph_a = rustworkx.PyDAG()
graph_b = rustworkx.PyDAG()
rustworkx.is_isomorphic_node_match(graph_a, graph_b,
lambda x, y: x == y)
.. note::
For better performance on large graphs, consider setting
`id_order=False`.
:param first: The first graph to compare. Can either be a
:class:`~rustworkx.PyGraph` or :class:`~rustworkx.PyDiGraph`.
:param second: The second graph to compare. Can either be a
:class:`~rustworkx.PyGraph` or :class:`~rustworkx.PyDiGraph`.
It should be the same type as the first graph.
:param callable matcher: A python callable object that takes 2 positional
one for each node data object. If the return of this
function evaluates to True then the nodes passed to it are vieded
as matching.
:param bool id_order: If set to ``False`` this function will use a
heuristic matching order based on [VF2]_ paper. Otherwise it will
default to matching the nodes in order specified by their ids.
:returns: ``True`` if the 2 graphs are isomorphic ``False`` if they are
not.
:rtype: bool
"""
return is_isomorphic(first, second, matcher, None, id_order)
@_rustworkx_dispatch
def is_subgraph_isomorphic(
first,
second,
node_matcher=None,
edge_matcher=None,
id_order=False,
induced=True,
call_limit=None,
):
"""Determine if 2 graphs are subgraph isomorphic
This checks if 2 graphs are subgraph isomorphic both structurally and also
comparing the node and edge data using the provided matcher functions.
The matcher functions take in 2 data objects and will compare them.
Since there is an ambiguity in the term 'subgraph', do note that we check
for an node-induced subgraph if argument `induced` is set to `True`. If it is
set to `False`, we check for a non induced subgraph, meaning the second graph
can have fewer edges than the subgraph of the first. By default it's `True`. A
simple example that checks if they're just equal would be::
graph_a = rustworkx.PyGraph()
graph_b = rustworkx.PyGraph()
rustworkx.is_subgraph_isomorphic(graph_a, graph_b,
lambda x, y: x == y)
:param first: The first graph to compare. Can either be a
:class:`~rustworkx.PyGraph` or :class:`~rustworkx.PyDiGraph`.
:param second: The second graph to compare. Can either be a
:class:`~rustworkx.PyGraph` or :class:`~rustworkx.PyDiGraph`.
It should be the same type as the first graph.
:param callable node_matcher: A python callable object that takes 2
positional one for each node data object. If the return of this
function evaluates to True then the nodes passed to it are viewed
as matching.
:param callable edge_matcher: A python callable object that takes 2
positional one for each edge data object. If the return of this
function evaluates to True then the edges passed to it are viewed
as matching.
:param bool id_order: If set to ``True`` this function will match the nodes
in order specified by their ids. Otherwise it will default to a heuristic
matching order based on [VF2]_ paper.
:param bool induced: If set to ``True`` this function will check the existence
of a node-induced subgraph of first isomorphic to second graph.
Default: ``True``.
:param int call_limit: An optional bound on the number of states that VF2
algorithm visits while searching for a solution. If it exceeds this limit,
the algorithm will stop and return ``False``.
:returns: ``True`` if there is a subgraph of `first` isomorphic to `second`
, ``False`` if there is not.
:rtype: bool
"""
raise TypeError("Invalid Input Type %s for graph" % type(first))
@_rustworkx_dispatch
def transitivity(graph):
"""Compute the transitivity of a graph.
This function is multithreaded and will run
launch a thread pool with threads equal to the number of CPUs by default.
You can tune the number of threads with the ``RAYON_NUM_THREADS``
environment variable. For example, setting ``RAYON_NUM_THREADS=4`` would
limit the thread pool to 4 threads.
.. note::
The function implicitly assumes that there are no parallel edges
or self loops. It may produce incorrect/unexpected results if the
input graph has self loops or parallel edges.
:param graph: The graph to be used. Can either be a
:class:`~rustworkx.PyGraph` or :class:`~rustworkx.PyDiGraph`.
:returns: Transitivity of the graph.
:rtype: float
raise TypeError("Invalid Input Type %s for graph" % type(graph))
"""
raise TypeError("Invalid Input Type %s for graph" % type(graph))
@_rustworkx_dispatch
def core_number(graph):
"""Return the core number for each node in the graph.
A k-core is a maximal subgraph that contains nodes of degree k or more.
.. note::
The function implicitly assumes that there are no parallel edges
or self loops. It may produce incorrect/unexpected results if the
input graph has self loops or parallel edges.
:param graph: The graph to get core numbers. Can either be a
:class:`~rustworkx.PyGraph` or :class:`~rustworkx.PyDiGraph`
:returns: A dictionary keyed by node index to the core number
:rtype: dict
raise TypeError("Invalid Input Type %s for graph" % type(graph))
"""
raise TypeError("Invalid Input Type %s for graph" % type(graph))
@_rustworkx_dispatch
def complement(graph):
"""Compute the complement of a graph.
:param graph: The graph to be used, can be either a
:class:`~rustworkx.PyGraph` or :class:`~rustworkx.PyDiGraph`.
:returns: The complement of the graph.
:rtype: :class:`~rustworkx.PyGraph` or :class:`~rustworkx.PyDiGraph`
.. note::
Parallel edges and self-loops are never created,
even if the ``multigraph`` is set to ``True``
"""
raise TypeError("Invalid Input Type %s for graph" % type(graph))
@_rustworkx_dispatch
def random_layout(graph, center=None, seed=None):
"""Generate a random layout
:param PyGraph graph: The graph to generate the layout for
:param tuple center: An optional center position. This is a 2 tuple of two
``float`` values for the center position
:param int seed: An optional seed to set for the random number generator.
:returns: The random layout of the graph.
:rtype: Pos2DMapping
"""
raise TypeError("Invalid Input Type %s for graph" % type(graph))
@_rustworkx_dispatch
def spring_layout(
graph,
pos=None,
fixed=None,
k=None,
repulsive_exponent=2,
adaptive_cooling=True,
num_iter=50,
tol=1e-6,
weight_fn=None,
default_weight=1,
scale=1,
center=None,
seed=None,
):
"""
Position nodes using Fruchterman-Reingold force-directed algorithm.
The algorithm simulates a force-directed representation of the network
treating edges as springs holding nodes close, while treating nodes
as repelling objects, sometimes called an anti-gravity force.
Simulation continues until the positions are close to an equilibrium.
:param graph: Graph to be used. Can either be a
:class:`~rustworkx.PyGraph` or :class:`~rustworkx.PyDiGraph`.
:param dict pos:
Initial node positions as a dictionary with node ids as keys and values
as a coordinate list. If ``None``, then use random initial positions.
(``default=None``)
:param set fixed: Nodes to keep fixed at initial position.
Error raised if fixed specified and ``pos`` is not. (``default=None``)
:param float k:
Optimal distance between nodes. If ``None`` the distance is set to
:math:`\\frac{1}{\\sqrt{n}}` where :math:`n` is the number of nodes.
Increase this value to move nodes farther apart. (``default=None``)
:param int repulsive_exponent:
Repulsive force exponent. (``default=2``)
:param bool adaptive_cooling:
Use an adaptive cooling scheme. If set to ``False``,
a linear cooling scheme is used. (``default=True``)
:param int num_iter:
Maximum number of iterations. (``default=50``)
:param float tol:
Threshold for relative error in node position changes.
The iteration stops if the error is below this threshold.
(``default = 1e-6``)
:param weight_fn: An optional weight function for an edge. It will accept
a single argument, the edge's weight object and will return a float
which will be used to represent the weight of the edge.
:param float (default=1) default_weight: If ``weight_fn`` isn't specified
this optional float value will be used for the weight/cost of each edge
:param float|None scale: Scale factor for positions.
Not used unless fixed is None. If scale is ``None``, no re-scaling is
performed. (``default=1.0``)
:param list center: Coordinate pair around which to center
the layout. Not used unless fixed is ``None``. (``default=None``)
:param int seed: An optional seed to use for the random number generator
:returns: A dictionary of positions keyed by node id.
:rtype: dict
"""
raise TypeError("Invalid Input Type %s for graph" % type(graph))
def networkx_converter(graph, keep_attributes: bool = False):
"""Convert a networkx graph object into a rustworkx graph object.
.. note::
networkx is **not** a dependency of rustworkx and this function
is provided as a convenience method for users of both networkx and
rustworkx. This function will not work unless you install networkx
independently.
:param networkx.Graph graph: The networkx graph to convert.
:param bool keep_attributes: If ``True``, add networkx node attributes to
the data payload in the nodes of the output rustworkx graph. When set to
``True``, the node data payloads in the output rustworkx graph object
will be dictionaries with the node attributes from the input networkx
graph where the ``"__networkx_node__"`` key contains the node from the
input networkx graph.
:returns: A rustworkx graph, either a :class:`~rustworkx.PyDiGraph` or a
:class:`~rustworkx.PyGraph` based on whether the input graph is directed
or not.
:rtype: :class:`~rustworkx.PyDiGraph` or :class:`~rustworkx.PyGraph`
"""
if graph.is_directed():
new_graph = PyDiGraph(multigraph=graph.is_multigraph())
else:
new_graph = PyGraph(multigraph=graph.is_multigraph())
nodes = list(graph.nodes)
node_indices = dict(zip(nodes, new_graph.add_nodes_from(nodes)))
new_graph.add_edges_from(
[(node_indices[x[0]], node_indices[x[1]], x[2]) for x in graph.edges(data=True)]
)
if keep_attributes:
for node, node_index in node_indices.items():
attributes = graph.nodes[node]
attributes["__networkx_node__"] = node
new_graph[node_index] = attributes
return new_graph
@_rustworkx_dispatch
def bipartite_layout(
graph,
first_nodes,
horizontal=False,
scale=1,
center=None,
aspect_ratio=4 / 3,
):