Skip to main content

problemreductions/models/graph/
isomorphic_spanning_tree.rs

1//! Isomorphic Spanning Tree problem implementation.
2//!
3//! Given a graph G and a tree T with |V(G)| = |V(T)|, determine whether G
4//! contains a spanning tree isomorphic to T. This is a classical NP-complete
5//! problem (Garey & Johnson, ND8) that generalizes Hamiltonian Path.
6
7use crate::registry::{FieldInfo, ProblemSchemaEntry, VariantDimension};
8use crate::topology::{Graph, SimpleGraph};
9use crate::traits::Problem;
10use crate::variant::VariantParam;
11use serde::{Deserialize, Serialize};
12
13inventory::submit! {
14    ProblemSchemaEntry {
15        name: "IsomorphicSpanningTree",
16        display_name: "Isomorphic Spanning Tree",
17        aliases: &[],
18        dimensions: &[
19            VariantDimension::new("graph", "SimpleGraph", &["SimpleGraph"]),
20        ],
21        module_path: module_path!(),
22        description: "Does graph G contain a spanning tree isomorphic to tree T?",
23        fields: &[
24            FieldInfo { name: "graph", type_name: "G", description: "The host graph G" },
25            FieldInfo { name: "tree", type_name: "SimpleGraph", description: "The target tree T (must be a tree with |V(T)| = |V(G)|)" },
26        ],
27    }
28}
29
30/// Isomorphic Spanning Tree problem.
31///
32/// Given an undirected graph G = (V, E) and a tree T = (V_T, E_T) with
33/// |V| = |V_T|, determine if there exists a bijection π: V_T → V such that
34/// for every edge {u, v} in E_T, {π(u), π(v)} is an edge in E.
35///
36/// The configuration encodes an isomorphism as a permutation of the vertices of
37/// `graph`: `config[i]` is the graph vertex that tree vertex `i` maps to.
38///
39/// # Example
40///
41/// ```
42/// use problemreductions::models::graph::IsomorphicSpanningTree;
43/// use problemreductions::topology::SimpleGraph;
44/// use problemreductions::{Problem, Solver, BruteForce};
45///
46/// // Host graph: triangle 0-1-2-0
47/// let graph = SimpleGraph::new(3, vec![(0, 1), (1, 2), (0, 2)]);
48/// // Tree: path 0-1-2
49/// let tree = SimpleGraph::new(3, vec![(0, 1), (1, 2)]);
50/// let problem = IsomorphicSpanningTree::new(graph, tree);
51///
52/// let solver = BruteForce::new();
53/// let sol = solver.find_witness(&problem);
54/// assert!(sol.is_some());
55/// ```
56#[derive(Debug, Clone, Serialize, Deserialize)]
57#[serde(bound(deserialize = "G: serde::Deserialize<'de>"))]
58pub struct IsomorphicSpanningTree<G> {
59    graph: G,
60    tree: SimpleGraph,
61}
62
63impl<G: Graph> IsomorphicSpanningTree<G> {
64    /// Create a new IsomorphicSpanningTree problem.
65    ///
66    /// # Panics
67    ///
68    /// Panics if |V(G)| != |V(T)| or if T is not a tree (not connected or
69    /// wrong number of edges).
70    pub fn new(graph: G, tree: SimpleGraph) -> Self {
71        let n = graph.num_vertices();
72        assert_eq!(
73            n,
74            tree.num_vertices(),
75            "graph and tree must have the same number of vertices"
76        );
77        assert_eq!(
78            tree.num_edges(),
79            n.saturating_sub(1),
80            "tree must have exactly n-1 edges"
81        );
82        assert!(is_connected(&tree), "tree must be connected");
83        Self { graph, tree }
84    }
85
86    /// Get a reference to the host graph.
87    pub fn graph(&self) -> &G {
88        &self.graph
89    }
90
91    /// Get a reference to the target tree.
92    pub fn tree(&self) -> &SimpleGraph {
93        &self.tree
94    }
95
96    /// Get the number of vertices.
97    pub fn num_vertices(&self) -> usize {
98        self.graph.num_vertices()
99    }
100
101    /// Get the number of edges in the host graph.
102    pub fn num_edges(&self) -> usize {
103        self.graph.num_edges()
104    }
105
106    /// Get the edges of the target tree.
107    pub fn tree_edges(&self) -> Vec<(usize, usize)> {
108        self.tree.edges()
109    }
110}
111
112impl<G> Problem for IsomorphicSpanningTree<G>
113where
114    G: Graph + VariantParam,
115{
116    const NAME: &'static str = "IsomorphicSpanningTree";
117    type Value = crate::types::Or;
118
119    fn variant() -> Vec<(&'static str, &'static str)> {
120        crate::variant_params![G]
121    }
122
123    fn dims(&self) -> Vec<usize> {
124        vec![self.graph.num_vertices(); self.graph.num_vertices()]
125    }
126
127    fn evaluate(&self, config: &[usize]) -> crate::types::Or {
128        crate::types::Or(is_valid_isomorphic_spanning_tree(
129            &self.graph,
130            &self.tree,
131            config,
132        ))
133    }
134}
135
136fn is_valid_isomorphic_spanning_tree<G: Graph>(
137    graph: &G,
138    tree: &SimpleGraph,
139    config: &[usize],
140) -> bool {
141    let n = graph.num_vertices();
142    if config.len() != n {
143        return false;
144    }
145
146    let mut seen = vec![false; n];
147    for &v in config {
148        if v >= n || seen[v] {
149            return false;
150        }
151        seen[v] = true;
152    }
153
154    tree.edges()
155        .into_iter()
156        .all(|(u, v)| graph.has_edge(config[u], config[v]))
157}
158
159fn is_connected(graph: &SimpleGraph) -> bool {
160    let n = graph.num_vertices();
161    if n == 0 {
162        return true;
163    }
164
165    let mut visited = vec![false; n];
166    let mut queue = std::collections::VecDeque::new();
167    visited[0] = true;
168    queue.push_back(0);
169    let mut count = 1;
170
171    while let Some(v) = queue.pop_front() {
172        for u in graph.neighbors(v) {
173            if !visited[u] {
174                visited[u] = true;
175                count += 1;
176                queue.push_back(u);
177            }
178        }
179    }
180
181    count == n
182}
183
184#[cfg(feature = "example-db")]
185pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
186    vec![crate::example_db::specs::ModelExampleSpec {
187        id: "isomorphic_spanning_tree",
188        instance: Box::new(IsomorphicSpanningTree::new(
189            SimpleGraph::new(4, vec![(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)]),
190            SimpleGraph::new(4, vec![(0, 1), (0, 2), (0, 3)]),
191        )),
192        optimal_config: vec![0, 1, 2, 3],
193        optimal_value: serde_json::json!(true),
194    }]
195}
196
197crate::declare_variants! {
198    default IsomorphicSpanningTree<SimpleGraph> => "2^num_vertices",
199}
200
201#[cfg(test)]
202#[path = "../../unit_tests/models/graph/isomorphic_spanning_tree.rs"]
203mod tests;