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        let mut inner = DiGraph::new();
57        for _ in 0..num_vertices {
58            inner.add_node(());
59        }
60        for (u, v) in arcs {
61            assert!(
62                u < num_vertices && v < num_vertices,
63                "arc ({}, {}) references vertex >= num_vertices ({})",
64                u,
65                v,
66                num_vertices
67            );
68            inner.add_edge(NodeIndex::new(u), NodeIndex::new(v), ());
69        }
70        Self { inner }
71    }
72
73    /// Creates an empty directed graph with the given number of vertices and no arcs.
74    pub fn empty(num_vertices: usize) -> Self {
75        Self::new(num_vertices, vec![])
76    }
77
78    /// Returns the number of vertices in the graph.
79    pub fn num_vertices(&self) -> usize {
80        self.inner.node_count()
81    }
82
83    /// Returns the number of arcs in the graph.
84    pub fn num_arcs(&self) -> usize {
85        self.inner.edge_count()
86    }
87
88    /// Returns all arcs as `(source, target)` pairs.
89    pub fn arcs(&self) -> Vec<(usize, usize)> {
90        self.inner
91            .edge_references()
92            .map(|e| (e.source().index(), e.target().index()))
93            .collect()
94    }
95
96    /// Returns `true` if there is an arc from `u` to `v`.
97    pub fn has_arc(&self, u: usize, v: usize) -> bool {
98        self.inner
99            .find_edge(NodeIndex::new(u), NodeIndex::new(v))
100            .is_some()
101    }
102
103    /// Returns the outgoing neighbors (successors) of vertex `v`.
104    ///
105    /// These are all vertices `w` such that there is an arc `v → w`.
106    pub fn successors(&self, v: usize) -> Vec<usize> {
107        self.inner
108            .neighbors(NodeIndex::new(v))
109            .map(|n| n.index())
110            .collect()
111    }
112
113    /// Returns the incoming neighbors (predecessors) of vertex `v`.
114    ///
115    /// These are all vertices `u` such that there is an arc `u → v`.
116    pub fn predecessors(&self, v: usize) -> Vec<usize> {
117        self.inner
118            .neighbors_directed(NodeIndex::new(v), petgraph::Incoming)
119            .map(|n| n.index())
120            .collect()
121    }
122
123    /// Returns the out-degree of vertex `v`.
124    pub fn out_degree(&self, v: usize) -> usize {
125        self.successors(v).len()
126    }
127
128    /// Returns the in-degree of vertex `v`.
129    pub fn in_degree(&self, v: usize) -> usize {
130        self.predecessors(v).len()
131    }
132
133    /// Returns true if the graph has no vertices.
134    pub fn is_empty(&self) -> bool {
135        self.num_vertices() == 0
136    }
137
138    /// Returns `true` if the graph is a directed acyclic graph (DAG).
139    ///
140    /// Uses petgraph's topological sort to detect cycles: if a topological
141    /// ordering exists, the graph is acyclic.
142    pub fn is_dag(&self) -> bool {
143        toposort(&self.inner, None).is_ok()
144    }
145
146    /// Returns `true` if every vertex can reach every other vertex.
147    pub fn is_strongly_connected(&self) -> bool {
148        kosaraju_scc(&self.inner).len() <= 1
149    }
150
151    /// Check if the subgraph induced by keeping only the given arcs is acyclic (a DAG).
152    ///
153    /// `kept_arcs` is a boolean slice of length `num_arcs()`, where `true` means the arc is kept.
154    ///
155    /// # Panics
156    ///
157    /// Panics if `kept_arcs.len() != self.num_arcs()`.
158    pub fn is_acyclic_subgraph(&self, kept_arcs: &[bool]) -> bool {
159        assert_eq!(
160            kept_arcs.len(),
161            self.num_arcs(),
162            "kept_arcs slice length must equal num_arcs"
163        );
164        let n = self.num_vertices();
165        let arcs = self.arcs();
166
167        // Build adjacency list for the subgraph
168        let mut adj = vec![vec![]; n];
169        let mut in_degree = vec![0usize; n];
170        for (i, &(u, v)) in arcs.iter().enumerate() {
171            if kept_arcs[i] {
172                adj[u].push(v);
173                in_degree[v] += 1;
174            }
175        }
176
177        // Kahn's algorithm (topological sort)
178        let mut queue: Vec<usize> = (0..n).filter(|&v| in_degree[v] == 0).collect();
179        let mut visited = 0;
180        while let Some(u) = queue.pop() {
181            visited += 1;
182            for &v in &adj[u] {
183                in_degree[v] -= 1;
184                if in_degree[v] == 0 {
185                    queue.push(v);
186                }
187            }
188        }
189        visited == n
190    }
191
192    /// Returns the induced subgraph on vertices where `keep[v] == true`.
193    ///
194    /// Vertex indices are remapped to be contiguous starting from 0. An arc
195    /// `(u, v)` is included only if both `u` and `v` are kept. The new index
196    /// of a kept vertex is its rank among the kept vertices in increasing order.
197    ///
198    /// # Panics
199    ///
200    /// Panics if `keep.len() != self.num_vertices()`.
201    pub fn induced_subgraph(&self, keep: &[bool]) -> Self {
202        assert_eq!(
203            keep.len(),
204            self.num_vertices(),
205            "keep slice length must equal num_vertices"
206        );
207
208        // Build old index -> new index mapping
209        let mut new_index = vec![usize::MAX; self.num_vertices()];
210        let mut count = 0;
211        for (v, &kept) in keep.iter().enumerate() {
212            if kept {
213                new_index[v] = count;
214                count += 1;
215            }
216        }
217
218        let new_arcs: Vec<(usize, usize)> = self
219            .arcs()
220            .into_iter()
221            .filter(|&(u, v)| keep[u] && keep[v])
222            .map(|(u, v)| (new_index[u], new_index[v]))
223            .collect();
224
225        Self::new(count, new_arcs)
226    }
227}
228
229impl PartialEq for DirectedGraph {
230    fn eq(&self, other: &Self) -> bool {
231        if self.num_vertices() != other.num_vertices() {
232            return false;
233        }
234        if self.num_arcs() != other.num_arcs() {
235            return false;
236        }
237        let mut self_arcs = self.arcs();
238        let mut other_arcs = other.arcs();
239        self_arcs.sort();
240        other_arcs.sort();
241        self_arcs == other_arcs
242    }
243}
244
245impl Eq for DirectedGraph {}
246
247impl Serialize for DirectedGraph {
248    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
249        use serde::ser::SerializeStruct;
250        let mut state = serializer.serialize_struct("DirectedGraph", 2)?;
251        state.serialize_field("num_vertices", &self.num_vertices())?;
252        let arcs: Vec<(usize, usize)> = self.arcs();
253        state.serialize_field("arcs", &arcs)?;
254        state.end()
255    }
256}
257
258impl<'de> Deserialize<'de> for DirectedGraph {
259    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
260        #[derive(Deserialize)]
261        struct GraphData {
262            num_vertices: usize,
263            arcs: Vec<(usize, usize)>,
264        }
265        let data = GraphData::deserialize(deserializer)?;
266        Ok(DirectedGraph::new(data.num_vertices, data.arcs))
267    }
268}
269
270use crate::impl_variant_param;
271impl_variant_param!(DirectedGraph, "graph");
272
273#[cfg(test)]
274#[path = "../unit_tests/topology/directed_graph.rs"]
275mod tests;