Skip to main content

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}