Skip to main content

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}