Skip to main content

problemreductions/topology/
directed_graph.rs

1//! Directed graph implementation.
2//!
3//! This module provides [`DirectedGraph`], a directed graph wrapping petgraph's
4//! `DiGraph`. It is used for problems that require directed input, such as
5//! [`MinimumFeedbackVertexSet`] and [`MinimumFeedbackArcSet`].
6//!
7//! Unlike [`SimpleGraph`], `DirectedGraph` does **not** implement the [`Graph`]
8//! trait (which is specific to undirected graphs). Arcs are ordered pairs `(u, v)`
9//! representing a directed edge from `u` to `v`.
10//!
11//! [`SimpleGraph`]: crate::topology::SimpleGraph
12//! [`Graph`]: crate::topology::Graph
13//! [`MinimumFeedbackVertexSet`]: crate::models::graph::MinimumFeedbackVertexSet
14//! [`MinimumFeedbackArcSet`]: crate::models::graph::MinimumFeedbackArcSet
15
16use petgraph::algo::{kosaraju_scc, toposort};
17use petgraph::graph::{DiGraph, NodeIndex};
18use petgraph::visit::EdgeRef;
19use serde::{Deserialize, Serialize};
20
21/// A simple unweighted directed graph.
22///
23/// Arcs are represented as ordered pairs `(u, v)` meaning there is an arc
24/// from vertex `u` to vertex `v`. Self-loops are permitted.
25///
26/// # Example
27///
28/// ```
29/// use problemreductions::topology::DirectedGraph;
30///
31/// let g = DirectedGraph::new(3, vec![(0, 1), (1, 2)]);
32/// assert_eq!(g.num_vertices(), 3);
33/// assert_eq!(g.num_arcs(), 2);
34/// assert!(g.is_dag());
35///
36/// let cyclic = DirectedGraph::new(3, vec![(0, 1), (1, 2), (2, 0)]);
37/// assert!(!cyclic.is_dag());
38/// ```
39#[derive(Debug, Clone)]
40pub struct DirectedGraph {
41    inner: DiGraph<(), ()>,
42}
43
44impl DirectedGraph {
45    /// Creates a new directed graph with the given vertices and arcs.
46    ///
47    /// # Arguments
48    ///
49    /// * `num_vertices` - Number of vertices in the graph
50    /// * `arcs` - List of arcs as `(source, target)` pairs
51    ///
52    /// # Panics
53    ///
54    /// Panics if any arc references a vertex index >= `num_vertices`.
55    pub fn new(num_vertices: usize, arcs: Vec<(usize, usize)>) -> Self {
56        Self::try_new(num_vertices, arcs).unwrap_or_else(|error| panic!("{error}"))
57    }
58
59    fn try_new(
60        num_vertices: usize,
61        arcs: Vec<(usize, usize)>,
62    ) -> Result<Self, crate::registry::ConstructionError> {
63        let mut inner = DiGraph::new();
64        for _ in 0..num_vertices {
65            inner.add_node(());
66        }
67        for (u, v) in arcs {
68            if !(u < num_vertices && v < num_vertices) {
69                return Err(format!(
70                    "arc ({}, {}) references vertex >= num_vertices ({})",
71                    u, v, num_vertices
72                )
73                .into());
74            }
75            inner.add_edge(NodeIndex::new(u), NodeIndex::new(v), ());
76        }
77        Ok(Self { inner })
78    }
79
80    /// Creates an empty directed graph with the given number of vertices and no arcs.
81    pub fn empty(num_vertices: usize) -> Self {
82        Self::new(num_vertices, vec![])
83    }
84
85    /// Returns the number of vertices in the graph.
86    pub fn num_vertices(&self) -> usize {
87        self.inner.node_count()
88    }
89
90    /// Returns the number of arcs in the graph.
91    pub fn num_arcs(&self) -> usize {
92        self.inner.edge_count()
93    }
94
95    /// Returns all arcs as `(source, target)` pairs.
96    pub fn arcs(&self) -> Vec<(usize, usize)> {
97        self.inner
98            .edge_references()
99            .map(|e| (e.source().index(), e.target().index()))
100            .collect()
101    }
102
103    /// Returns `true` if there is an arc from `u` to `v`.
104    pub fn has_arc(&self, u: usize, v: usize) -> bool {
105        self.inner
106            .find_edge(NodeIndex::new(u), NodeIndex::new(v))
107            .is_some()
108    }
109
110    /// Returns the outgoing neighbors (successors) of vertex `v`.
111    ///
112    /// These are all vertices `w` such that there is an arc `v → w`.
113    pub fn successors(&self, v: usize) -> Vec<usize> {
114        self.inner
115            .neighbors(NodeIndex::new(v))
116            .map(|n| n.index())
117            .collect()
118    }
119
120    /// Returns the incoming neighbors (predecessors) of vertex `v`.
121    ///
122    /// These are all vertices `u` such that there is an arc `u → v`.
123    pub fn predecessors(&self, v: usize) -> Vec<usize> {
124        self.inner
125            .neighbors_directed(NodeIndex::new(v), petgraph::Incoming)
126            .map(|n| n.index())
127            .collect()
128    }
129
130    /// Returns the out-degree of vertex `v`.
131    pub fn out_degree(&self, v: usize) -> usize {
132        self.successors(v).len()
133    }
134
135    /// Returns the in-degree of vertex `v`.
136    pub fn in_degree(&self, v: usize) -> usize {
137        self.predecessors(v).len()
138    }
139
140    /// Returns true if the graph has no vertices.
141    pub fn is_empty(&self) -> bool {
142        self.num_vertices() == 0
143    }
144
145    /// Returns `true` if the graph is a directed acyclic graph (DAG).
146    ///
147    /// Uses petgraph's topological sort to detect cycles: if a topological
148    /// ordering exists, the graph is acyclic.
149    pub fn is_dag(&self) -> bool {
150        toposort(&self.inner, None).is_ok()
151    }
152
153    /// Returns `true` if every vertex can reach every other vertex.
154    pub fn is_strongly_connected(&self) -> bool {
155        kosaraju_scc(&self.inner).len() <= 1
156    }
157
158    /// Check if the subgraph induced by keeping only the given arcs is acyclic (a DAG).
159    ///
160    /// `kept_arcs` is a boolean slice of length `num_arcs()`, where `true` means the arc is kept.
161    ///
162    /// # Panics
163    ///
164    /// Panics if `kept_arcs.len() != self.num_arcs()`.
165    pub fn is_acyclic_subgraph(&self, kept_arcs: &[bool]) -> bool {
166        assert_eq!(
167            kept_arcs.len(),
168            self.num_arcs(),
169            "kept_arcs slice length must equal num_arcs"
170        );
171        let n = self.num_vertices();
172        let arcs = self.arcs();
173
174        // Build adjacency list for the subgraph
175        let mut adj = vec![vec![]; n];
176        let mut in_degree = vec![0usize; n];
177        for (i, &(u, v)) in arcs.iter().enumerate() {
178            if kept_arcs[i] {
179                adj[u].push(v);
180                in_degree[v] += 1;
181            }
182        }
183
184        // Kahn's algorithm (topological sort)
185        let mut queue: Vec<usize> = (0..n).filter(|&v| in_degree[v] == 0).collect();
186        let mut visited = 0;
187        while let Some(u) = queue.pop() {
188            visited += 1;
189            for &v in &adj[u] {
190                in_degree[v] -= 1;
191                if in_degree[v] == 0 {
192                    queue.push(v);
193                }
194            }
195        }
196        visited == n
197    }
198
199    /// Returns the induced subgraph on vertices where `keep[v] == true`.
200    ///
201    /// Vertex indices are remapped to be contiguous starting from 0. An arc
202    /// `(u, v)` is included only if both `u` and `v` are kept. The new index
203    /// of a kept vertex is its rank among the kept vertices in increasing order.
204    ///
205    /// # Panics
206    ///
207    /// Panics if `keep.len() != self.num_vertices()`.
208    pub fn induced_subgraph(&self, keep: &[bool]) -> Self {
209        assert_eq!(
210            keep.len(),
211            self.num_vertices(),
212            "keep slice length must equal num_vertices"
213        );
214
215        // Build old index -> new index mapping
216        let mut new_index = vec![usize::MAX; self.num_vertices()];
217        let mut count = 0;
218        for (v, &kept) in keep.iter().enumerate() {
219            if kept {
220                new_index[v] = count;
221                count += 1;
222            }
223        }
224
225        let new_arcs: Vec<(usize, usize)> = self
226            .arcs()
227            .into_iter()
228            .filter(|&(u, v)| keep[u] && keep[v])
229            .map(|(u, v)| (new_index[u], new_index[v]))
230            .collect();
231
232        Self::new(count, new_arcs)
233    }
234}
235
236impl PartialEq for DirectedGraph {
237    fn eq(&self, other: &Self) -> bool {
238        if self.num_vertices() != other.num_vertices() {
239            return false;
240        }
241        if self.num_arcs() != other.num_arcs() {
242            return false;
243        }
244        let mut self_arcs = self.arcs();
245        let mut other_arcs = other.arcs();
246        self_arcs.sort();
247        other_arcs.sort();
248        self_arcs == other_arcs
249    }
250}
251
252impl Eq for DirectedGraph {}
253
254impl Serialize for DirectedGraph {
255    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
256        use serde::ser::SerializeStruct;
257        let mut state = serializer.serialize_struct("DirectedGraph", 2)?;
258        state.serialize_field("num_vertices", &self.num_vertices())?;
259        let arcs: Vec<(usize, usize)> = self.arcs();
260        state.serialize_field("arcs", &arcs)?;
261        state.end()
262    }
263}
264
265impl<'de> Deserialize<'de> for DirectedGraph {
266    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
267        #[derive(Deserialize)]
268        struct GraphData {
269            num_vertices: usize,
270            arcs: Vec<(usize, usize)>,
271        }
272        let data = GraphData::deserialize(deserializer)?;
273        DirectedGraph::try_new(data.num_vertices, data.arcs).map_err(serde::de::Error::custom)
274    }
275}
276
277use crate::impl_variant_param;
278impl_variant_param!(DirectedGraph, "graph");
279
280#[cfg(test)]
281#[path = "../unit_tests/topology/directed_graph.rs"]
282mod tests;