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}