Run SSSP (Dijkstra) from a source node on the CSR index. Returns `(node_id, distance)` for all reachable nodes. Unreachable nodes get `f64::INFINITY`. Returns an error if the source node doesn't exist.
(csr: &CsrIndex, params: &AlgoParams)
| 24 | /// Returns `(node_id, distance)` for all reachable nodes. Unreachable nodes |
| 25 | /// get `f64::INFINITY`. Returns an error if the source node doesn't exist. |
| 26 | pub fn run(csr: &CsrIndex, params: &AlgoParams) -> Result<AlgoResultBatch, crate::Error> { |
| 27 | let n = csr.node_count(); |
| 28 | if n == 0 { |
| 29 | return Ok(AlgoResultBatch::new(GraphAlgorithm::Sssp)); |
| 30 | } |
| 31 | |
| 32 | let source = params |
| 33 | .source_node |
| 34 | .as_deref() |
| 35 | .ok_or_else(|| crate::Error::BadRequest { |
| 36 | detail: "SSSP requires source_node parameter".into(), |
| 37 | })?; |
| 38 | |
| 39 | let source_id = csr |
| 40 | .node_id_raw(source) |
| 41 | .ok_or_else(|| crate::Error::BadRequest { |
| 42 | detail: format!("source node '{source}' not found in graph"), |
| 43 | })?; |
| 44 | |
| 45 | // Dijkstra requires non-negative edge weights. Pre-scan for negatives |
| 46 | // to fail fast with a clear error rather than silently producing wrong results. |
| 47 | if csr.has_weights() { |
| 48 | for node in 0..n { |
| 49 | for (_lid, _dst, w) in csr.iter_out_edges_weighted_raw(node as u32) { |
| 50 | if w < 0.0 { |
| 51 | return Err(crate::Error::BadRequest { |
| 52 | detail: format!( |
| 53 | "SSSP (Dijkstra) requires non-negative edge weights, found {w} on edge from '{}'", |
| 54 | csr.node_name_raw(node as u32) |
| 55 | ), |
| 56 | }); |
| 57 | } |
| 58 | } |
| 59 | } |
| 60 | } |
| 61 | |
| 62 | let mut dist = vec![f64::INFINITY; n]; |
| 63 | dist[source_id as usize] = 0.0; |
| 64 | |
| 65 | // Min-heap: (Reverse(distance), node_id). |
| 66 | // Reverse wraps f64 for min-heap behavior with Rust's max-heap BinaryHeap. |
| 67 | let mut heap: BinaryHeap<Reverse<(OrdF64, u32)>> = BinaryHeap::new(); |
| 68 | heap.push(Reverse((OrdF64(0.0), source_id))); |
| 69 | |
| 70 | while let Some(Reverse((OrdF64(d), u))) = heap.pop() { |
| 71 | // Skip stale entries (lazy deletion). |
| 72 | if d > dist[u as usize] { |
| 73 | continue; |
| 74 | } |
| 75 | |
| 76 | // Relax outbound edges. |
| 77 | for (_lid, v, weight) in csr.iter_out_edges_weighted_raw(u) { |
| 78 | let new_dist = d + weight; |
| 79 | if new_dist < dist[v as usize] { |
| 80 | dist[v as usize] = new_dist; |
| 81 | heap.push(Reverse((OrdF64(new_dist), v))); |
| 82 | } |
| 83 | } |