zrx_graph/graph/topology/distance.rs
1// Copyright (c) 2025 Zensical and contributors
2
3// SPDX-License-Identifier: MIT
4// Third-party contributions licensed under 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::Builder;
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 /// # Examples
58 ///
59 /// ```
60 /// # use std::error::Error;
61 /// # fn main() -> Result<(), Box<dyn Error>> {
62 /// use zrx_graph::topology::Distance;
63 /// use zrx_graph::Graph;
64 ///
65 /// // Create graph builder and add nodes
66 /// let mut builder = Graph::builder();
67 /// let a = builder.add_node("a");
68 /// let b = builder.add_node("b");
69 /// let c = builder.add_node("c");
70 ///
71 /// // Create edges between nodes
72 /// builder.add_edge(a, b, 0)?;
73 /// builder.add_edge(b, c, 0)?;
74 ///
75 /// // Create distance matrix
76 /// let dist = Distance::new(&builder);
77 /// assert_eq!(dist[a][c], 2);
78 /// # Ok(())
79 /// # }
80 /// ```
81 #[must_use]
82 pub fn new<T, W>(builder: &Builder<T, W>) -> Self
83 where
84 W: Clone,
85 {
86 let nodes = builder.len();
87 let mut data = vec![u8::MAX; nodes * nodes];
88
89 // Initialize the distance for all nodes to themselves to 0
90 for index in 0..nodes {
91 data[index * nodes + index] = 0;
92 }
93
94 // Initialize the distances for all directed edges to 1
95 for edge in builder.edges() {
96 data[edge.source * nodes + edge.target] = 1;
97 }
98
99 // Create distance matrix and compute all-pairs shortest paths
100 let mut dist = Self { rows: nodes, columns: data };
101 floyd_warshall(&mut dist);
102 dist
103 }
104}
105
106// ----------------------------------------------------------------------------
107// Trait implementations
108// ----------------------------------------------------------------------------
109
110impl Index<usize> for Distance {
111 type Output = [u8];
112
113 /// Returns the column values for the given row.
114 ///
115 /// This method returns a slice representing the distances from the node as
116 /// identified by the given index to all other nodes in the graph. Distances
117 /// are represented as the number of edges on the shortest path between the
118 /// nodes. For all unreachable nodes, the distance equals [`u8::MAX`].
119 ///
120 /// # Panics
121 ///
122 /// Panics if the index is out of bounds.
123 ///
124 /// # Examples
125 ///
126 /// ```
127 /// # use std::error::Error;
128 /// # fn main() -> Result<(), Box<dyn Error>> {
129 /// use zrx_graph::topology::Distance;
130 /// use zrx_graph::Graph;
131 ///
132 /// // Create graph builder and add nodes
133 /// let mut builder = Graph::builder();
134 /// let a = builder.add_node("a");
135 /// let b = builder.add_node("b");
136 /// let c = builder.add_node("c");
137 ///
138 /// // Create edges between nodes
139 /// builder.add_edge(a, b, 0)?;
140 /// builder.add_edge(b, c, 0)?;
141 ///
142 /// // Create distance matrix
143 /// let dist = Distance::new(&builder);
144 /// assert_eq!(dist[a][c], 2);
145 /// # Ok(())
146 /// # }
147 /// ```
148 #[inline]
149 fn index(&self, index: usize) -> &Self::Output {
150 let start = index * self.rows;
151 let end = start + self.rows;
152 &self.columns[start..end]
153 }
154}
155
156// ----------------------------------------------------------------------------
157// Functions
158// ----------------------------------------------------------------------------
159
160/// Execute Floyd-Warshall algorithm to compute the all-pairs shortest paths,
161/// updating the distance matrix in-place. While Floyd-Warshall is not the most
162/// efficient algorithm for sparse graphs, it is simple and effective enough to
163/// implement, and should be sufficient for our case.
164fn floyd_warshall(dist: &mut Distance) {
165 let n = dist.rows;
166 for k in 0..n {
167 for i in 0..n {
168 // Obtain distance from i to k, then check whether the path is
169 // marked as reachable, and obtain all distances from k to j
170 let i_to_k = dist.columns[i * n + k];
171 if i_to_k != u8::MAX {
172 for j in 0..n {
173 // If j is reachable from k, compute the distance from
174 // i to j via k, and update the distance matrix
175 let k_to_j = dist.columns[k * n + j];
176 if k_to_j != u8::MAX {
177 let value = i_to_k + k_to_j;
178
179 // Update the distance matrix
180 let i_to_j = &mut dist.columns[i * n + j];
181 if *i_to_j > value {
182 *i_to_j = value;
183 }
184 }
185 }
186 }
187 }
188 }
189}