From 7b9d469cb70333ef808fa2089e2bb0135ed95ea2 Mon Sep 17 00:00:00 2001 From: zazabap Date: Fri, 13 Mar 2026 06:30:44 +0000 Subject: [PATCH 1/6] Add plan for #217: HamiltonianPath model --- docs/plans/2026-03-13-hamiltonian-path.md | 48 +++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 docs/plans/2026-03-13-hamiltonian-path.md diff --git a/docs/plans/2026-03-13-hamiltonian-path.md b/docs/plans/2026-03-13-hamiltonian-path.md new file mode 100644 index 000000000..2c7b3909c --- /dev/null +++ b/docs/plans/2026-03-13-hamiltonian-path.md @@ -0,0 +1,48 @@ +# Plan: Add HamiltonianPath Model (#217) + +## Overview +Add `HamiltonianPath` — a graph-based satisfaction problem (Metric = bool) that asks whether a graph contains a simple path visiting every vertex exactly once. Follows the `add-model` skill pipeline. + +## Step 1: Create Model File +**File:** `src/models/graph/hamiltonian_path.rs` + +- Define `HamiltonianPath` struct with a single `graph: G` field +- Implement `new()`, `graph()`, `num_vertices()`, `num_edges()` getters +- Implement `Problem` trait: + - `NAME = "HamiltonianPath"` + - `Metric = bool` + - `variant()` via `variant_params![G]` + - `dims()` returns `vec![n; n]` where n = num_vertices (each variable = which vertex at position i) + - `evaluate()` checks: (1) config is a valid permutation of 0..n, (2) consecutive vertices are adjacent +- Implement `SatisfactionProblem` marker trait +- Add `declare_variants!` with `HamiltonianPath => "1.657^num_vertices"` +- Add `#[cfg(test)] #[path]` link to unit tests + +## Step 2: Register Module +- Add `pub(crate) mod hamiltonian_path;` and `pub use hamiltonian_path::HamiltonianPath;` to `src/models/graph/mod.rs` +- Add `HamiltonianPath` to re-exports in `src/models/mod.rs` + +## Step 3: CLI Dispatch +- Add `"HamiltonianPath" => deser_sat::>(data)` in `problemreductions-cli/src/dispatch.rs` + +## Step 4: Unit Tests +**File:** `src/unit_tests/models/graph/hamiltonian_path.rs` + +- `test_hamiltonian_path_basic`: create instance, test evaluate on valid/invalid paths +- `test_hamiltonian_path_no_solution`: graph with isolated vertices → no satisfying assignment +- `test_hamiltonian_path_brute_force`: use BruteForce::find_satisfying and find_all_satisfying +- `test_hamiltonian_path_serialization`: serde round-trip + +## Step 5: Example +**File:** `examples/hamiltonian_path.rs` + +- Create Instance 2 from the issue (6 vertices, 8 edges, non-trivial path) +- Print JSON, verify Hamiltonian path exists +- `pub fn run()` + `fn main() { run() }` + +## Step 6: Export & Regenerate +- Run `make export-schemas` to regenerate problem schemas +- Run `make check` to verify fmt + clippy + tests pass + +## Step 7: Review +- Run `/review-implementation` for completeness check From 50550bfef3e1c72e6d3e32252036eb7fe0bf68a0 Mon Sep 17 00:00:00 2001 From: zazabap Date: Fri, 13 Mar 2026 06:38:47 +0000 Subject: [PATCH 2/6] Implement #217: Add HamiltonianPath model --- docs/src/reductions/problem_schemas.json | 11 ++ examples/hamiltonian_path.rs | 41 ++++++ problemreductions-cli/src/dispatch.rs | 2 + src/lib.rs | 2 +- src/models/graph/hamiltonian_path.rs | 139 ++++++++++++++++++ src/models/graph/mod.rs | 3 + src/models/mod.rs | 2 +- .../models/graph/hamiltonian_path.rs | 138 +++++++++++++++++ 8 files changed, 336 insertions(+), 2 deletions(-) create mode 100644 examples/hamiltonian_path.rs create mode 100644 src/models/graph/hamiltonian_path.rs create mode 100644 src/unit_tests/models/graph/hamiltonian_path.rs diff --git a/docs/src/reductions/problem_schemas.json b/docs/src/reductions/problem_schemas.json index ef694f9de..520055473 100644 --- a/docs/src/reductions/problem_schemas.json +++ b/docs/src/reductions/problem_schemas.json @@ -121,6 +121,17 @@ } ] }, + { + "name": "HamiltonianPath", + "description": "Find a Hamiltonian path in a graph", + "fields": [ + { + "name": "graph", + "type_name": "G", + "description": "The underlying graph G=(V,E)" + } + ] + }, { "name": "ILP", "description": "Optimize linear objective subject to linear constraints", diff --git a/examples/hamiltonian_path.rs b/examples/hamiltonian_path.rs new file mode 100644 index 000000000..311a03c38 --- /dev/null +++ b/examples/hamiltonian_path.rs @@ -0,0 +1,41 @@ +use problemreductions::models::graph::HamiltonianPath; +use problemreductions::topology::SimpleGraph; +use problemreductions::{BruteForce, Problem}; + +pub fn run() { + // Instance 2 from issue: 6 vertices, 8 edges (non-trivial) + let graph = SimpleGraph::new( + 6, + vec![ + (0, 1), + (0, 2), + (1, 3), + (2, 3), + (3, 4), + (3, 5), + (4, 2), + (5, 1), + ], + ); + let problem = HamiltonianPath::new(graph); + + println!("HamiltonianPath instance:"); + println!(" Vertices: {}", problem.num_vertices()); + println!(" Edges: {}", problem.num_edges()); + + let json = serde_json::to_string_pretty(&problem).unwrap(); + println!(" JSON: {}", json); + + // Find all Hamiltonian paths + let solver = BruteForce::new(); + let solutions = solver.find_all_satisfying(&problem); + println!(" Solutions found: {}", solutions.len()); + + for (i, sol) in solutions.iter().enumerate() { + println!(" Path {}: {:?} (valid: {})", i, sol, problem.evaluate(sol)); + } +} + +fn main() { + run(); +} diff --git a/problemreductions-cli/src/dispatch.rs b/problemreductions-cli/src/dispatch.rs index 49dd523cb..6546ffd45 100644 --- a/problemreductions-cli/src/dispatch.rs +++ b/problemreductions-cli/src/dispatch.rs @@ -211,6 +211,7 @@ pub fn load_problem( "MaximumMatching" => deser_opt::>(data), "MinimumDominatingSet" => deser_opt::>(data), "GraphPartitioning" => deser_opt::>(data), + "HamiltonianPath" => deser_sat::>(data), "MaxCut" => deser_opt::>(data), "MaximalIS" => deser_opt::>(data), "TravelingSalesman" => deser_opt::>(data), @@ -271,6 +272,7 @@ pub fn serialize_any_problem( "MaximumMatching" => try_ser::>(any), "MinimumDominatingSet" => try_ser::>(any), "GraphPartitioning" => try_ser::>(any), + "HamiltonianPath" => try_ser::>(any), "MaxCut" => try_ser::>(any), "MaximalIS" => try_ser::>(any), "TravelingSalesman" => try_ser::>(any), diff --git a/src/lib.rs b/src/lib.rs index a74c906f3..b28888696 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -40,7 +40,7 @@ pub mod prelude { // Problem types pub use crate::models::algebraic::{BMF, QUBO}; pub use crate::models::formula::{CNFClause, CircuitSAT, KSatisfiability, Satisfiability}; - pub use crate::models::graph::{BicliqueCover, GraphPartitioning, SpinGlass}; + pub use crate::models::graph::{BicliqueCover, GraphPartitioning, HamiltonianPath, SpinGlass}; pub use crate::models::graph::{ KColoring, MaxCut, MaximalIS, MaximumClique, MaximumIndependentSet, MaximumMatching, MinimumDominatingSet, MinimumFeedbackVertexSet, MinimumVertexCover, TravelingSalesman, diff --git a/src/models/graph/hamiltonian_path.rs b/src/models/graph/hamiltonian_path.rs new file mode 100644 index 000000000..a08ee60a1 --- /dev/null +++ b/src/models/graph/hamiltonian_path.rs @@ -0,0 +1,139 @@ +//! Hamiltonian Path problem implementation. +//! +//! The Hamiltonian Path problem asks whether a graph contains a simple path +//! that visits every vertex exactly once. + +use crate::registry::{FieldInfo, ProblemSchemaEntry}; +use crate::topology::{Graph, SimpleGraph}; +use crate::traits::{Problem, SatisfactionProblem}; +use crate::variant::VariantParam; +use serde::{Deserialize, Serialize}; + +inventory::submit! { + ProblemSchemaEntry { + name: "HamiltonianPath", + module_path: module_path!(), + description: "Find a Hamiltonian path in a graph", + fields: &[ + FieldInfo { name: "graph", type_name: "G", description: "The underlying graph G=(V,E)" }, + ], + } +} + +/// The Hamiltonian Path problem. +/// +/// Given a graph G = (V, E), determine whether G contains a Hamiltonian path, +/// i.e., a simple path that visits every vertex exactly once. +/// +/// # Type Parameters +/// +/// * `G` - Graph type (e.g., SimpleGraph) +/// +/// # Example +/// +/// ``` +/// use problemreductions::models::graph::HamiltonianPath; +/// use problemreductions::topology::SimpleGraph; +/// use problemreductions::{Problem, Solver, BruteForce}; +/// +/// // Path graph: 0-1-2-3 +/// let graph = SimpleGraph::new(4, vec![(0, 1), (1, 2), (2, 3)]); +/// let problem = HamiltonianPath::new(graph); +/// +/// let solver = BruteForce::new(); +/// let solution = solver.find_satisfying(&problem); +/// assert!(solution.is_some()); +/// ``` +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(bound(deserialize = "G: serde::Deserialize<'de>"))] +pub struct HamiltonianPath { + graph: G, +} + +impl HamiltonianPath { + /// Create a new Hamiltonian Path problem from a graph. + pub fn new(graph: G) -> Self { + Self { graph } + } + + /// Get a reference to the underlying graph. + pub fn graph(&self) -> &G { + &self.graph + } + + /// Get the number of vertices in the underlying graph. + pub fn num_vertices(&self) -> usize { + self.graph.num_vertices() + } + + /// Get the number of edges in the underlying graph. + pub fn num_edges(&self) -> usize { + self.graph.num_edges() + } + + /// Check if a configuration is a valid Hamiltonian path. + pub fn is_valid_solution(&self, config: &[usize]) -> bool { + is_valid_hamiltonian_path(&self.graph, config) + } +} + +impl Problem for HamiltonianPath +where + G: Graph + VariantParam, +{ + const NAME: &'static str = "HamiltonianPath"; + type Metric = bool; + + fn variant() -> Vec<(&'static str, &'static str)> { + crate::variant_params![G] + } + + fn dims(&self) -> Vec { + let n = self.graph.num_vertices(); + vec![n; n] + } + + fn evaluate(&self, config: &[usize]) -> bool { + is_valid_hamiltonian_path(&self.graph, config) + } +} + +impl SatisfactionProblem for HamiltonianPath {} + +/// Check if a configuration represents a valid Hamiltonian path in the graph. +/// +/// A valid Hamiltonian path is a permutation of the vertices such that +/// consecutive vertices in the permutation are adjacent in the graph. +pub(crate) fn is_valid_hamiltonian_path(graph: &G, config: &[usize]) -> bool { + let n = graph.num_vertices(); + if config.len() != n { + return false; + } + + // Check that config is a valid permutation of 0..n + let mut seen = vec![false; n]; + for &v in config { + if v >= n || seen[v] { + return false; + } + seen[v] = true; + } + + // Check consecutive vertices are adjacent + for i in 0..n.saturating_sub(1) { + if !graph.has_edge(config[i], config[i + 1]) { + return false; + } + } + + true +} + +// Use Bjorklund (2014) O*(1.657^n) as best known for general undirected graphs +crate::declare_variants! { + HamiltonianPath => "1.657^num_vertices", +} + +#[cfg(test)] +#[path = "../../unit_tests/models/graph/hamiltonian_path.rs"] +mod tests; diff --git a/src/models/graph/mod.rs b/src/models/graph/mod.rs index 42f46a155..2d0a37bd1 100644 --- a/src/models/graph/mod.rs +++ b/src/models/graph/mod.rs @@ -13,10 +13,12 @@ //! - [`MaximumMatching`]: Maximum weight matching //! - [`TravelingSalesman`]: Traveling Salesman (minimum weight Hamiltonian cycle) //! - [`SpinGlass`]: Ising model Hamiltonian +//! - [`HamiltonianPath`]: Hamiltonian path (simple path visiting every vertex) //! - [`BicliqueCover`]: Biclique cover on bipartite graphs pub(crate) mod biclique_cover; pub(crate) mod graph_partitioning; +pub(crate) mod hamiltonian_path; pub(crate) mod kcoloring; pub(crate) mod max_cut; pub(crate) mod maximal_is; @@ -31,6 +33,7 @@ pub(crate) mod traveling_salesman; pub use biclique_cover::BicliqueCover; pub use graph_partitioning::GraphPartitioning; +pub use hamiltonian_path::HamiltonianPath; pub use kcoloring::KColoring; pub use max_cut::MaxCut; pub use maximal_is::MaximalIS; diff --git a/src/models/mod.rs b/src/models/mod.rs index ceb584ce2..6472d9ee7 100644 --- a/src/models/mod.rs +++ b/src/models/mod.rs @@ -12,7 +12,7 @@ pub mod set; pub use algebraic::{ClosestVectorProblem, BMF, ILP, QUBO}; pub use formula::{CNFClause, CircuitSAT, KSatisfiability, Satisfiability}; pub use graph::{ - BicliqueCover, GraphPartitioning, KColoring, MaxCut, MaximalIS, MaximumClique, + BicliqueCover, GraphPartitioning, HamiltonianPath, KColoring, MaxCut, MaximalIS, MaximumClique, MaximumIndependentSet, MaximumMatching, MinimumDominatingSet, MinimumFeedbackVertexSet, MinimumVertexCover, SpinGlass, TravelingSalesman, }; diff --git a/src/unit_tests/models/graph/hamiltonian_path.rs b/src/unit_tests/models/graph/hamiltonian_path.rs new file mode 100644 index 000000000..7124f2f4f --- /dev/null +++ b/src/unit_tests/models/graph/hamiltonian_path.rs @@ -0,0 +1,138 @@ +use super::*; +use crate::solvers::{BruteForce, Solver}; +use crate::topology::SimpleGraph; + +#[test] +fn test_hamiltonian_path_basic() { + use crate::traits::Problem; + + // Path graph: 0-1-2-3 (has Hamiltonian path) + let problem = HamiltonianPath::new(SimpleGraph::new(4, vec![(0, 1), (1, 2), (2, 3)])); + assert_eq!(problem.num_vertices(), 4); + assert_eq!(problem.num_edges(), 3); + assert_eq!(problem.dims(), vec![4, 4, 4, 4]); + + // Valid path: 0->1->2->3 + assert!(problem.evaluate(&[0, 1, 2, 3])); + // Valid path: 3->2->1->0 (reversed) + assert!(problem.evaluate(&[3, 2, 1, 0])); + // Invalid: 0->1->3->2 (no edge 1-3) + assert!(!problem.evaluate(&[0, 1, 3, 2])); + // Invalid: not a permutation (repeated vertex) + assert!(!problem.evaluate(&[0, 1, 1, 2])); +} + +#[test] +fn test_hamiltonian_path_no_solution() { + // K4 on {0,1,2,3} + two isolated vertices {4,5} + let problem = HamiltonianPath::new(SimpleGraph::new( + 6, + vec![(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)], + )); + let solver = BruteForce::new(); + let solution = solver.find_satisfying(&problem); + assert!( + solution.is_none(), + "Graph with isolated vertices has no Hamiltonian path" + ); +} + +#[test] +fn test_hamiltonian_path_brute_force() { + // Path graph P4: 0-1-2-3 + let problem = HamiltonianPath::new(SimpleGraph::new(4, vec![(0, 1), (1, 2), (2, 3)])); + let solver = BruteForce::new(); + + let solution = solver.find_satisfying(&problem); + assert!(solution.is_some()); + assert!(problem.evaluate(&solution.unwrap())); + + let all = solver.find_all_satisfying(&problem); + // Path graph P4 has exactly 2 Hamiltonian paths: 0-1-2-3 and 3-2-1-0 + assert_eq!(all.len(), 2); + for sol in &all { + assert!(problem.evaluate(sol)); + } +} + +#[test] +fn test_hamiltonian_path_nontrivial() { + use crate::traits::Problem; + + // Instance 2 from issue: 6 vertices, 8 edges + let problem = HamiltonianPath::new(SimpleGraph::new( + 6, + vec![ + (0, 1), + (0, 2), + (1, 3), + (2, 3), + (3, 4), + (3, 5), + (4, 2), + (5, 1), + ], + )); + // Hamiltonian path: 0->2->4->3->1->5 + assert!(problem.evaluate(&[0, 2, 4, 3, 1, 5])); +} + +#[test] +fn test_hamiltonian_path_complete_graph() { + // Complete graph K4: every permutation is a Hamiltonian path + let problem = HamiltonianPath::new(SimpleGraph::new( + 4, + vec![(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)], + )); + let solver = BruteForce::new(); + let all = solver.find_all_satisfying(&problem); + // K4 has 4! = 24 Hamiltonian paths (all permutations) + assert_eq!(all.len(), 24); +} + +#[test] +fn test_is_valid_hamiltonian_path_function() { + let graph = SimpleGraph::new(4, vec![(0, 1), (1, 2), (2, 3)]); + assert!(is_valid_hamiltonian_path(&graph, &[0, 1, 2, 3])); + assert!(is_valid_hamiltonian_path(&graph, &[3, 2, 1, 0])); + assert!(!is_valid_hamiltonian_path(&graph, &[0, 1, 3, 2])); + // Wrong length + assert!(!is_valid_hamiltonian_path(&graph, &[0, 1, 2])); + // Vertex out of range + assert!(!is_valid_hamiltonian_path(&graph, &[0, 1, 2, 4])); +} + +#[test] +fn test_hamiltonian_path_serialization() { + let problem = HamiltonianPath::new(SimpleGraph::new(3, vec![(0, 1), (1, 2)])); + let json = serde_json::to_value(&problem).unwrap(); + let deserialized: HamiltonianPath = serde_json::from_value(json).unwrap(); + assert_eq!(deserialized.num_vertices(), 3); + assert_eq!(deserialized.num_edges(), 2); +} + +#[test] +fn test_is_valid_solution() { + let problem = HamiltonianPath::new(SimpleGraph::new(3, vec![(0, 1), (1, 2)])); + assert!(problem.is_valid_solution(&[0, 1, 2])); + assert!(!problem.is_valid_solution(&[0, 2, 1])); // no edge 0-2 +} + +#[test] +fn test_size_getters() { + let problem = HamiltonianPath::new(SimpleGraph::new(5, vec![(0, 1), (1, 2), (2, 3), (3, 4)])); + assert_eq!(problem.num_vertices(), 5); + assert_eq!(problem.num_edges(), 4); +} + +#[test] +fn test_single_vertex() { + use crate::traits::Problem; + + // Single vertex graph: trivially has a Hamiltonian "path" (just the vertex) + let problem = HamiltonianPath::new(SimpleGraph::new(1, vec![])); + assert!(problem.evaluate(&[0])); + let solver = BruteForce::new(); + let all = solver.find_all_satisfying(&problem); + assert_eq!(all.len(), 1); +} From b0d00e3ea04172679895e862ccfe009acc5ec72e Mon Sep 17 00:00:00 2001 From: zazabap Date: Fri, 13 Mar 2026 06:42:41 +0000 Subject: [PATCH 3/6] Review fixes: add resolve_alias entry and register example in integration tests --- problemreductions-cli/src/problem_name.rs | 1 + tests/suites/examples.rs | 2 ++ 2 files changed, 3 insertions(+) diff --git a/problemreductions-cli/src/problem_name.rs b/problemreductions-cli/src/problem_name.rs index 2b6c8c737..6c535e05b 100644 --- a/problemreductions-cli/src/problem_name.rs +++ b/problemreductions-cli/src/problem_name.rs @@ -56,6 +56,7 @@ pub fn resolve_alias(input: &str) -> String { "knapsack" => "Knapsack".to_string(), "fvs" | "minimumfeedbackvertexset" => "MinimumFeedbackVertexSet".to_string(), "subsetsum" => "SubsetSum".to_string(), + "hamiltonianpath" => "HamiltonianPath".to_string(), _ => input.to_string(), // pass-through for exact names } } diff --git a/tests/suites/examples.rs b/tests/suites/examples.rs index 3002a6838..22c797d76 100644 --- a/tests/suites/examples.rs +++ b/tests/suites/examples.rs @@ -10,6 +10,7 @@ macro_rules! example_test { }; } +example_test!(hamiltonian_path); example_test!(chained_reduction_factoring_to_spinglass); example_test!(chained_reduction_ksat_to_mis); example_test!(reduction_binpacking_to_ilp); @@ -61,6 +62,7 @@ macro_rules! example_fn { }; } +example_fn!(test_hamiltonian_path, hamiltonian_path); example_fn!( test_chained_reduction_factoring_to_spinglass, chained_reduction_factoring_to_spinglass From 056f30284d85dc7250138d92a28b787d4686a9fd Mon Sep 17 00:00:00 2001 From: zazabap Date: Fri, 13 Mar 2026 06:42:44 +0000 Subject: [PATCH 4/6] chore: remove plan file after implementation --- docs/plans/2026-03-13-hamiltonian-path.md | 48 ----------------------- 1 file changed, 48 deletions(-) delete mode 100644 docs/plans/2026-03-13-hamiltonian-path.md diff --git a/docs/plans/2026-03-13-hamiltonian-path.md b/docs/plans/2026-03-13-hamiltonian-path.md deleted file mode 100644 index 2c7b3909c..000000000 --- a/docs/plans/2026-03-13-hamiltonian-path.md +++ /dev/null @@ -1,48 +0,0 @@ -# Plan: Add HamiltonianPath Model (#217) - -## Overview -Add `HamiltonianPath` — a graph-based satisfaction problem (Metric = bool) that asks whether a graph contains a simple path visiting every vertex exactly once. Follows the `add-model` skill pipeline. - -## Step 1: Create Model File -**File:** `src/models/graph/hamiltonian_path.rs` - -- Define `HamiltonianPath` struct with a single `graph: G` field -- Implement `new()`, `graph()`, `num_vertices()`, `num_edges()` getters -- Implement `Problem` trait: - - `NAME = "HamiltonianPath"` - - `Metric = bool` - - `variant()` via `variant_params![G]` - - `dims()` returns `vec![n; n]` where n = num_vertices (each variable = which vertex at position i) - - `evaluate()` checks: (1) config is a valid permutation of 0..n, (2) consecutive vertices are adjacent -- Implement `SatisfactionProblem` marker trait -- Add `declare_variants!` with `HamiltonianPath => "1.657^num_vertices"` -- Add `#[cfg(test)] #[path]` link to unit tests - -## Step 2: Register Module -- Add `pub(crate) mod hamiltonian_path;` and `pub use hamiltonian_path::HamiltonianPath;` to `src/models/graph/mod.rs` -- Add `HamiltonianPath` to re-exports in `src/models/mod.rs` - -## Step 3: CLI Dispatch -- Add `"HamiltonianPath" => deser_sat::>(data)` in `problemreductions-cli/src/dispatch.rs` - -## Step 4: Unit Tests -**File:** `src/unit_tests/models/graph/hamiltonian_path.rs` - -- `test_hamiltonian_path_basic`: create instance, test evaluate on valid/invalid paths -- `test_hamiltonian_path_no_solution`: graph with isolated vertices → no satisfying assignment -- `test_hamiltonian_path_brute_force`: use BruteForce::find_satisfying and find_all_satisfying -- `test_hamiltonian_path_serialization`: serde round-trip - -## Step 5: Example -**File:** `examples/hamiltonian_path.rs` - -- Create Instance 2 from the issue (6 vertices, 8 edges, non-trivial path) -- Print JSON, verify Hamiltonian path exists -- `pub fn run()` + `fn main() { run() }` - -## Step 6: Export & Regenerate -- Run `make export-schemas` to regenerate problem schemas -- Run `make check` to verify fmt + clippy + tests pass - -## Step 7: Review -- Run `/review-implementation` for completeness check From 57b245a9fb116e02635d079a676fd28c3d872ba5 Mon Sep 17 00:00:00 2001 From: zazabap Date: Fri, 13 Mar 2026 14:18:49 +0000 Subject: [PATCH 5/6] Fix Copilot review comments on HamiltonianPath PR - Document configuration semantics (vertex ordering/permutation) in HamiltonianPath struct doc comment, including dims() encoding rationale - Add explicit `use crate::traits::Problem` import in test_hamiltonian_path_brute_force for consistency with other test functions - Add CLI create support for HamiltonianPath: example_for hint, create_problem match arm, create_random match arm, and updated error message Co-Authored-By: Claude Opus 4.6 --- problemreductions-cli/src/commands/create.rs | 30 +++++++++++++++++-- src/models/graph/hamiltonian_path.rs | 12 ++++++++ .../models/graph/hamiltonian_path.rs | 2 ++ 3 files changed, 42 insertions(+), 2 deletions(-) diff --git a/problemreductions-cli/src/commands/create.rs b/problemreductions-cli/src/commands/create.rs index 2b4cb04b3..d9a746b15 100644 --- a/problemreductions-cli/src/commands/create.rs +++ b/problemreductions-cli/src/commands/create.rs @@ -5,7 +5,7 @@ use crate::problem_name::{parse_problem_spec, resolve_variant}; use crate::util; use anyhow::{bail, Context, Result}; use problemreductions::models::algebraic::{ClosestVectorProblem, BMF}; -use problemreductions::models::graph::GraphPartitioning; +use problemreductions::models::graph::{GraphPartitioning, HamiltonianPath}; use problemreductions::models::misc::{BinPacking, LongestCommonSubsequence, PaintShop, SubsetSum}; use problemreductions::prelude::*; use problemreductions::registry::collect_schemas; @@ -81,6 +81,7 @@ fn example_for(canonical: &str, graph_type: Option<&str>) -> &'static str { _ => "--graph 0-1,1-2,2-3 --weights 1,1,1,1", }, "GraphPartitioning" => "--graph 0-1,1-2,2-3,0-2,1-3,0-3", + "HamiltonianPath" => "--graph 0-1,1-2,2-3", "MaxCut" | "MaximumMatching" | "TravelingSalesman" => { "--graph 0-1,1-2,2-3 --edge-weights 1,1,1" } @@ -213,6 +214,19 @@ pub fn create(args: &CreateArgs, out: &OutputConfig) -> Result<()> { ) } + // Hamiltonian path (graph only, no weights) + "HamiltonianPath" => { + let (graph, _) = parse_graph(args).map_err(|e| { + anyhow::anyhow!( + "{e}\n\nUsage: pred create HamiltonianPath --graph 0-1,1-2,2-3" + ) + })?; + ( + ser(HamiltonianPath::new(graph))?, + resolved_variant.clone(), + ) + } + // Graph problems with edge weights "MaxCut" | "MaximumMatching" | "TravelingSalesman" => { let (graph, _) = parse_graph(args).map_err(|e| { @@ -1018,6 +1032,17 @@ fn create_random( (ser(GraphPartitioning::new(graph))?, variant) } + // HamiltonianPath (graph only, no weights) + "HamiltonianPath" => { + let edge_prob = args.edge_prob.unwrap_or(0.5); + if !(0.0..=1.0).contains(&edge_prob) { + bail!("--edge-prob must be between 0.0 and 1.0"); + } + let graph = util::create_random_graph(num_vertices, edge_prob, args.seed); + let variant = variant_map(&[("graph", "SimpleGraph")]); + (ser(HamiltonianPath::new(graph))?, variant) + } + // Graph problems with edge weights "MaxCut" | "MaximumMatching" | "TravelingSalesman" => { let edge_prob = args.edge_prob.unwrap_or(0.5); @@ -1069,7 +1094,8 @@ fn create_random( _ => bail!( "Random generation is not supported for {canonical}. \ Supported: graph-based problems (MIS, MVC, MaxCut, MaxClique, \ - MaximumMatching, MinimumDominatingSet, SpinGlass, KColoring, TravelingSalesman)" + MaximumMatching, MinimumDominatingSet, SpinGlass, KColoring, TravelingSalesman, \ + HamiltonianPath)" ), }; diff --git a/src/models/graph/hamiltonian_path.rs b/src/models/graph/hamiltonian_path.rs index a08ee60a1..7fabfc1c9 100644 --- a/src/models/graph/hamiltonian_path.rs +++ b/src/models/graph/hamiltonian_path.rs @@ -25,6 +25,18 @@ inventory::submit! { /// Given a graph G = (V, E), determine whether G contains a Hamiltonian path, /// i.e., a simple path that visits every vertex exactly once. /// +/// # Representation +/// +/// A configuration is a sequence of `n` vertex indices representing a vertex +/// ordering (permutation). Each position `i` in the configuration holds the +/// vertex visited at step `i`. A valid solution must be a permutation of +/// `0..n` where consecutive entries are adjacent in the graph. +/// +/// The search space has `dims() = [n; n]` (each position can hold any of `n` +/// vertices), so brute-force enumerates `n^n` configurations. Only `n!` +/// permutations can satisfy the constraints, but the encoding avoids complex +/// variable-domain schemes and matches the problem's natural formulation. +/// /// # Type Parameters /// /// * `G` - Graph type (e.g., SimpleGraph) diff --git a/src/unit_tests/models/graph/hamiltonian_path.rs b/src/unit_tests/models/graph/hamiltonian_path.rs index 7124f2f4f..fcb2d167a 100644 --- a/src/unit_tests/models/graph/hamiltonian_path.rs +++ b/src/unit_tests/models/graph/hamiltonian_path.rs @@ -39,6 +39,8 @@ fn test_hamiltonian_path_no_solution() { #[test] fn test_hamiltonian_path_brute_force() { + use crate::traits::Problem; + // Path graph P4: 0-1-2-3 let problem = HamiltonianPath::new(SimpleGraph::new(4, vec![(0, 1), (1, 2), (2, 3)])); let solver = BruteForce::new(); From f0db9e2cb0526b7f2163db3e2a04ffe588b544e2 Mon Sep 17 00:00:00 2001 From: GiggleLiu Date: Sat, 14 Mar 2026 01:13:06 +0800 Subject: [PATCH 6/6] Add paper section and trait consistency for HamiltonianPath Co-Authored-By: Claude Opus 4.6 --- docs/paper/reductions.typ | 10 ++++++++++ docs/paper/references.bib | 11 +++++++++++ src/unit_tests/trait_consistency.rs | 4 ++++ 3 files changed, 25 insertions(+) diff --git a/docs/paper/reductions.typ b/docs/paper/reductions.typ index c34812b5b..8298a429c 100644 --- a/docs/paper/reductions.typ +++ b/docs/paper/reductions.typ @@ -30,6 +30,7 @@ "MinimumVertexCover": [Minimum Vertex Cover], "MaxCut": [Max-Cut], "GraphPartitioning": [Graph Partitioning], + "HamiltonianPath": [Hamiltonian Path], "KColoring": [$k$-Coloring], "MinimumDominatingSet": [Minimum Dominating Set], "MaximumMatching": [Maximum Matching], @@ -439,6 +440,15 @@ Graph Partitioning is a core NP-hard problem arising in VLSI design, parallel co caption: [Graph with $n = 6$ vertices partitioned into $A = {v_0, v_1, v_2}$ (blue) and $B = {v_3, v_4, v_5}$ (red). The 3 crossing edges $(v_1, v_3)$, $(v_2, v_3)$, $(v_2, v_4)$ are shown in bold red; internal edges are gray.], ) ] +#problem-def("HamiltonianPath")[ + Given a graph $G = (V, E)$, determine whether $G$ contains a _Hamiltonian path_, i.e., a simple path that visits every vertex exactly once. +][ + A classical NP-complete decision problem from Garey & Johnson (A1.3 GT39), closely related to _Hamiltonian Circuit_. Finding a Hamiltonian path in $G$ is equivalent to finding a Hamiltonian circuit in an augmented graph $G'$ obtained by adding a new vertex adjacent to all vertices of $G$. The problem remains NP-complete for planar graphs, cubic graphs, and bipartite graphs. + + The best known exact algorithm is Björklund's randomized $O^*(1.657^n)$ "Determinant Sums" method @bjorklund2014, which applies to both Hamiltonian path and circuit. The classical Held--Karp dynamic programming algorithm solves it in $O(n^2 dot 2^n)$ deterministic time. + + Variables: $n = |V|$ values forming a permutation. Position $i$ holds the vertex visited at step $i$. A configuration is satisfying when it forms a valid permutation of all vertices and consecutive vertices are adjacent in $G$. +] #problem-def("KColoring")[ Given $G = (V, E)$ and $k$ colors, find $c: V -> {1, ..., k}$ minimizing $|{(u, v) in E : c(u) = c(v)}|$. ][ diff --git a/docs/paper/references.bib b/docs/paper/references.bib index 94dcd02f1..d73afb4e3 100644 --- a/docs/paper/references.bib +++ b/docs/paper/references.bib @@ -196,6 +196,17 @@ @article{bjorklund2009 doi = {10.1137/070683933} } +@article{bjorklund2014, + author = {Andreas Bj{\"o}rklund}, + title = {Determinant Sums for Undirected Hamiltonicity}, + journal = {SIAM Journal on Computing}, + volume = {43}, + number = {1}, + pages = {280--299}, + year = {2014}, + doi = {10.1137/110839229}, +} + @article{aspvall1979, author = {Bengt Aspvall and Michael F. Plass and Robert Endre Tarjan}, title = {A Linear-Time Algorithm for Testing the Truth of Certain Quantified Boolean Formulas}, diff --git a/src/unit_tests/trait_consistency.rs b/src/unit_tests/trait_consistency.rs index cdbdba7a1..91754a81d 100644 --- a/src/unit_tests/trait_consistency.rs +++ b/src/unit_tests/trait_consistency.rs @@ -94,6 +94,10 @@ fn test_all_problems_implement_trait_correctly() { &MinimumSumMulticenter::new(SimpleGraph::new(3, vec![(0, 1), (1, 2)]), vec![1i32; 3], vec![1i32; 2], 1), "MinimumSumMulticenter", ); + check_problem_trait( + &HamiltonianPath::new(SimpleGraph::new(3, vec![(0, 1), (1, 2)])), + "HamiltonianPath", + ); } #[test]