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`].
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
15use petgraph::algo::toposort;
16use petgraph::graph::{DiGraph, NodeIndex};
17use petgraph::visit::EdgeRef;
18use serde::{Deserialize, Serialize};
19
20/// A simple unweighted directed graph.
21///
22/// Arcs are represented as ordered pairs `(u, v)` meaning there is an arc
23/// from vertex `u` to vertex `v`. Self-loops are permitted.
24///
25/// # Example
26///
27/// ```
28/// use problemreductions::topology::DirectedGraph;
29///
30/// let g = DirectedGraph::new(3, vec![(0, 1), (1, 2)]);
31/// assert_eq!(g.num_vertices(), 3);
32/// assert_eq!(g.num_arcs(), 2);
33/// assert!(g.is_dag());
34///
35/// let cyclic = DirectedGraph::new(3, vec![(0, 1), (1, 2), (2, 0)]);
36/// assert!(!cyclic.is_dag());
37/// ```
38#[derive(Debug, Clone, Serialize, Deserialize)]
39pub struct DirectedGraph {
40    inner: DiGraph<(), ()>,
41}
42
43impl DirectedGraph {
44    /// Creates a new directed graph with the given vertices and arcs.
45    ///
46    /// # Arguments
47    ///
48    /// * `num_vertices` - Number of vertices in the graph
49    /// * `arcs` - List of arcs as `(source, target)` pairs
50    ///
51    /// # Panics
52    ///
53    /// Panics if any arc references a vertex index >= `num_vertices`.
54    pub fn new(num_vertices: usize, arcs: Vec<(usize, usize)>) -> Self {
55        let mut inner = DiGraph::new();
56        for _ in 0..num_vertices {
57            inner.add_node(());
58        }
59        for (u, v) in arcs {
60            assert!(
61                u < num_vertices && v < num_vertices,
62                "arc ({}, {}) references vertex >= num_vertices ({})",
63                u,
64                v,
65                num_vertices
66            );
67            inner.add_edge(NodeIndex::new(u), NodeIndex::new(v), ());
68        }
69        Self { inner }
70    }
71
72    /// Creates an empty directed graph with the given number of vertices and no arcs.
73    pub fn empty(num_vertices: usize) -> Self {
74        Self::new(num_vertices, vec![])
75    }
76
77    /// Returns the number of vertices in the graph.
78    pub fn num_vertices(&self) -> usize {
79        self.inner.node_count()
80    }
81
82    /// Returns the number of arcs in the graph.
83    pub fn num_arcs(&self) -> usize {
84        self.inner.edge_count()
85    }
86
87    /// Returns all arcs as `(source, target)` pairs.
88    pub fn arcs(&self) -> Vec<(usize, usize)> {
89        self.inner
90            .edge_references()
91            .map(|e| (e.source().index(), e.target().index()))
92            .collect()
93    }
94
95    /// Returns `true` if there is an arc from `u` to `v`.
96    pub fn has_arc(&self, u: usize, v: usize) -> bool {
97        self.inner
98            .find_edge(NodeIndex::new(u), NodeIndex::new(v))
99            .is_some()
100    }
101
102    /// Returns the outgoing neighbors (successors) of vertex `v`.
103    ///
104    /// These are all vertices `w` such that there is an arc `v → w`.
105    pub fn successors(&self, v: usize) -> Vec<usize> {
106        self.inner
107            .neighbors(NodeIndex::new(v))
108            .map(|n| n.index())
109            .collect()
110    }
111
112    /// Returns the incoming neighbors (predecessors) of vertex `v`.
113    ///
114    /// These are all vertices `u` such that there is an arc `u → v`.
115    pub fn predecessors(&self, v: usize) -> Vec<usize> {
116        self.inner
117            .neighbors_directed(NodeIndex::new(v), petgraph::Direction::Incoming)
118            .map(|n| n.index())
119            .collect()
120    }
121
122    /// Returns `true` if the graph is a directed acyclic graph (DAG).
123    ///
124    /// Uses petgraph's topological sort to detect cycles: if a topological
125    /// ordering exists, the graph is acyclic.
126    pub fn is_dag(&self) -> bool {
127        toposort(&self.inner, None).is_ok()
128    }
129
130    /// Returns the induced subgraph on vertices where `keep[v] == true`.
131    ///
132    /// Vertex indices are remapped to be contiguous starting from 0. An arc
133    /// `(u, v)` is included only if both `u` and `v` are kept. The new index
134    /// of a kept vertex is its rank among the kept vertices in increasing order.
135    ///
136    /// # Panics
137    ///
138    /// Panics if `keep.len() != self.num_vertices()`.
139    pub fn induced_subgraph(&self, keep: &[bool]) -> Self {
140        assert_eq!(
141            keep.len(),
142            self.num_vertices(),
143            "keep slice length must equal num_vertices"
144        );
145
146        // Build old index -> new index mapping
147        let mut new_index = vec![usize::MAX; self.num_vertices()];
148        let mut count = 0;
149        for (v, &kept) in keep.iter().enumerate() {
150            if kept {
151                new_index[v] = count;
152                count += 1;
153            }
154        }
155
156        let new_arcs: Vec<(usize, usize)> = self
157            .arcs()
158            .into_iter()
159            .filter(|&(u, v)| keep[u] && keep[v])
160            .map(|(u, v)| (new_index[u], new_index[v]))
161            .collect();
162
163        Self::new(count, new_arcs)
164    }
165}
166
167impl PartialEq for DirectedGraph {
168    fn eq(&self, other: &Self) -> bool {
169        if self.num_vertices() != other.num_vertices() {
170            return false;
171        }
172        if self.num_arcs() != other.num_arcs() {
173            return false;
174        }
175        let mut self_arcs = self.arcs();
176        let mut other_arcs = other.arcs();
177        self_arcs.sort();
178        other_arcs.sort();
179        self_arcs == other_arcs
180    }
181}
182
183impl Eq for DirectedGraph {}
184
185use crate::impl_variant_param;
186impl_variant_param!(DirectedGraph, "graph");
187
188#[cfg(test)]
189#[path = "../unit_tests/topology/directed_graph.rs"]
190mod tests;