zrx_graph/graph/property.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//! Graph properties.
27
28use std::collections::VecDeque;
29
30use super::topology::Transitive;
31use super::Graph;
32
33// ----------------------------------------------------------------------------
34// Implementations
35// ----------------------------------------------------------------------------
36
37impl<T, R> Graph<T, R> {
38 /// Returns whether the given node is a source.
39 ///
40 /// # Panics
41 ///
42 /// Panics if the node does not exist.
43 ///
44 /// # Examples
45 ///
46 /// ```
47 /// # use std::error::Error;
48 /// # fn main() -> Result<(), Box<dyn Error>> {
49 /// use zrx_graph::Graph;
50 ///
51 /// // Create graph builder and add nodes
52 /// let mut builder = Graph::builder();
53 /// let a = builder.add_node("a");
54 /// let b = builder.add_node("b");
55 /// let c = builder.add_node("c");
56 ///
57 /// // Create edges between nodes
58 /// builder.add_edge(a, b)?;
59 /// builder.add_edge(b, c)?;
60 ///
61 /// // Create graph from builder
62 /// let graph = builder.build();
63 ///
64 /// // Ensure nodes are sources (or not)
65 /// assert_eq!(graph.is_source(a), true);
66 /// assert_eq!(graph.is_source(b), false);
67 /// assert_eq!(graph.is_source(c), false);
68 /// # Ok(())
69 /// # }
70 /// ```
71 #[inline]
72 #[must_use]
73 pub fn is_source(&self, node: usize) -> bool {
74 let incoming = self.topology.incoming();
75 incoming[node].is_empty()
76 }
77
78 /// Returns whether the given node is a sink.
79 ///
80 /// # Panics
81 ///
82 /// Panics if the node does not exist.
83 ///
84 /// # Examples
85 ///
86 /// ```
87 /// # use std::error::Error;
88 /// # fn main() -> Result<(), Box<dyn Error>> {
89 /// use zrx_graph::Graph;
90 ///
91 /// // Create graph builder and add nodes
92 /// let mut builder = Graph::builder();
93 /// let a = builder.add_node("a");
94 /// let b = builder.add_node("b");
95 /// let c = builder.add_node("c");
96 ///
97 /// // Create edges between nodes
98 /// builder.add_edge(a, b)?;
99 /// builder.add_edge(b, c)?;
100 ///
101 /// // Create graph from builder
102 /// let graph = builder.build();
103 ///
104 /// // Ensure nodes are sinks (or not)
105 /// assert_eq!(graph.is_sink(a), false);
106 /// assert_eq!(graph.is_sink(b), false);
107 /// assert_eq!(graph.is_sink(c), true);
108 /// # Ok(())
109 /// # }
110 /// ```
111 #[inline]
112 #[must_use]
113 pub fn is_sink(&self, node: usize) -> bool {
114 let outgoing = self.topology.outgoing();
115 outgoing[node].is_empty()
116 }
117
118 /// Returns whether the source node is a predecessor of the target node.
119 ///
120 /// # Panics
121 ///
122 /// Panics if the node does not exist.
123 ///
124 /// # Examples
125 ///
126 /// ```
127 /// # use std::error::Error;
128 /// # fn main() -> Result<(), Box<dyn Error>> {
129 /// use zrx_graph::Graph;
130 ///
131 /// // Create graph builder and add nodes
132 /// let mut builder = Graph::builder();
133 /// let a = builder.add_node("a");
134 /// let b = builder.add_node("b");
135 /// let c = builder.add_node("c");
136 ///
137 /// // Create edges between nodes
138 /// builder.add_edge(a, b)?;
139 /// builder.add_edge(b, c)?;
140 ///
141 /// // Create graph from builder
142 /// let graph = builder.build();
143 ///
144 /// // Ensure nodes are predecessors (or not)
145 /// assert_eq!(graph.is_predecessor(a, b), true);
146 /// assert_eq!(graph.is_predecessor(a, c), false);
147 /// assert_eq!(graph.is_predecessor(b, a), false);
148 /// # Ok(())
149 /// # }
150 /// ```
151 #[inline]
152 #[must_use]
153 pub fn is_predecessor(&self, source: usize, target: usize) -> bool {
154 let outgoing = self.topology.outgoing();
155 outgoing[source].contains(&target)
156 }
157
158 /// Returns whether the source node is a successor of the target node.
159 ///
160 /// # Panics
161 ///
162 /// Panics if the node does not exist.
163 ///
164 /// # Examples
165 ///
166 /// ```
167 /// # use std::error::Error;
168 /// # fn main() -> Result<(), Box<dyn Error>> {
169 /// use zrx_graph::Graph;
170 ///
171 /// // Create graph builder and add nodes
172 /// let mut builder = Graph::builder();
173 /// let a = builder.add_node("a");
174 /// let b = builder.add_node("b");
175 /// let c = builder.add_node("c");
176 ///
177 /// // Create edges between nodes
178 /// builder.add_edge(a, b)?;
179 /// builder.add_edge(b, c)?;
180 ///
181 /// // Create graph from builder
182 /// let graph = builder.build();
183 ///
184 /// // Ensure nodes are successors (or not)
185 /// assert_eq!(graph.is_successor(b, a), true);
186 /// assert_eq!(graph.is_successor(c, a), false);
187 /// assert_eq!(graph.is_successor(a, b), false);
188 /// # Ok(())
189 /// # }
190 /// ```
191 #[inline]
192 #[must_use]
193 pub fn is_successor(&self, source: usize, target: usize) -> bool {
194 let incoming = self.topology.incoming();
195 incoming[source].contains(&target)
196 }
197
198 /// Returns whether the graph is acyclic.
199 ///
200 /// # Examples
201 ///
202 /// ```
203 /// # use std::error::Error;
204 /// # fn main() -> Result<(), Box<dyn Error>> {
205 /// use zrx_graph::Graph;
206 ///
207 /// // Create graph builder and add nodes
208 /// let mut builder = Graph::builder();
209 /// let a = builder.add_node("a");
210 /// let b = builder.add_node("b");
211 /// let c = builder.add_node("c");
212 ///
213 /// // Create edges between nodes
214 /// builder.add_edge(a, b)?;
215 /// builder.add_edge(b, c)?;
216 ///
217 /// // Create graph from builder
218 /// let graph = builder.build();
219 ///
220 /// // Ensure there are no cycles
221 /// assert_eq!(graph.is_acyclic(), true);
222 /// # Ok(())
223 /// # }
224 /// ```
225 #[must_use]
226 pub fn is_acyclic(&self) -> bool {
227 let incoming = self.topology.incoming();
228 let outgoing = self.topology.outgoing();
229
230 // Initialize in-degrees and seed queue with source nodes
231 let mut degrees = incoming.degrees().to_vec();
232 let mut queue: VecDeque<usize> =
233 self.iter().filter(|&node| degrees[node] == 0).collect();
234
235 // Process nodes using Kahn's algorithm for topological sorting
236 let mut visited = 0;
237 while let Some(source) = queue.pop_front() {
238 visited += 1;
239
240 // Decrement in-degrees of successors, enqueuing new sources
241 for &target in &outgoing[source] {
242 degrees[target] -= 1;
243 if degrees[target] == 0 {
244 queue.push_back(target);
245 }
246 }
247 }
248
249 // Graph is acyclic if all nodes were visited
250 visited == self.len()
251 }
252}
253
254impl<T> Graph<T, Transitive> {
255 /// Returns whether the source node is an ancestor of the target node.
256 ///
257 /// # Panics
258 ///
259 /// Panics if the node does not exist.
260 ///
261 /// # Examples
262 ///
263 /// ```
264 /// # use std::error::Error;
265 /// # fn main() -> Result<(), Box<dyn Error>> {
266 /// use zrx_graph::Graph;
267 ///
268 /// // Create graph builder and add nodes
269 /// let mut builder = Graph::builder();
270 /// let a = builder.add_node("a");
271 /// let b = builder.add_node("b");
272 /// let c = builder.add_node("c");
273 ///
274 /// // Create edges between nodes
275 /// builder.add_edge(a, b)?;
276 /// builder.add_edge(b, c)?;
277 ///
278 /// // Create graph from builder
279 /// let graph = builder.build().into_transitive();
280 ///
281 /// // Ensure nodes are ancestors (or not)
282 /// assert_eq!(graph.is_ancestor(a, b), true);
283 /// assert_eq!(graph.is_ancestor(a, c), true);
284 /// assert_eq!(graph.is_ancestor(b, a), false);
285 /// # Ok(())
286 /// # }
287 /// ```
288 #[inline]
289 #[must_use]
290 pub fn is_ancestor(&self, source: usize, target: usize) -> bool {
291 self.topology.has_path(source, target)
292 }
293
294 /// Returns whether the source node is a descendant of the target node.
295 ///
296 /// # Panics
297 ///
298 /// Panics if the node does not exist.
299 ///
300 /// # Examples
301 ///
302 /// ```
303 /// # use std::error::Error;
304 /// # fn main() -> Result<(), Box<dyn Error>> {
305 /// use zrx_graph::Graph;
306 ///
307 /// // Create graph builder and add nodes
308 /// let mut builder = Graph::builder();
309 /// let a = builder.add_node("a");
310 /// let b = builder.add_node("b");
311 /// let c = builder.add_node("c");
312 ///
313 /// // Create edges between nodes
314 /// builder.add_edge(a, b)?;
315 /// builder.add_edge(b, c)?;
316 ///
317 /// // Create graph from builder
318 /// let graph = builder.build().into_transitive();
319 ///
320 /// // Ensure nodes are descendants (or not)
321 /// assert_eq!(graph.is_descendant(b, a), true);
322 /// assert_eq!(graph.is_descendant(c, a), true);
323 /// assert_eq!(graph.is_descendant(a, b), false);
324 /// # Ok(())
325 /// # }
326 /// ```
327 #[inline]
328 #[must_use]
329 pub fn is_descendant(&self, source: usize, target: usize) -> bool {
330 self.topology.has_path(target, source)
331 }
332}