zrx_graph/graph/topology/distance.rs
1// Copyright (c) 2025-2026 Zensical and contributors
2
3// SPDX-License-Identifier: MIT
4// All contributions are certified under the DCO
5
6// Permission is hereby granted, free of charge, to any person obtaining a copy
7// of this software and associated documentation files (the "Software"), to
8// deal in the Software without restriction, including without limitation the
9// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10// sell copies of the Software, and to permit persons to whom the Software is
11// furnished to do so, subject to the following conditions:
12
13// The above copyright notice and this permission notice shall be included in
14// all copies or substantial portions of the Software.
15
16// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
19// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
22// IN THE SOFTWARE.
23
24// ----------------------------------------------------------------------------
25
26//! Distance matrix.
27
28use std::ops::Index;
29
30use super::adjacency::Adjacency;
31
32// ----------------------------------------------------------------------------
33// Structs
34// ----------------------------------------------------------------------------
35
36/// Distance matrix.
37///
38/// This data type stores the all-pairs shortest path distances for all nodes
39/// in a directed acyclic graph (DAG). It's computed through the Floyd-Warshall
40/// algorithm, and allows for efficient retrieval of distances between any two
41/// nodes, which is essential for many graph algorithms.
42#[derive(Debug)]
43pub struct Distance {
44 /// Row number.
45 rows: usize,
46 /// Column values.
47 columns: Vec<u8>,
48}
49
50// ----------------------------------------------------------------------------
51// Implementations
52// ----------------------------------------------------------------------------
53
54impl Distance {
55 /// Creates a distance matrix.
56 ///
57 /// This method is called by [`Topology::new`][], and is not intended to be
58 /// used on its own, since an adjacency list is needed to create the matrix.
59 /// Computation is expensive, which is why [`Topology`] will defer creation
60 /// via [`OnceCell`], so it's only computed when first accessed.
61 ///
62 /// [`OnceCell`]: std::cell::OnceCell
63 /// [`Topology`]: crate::graph::topology::Topology
64 /// [`Topology::new`]: crate::graph::topology::Topology::new
65 #[must_use]
66 pub fn new(adj: &Adjacency) -> Self {
67 let nodes = adj.len();
68 let mut data = vec![u8::MAX; nodes * nodes];
69
70 // Initialize the distance for all nodes to themselves to 0
71 for source in adj {
72 data[source * nodes + source] = 0;
73
74 // Initialize the distances for all directed edges to 1
75 for &target in &adj[source] {
76 data[source * nodes + target] = 1;
77 }
78 }
79
80 // Create distance matrix and compute all-pairs shortest paths
81 let mut dist = Self { rows: nodes, columns: data };
82 floyd_warshall(&mut dist);
83 dist
84 }
85}
86
87// ----------------------------------------------------------------------------
88// Trait implementations
89// ----------------------------------------------------------------------------
90
91impl Index<usize> for Distance {
92 type Output = [u8];
93
94 /// Returns the column values for the row at the index.
95 ///
96 /// This method returns a slice representing the distances from the node as
97 /// identified by the given index to all other nodes in the graph. Distances
98 /// are represented as the number of edges on the shortest path between the
99 /// nodes. For all unreachable nodes, the distance equals [`u8::MAX`].
100 ///
101 /// # Panics
102 ///
103 /// Panics if the index is out of bounds.
104 ///
105 /// # Examples
106 ///
107 /// ```
108 /// # use std::error::Error;
109 /// # fn main() -> Result<(), Box<dyn Error>> {
110 /// use zrx_graph::topology::Topology;
111 /// use zrx_graph::Graph;
112 ///
113 /// // Create graph builder and add nodes
114 /// let mut builder = Graph::builder();
115 /// let a = builder.add_node("a");
116 /// let b = builder.add_node("b");
117 /// let c = builder.add_node("c");
118 ///
119 /// // Create edges between nodes
120 /// builder.add_edge(a, b)?;
121 /// builder.add_edge(b, c)?;
122 ///
123 /// // Create topology
124 /// let topology = Topology::new(builder.len(), builder.edges());
125 ///
126 /// // Obtain distance matrix
127 /// let dist = topology.distance();
128 /// assert_eq!(dist[a][c], 2);
129 /// # Ok(())
130 /// # }
131 /// ```
132 #[inline]
133 fn index(&self, index: usize) -> &Self::Output {
134 let start = index * self.rows;
135 let end = start + self.rows;
136 &self.columns[start..end]
137 }
138}
139
140// ----------------------------------------------------------------------------
141// Functions
142// ----------------------------------------------------------------------------
143
144/// Executes the Floyd-Warshall algorithm to compute all-pairs shortest paths,
145/// updating the distance matrix in-place. While Floyd-Warshall is not the most
146/// efficient algorithm for sparse graphs, it is simple and effective enough to
147/// implement, and should be sufficient for our case.
148fn floyd_warshall(dist: &mut Distance) {
149 let n = dist.rows;
150 for k in 0..n {
151 for i in 0..n {
152 // Obtain distance from i to k, then check whether the path is
153 // marked as reachable, and obtain all distances from k to j
154 let i_to_k = dist.columns[i * n + k];
155 if i_to_k != u8::MAX {
156 for j in 0..n {
157 // If j is reachable from k, compute the distance from
158 // i to j via k, and update the distance matrix
159 let k_to_j = dist.columns[k * n + j];
160 if k_to_j != u8::MAX {
161 let value = i_to_k + k_to_j;
162
163 // Update the distance matrix
164 let i_to_j = &mut dist.columns[i * n + j];
165 if *i_to_j > value {
166 *i_to_j = value;
167 }
168 }
169 }
170 }
171 }
172 }
173}