Skip to main content

problemreductions/models/graph/
hamiltonian_path_between_two_vertices.rs

1//! Hamiltonian Path Between Two Vertices problem implementation.
2//!
3//! The Hamiltonian Path Between Two Vertices problem asks whether a graph contains a
4//! simple path that starts at a specified source vertex, ends at a specified target
5//! vertex, and visits every other vertex exactly once.
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: "HamiltonianPathBetweenTwoVertices",
16        display_name: "Hamiltonian Path Between Two Vertices",
17        aliases: &[],
18        dimensions: &[
19            VariantDimension::new("graph", "SimpleGraph", &["SimpleGraph"]),
20        ],
21        module_path: module_path!(),
22        description: "Find a Hamiltonian path between two specified vertices in a graph",
23        fields: &[
24            FieldInfo { name: "graph", type_name: "G", description: "The underlying graph G=(V,E)" },
25            FieldInfo { name: "source_vertex", type_name: "usize", description: "Source vertex s" },
26            FieldInfo { name: "target_vertex", type_name: "usize", description: "Target vertex t" },
27        ],
28    }
29}
30
31/// The Hamiltonian Path Between Two Vertices problem.
32///
33/// Given a graph G = (V, E) and two distinguished vertices s, t in V,
34/// determine whether G contains a Hamiltonian path from s to t, i.e.,
35/// a simple path that begins at s, ends at t, and visits every vertex
36/// exactly once.
37///
38/// # Representation
39///
40/// A configuration is a sequence of `n` vertex indices representing a vertex
41/// ordering (permutation). Each position `i` in the configuration holds the
42/// vertex visited at step `i`. A valid solution must be a permutation of
43/// `0..n` where:
44/// - The first element equals `source_vertex`
45/// - The last element equals `target_vertex`
46/// - Consecutive entries are adjacent in the graph
47///
48/// The search space has `dims() = [n; n]` (each position can hold any of `n`
49/// vertices), so brute-force enumerates `n^n` configurations.
50///
51/// # Type Parameters
52///
53/// * `G` - Graph type (e.g., SimpleGraph)
54///
55/// # Example
56///
57/// ```
58/// use problemreductions::models::graph::HamiltonianPathBetweenTwoVertices;
59/// use problemreductions::topology::SimpleGraph;
60/// use problemreductions::{Problem, Solver, BruteForce};
61///
62/// // Path graph: 0-1-2-3, source=0, target=3
63/// let graph = SimpleGraph::new(4, vec![(0, 1), (1, 2), (2, 3)]);
64/// let problem = HamiltonianPathBetweenTwoVertices::new(graph, 0, 3);
65///
66/// let solver = BruteForce::new();
67/// let solution = solver.find_witness(&problem);
68/// assert!(solution.is_some());
69/// ```
70#[derive(Debug, Clone, Serialize, Deserialize)]
71#[serde(bound(deserialize = "G: serde::Deserialize<'de>"))]
72pub struct HamiltonianPathBetweenTwoVertices<G> {
73    graph: G,
74    source_vertex: usize,
75    target_vertex: usize,
76}
77
78impl<G: Graph> HamiltonianPathBetweenTwoVertices<G> {
79    /// Create a new Hamiltonian Path Between Two Vertices problem.
80    ///
81    /// # Panics
82    ///
83    /// Panics if `source_vertex` or `target_vertex` is out of range, or if they are equal.
84    pub fn new(graph: G, source_vertex: usize, target_vertex: usize) -> Self {
85        let n = graph.num_vertices();
86        assert!(
87            source_vertex < n,
88            "source_vertex {source_vertex} out of range for graph with {n} vertices"
89        );
90        assert!(
91            target_vertex < n,
92            "target_vertex {target_vertex} out of range for graph with {n} vertices"
93        );
94        assert_ne!(
95            source_vertex, target_vertex,
96            "source_vertex and target_vertex must be distinct"
97        );
98        Self {
99            graph,
100            source_vertex,
101            target_vertex,
102        }
103    }
104
105    /// Get a reference to the underlying graph.
106    pub fn graph(&self) -> &G {
107        &self.graph
108    }
109
110    /// Get the source vertex s.
111    pub fn source_vertex(&self) -> usize {
112        self.source_vertex
113    }
114
115    /// Get the target vertex t.
116    pub fn target_vertex(&self) -> usize {
117        self.target_vertex
118    }
119
120    /// Get the number of vertices in the underlying graph.
121    pub fn num_vertices(&self) -> usize {
122        self.graph.num_vertices()
123    }
124
125    /// Get the number of edges in the underlying graph.
126    pub fn num_edges(&self) -> usize {
127        self.graph.num_edges()
128    }
129
130    /// Check if a configuration is a valid Hamiltonian s-t path.
131    pub fn is_valid_solution(&self, config: &[usize]) -> bool {
132        is_valid_hamiltonian_st_path(&self.graph, config, self.source_vertex, self.target_vertex)
133    }
134}
135
136impl<G> Problem for HamiltonianPathBetweenTwoVertices<G>
137where
138    G: Graph + VariantParam,
139{
140    const NAME: &'static str = "HamiltonianPathBetweenTwoVertices";
141    type Value = crate::types::Or;
142
143    fn variant() -> Vec<(&'static str, &'static str)> {
144        crate::variant_params![G]
145    }
146
147    fn dims(&self) -> Vec<usize> {
148        let n = self.graph.num_vertices();
149        vec![n; n]
150    }
151
152    fn evaluate(&self, config: &[usize]) -> crate::types::Or {
153        crate::types::Or(is_valid_hamiltonian_st_path(
154            &self.graph,
155            config,
156            self.source_vertex,
157            self.target_vertex,
158        ))
159    }
160}
161
162/// Check if a configuration represents a valid Hamiltonian s-t path in the graph.
163///
164/// A valid Hamiltonian s-t path is a permutation of all vertices such that:
165/// - The first element is `source`
166/// - The last element is `target`
167/// - Consecutive vertices in the permutation are adjacent in the graph
168pub(crate) fn is_valid_hamiltonian_st_path<G: Graph>(
169    graph: &G,
170    config: &[usize],
171    source: usize,
172    target: usize,
173) -> bool {
174    let n = graph.num_vertices();
175    if config.len() != n {
176        return false;
177    }
178
179    // Check that config is a valid permutation of 0..n
180    let mut seen = vec![false; n];
181    for &v in config {
182        if v >= n || seen[v] {
183            return false;
184        }
185        seen[v] = true;
186    }
187
188    // Check endpoint constraints
189    if config[0] != source || config[n - 1] != target {
190        return false;
191    }
192
193    // Check consecutive vertices are adjacent
194    for i in 0..n.saturating_sub(1) {
195        if !graph.has_edge(config[i], config[i + 1]) {
196            return false;
197        }
198    }
199
200    true
201}
202
203#[cfg(feature = "example-db")]
204pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
205    // Instance from issue #831: 6 vertices, s=0, t=5
206    // Hamiltonian s-t path: [0, 3, 2, 1, 4, 5]
207    vec![crate::example_db::specs::ModelExampleSpec {
208        id: "hamiltonian_path_between_two_vertices_simplegraph",
209        instance: Box::new(HamiltonianPathBetweenTwoVertices::new(
210            SimpleGraph::new(
211                6,
212                vec![
213                    (0, 1),
214                    (0, 3),
215                    (1, 2),
216                    (1, 4),
217                    (2, 5),
218                    (3, 4),
219                    (4, 5),
220                    (2, 3),
221                ],
222            ),
223            0,
224            5,
225        )),
226        optimal_config: vec![0, 3, 2, 1, 4, 5],
227        optimal_value: serde_json::json!(true),
228    }]
229}
230
231// Use Bjorklund (2014) O*(1.657^n) as best known for general undirected graphs
232crate::declare_variants! {
233    default HamiltonianPathBetweenTwoVertices<SimpleGraph> => "1.657^num_vertices",
234}
235
236#[cfg(test)]
237#[path = "../../unit_tests/models/graph/hamiltonian_path_between_two_vertices.rs"]
238mod tests;