Skip to main content

qualia_core_db/solvers/graph_opt/
hierarchical_path.rs

1//! Hierarchical / fractal shortest-path decomposition (Riehl-Hespanha, *Fractal
2//! Graph Optimization*) — solve shortest paths by splitting the graph into clusters
3//! and composing a **high-level portal problem** with **independent intra-cluster
4//! subproblems**. The subproblems are independent, so they map naturally onto the
5//! engine's independent 512 MB fractal-swarm worker cells (affordability: less
6//! coupled compute, distributable).
7//!
8//! With the full border set the decomposition is **exact** (it matches plain
9//! Dijkstra — any inter-cluster path must cross a border), which the tests verify.
10//! [`dijkstra`] is the always-present CPU reference. Kernel-class `Reduction`.
11
12use std::collections::HashMap;
13
14/// Plain Dijkstra (the exact reference). Returns shortest distance from `source` to
15/// every node over the directed weighted graph; unreachable nodes are `f64::INFINITY`.
16/// `edges_of[i]` lists `(neighbour, weight)`.
17pub fn dijkstra(n: usize, edges_of: &[Vec<(usize, f64)>], source: usize) -> Vec<f64> {
18    let mut dist = vec![f64::INFINITY; n];
19    if source >= n {
20        return dist;
21    }
22    dist[source] = 0.0;
23    let mut visited = vec![false; n];
24    for _ in 0..n {
25        // O(V²) selection (graphs here are user-side small).
26        let mut u = usize::MAX;
27        let mut best = f64::INFINITY;
28        for i in 0..n {
29            if !visited[i] && dist[i] < best {
30                best = dist[i];
31                u = i;
32            }
33        }
34        if u == usize::MAX {
35            break;
36        }
37        visited[u] = true;
38        for &(v, w) in &edges_of[u] {
39            if dist[u] + w < dist[v] {
40                dist[v] = dist[u] + w;
41            }
42        }
43    }
44    dist
45}
46
47/// Shortest distance from `source` to `target` using **only** edges whose both
48/// endpoints are in `cluster_of == cluster` (an intra-cluster subproblem). `∞` if no
49/// in-cluster path. This is the independent, cell-distributable piece.
50fn intra_distance(
51    edges_of: &[Vec<(usize, f64)>],
52    cluster_of: &[usize],
53    cluster: usize,
54    source: usize,
55    target: usize,
56) -> f64 {
57    let n = edges_of.len();
58    let mut dist = vec![f64::INFINITY; n];
59    dist[source] = 0.0;
60    let mut visited = vec![false; n];
61    loop {
62        let mut u = usize::MAX;
63        let mut best = f64::INFINITY;
64        for i in 0..n {
65            if !visited[i] && cluster_of[i] == cluster && dist[i] < best {
66                best = dist[i];
67                u = i;
68            }
69        }
70        if u == usize::MAX {
71            break;
72        }
73        visited[u] = true;
74        for &(v, w) in &edges_of[u] {
75            if cluster_of[v] == cluster && dist[u] + w < dist[v] {
76                dist[v] = dist[u] + w;
77            }
78        }
79    }
80    dist[target]
81}
82
83/// Hierarchical shortest distance from `s` to `t` using the cluster decomposition in
84/// `cluster_of`. Builds a small portal graph over the border nodes (+ `s`, `t`), so
85/// the only global work is on borders; everything else is independent intra-cluster
86/// subproblems. Returns the exact shortest distance.
87pub fn hierarchical_shortest_path(
88    n: usize,
89    edges_of: &[Vec<(usize, f64)>],
90    cluster_of: &[usize],
91    s: usize,
92    t: usize,
93) -> f64 {
94    if s >= n || t >= n {
95        return f64::INFINITY;
96    }
97    if cluster_of[s] == cluster_of[t] {
98        // Same cluster: but the optimal path could leave and re-enter, so fall
99        // through to the portal construction unless it's trivially intra-only.
100        let intra = intra_distance(edges_of, cluster_of, cluster_of[s], s, t);
101        // Still build the portal graph and take the min (handles leave-and-return).
102        let portal = portal_distance(n, edges_of, cluster_of, s, t);
103        return intra.min(portal);
104    }
105    portal_distance(n, edges_of, cluster_of, s, t)
106}
107
108/// Build the border portal graph (+ s, t as temporary nodes) and Dijkstra over it.
109fn portal_distance(
110    n: usize,
111    edges_of: &[Vec<(usize, f64)>],
112    cluster_of: &[usize],
113    s: usize,
114    t: usize,
115) -> f64 {
116    // Border nodes: have an edge crossing clusters (either direction).
117    let mut is_border = vec![false; n];
118    for u in 0..n {
119        for &(v, _) in &edges_of[u] {
120            if cluster_of[u] != cluster_of[v] {
121                is_border[u] = true;
122                is_border[v] = true;
123            }
124        }
125    }
126    // Portal node set: borders ∪ {s, t}.
127    let mut nodes: Vec<usize> = (0..n).filter(|&i| is_border[i]).collect();
128    if !nodes.contains(&s) {
129        nodes.push(s);
130    }
131    if !nodes.contains(&t) {
132        nodes.push(t);
133    }
134    let index: HashMap<usize, usize> = nodes
135        .iter()
136        .enumerate()
137        .map(|(i, &orig)| (orig, i))
138        .collect();
139    let m = nodes.len();
140    let mut portal_edges: Vec<Vec<(usize, f64)>> = vec![Vec::new(); m];
141
142    // Intra-cluster shortest distances between every pair of portal nodes in the
143    // same cluster (the independent subproblems).
144    for &a in &nodes {
145        for &b in &nodes {
146            if a != b && cluster_of[a] == cluster_of[b] {
147                let d = intra_distance(edges_of, cluster_of, cluster_of[a], a, b);
148                if d.is_finite() {
149                    portal_edges[index[&a]].push((index[&b], d));
150                }
151            }
152        }
153    }
154    // Cross edges (connect borders of different clusters) carried as-is.
155    for u in 0..n {
156        for &(v, w) in &edges_of[u] {
157            if cluster_of[u] != cluster_of[v] {
158                if let (Some(&iu), Some(&iv)) = (index.get(&u), index.get(&v)) {
159                    portal_edges[iu].push((iv, w));
160                }
161            }
162        }
163    }
164
165    let dist = dijkstra(m, &portal_edges, index[&s]);
166    dist[index[&t]]
167}
168
169#[cfg(test)]
170mod tests {
171    use super::*;
172
173    /// Build an adjacency list from undirected weighted edges.
174    fn graph(n: usize, edges: &[(usize, usize, f64)]) -> Vec<Vec<(usize, f64)>> {
175        let mut adj = vec![Vec::new(); n];
176        for &(a, b, w) in edges {
177            adj[a].push((b, w));
178            adj[b].push((a, w));
179        }
180        adj
181    }
182
183    #[test]
184    fn dijkstra_matches_known_distances() {
185        // 0-1(1) 1-2(2) 0-2(4): shortest 0→2 is via 1 = 3.
186        let adj = graph(3, &[(0, 1, 1.0), (1, 2, 2.0), (0, 2, 4.0)]);
187        let d = dijkstra(3, &adj, 0);
188        assert!((d[2] - 3.0).abs() < 1e-9);
189    }
190
191    #[test]
192    fn hierarchical_equals_exact_across_clusters() {
193        // Two clusters {0,1,2} and {3,4,5}, joined by 2-3.
194        let edges = [
195            (0, 1, 1.0),
196            (1, 2, 1.0),
197            (0, 2, 3.0), // cluster A
198            (3, 4, 1.0),
199            (4, 5, 1.0),
200            (3, 5, 3.0), // cluster B
201            (2, 3, 2.0), // bridge
202        ];
203        let adj = graph(6, &edges);
204        let cluster = [0, 0, 0, 1, 1, 1];
205        // Check every pair against exact Dijkstra.
206        for s in 0..6 {
207            let exact = dijkstra(6, &adj, s);
208            for t in 0..6 {
209                let h = hierarchical_shortest_path(6, &adj, &cluster, s, t);
210                assert!(
211                    (h - exact[t]).abs() < 1e-9,
212                    "s={s} t={t}: hier {h} vs exact {}",
213                    exact[t]
214                );
215            }
216        }
217    }
218
219    #[test]
220    fn hierarchical_handles_leave_and_return() {
221        // Within-cluster nodes whose best path goes through the other cluster.
222        let edges = [
223            (0, 1, 10.0), // expensive intra link
224            (0, 2, 1.0),
225            (2, 3, 1.0),
226            (3, 1, 1.0), // cheap detour via cluster B
227        ];
228        let adj = graph(4, &edges);
229        let cluster = [0, 0, 1, 1];
230        let exact = dijkstra(4, &adj, 0);
231        let h = hierarchical_shortest_path(4, &adj, &cluster, 0, 1);
232        assert!(
233            (h - exact[1]).abs() < 1e-9,
234            "leave-and-return: {h} vs {}",
235            exact[1]
236        );
237        assert!(h < 10.0, "should take the cheap detour");
238    }
239}