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;