From 0182bb192cc84530d59b1f28aed265b238a95a75 Mon Sep 17 00:00:00 2001
From: GiggleLiu <cacate0129@gmail.com>
Date: Mon, 16 Feb 2026 23:33:12 +0800
Subject: [PATCH 1/8] 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 <noreply@anthropic.com>
---
 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<W> {
     /// The SpinGlass problem encoding the gate.
     pub problem: SpinGlass<SimpleGraph, W>,
     /// Input spin indices (0-indexed within the gadget).
+    #[allow(dead_code)] // read in tests
     pub inputs: Vec<usize>,
     /// Output spin indices (0-indexed within the gadget).
+    #[allow(dead_code)] // read in tests
     pub outputs: Vec<usize>,
 }
 
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<K3, SimpleGraph>;
-
 #[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<NodeJson>,
+    pub(crate) nodes: Vec<NodeJson>,
     /// List of reduction edges.
-    pub edges: Vec<EdgeJson>,
+    pub(crate) edges: Vec<EdgeJson>,
 }
 
 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<String, String>,
+    pub(crate) variant: BTreeMap<String, String>,
     /// 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<OverheadFieldJson>,
+    pub(crate) overhead: Vec<OverheadFieldJson>,
     /// 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 <cacate0129@gmail.com>
