|
3 | 3 | from .base import GraphOperator |
4 | 4 | from distanceclosure.dijkstra import single_source_dijkstra_path_length |
5 | 5 | from networkx.algorithms.shortest_paths.weighted import _weight_function |
6 | | -from heapq import heappush, heappop |
7 | | -from itertools import count |
8 | 6 | import networkx as nx |
9 | 7 | from typing import Literal |
10 | 8 |
|
11 | | -def single_source_dijkstra_path_length(G, source, weight_function, paths=None, disjunction=sum): |
12 | | - """Uses (a custom) Dijkstra's algorithm to find shortest weighted paths |
13 | | -
|
14 | | - Parameters |
15 | | - ---------- |
16 | | - G : NetworkX graph |
17 | | -
|
18 | | - source : node |
19 | | - Starting node for path. |
20 | | -
|
21 | | - weight_function: function |
22 | | - Function with (u, v, data) input that returns that edges weight |
23 | | -
|
24 | | - paths: dict, optional (default=None) |
25 | | - dict to store the path list from source to each node, keyed by node. |
26 | | - If None, paths are not stored. |
27 | | -
|
28 | | - disjunction: function (default=sum) |
29 | | - Whether to sum paths or use the max value. |
30 | | - Use `sum` for metric and `max` for ultrametric. |
31 | | -
|
32 | | - Returns |
33 | | - ------- |
34 | | - distance : dictionary |
35 | | - A mapping from node to shortest distance to that node from one |
36 | | - of the source nodes. |
37 | | -
|
38 | | - Raises |
39 | | - ------ |
40 | | - NodeNotFound |
41 | | - If `source` is not in `G`. |
42 | | -
|
43 | | - Note |
44 | | - ----- |
45 | | - The optional predecessor and path dictionaries can be accessed by |
46 | | - the caller through the original paths objects passed |
47 | | - as arguments. No need to explicitly return paths. |
48 | | -
|
49 | | - """ |
50 | | - G_succ = G._succ if G.is_directed() else G._adj |
51 | | - |
52 | | - push = heappush |
53 | | - pop = heappop |
54 | | - dist = {} # dictionary of final distances |
55 | | - seen = {} |
56 | | - # fringe is heapq with 3-tuples (distance,c,node) |
57 | | - # use the count c to avoid comparing nodes (may not be able to) |
58 | | - c = count() |
59 | | - fringe = [] |
60 | | - if source not in G: |
61 | | - raise nx.NodeNotFound(f"Source {source} not in G") |
62 | | - seen[source] = 0 |
63 | | - push(fringe, (0, next(c), source)) |
64 | | - while fringe: |
65 | | - (d, _, v) = pop(fringe) |
66 | | - if v in dist: |
67 | | - continue # already searched this node. |
68 | | - dist[v] = d |
69 | | - for u, e in G_succ[v].items(): |
70 | | - cost = weight_function(v, u, e) |
71 | | - if cost is None: |
72 | | - continue |
73 | | - vu_dist = disjunction([dist[v], cost]) |
74 | | - if u in dist: |
75 | | - u_dist = dist[u] |
76 | | - if vu_dist < u_dist: |
77 | | - raise ValueError("Contradictory paths found:", "negative weights?") |
78 | | - elif u not in seen or vu_dist < seen[u]: |
79 | | - seen[u] = vu_dist |
80 | | - push(fringe, (vu_dist, next(c), u)) |
81 | | - if paths is not None: |
82 | | - paths[u] = paths[v] + [u] |
83 | | - return dist |
84 | | - |
85 | 9 | Mode = Literal["distance", "similarity"] |
86 | 10 |
|
87 | 11 | @dataclass(slots=True) |
|
0 commit comments