problemreductions/topology/graph.rs
1//! Graph trait and SimpleGraph implementation.
2//!
3//! This module provides a `Graph` trait that abstracts over different graph
4//! representations, following Julia's Graphs.jl `AbstractGraph` pattern.
5//!
6//! Supported graph types:
7//! - [`SimpleGraph`]: Standard unweighted graph (wrapper around petgraph)
8//! - [`UnitDiskGraph`]: Vertices with 2D positions, edges based on distance
9//! - [`HyperGraph`]: Edges can connect any number of vertices (via adapter)
10
11use petgraph::graph::{NodeIndex, UnGraph};
12use petgraph::visit::EdgeRef;
13use serde::{Deserialize, Serialize};
14
15/// Trait for graph types, following Julia's Graphs.jl AbstractGraph pattern.
16///
17/// This trait abstracts over different graph representations, allowing
18/// problems to be generic over the underlying graph type.
19///
20/// # Example
21///
22/// ```rust,ignore
23/// use problemreductions::topology::{Graph, SimpleGraph};
24///
25/// fn count_triangles<G: Graph>(graph: &G) -> usize {
26/// let mut count = 0;
27/// for u in 0..graph.num_vertices() {
28/// for v in graph.neighbors(u) {
29/// if v > u {
30/// for w in graph.neighbors(v) {
31/// if w > v && graph.has_edge(u, w) {
32/// count += 1;
33/// }
34/// }
35/// }
36/// }
37/// }
38/// count
39/// }
40/// ```
41pub trait Graph: Clone + Send + Sync + 'static {
42 /// The name of the graph type (e.g., "SimpleGraph", "KingsSubgraph").
43 const NAME: &'static str;
44
45 /// Returns the number of vertices in the graph.
46 fn num_vertices(&self) -> usize;
47
48 /// Returns the number of edges in the graph.
49 fn num_edges(&self) -> usize;
50
51 /// Returns all edges as a list of (u, v) pairs.
52 ///
53 /// For undirected graphs, each edge appears once with u < v.
54 fn edges(&self) -> Vec<(usize, usize)>;
55
56 /// Checks if an edge exists between vertices u and v.
57 fn has_edge(&self, u: usize, v: usize) -> bool;
58
59 /// Returns all neighbors of vertex v.
60 fn neighbors(&self, v: usize) -> Vec<usize>;
61
62 /// Returns the degree of vertex v (number of neighbors).
63 fn degree(&self, v: usize) -> usize {
64 self.neighbors(v).len()
65 }
66
67 /// Returns true if the graph has no vertices.
68 fn is_empty(&self) -> bool {
69 self.num_vertices() == 0
70 }
71
72 /// Iterates over all edges, calling a closure for each.
73 ///
74 /// This can be more efficient than `edges()` when you don't need to collect.
75 fn for_each_edge<F>(&self, mut f: F)
76 where
77 F: FnMut(usize, usize),
78 {
79 for (u, v) in self.edges() {
80 f(u, v);
81 }
82 }
83}
84
85/// Trait for casting a graph to a supertype in the graph hierarchy.
86///
87/// When `A: GraphCast<B>`, graph `A` can be losslessly converted to graph `B`
88/// by extracting the adjacency structure. This enables natural-edge reductions
89/// where a problem on a specific graph type is solved by treating it as a more
90/// general graph.
91pub trait GraphCast<Target: Graph>: Graph {
92 /// Convert this graph to the target graph type.
93 fn cast_graph(&self) -> Target;
94}
95
96/// Any graph can be cast to a `SimpleGraph` by extracting vertices and edges.
97impl<G: Graph> GraphCast<SimpleGraph> for G {
98 fn cast_graph(&self) -> SimpleGraph {
99 SimpleGraph::new(self.num_vertices(), self.edges())
100 }
101}
102
103/// A simple unweighted undirected graph.
104///
105/// This is the default graph type for most problems. It wraps petgraph's
106/// `UnGraph` and implements the `Graph` trait.
107///
108/// # Example
109///
110/// ```
111/// use problemreductions::topology::SimpleGraph;
112/// use problemreductions::topology::Graph;
113///
114/// let graph = SimpleGraph::new(4, vec![(0, 1), (1, 2), (2, 3)]);
115/// assert_eq!(graph.num_vertices(), 4);
116/// assert_eq!(graph.num_edges(), 3);
117/// assert!(graph.has_edge(0, 1));
118/// assert!(!graph.has_edge(0, 2));
119/// ```
120#[derive(Debug, Clone, Serialize, Deserialize)]
121pub struct SimpleGraph {
122 inner: UnGraph<(), ()>,
123}
124
125impl SimpleGraph {
126 /// Creates a new graph with the given vertices and edges.
127 ///
128 /// # Arguments
129 ///
130 /// * `num_vertices` - Number of vertices in the graph
131 /// * `edges` - List of edges as (u, v) pairs
132 ///
133 /// # Panics
134 ///
135 /// Panics if any edge references a vertex index >= num_vertices.
136 pub fn new(num_vertices: usize, edges: Vec<(usize, usize)>) -> Self {
137 let mut inner = UnGraph::new_undirected();
138 for _ in 0..num_vertices {
139 inner.add_node(());
140 }
141 for (u, v) in edges {
142 assert!(
143 u < num_vertices && v < num_vertices,
144 "edge ({}, {}) references vertex >= num_vertices ({})",
145 u,
146 v,
147 num_vertices
148 );
149 inner.add_edge(NodeIndex::new(u), NodeIndex::new(v), ());
150 }
151 Self { inner }
152 }
153
154 /// Creates an empty graph with the given number of vertices.
155 pub fn empty(num_vertices: usize) -> Self {
156 Self::new(num_vertices, vec![])
157 }
158
159 /// Creates a complete graph (all vertices connected).
160 pub fn complete(num_vertices: usize) -> Self {
161 let mut edges = Vec::new();
162 for i in 0..num_vertices {
163 for j in (i + 1)..num_vertices {
164 edges.push((i, j));
165 }
166 }
167 Self::new(num_vertices, edges)
168 }
169
170 /// Creates a path graph (0-1-2-...-n).
171 pub fn path(num_vertices: usize) -> Self {
172 let edges: Vec<_> = (0..num_vertices.saturating_sub(1))
173 .map(|i| (i, i + 1))
174 .collect();
175 Self::new(num_vertices, edges)
176 }
177
178 /// Creates a cycle graph (0-1-2-...-n-0).
179 pub fn cycle(num_vertices: usize) -> Self {
180 if num_vertices < 3 {
181 return Self::path(num_vertices);
182 }
183 let mut edges: Vec<_> = (0..num_vertices - 1).map(|i| (i, i + 1)).collect();
184 edges.push((num_vertices - 1, 0));
185 Self::new(num_vertices, edges)
186 }
187
188 /// Creates a star graph (vertex 0 connected to all others).
189 pub fn star(num_vertices: usize) -> Self {
190 let edges: Vec<_> = (1..num_vertices).map(|i| (0, i)).collect();
191 Self::new(num_vertices, edges)
192 }
193
194 /// Creates a grid graph with the given dimensions.
195 ///
196 /// Vertices are numbered row by row: vertex `r * cols + c` is at row `r`, column `c`.
197 pub fn grid(rows: usize, cols: usize) -> Self {
198 let num_vertices = rows * cols;
199 let mut edges = Vec::new();
200
201 for r in 0..rows {
202 for c in 0..cols {
203 let v = r * cols + c;
204 // Right neighbor
205 if c + 1 < cols {
206 edges.push((v, v + 1));
207 }
208 // Down neighbor
209 if r + 1 < rows {
210 edges.push((v, v + cols));
211 }
212 }
213 }
214
215 Self::new(num_vertices, edges)
216 }
217}
218
219impl Graph for SimpleGraph {
220 const NAME: &'static str = "SimpleGraph";
221
222 fn num_vertices(&self) -> usize {
223 self.inner.node_count()
224 }
225
226 fn num_edges(&self) -> usize {
227 self.inner.edge_count()
228 }
229
230 fn edges(&self) -> Vec<(usize, usize)> {
231 self.inner
232 .edge_references()
233 .map(|e| (e.source().index(), e.target().index()))
234 .collect()
235 }
236
237 fn has_edge(&self, u: usize, v: usize) -> bool {
238 self.inner
239 .find_edge(NodeIndex::new(u), NodeIndex::new(v))
240 .is_some()
241 }
242
243 fn neighbors(&self, v: usize) -> Vec<usize> {
244 self.inner
245 .neighbors(NodeIndex::new(v))
246 .map(|n| n.index())
247 .collect()
248 }
249}
250
251impl PartialEq for SimpleGraph {
252 fn eq(&self, other: &Self) -> bool {
253 if self.num_vertices() != other.num_vertices() {
254 return false;
255 }
256 if self.num_edges() != other.num_edges() {
257 return false;
258 }
259 // Check all edges exist in both
260 let mut self_edges: Vec<_> = self.edges();
261 let mut other_edges: Vec<_> = other.edges();
262 // Normalize edge order
263 for e in &mut self_edges {
264 if e.0 > e.1 {
265 *e = (e.1, e.0);
266 }
267 }
268 for e in &mut other_edges {
269 if e.0 > e.1 {
270 *e = (e.1, e.0);
271 }
272 }
273 self_edges.sort();
274 other_edges.sort();
275 self_edges == other_edges
276 }
277}
278
279impl Eq for SimpleGraph {}
280
281use crate::impl_variant_param;
282impl_variant_param!(SimpleGraph, "graph");
283
284#[cfg(test)]
285#[path = "../unit_tests/topology/graph.rs"]
286mod tests;