Date: Tue, 17 Feb 2026 00:05:34 +0800
Subject: [PATCH 2/8] 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 <noreply@anthropic.com>
---
 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<GridNodeExport> {
-    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<P: Pattern>(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<P: Pattern>(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 <cacate0129@gmail.com>
Date: Tue, 17 Feb 2026 00:11:17 +0800
Subject: [PATCH 3/8] 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 <noreply@anthropic.com>
---
 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 <cacate0129@gmail.com>
Date: Tue, 17 Feb 2026 00:35:08 +0800
Subject: [PATCH 4/8] 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 <noreply@anthropic.com>
---
 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<K: KValue, G: Graph> KColoring<K, G> {
         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<G: Graph, W: Clone + Default> MaxCut<G, W> {
     pub fn edge_weights(&self) -> Vec<W> {
         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<bool> = config.iter().map(|&c| c != 0).collect();
+        cut_size(&self.graph, &self.edge_weights, &partition)
+    }
 }
 
 impl<G, W> Problem for MaxCut<G, W>
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<G: Graph, W: Clone + Default> MaximalIS<G, W> {
         !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<G: Graph, W: Clone + Default> MaximumClique<G, W> {
     {
         !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<G, W> Problem for MaximumClique<G, W>
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<G: Graph, W: Clone + Default> MaximumIndependentSet<G, W> {
     {
         !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<G, W> Problem for MaximumIndependentSet<G, W>
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<G: Graph, W: Clone + Default> MaximumMatching<G, W> {
     }
 
     /// 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<G: Graph, W: Clone + Default> MinimumDominatingSet<G, W> {
         &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<G: Graph, W: Clone + Default> MinimumVertexCover<G, W> {
     {
         !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<G, W> Problem for MinimumVertexCover<G, W>
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<G: Graph, W: Clone + Default> TravelingSalesman<G, W> {
         !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 <cacate0129@gmail.com>
Date: Tue, 17 Feb 2026 00:35:14 +0800
Subject: [PATCH 5/8] 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 <noreply@anthropic.com>
---
 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<W: Clone + Default> MaximumSetPacking<W> {
     pub fn weights_ref(&self) -> &Vec<W> {
         &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<W> Problem for MaximumSetPacking<W>
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<W: Clone + Default> MinimumSetCovering<W> {
         &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<usize> {
         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<String, bool> {
         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 <cacate0129@gmail.com>
Date: Tue, 17 Feb 2026 00:58:14 +0800
Subject: [PATCH 6/8] 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 <noreply@anthropic.com>
---
 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<K: KValue, G: Graph + VariantParam> SatisfactionProblem for KColoring<K, G>
 ///
 /// # Panics
 /// Panics if `coloring.len() != graph.num_vertices()`.
-pub fn is_valid_coloring<G: Graph>(graph: &G, coloring: &[usize], num_colors: usize) -> bool {
+pub(crate) fn is_valid_coloring<G: Graph>(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<G, W>(graph: &G, edge_weights: &[W], partition: &[bool]) -> W::Sum
+pub(crate) fn cut_size<G, W>(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<G: Graph>(graph: &G, selected: &[bool]) -> bool {
+#[cfg(test)]
+pub(crate) fn is_maximal_independent_set<G: Graph>(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<G: Graph>(graph: &G, config: &[usize]) -> bool {
 ///
 /// # Panics
 /// Panics if `selected.len() != graph.num_vertices()`.
-pub fn is_clique<G: Graph>(graph: &G, selected: &[bool]) -> bool {
+#[cfg(test)]
+pub(crate) fn is_clique<G: Graph>(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<G: Graph>(graph: &G, config: &[usize]) -> bool {
 ///
 /// # Panics
 /// Panics if `selected.len() != graph.num_vertices()`.
-pub fn is_independent_set<G: Graph>(graph: &G, selected: &[bool]) -> bool {
+#[cfg(test)]
+pub(crate) fn is_independent_set<G: Graph>(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<G: Graph>(graph: &G, selected: &[bool]) -> bool {
+#[cfg(test)]
+pub(crate) fn is_matching<G: Graph>(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<G: Graph>(graph: &G, selected: &[bool]) -> bool {
+#[cfg(test)]
+pub(crate) fn is_dominating_set<G: Graph>(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<G: Graph>(graph: &G, config: &[usize]) -> bool {
 ///
 /// # Panics
 /// Panics if `selected.len() != graph.num_vertices()`.
-pub fn is_vertex_cover<G: Graph>(graph: &G, selected: &[bool]) -> bool {
+#[cfg(test)]
+pub(crate) fn is_vertex_cover<G: Graph>(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<G: Graph>(graph: &G, selected: &[bool]) -> bool {
+pub(crate) fn is_hamiltonian_cycle<G: Graph>(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<bool> {
         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<i32>],
     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<usize>], config: &[usize]) -> bool {
 }
 
 /// Check if a selection of sets forms a valid set packing.
-pub fn is_set_packing(sets: &[Vec<usize>], selected: &[bool]) -> bool {
+#[cfg(test)]
+pub(crate) fn is_set_packing(sets: &[Vec<usize>], 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<usize>], selected: &[bool]) -> bool {
+#[cfg(test)]
+pub(crate) fn is_set_cover(universe_size: usize, sets: &[Vec<usize>], 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<usize>],
     right_bicliques: &[HashSet<usize>],
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<bool>], c: &[Vec<bool>]) -> Vec<Vec<bool>> {
+#[cfg(test)]
+pub(crate) fn boolean_matrix_product(b: &[Vec<bool>], c: &[Vec<bool>]) -> Vec<Vec<bool>> {
     BMF::boolean_product(b, c)
 }
 
 /// Compute the Hamming distance between two boolean matrices.
-pub fn matrix_hamming_distance(a: &[Vec<bool>], b: &[Vec<bool>]) -> usize {
+#[cfg(test)]
+pub(crate) fn matrix_hamming_distance(a: &[Vec<bool>], b: &[Vec<bool>]) -> 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<String, bool>) -> bool {
+#[cfg(test)]
+pub(crate) fn is_circuit_satisfying(circuit: &Circuit, assignments: &HashMap<String, bool>) -> 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<usize> {
 }
 
 /// 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 <cacate0129@gmail.com>
Date: Tue, 17 Feb 2026 01:11:25 +0800
Subject: [PATCH 7/8] 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 <noreply@anthropic.com>
---
 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<bool> {
+#[cfg(test)]
+pub(crate) fn config_to_bits(config: &[usize]) -> Vec<bool> {
     config.iter().map(|&v| v != 0).collect()
 }
 
 /// Convert a bitvec-style representation to a binary configuration.
-pub fn bits_to_config(bits: &[bool]) -> Vec<usize> {
+#[cfg(test)]
+pub(crate) fn bits_to_config(bits: &[bool]) -> Vec<usize> {
     bits.iter().map(|&b| if b { 1 } else { 0 }).collect()
 }
 

From b1e890da795746dbbe928b5858a5b7cba89423fd Mon Sep 17 00:00:00 2001
From: GiggleLiu <cacate0129@gmail.com>
Date: Tue, 17 Feb 2026 01:11:36 +0800
Subject: [PATCH 8/8] 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 <noreply@anthropic.com>
---
 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 {
