Skip to main content

zrx_graph/graph/
topology.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//! Topology.
27
28use std::sync::Arc;
29
30use super::builder::Edge;
31
32mod adjacency;
33mod reachability;
34
35pub use adjacency::Adjacency;
36pub use reachability::{Direct, Transitive};
37
38// ----------------------------------------------------------------------------
39// Structs
40// ----------------------------------------------------------------------------
41
42/// Topology.
43///
44/// This data type represents the topology of a graph, which allows to find the
45/// outgoing and incoming edges for each node in linear time by using efficient
46/// adjacency lists. Our implementation does not support edge weights, as they
47/// would add unnecessary complexity and overhead.
48///
49/// Topologies can be [`Direct`] and [`Transitive`], the latter of which allows
50/// to determine whether one node is reachable from another. The [`Direct`]
51/// topology is the default, and can be converted on-demand.
52#[derive(Debug)]
53pub struct Topology<R = Direct> {
54    /// Inner state.
55    inner: Arc<Inner<R>>,
56}
57
58// ----------------------------------------------------------------------------
59
60/// Inner state.
61#[derive(Debug)]
62struct Inner<R> {
63    /// Outgoing edges.
64    outgoing: Adjacency,
65    /// Incoming edges.
66    incoming: Adjacency,
67    /// Reachability.
68    reachability: R,
69}
70
71// ----------------------------------------------------------------------------
72// Implementations
73// ----------------------------------------------------------------------------
74
75impl Topology<Direct> {
76    /// Creates a topology of the given graph.
77    ///
78    /// This method constructs a topology from a graph's nodes and edges, and is
79    /// the key component of an executable [`Graph`][]. It's usually not needed
80    /// to create a topology manually, as it's automatically created when the
81    /// graph is built using the [`Builder::build`][] method.
82    ///
83    /// [`Builder::build`]: crate::graph::Builder::build
84    /// [`Graph`]: crate::graph::Graph
85    ///
86    /// # Examples
87    ///
88    /// ```
89    /// # use std::error::Error;
90    /// # fn main() -> Result<(), Box<dyn Error>> {
91    /// use zrx_graph::{Graph, Topology};
92    ///
93    /// // Create graph builder and add nodes
94    /// let mut builder = Graph::builder();
95    /// let a = builder.add_node("a");
96    /// let b = builder.add_node("b");
97    /// let c = builder.add_node("c");
98    ///
99    /// // Create edges between nodes
100    /// builder.add_edge(a, b)?;
101    /// builder.add_edge(b, c)?;
102    ///
103    /// // Create topology
104    /// let topology = Topology::new(builder.len(), builder.edges());
105    /// # Ok(())
106    /// # }
107    /// ```
108    #[must_use]
109    pub fn new(nodes: usize, edges: &[Edge]) -> Self {
110        Self {
111            inner: Arc::new(Inner {
112                outgoing: Adjacency::outgoing(nodes, edges),
113                incoming: Adjacency::incoming(nodes, edges),
114                reachability: Direct,
115            }),
116        }
117    }
118
119    /// Converts this topology into one with transitive reachability.
120    ///
121    /// # Examples
122    ///
123    /// ```
124    /// # use std::error::Error;
125    /// # fn main() -> Result<(), Box<dyn Error>> {
126    /// use zrx_graph::{Graph, Topology};
127    ///
128    /// // Create graph builder and add nodes
129    /// let mut builder = Graph::builder();
130    /// let a = builder.add_node("a");
131    /// let b = builder.add_node("b");
132    /// let c = builder.add_node("c");
133    ///
134    /// // Create edges between nodes
135    /// builder.add_edge(a, b)?;
136    /// builder.add_edge(b, c)?;
137    ///
138    /// // Create transitive topology
139    /// let topology = Topology::new(builder.len(), builder.edges())
140    ///     .into_transitive();
141    /// # Ok(())
142    /// # }
143    /// ```
144    #[must_use]
145    pub fn into_transitive(self) -> Topology<Transitive> {
146        let inner = Arc::try_unwrap(self.inner) // fmt
147            .unwrap_or_else(|inner| Inner {
148                outgoing: inner.outgoing.clone(),
149                incoming: inner.incoming.clone(),
150                reachability: Direct,
151            });
152
153        // Create and return transitive topology
154        Topology {
155            inner: Arc::new(Inner {
156                reachability: Transitive::new(&inner.outgoing),
157                outgoing: inner.outgoing,
158                incoming: inner.incoming,
159            }),
160        }
161    }
162}
163
164impl Topology<Transitive> {
165    /// Returns whether there is a path from the source to the target.
166    #[inline]
167    #[must_use]
168    pub fn has_path(&self, source: usize, target: usize) -> bool {
169        self.inner.reachability.has_path(source, target)
170    }
171}
172
173#[allow(clippy::must_use_candidate)]
174impl<R> Topology<R> {
175    /// Returns a reference to the outgoing edges.
176    #[inline]
177    pub fn outgoing(&self) -> &Adjacency {
178        &self.inner.outgoing
179    }
180
181    /// Returns a reference to the incoming edges.
182    #[inline]
183    pub fn incoming(&self) -> &Adjacency {
184        &self.inner.incoming
185    }
186}
187
188// ----------------------------------------------------------------------------
189// Trait implementations
190// ----------------------------------------------------------------------------
191
192impl<R> PartialEq for Topology<R> {
193    /// Compares two topologies for equality.
194    ///
195    /// # Examples
196    ///
197    /// ```
198    /// # use std::error::Error;
199    /// # fn main() -> Result<(), Box<dyn Error>> {
200    /// use zrx_graph::{Graph, Topology};
201    ///
202    /// // Create graph builder and add nodes
203    /// let mut builder = Graph::builder();
204    /// let a = builder.add_node("a");
205    /// let b = builder.add_node("b");
206    /// let c = builder.add_node("c");
207    ///
208    /// // Create edges between nodes
209    /// builder.add_edge(a, b)?;
210    /// builder.add_edge(b, c)?;
211    ///
212    /// // Create and compare topologies
213    /// let topology = Topology::new(builder.len(), builder.edges());
214    /// assert_eq!(topology, topology.clone());
215    /// # Ok(())
216    /// # }
217    /// ```
218    #[inline]
219    fn eq(&self, other: &Self) -> bool {
220        Arc::ptr_eq(&self.inner, &other.inner)
221    }
222}
223
224impl<R> Eq for Topology<R> {}
225
226// ----------------------------------------------------------------------------
227
228impl<R> Clone for Topology<R> {
229    /// Clones the topology.
230    #[inline]
231    fn clone(&self) -> Self {
232        Self { inner: self.inner.clone() }
233    }
234}