Skip to main content

problemreductions/models/graph/
directed_hamiltonian_path.rs

1//! Directed Hamiltonian Path problem implementation.
2//!
3//! The Directed Hamiltonian Path problem asks whether a directed graph contains
4//! a simple directed path that visits every vertex exactly once.
5
6use crate::registry::{FieldInfo, ProblemSchemaEntry, VariantDimension};
7use crate::topology::DirectedGraph;
8use crate::traits::Problem;
9use serde::{Deserialize, Serialize};
10
11inventory::submit! {
12    ProblemSchemaEntry {
13        name: "DirectedHamiltonianPath",
14        display_name: "Directed Hamiltonian Path",
15        aliases: &["DHP"],
16        dimensions: &[
17            VariantDimension::new("graph", "DirectedGraph", &["DirectedGraph"]),
18        ],
19        module_path: module_path!(),
20        description: "Does the directed graph contain a Hamiltonian path?",
21        fields: &[
22            FieldInfo { name: "graph", type_name: "DirectedGraph", description: "The directed graph G=(V,A)" },
23        ],
24    }
25}
26
27/// The Directed Hamiltonian Path problem.
28///
29/// Given a directed graph G = (V, A), determine whether G contains a Hamiltonian path,
30/// i.e., a simple directed path that visits every vertex exactly once following arc
31/// directions.
32///
33/// # Representation
34///
35/// A configuration encodes a permutation using the Lehmer code:
36/// `dims() = [n, n-1, ..., 2, 1]`, yielding `n!` reachable configurations.
37/// Each configuration is decoded to a permutation of `0..n`, and a solution is
38/// valid when every consecutive pair `(path[i], path[i+1])` is an arc in the
39/// directed graph.
40///
41/// # Example
42///
43/// ```
44/// use problemreductions::models::graph::DirectedHamiltonianPath;
45/// use problemreductions::topology::DirectedGraph;
46/// use problemreductions::{Problem, Solver, BruteForce};
47///
48/// // Simple directed path: 0->1->2->3
49/// let graph = DirectedGraph::new(4, vec![(0, 1), (1, 2), (2, 3)]);
50/// let problem = DirectedHamiltonianPath::new(graph);
51///
52/// let solver = BruteForce::new();
53/// let solution = solver.find_witness(&problem);
54/// assert!(solution.is_some());
55/// ```
56#[derive(Debug, Clone, Serialize, Deserialize)]
57pub struct DirectedHamiltonianPath {
58    graph: DirectedGraph,
59}
60
61impl DirectedHamiltonianPath {
62    /// Create a new Directed Hamiltonian Path problem from a directed graph.
63    pub fn new(graph: DirectedGraph) -> Self {
64        Self { graph }
65    }
66
67    /// Get a reference to the underlying directed graph.
68    pub fn graph(&self) -> &DirectedGraph {
69        &self.graph
70    }
71
72    /// Get the number of vertices in the directed graph.
73    pub fn num_vertices(&self) -> usize {
74        self.graph.num_vertices()
75    }
76
77    /// Get the number of arcs in the directed graph.
78    pub fn num_arcs(&self) -> usize {
79        self.graph.num_arcs()
80    }
81
82    /// Check if a configuration is a valid directed Hamiltonian path.
83    pub fn is_valid_solution(&self, config: &[usize]) -> bool {
84        let perm = decode_lehmer(config);
85        is_valid_directed_hamiltonian_path(&self.graph, &perm)
86    }
87}
88
89impl Problem for DirectedHamiltonianPath {
90    const NAME: &'static str = "DirectedHamiltonianPath";
91    type Value = crate::types::Or;
92
93    fn variant() -> Vec<(&'static str, &'static str)> {
94        crate::variant_params![]
95    }
96
97    fn dims(&self) -> Vec<usize> {
98        lehmer_dims(self.graph.num_vertices())
99    }
100
101    fn evaluate(&self, config: &[usize]) -> crate::types::Or {
102        let perm = decode_lehmer(config);
103        crate::types::Or(is_valid_directed_hamiltonian_path(&self.graph, &perm))
104    }
105}
106
107/// Returns the Lehmer code dimension vector for `n` items: `[n, n-1, ..., 2, 1]`.
108pub(crate) fn lehmer_dims(n: usize) -> Vec<usize> {
109    (1..=n).rev().collect()
110}
111
112/// Decode a Lehmer code into a permutation.
113///
114/// Given a configuration `code` where `code[i] < n - i`, returns the
115/// corresponding permutation of `0..n`.
116pub(crate) fn decode_lehmer(code: &[usize]) -> Vec<usize> {
117    let n = code.len();
118    let mut available: Vec<usize> = (0..n).collect();
119    let mut perm = Vec::with_capacity(n);
120    for &idx in code {
121        let idx = idx.min(available.len().saturating_sub(1));
122        perm.push(available.remove(idx));
123    }
124    perm
125}
126
127/// Check if a permutation is a valid directed Hamiltonian path.
128///
129/// A valid directed Hamiltonian path visits every vertex exactly once and
130/// every consecutive pair `(perm[i], perm[i+1])` must be an arc in the graph.
131pub(crate) fn is_valid_directed_hamiltonian_path(graph: &DirectedGraph, perm: &[usize]) -> bool {
132    let n = graph.num_vertices();
133    if perm.len() != n {
134        return false;
135    }
136
137    // Check that perm is a valid permutation of 0..n
138    let mut seen = vec![false; n];
139    for &v in perm {
140        if v >= n || seen[v] {
141            return false;
142        }
143        seen[v] = true;
144    }
145
146    // Check that consecutive pairs are directed arcs
147    for i in 0..n.saturating_sub(1) {
148        if !graph.has_arc(perm[i], perm[i + 1]) {
149            return false;
150        }
151    }
152
153    true
154}
155
156#[cfg(feature = "example-db")]
157pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
158    use crate::rules::ilp_helpers::permutation_to_lehmer;
159
160    // 6 vertices, arcs from issue #813
161    // Hamiltonian path: [0, 1, 3, 2, 4, 5]
162    let graph = DirectedGraph::new(
163        6,
164        vec![
165            (0, 1),
166            (0, 3),
167            (1, 3),
168            (1, 4),
169            (2, 0),
170            (2, 4),
171            (3, 2),
172            (3, 5),
173            (4, 5),
174            (5, 1),
175        ],
176    );
177    let optimal_perm = vec![0usize, 1, 3, 2, 4, 5];
178    let optimal_config = permutation_to_lehmer(&optimal_perm);
179    vec![crate::example_db::specs::ModelExampleSpec {
180        id: "directed_hamiltonian_path",
181        instance: Box::new(DirectedHamiltonianPath::new(graph)),
182        optimal_config,
183        optimal_value: serde_json::json!(true),
184    }]
185}
186
187crate::declare_variants! {
188    default DirectedHamiltonianPath => "num_vertices^2 * 2^num_vertices",
189}
190
191#[cfg(test)]
192#[path = "../../unit_tests/models/graph/directed_hamiltonian_path.rs"]
193mod tests;