From 0182bb192cc84530d59b1f28aed265b238a95a75 Mon Sep 17 00:00:00 2001 From: GiggleLiu Date: Mon, 16 Feb 2026 23:33:12 +0800 Subject: [PATCH 01/10] refactor: internalize reduction structs and gadgets in rules module Remove public re-exports of ~30 ReductionXToY structs, 6 gadget functions, BoolVar, LogicGadget, and JSON serialization types (EdgeJson, NodeJson, ReductionGraphJson) from rules/mod.rs. Users interact with reductions via the ReduceTo trait or ReductionGraph, never referencing these structs by name. Also remove unused ReductionColoringToILP type alias and suppress dead_code warnings on LogicGadget fields only read in tests. Co-Authored-By: Claude Opus 4.6 --- src/rules/circuit_spinglass.rs | 2 ++ src/rules/coloring_ilp.rs | 3 --- src/rules/graph.rs | 40 ++++++++++++++-------------- src/rules/mod.rs | 48 +--------------------------------- 4 files changed, 24 insertions(+), 69 deletions(-) diff --git a/src/rules/circuit_spinglass.rs b/src/rules/circuit_spinglass.rs index b31f73583..30a019bfd 100644 --- a/src/rules/circuit_spinglass.rs +++ b/src/rules/circuit_spinglass.rs @@ -30,8 +30,10 @@ pub struct LogicGadget { /// The SpinGlass problem encoding the gate. pub problem: SpinGlass, /// Input spin indices (0-indexed within the gadget). + #[allow(dead_code)] // read in tests pub inputs: Vec, /// Output spin indices (0-indexed within the gadget). + #[allow(dead_code)] // read in tests pub outputs: Vec, } diff --git a/src/rules/coloring_ilp.rs b/src/rules/coloring_ilp.rs index 0989ec208..f9e982da0 100644 --- a/src/rules/coloring_ilp.rs +++ b/src/rules/coloring_ilp.rs @@ -150,9 +150,6 @@ macro_rules! impl_kcoloring_to_ilp { impl_kcoloring_to_ilp!(K1, K2, K3, K4); -// Keep the old type alias for backwards compatibility -pub type ReductionColoringToILP = ReductionKColoringToILP; - #[cfg(test)] #[path = "../unit_tests/rules/coloring_ilp.rs"] mod tests; diff --git a/src/rules/graph.rs b/src/rules/graph.rs index be67eee8d..8183ac2c5 100644 --- a/src/rules/graph.rs +++ b/src/rules/graph.rs @@ -32,36 +32,38 @@ pub(crate) struct ReductionEdgeData { /// JSON-serializable representation of the reduction graph. #[derive(Debug, Clone, Serialize)] -pub struct ReductionGraphJson { +pub(crate) struct ReductionGraphJson { /// List of problem type nodes. - pub nodes: Vec, + pub(crate) nodes: Vec, /// List of reduction edges. - pub edges: Vec, + pub(crate) edges: Vec, } impl ReductionGraphJson { /// Get the source node of an edge. - pub fn source_node(&self, edge: &EdgeJson) -> &NodeJson { + #[cfg_attr(not(test), allow(dead_code))] + pub(crate) fn source_node(&self, edge: &EdgeJson) -> &NodeJson { &self.nodes[edge.source] } /// Get the target node of an edge. - pub fn target_node(&self, edge: &EdgeJson) -> &NodeJson { + #[cfg_attr(not(test), allow(dead_code))] + pub(crate) fn target_node(&self, edge: &EdgeJson) -> &NodeJson { &self.nodes[edge.target] } } /// A node in the reduction graph JSON. #[derive(Debug, Clone, Serialize)] -pub struct NodeJson { +pub(crate) struct NodeJson { /// Base problem name (e.g., "MaximumIndependentSet"). - pub name: String, + pub(crate) name: String, /// Variant attributes as key-value pairs. - pub variant: BTreeMap, + pub(crate) variant: BTreeMap, /// Category of the problem (e.g., "graph", "set", "optimization", "satisfiability", "specialized"). - pub category: String, + pub(crate) category: String, /// Relative rustdoc path (e.g., "models/graph/maximum_independent_set"). - pub doc_path: String, + pub(crate) doc_path: String, } /// Internal reference to a problem variant, used as HashMap key. @@ -73,24 +75,24 @@ struct VariantRef { /// A single output field in the reduction overhead. #[derive(Debug, Clone, Serialize)] -pub struct OverheadFieldJson { +pub(crate) struct OverheadFieldJson { /// Output field name (e.g., "num_vars"). - pub field: String, + pub(crate) field: String, /// Formula as a human-readable string (e.g., "num_vertices"). - pub formula: String, + pub(crate) formula: String, } /// An edge in the reduction graph JSON. #[derive(Debug, Clone, Serialize)] -pub struct EdgeJson { +pub(crate) struct EdgeJson { /// Index into the `nodes` array for the source problem variant. - pub source: usize, + pub(crate) source: usize, /// Index into the `nodes` array for the target problem variant. - pub target: usize, + pub(crate) target: usize, /// Reduction overhead: output size as polynomials of input size. - pub overhead: Vec, + pub(crate) overhead: Vec, /// Relative rustdoc path for the reduction module. - pub doc_path: String, + pub(crate) doc_path: String, } /// A path through the variant-level reduction graph. @@ -641,7 +643,7 @@ impl ReductionGraph { /// Export the reduction graph as a JSON-serializable structure. /// /// Nodes and edges come directly from the variant-level graph. - pub fn to_json(&self) -> ReductionGraphJson { + pub(crate) fn to_json(&self) -> ReductionGraphJson { use crate::registry::ProblemSchemaEntry; // Build name -> module_path lookup from ProblemSchemaEntry inventory diff --git a/src/rules/mod.rs b/src/rules/mod.rs index 785ba9049..97f1bf711 100644 --- a/src/rules/mod.rs +++ b/src/rules/mod.rs @@ -58,35 +58,11 @@ mod minimumvertexcover_ilp; #[cfg(feature = "ilp")] mod travelingsalesman_ilp; -pub use circuit_spinglass::{ - and_gadget, not_gadget, or_gadget, set0_gadget, set1_gadget, xor_gadget, LogicGadget, - ReductionCircuitToSG, -}; -pub use coloring_qubo::ReductionKColoringToQUBO; -pub use factoring_circuit::ReductionFactoringToCircuit; pub use graph::{ - ChainedReduction, EdgeJson, ExecutablePath, NodeJson, ReductionGraph, ReductionGraphJson, - ReductionPath, ReductionStep, + ChainedReduction, ExecutablePath, ReductionGraph, ReductionPath, ReductionStep, }; #[cfg(test)] pub(crate) use graph::validate_overhead_variables; -pub use ksatisfiability_qubo::{Reduction3SATToQUBO, ReductionKSatToQUBO}; -pub use maximumindependentset_gridgraph::{ReductionISSimpleToGrid, ReductionISUnitDiskToGrid}; -pub use maximumindependentset_maximumsetpacking::{ReductionISToSP, ReductionSPToIS}; -pub use maximumindependentset_qubo::ReductionISToQUBO; -pub use maximumindependentset_triangular::ReductionISSimpleToTriangular; -pub use maximummatching_maximumsetpacking::ReductionMatchingToSP; -pub use maximumsetpacking_qubo::ReductionSPToQUBO; -pub use minimumvertexcover_maximumindependentset::{ReductionISToVC, ReductionVCToIS}; -pub use minimumvertexcover_minimumsetcovering::ReductionVCToSC; -pub use minimumvertexcover_qubo::ReductionVCToQUBO; -pub use sat_circuitsat::ReductionSATToCircuit; -pub use sat_coloring::ReductionSATToColoring; -pub use sat_ksat::{ReductionKSATToSAT, ReductionSATToKSAT}; -pub use sat_maximumindependentset::{BoolVar, ReductionSATToIS}; -pub use sat_minimumdominatingset::ReductionSATToDS; -pub use spinglass_maxcut::{ReductionMaxCutToSG, ReductionSGToMaxCut}; -pub use spinglass_qubo::{ReductionQUBOToSG, ReductionSGToQUBO}; pub use traits::{ReduceTo, ReductionAutoCast, ReductionResult}; /// Generates a variant-cast `ReduceTo` impl with `#[reduction]` registration. @@ -137,25 +113,3 @@ macro_rules! impl_variant_reduction { }; } -#[cfg(feature = "ilp")] -pub use coloring_ilp::{ReductionColoringToILP, ReductionKColoringToILP}; -#[cfg(feature = "ilp")] -pub use factoring_ilp::ReductionFactoringToILP; -#[cfg(feature = "ilp")] -pub use ilp_qubo::ReductionILPToQUBO; -#[cfg(feature = "ilp")] -pub use maximumclique_ilp::ReductionCliqueToILP; -#[cfg(feature = "ilp")] -pub use maximumindependentset_ilp::ReductionISToILP; -#[cfg(feature = "ilp")] -pub use maximummatching_ilp::ReductionMatchingToILP; -#[cfg(feature = "ilp")] -pub use maximumsetpacking_ilp::ReductionSPToILP; -#[cfg(feature = "ilp")] -pub use minimumdominatingset_ilp::ReductionDSToILP; -#[cfg(feature = "ilp")] -pub use minimumsetcovering_ilp::ReductionSCToILP; -#[cfg(feature = "ilp")] -pub use minimumvertexcover_ilp::ReductionVCToILP; -#[cfg(feature = "ilp")] -pub use travelingsalesman_ilp::ReductionTSPToILP; From e23cb60d5eab137d4e2e1f433e01c4d9ffe21a8e Mon Sep 17 00:00:00 2001 From: GiggleLiu Date: Tue, 17 Feb 2026 00:05:34 +0800 Subject: [PATCH 02/10] refactor: internalize unitdiskmapping implementation details Change internal type re-exports (CopyLine, MappingGrid, CellState, Pattern, etc.) from pub to pub(crate) in unitdiskmapping/mod.rs. Change alpha_tensor and pathdecomposition modules to pub(crate). Add #[cfg(test)] guards on re-exports only needed by unit tests. Add #[doc(hidden)] _internal module for the export_mapping_stages example which needs these types. Keep ksg, triangular modules and GridKind/MappingResult as pub. Co-Authored-By: Claude Opus 4.6 --- examples/export_mapping_stages.rs | 9 +++-- src/rules/unitdiskmapping/copyline.rs | 1 + src/rules/unitdiskmapping/mod.rs | 34 +++++++++++++------ .../unitdiskmapping/pathdecomposition.rs | 4 +-- src/rules/unitdiskmapping/traits.rs | 1 + 5 files changed, 32 insertions(+), 17 deletions(-) diff --git a/examples/export_mapping_stages.rs b/examples/export_mapping_stages.rs index 46d34e713..3caf8cdec 100644 --- a/examples/export_mapping_stages.rs +++ b/examples/export_mapping_stages.rs @@ -8,8 +8,11 @@ //! cargo run --example export_mapping_stages -- petersen triangular use problemreductions::rules::unitdiskmapping::{ - create_copylines, ksg, mis_overhead_copyline, mis_overhead_copyline_triangular, triangular, - CopyLine, MappingGrid, + _internal::{ + create_copylines, mis_overhead_copyline, mis_overhead_copyline_triangular, CopyLine, + MappingGrid, + }, + ksg, triangular, }; use problemreductions::topology::smallgraph; use serde::Serialize; @@ -102,7 +105,7 @@ fn gadget_name(idx: usize) -> String { // The Typst script converts to 1-indexed for comparison with Julia. // DO NOT add +1 here - keep 0-indexed! fn extract_grid_nodes(grid: &MappingGrid) -> Vec { - use problemreductions::rules::unitdiskmapping::CellState; + use problemreductions::rules::unitdiskmapping::_internal::CellState; let mut nodes = Vec::new(); let (rows, cols) = grid.size(); for r in 0..rows { diff --git a/src/rules/unitdiskmapping/copyline.rs b/src/rules/unitdiskmapping/copyline.rs index d74d7ac36..aeda04850 100644 --- a/src/rules/unitdiskmapping/copyline.rs +++ b/src/rules/unitdiskmapping/copyline.rs @@ -425,6 +425,7 @@ pub fn mis_overhead_copyline(line: &CopyLine, spacing: usize, padding: usize) -> /// /// # Returns /// A tuple of (locations, weights) vectors. +#[allow(dead_code)] pub fn copyline_weighted_locations_triangular( line: &CopyLine, spacing: usize, diff --git a/src/rules/unitdiskmapping/mod.rs b/src/rules/unitdiskmapping/mod.rs index d807103bf..d44f651c3 100644 --- a/src/rules/unitdiskmapping/mod.rs +++ b/src/rules/unitdiskmapping/mod.rs @@ -25,24 +25,36 @@ //! let tri_result = triangular::map_weighted(3, &edges); //! ``` -pub mod alpha_tensor; +#[allow(dead_code)] +pub(crate) mod alpha_tensor; mod copyline; mod grid; pub mod ksg; -pub mod pathdecomposition; +pub(crate) mod pathdecomposition; mod traits; pub mod triangular; mod weighted; -// Re-export shared types -pub use copyline::{create_copylines, mis_overhead_copyline, remove_order, CopyLine}; -pub use grid::{CellState, MappingGrid}; -pub use pathdecomposition::{pathwidth, Layout, PathDecompositionMethod}; -pub use traits::{apply_gadget, pattern_matches, unapply_gadget, Pattern, PatternCell}; - // Re-export commonly used items from submodules for convenience pub use ksg::{GridKind, MappingResult}; -// Re-exports from private modules (not accessible via ksg:: or triangular::) -pub use copyline::{copyline_weighted_locations_triangular, mis_overhead_copyline_triangular}; -pub use weighted::{map_weights, trace_centers, Weightable}; +// Re-exports for unit tests (only needed in test builds) +#[cfg(test)] +pub(crate) use copyline::{ + copyline_weighted_locations_triangular, create_copylines, mis_overhead_copyline, CopyLine, +}; +#[cfg(test)] +pub(crate) use grid::{CellState, MappingGrid}; +#[cfg(test)] +pub(crate) use traits::{apply_gadget, unapply_gadget, Pattern}; +#[cfg(test)] +pub(crate) use weighted::{map_weights, trace_centers}; + +// Hidden re-exports for development tools (examples/export_mapping_stages.rs) +#[doc(hidden)] +pub mod _internal { + pub use super::copyline::{ + create_copylines, mis_overhead_copyline, mis_overhead_copyline_triangular, CopyLine, + }; + pub use super::grid::{CellState, MappingGrid}; +} diff --git a/src/rules/unitdiskmapping/pathdecomposition.rs b/src/rules/unitdiskmapping/pathdecomposition.rs index ec0ef889a..cc930fde7 100644 --- a/src/rules/unitdiskmapping/pathdecomposition.rs +++ b/src/rules/unitdiskmapping/pathdecomposition.rs @@ -409,10 +409,8 @@ impl PathDecompositionMethod { /// * `method` - The decomposition method to use /// /// # Example -/// ``` -/// use problemreductions::rules::unitdiskmapping::pathdecomposition::{pathwidth, PathDecompositionMethod}; /// -/// // Path graph: 0-1-2 +/// ```text /// let edges = vec![(0, 1), (1, 2)]; /// let layout = pathwidth(3, &edges, PathDecompositionMethod::greedy()); /// assert_eq!(layout.vertices.len(), 3); diff --git a/src/rules/unitdiskmapping/traits.rs b/src/rules/unitdiskmapping/traits.rs index 0fabe8ed5..e89910fb0 100644 --- a/src/rules/unitdiskmapping/traits.rs +++ b/src/rules/unitdiskmapping/traits.rs @@ -188,6 +188,7 @@ pub fn apply_gadget(pattern: &P, grid: &mut MappingGrid, i: usize, j /// Unapply a gadget pattern at position (i, j). #[allow(clippy::needless_range_loop)] +#[allow(dead_code)] pub fn unapply_gadget(pattern: &P, grid: &mut MappingGrid, i: usize, j: usize) { let source = pattern.source_matrix(); let (m, n) = pattern.size(); From eb5b601fd34b330207584e19b40a98ab82a8fde3 Mon Sep 17 00:00:00 2001 From: GiggleLiu Date: Tue, 17 Feb 2026 00:11:17 +0800 Subject: [PATCH 03/10] refactor: internalize polynomial/truth_table, delete unused graph_types module - Change polynomial and truth_table modules from pub to pub(crate) since they are only used internally via crate:: imports. - Delete graph_types.rs and its unit test file (zero internal imports, confirmed by codebase audit; actual graph types live in topology/). - Remove truth_table integration tests from tests/suites/reductions.rs (equivalent coverage exists in src/unit_tests/truth_table.rs). - Remove doc example from TruthTable since the module is now internal. Co-Authored-By: Claude Opus 4.6 --- src/graph_types.rs | 28 -------------------- src/lib.rs | 6 ++--- src/truth_table.rs | 12 --------- src/unit_tests/graph_types.rs | 45 -------------------------------- tests/suites/reductions.rs | 49 ++--------------------------------- 5 files changed, 5 insertions(+), 135 deletions(-) delete mode 100644 src/graph_types.rs delete mode 100644 src/unit_tests/graph_types.rs diff --git a/src/graph_types.rs b/src/graph_types.rs deleted file mode 100644 index 4f0d15e71..000000000 --- a/src/graph_types.rs +++ /dev/null @@ -1,28 +0,0 @@ -//! Graph type markers for parametric problem modeling. -//! -//! ZST marker structs for graph types used as type parameters in problem definitions. -//! The subtype hierarchy is managed via `VariantParam` trait implementations (see `src/variant.rs`). - -/// Simple (arbitrary) graph - the most general graph type. -#[derive(Debug, Clone, Copy, Default)] -pub struct SimpleGraph; - -/// Unit disk graph - vertices are points, edges connect points within unit distance. -#[derive(Debug, Clone, Copy, Default)] -pub struct UnitDiskGraph; - -/// King's subgraph - a unit disk graph on a square grid with king's move connectivity. -#[derive(Debug, Clone, Copy, Default)] -pub struct KingsSubgraph; - -/// Triangular subgraph - a unit disk graph on a triangular lattice. -#[derive(Debug, Clone, Copy, Default)] -pub struct TriangularSubgraph; - -/// Hypergraph - most general graph type. Edges can connect any number of vertices. -#[derive(Debug, Clone, Copy, Default)] -pub struct HyperGraph; - -#[cfg(test)] -#[path = "unit_tests/graph_types.rs"] -mod tests; diff --git a/src/lib.rs b/src/lib.rs index 743813c7d..bb6c0f953 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -64,17 +64,17 @@ pub mod config; pub mod error; pub mod export; -pub mod graph_types; pub mod io; pub mod models; -pub mod polynomial; +pub(crate) mod polynomial; pub mod registry; pub mod rules; pub mod solvers; pub mod testing; pub mod topology; pub mod traits; -pub mod truth_table; +#[allow(dead_code)] +pub(crate) mod truth_table; pub mod types; pub mod variant; diff --git a/src/truth_table.rs b/src/truth_table.rs index 139373b39..479b02983 100644 --- a/src/truth_table.rs +++ b/src/truth_table.rs @@ -10,18 +10,6 @@ use serde::{Deserialize, Serialize}; /// /// The truth table stores the output for each possible input combination. /// For n input variables, there are 2^n rows in the table. -/// -/// # Example -/// -/// ``` -/// use problemreductions::truth_table::TruthTable; -/// -/// // Create AND gate truth table -/// let and_gate = TruthTable::from_outputs(2, vec![false, false, false, true]); -/// assert!(!and_gate.evaluate(&[false, false])); -/// assert!(!and_gate.evaluate(&[true, false])); -/// assert!(and_gate.evaluate(&[true, true])); -/// ``` #[derive(Debug, Clone, PartialEq, Eq)] pub struct TruthTable { /// Number of input variables. diff --git a/src/unit_tests/graph_types.rs b/src/unit_tests/graph_types.rs deleted file mode 100644 index a2a097f8d..000000000 --- a/src/unit_tests/graph_types.rs +++ /dev/null @@ -1,45 +0,0 @@ -use super::*; -use crate::variant::VariantParam; - -#[test] -fn test_graph_type_traits() { - // Test Default - let _: SimpleGraph = Default::default(); - let _: UnitDiskGraph = Default::default(); - let _: KingsSubgraph = Default::default(); - let _: TriangularSubgraph = Default::default(); - let _: HyperGraph = Default::default(); - - // Test Copy (SimpleGraph implements Copy, so no need to clone) - let g = SimpleGraph; - let _g2 = g; // Copy - let g = SimpleGraph; - let _g2 = g; - let _g3 = g; // still usable -} - -#[test] -fn test_planargraph_variant_param() { - use crate::topology::PlanarGraph; - assert_eq!(PlanarGraph::CATEGORY, "graph"); - assert_eq!(PlanarGraph::VALUE, "PlanarGraph"); - assert_eq!(PlanarGraph::PARENT_VALUE, Some("SimpleGraph")); -} - -#[test] -fn test_bipartitegraph_variant_param() { - use crate::topology::BipartiteGraph; - assert_eq!(BipartiteGraph::CATEGORY, "graph"); - assert_eq!(BipartiteGraph::VALUE, "BipartiteGraph"); - assert_eq!(BipartiteGraph::PARENT_VALUE, Some("SimpleGraph")); -} - -#[test] -fn test_marker_structs_exist() { - // Verify that all ZST marker structs still exist and can be instantiated - let _ = SimpleGraph; - let _ = UnitDiskGraph; - let _ = KingsSubgraph; - let _ = TriangularSubgraph; - let _ = HyperGraph; -} diff --git a/tests/suites/reductions.rs b/tests/suites/reductions.rs index a71202443..aca43ae8e 100644 --- a/tests/suites/reductions.rs +++ b/tests/suites/reductions.rs @@ -420,53 +420,8 @@ mod topology_tests { } } -/// Tests for TruthTable integration with reductions. -mod truth_table_tests { - use problemreductions::truth_table::TruthTable; - - #[test] - fn test_truth_table_to_sat() { - // Create a simple truth table (AND gate) - let and_gate = TruthTable::and(2); - - // Find satisfying assignments - let satisfying = and_gate.satisfying_assignments(); - - // AND gate: only [T, T] satisfies - assert_eq!(satisfying.len(), 1); - assert_eq!(satisfying[0], vec![true, true]); - } - - #[test] - fn test_truth_table_xor_to_sat() { - // XOR has exactly 2^(n-1) satisfying assignments for n inputs - let xor3 = TruthTable::xor(3); - let satisfying = xor3.satisfying_assignments(); - - // 3-XOR: exactly 4 satisfying assignments - assert_eq!(satisfying.len(), 4); - - // Each should have odd number of true inputs - for assignment in &satisfying { - let true_count = assignment.iter().filter(|&&b| b).count(); - assert_eq!(true_count % 2, 1); - } - } - - #[test] - fn test_truth_table_combined() { - // Test combining truth tables (useful for circuit construction) - let a = TruthTable::and(2); - let b = TruthTable::or(2); - - // a AND b (element-wise AND of two truth tables) - let combined = a.and_with(&b); - - // AND result: [F,F,F,T], OR result: [F,T,T,T] - // Combined: [F,F,F,T] - assert_eq!(combined.outputs_vec(), vec![false, false, false, true]); - } -} +// TruthTable integration tests moved to src/unit_tests/truth_table.rs +// (truth_table module is now pub(crate)) /// Tests for QUBO reductions against ground truth JSON. mod qubo_reductions { From ca83a12ec0a4c7bf688e185abb0b63e14438c543 Mon Sep 17 00:00:00 2001 From: GiggleLiu Date: Tue, 17 Feb 2026 00:35:08 +0800 Subject: [PATCH 04/10] feat: add is_valid_solution methods to graph problem types Add public is_valid_solution(&self, config: &[usize]) -> bool methods to all graph problem types, delegating to existing private validation helpers. Also add cut_size method to MaxCut for computing partition cut sizes. Co-Authored-By: Claude Opus 4.6 --- src/models/graph/kcoloring.rs | 5 +++++ src/models/graph/max_cut.rs | 9 +++++++++ src/models/graph/maximal_is.rs | 5 +++++ src/models/graph/maximum_clique.rs | 5 +++++ src/models/graph/maximum_independent_set.rs | 5 +++++ src/models/graph/maximum_matching.rs | 5 +++++ src/models/graph/minimum_dominating_set.rs | 5 +++++ src/models/graph/minimum_vertex_cover.rs | 5 +++++ src/models/graph/traveling_salesman.rs | 5 +++++ 9 files changed, 49 insertions(+) diff --git a/src/models/graph/kcoloring.rs b/src/models/graph/kcoloring.rs index efa0619f2..396be8fe4 100644 --- a/src/models/graph/kcoloring.rs +++ b/src/models/graph/kcoloring.rs @@ -89,6 +89,11 @@ impl KColoring { self.num_colors } + /// Check if a configuration is a valid coloring. + pub fn is_valid_solution(&self, config: &[usize]) -> bool { + is_valid_coloring(&self.graph, config, self.num_colors) + } + /// Check if a coloring is valid. fn is_valid_coloring(&self, config: &[usize]) -> bool { for (u, v) in self.graph.edges() { diff --git a/src/models/graph/max_cut.rs b/src/models/graph/max_cut.rs index 7b0b2fc4b..e0a6d103d 100644 --- a/src/models/graph/max_cut.rs +++ b/src/models/graph/max_cut.rs @@ -136,6 +136,15 @@ impl MaxCut { pub fn edge_weights(&self) -> Vec { self.edge_weights.clone() } + + /// Compute the cut size for a given partition configuration. + pub fn cut_size(&self, config: &[usize]) -> W::Sum + where + W: WeightElement, + { + let partition: Vec = config.iter().map(|&c| c != 0).collect(); + cut_size(&self.graph, &self.edge_weights, &partition) + } } impl Problem for MaxCut diff --git a/src/models/graph/maximal_is.rs b/src/models/graph/maximal_is.rs index 8da3b9dab..35720c2e0 100644 --- a/src/models/graph/maximal_is.rs +++ b/src/models/graph/maximal_is.rs @@ -86,6 +86,11 @@ impl MaximalIS { !W::IS_UNIT } + /// Check if a configuration is a valid maximal independent set. + pub fn is_valid_solution(&self, config: &[usize]) -> bool { + self.is_maximal(config) + } + /// Check if a configuration is an independent set. fn is_independent(&self, config: &[usize]) -> bool { for (u, v) in self.graph.edges() { diff --git a/src/models/graph/maximum_clique.rs b/src/models/graph/maximum_clique.rs index 6047b8dc9..7fa30b26f 100644 --- a/src/models/graph/maximum_clique.rs +++ b/src/models/graph/maximum_clique.rs @@ -88,6 +88,11 @@ impl MaximumClique { { !W::IS_UNIT } + + /// Check if a configuration is a valid clique. + pub fn is_valid_solution(&self, config: &[usize]) -> bool { + is_clique_config(&self.graph, config) + } } impl Problem for MaximumClique diff --git a/src/models/graph/maximum_independent_set.rs b/src/models/graph/maximum_independent_set.rs index 8177191eb..ef00de3d0 100644 --- a/src/models/graph/maximum_independent_set.rs +++ b/src/models/graph/maximum_independent_set.rs @@ -88,6 +88,11 @@ impl MaximumIndependentSet { { !W::IS_UNIT } + + /// Check if a configuration is a valid independent set. + pub fn is_valid_solution(&self, config: &[usize]) -> bool { + is_independent_set_config(&self.graph, config) + } } impl Problem for MaximumIndependentSet diff --git a/src/models/graph/maximum_matching.rs b/src/models/graph/maximum_matching.rs index 5e7035613..28ad40299 100644 --- a/src/models/graph/maximum_matching.rs +++ b/src/models/graph/maximum_matching.rs @@ -121,6 +121,11 @@ impl MaximumMatching { } /// Check if a configuration is a valid matching. + pub fn is_valid_solution(&self, config: &[usize]) -> bool { + self.is_valid_matching(config) + } + + /// Check if a configuration is a valid matching (internal). fn is_valid_matching(&self, config: &[usize]) -> bool { let mut vertex_used = vec![false; self.graph.num_vertices()]; diff --git a/src/models/graph/minimum_dominating_set.rs b/src/models/graph/minimum_dominating_set.rs index 44544c1e1..2bf738cbf 100644 --- a/src/models/graph/minimum_dominating_set.rs +++ b/src/models/graph/minimum_dominating_set.rs @@ -88,6 +88,11 @@ impl MinimumDominatingSet { &self.weights } + /// Check if a configuration is a valid dominating set. + pub fn is_valid_solution(&self, config: &[usize]) -> bool { + self.is_dominating(config) + } + /// Check if a set of vertices is a dominating set. fn is_dominating(&self, config: &[usize]) -> bool { let n = self.graph.num_vertices(); diff --git a/src/models/graph/minimum_vertex_cover.rs b/src/models/graph/minimum_vertex_cover.rs index fd21883ac..e60372755 100644 --- a/src/models/graph/minimum_vertex_cover.rs +++ b/src/models/graph/minimum_vertex_cover.rs @@ -83,6 +83,11 @@ impl MinimumVertexCover { { !W::IS_UNIT } + + /// Check if a configuration is a valid vertex cover. + pub fn is_valid_solution(&self, config: &[usize]) -> bool { + is_vertex_cover_config(&self.graph, config) + } } impl Problem for MinimumVertexCover diff --git a/src/models/graph/traveling_salesman.rs b/src/models/graph/traveling_salesman.rs index 35edcf2c5..38feec44b 100644 --- a/src/models/graph/traveling_salesman.rs +++ b/src/models/graph/traveling_salesman.rs @@ -111,6 +111,11 @@ impl TravelingSalesman { !W::IS_UNIT } + /// Check if a configuration is a valid Hamiltonian cycle. + pub fn is_valid_solution(&self, config: &[usize]) -> bool { + self.is_valid_hamiltonian_cycle(config) + } + /// Check if a configuration forms a valid Hamiltonian cycle. fn is_valid_hamiltonian_cycle(&self, config: &[usize]) -> bool { if config.len() != self.graph.num_edges() { From d6194753de977af7a7e6fffa28672f133c340100 Mon Sep 17 00:00:00 2001 From: GiggleLiu Date: Tue, 17 Feb 2026 00:35:14 +0800 Subject: [PATCH 05/10] feat: add is_valid_solution methods to set and specialized problems Add public is_valid_solution methods to MaximumSetPacking, MinimumSetCovering, BicliqueCover, CircuitSAT, and Factoring. Each delegates to existing validation logic. PaintShop already has count_switches and BMF is skipped per design. Co-Authored-By: Claude Opus 4.6 --- src/models/set/maximum_set_packing.rs | 5 +++++ src/models/set/minimum_set_covering.rs | 7 +++++++ src/models/specialized/biclique_cover.rs | 5 +++++ src/models/specialized/circuit.rs | 5 +++++ src/models/specialized/factoring.rs | 5 +++++ 5 files changed, 27 insertions(+) diff --git a/src/models/set/maximum_set_packing.rs b/src/models/set/maximum_set_packing.rs index 26272c6eb..9bfc8c5e9 100644 --- a/src/models/set/maximum_set_packing.rs +++ b/src/models/set/maximum_set_packing.rs @@ -117,6 +117,11 @@ impl MaximumSetPacking { pub fn weights_ref(&self) -> &Vec { &self.weights } + + /// Check if a configuration is a valid set packing. + pub fn is_valid_solution(&self, config: &[usize]) -> bool { + is_valid_packing(&self.sets, config) + } } impl Problem for MaximumSetPacking diff --git a/src/models/set/minimum_set_covering.rs b/src/models/set/minimum_set_covering.rs index d2189c535..889abb915 100644 --- a/src/models/set/minimum_set_covering.rs +++ b/src/models/set/minimum_set_covering.rs @@ -115,6 +115,13 @@ impl MinimumSetCovering { &self.weights } + /// Check if a configuration is a valid set cover. + pub fn is_valid_solution(&self, config: &[usize]) -> bool { + let covered = self.covered_elements(config); + covered.len() == self.universe_size + && (0..self.universe_size).all(|e| covered.contains(&e)) + } + /// Check which elements are covered by selected sets. pub fn covered_elements(&self, config: &[usize]) -> HashSet { let mut covered = HashSet::new(); diff --git a/src/models/specialized/biclique_cover.rs b/src/models/specialized/biclique_cover.rs index 75dbe4f65..6d5075619 100644 --- a/src/models/specialized/biclique_cover.rs +++ b/src/models/specialized/biclique_cover.rs @@ -164,6 +164,11 @@ impl BicliqueCover { false } + /// Check if a configuration is a valid biclique cover. + pub fn is_valid_solution(&self, config: &[usize]) -> bool { + self.is_valid_cover(config) + } + /// Check if all edges are covered. pub fn is_valid_cover(&self, config: &[usize]) -> bool { use crate::topology::Graph; diff --git a/src/models/specialized/circuit.rs b/src/models/specialized/circuit.rs index a3736db50..2cb7c6b41 100644 --- a/src/models/specialized/circuit.rs +++ b/src/models/specialized/circuit.rs @@ -238,6 +238,11 @@ impl CircuitSAT { &self.variables } + /// Check if a configuration is a valid satisfying assignment. + pub fn is_valid_solution(&self, config: &[usize]) -> bool { + self.count_satisfied(config) == self.circuit.num_assignments() + } + /// Convert a configuration to variable assignments. fn config_to_assignments(&self, config: &[usize]) -> HashMap { self.variables diff --git a/src/models/specialized/factoring.rs b/src/models/specialized/factoring.rs index e818b788d..877a98e7f 100644 --- a/src/models/specialized/factoring.rs +++ b/src/models/specialized/factoring.rs @@ -90,6 +90,11 @@ impl Factoring { (a, b) } + /// Check if a configuration is a valid factorization. + pub fn is_valid_solution(&self, config: &[usize]) -> bool { + self.is_valid_factorization(config) + } + /// Check if the configuration is a valid factorization. pub fn is_valid_factorization(&self, config: &[usize]) -> bool { let (a, b) = self.read_factors(config); From b05039d64638b67bc713df1d5562db7a7ddfd157 Mon Sep 17 00:00:00 2001 From: GiggleLiu Date: Tue, 17 Feb 2026 00:58:14 +0800 Subject: [PATCH 06/10] refactor: internalize validation free functions, keep as problem methods Change standalone validation free functions (is_independent_set, is_vertex_cover, is_clique, etc.) from pub to pub(crate) with #[cfg(test)] for test-only functions. Functions still used in non-test code (is_valid_coloring, is_hamiltonian_cycle, cut_size) remain pub(crate) without #[cfg(test)]. - Change 17 validation functions from pub to pub(crate) - Add #[cfg(test)] to 14 functions only used in tests - Remove validation function re-exports from graph/set/specialized mod.rs - Make submodules pub(crate) for crate-internal test access - Update graph_models.rs imports to use full module paths Co-Authored-By: Claude Opus 4.6 --- src/models/graph/kcoloring.rs | 2 +- src/models/graph/max_cut.rs | 2 +- src/models/graph/maximal_is.rs | 3 +- src/models/graph/maximum_clique.rs | 3 +- src/models/graph/maximum_independent_set.rs | 3 +- src/models/graph/maximum_matching.rs | 3 +- src/models/graph/minimum_dominating_set.rs | 3 +- src/models/graph/minimum_vertex_cover.rs | 3 +- src/models/graph/mod.rs | 36 ++++++++++----------- src/models/graph/traveling_salesman.rs | 2 +- src/models/satisfiability/mod.rs | 3 -- src/models/satisfiability/sat.rs | 10 +++++- src/models/set/maximum_set_packing.rs | 3 +- src/models/set/minimum_set_covering.rs | 3 +- src/models/set/mod.rs | 8 ++--- src/models/specialized/biclique_cover.rs | 3 +- src/models/specialized/bmf.rs | 6 ++-- src/models/specialized/circuit.rs | 3 +- src/models/specialized/factoring.rs | 3 +- src/models/specialized/mod.rs | 20 ++++++------ src/models/specialized/paintshop.rs | 3 +- src/unit_tests/graph_models.rs | 8 ++--- 22 files changed, 74 insertions(+), 59 deletions(-) diff --git a/src/models/graph/kcoloring.rs b/src/models/graph/kcoloring.rs index 396be8fe4..a3bc7bfb4 100644 --- a/src/models/graph/kcoloring.rs +++ b/src/models/graph/kcoloring.rs @@ -155,7 +155,7 @@ impl SatisfactionProblem for KColoring /// /// # Panics /// Panics if `coloring.len() != graph.num_vertices()`. -pub fn is_valid_coloring(graph: &G, coloring: &[usize], num_colors: usize) -> bool { +pub(crate) fn is_valid_coloring(graph: &G, coloring: &[usize], num_colors: usize) -> bool { assert_eq!( coloring.len(), graph.num_vertices(), diff --git a/src/models/graph/max_cut.rs b/src/models/graph/max_cut.rs index e0a6d103d..f829e524a 100644 --- a/src/models/graph/max_cut.rs +++ b/src/models/graph/max_cut.rs @@ -195,7 +195,7 @@ where /// * `graph` - The graph structure /// * `edge_weights` - Weights for each edge (same order as `graph.edges()`) /// * `partition` - Boolean slice indicating which set each vertex belongs to -pub fn cut_size(graph: &G, edge_weights: &[W], partition: &[bool]) -> W::Sum +pub(crate) fn cut_size(graph: &G, edge_weights: &[W], partition: &[bool]) -> W::Sum where G: Graph, W: WeightElement, diff --git a/src/models/graph/maximal_is.rs b/src/models/graph/maximal_is.rs index 35720c2e0..193938cad 100644 --- a/src/models/graph/maximal_is.rs +++ b/src/models/graph/maximal_is.rs @@ -182,7 +182,8 @@ where /// /// # Panics /// Panics if `selected.len() != graph.num_vertices()`. -pub fn is_maximal_independent_set(graph: &G, selected: &[bool]) -> bool { +#[cfg(test)] +pub(crate) fn is_maximal_independent_set(graph: &G, selected: &[bool]) -> bool { assert_eq!( selected.len(), graph.num_vertices(), diff --git a/src/models/graph/maximum_clique.rs b/src/models/graph/maximum_clique.rs index 7fa30b26f..5161e9061 100644 --- a/src/models/graph/maximum_clique.rs +++ b/src/models/graph/maximum_clique.rs @@ -173,7 +173,8 @@ fn is_clique_config(graph: &G, config: &[usize]) -> bool { /// /// # Panics /// Panics if `selected.len() != graph.num_vertices()`. -pub fn is_clique(graph: &G, selected: &[bool]) -> bool { +#[cfg(test)] +pub(crate) fn is_clique(graph: &G, selected: &[bool]) -> bool { assert_eq!( selected.len(), graph.num_vertices(), diff --git a/src/models/graph/maximum_independent_set.rs b/src/models/graph/maximum_independent_set.rs index ef00de3d0..a919c5863 100644 --- a/src/models/graph/maximum_independent_set.rs +++ b/src/models/graph/maximum_independent_set.rs @@ -162,7 +162,8 @@ fn is_independent_set_config(graph: &G, config: &[usize]) -> bool { /// /// # Panics /// Panics if `selected.len() != graph.num_vertices()`. -pub fn is_independent_set(graph: &G, selected: &[bool]) -> bool { +#[cfg(test)] +pub(crate) fn is_independent_set(graph: &G, selected: &[bool]) -> bool { assert_eq!( selected.len(), graph.num_vertices(), diff --git a/src/models/graph/maximum_matching.rs b/src/models/graph/maximum_matching.rs index 28ad40299..0320e5b97 100644 --- a/src/models/graph/maximum_matching.rs +++ b/src/models/graph/maximum_matching.rs @@ -218,7 +218,8 @@ where /// /// # Panics /// Panics if `selected.len() != graph.num_edges()`. -pub fn is_matching(graph: &G, selected: &[bool]) -> bool { +#[cfg(test)] +pub(crate) fn is_matching(graph: &G, selected: &[bool]) -> bool { assert_eq!( selected.len(), graph.num_edges(), diff --git a/src/models/graph/minimum_dominating_set.rs b/src/models/graph/minimum_dominating_set.rs index 2bf738cbf..e7b1daec4 100644 --- a/src/models/graph/minimum_dominating_set.rs +++ b/src/models/graph/minimum_dominating_set.rs @@ -168,7 +168,8 @@ where /// /// # Panics /// Panics if `selected.len() != graph.num_vertices()`. -pub fn is_dominating_set(graph: &G, selected: &[bool]) -> bool { +#[cfg(test)] +pub(crate) fn is_dominating_set(graph: &G, selected: &[bool]) -> bool { assert_eq!( selected.len(), graph.num_vertices(), diff --git a/src/models/graph/minimum_vertex_cover.rs b/src/models/graph/minimum_vertex_cover.rs index e60372755..6fd5f0412 100644 --- a/src/models/graph/minimum_vertex_cover.rs +++ b/src/models/graph/minimum_vertex_cover.rs @@ -159,7 +159,8 @@ fn is_vertex_cover_config(graph: &G, config: &[usize]) -> bool { /// /// # Panics /// Panics if `selected.len() != graph.num_vertices()`. -pub fn is_vertex_cover(graph: &G, selected: &[bool]) -> bool { +#[cfg(test)] +pub(crate) fn is_vertex_cover(graph: &G, selected: &[bool]) -> bool { assert_eq!( selected.len(), graph.num_vertices(), diff --git a/src/models/graph/mod.rs b/src/models/graph/mod.rs index 7dd9d8052..a924af196 100644 --- a/src/models/graph/mod.rs +++ b/src/models/graph/mod.rs @@ -11,22 +11,22 @@ //! - [`MaximumMatching`]: Maximum weight matching //! - [`TravelingSalesman`]: Traveling Salesman (minimum weight Hamiltonian cycle) -mod kcoloring; -mod max_cut; -mod maximal_is; -mod maximum_clique; -mod maximum_independent_set; -mod maximum_matching; -mod minimum_dominating_set; -mod minimum_vertex_cover; -mod traveling_salesman; +pub(crate) mod kcoloring; +pub(crate) mod max_cut; +pub(crate) mod maximal_is; +pub(crate) mod maximum_clique; +pub(crate) mod maximum_independent_set; +pub(crate) mod maximum_matching; +pub(crate) mod minimum_dominating_set; +pub(crate) mod minimum_vertex_cover; +pub(crate) mod traveling_salesman; -pub use kcoloring::{is_valid_coloring, KColoring}; -pub use max_cut::{cut_size, MaxCut}; -pub use maximal_is::{is_maximal_independent_set, MaximalIS}; -pub use maximum_clique::{is_clique, MaximumClique}; -pub use maximum_independent_set::{is_independent_set, MaximumIndependentSet}; -pub use maximum_matching::{is_matching, MaximumMatching}; -pub use minimum_dominating_set::{is_dominating_set, MinimumDominatingSet}; -pub use minimum_vertex_cover::{is_vertex_cover, MinimumVertexCover}; -pub use traveling_salesman::{is_hamiltonian_cycle, TravelingSalesman}; +pub use kcoloring::KColoring; +pub use max_cut::MaxCut; +pub use maximal_is::MaximalIS; +pub use maximum_clique::MaximumClique; +pub use maximum_independent_set::MaximumIndependentSet; +pub use maximum_matching::MaximumMatching; +pub use minimum_dominating_set::MinimumDominatingSet; +pub use minimum_vertex_cover::MinimumVertexCover; +pub use traveling_salesman::TravelingSalesman; diff --git a/src/models/graph/traveling_salesman.rs b/src/models/graph/traveling_salesman.rs index 38feec44b..e99d12054 100644 --- a/src/models/graph/traveling_salesman.rs +++ b/src/models/graph/traveling_salesman.rs @@ -181,7 +181,7 @@ where /// /// # Panics /// Panics if `selected.len() != graph.num_edges()`. -pub fn is_hamiltonian_cycle(graph: &G, selected: &[bool]) -> bool { +pub(crate) fn is_hamiltonian_cycle(graph: &G, selected: &[bool]) -> bool { assert_eq!( selected.len(), graph.num_edges(), diff --git a/src/models/satisfiability/mod.rs b/src/models/satisfiability/mod.rs index 7e9796d5a..9766fd1ca 100644 --- a/src/models/satisfiability/mod.rs +++ b/src/models/satisfiability/mod.rs @@ -9,6 +9,3 @@ mod sat; pub use ksat::KSatisfiability; pub use sat::{CNFClause, Satisfiability}; - -// Validation utilities -pub use sat::is_satisfying_assignment; diff --git a/src/models/satisfiability/sat.rs b/src/models/satisfiability/sat.rs index a5ec9fec7..532257588 100644 --- a/src/models/satisfiability/sat.rs +++ b/src/models/satisfiability/sat.rs @@ -162,6 +162,13 @@ impl Satisfiability { self.clauses.iter().all(|c| c.is_satisfied(assignment)) } + /// Check if a solution (config) is valid. + /// + /// For SAT, a valid solution is one that satisfies all clauses. + pub fn is_valid_solution(&self, config: &[usize]) -> bool { + self.evaluate(config) + } + /// Convert a usize config to boolean assignment. fn config_to_assignment(config: &[usize]) -> Vec { config.iter().map(|&v| v == 1).collect() @@ -201,7 +208,8 @@ impl SatisfactionProblem for Satisfiability {} /// * `num_vars` - Number of variables /// * `clauses` - Clauses as vectors of literals (1-indexed, signed) /// * `assignment` - Boolean assignment (0-indexed) -pub fn is_satisfying_assignment( +#[cfg(test)] +pub(crate) fn is_satisfying_assignment( _num_vars: usize, clauses: &[Vec], assignment: &[bool], diff --git a/src/models/set/maximum_set_packing.rs b/src/models/set/maximum_set_packing.rs index 9bfc8c5e9..c4fb0e1bf 100644 --- a/src/models/set/maximum_set_packing.rs +++ b/src/models/set/maximum_set_packing.rs @@ -199,7 +199,8 @@ fn is_valid_packing(sets: &[Vec], config: &[usize]) -> bool { } /// Check if a selection of sets forms a valid set packing. -pub fn is_set_packing(sets: &[Vec], selected: &[bool]) -> bool { +#[cfg(test)] +pub(crate) fn is_set_packing(sets: &[Vec], selected: &[bool]) -> bool { if selected.len() != sets.len() { return false; } diff --git a/src/models/set/minimum_set_covering.rs b/src/models/set/minimum_set_covering.rs index 889abb915..ed10981dd 100644 --- a/src/models/set/minimum_set_covering.rs +++ b/src/models/set/minimum_set_covering.rs @@ -187,7 +187,8 @@ where } /// Check if a selection of sets forms a valid set cover. -pub fn is_set_cover(universe_size: usize, sets: &[Vec], selected: &[bool]) -> bool { +#[cfg(test)] +pub(crate) fn is_set_cover(universe_size: usize, sets: &[Vec], selected: &[bool]) -> bool { if selected.len() != sets.len() { return false; } diff --git a/src/models/set/mod.rs b/src/models/set/mod.rs index 7120524e8..0bee7cef5 100644 --- a/src/models/set/mod.rs +++ b/src/models/set/mod.rs @@ -4,12 +4,8 @@ //! - [`MinimumSetCovering`]: Minimum weight set cover //! - [`MaximumSetPacking`]: Maximum weight set packing -mod maximum_set_packing; -mod minimum_set_covering; +pub(crate) mod maximum_set_packing; +pub(crate) mod minimum_set_covering; pub use maximum_set_packing::MaximumSetPacking; pub use minimum_set_covering::MinimumSetCovering; - -// Validation utilities -pub use maximum_set_packing::is_set_packing; -pub use minimum_set_covering::is_set_cover; diff --git a/src/models/specialized/biclique_cover.rs b/src/models/specialized/biclique_cover.rs index 6d5075619..31164a7bc 100644 --- a/src/models/specialized/biclique_cover.rs +++ b/src/models/specialized/biclique_cover.rs @@ -195,7 +195,8 @@ impl BicliqueCover { } /// Check if a biclique configuration covers all edges. -pub fn is_biclique_cover( +#[cfg(test)] +pub(crate) fn is_biclique_cover( edges: &[(usize, usize)], left_bicliques: &[HashSet], right_bicliques: &[HashSet], diff --git a/src/models/specialized/bmf.rs b/src/models/specialized/bmf.rs index b0883d8fa..69003086d 100644 --- a/src/models/specialized/bmf.rs +++ b/src/models/specialized/bmf.rs @@ -172,12 +172,14 @@ impl BMF { } /// Compute the boolean matrix product. -pub fn boolean_matrix_product(b: &[Vec], c: &[Vec]) -> Vec> { +#[cfg(test)] +pub(crate) fn boolean_matrix_product(b: &[Vec], c: &[Vec]) -> Vec> { BMF::boolean_product(b, c) } /// Compute the Hamming distance between two boolean matrices. -pub fn matrix_hamming_distance(a: &[Vec], b: &[Vec]) -> usize { +#[cfg(test)] +pub(crate) fn matrix_hamming_distance(a: &[Vec], b: &[Vec]) -> usize { a.iter() .zip(b.iter()) .map(|(a_row, b_row)| { diff --git a/src/models/specialized/circuit.rs b/src/models/specialized/circuit.rs index 2cb7c6b41..d1dbeab6d 100644 --- a/src/models/specialized/circuit.rs +++ b/src/models/specialized/circuit.rs @@ -264,7 +264,8 @@ impl CircuitSAT { } /// Check if a circuit assignment is satisfying. -pub fn is_circuit_satisfying(circuit: &Circuit, assignments: &HashMap) -> bool { +#[cfg(test)] +pub(crate) fn is_circuit_satisfying(circuit: &Circuit, assignments: &HashMap) -> bool { circuit .assignments .iter() diff --git a/src/models/specialized/factoring.rs b/src/models/specialized/factoring.rs index 877a98e7f..d08b0e7d0 100644 --- a/src/models/specialized/factoring.rs +++ b/src/models/specialized/factoring.rs @@ -114,7 +114,8 @@ fn int_to_bits(n: u64, num_bits: usize) -> Vec { } /// Check if the given factors correctly factorize the target. -pub fn is_factoring(target: u64, a: u64, b: u64) -> bool { +#[cfg(test)] +pub(crate) fn is_factoring(target: u64, a: u64, b: u64) -> bool { a * b == target } diff --git a/src/models/specialized/mod.rs b/src/models/specialized/mod.rs index 521ceb8a1..63b70c681 100644 --- a/src/models/specialized/mod.rs +++ b/src/models/specialized/mod.rs @@ -7,14 +7,14 @@ //! - [`BicliqueCover`]: Biclique cover on bipartite graphs //! - [`BMF`]: Boolean matrix factorization -mod biclique_cover; -mod bmf; -mod circuit; -mod factoring; -mod paintshop; +pub(crate) mod biclique_cover; +pub(crate) mod bmf; +pub(crate) mod circuit; +pub(crate) mod factoring; +pub(crate) mod paintshop; -pub use biclique_cover::{is_biclique_cover, BicliqueCover}; -pub use bmf::{boolean_matrix_product, matrix_hamming_distance, BMF}; -pub use circuit::{is_circuit_satisfying, Assignment, BooleanExpr, BooleanOp, Circuit, CircuitSAT}; -pub use factoring::{is_factoring, Factoring}; -pub use paintshop::{count_paint_switches, PaintShop}; +pub use biclique_cover::BicliqueCover; +pub use bmf::BMF; +pub use circuit::{Assignment, BooleanExpr, BooleanOp, Circuit, CircuitSAT}; +pub use factoring::Factoring; +pub use paintshop::PaintShop; diff --git a/src/models/specialized/paintshop.rs b/src/models/specialized/paintshop.rs index 7bb45651b..4ee9386f2 100644 --- a/src/models/specialized/paintshop.rs +++ b/src/models/specialized/paintshop.rs @@ -156,7 +156,8 @@ impl PaintShop { } /// Count color switches in a painted sequence. -pub fn count_paint_switches(coloring: &[usize]) -> usize { +#[cfg(test)] +pub(crate) fn count_paint_switches(coloring: &[usize]) -> usize { coloring.windows(2).filter(|w| w[0] != w[1]).count() } diff --git a/src/unit_tests/graph_models.rs b/src/unit_tests/graph_models.rs index b8c9b0c96..8cb06ae3a 100644 --- a/src/unit_tests/graph_models.rs +++ b/src/unit_tests/graph_models.rs @@ -3,10 +3,10 @@ //! Tests extracted from source files for better compilation times //! and clearer separation of concerns. -use crate::models::graph::{ - is_independent_set, is_valid_coloring, is_vertex_cover, KColoring, MaximumIndependentSet, - MinimumVertexCover, -}; +use crate::models::graph::kcoloring::is_valid_coloring; +use crate::models::graph::maximum_independent_set::is_independent_set; +use crate::models::graph::minimum_vertex_cover::is_vertex_cover; +use crate::models::graph::{KColoring, MaximumIndependentSet, MinimumVertexCover}; use crate::prelude::*; use crate::topology::{Graph, SimpleGraph}; use crate::traits::{OptimizationProblem, Problem}; From 9c19c87b1fdfd6e60e31cb97ff71c6c9cb4f7c33 Mon Sep 17 00:00:00 2001 From: GiggleLiu Date: Tue, 17 Feb 2026 01:11:25 +0800 Subject: [PATCH 07/10] refactor: internalize config_to_bits and bits_to_config Change these utility functions from pub to pub(crate) with #[cfg(test)] since they are only used in unit tests. This reduces the public API surface of the config module. Co-Authored-By: Claude Opus 4.6 --- src/config.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/config.rs b/src/config.rs index a5e89c9d4..31c09ac82 100644 --- a/src/config.rs +++ b/src/config.rs @@ -109,12 +109,14 @@ pub fn config_to_index(config: &[usize], num_flavors: usize) -> usize { } /// Convert a binary configuration to a bitvec-style representation. -pub fn config_to_bits(config: &[usize]) -> Vec { +#[cfg(test)] +pub(crate) fn config_to_bits(config: &[usize]) -> Vec { config.iter().map(|&v| v != 0).collect() } /// Convert a bitvec-style representation to a binary configuration. -pub fn bits_to_config(bits: &[bool]) -> Vec { +#[cfg(test)] +pub(crate) fn bits_to_config(bits: &[bool]) -> Vec { bits.iter().map(|&b| if b { 1 } else { 0 }).collect() } From b1e890da795746dbbe928b5858a5b7cba89423fd Mon Sep 17 00:00:00 2001 From: GiggleLiu Date: Tue, 17 Feb 2026 01:11:36 +0800 Subject: [PATCH 08/10] refactor: slim down prelude to essential items Remove from prelude: config utilities, registry types, variant types, ILP/optimization internals, NumericSize, and WeightElement. These items remain accessible via their full module paths. Add explicit imports to 16 examples, 2 integration test files that previously relied on the broader prelude: - ILP type: 13 examples + 1 test file - K3/K2 variant types: 7 examples + 2 test files - LinearConstraint/ObjectiveSense: 1 example + 1 test file Co-Authored-By: Claude Opus 4.6 --- examples/chained_reduction_ksat_to_mis.rs | 1 + examples/reduction_factoring_to_ilp.rs | 1 + examples/reduction_ilp_to_qubo.rs | 1 + examples/reduction_kcoloring_to_ilp.rs | 2 ++ examples/reduction_kcoloring_to_qubo.rs | 1 + examples/reduction_ksatisfiability_to_qubo.rs | 1 + examples/reduction_maximumclique_to_ilp.rs | 1 + .../reduction_maximumindependentset_to_ilp.rs | 1 + examples/reduction_maximummatching_to_ilp.rs | 1 + .../reduction_maximumsetpacking_to_ilp.rs | 1 + .../reduction_minimumdominatingset_to_ilp.rs | 1 + .../reduction_minimumsetcovering_to_ilp.rs | 1 + .../reduction_minimumvertexcover_to_ilp.rs | 1 + .../reduction_satisfiability_to_kcoloring.rs | 1 + ...ction_satisfiability_to_ksatisfiability.rs | 1 + .../reduction_travelingsalesman_to_ilp.rs | 1 + src/lib.rs | 22 ++++++++----------- tests/suites/integration.rs | 1 + tests/suites/reductions.rs | 2 ++ 19 files changed, 29 insertions(+), 13 deletions(-) diff --git a/examples/chained_reduction_ksat_to_mis.rs b/examples/chained_reduction_ksat_to_mis.rs index 07f8d72d9..9f6623583 100644 --- a/examples/chained_reduction_ksat_to_mis.rs +++ b/examples/chained_reduction_ksat_to_mis.rs @@ -10,6 +10,7 @@ use problemreductions::rules::{MinimizeSteps, ReductionGraph}; use problemreductions::solvers::ILPSolver; use problemreductions::topology::SimpleGraph; use problemreductions::types::ProblemSize; +use problemreductions::variant::K3; // ANCHOR_END: imports pub fn run() { diff --git a/examples/reduction_factoring_to_ilp.rs b/examples/reduction_factoring_to_ilp.rs index 960a02654..a9bf1fc0d 100644 --- a/examples/reduction_factoring_to_ilp.rs +++ b/examples/reduction_factoring_to_ilp.rs @@ -18,6 +18,7 @@ // Exports `docs/paper/examples/factoring_to_ilp.json` for use in paper code blocks. use problemreductions::export::*; +use problemreductions::models::optimization::ILP; use problemreductions::prelude::*; use problemreductions::solvers::ILPSolver; diff --git a/examples/reduction_ilp_to_qubo.rs b/examples/reduction_ilp_to_qubo.rs index 2c1018cfb..22588b3a8 100644 --- a/examples/reduction_ilp_to_qubo.rs +++ b/examples/reduction_ilp_to_qubo.rs @@ -36,6 +36,7 @@ // ``` use problemreductions::export::*; +use problemreductions::models::optimization::{ILP, LinearConstraint, ObjectiveSense}; use problemreductions::prelude::*; pub fn run() { diff --git a/examples/reduction_kcoloring_to_ilp.rs b/examples/reduction_kcoloring_to_ilp.rs index 7a243abbf..b52fac603 100644 --- a/examples/reduction_kcoloring_to_ilp.rs +++ b/examples/reduction_kcoloring_to_ilp.rs @@ -16,10 +16,12 @@ // Exports `docs/paper/examples/kcoloring_to_ilp.json` and `kcoloring_to_ilp.result.json`. use problemreductions::export::*; +use problemreductions::models::optimization::ILP; use problemreductions::prelude::*; use problemreductions::solvers::ILPSolver; use problemreductions::topology::small_graphs::petersen; use problemreductions::topology::{Graph, SimpleGraph}; +use problemreductions::variant::K3; pub fn run() { // 1. Create KColoring instance: Petersen graph (10 vertices, 15 edges) with 3 colors, χ=3 diff --git a/examples/reduction_kcoloring_to_qubo.rs b/examples/reduction_kcoloring_to_qubo.rs index 6bdfc1dbd..42a6f03c7 100644 --- a/examples/reduction_kcoloring_to_qubo.rs +++ b/examples/reduction_kcoloring_to_qubo.rs @@ -32,6 +32,7 @@ use problemreductions::export::*; use problemreductions::prelude::*; use problemreductions::topology::small_graphs::house; use problemreductions::topology::{Graph, SimpleGraph}; +use problemreductions::variant::K3; pub fn run() { println!("=== K-Coloring -> QUBO Reduction ===\n"); diff --git a/examples/reduction_ksatisfiability_to_qubo.rs b/examples/reduction_ksatisfiability_to_qubo.rs index d62686be9..0886ef6e8 100644 --- a/examples/reduction_ksatisfiability_to_qubo.rs +++ b/examples/reduction_ksatisfiability_to_qubo.rs @@ -38,6 +38,7 @@ use problemreductions::export::*; use problemreductions::prelude::*; +use problemreductions::variant::K3; pub fn run() { println!("=== K-Satisfiability (3-SAT) -> QUBO Reduction ===\n"); diff --git a/examples/reduction_maximumclique_to_ilp.rs b/examples/reduction_maximumclique_to_ilp.rs index 88d85d0a7..978eddc20 100644 --- a/examples/reduction_maximumclique_to_ilp.rs +++ b/examples/reduction_maximumclique_to_ilp.rs @@ -15,6 +15,7 @@ // Exports `docs/paper/examples/maximumclique_to_ilp.json` and `maximumclique_to_ilp.result.json`. use problemreductions::export::*; +use problemreductions::models::optimization::ILP; use problemreductions::prelude::*; use problemreductions::topology::small_graphs::octahedral; use problemreductions::topology::{Graph, SimpleGraph}; diff --git a/examples/reduction_maximumindependentset_to_ilp.rs b/examples/reduction_maximumindependentset_to_ilp.rs index 1407eb0a5..babac97ac 100644 --- a/examples/reduction_maximumindependentset_to_ilp.rs +++ b/examples/reduction_maximumindependentset_to_ilp.rs @@ -14,6 +14,7 @@ // Exports `docs/paper/examples/maximumindependentset_to_ilp.json` and `maximumindependentset_to_ilp.result.json`. use problemreductions::export::*; +use problemreductions::models::optimization::ILP; use problemreductions::prelude::*; use problemreductions::topology::small_graphs::petersen; use problemreductions::topology::{Graph, SimpleGraph}; diff --git a/examples/reduction_maximummatching_to_ilp.rs b/examples/reduction_maximummatching_to_ilp.rs index c9cdd6ad6..2c6ca3482 100644 --- a/examples/reduction_maximummatching_to_ilp.rs +++ b/examples/reduction_maximummatching_to_ilp.rs @@ -14,6 +14,7 @@ // Exports `docs/paper/examples/maximummatching_to_ilp.json` and `maximummatching_to_ilp.result.json`. use problemreductions::export::*; +use problemreductions::models::optimization::ILP; use problemreductions::prelude::*; use problemreductions::topology::small_graphs::petersen; use problemreductions::topology::{Graph, SimpleGraph}; diff --git a/examples/reduction_maximumsetpacking_to_ilp.rs b/examples/reduction_maximumsetpacking_to_ilp.rs index 1de5320af..5e6ce4b7b 100644 --- a/examples/reduction_maximumsetpacking_to_ilp.rs +++ b/examples/reduction_maximumsetpacking_to_ilp.rs @@ -15,6 +15,7 @@ // Exports `docs/paper/examples/maximumsetpacking_to_ilp.json` and `maximumsetpacking_to_ilp.result.json`. use problemreductions::export::*; +use problemreductions::models::optimization::ILP; use problemreductions::prelude::*; pub fn run() { diff --git a/examples/reduction_minimumdominatingset_to_ilp.rs b/examples/reduction_minimumdominatingset_to_ilp.rs index b2b2cae41..57aa18e64 100644 --- a/examples/reduction_minimumdominatingset_to_ilp.rs +++ b/examples/reduction_minimumdominatingset_to_ilp.rs @@ -14,6 +14,7 @@ // Exports `docs/paper/examples/minimumdominatingset_to_ilp.json` and `minimumdominatingset_to_ilp.result.json`. use problemreductions::export::*; +use problemreductions::models::optimization::ILP; use problemreductions::prelude::*; use problemreductions::topology::small_graphs::petersen; use problemreductions::topology::{Graph, SimpleGraph}; diff --git a/examples/reduction_minimumsetcovering_to_ilp.rs b/examples/reduction_minimumsetcovering_to_ilp.rs index e921dd719..f7b28fa2c 100644 --- a/examples/reduction_minimumsetcovering_to_ilp.rs +++ b/examples/reduction_minimumsetcovering_to_ilp.rs @@ -15,6 +15,7 @@ // Exports `docs/paper/examples/minimumsetcovering_to_ilp.json` and `minimumsetcovering_to_ilp.result.json`. use problemreductions::export::*; +use problemreductions::models::optimization::ILP; use problemreductions::prelude::*; pub fn run() { diff --git a/examples/reduction_minimumvertexcover_to_ilp.rs b/examples/reduction_minimumvertexcover_to_ilp.rs index 923a08dca..af6a212e1 100644 --- a/examples/reduction_minimumvertexcover_to_ilp.rs +++ b/examples/reduction_minimumvertexcover_to_ilp.rs @@ -14,6 +14,7 @@ // Exports `docs/paper/examples/minimumvertexcover_to_ilp.json` and `minimumvertexcover_to_ilp.result.json`. use problemreductions::export::*; +use problemreductions::models::optimization::ILP; use problemreductions::prelude::*; use problemreductions::topology::small_graphs::petersen; use problemreductions::topology::{Graph, SimpleGraph}; diff --git a/examples/reduction_satisfiability_to_kcoloring.rs b/examples/reduction_satisfiability_to_kcoloring.rs index 13d1daebb..27ecc4dd5 100644 --- a/examples/reduction_satisfiability_to_kcoloring.rs +++ b/examples/reduction_satisfiability_to_kcoloring.rs @@ -18,6 +18,7 @@ use problemreductions::export::*; use problemreductions::prelude::*; use problemreductions::topology::{Graph, SimpleGraph}; +use problemreductions::variant::K3; pub fn run() { // 1. Create SAT instance: 5-variable, 3-clause formula with unit clauses diff --git a/examples/reduction_satisfiability_to_ksatisfiability.rs b/examples/reduction_satisfiability_to_ksatisfiability.rs index 817ce307b..bc2d8d975 100644 --- a/examples/reduction_satisfiability_to_ksatisfiability.rs +++ b/examples/reduction_satisfiability_to_ksatisfiability.rs @@ -20,6 +20,7 @@ use problemreductions::export::*; use problemreductions::prelude::*; +use problemreductions::variant::K3; pub fn run() { // 1. Create SAT instance with varied clause sizes to demonstrate padding and splitting: diff --git a/examples/reduction_travelingsalesman_to_ilp.rs b/examples/reduction_travelingsalesman_to_ilp.rs index 681cb07d4..28b629ba7 100644 --- a/examples/reduction_travelingsalesman_to_ilp.rs +++ b/examples/reduction_travelingsalesman_to_ilp.rs @@ -15,6 +15,7 @@ // Exports `docs/paper/examples/travelingsalesman_to_ilp.json` and `travelingsalesman_to_ilp.result.json`. use problemreductions::export::*; +use problemreductions::models::optimization::ILP; use problemreductions::prelude::*; use problemreductions::solvers::ILPSolver; use problemreductions::topology::{Graph, SimpleGraph}; diff --git a/src/lib.rs b/src/lib.rs index bb6c0f953..ec8097f40 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -80,28 +80,24 @@ pub mod variant; /// Prelude module for convenient imports. pub mod prelude { - pub use crate::config::{ - bits_to_config, config_to_bits, config_to_index, index_to_config, ConfigIterator, - }; - pub use crate::error::{ProblemError, Result}; + // Problem types pub use crate::models::graph::{ KColoring, MaxCut, MaximalIS, MaximumClique, MaximumIndependentSet, MaximumMatching, MinimumDominatingSet, MinimumVertexCover, TravelingSalesman, }; - pub use crate::models::optimization::{ - Comparison, LinearConstraint, ObjectiveSense, SpinGlass, VarBounds, ILP, QUBO, - }; + pub use crate::models::optimization::{SpinGlass, QUBO}; pub use crate::models::satisfiability::{CNFClause, KSatisfiability, Satisfiability}; pub use crate::models::set::{MaximumSetPacking, MinimumSetCovering}; - pub use crate::models::specialized::{BicliqueCover, CircuitSAT, Factoring, PaintShop, BMF}; - pub use crate::registry::{ComplexityClass, ProblemInfo, ProblemMetadata}; + pub use crate::models::specialized::{BicliqueCover, BMF, CircuitSAT, Factoring, PaintShop}; + + // Core traits pub use crate::rules::{ReduceTo, ReductionResult}; pub use crate::solvers::{BruteForce, Solver}; pub use crate::traits::{problem_size, OptimizationProblem, Problem, SatisfactionProblem}; - pub use crate::types::{ - Direction, NumericSize, One, ProblemSize, SolutionSize, Unweighted, WeightElement, - }; - pub use crate::variant::{CastToParent, KValue, VariantParam, K1, K2, K3, K4, K5, KN}; + + // Types + pub use crate::error::{ProblemError, Result}; + pub use crate::types::{Direction, One, ProblemSize, SolutionSize, Unweighted}; } // Re-export commonly used items at crate root diff --git a/tests/suites/integration.rs b/tests/suites/integration.rs index 06b1a31bf..5de6cd295 100644 --- a/tests/suites/integration.rs +++ b/tests/suites/integration.rs @@ -10,6 +10,7 @@ use problemreductions::models::set::*; use problemreductions::models::specialized::*; use problemreductions::prelude::*; use problemreductions::topology::{BipartiteGraph, SimpleGraph}; +use problemreductions::variant::K3; /// Test that all problem types can be instantiated and solved. mod all_problems_solvable { diff --git a/tests/suites/reductions.rs b/tests/suites/reductions.rs index aca43ae8e..1dd7e76c7 100644 --- a/tests/suites/reductions.rs +++ b/tests/suites/reductions.rs @@ -3,8 +3,10 @@ //! These tests verify that reduction chains work correctly and //! solutions can be properly extracted through the reduction pipeline. +use problemreductions::models::optimization::{ILP, LinearConstraint, ObjectiveSense}; use problemreductions::prelude::*; use problemreductions::topology::{Graph, SimpleGraph}; +use problemreductions::variant::{K2, K3}; /// Tests for MaximumIndependentSet <-> MinimumVertexCover reductions. mod is_vc_reductions { From 11d814716b69ebbfd6379e2bfe6a66b91c42e5f8 Mon Sep 17 00:00:00 2001 From: GiggleLiu Date: Tue, 17 Feb 2026 01:40:16 +0800 Subject: [PATCH 09/10] fix: restore PlanarGraph and BipartiteGraph VariantParam tests These tests were dropped when graph_types.rs was deleted but had no equivalent coverage elsewhere. Added them to variant.rs where similar graph type VariantParam tests already exist. Co-Authored-By: Claude Opus 4.6 --- src/unit_tests/variant.rs | 16 +++++++++++++++- tests/suites/reductions.rs | 4 ++-- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/unit_tests/variant.rs b/src/unit_tests/variant.rs index 850bc15e0..90b582e28 100644 --- a/src/unit_tests/variant.rs +++ b/src/unit_tests/variant.rs @@ -243,7 +243,7 @@ fn test_kvalue_kn() { // --- Graph type VariantParam tests --- use crate::topology::HyperGraph; -use crate::topology::{Graph, SimpleGraph, UnitDiskGraph}; +use crate::topology::{BipartiteGraph, Graph, PlanarGraph, SimpleGraph, UnitDiskGraph}; #[test] fn test_simple_graph_variant_param() { @@ -266,6 +266,20 @@ fn test_hyper_graph_variant_param() { assert_eq!(HyperGraph::PARENT_VALUE, None); } +#[test] +fn test_planar_graph_variant_param() { + assert_eq!(PlanarGraph::CATEGORY, "graph"); + assert_eq!(PlanarGraph::VALUE, "PlanarGraph"); + assert_eq!(PlanarGraph::PARENT_VALUE, Some("SimpleGraph")); +} + +#[test] +fn test_bipartite_graph_variant_param() { + assert_eq!(BipartiteGraph::CATEGORY, "graph"); + assert_eq!(BipartiteGraph::VALUE, "BipartiteGraph"); + assert_eq!(BipartiteGraph::PARENT_VALUE, Some("SimpleGraph")); +} + #[test] fn test_simple_graph_cast_to_parent() { let sg = SimpleGraph::new(3, vec![(0, 1), (1, 2)]); diff --git a/tests/suites/reductions.rs b/tests/suites/reductions.rs index 1dd7e76c7..314c8defd 100644 --- a/tests/suites/reductions.rs +++ b/tests/suites/reductions.rs @@ -422,8 +422,8 @@ mod topology_tests { } } -// TruthTable integration tests moved to src/unit_tests/truth_table.rs -// (truth_table module is now pub(crate)) +// TruthTable integration tests removed (module is now pub(crate)); +// equivalent coverage exists in src/unit_tests/truth_table.rs /// Tests for QUBO reductions against ground truth JSON. mod qubo_reductions { From 4b87242476a7f2a6b6f9f0f15910bbdd36475920 Mon Sep 17 00:00:00 2001 From: GiggleLiu Date: Tue, 17 Feb 2026 01:49:02 +0800 Subject: [PATCH 10/10] test: add is_valid_solution and cut_size method tests Cover all 15 new public methods with both valid and invalid cases: - 14 is_valid_solution tests across all problem types - 1 cut_size method test for MaxCut Co-Authored-By: Claude Opus 4.6 --- src/unit_tests/models/graph/kcoloring.rs | 10 ++++++++++ src/unit_tests/models/graph/max_cut.rs | 9 +++++++++ src/unit_tests/models/graph/maximal_is.rs | 10 ++++++++++ src/unit_tests/models/graph/maximum_clique.rs | 15 +++++++++++++++ .../models/graph/maximum_independent_set.rs | 10 ++++++++++ src/unit_tests/models/graph/maximum_matching.rs | 13 +++++++++++++ .../models/graph/minimum_dominating_set.rs | 10 ++++++++++ .../models/graph/minimum_vertex_cover.rs | 10 ++++++++++ src/unit_tests/models/graph/traveling_salesman.rs | 13 +++++++++++++ src/unit_tests/models/satisfiability/sat.rs | 13 +++++++++++++ src/unit_tests/models/set/maximum_set_packing.rs | 10 ++++++++++ src/unit_tests/models/set/minimum_set_covering.rs | 10 ++++++++++ .../models/specialized/biclique_cover.rs | 13 +++++++++++++ src/unit_tests/models/specialized/circuit.rs | 15 +++++++++++++++ src/unit_tests/models/specialized/factoring.rs | 10 ++++++++++ 15 files changed, 171 insertions(+) diff --git a/src/unit_tests/models/graph/kcoloring.rs b/src/unit_tests/models/graph/kcoloring.rs index c710cdb32..7bfe2fc9a 100644 --- a/src/unit_tests/models/graph/kcoloring.rs +++ b/src/unit_tests/models/graph/kcoloring.rs @@ -178,3 +178,13 @@ fn test_jl_parity_evaluation() { assert_eq!(rust_sat, jl_best, "KColoring satisfying solutions mismatch"); } } + +#[test] +fn test_is_valid_solution() { + // Path graph: 0-1-2, 3-coloring + let problem = KColoring::::new(SimpleGraph::new(3, vec![(0, 1), (1, 2)])); + // Valid: neighbors have different colors + assert!(problem.is_valid_solution(&[0, 1, 0])); + // Invalid: adjacent vertices 0 and 1 have same color + assert!(!problem.is_valid_solution(&[0, 0, 1])); +} diff --git a/src/unit_tests/models/graph/max_cut.rs b/src/unit_tests/models/graph/max_cut.rs index 4e2509f0c..3d7059c62 100644 --- a/src/unit_tests/models/graph/max_cut.rs +++ b/src/unit_tests/models/graph/max_cut.rs @@ -128,3 +128,12 @@ fn test_jl_parity_evaluation() { assert_eq!(rust_best, jl_best, "MaxCut best solutions mismatch"); } } + +#[test] +fn test_cut_size_method() { + let problem = MaxCut::new(SimpleGraph::new(3, vec![(0, 1), (1, 2), (0, 2)]), vec![1, 2, 3]); + // Partition {0} vs {1, 2}: cuts edges (0,1)=1 and (0,2)=3 + assert_eq!(problem.cut_size(&[0, 1, 1]), 4); + // All same partition: no edges cut + assert_eq!(problem.cut_size(&[0, 0, 0]), 0); +} diff --git a/src/unit_tests/models/graph/maximal_is.rs b/src/unit_tests/models/graph/maximal_is.rs index 64ab76358..22069621a 100644 --- a/src/unit_tests/models/graph/maximal_is.rs +++ b/src/unit_tests/models/graph/maximal_is.rs @@ -163,3 +163,13 @@ fn test_jl_parity_evaluation() { assert_eq!(rust_best, jl_best, "MaximalIS best solutions mismatch"); } } + +#[test] +fn test_is_valid_solution() { + // Path graph: 0-1-2 + let problem = MaximalIS::new(SimpleGraph::new(3, vec![(0, 1), (1, 2)]), vec![1i32; 3]); + // Valid: {0, 2} is maximal (independent and no vertex can be added) + assert!(problem.is_valid_solution(&[1, 0, 1])); + // Invalid: {0} is independent but not maximal (vertex 2 can be added) + assert!(!problem.is_valid_solution(&[1, 0, 0])); +} diff --git a/src/unit_tests/models/graph/maximum_clique.rs b/src/unit_tests/models/graph/maximum_clique.rs index 32f65b806..9ef849209 100644 --- a/src/unit_tests/models/graph/maximum_clique.rs +++ b/src/unit_tests/models/graph/maximum_clique.rs @@ -269,3 +269,18 @@ fn test_clique_problem() { assert_eq!(p.evaluate(&[1, 0, 0]), SolutionSize::Valid(1)); assert_eq!(p.direction(), Direction::Maximize); } + +#[test] +fn test_is_valid_solution() { + // Triangle: 0-1-2 all connected + let problem = MaximumClique::new( + SimpleGraph::new(3, vec![(0, 1), (1, 2), (0, 2)]), + vec![1i32; 3], + ); + // Valid: all three form a clique + assert!(problem.is_valid_solution(&[1, 1, 1])); + // Now path graph: 0-1-2 (no 0-2 edge) + let problem2 = MaximumClique::new(SimpleGraph::new(3, vec![(0, 1), (1, 2)]), vec![1i32; 3]); + // Invalid: {0, 2} not adjacent + assert!(!problem2.is_valid_solution(&[1, 0, 1])); +} diff --git a/src/unit_tests/models/graph/maximum_independent_set.rs b/src/unit_tests/models/graph/maximum_independent_set.rs index b5cc3d049..842870f14 100644 --- a/src/unit_tests/models/graph/maximum_independent_set.rs +++ b/src/unit_tests/models/graph/maximum_independent_set.rs @@ -161,3 +161,13 @@ fn test_jl_parity_evaluation() { assert_eq!(rust_best, jl_best, "IS best solutions mismatch"); } } + +#[test] +fn test_is_valid_solution() { + // Path graph: 0-1-2 + let problem = MaximumIndependentSet::new(SimpleGraph::new(3, vec![(0, 1), (1, 2)]), vec![1i32; 3]); + // Valid: {0, 2} is independent + assert!(problem.is_valid_solution(&[1, 0, 1])); + // Invalid: {0, 1} are adjacent + assert!(!problem.is_valid_solution(&[1, 1, 0])); +} diff --git a/src/unit_tests/models/graph/maximum_matching.rs b/src/unit_tests/models/graph/maximum_matching.rs index a71a51215..745cc8050 100644 --- a/src/unit_tests/models/graph/maximum_matching.rs +++ b/src/unit_tests/models/graph/maximum_matching.rs @@ -153,3 +153,16 @@ fn test_jl_parity_evaluation() { assert_eq!(rust_best, jl_best, "Matching best solutions mismatch"); } } + +#[test] +fn test_is_valid_solution() { + // Triangle: edges (0,1), (1,2), (0,2) — config is per edge + let problem = MaximumMatching::new( + SimpleGraph::new(3, vec![(0, 1), (1, 2), (0, 2)]), + vec![1i32; 3], + ); + // Valid: select edge (0,1) only — no shared vertices + assert!(problem.is_valid_solution(&[1, 0, 0])); + // Invalid: select edges (0,1) and (1,2) — vertex 1 shared + assert!(!problem.is_valid_solution(&[1, 1, 0])); +} diff --git a/src/unit_tests/models/graph/minimum_dominating_set.rs b/src/unit_tests/models/graph/minimum_dominating_set.rs index c99f3a733..432f4a280 100644 --- a/src/unit_tests/models/graph/minimum_dominating_set.rs +++ b/src/unit_tests/models/graph/minimum_dominating_set.rs @@ -158,3 +158,13 @@ fn test_jl_parity_evaluation() { assert_eq!(rust_best, jl_best, "DS best solutions mismatch"); } } + +#[test] +fn test_is_valid_solution() { + // Path graph: 0-1-2 + let problem = MinimumDominatingSet::new(SimpleGraph::new(3, vec![(0, 1), (1, 2)]), vec![1i32; 3]); + // Valid: {1} dominates all vertices (0 and 2 are neighbors of 1) + assert!(problem.is_valid_solution(&[0, 1, 0])); + // Invalid: {0} doesn't dominate vertex 2 + assert!(!problem.is_valid_solution(&[1, 0, 0])); +} diff --git a/src/unit_tests/models/graph/minimum_vertex_cover.rs b/src/unit_tests/models/graph/minimum_vertex_cover.rs index 509b5e803..396105fb5 100644 --- a/src/unit_tests/models/graph/minimum_vertex_cover.rs +++ b/src/unit_tests/models/graph/minimum_vertex_cover.rs @@ -144,3 +144,13 @@ fn test_jl_parity_evaluation() { assert_eq!(rust_best, jl_best, "VC best solutions mismatch"); } } + +#[test] +fn test_is_valid_solution() { + // Path graph: 0-1-2 + let problem = MinimumVertexCover::new(SimpleGraph::new(3, vec![(0, 1), (1, 2)]), vec![1i32; 3]); + // Valid: {1} covers both edges + assert!(problem.is_valid_solution(&[0, 1, 0])); + // Invalid: {0} doesn't cover edge (1,2) + assert!(!problem.is_valid_solution(&[1, 0, 0])); +} diff --git a/src/unit_tests/models/graph/traveling_salesman.rs b/src/unit_tests/models/graph/traveling_salesman.rs index e1047e243..87d471227 100644 --- a/src/unit_tests/models/graph/traveling_salesman.rs +++ b/src/unit_tests/models/graph/traveling_salesman.rs @@ -222,3 +222,16 @@ fn test_brute_force_triangle_weighted() { assert_eq!(solutions[0], vec![1, 1, 1]); assert_eq!(problem.evaluate(&solutions[0]), SolutionSize::Valid(30)); } + +#[test] +fn test_is_valid_solution() { + // K3 triangle: edges (0,1), (0,2), (1,2) — config is per edge + let problem = TravelingSalesman::new( + SimpleGraph::new(3, vec![(0, 1), (0, 2), (1, 2)]), + vec![1, 2, 3], + ); + // Valid: select all 3 edges forms Hamiltonian cycle 0-1-2-0 + assert!(problem.is_valid_solution(&[1, 1, 1])); + // Invalid: select only 2 edges — not a cycle + assert!(!problem.is_valid_solution(&[1, 1, 0])); +} diff --git a/src/unit_tests/models/satisfiability/sat.rs b/src/unit_tests/models/satisfiability/sat.rs index 42f2e25f1..cd346f859 100644 --- a/src/unit_tests/models/satisfiability/sat.rs +++ b/src/unit_tests/models/satisfiability/sat.rs @@ -194,3 +194,16 @@ fn test_jl_parity_evaluation() { } } } + +#[test] +fn test_is_valid_solution() { + // (x1 OR x2) AND (NOT x1 OR x3) + let problem = Satisfiability::new( + 3, + vec![CNFClause::new(vec![1, 2]), CNFClause::new(vec![-1, 3])], + ); + // Valid: x1=F, x2=T, x3=T → (T) AND (T) = T + assert!(problem.is_valid_solution(&[0, 1, 1])); + // Invalid: x1=T, x2=F, x3=F → (T) AND (F) = F + assert!(!problem.is_valid_solution(&[1, 0, 0])); +} diff --git a/src/unit_tests/models/set/maximum_set_packing.rs b/src/unit_tests/models/set/maximum_set_packing.rs index 6f65c4d6b..8242c3a9f 100644 --- a/src/unit_tests/models/set/maximum_set_packing.rs +++ b/src/unit_tests/models/set/maximum_set_packing.rs @@ -136,3 +136,13 @@ fn test_jl_parity_evaluation() { assert_eq!(rust_best, jl_best, "SetPacking best solutions mismatch"); } } + +#[test] +fn test_is_valid_solution() { + // Sets: {0,1}, {1,2}, {3,4} + let problem = MaximumSetPacking::::new(vec![vec![0, 1], vec![1, 2], vec![3, 4]]); + // Valid: select sets 0 and 2 (disjoint: {0,1} and {3,4}) + assert!(problem.is_valid_solution(&[1, 0, 1])); + // Invalid: select sets 0 and 1 (share element 1) + assert!(!problem.is_valid_solution(&[1, 1, 0])); +} diff --git a/src/unit_tests/models/set/minimum_set_covering.rs b/src/unit_tests/models/set/minimum_set_covering.rs index fa9036e54..c04aac0b0 100644 --- a/src/unit_tests/models/set/minimum_set_covering.rs +++ b/src/unit_tests/models/set/minimum_set_covering.rs @@ -106,3 +106,13 @@ fn test_jl_parity_evaluation() { assert_eq!(rust_best, jl_best, "SetCovering best solutions mismatch"); } } + +#[test] +fn test_is_valid_solution() { + // Universe: {0,1,2,3}, Sets: {0,1}, {1,2}, {2,3} + let problem = MinimumSetCovering::::new(4, vec![vec![0, 1], vec![1, 2], vec![2, 3]]); + // Valid: all sets selected covers {0,1,2,3} + assert!(problem.is_valid_solution(&[1, 1, 1])); + // Invalid: only set 1 ({1,2}) doesn't cover 0 and 3 + assert!(!problem.is_valid_solution(&[0, 1, 0])); +} diff --git a/src/unit_tests/models/specialized/biclique_cover.rs b/src/unit_tests/models/specialized/biclique_cover.rs index 10cfbaacf..bb59df538 100644 --- a/src/unit_tests/models/specialized/biclique_cover.rs +++ b/src/unit_tests/models/specialized/biclique_cover.rs @@ -230,3 +230,16 @@ fn test_jl_parity_evaluation() { assert_eq!(rust_best, jl_best, "BicliqueCover best solutions mismatch"); } } + +#[test] +fn test_is_valid_solution() { + use crate::topology::BipartiteGraph; + // Single edge (0,0) with 1 biclique + let graph = BipartiteGraph::new(1, 1, vec![(0, 0)]); + let problem = BicliqueCover::new(graph, 1); + // 2 vertices (left_0, right_0), 1 biclique → config length = 2 + // Valid: both vertices in biclique 0 → covers edge (0,0) + assert!(problem.is_valid_solution(&[1, 1])); + // Invalid: only left vertex in biclique → doesn't form complete bipartite subgraph covering edge + assert!(!problem.is_valid_solution(&[1, 0])); +} diff --git a/src/unit_tests/models/specialized/circuit.rs b/src/unit_tests/models/specialized/circuit.rs index e786ef196..6e75834d6 100644 --- a/src/unit_tests/models/specialized/circuit.rs +++ b/src/unit_tests/models/specialized/circuit.rs @@ -235,3 +235,18 @@ fn test_circuit_sat_problem() { // c=1, x=1, y=0: c = 1 AND 0 = 0 != 1 => not satisfied assert!(!p.evaluate(&[1, 1, 0])); } + +#[test] +fn test_is_valid_solution() { + // c = x AND y + let circuit = Circuit::new(vec![Assignment::new( + vec!["c".to_string()], + BooleanExpr::and(vec![BooleanExpr::var("x"), BooleanExpr::var("y")]), + )]); + let problem = CircuitSAT::new(circuit); + // Variables sorted: c, x, y + // Valid: c=1, x=1, y=1 (c = 1 AND 1 = 1) + assert!(problem.is_valid_solution(&[1, 1, 1])); + // Invalid: c=1, x=1, y=0 (c = 1 AND 0 = 0, but c=1) + assert!(!problem.is_valid_solution(&[1, 1, 0])); +} diff --git a/src/unit_tests/models/specialized/factoring.rs b/src/unit_tests/models/specialized/factoring.rs index 1acd08928..ed6997037 100644 --- a/src/unit_tests/models/specialized/factoring.rs +++ b/src/unit_tests/models/specialized/factoring.rs @@ -96,3 +96,13 @@ fn test_jl_parity_evaluation() { assert_eq!(rust_best, jl_best, "Factoring best solutions mismatch"); } } + +#[test] +fn test_is_valid_solution() { + // Factor 15 = 3 × 5, 3 bits each + let problem = Factoring::new(3, 3, 15); + // Valid: 3 = [1,1,0], 5 = [1,0,1] → config = [1,1,0,1,0,1] + assert!(problem.is_valid_solution(&[1, 1, 0, 1, 0, 1])); + // Invalid: 2 = [0,1,0], 3 = [1,1,0] → 2*3=6 ≠ 15 + assert!(!problem.is_valid_solution(&[0, 1, 0, 1, 1, 0])); +}