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}