From b229ceeb289810d8f357818cd2fda4617a10e879 Mon Sep 17 00:00:00 2001 From: GiggleLiu Date: Fri, 13 Mar 2026 08:35:58 +0800 Subject: [PATCH 1/8] Add plan for #216: HamiltonianCircuit model Co-Authored-By: Claude Opus 4.6 --- docs/plans/2026-03-13-hamiltonian-circuit.md | 151 +++++++++++++++++++ 1 file changed, 151 insertions(+) create mode 100644 docs/plans/2026-03-13-hamiltonian-circuit.md diff --git a/docs/plans/2026-03-13-hamiltonian-circuit.md b/docs/plans/2026-03-13-hamiltonian-circuit.md new file mode 100644 index 00000000..d359c1d1 --- /dev/null +++ b/docs/plans/2026-03-13-hamiltonian-circuit.md @@ -0,0 +1,151 @@ +# Plan: Add HamiltonianCircuit Model (#216) + +## Overview + +Add the HamiltonianCircuit satisfaction problem — a classical NP-complete problem (Karp, 1972) asking whether an undirected graph contains a cycle visiting every vertex exactly once. + +**Problem type:** SatisfactionProblem (Metric = bool) +**Category:** graph +**Type parameter:** G: Graph (no weight) + +## Information Checklist + +| # | Item | Value | +|---|------|-------| +| 1 | Problem name | `HamiltonianCircuit` | +| 2 | Definition | Given G=(V,E), does G contain a Hamiltonian circuit (closed path visiting every vertex exactly once)? | +| 3 | Problem type | Satisfaction (bool) | +| 4 | Type parameters | `G: Graph` | +| 5 | Struct fields | `graph: G` | +| 6 | Config space | `dims() = vec![n; n]` — permutation encoding: n positions, each picks a vertex (0..n-1) | +| 7 | Feasibility check | Config represents a valid permutation AND consecutive vertices (including wrap-around) are adjacent | +| 8 | Objective | None (satisfaction) — returns `true` if Hamiltonian circuit exists | +| 9 | Complexity | O(num_vertices^2 * 2^num_vertices) — Held-Karp DP (1962), deterministic | +| 10 | Solving strategy | BruteForce (enumerate all configs, check evaluate()) | +| 11 | Category | `graph` | + +## Steps + +### Step 1: Implement the model [INDEPENDENT] + +Create `src/models/graph/hamiltonian_circuit.rs`: + +1. **ProblemSchemaEntry** via `inventory::submit!`: + - name: "HamiltonianCircuit" + - description: "Does the graph contain a Hamiltonian circuit?" + - fields: `[FieldInfo { name: "graph", type_name: "G", description: "The undirected graph G=(V,E)" }]` + +2. **Struct definition:** + ```rust + #[derive(Debug, Clone, Serialize, Deserialize)] + #[serde(bound(deserialize = "G: serde::Deserialize<'de>"))] + pub struct HamiltonianCircuit { + graph: G, + } + ``` + +3. **Inherent methods:** + - `pub fn new(graph: G) -> Self` + - `pub fn graph(&self) -> &G` + - `pub fn num_vertices(&self) -> usize` (delegates to graph) + - `pub fn num_edges(&self) -> usize` (delegates to graph) + +4. **Problem trait impl:** + - `const NAME: &'static str = "HamiltonianCircuit"` + - `type Metric = bool` + - `fn variant()` → `crate::variant_params![G]` + - `fn dims(&self)` → `vec![n; n]` where n = num_vertices (permutation encoding) + - `fn evaluate(&self, config: &[usize])` → check: + 1. All values in 0..n (automatic from dims) + 2. Config is a valid permutation (no duplicates — use a boolean seen-array) + 3. For all i in 0..n: edge (config[i], config[(i+1) % n]) exists in graph + Return true only if all checks pass. + +5. **SatisfactionProblem impl:** marker trait, empty impl + +6. **declare_variants!:** + ```rust + crate::declare_variants! { + HamiltonianCircuit => "num_vertices^2 * 2^num_vertices", + } + ``` + Uses Held-Karp complexity: O(n^2 * 2^n), deterministic. + +7. **Test link:** + ```rust + #[cfg(test)] + #[path = "../../unit_tests/models/graph/hamiltonian_circuit.rs"] + mod tests; + ``` + +### Step 2: Register the model [DEPENDS ON: Step 1] + +1. **`src/models/graph/mod.rs`:** Add `pub(crate) mod hamiltonian_circuit;` and `pub use hamiltonian_circuit::HamiltonianCircuit;` +2. **`src/models/mod.rs`:** Add `HamiltonianCircuit` to the `pub use graph::{...}` line + +### Step 3: Register in CLI [DEPENDS ON: Step 2] + +1. **`problemreductions-cli/src/dispatch.rs`:** + - In `load_problem()`: add match arm `"HamiltonianCircuit" => deser_sat::>(data)` + - In `serialize_any_problem()`: add match arm `"HamiltonianCircuit" => try_ser::>(any)` + +2. **`problemreductions-cli/src/problem_name.rs`:** + - In `resolve_alias()`: add `"hamiltoniancircuit" => "HamiltonianCircuit".to_string()` + - Add `("HC", "HamiltonianCircuit")` to `ALIASES` — HC is a well-established abbreviation in the literature + +3. **`problemreductions-cli/src/commands/create.rs`:** + - Add match arm for `"HamiltonianCircuit"` — parse `--graph` flag, construct `HamiltonianCircuit::new(graph)`, serialize + - Pattern: similar to simple graph problems without weights + +4. **`problemreductions-cli/src/cli.rs`:** + - Add `HamiltonianCircuit` to the "Flags by problem type" table in after_help: `HamiltonianCircuit, HC --graph` + +### Step 4: Write unit tests [INDEPENDENT] + +Create `src/unit_tests/models/graph/hamiltonian_circuit.rs`: + +1. **`test_hamiltonian_circuit_basic`:** + - Create prism graph (6 vertices, 9 edges from issue Example 1) + - Verify dims() returns `vec![6; 6]` + - Verify evaluate() returns true for valid Hamiltonian circuit `[0, 1, 2, 5, 4, 3]` + - Verify evaluate() returns false for invalid configs (non-permutation, missing edge) + +2. **`test_hamiltonian_circuit_no_solution`:** + - Create graph from issue Example 2 (star + chords, no HC) + - Use BruteForce solver to verify no satisfying solution exists + +3. **`test_hamiltonian_circuit_solver`:** + - Create a small graph (e.g., cycle on 4 vertices — square) + - Use BruteForce to find all satisfying solutions + - Verify each solution is a valid Hamiltonian circuit + +4. **`test_hamiltonian_circuit_serialization`:** + - Round-trip serde test: serialize → deserialize → verify equal evaluation + +5. Register test file in `src/unit_tests/models/graph/mod.rs` + +### Step 5: Document in paper [INDEPENDENT] + +Update `docs/paper/reductions.typ`: + +1. Add to `display-name` dict: `"HamiltonianCircuit": [Hamiltonian Circuit]` + +2. Add `problem-def("HamiltonianCircuit")`: + - **Definition:** Given G=(V,E), does G contain a Hamiltonian circuit — a closed path visiting every vertex exactly once? + - **Body:** Background on NP-completeness (Karp 1972), relationship to TSP, Held-Karp algorithm, permutation encoding explanation. Include example with prism graph visualization. + +### Step 6: Verify [DEPENDS ON: Steps 1-5] + +```bash +make fmt +make clippy +make test +``` + +All must pass. Coverage >95% on new code. + +## Parallelism + +- Steps 1, 4, 5 are independent and can run in parallel +- Steps 2, 3 depend on Step 1 +- Step 6 depends on all others From 8ae3ac91387357a3f3f7581e829e15055bfd6539 Mon Sep 17 00:00:00 2001 From: GiggleLiu Date: Fri, 13 Mar 2026 08:51:20 +0800 Subject: [PATCH 2/8] Implement #216: Add HamiltonianCircuit model - New satisfaction problem (Metric = bool) for Hamiltonian circuit detection - Permutation encoding: dims() = vec![n; n] - Evaluate checks valid permutation + consecutive edge existence - Held-Karp complexity: O(n^2 * 2^n) in declare_variants! - CLI integration: dispatch, alias (HC), create, random generation - Unit tests: basic, no-solution, solver, serialization - Paper: problem-def + Bjorklund 2014 bibliography entry Co-Authored-By: Claude Opus 4.6 --- docs/paper/reductions.typ | 16 +++ docs/paper/references.bib | 11 ++ problemreductions-cli/src/cli.rs | 1 + problemreductions-cli/src/commands/create.rs | 30 +++- problemreductions-cli/src/dispatch.rs | 3 + problemreductions-cli/src/problem_name.rs | 2 + src/models/graph/hamiltonian_circuit.rs | 132 ++++++++++++++++++ src/models/graph/mod.rs | 3 + src/models/mod.rs | 6 +- .../models/graph/hamiltonian_circuit.rs | 89 ++++++++++++ 10 files changed, 288 insertions(+), 5 deletions(-) create mode 100644 src/models/graph/hamiltonian_circuit.rs create mode 100644 src/unit_tests/models/graph/hamiltonian_circuit.rs diff --git a/docs/paper/reductions.typ b/docs/paper/reductions.typ index ab67b7c1..277d76ec 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], + "HamiltonianCircuit": [Hamiltonian Circuit], "KColoring": [$k$-Coloring], "MinimumDominatingSet": [Minimum Dominating Set], "MaximumMatching": [Maximum Matching], @@ -510,6 +511,21 @@ One of the most intensely studied NP-hard problems, with applications in logisti caption: [Complete graph $K_4$ with weighted edges. The optimal tour $v_0 -> v_1 -> v_2 -> v_3 -> v_0$ (blue edges) has cost 6.], ) ] +#problem-def("HamiltonianCircuit")[ + *Instance:* An undirected graph $G = (V, E)$. + + *Question:* Does $G$ contain a _Hamiltonian circuit_ --- a closed path that visits every vertex exactly once? +][ + The Hamiltonian Circuit problem is one of Karp's original 21 NP-complete problems @karp1972, and is listed as GT37 in Garey & Johnson @garey1979. + It is closely related to the Traveling Salesman Problem: while TSP seeks to minimize the total weight of a Hamiltonian cycle on a weighted complete graph, the Hamiltonian Circuit problem simply asks whether _any_ such cycle exists on a general (unweighted) graph. + + A configuration is a permutation $pi$ of the vertices, interpreted as the order in which they are visited. + The circuit is valid when every consecutive pair $(pi(i), pi(i+1 mod n))$ is an edge in $G$. + + *Algorithms.* + The classical Held--Karp dynamic programming algorithm @heldkarp1962 solves the problem in $O(n^2 dot 2^n)$ time and $O(n dot 2^n)$ space. + Björklund's randomized "Determinant Sums" algorithm achieves $O^*(1.657^n)$ time for general graphs and $O^*(sqrt(2)^n)$ for bipartite graphs @bjorklund2014. +] #problem-def("MaximumClique")[ Given $G = (V, E)$, find $K subset.eq V$ maximizing $|K|$ such that all pairs in $K$ are adjacent: $forall u, v in K: (u, v) in E$. Equivalent to MIS on the complement graph $overline(G)$. ][ diff --git a/docs/paper/references.bib b/docs/paper/references.bib index 0ec874f6..81635887 100644 --- a/docs/paper/references.bib +++ b/docs/paper/references.bib @@ -185,6 +185,17 @@ @inproceedings{zamir2021 doi = {10.4230/LIPIcs.ICALP.2021.113} } +@article{bjorklund2014, + author = {Andreas Bj\"{o}rklund}, + title = {Determinant Sums for Undirected {H}amiltonicity}, + journal = {SIAM Journal on Computing}, + volume = {43}, + number = {1}, + pages = {280--299}, + year = {2014}, + doi = {10.1137/110839229} +} + @article{bjorklund2009, author = {Andreas Bj\"{o}rklund and Thore Husfeldt and Mikko Koivisto}, title = {Set Partitioning via Inclusion-Exclusion}, diff --git a/problemreductions-cli/src/cli.rs b/problemreductions-cli/src/cli.rs index 91792e20..c6710582 100644 --- a/problemreductions-cli/src/cli.rs +++ b/problemreductions-cli/src/cli.rs @@ -209,6 +209,7 @@ Flags by problem type: SpinGlass --graph, --couplings, --fields KColoring --graph, --k GraphPartitioning --graph + HamiltonianCircuit, HC --graph Factoring --target, --m, --n BinPacking --sizes, --capacity PaintShop --sequence diff --git a/problemreductions-cli/src/commands/create.rs b/problemreductions-cli/src/commands/create.rs index 2df4f099..1da7ced8 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, HamiltonianCircuit}; use problemreductions::models::misc::{BinPacking, PaintShop}; use problemreductions::prelude::*; use problemreductions::registry::collect_schemas; @@ -86,6 +86,7 @@ fn example_for(canonical: &str, graph_type: Option<&str>) -> &'static str { "QUBO" => "--matrix \"1,0.5;0.5,2\"", "SpinGlass" => "--graph 0-1,1-2 --couplings 1,1", "KColoring" => "--graph 0-1,1-2,2-0 --k 3", + "HamiltonianCircuit" => "--graph 0-1,1-2,2-3,3-0", "Factoring" => "--target 15 --m 4 --n 4", _ => "", } @@ -209,6 +210,19 @@ pub fn create(args: &CreateArgs, out: &OutputConfig) -> Result<()> { ) } + // Hamiltonian Circuit (graph only, no weights) + "HamiltonianCircuit" => { + let (graph, _) = parse_graph(args).map_err(|e| { + anyhow::anyhow!( + "{e}\n\nUsage: pred create HamiltonianCircuit --graph 0-1,1-2,2-3,3-0" + ) + })?; + ( + ser(HamiltonianCircuit::new(graph))?, + resolved_variant.clone(), + ) + } + // Graph problems with edge weights "MaxCut" | "MaximumMatching" | "TravelingSalesman" => { let (graph, _) = parse_graph(args).map_err(|e| { @@ -975,6 +989,17 @@ fn create_random( (ser(GraphPartitioning::new(graph))?, variant) } + // Hamiltonian Circuit (graph only, no weights) + "HamiltonianCircuit" => { + 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(HamiltonianCircuit::new(graph))?, variant) + } + // Graph problems with edge weights "MaxCut" | "MaximumMatching" | "TravelingSalesman" => { let edge_prob = args.edge_prob.unwrap_or(0.5); @@ -1026,7 +1051,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, HamiltonianCircuit)" ), }; diff --git a/problemreductions-cli/src/dispatch.rs b/problemreductions-cli/src/dispatch.rs index 49dd523c..1fcfc013 100644 --- a/problemreductions-cli/src/dispatch.rs +++ b/problemreductions-cli/src/dispatch.rs @@ -1,5 +1,6 @@ use anyhow::{bail, Context, Result}; use problemreductions::models::algebraic::{ClosestVectorProblem, ILP}; +use problemreductions::models::graph::HamiltonianCircuit; use problemreductions::models::misc::{BinPacking, Knapsack, SubsetSum}; use problemreductions::prelude::*; use problemreductions::rules::{MinimizeSteps, ReductionGraph}; @@ -211,6 +212,7 @@ pub fn load_problem( "MaximumMatching" => deser_opt::>(data), "MinimumDominatingSet" => deser_opt::>(data), "GraphPartitioning" => deser_opt::>(data), + "HamiltonianCircuit" => deser_sat::>(data), "MaxCut" => deser_opt::>(data), "MaximalIS" => deser_opt::>(data), "TravelingSalesman" => deser_opt::>(data), @@ -271,6 +273,7 @@ pub fn serialize_any_problem( "MaximumMatching" => try_ser::>(any), "MinimumDominatingSet" => try_ser::>(any), "GraphPartitioning" => try_ser::>(any), + "HamiltonianCircuit" => try_ser::>(any), "MaxCut" => try_ser::>(any), "MaximalIS" => try_ser::>(any), "TravelingSalesman" => try_ser::>(any), diff --git a/problemreductions-cli/src/problem_name.rs b/problemreductions-cli/src/problem_name.rs index 2b6c8c73..7a949879 100644 --- a/problemreductions-cli/src/problem_name.rs +++ b/problemreductions-cli/src/problem_name.rs @@ -22,6 +22,7 @@ pub const ALIASES: &[(&str, &str)] = &[ ("CVP", "ClosestVectorProblem"), ("MaxMatching", "MaximumMatching"), ("FVS", "MinimumFeedbackVertexSet"), + ("HC", "HamiltonianCircuit"), ]; /// Resolve a short alias to the canonical problem name. @@ -55,6 +56,7 @@ pub fn resolve_alias(input: &str) -> String { "cvp" | "closestvectorproblem" => "ClosestVectorProblem".to_string(), "knapsack" => "Knapsack".to_string(), "fvs" | "minimumfeedbackvertexset" => "MinimumFeedbackVertexSet".to_string(), + "hc" | "hamiltoniancircuit" => "HamiltonianCircuit".to_string(), "subsetsum" => "SubsetSum".to_string(), _ => input.to_string(), // pass-through for exact names } diff --git a/src/models/graph/hamiltonian_circuit.rs b/src/models/graph/hamiltonian_circuit.rs new file mode 100644 index 00000000..6d8220d7 --- /dev/null +++ b/src/models/graph/hamiltonian_circuit.rs @@ -0,0 +1,132 @@ +//! Hamiltonian Circuit problem implementation. +//! +//! The Hamiltonian Circuit problem asks whether a graph contains a cycle +//! that visits every vertex exactly once and returns to the starting vertex. + +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: "HamiltonianCircuit", + module_path: module_path!(), + description: "Does the graph contain a Hamiltonian circuit?", + fields: &[ + FieldInfo { name: "graph", type_name: "G", description: "The undirected graph G=(V,E)" }, + ], + } +} + +/// The Hamiltonian Circuit problem. +/// +/// Given a graph G = (V, E), determine whether there exists a cycle that +/// visits every vertex exactly once and returns to the starting vertex. +/// +/// # Type Parameters +/// +/// * `G` - Graph type (e.g., SimpleGraph) +/// +/// # Example +/// +/// ``` +/// use problemreductions::models::graph::HamiltonianCircuit; +/// use problemreductions::topology::SimpleGraph; +/// use problemreductions::{Problem, Solver, BruteForce}; +/// +/// // Square graph (4-cycle) has a Hamiltonian circuit +/// let graph = SimpleGraph::new(4, vec![(0, 1), (1, 2), (2, 3), (0, 3)]); +/// let problem = HamiltonianCircuit::new(graph); +/// +/// let solver = BruteForce::new(); +/// let solutions = solver.find_all_satisfying(&problem); +/// +/// // Verify all solutions are valid Hamiltonian circuits +/// for sol in &solutions { +/// assert!(problem.evaluate(sol)); +/// } +/// ``` +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(bound(deserialize = "G: serde::Deserialize<'de>"))] +pub struct HamiltonianCircuit { + /// The underlying graph. + graph: G, +} + +impl HamiltonianCircuit { + /// Create a new Hamiltonian Circuit 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() + } +} + +impl Problem for HamiltonianCircuit +where + G: Graph + VariantParam, +{ + const NAME: &'static str = "HamiltonianCircuit"; + 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 { + let n = self.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 that consecutive vertices (including wrap-around) are connected by edges + for i in 0..n { + let u = config[i]; + let v = config[(i + 1) % n]; + if !self.graph.has_edge(u, v) { + return false; + } + } + + true + } +} + +impl SatisfactionProblem for HamiltonianCircuit {} + +crate::declare_variants! { + HamiltonianCircuit => "num_vertices^2 * 2^num_vertices", +} + +#[cfg(test)] +#[path = "../../unit_tests/models/graph/hamiltonian_circuit.rs"] +mod tests; diff --git a/src/models/graph/mod.rs b/src/models/graph/mod.rs index 42f46a15..f1ae24fe 100644 --- a/src/models/graph/mod.rs +++ b/src/models/graph/mod.rs @@ -9,6 +9,7 @@ //! - [`MaximumClique`]: Maximum weight clique //! - [`MaxCut`]: Maximum cut on weighted graphs //! - [`GraphPartitioning`]: Minimum bisection (balanced graph partitioning) +//! - [`HamiltonianCircuit`]: Hamiltonian circuit (decision problem) //! - [`KColoring`]: K-vertex coloring //! - [`MaximumMatching`]: Maximum weight matching //! - [`TravelingSalesman`]: Traveling Salesman (minimum weight Hamiltonian cycle) @@ -17,6 +18,7 @@ pub(crate) mod biclique_cover; pub(crate) mod graph_partitioning; +pub(crate) mod hamiltonian_circuit; 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_circuit::HamiltonianCircuit; 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 ceb584ce..2fad36e1 100644 --- a/src/models/mod.rs +++ b/src/models/mod.rs @@ -12,9 +12,9 @@ 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, - MaximumIndependentSet, MaximumMatching, MinimumDominatingSet, MinimumFeedbackVertexSet, - MinimumVertexCover, SpinGlass, TravelingSalesman, + BicliqueCover, GraphPartitioning, HamiltonianCircuit, KColoring, MaxCut, MaximalIS, + MaximumClique, MaximumIndependentSet, MaximumMatching, MinimumDominatingSet, + MinimumFeedbackVertexSet, MinimumVertexCover, SpinGlass, TravelingSalesman, }; pub use misc::{BinPacking, Factoring, Knapsack, PaintShop, SubsetSum}; pub use set::{MaximumSetPacking, MinimumSetCovering}; diff --git a/src/unit_tests/models/graph/hamiltonian_circuit.rs b/src/unit_tests/models/graph/hamiltonian_circuit.rs new file mode 100644 index 00000000..6f6cc9d6 --- /dev/null +++ b/src/unit_tests/models/graph/hamiltonian_circuit.rs @@ -0,0 +1,89 @@ +use super::*; +use crate::solvers::{BruteForce, Solver}; +use crate::topology::SimpleGraph; +use crate::traits::Problem; + +#[test] +fn test_hamiltonian_circuit_basic() { + // Prism graph: 6 vertices, 9 edges + // Two triangles (0,1,2) and (3,4,5) connected by edges (0,3), (1,4), (2,5) + let graph = SimpleGraph::new( + 6, + vec![ + (0, 1), + (1, 2), + (2, 0), + (3, 4), + (4, 5), + (5, 3), + (0, 3), + (1, 4), + (2, 5), + ], + ); + let problem = HamiltonianCircuit::new(graph); + + assert_eq!(problem.num_vertices(), 6); + assert_eq!(problem.num_edges(), 9); + assert_eq!(problem.dims(), vec![6; 6]); + + // Valid Hamiltonian circuit: 0->1->2->5->4->3->0 + // Edges used: (0,1), (1,2), (2,5), (5,4), (4,3), (3,0) -- all present + assert!(problem.evaluate(&[0, 1, 2, 5, 4, 3])); + + // Invalid: 0->1->2->3 requires edge (2,3) which is NOT in the edge list + assert!(!problem.evaluate(&[0, 1, 2, 3, 4, 5])); + + // Invalid: duplicate vertex 0 -- not a valid permutation + assert!(!problem.evaluate(&[0, 0, 1, 2, 3, 4])); +} + +#[test] +fn test_hamiltonian_circuit_no_solution() { + // Path graph on 4 vertices: no Hamiltonian circuit possible + let graph = SimpleGraph::new(4, vec![(0, 1), (1, 2), (2, 3)]); + let problem = HamiltonianCircuit::new(graph); + + let solver = BruteForce::new(); + assert!(solver.find_satisfying(&problem).is_none()); + assert!(solver.find_all_satisfying(&problem).is_empty()); +} + +#[test] +fn test_hamiltonian_circuit_solver() { + // Cycle on 4 vertices (square): edges {0,1}, {1,2}, {2,3}, {3,0} + let graph = SimpleGraph::new(4, vec![(0, 1), (1, 2), (2, 3), (3, 0)]); + let problem = HamiltonianCircuit::new(graph); + + let solver = BruteForce::new(); + let solutions = solver.find_all_satisfying(&problem); + + // 4-cycle has 8 Hamiltonian circuits: 4 starting positions x 2 directions + assert_eq!(solutions.len(), 8); + + for sol in &solutions { + assert!(problem.evaluate(sol)); + } +} + +#[test] +fn test_hamiltonian_circuit_serialization() { + let graph = SimpleGraph::new(4, vec![(0, 1), (1, 2), (2, 3), (3, 0)]); + let problem = HamiltonianCircuit::new(graph); + + let json = serde_json::to_string(&problem).unwrap(); + let restored: HamiltonianCircuit = serde_json::from_str(&json).unwrap(); + + assert_eq!(problem.dims(), restored.dims()); + + // Valid circuit gives the same result on both instances + assert_eq!( + problem.evaluate(&[0, 1, 2, 3]), + restored.evaluate(&[0, 1, 2, 3]) + ); + // Invalid config gives the same result on both instances + assert_eq!( + problem.evaluate(&[0, 0, 1, 2]), + restored.evaluate(&[0, 0, 1, 2]) + ); +} From c3d5a0d08c56d53b87d846e55384c16238bc22a3 Mon Sep 17 00:00:00 2001 From: GiggleLiu Date: Fri, 13 Mar 2026 08:51:25 +0800 Subject: [PATCH 3/8] chore: remove plan file after implementation Co-Authored-By: Claude Opus 4.6 --- docs/plans/2026-03-13-hamiltonian-circuit.md | 151 ------------------- 1 file changed, 151 deletions(-) delete mode 100644 docs/plans/2026-03-13-hamiltonian-circuit.md diff --git a/docs/plans/2026-03-13-hamiltonian-circuit.md b/docs/plans/2026-03-13-hamiltonian-circuit.md deleted file mode 100644 index d359c1d1..00000000 --- a/docs/plans/2026-03-13-hamiltonian-circuit.md +++ /dev/null @@ -1,151 +0,0 @@ -# Plan: Add HamiltonianCircuit Model (#216) - -## Overview - -Add the HamiltonianCircuit satisfaction problem — a classical NP-complete problem (Karp, 1972) asking whether an undirected graph contains a cycle visiting every vertex exactly once. - -**Problem type:** SatisfactionProblem (Metric = bool) -**Category:** graph -**Type parameter:** G: Graph (no weight) - -## Information Checklist - -| # | Item | Value | -|---|------|-------| -| 1 | Problem name | `HamiltonianCircuit` | -| 2 | Definition | Given G=(V,E), does G contain a Hamiltonian circuit (closed path visiting every vertex exactly once)? | -| 3 | Problem type | Satisfaction (bool) | -| 4 | Type parameters | `G: Graph` | -| 5 | Struct fields | `graph: G` | -| 6 | Config space | `dims() = vec![n; n]` — permutation encoding: n positions, each picks a vertex (0..n-1) | -| 7 | Feasibility check | Config represents a valid permutation AND consecutive vertices (including wrap-around) are adjacent | -| 8 | Objective | None (satisfaction) — returns `true` if Hamiltonian circuit exists | -| 9 | Complexity | O(num_vertices^2 * 2^num_vertices) — Held-Karp DP (1962), deterministic | -| 10 | Solving strategy | BruteForce (enumerate all configs, check evaluate()) | -| 11 | Category | `graph` | - -## Steps - -### Step 1: Implement the model [INDEPENDENT] - -Create `src/models/graph/hamiltonian_circuit.rs`: - -1. **ProblemSchemaEntry** via `inventory::submit!`: - - name: "HamiltonianCircuit" - - description: "Does the graph contain a Hamiltonian circuit?" - - fields: `[FieldInfo { name: "graph", type_name: "G", description: "The undirected graph G=(V,E)" }]` - -2. **Struct definition:** - ```rust - #[derive(Debug, Clone, Serialize, Deserialize)] - #[serde(bound(deserialize = "G: serde::Deserialize<'de>"))] - pub struct HamiltonianCircuit { - graph: G, - } - ``` - -3. **Inherent methods:** - - `pub fn new(graph: G) -> Self` - - `pub fn graph(&self) -> &G` - - `pub fn num_vertices(&self) -> usize` (delegates to graph) - - `pub fn num_edges(&self) -> usize` (delegates to graph) - -4. **Problem trait impl:** - - `const NAME: &'static str = "HamiltonianCircuit"` - - `type Metric = bool` - - `fn variant()` → `crate::variant_params![G]` - - `fn dims(&self)` → `vec![n; n]` where n = num_vertices (permutation encoding) - - `fn evaluate(&self, config: &[usize])` → check: - 1. All values in 0..n (automatic from dims) - 2. Config is a valid permutation (no duplicates — use a boolean seen-array) - 3. For all i in 0..n: edge (config[i], config[(i+1) % n]) exists in graph - Return true only if all checks pass. - -5. **SatisfactionProblem impl:** marker trait, empty impl - -6. **declare_variants!:** - ```rust - crate::declare_variants! { - HamiltonianCircuit => "num_vertices^2 * 2^num_vertices", - } - ``` - Uses Held-Karp complexity: O(n^2 * 2^n), deterministic. - -7. **Test link:** - ```rust - #[cfg(test)] - #[path = "../../unit_tests/models/graph/hamiltonian_circuit.rs"] - mod tests; - ``` - -### Step 2: Register the model [DEPENDS ON: Step 1] - -1. **`src/models/graph/mod.rs`:** Add `pub(crate) mod hamiltonian_circuit;` and `pub use hamiltonian_circuit::HamiltonianCircuit;` -2. **`src/models/mod.rs`:** Add `HamiltonianCircuit` to the `pub use graph::{...}` line - -### Step 3: Register in CLI [DEPENDS ON: Step 2] - -1. **`problemreductions-cli/src/dispatch.rs`:** - - In `load_problem()`: add match arm `"HamiltonianCircuit" => deser_sat::>(data)` - - In `serialize_any_problem()`: add match arm `"HamiltonianCircuit" => try_ser::>(any)` - -2. **`problemreductions-cli/src/problem_name.rs`:** - - In `resolve_alias()`: add `"hamiltoniancircuit" => "HamiltonianCircuit".to_string()` - - Add `("HC", "HamiltonianCircuit")` to `ALIASES` — HC is a well-established abbreviation in the literature - -3. **`problemreductions-cli/src/commands/create.rs`:** - - Add match arm for `"HamiltonianCircuit"` — parse `--graph` flag, construct `HamiltonianCircuit::new(graph)`, serialize - - Pattern: similar to simple graph problems without weights - -4. **`problemreductions-cli/src/cli.rs`:** - - Add `HamiltonianCircuit` to the "Flags by problem type" table in after_help: `HamiltonianCircuit, HC --graph` - -### Step 4: Write unit tests [INDEPENDENT] - -Create `src/unit_tests/models/graph/hamiltonian_circuit.rs`: - -1. **`test_hamiltonian_circuit_basic`:** - - Create prism graph (6 vertices, 9 edges from issue Example 1) - - Verify dims() returns `vec![6; 6]` - - Verify evaluate() returns true for valid Hamiltonian circuit `[0, 1, 2, 5, 4, 3]` - - Verify evaluate() returns false for invalid configs (non-permutation, missing edge) - -2. **`test_hamiltonian_circuit_no_solution`:** - - Create graph from issue Example 2 (star + chords, no HC) - - Use BruteForce solver to verify no satisfying solution exists - -3. **`test_hamiltonian_circuit_solver`:** - - Create a small graph (e.g., cycle on 4 vertices — square) - - Use BruteForce to find all satisfying solutions - - Verify each solution is a valid Hamiltonian circuit - -4. **`test_hamiltonian_circuit_serialization`:** - - Round-trip serde test: serialize → deserialize → verify equal evaluation - -5. Register test file in `src/unit_tests/models/graph/mod.rs` - -### Step 5: Document in paper [INDEPENDENT] - -Update `docs/paper/reductions.typ`: - -1. Add to `display-name` dict: `"HamiltonianCircuit": [Hamiltonian Circuit]` - -2. Add `problem-def("HamiltonianCircuit")`: - - **Definition:** Given G=(V,E), does G contain a Hamiltonian circuit — a closed path visiting every vertex exactly once? - - **Body:** Background on NP-completeness (Karp 1972), relationship to TSP, Held-Karp algorithm, permutation encoding explanation. Include example with prism graph visualization. - -### Step 6: Verify [DEPENDS ON: Steps 1-5] - -```bash -make fmt -make clippy -make test -``` - -All must pass. Coverage >95% on new code. - -## Parallelism - -- Steps 1, 4, 5 are independent and can run in parallel -- Steps 2, 3 depend on Step 1 -- Step 6 depends on all others From 5c740eda0ded8f157f85ff352c3fd8c31432f42f Mon Sep 17 00:00:00 2001 From: GiggleLiu Date: Sun, 15 Mar 2026 01:58:23 +0800 Subject: [PATCH 4/8] fix: guard against n < 3 in HamiltonianCircuit::evaluate A Hamiltonian circuit requires at least 3 vertices. Return false early for degenerate cases, addressing Copilot review comment. Co-Authored-By: Claude Opus 4.6 --- src/models/graph/hamiltonian_circuit.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/models/graph/hamiltonian_circuit.rs b/src/models/graph/hamiltonian_circuit.rs index c0a8c25a..7f029590 100644 --- a/src/models/graph/hamiltonian_circuit.rs +++ b/src/models/graph/hamiltonian_circuit.rs @@ -100,7 +100,7 @@ where fn evaluate(&self, config: &[usize]) -> bool { let n = self.graph.num_vertices(); - if config.len() != n { + if n < 3 || config.len() != n { return false; } From 888ff0a1b747dc8523b3c6b8255e99dee499c9f2 Mon Sep 17 00:00:00 2001 From: GiggleLiu Date: Sun, 15 Mar 2026 02:09:17 +0800 Subject: [PATCH 5/8] fix: address review findings for HamiltonianCircuit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Use 1.657^num_vertices complexity (Björklund 2014), matching HamiltonianPath - Add canonical_model_example_specs for example-db system - Register in graph/mod.rs example specs aggregator - Add trait_consistency entry - Add edge case tests: n<3 graphs, wrong-length config, out-of-range vertex, K3 triangle, K4 complete graph solution count Co-Authored-By: Claude Opus 4.6 --- src/models/graph/hamiltonian_circuit.rs | 30 +++++++++++- src/models/graph/mod.rs | 1 + .../models/graph/hamiltonian_circuit.rs | 48 +++++++++++++++++++ src/unit_tests/trait_consistency.rs | 4 ++ 4 files changed, 82 insertions(+), 1 deletion(-) diff --git a/src/models/graph/hamiltonian_circuit.rs b/src/models/graph/hamiltonian_circuit.rs index 7f029590..b9aa764d 100644 --- a/src/models/graph/hamiltonian_circuit.rs +++ b/src/models/graph/hamiltonian_circuit.rs @@ -128,8 +128,36 @@ where impl SatisfactionProblem for HamiltonianCircuit {} +#[cfg(feature = "example-db")] +pub(crate) fn canonical_model_example_specs() -> Vec { + vec![crate::example_db::specs::ModelExampleSpec { + id: "hamiltonian_circuit_simplegraph", + build: || { + // Prism graph (triangular prism): 6 vertices, 9 edges + let problem = HamiltonianCircuit::new(SimpleGraph::new( + 6, + vec![ + (0, 1), + (1, 2), + (2, 0), + (3, 4), + (4, 5), + (5, 3), + (0, 3), + (1, 4), + (2, 5), + ], + )); + crate::example_db::specs::satisfaction_example( + problem, + vec![vec![0, 1, 2, 5, 4, 3]], + ) + }, + }] +} + crate::declare_variants! { - default sat HamiltonianCircuit => "num_vertices^2 * 2^num_vertices", + default sat HamiltonianCircuit => "1.657^num_vertices", } #[cfg(test)] diff --git a/src/models/graph/mod.rs b/src/models/graph/mod.rs index 4ff12775..e174414b 100644 --- a/src/models/graph/mod.rs +++ b/src/models/graph/mod.rs @@ -76,6 +76,7 @@ pub(crate) fn canonical_model_example_specs() -> Vec 24 total + assert_eq!(solutions.len(), 24); + for sol in &solutions { + assert!(problem.evaluate(sol)); + } } #[test] diff --git a/src/unit_tests/trait_consistency.rs b/src/unit_tests/trait_consistency.rs index ebbc68a0..8dde6dcd 100644 --- a/src/unit_tests/trait_consistency.rs +++ b/src/unit_tests/trait_consistency.rs @@ -99,6 +99,10 @@ fn test_all_problems_implement_trait_correctly() { ), "MinimumSumMulticenter", ); + check_problem_trait( + &HamiltonianCircuit::new(SimpleGraph::new(3, vec![(0, 1), (1, 2), (2, 0)])), + "HamiltonianCircuit", + ); check_problem_trait( &HamiltonianPath::new(SimpleGraph::new(3, vec![(0, 1), (1, 2)])), "HamiltonianPath", From c5b097172ac21ec7a8543198748ba8e14f9fb75f Mon Sep 17 00:00:00 2001 From: GiggleLiu Date: Sun, 15 Mar 2026 02:10:20 +0800 Subject: [PATCH 6/8] Merge origin/main into issue-216-hamiltonian-circuit Resolve conflicts: keep registry-based dispatch from main, add HamiltonianCircuit alongside main's new models. Update HamiltonianCircuit to current declare_variants!/ProblemSchemaEntry patterns. --- docs/src/reductions/problem_schemas.json | 11 ++ docs/src/reductions/reduction_graph.json | 227 ++++++++++++----------- 2 files changed, 129 insertions(+), 109 deletions(-) diff --git a/docs/src/reductions/problem_schemas.json b/docs/src/reductions/problem_schemas.json index 078dea0a..3e8064fa 100644 --- a/docs/src/reductions/problem_schemas.json +++ b/docs/src/reductions/problem_schemas.json @@ -142,6 +142,17 @@ } ] }, + { + "name": "HamiltonianCircuit", + "description": "Does the graph contain a Hamiltonian circuit?", + "fields": [ + { + "name": "graph", + "type_name": "G", + "description": "The undirected graph G=(V,E)" + } + ] + }, { "name": "HamiltonianPath", "description": "Find a Hamiltonian path in a graph", diff --git a/docs/src/reductions/reduction_graph.json b/docs/src/reductions/reduction_graph.json index e8a63e10..63823916 100644 --- a/docs/src/reductions/reduction_graph.json +++ b/docs/src/reductions/reduction_graph.json @@ -80,6 +80,15 @@ "doc_path": "models/graph/struct.GraphPartitioning.html", "complexity": "2^num_vertices" }, + { + "name": "HamiltonianCircuit", + "variant": { + "graph": "SimpleGraph" + }, + "category": "graph", + "doc_path": "models/graph/struct.HamiltonianCircuit.html", + "complexity": "num_vertices^2 * 2^num_vertices" + }, { "name": "HamiltonianPath", "variant": { @@ -505,7 +514,7 @@ "edges": [ { "source": 3, - "target": 11, + "target": 12, "overhead": [ { "field": "num_vars", @@ -520,7 +529,7 @@ }, { "source": 4, - "target": 11, + "target": 12, "overhead": [ { "field": "num_vars", @@ -535,7 +544,7 @@ }, { "source": 4, - "target": 52, + "target": 53, "overhead": [ { "field": "num_spins", @@ -565,7 +574,7 @@ }, { "source": 7, - "target": 12, + "target": 13, "overhead": [ { "field": "num_vars", @@ -579,8 +588,8 @@ "doc_path": "rules/factoring_ilp/index.html" }, { - "source": 11, - "target": 12, + "source": 12, + "target": 13, "overhead": [ { "field": "num_vars", @@ -594,8 +603,8 @@ "doc_path": "rules/ilp_bool_ilp_i32/index.html" }, { - "source": 11, - "target": 47, + "source": 12, + "target": 48, "overhead": [ { "field": "num_vars", @@ -605,8 +614,8 @@ "doc_path": "rules/ilp_qubo/index.html" }, { - "source": 15, - "target": 18, + "source": 16, + "target": 19, "overhead": [ { "field": "num_vertices", @@ -620,8 +629,8 @@ "doc_path": "rules/kcoloring_casts/index.html" }, { - "source": 18, - "target": 11, + "source": 19, + "target": 12, "overhead": [ { "field": "num_vars", @@ -635,8 +644,8 @@ "doc_path": "rules/coloring_ilp/index.html" }, { - "source": 18, - "target": 47, + "source": 19, + "target": 48, "overhead": [ { "field": "num_vars", @@ -646,8 +655,8 @@ "doc_path": "rules/coloring_qubo/index.html" }, { - "source": 19, - "target": 21, + "source": 20, + "target": 22, "overhead": [ { "field": "num_vars", @@ -661,8 +670,8 @@ "doc_path": "rules/ksatisfiability_casts/index.html" }, { - "source": 19, - "target": 47, + "source": 20, + "target": 48, "overhead": [ { "field": "num_vars", @@ -672,8 +681,8 @@ "doc_path": "rules/ksatisfiability_qubo/index.html" }, { - "source": 20, - "target": 21, + "source": 21, + "target": 22, "overhead": [ { "field": "num_vars", @@ -687,8 +696,8 @@ "doc_path": "rules/ksatisfiability_casts/index.html" }, { - "source": 20, - "target": 47, + "source": 21, + "target": 48, "overhead": [ { "field": "num_vars", @@ -698,8 +707,8 @@ "doc_path": "rules/ksatisfiability_qubo/index.html" }, { - "source": 20, - "target": 54, + "source": 21, + "target": 55, "overhead": [ { "field": "num_elements", @@ -709,8 +718,8 @@ "doc_path": "rules/ksatisfiability_subsetsum/index.html" }, { - "source": 21, - "target": 49, + "source": 22, + "target": 50, "overhead": [ { "field": "num_clauses", @@ -728,8 +737,8 @@ "doc_path": "rules/sat_ksat/index.html" }, { - "source": 23, - "target": 11, + "source": 24, + "target": 12, "overhead": [ { "field": "num_vars", @@ -743,8 +752,8 @@ "doc_path": "rules/longestcommonsubsequence_ilp/index.html" }, { - "source": 24, - "target": 52, + "source": 25, + "target": 53, "overhead": [ { "field": "num_spins", @@ -758,8 +767,8 @@ "doc_path": "rules/spinglass_maxcut/index.html" }, { - "source": 26, - "target": 11, + "source": 27, + "target": 12, "overhead": [ { "field": "num_vars", @@ -773,8 +782,8 @@ "doc_path": "rules/maximumclique_ilp/index.html" }, { - "source": 26, - "target": 30, + "source": 27, + "target": 31, "overhead": [ { "field": "num_vertices", @@ -788,8 +797,8 @@ "doc_path": "rules/maximumclique_maximumindependentset/index.html" }, { - "source": 27, - "target": 28, + "source": 28, + "target": 29, "overhead": [ { "field": "num_vertices", @@ -803,8 +812,8 @@ "doc_path": "rules/maximumindependentset_casts/index.html" }, { - "source": 27, - "target": 32, + "source": 28, + "target": 33, "overhead": [ { "field": "num_vertices", @@ -818,8 +827,8 @@ "doc_path": "rules/maximumindependentset_casts/index.html" }, { - "source": 28, - "target": 33, + "source": 29, + "target": 34, "overhead": [ { "field": "num_vertices", @@ -833,8 +842,8 @@ "doc_path": "rules/maximumindependentset_casts/index.html" }, { - "source": 29, - "target": 27, + "source": 30, + "target": 28, "overhead": [ { "field": "num_vertices", @@ -848,8 +857,8 @@ "doc_path": "rules/maximumindependentset_gridgraph/index.html" }, { - "source": 29, - "target": 30, + "source": 30, + "target": 31, "overhead": [ { "field": "num_vertices", @@ -863,8 +872,8 @@ "doc_path": "rules/maximumindependentset_casts/index.html" }, { - "source": 29, - "target": 31, + "source": 30, + "target": 32, "overhead": [ { "field": "num_vertices", @@ -878,8 +887,8 @@ "doc_path": "rules/maximumindependentset_triangular/index.html" }, { - "source": 29, - "target": 35, + "source": 30, + "target": 36, "overhead": [ { "field": "num_sets", @@ -893,8 +902,8 @@ "doc_path": "rules/maximumindependentset_maximumsetpacking/index.html" }, { - "source": 30, - "target": 26, + "source": 31, + "target": 27, "overhead": [ { "field": "num_vertices", @@ -908,8 +917,8 @@ "doc_path": "rules/maximumindependentset_maximumclique/index.html" }, { - "source": 30, - "target": 37, + "source": 31, + "target": 38, "overhead": [ { "field": "num_sets", @@ -923,8 +932,8 @@ "doc_path": "rules/maximumindependentset_maximumsetpacking/index.html" }, { - "source": 30, - "target": 43, + "source": 31, + "target": 44, "overhead": [ { "field": "num_vertices", @@ -938,8 +947,8 @@ "doc_path": "rules/minimumvertexcover_maximumindependentset/index.html" }, { - "source": 31, - "target": 33, + "source": 32, + "target": 34, "overhead": [ { "field": "num_vertices", @@ -953,8 +962,8 @@ "doc_path": "rules/maximumindependentset_casts/index.html" }, { - "source": 32, - "target": 29, + "source": 33, + "target": 30, "overhead": [ { "field": "num_vertices", @@ -968,8 +977,8 @@ "doc_path": "rules/maximumindependentset_casts/index.html" }, { - "source": 32, - "target": 33, + "source": 33, + "target": 34, "overhead": [ { "field": "num_vertices", @@ -983,8 +992,8 @@ "doc_path": "rules/maximumindependentset_casts/index.html" }, { - "source": 33, - "target": 30, + "source": 34, + "target": 31, "overhead": [ { "field": "num_vertices", @@ -998,8 +1007,8 @@ "doc_path": "rules/maximumindependentset_casts/index.html" }, { - "source": 34, - "target": 11, + "source": 35, + "target": 12, "overhead": [ { "field": "num_vars", @@ -1013,8 +1022,8 @@ "doc_path": "rules/maximummatching_ilp/index.html" }, { - "source": 34, - "target": 37, + "source": 35, + "target": 38, "overhead": [ { "field": "num_sets", @@ -1028,8 +1037,8 @@ "doc_path": "rules/maximummatching_maximumsetpacking/index.html" }, { - "source": 35, - "target": 29, + "source": 36, + "target": 30, "overhead": [ { "field": "num_vertices", @@ -1043,8 +1052,8 @@ "doc_path": "rules/maximumindependentset_maximumsetpacking/index.html" }, { - "source": 35, - "target": 37, + "source": 36, + "target": 38, "overhead": [ { "field": "num_sets", @@ -1058,8 +1067,8 @@ "doc_path": "rules/maximumsetpacking_casts/index.html" }, { - "source": 36, - "target": 47, + "source": 37, + "target": 48, "overhead": [ { "field": "num_vars", @@ -1069,8 +1078,8 @@ "doc_path": "rules/maximumsetpacking_qubo/index.html" }, { - "source": 37, - "target": 11, + "source": 38, + "target": 12, "overhead": [ { "field": "num_vars", @@ -1084,8 +1093,8 @@ "doc_path": "rules/maximumsetpacking_ilp/index.html" }, { - "source": 37, - "target": 30, + "source": 38, + "target": 31, "overhead": [ { "field": "num_vertices", @@ -1099,8 +1108,8 @@ "doc_path": "rules/maximumindependentset_maximumsetpacking/index.html" }, { - "source": 37, - "target": 36, + "source": 38, + "target": 37, "overhead": [ { "field": "num_sets", @@ -1114,8 +1123,8 @@ "doc_path": "rules/maximumsetpacking_casts/index.html" }, { - "source": 38, - "target": 11, + "source": 39, + "target": 12, "overhead": [ { "field": "num_vars", @@ -1129,8 +1138,8 @@ "doc_path": "rules/minimumdominatingset_ilp/index.html" }, { - "source": 41, - "target": 11, + "source": 42, + "target": 12, "overhead": [ { "field": "num_vars", @@ -1144,8 +1153,8 @@ "doc_path": "rules/minimumsetcovering_ilp/index.html" }, { - "source": 43, - "target": 30, + "source": 44, + "target": 31, "overhead": [ { "field": "num_vertices", @@ -1159,8 +1168,8 @@ "doc_path": "rules/minimumvertexcover_maximumindependentset/index.html" }, { - "source": 43, - "target": 41, + "source": 44, + "target": 42, "overhead": [ { "field": "num_sets", @@ -1174,8 +1183,8 @@ "doc_path": "rules/minimumvertexcover_minimumsetcovering/index.html" }, { - "source": 47, - "target": 11, + "source": 48, + "target": 12, "overhead": [ { "field": "num_vars", @@ -1189,8 +1198,8 @@ "doc_path": "rules/qubo_ilp/index.html" }, { - "source": 47, - "target": 51, + "source": 48, + "target": 52, "overhead": [ { "field": "num_spins", @@ -1200,7 +1209,7 @@ "doc_path": "rules/spinglass_qubo/index.html" }, { - "source": 49, + "source": 50, "target": 4, "overhead": [ { @@ -1215,8 +1224,8 @@ "doc_path": "rules/sat_circuitsat/index.html" }, { - "source": 49, - "target": 15, + "source": 50, + "target": 16, "overhead": [ { "field": "num_vertices", @@ -1230,8 +1239,8 @@ "doc_path": "rules/sat_coloring/index.html" }, { - "source": 49, - "target": 20, + "source": 50, + "target": 21, "overhead": [ { "field": "num_clauses", @@ -1245,8 +1254,8 @@ "doc_path": "rules/sat_ksat/index.html" }, { - "source": 49, - "target": 29, + "source": 50, + "target": 30, "overhead": [ { "field": "num_vertices", @@ -1260,8 +1269,8 @@ "doc_path": "rules/sat_maximumindependentset/index.html" }, { - "source": 49, - "target": 38, + "source": 50, + "target": 39, "overhead": [ { "field": "num_vertices", @@ -1275,8 +1284,8 @@ "doc_path": "rules/sat_minimumdominatingset/index.html" }, { - "source": 51, - "target": 47, + "source": 52, + "target": 48, "overhead": [ { "field": "num_vars", @@ -1286,8 +1295,8 @@ "doc_path": "rules/spinglass_qubo/index.html" }, { - "source": 52, - "target": 24, + "source": 53, + "target": 25, "overhead": [ { "field": "num_vertices", @@ -1301,8 +1310,8 @@ "doc_path": "rules/spinglass_maxcut/index.html" }, { - "source": 52, - "target": 51, + "source": 53, + "target": 52, "overhead": [ { "field": "num_spins", @@ -1316,8 +1325,8 @@ "doc_path": "rules/spinglass_casts/index.html" }, { - "source": 55, - "target": 11, + "source": 56, + "target": 12, "overhead": [ { "field": "num_vars", @@ -1331,8 +1340,8 @@ "doc_path": "rules/travelingsalesman_ilp/index.html" }, { - "source": 55, - "target": 47, + "source": 56, + "target": 48, "overhead": [ { "field": "num_vars", From 9e4417e2989f506fe5476e5f2ba2ec1fcd38002a Mon Sep 17 00:00:00 2001 From: GiggleLiu Date: Sun, 15 Mar 2026 02:18:25 +0800 Subject: [PATCH 7/8] Add HamiltonianCircuit to prelude and regenerate JSON artifacts Co-Authored-By: Claude Opus 4.6 --- docs/src/reductions/reduction_graph.json | 13 +++++++++++-- src/lib.rs | 3 ++- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/docs/src/reductions/reduction_graph.json b/docs/src/reductions/reduction_graph.json index 63823916..34434108 100644 --- a/docs/src/reductions/reduction_graph.json +++ b/docs/src/reductions/reduction_graph.json @@ -1,3 +1,12 @@ +{ + "nodes": [ + { + "name": "BMF", + "variant": {}, + +Exported to: docs/src/reductions/reduction_graph.json + +JSON content: { "nodes": [ { @@ -87,7 +96,7 @@ }, "category": "graph", "doc_path": "models/graph/struct.HamiltonianCircuit.html", - "complexity": "num_vertices^2 * 2^num_vertices" + "complexity": "1.657^num_vertices" }, { "name": "HamiltonianPath", @@ -1351,4 +1360,4 @@ "doc_path": "rules/travelingsalesman_qubo/index.html" } ] -} \ No newline at end of file +} diff --git a/src/lib.rs b/src/lib.rs index 1f1c99c3..579e31d4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -45,7 +45,8 @@ pub mod prelude { pub use crate::models::algebraic::{BMF, QUBO}; pub use crate::models::formula::{CNFClause, CircuitSAT, KSatisfiability, Satisfiability}; pub use crate::models::graph::{ - BicliqueCover, GraphPartitioning, HamiltonianPath, IsomorphicSpanningTree, SpinGlass, + BicliqueCover, GraphPartitioning, HamiltonianCircuit, HamiltonianPath, + IsomorphicSpanningTree, SpinGlass, SubgraphIsomorphism, }; pub use crate::models::graph::{ From ba1e534a189457f03fb4795090bb4c7b339b1f19 Mon Sep 17 00:00:00 2001 From: GiggleLiu Date: Sun, 15 Mar 2026 02:22:41 +0800 Subject: [PATCH 8/8] Fix formatting and update reduction_graph.json complexity Co-Authored-By: Claude Opus 4.6 --- docs/src/reductions/reduction_graph.json | 2 +- src/models/graph/hamiltonian_circuit.rs | 5 +---- src/models/graph/maximum_independent_set.rs | 3 +-- src/models/mod.rs | 10 +++++----- 4 files changed, 8 insertions(+), 12 deletions(-) diff --git a/docs/src/reductions/reduction_graph.json b/docs/src/reductions/reduction_graph.json index 63823916..278d3693 100644 --- a/docs/src/reductions/reduction_graph.json +++ b/docs/src/reductions/reduction_graph.json @@ -87,7 +87,7 @@ }, "category": "graph", "doc_path": "models/graph/struct.HamiltonianCircuit.html", - "complexity": "num_vertices^2 * 2^num_vertices" + "complexity": "1.657^num_vertices" }, { "name": "HamiltonianPath", diff --git a/src/models/graph/hamiltonian_circuit.rs b/src/models/graph/hamiltonian_circuit.rs index b9aa764d..21472167 100644 --- a/src/models/graph/hamiltonian_circuit.rs +++ b/src/models/graph/hamiltonian_circuit.rs @@ -148,10 +148,7 @@ pub(crate) fn canonical_model_example_specs() -> Vec Vec