From 08a51104030cede29f3b6108e78faf5110a4bd3c Mon Sep 17 00:00:00 2001 From: GiggleLiu Date: Sat, 28 Feb 2026 10:08:58 +0800 Subject: [PATCH 1/3] =?UTF-8?q?feat:=20add=20One=20weight=20variant=20for?= =?UTF-8?q?=20IS=E2=86=94SP=20reductions=20and=20cleanup=20dead=20APIs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add One weight variant for MaximumIndependentSet ↔ MaximumSetPacking reductions using local macros to keep impls DRY - Register MaximumSetPacking variant and add One→i32 weight cast - Remove ConfigIterator (superseded by DimsIterator) - Remove unused testing module (macros never adopted) - Make LinearConstraint::new() pub(crate) (only le/ge/eq delegate to it) Co-Authored-By: Claude Opus 4.6 --- docs/src/reductions/reduction_graph.json | 136 +++++++--- src/config.rs | 87 +------ src/lib.rs | 1 - src/models/optimization/ilp.rs | 2 +- src/models/set/maximum_set_packing.rs | 3 +- ...maximumindependentset_maximumsetpacking.rs | 106 ++++---- src/rules/maximumsetpacking_casts.rs | 10 + src/testing/macros.rs | 234 ------------------ src/testing/mod.rs | 184 -------------- src/unit_tests/config.rs | 70 ------ ...maximumindependentset_maximumsetpacking.rs | 43 ++++ src/unit_tests/testing/macros.rs | 63 ----- src/unit_tests/testing/mod.rs | 37 --- 13 files changed, 208 insertions(+), 768 deletions(-) delete mode 100644 src/testing/macros.rs delete mode 100644 src/testing/mod.rs delete mode 100644 src/unit_tests/testing/macros.rs delete mode 100644 src/unit_tests/testing/mod.rs diff --git a/docs/src/reductions/reduction_graph.json b/docs/src/reductions/reduction_graph.json index e25f33c65..ecd559478 100644 --- a/docs/src/reductions/reduction_graph.json +++ b/docs/src/reductions/reduction_graph.json @@ -208,6 +208,15 @@ "doc_path": "models/graph/struct.MaximumMatching.html", "complexity": "2^num_vertices" }, + { + "name": "MaximumSetPacking", + "variant": { + "weight": "One" + }, + "category": "set", + "doc_path": "models/set/struct.MaximumSetPacking.html", + "complexity": "2^num_sets" + }, { "name": "MaximumSetPacking", "variant": { @@ -320,7 +329,7 @@ }, { "source": 0, - "target": 30, + "target": 31, "overhead": [ { "field": "num_spins", @@ -365,7 +374,7 @@ }, { "source": 2, - "target": 27, + "target": 28, "overhead": [ { "field": "num_vars", @@ -406,7 +415,7 @@ }, { "source": 7, - "target": 27, + "target": 28, "overhead": [ { "field": "num_vars", @@ -432,7 +441,7 @@ }, { "source": 8, - "target": 27, + "target": 28, "overhead": [ { "field": "num_vars", @@ -443,7 +452,7 @@ }, { "source": 8, - "target": 28, + "target": 29, "overhead": [ { "field": "num_clauses", @@ -477,7 +486,7 @@ }, { "source": 9, - "target": 27, + "target": 28, "overhead": [ { "field": "num_vars", @@ -488,7 +497,7 @@ }, { "source": 9, - "target": 28, + "target": 29, "overhead": [ { "field": "num_clauses", @@ -507,7 +516,7 @@ }, { "source": 10, - "target": 28, + "target": 29, "overhead": [ { "field": "num_clauses", @@ -526,7 +535,7 @@ }, { "source": 11, - "target": 30, + "target": 31, "overhead": [ { "field": "num_spins", @@ -659,6 +668,21 @@ ], "doc_path": "rules/maximumindependentset_triangular/index.html" }, + { + "source": 16, + "target": 22, + "overhead": [ + { + "field": "num_sets", + "formula": "num_vertices" + }, + { + "field": "universe_size", + "formula": "num_vertices" + } + ], + "doc_path": "rules/maximumindependentset_maximumsetpacking/index.html" + }, { "source": 17, "target": 2, @@ -676,7 +700,7 @@ }, { "source": 17, - "target": 23, + "target": 24, "overhead": [ { "field": "num_sets", @@ -691,7 +715,7 @@ }, { "source": 17, - "target": 26, + "target": 27, "overhead": [ { "field": "num_vertices", @@ -706,7 +730,7 @@ }, { "source": 17, - "target": 27, + "target": 28, "overhead": [ { "field": "num_vars", @@ -792,7 +816,7 @@ }, { "source": 21, - "target": 23, + "target": 24, "overhead": [ { "field": "num_sets", @@ -807,7 +831,37 @@ }, { "source": 22, - "target": 27, + "target": 16, + "overhead": [ + { + "field": "num_vertices", + "formula": "num_sets" + }, + { + "field": "num_edges", + "formula": "num_sets" + } + ], + "doc_path": "rules/maximumindependentset_maximumsetpacking/index.html" + }, + { + "source": 22, + "target": 24, + "overhead": [ + { + "field": "num_sets", + "formula": "num_sets" + }, + { + "field": "universe_size", + "formula": "universe_size" + } + ], + "doc_path": "rules/maximumsetpacking_casts/index.html" + }, + { + "source": 23, + "target": 28, "overhead": [ { "field": "num_vars", @@ -817,7 +871,7 @@ "doc_path": "rules/maximumsetpacking_qubo/index.html" }, { - "source": 23, + "source": 24, "target": 2, "overhead": [ { @@ -832,7 +886,7 @@ "doc_path": "rules/maximumsetpacking_ilp/index.html" }, { - "source": 23, + "source": 24, "target": 17, "overhead": [ { @@ -847,8 +901,8 @@ "doc_path": "rules/maximumindependentset_maximumsetpacking/index.html" }, { - "source": 23, - "target": 22, + "source": 24, + "target": 23, "overhead": [ { "field": "num_sets", @@ -862,7 +916,7 @@ "doc_path": "rules/maximumsetpacking_casts/index.html" }, { - "source": 24, + "source": 25, "target": 2, "overhead": [ { @@ -877,7 +931,7 @@ "doc_path": "rules/minimumdominatingset_ilp/index.html" }, { - "source": 25, + "source": 26, "target": 2, "overhead": [ { @@ -892,7 +946,7 @@ "doc_path": "rules/minimumsetcovering_ilp/index.html" }, { - "source": 26, + "source": 27, "target": 2, "overhead": [ { @@ -907,7 +961,7 @@ "doc_path": "rules/minimumvertexcover_ilp/index.html" }, { - "source": 26, + "source": 27, "target": 17, "overhead": [ { @@ -922,8 +976,8 @@ "doc_path": "rules/minimumvertexcover_maximumindependentset/index.html" }, { - "source": 26, - "target": 25, + "source": 27, + "target": 26, "overhead": [ { "field": "num_sets", @@ -937,8 +991,8 @@ "doc_path": "rules/minimumvertexcover_minimumsetcovering/index.html" }, { - "source": 26, - "target": 27, + "source": 27, + "target": 28, "overhead": [ { "field": "num_vars", @@ -948,7 +1002,7 @@ "doc_path": "rules/minimumvertexcover_qubo/index.html" }, { - "source": 27, + "source": 28, "target": 2, "overhead": [ { @@ -963,8 +1017,8 @@ "doc_path": "rules/qubo_ilp/index.html" }, { - "source": 27, - "target": 29, + "source": 28, + "target": 30, "overhead": [ { "field": "num_spins", @@ -974,7 +1028,7 @@ "doc_path": "rules/spinglass_qubo/index.html" }, { - "source": 28, + "source": 29, "target": 0, "overhead": [ { @@ -989,7 +1043,7 @@ "doc_path": "rules/sat_circuitsat/index.html" }, { - "source": 28, + "source": 29, "target": 4, "overhead": [ { @@ -1004,7 +1058,7 @@ "doc_path": "rules/sat_coloring/index.html" }, { - "source": 28, + "source": 29, "target": 9, "overhead": [ { @@ -1019,7 +1073,7 @@ "doc_path": "rules/sat_ksat/index.html" }, { - "source": 28, + "source": 29, "target": 16, "overhead": [ { @@ -1034,8 +1088,8 @@ "doc_path": "rules/sat_maximumindependentset/index.html" }, { - "source": 28, - "target": 24, + "source": 29, + "target": 25, "overhead": [ { "field": "num_vertices", @@ -1049,8 +1103,8 @@ "doc_path": "rules/sat_minimumdominatingset/index.html" }, { - "source": 29, - "target": 27, + "source": 30, + "target": 28, "overhead": [ { "field": "num_vars", @@ -1060,7 +1114,7 @@ "doc_path": "rules/spinglass_qubo/index.html" }, { - "source": 30, + "source": 31, "target": 11, "overhead": [ { @@ -1075,8 +1129,8 @@ "doc_path": "rules/spinglass_maxcut/index.html" }, { - "source": 30, - "target": 29, + "source": 31, + "target": 30, "overhead": [ { "field": "num_spins", @@ -1090,7 +1144,7 @@ "doc_path": "rules/spinglass_casts/index.html" }, { - "source": 31, + "source": 32, "target": 2, "overhead": [ { diff --git a/src/config.rs b/src/config.rs index 31c09ac82..6a9e687ed 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,89 +1,5 @@ //! Configuration utilities for problem solving. -/// Iterator over all possible configurations for a given number of variables and flavors. -/// -/// Generates configurations in lexicographic order, from [0, 0, ..., 0] to -/// [num_flavors-1, num_flavors-1, ..., num_flavors-1]. -pub struct ConfigIterator { - num_variables: usize, - num_flavors: usize, - current: Option>, - total_configs: usize, - current_index: usize, -} - -impl ConfigIterator { - /// Create a new configuration iterator. - /// - /// For 0 variables, produces exactly one configuration (the empty config). - /// For 0 flavors with non-zero variables, produces no configurations. - pub fn new(num_variables: usize, num_flavors: usize) -> Self { - let total_configs = if num_variables == 0 { - // 0 variables means exactly 1 configuration: the empty config - 1 - } else if num_flavors == 0 { - // Non-zero variables with 0 flavors means no valid configs - 0 - } else { - num_flavors.pow(num_variables as u32) - }; - let current = if total_configs == 0 { - None - } else { - Some(vec![0; num_variables]) - }; - Self { - num_variables, - num_flavors, - current, - total_configs, - current_index: 0, - } - } - - /// Returns the total number of configurations. - pub fn total(&self) -> usize { - self.total_configs - } -} - -impl Iterator for ConfigIterator { - type Item = Vec; - - fn next(&mut self) -> Option { - let current = self.current.take()?; - let result = current.clone(); - - // Advance to next configuration - let mut next = current; - let mut carry = true; - for i in (0..self.num_variables).rev() { - if carry { - next[i] += 1; - if next[i] >= self.num_flavors { - next[i] = 0; - } else { - carry = false; - } - } - } - - self.current_index += 1; - if self.current_index < self.total_configs { - self.current = Some(next); - } - - Some(result) - } - - fn size_hint(&self) -> (usize, Option) { - let remaining = self.total_configs - self.current_index; - (remaining, Some(remaining)) - } -} - -impl ExactSizeIterator for ConfigIterator {} - /// Convert a configuration index to a configuration vector. /// /// The index is treated as a number in base `num_flavors`. @@ -122,8 +38,7 @@ pub(crate) fn bits_to_config(bits: &[bool]) -> Vec { /// Iterator over all configurations for per-variable dimension sizes. /// -/// Unlike `ConfigIterator` which assumes uniform flavors, this supports -/// different cardinalities per variable (e.g., `dims = [2, 3, 2]`). +/// Supports different cardinalities per variable (e.g., `dims = [2, 3, 2]`). pub struct DimsIterator { dims: Vec, current: Option>, diff --git a/src/lib.rs b/src/lib.rs index e76634374..8a535cf1c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -26,7 +26,6 @@ pub mod models; pub mod registry; pub mod rules; pub mod solvers; -pub mod testing; pub mod topology; pub mod traits; #[allow(dead_code)] diff --git a/src/models/optimization/ilp.rs b/src/models/optimization/ilp.rs index 15dd5a89b..6a3f44166 100644 --- a/src/models/optimization/ilp.rs +++ b/src/models/optimization/ilp.rs @@ -136,7 +136,7 @@ pub struct LinearConstraint { impl LinearConstraint { /// Create a new linear constraint. - pub fn new(terms: Vec<(usize, f64)>, cmp: Comparison, rhs: f64) -> Self { + pub(crate) fn new(terms: Vec<(usize, f64)>, cmp: Comparison, rhs: f64) -> Self { Self { terms, cmp, rhs } } diff --git a/src/models/set/maximum_set_packing.rs b/src/models/set/maximum_set_packing.rs index 19719fa31..0b35c75cb 100644 --- a/src/models/set/maximum_set_packing.rs +++ b/src/models/set/maximum_set_packing.rs @@ -5,7 +5,7 @@ use crate::registry::{FieldInfo, ProblemSchemaEntry}; use crate::traits::{OptimizationProblem, Problem}; -use crate::types::{Direction, SolutionSize, WeightElement}; +use crate::types::{Direction, One, SolutionSize, WeightElement}; use num_traits::Zero; use serde::{Deserialize, Serialize}; use std::collections::HashSet; @@ -174,6 +174,7 @@ where } crate::declare_variants! { + MaximumSetPacking => "2^num_sets", MaximumSetPacking => "2^num_sets", MaximumSetPacking => "2^num_sets", } diff --git a/src/rules/maximumindependentset_maximumsetpacking.rs b/src/rules/maximumindependentset_maximumsetpacking.rs index e6813bc2b..94f836d13 100644 --- a/src/rules/maximumindependentset_maximumsetpacking.rs +++ b/src/rules/maximumindependentset_maximumsetpacking.rs @@ -8,7 +8,7 @@ use crate::models::set::MaximumSetPacking; use crate::reduction; use crate::rules::traits::{ReduceTo, ReductionResult}; use crate::topology::{Graph, SimpleGraph}; -use crate::types::WeightElement; +use crate::types::{One, WeightElement}; use std::collections::HashSet; /// Result of reducing MaximumIndependentSet to MaximumSetPacking. @@ -34,32 +34,34 @@ where } } -#[reduction( - overhead = { - num_sets = "num_vertices", - universe_size = "num_vertices", - } -)] -impl ReduceTo> for MaximumIndependentSet { - type Result = ReductionISToSP; - - fn reduce_to(&self) -> Self::Result { - let edges = self.graph().edges(); - let n = self.graph().num_vertices(); - - // For each vertex, collect the indices of its incident edges - let mut sets: Vec> = vec![Vec::new(); n]; - for (edge_idx, &(u, v)) in edges.iter().enumerate() { - sets[u].push(edge_idx); - sets[v].push(edge_idx); - } +macro_rules! impl_is_to_sp { + ($W:ty) => { + #[reduction(overhead = { num_sets = "num_vertices", universe_size = "num_vertices" })] + impl ReduceTo> for MaximumIndependentSet { + type Result = ReductionISToSP<$W>; + + fn reduce_to(&self) -> Self::Result { + let edges = self.graph().edges(); + let n = self.graph().num_vertices(); + + // For each vertex, collect the indices of its incident edges + let mut sets: Vec> = vec![Vec::new(); n]; + for (edge_idx, &(u, v)) in edges.iter().enumerate() { + sets[u].push(edge_idx); + sets[v].push(edge_idx); + } - let target = MaximumSetPacking::with_weights(sets, self.weights().to_vec()); + let target = MaximumSetPacking::with_weights(sets, self.weights().to_vec()); - ReductionISToSP { target } - } + ReductionISToSP { target } + } + } + }; } +impl_is_to_sp!(i32); +impl_is_to_sp!(One); + /// Result of reducing MaximumSetPacking to MaximumIndependentSet. #[derive(Debug, Clone)] pub struct ReductionSPToIS { @@ -83,38 +85,42 @@ where } } -#[reduction( - overhead = { - num_vertices = "num_sets", - num_edges = "num_sets", - } -)] -impl ReduceTo> for MaximumSetPacking { - type Result = ReductionSPToIS; - - fn reduce_to(&self) -> Self::Result { - let sets = self.sets(); - let n = sets.len(); - - // Create edges between sets that overlap - let mut edges = Vec::new(); - for (i, set_i_vec) in sets.iter().enumerate() { - let set_i: HashSet<_> = set_i_vec.iter().collect(); - for (j, set_j) in sets.iter().enumerate().skip(i + 1) { - // Check if sets[i] and sets[j] overlap - if set_j.iter().any(|elem| set_i.contains(elem)) { - edges.push((i, j)); +macro_rules! impl_sp_to_is { + ($W:ty) => { + #[reduction(overhead = { num_vertices = "num_sets", num_edges = "num_sets" })] + impl ReduceTo> for MaximumSetPacking<$W> { + type Result = ReductionSPToIS<$W>; + + fn reduce_to(&self) -> Self::Result { + let sets = self.sets(); + let n = sets.len(); + + // Create edges between sets that overlap + let mut edges = Vec::new(); + for (i, set_i_vec) in sets.iter().enumerate() { + let set_i: HashSet<_> = set_i_vec.iter().collect(); + for (j, set_j) in sets.iter().enumerate().skip(i + 1) { + // Check if sets[i] and sets[j] overlap + if set_j.iter().any(|elem| set_i.contains(elem)) { + edges.push((i, j)); + } + } } - } - } - let target = - MaximumIndependentSet::new(SimpleGraph::new(n, edges), self.weights_ref().clone()); + let target = MaximumIndependentSet::new( + SimpleGraph::new(n, edges), + self.weights_ref().clone(), + ); - ReductionSPToIS { target } - } + ReductionSPToIS { target } + } + } + }; } +impl_sp_to_is!(i32); +impl_sp_to_is!(One); + #[cfg(test)] #[path = "../unit_tests/rules/maximumindependentset_maximumsetpacking.rs"] mod tests; diff --git a/src/rules/maximumsetpacking_casts.rs b/src/rules/maximumsetpacking_casts.rs index 7aa5175f2..e993fdb09 100644 --- a/src/rules/maximumsetpacking_casts.rs +++ b/src/rules/maximumsetpacking_casts.rs @@ -2,8 +2,18 @@ use crate::impl_variant_reduction; use crate::models::set::MaximumSetPacking; +use crate::types::One; use crate::variant::CastToParent; +impl_variant_reduction!( + MaximumSetPacking, + => , + fields: [num_sets, universe_size], + |src| MaximumSetPacking::with_weights( + src.sets().to_vec(), + src.weights_ref().iter().map(|w| w.cast_to_parent()).collect()) +); + impl_variant_reduction!( MaximumSetPacking, => , diff --git a/src/testing/macros.rs b/src/testing/macros.rs deleted file mode 100644 index ef35ca706..000000000 --- a/src/testing/macros.rs +++ /dev/null @@ -1,234 +0,0 @@ -//! Testing macros for problem implementations. - -/// Generate standard tests for a graph problem. -/// -/// This macro generates tests for: -/// - Problem creation -/// - Solution evaluation -/// - Brute force solving (for small instances) -/// - Metadata (if ProblemMetadata is implemented) -/// -/// # Example -/// -/// ```text -/// // Macro usage example - users customize for their tests -/// use problemreductions::graph_problem_tests; -/// use problemreductions::models::graph::MaximumIndependentSet; -/// use problemreductions::topology::SimpleGraph; -/// -/// graph_problem_tests! { -/// problem_type: MaximumIndependentSet, -/// test_cases: [ -/// // (name, num_vertices, edges, valid_solution, expected_value, is_maximization) -/// (triangle, 3, [(0, 1), (1, 2), (0, 2)], [1, 0, 0], 1, true), -/// (path3, 3, [(0, 1), (1, 2)], [1, 0, 1], 2, true), -/// ] -/// } -/// ``` -#[macro_export] -macro_rules! graph_problem_tests { - ( - problem_type: $problem:ty, - test_cases: [ - $( - ($name:ident, $n:expr, [$($edge:expr),*], [$($sol:expr),*], $size:expr, $is_max:expr) - ),* $(,)? - ] - ) => { - mod generated_tests { - use super::*; - use $crate::prelude::*; - use $crate::registry::ProblemMetadata; - use $crate::types::Direction; - - $( - mod $name { - use super::*; - - fn create_problem() -> $problem { - <$problem>::new($n, vec![$($edge),*]) - } - - #[test] - fn test_creation() { - let problem = create_problem(); - assert_eq!(problem.num_variables(), $n); - } - - #[test] - fn test_solution_evaluation() { - let problem = create_problem(); - let solution = vec![$($sol),*]; - let value = problem.evaluate(&solution); - assert_eq!(value, $size, "Solution value mismatch"); - } - - #[test] - fn test_direction() { - let problem = create_problem(); - if $is_max { - assert_eq!(problem.direction(), Direction::Maximize); - } else { - assert_eq!(problem.direction(), Direction::Minimize); - } - } - - #[test] - fn test_brute_force() { - if $n <= 15 { - let problem = create_problem(); - let solver = BruteForce::new(); - let solutions = solver.find_all_best(&problem); - - // All solutions should have the same (optimal) value - if solutions.len() > 1 { - let first_value = problem.evaluate(&solutions[0]); - for sol in &solutions[1..] { - assert_eq!(problem.evaluate(sol), first_value); - } - } - } - } - } - )* - - #[test] - fn test_problem_metadata() { - let info = <$problem as ProblemMetadata>::problem_info(); - assert!(!info.name.is_empty()); - assert!(!info.description.is_empty()); - - let category = <$problem as ProblemMetadata>::category(); - assert_eq!(category.name(), "graph"); - } - } - }; -} - -/// Generate tests for verifying complement relationships between problems. -/// -/// For complement problems (like MIS and MVC), the optimal solutions are complements -/// of each other: if S is a maximum independent set, then V-S is a minimum vertex cover. -/// -/// # Example -/// -/// ```text -/// // Macro usage example - users customize for their tests -/// use problemreductions::complement_test; -/// use problemreductions::prelude::{MaximumIndependentSet, MinimumVertexCover}; -/// -/// complement_test! { -/// name: is_vc_complement, -/// problem_a: MaximumIndependentSet, -/// problem_b: MinimumVertexCover, -/// test_graphs: [ -/// (3, [(0, 1), (1, 2)]), -/// (4, [(0, 1), (1, 2), (2, 3), (0, 3)]), -/// ] -/// } -/// ``` -#[macro_export] -macro_rules! complement_test { - ( - name: $name:ident, - problem_a: $prob_a:ty, - problem_b: $prob_b:ty, - test_graphs: [ - $(($n:expr, [$($edge:expr),*])),* $(,)? - ] - ) => { - #[test] - fn $name() { - use $crate::prelude::*; - - $( - { - let edges = vec![$($edge),*]; - let n = $n; - - let problem_a = <$prob_a>::new(n, edges.clone()); - let problem_b = <$prob_b>::new(n, edges); - - let solver = BruteForce::new(); - let solutions_a = solver.find_all_best(&problem_a); - let solutions_b = solver.find_all_best(&problem_b); - - // Get optimal sizes (count of selected vertices) - let size_a: usize = solutions_a[0].iter().sum(); - let size_b: usize = solutions_b[0].iter().sum(); - - // For complement problems: size_a + size_b = n - assert_eq!( - size_a + size_b, - n, - "Complement relationship failed for graph with {} vertices", - n - ); - - // Verify that complement of solution_a is valid for problem_b - // (i.e., evaluates to a valid value, is_valid() returns true) - for sol_a in &solutions_a { - let complement: Vec = sol_a.iter().map(|&x| 1 - x).collect(); - let value = problem_b.evaluate(&complement); - assert!( - value.is_valid(), - "Complement of A solution should be valid for B" - ); - } - } - )* - } - }; -} - -/// Quick test for a single problem instance. -/// -/// For maximization problems, invalid solutions evaluate to i32::MIN. -/// For minimization problems, invalid solutions evaluate to i32::MAX. -/// -/// # Example -/// -/// ```text -/// // Macro usage example - users customize for their tests -/// use problemreductions::quick_problem_test; -/// use problemreductions::prelude::MaximumIndependentSet; -/// -/// // Test a valid solution (is_max=true means maximization problem) -/// quick_problem_test!( -/// MaximumIndependentSet, -/// new(3, vec![(0, 1), (1, 2)]), -/// solution: [1, 0, 1], -/// expected_value: 2, -/// is_max: true -/// ); -/// -/// // Test an invalid solution (adjacent vertices selected) -/// quick_problem_test!( -/// MaximumIndependentSet, -/// new(3, vec![(0, 1), (1, 2)]), -/// solution: [1, 1, 0], -/// expected_value: i32::MIN, -/// is_max: true -/// ); -/// ``` -#[macro_export] -macro_rules! quick_problem_test { - ( - $problem_type:ty, - $constructor:ident($($args:expr),*), - solution: [$($sol:expr),*], - expected_value: $value:expr, - is_max: $is_max:expr - ) => { - { - let problem = <$problem_type>::$constructor($($args),*); - let solution = vec![$($sol),*]; - let result = problem.evaluate(&solution); - assert_eq!(result, $value); - } - }; -} - -#[cfg(test)] -#[path = "../unit_tests/testing/macros.rs"] -mod tests; diff --git a/src/testing/mod.rs b/src/testing/mod.rs deleted file mode 100644 index 8e70d9f9f..000000000 --- a/src/testing/mod.rs +++ /dev/null @@ -1,184 +0,0 @@ -//! Testing utilities and macros for problem implementations. -//! -//! This module provides macros and helpers to reduce test boilerplate by ~90% -//! when implementing new problems. Instead of writing 300+ lines of tests per -//! problem, you can use these macros to generate comprehensive test suites. -//! -//! # Macros -//! -//! ## `graph_problem_tests!` -//! -//! Generates a complete test suite for graph problems: -//! -//! ```text -//! // Macro usage example - users customize for their tests -//! use problemreductions::graph_problem_tests; -//! use problemreductions::models::graph::MaximumIndependentSet; -//! use problemreductions::topology::SimpleGraph; -//! -//! graph_problem_tests! { -//! problem_type: MaximumIndependentSet, -//! test_cases: [ -//! // (name, num_vertices, edges, valid_solution, expected_value, is_maximization) -//! (triangle, 3, [(0, 1), (1, 2), (0, 2)], [1, 0, 0], 1, true), -//! (path, 3, [(0, 1), (1, 2)], [1, 0, 1], 2, true), -//! ] -//! } -//! ``` -//! -//! This generates tests for: -//! - Problem creation and metadata -//! - Solution evaluation -//! - Direction (maximization vs minimization) -//! - Brute force solving (for small instances) -//! -//! ## `complement_test!` -//! -//! Tests that two problems are complements (e.g., IS + VC = n): -//! -//! ```text -//! // Macro usage example - users customize for their tests -//! use problemreductions::complement_test; -//! use problemreductions::models::graph::{MaximumIndependentSet, MinimumVertexCover}; -//! use problemreductions::topology::SimpleGraph; -//! -//! complement_test! { -//! name: test_is_vc_complement, -//! problem_a: MaximumIndependentSet, -//! problem_b: MinimumVertexCover, -//! test_graphs: [ -//! (3, [(0, 1), (1, 2)]), -//! (4, [(0, 1), (1, 2), (2, 3)]), -//! ] -//! } -//! ``` -//! -//! ## `quick_problem_test!` -//! -//! Quick single-instance validation: -//! -//! ```text -//! // Macro usage example - users customize for their tests -//! use problemreductions::quick_problem_test; -//! use problemreductions::prelude::MaximumIndependentSet; -//! -//! // Test a valid solution (is_max=true means maximization problem) -//! quick_problem_test!( -//! MaximumIndependentSet, -//! new(3, vec![(0, 1)]), -//! solution: [0, 0, 1], -//! expected_value: 1, -//! is_max: true -//! ); -//! ``` -//! -//! # Test Case Types -//! -//! - [`GraphTestCase`] - Structured test case for graph problems -//! - [`SatTestCase`] - Structured test case for SAT problems - -#[macro_use] -mod macros; - -/// A test case for a graph problem. -#[derive(Debug, Clone)] -pub struct GraphTestCase { - /// Number of vertices. - pub num_vertices: usize, - /// Edge list. - pub edges: Vec<(usize, usize)>, - /// Vertex weights (if any). - pub weights: Option>, - /// A known valid solution. - pub valid_solution: Vec, - /// The expected objective value for the valid solution. - pub expected_size: W, - /// The optimal objective value (for brute force testing). - pub optimal_size: Option, -} - -impl GraphTestCase { - /// Create a new test case with unit weights. - pub fn new( - num_vertices: usize, - edges: Vec<(usize, usize)>, - valid_solution: Vec, - expected_size: W, - ) -> Self { - Self { - num_vertices, - edges, - weights: None, - valid_solution, - expected_size, - optimal_size: None, - } - } - - /// Create a new test case with custom weights. - pub fn with_weights( - num_vertices: usize, - edges: Vec<(usize, usize)>, - weights: Vec, - valid_solution: Vec, - expected_size: W, - ) -> Self { - Self { - num_vertices, - edges, - weights: Some(weights), - valid_solution, - expected_size, - optimal_size: None, - } - } - - /// Set the optimal objective value. - pub fn with_optimal(mut self, optimal: W) -> Self { - self.optimal_size = Some(optimal); - self - } -} - -/// A test case for a SAT problem. -#[derive(Debug, Clone)] -pub struct SatTestCase { - /// Number of variables. - pub num_vars: usize, - /// Clauses as lists of literals (positive = true, negative = negated). - pub clauses: Vec>, - /// A known satisfying assignment (if satisfiable). - pub satisfying_assignment: Option>, - /// Whether the formula is satisfiable. - pub is_satisfiable: bool, -} - -impl SatTestCase { - /// Create a satisfiable test case. - pub fn satisfiable( - num_vars: usize, - clauses: Vec>, - satisfying_assignment: Vec, - ) -> Self { - Self { - num_vars, - clauses, - satisfying_assignment: Some(satisfying_assignment), - is_satisfiable: true, - } - } - - /// Create an unsatisfiable test case. - pub fn unsatisfiable(num_vars: usize, clauses: Vec>) -> Self { - Self { - num_vars, - clauses, - satisfying_assignment: None, - is_satisfiable: false, - } - } -} - -#[cfg(test)] -#[path = "../unit_tests/testing/mod.rs"] -mod tests; diff --git a/src/unit_tests/config.rs b/src/unit_tests/config.rs index 4d5f961e8..5d853a008 100644 --- a/src/unit_tests/config.rs +++ b/src/unit_tests/config.rs @@ -1,64 +1,5 @@ use super::*; -#[test] -fn test_config_iterator_binary() { - let iter = ConfigIterator::new(3, 2); - assert_eq!(iter.total(), 8); - - let configs: Vec<_> = iter.collect(); - assert_eq!(configs.len(), 8); - assert_eq!(configs[0], vec![0, 0, 0]); - assert_eq!(configs[1], vec![0, 0, 1]); - assert_eq!(configs[2], vec![0, 1, 0]); - assert_eq!(configs[3], vec![0, 1, 1]); - assert_eq!(configs[4], vec![1, 0, 0]); - assert_eq!(configs[5], vec![1, 0, 1]); - assert_eq!(configs[6], vec![1, 1, 0]); - assert_eq!(configs[7], vec![1, 1, 1]); -} - -#[test] -fn test_config_iterator_ternary() { - let iter = ConfigIterator::new(2, 3); - assert_eq!(iter.total(), 9); - - let configs: Vec<_> = iter.collect(); - assert_eq!(configs.len(), 9); - assert_eq!(configs[0], vec![0, 0]); - assert_eq!(configs[1], vec![0, 1]); - assert_eq!(configs[2], vec![0, 2]); - assert_eq!(configs[3], vec![1, 0]); - assert_eq!(configs[8], vec![2, 2]); -} - -#[test] -fn test_config_iterator_zero_variables() { - // 0 variables means exactly 1 configuration: the empty config - let iter = ConfigIterator::new(0, 2); - assert_eq!(iter.total(), 1); - let configs: Vec<_> = iter.collect(); - let expected: Vec> = vec![vec![]]; - assert_eq!(configs, expected); // One config: the empty config -} - -#[test] -fn test_config_iterator_zero_flavors() { - // Non-zero variables with 0 flavors means no valid configs - let iter = ConfigIterator::new(3, 0); - assert_eq!(iter.total(), 0); - let configs: Vec<_> = iter.collect(); - assert!(configs.is_empty()); -} - -#[test] -fn test_config_iterator_single_variable() { - let iter = ConfigIterator::new(1, 4); - assert_eq!(iter.total(), 4); - - let configs: Vec<_> = iter.collect(); - assert_eq!(configs, vec![vec![0], vec![1], vec![2], vec![3]]); -} - #[test] fn test_index_to_config() { assert_eq!(index_to_config(0, 3, 2), vec![0, 0, 0]); @@ -103,17 +44,6 @@ fn test_bits_to_config() { assert_eq!(bits_to_config(&[true, true, true]), vec![1, 1, 1]); } -#[test] -fn test_exact_size_iterator() { - let mut iter = ConfigIterator::new(3, 2); - assert_eq!(iter.len(), 8); - iter.next(); - assert_eq!(iter.len(), 7); - iter.next(); - iter.next(); - assert_eq!(iter.len(), 5); -} - // === DimsIterator tests === #[test] diff --git a/src/unit_tests/rules/maximumindependentset_maximumsetpacking.rs b/src/unit_tests/rules/maximumindependentset_maximumsetpacking.rs index 693c2cbae..0bb302544 100644 --- a/src/unit_tests/rules/maximumindependentset_maximumsetpacking.rs +++ b/src/unit_tests/rules/maximumindependentset_maximumsetpacking.rs @@ -1,5 +1,6 @@ use super::*; use crate::solvers::BruteForce; +use crate::types::One; include!("../jl_helpers.rs"); #[test] @@ -167,3 +168,45 @@ fn test_jl_parity_doc_is_to_setpacking() { assert_eq!(best_source, jl_parse_configs_set(&case["best_source"])); } } + +#[test] +fn test_maximumindependentset_one_to_maximumsetpacking_closed_loop() { + // Path graph: 0-1-2 with unit weights (MIS = 2: select vertices 0, 2) + let is_problem = MaximumIndependentSet::new( + SimpleGraph::new(3, vec![(0, 1), (1, 2)]), + vec![One; 3], + ); + let reduction = ReduceTo::>::reduce_to(&is_problem); + let sp_problem = reduction.target_problem(); + + assert_eq!(sp_problem.num_sets(), 3); + + let solver = BruteForce::new(); + let sp_solutions = solver.find_all_best(sp_problem); + assert!(!sp_solutions.is_empty()); + + let original_solution = reduction.extract_solution(&sp_solutions[0]); + assert_eq!(original_solution.len(), 3); + let size: usize = original_solution.iter().sum(); + assert_eq!(size, 2, "Max IS in path of 3 should be 2"); +} + +#[test] +fn test_maximumsetpacking_one_to_maximumindependentset_closed_loop() { + // Disjoint sets: S0={0,1}, S1={1,2}, S2={3,4} — S0 and S1 overlap + let sets = vec![vec![0, 1], vec![1, 2], vec![3, 4]]; + let sp_problem = MaximumSetPacking::with_weights(sets, vec![One; 3]); + let reduction = ReduceTo::>::reduce_to(&sp_problem); + let is_problem = reduction.target_problem(); + + assert_eq!(is_problem.graph().num_vertices(), 3); + + let solver = BruteForce::new(); + let is_solutions = solver.find_all_best(is_problem); + assert!(!is_solutions.is_empty()); + + let original_solution = reduction.extract_solution(&is_solutions[0]); + assert_eq!(original_solution.len(), 3); + let size: usize = original_solution.iter().sum(); + assert_eq!(size, 2, "Max set packing should select 2 non-overlapping sets"); +} diff --git a/src/unit_tests/testing/macros.rs b/src/unit_tests/testing/macros.rs deleted file mode 100644 index 0e55eb887..000000000 --- a/src/unit_tests/testing/macros.rs +++ /dev/null @@ -1,63 +0,0 @@ -use crate::prelude::*; -use crate::topology::SimpleGraph; -use crate::types::SolutionSize; - -// Test the quick_problem_test macro -#[test] -fn test_quick_problem_test_macro() { - // Test a valid solution - { - let problem = - MaximumIndependentSet::new(SimpleGraph::new(3, vec![(0, 1), (1, 2)]), vec![1i32; 3]); - let solution = vec![1, 0, 1]; - let result = problem.evaluate(&solution); - assert_eq!(result, SolutionSize::Valid(2)); - } - - // Test an invalid solution (adjacent vertices selected) -> returns Invalid - { - let problem = - MaximumIndependentSet::new(SimpleGraph::new(3, vec![(0, 1), (1, 2)]), vec![1i32; 3]); - let solution = vec![1, 1, 0]; - let result = problem.evaluate(&solution); - assert_eq!(result, SolutionSize::Invalid); - } -} - -// Test the complement_test macro - manually implemented since MIS constructor changed -#[test] -fn test_is_vc_complement() { - use crate::prelude::*; - - for (n, edges) in [ - (3usize, vec![(0, 1), (1, 2)]), - (4usize, vec![(0, 1), (1, 2), (2, 3), (0, 3)]), - ] { - let problem_a = - MaximumIndependentSet::new(SimpleGraph::new(n, edges.clone()), vec![1i32; n]); - let problem_b = MinimumVertexCover::new(SimpleGraph::new(n, edges), vec![1i32; n]); - - let solver = BruteForce::new(); - let solutions_a = solver.find_all_best(&problem_a); - let solutions_b = solver.find_all_best(&problem_b); - - let size_a: usize = solutions_a[0].iter().sum(); - let size_b: usize = solutions_b[0].iter().sum(); - - assert_eq!( - size_a + size_b, - n, - "Complement relationship failed for graph with {} vertices", - n - ); - - for sol_a in &solutions_a { - let complement: Vec = sol_a.iter().map(|&x| 1 - x).collect(); - let value = problem_b.evaluate(&complement); - assert!( - value.is_valid(), - "Complement of A solution should be valid for B" - ); - } - } -} diff --git a/src/unit_tests/testing/mod.rs b/src/unit_tests/testing/mod.rs deleted file mode 100644 index 336311b67..000000000 --- a/src/unit_tests/testing/mod.rs +++ /dev/null @@ -1,37 +0,0 @@ -use super::*; - -#[test] -fn test_graph_test_case() { - let case = GraphTestCase::new(3, vec![(0, 1), (1, 2)], vec![1, 0, 1], 2); - assert_eq!(case.num_vertices, 3); - assert_eq!(case.edges.len(), 2); - assert!(case.weights.is_none()); - assert!(case.optimal_size.is_none()); -} - -#[test] -fn test_graph_test_case_with_weights() { - let case = GraphTestCase::with_weights(3, vec![(0, 1)], vec![1, 2, 3], vec![0, 0, 1], 3); - assert!(case.weights.is_some()); - assert_eq!(case.weights.as_ref().unwrap(), &vec![1, 2, 3]); -} - -#[test] -fn test_graph_test_case_with_optimal() { - let case = GraphTestCase::new(3, vec![(0, 1)], vec![0, 0, 1], 1).with_optimal(2); - assert_eq!(case.optimal_size, Some(2)); -} - -#[test] -fn test_sat_test_case_satisfiable() { - let case = SatTestCase::satisfiable(2, vec![vec![1, 2], vec![-1]], vec![0, 1]); - assert!(case.is_satisfiable); - assert!(case.satisfying_assignment.is_some()); -} - -#[test] -fn test_sat_test_case_unsatisfiable() { - let case = SatTestCase::unsatisfiable(1, vec![vec![1], vec![-1]]); - assert!(!case.is_satisfiable); - assert!(case.satisfying_assignment.is_none()); -} From 6cb8eb69fd3045f60ac534a86dc07923159a0cfa Mon Sep 17 00:00:00 2001 From: GiggleLiu Date: Sat, 28 Feb 2026 11:43:40 +0800 Subject: [PATCH 2/3] fix: correct 7 variant complexities and 4 reduction overheads; enrich paper MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Variant complexity fixes: - MaximumMatching: 2^n → n^3 (polynomial by Edmonds' blossom) - KColoring: 2^n → n+m (polynomial bipartiteness test) - KColoring: 3^n → 1.3289^n (Beigel-Eppstein) - KColoring: 4^n → 1.7159^n (Wu et al.) - KColoring: 5^n → 2^n (Zamir) - KColoring: k^n → 2^n (Björklund et al.) - KSatisfiability: 2^n → n+m (Aspvall-Plass-Tarjan SCC) Reduction overhead fixes: - IS→SP: universe_size num_vertices → num_edges (sets contain edge indices) - SP→IS: num_edges num_sets → num_sets^2 (intersection graph) - SAT→kSAT: tighter clause/variable bounds for short-clause padding - Factoring→CircuitSAT: 6 assignments + I/O vars per multiplier cell Paper: remove standalone complexity table (Section 2.6), add render-complexity inline in problem-def, enrich all 21 definitions with algorithm context, limitations, and citations verified via web search. Fix MaxClique bound (1.1892→1.1996 via MIS complement). Co-Authored-By: Claude Opus 4.6 --- .claude/CLAUDE.md | 16 ++ docs/paper/reductions.typ | 42 +++- docs/paper/references.bib | 211 ++++++++++++++++++ docs/src/reductions/reduction_graph.json | 30 +-- src/models/graph/kcoloring.rs | 10 +- src/models/graph/maximum_matching.rs | 2 +- src/models/satisfiability/ksat.rs | 2 +- src/rules/factoring_circuit.rs | 4 +- ...maximumindependentset_maximumsetpacking.rs | 4 +- src/rules/sat_ksat.rs | 4 +- 10 files changed, 296 insertions(+), 29 deletions(-) diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md index 5df7811a1..7e187132f 100644 --- a/.claude/CLAUDE.md +++ b/.claude/CLAUDE.md @@ -205,3 +205,19 @@ Also add to the `display-name` dictionary: ``` Every directed reduction in the graph needs its own `reduction-rule` entry. The paper auto-checks completeness against `reduction_graph.json`. + +## Complexity Verification Requirements + +### Variant Worst-Case Complexity (`declare_variants!`) +The complexity string represents the **worst-case time complexity of the best known algorithm** for that problem variant. To verify correctness: +1. Identify the best known exact algorithm for the problem (name, author, year, citation) +2. Confirm the worst-case time bound from the original paper or a survey +3. Check that polynomial-time problems (e.g., MaximumMatching, 2-SAT, 2-Coloring) are NOT declared with exponential complexity +4. For NP-hard problems, verify the base of the exponential matches the literature (e.g., 1.1996^n for MIS, not 2^n) + +### Reduction Overhead (`#[reduction(overhead = {...})]`) +Overhead expressions describe how target problem size relates to source problem size. To verify correctness: +1. Read the `reduce_to()` implementation and count the actual output sizes +2. Check that each field (e.g., `num_vertices`, `num_edges`, `num_sets`) matches the constructed target problem +3. Watch for common errors: universe elements mismatch (edge indices vs vertex indices), worst-case edge counts in intersection graphs (quadratic, not linear), constant factors in circuit constructions +4. Test with concrete small instances: construct a source problem, run the reduction, and compare target sizes against the formula diff --git a/docs/paper/reductions.typ b/docs/paper/reductions.typ index cfae33fe3..9b639e589 100644 --- a/docs/paper/reductions.typ +++ b/docs/paper/reductions.typ @@ -85,6 +85,24 @@ } } +// Render complexity from graph-data nodes +#let render-complexity(name) = { + let nodes = graph-data.nodes.filter(n => n.name == name) + if nodes.len() == 0 { return } + let seen = () + let entries = () + for node in nodes { + if node.complexity not in seen { + seen.push(node.complexity) + entries.push(node.complexity) + } + } + block(above: 0.5em)[ + #set text(size: 9pt) + - Complexity: #entries.map(e => raw(e)).join("; "). + ] +} + // Render the "Reduces to/from" lines for a problem #let render-reductions(problem-name) = { let reduces-to = get-reductions-to(problem-name) @@ -158,13 +176,14 @@ base_level: 1, ) -// Problem definition wrapper: auto-adds schema, reductions list, and label +// Problem definition wrapper: auto-adds schema, complexity, reductions list, and label #let problem-def(name, body) = { let lbl = label("def:" + name) let title = display-name.at(name) [#definition(title)[ #body #render-schema(name) + #render-complexity(name) #render-reductions(name) ] #lbl] } @@ -309,38 +328,47 @@ In all graph problems below, $G = (V, E)$ denotes an undirected graph with $|V| #problem-def("MaximumIndependentSet")[ Given $G = (V, E)$ with vertex weights $w: V -> RR$, find $S subset.eq V$ maximizing $sum_(v in S) w(v)$ such that no two vertices in $S$ are adjacent: $forall u, v in S: (u, v) in.not E$. + One of Karp's 21 NP-complete problems @karp1972. Best known: $O^*(1.1996^n)$ via measure-and-conquer branching @xiao2017. Solvable in polynomial time on bipartite, interval, and cograph classes. ] #problem-def("MinimumVertexCover")[ Given $G = (V, E)$ with vertex weights $w: V -> RR$, find $S subset.eq V$ minimizing $sum_(v in S) w(v)$ such that every edge has at least one endpoint in $S$: $forall (u, v) in E: u in S or v in S$. + Best known: $O^*(1.1996^n)$ via MIS complement ($|"VC"| + |"IS"| = n$) @xiao2017. A central problem in parameterized complexity: admits FPT algorithms in $O^*(1.2738^k)$ time parameterized by solution size $k$. ] #problem-def("MaxCut")[ Given $G = (V, E)$ with weights $w: E -> RR$, find partition $(S, overline(S))$ maximizing $sum_((u,v) in E: u in S, v in overline(S)) w(u, v)$. + Best known: $O^*(2^(omega n slash 3))$ via algebraic 2-CSP techniques @williams2005, where $omega < 2.372$ is the matrix multiplication exponent; requires exponential space. Polynomial-time solvable on planar graphs. The Goemans-Williamson SDP relaxation achieves a 0.878-approximation @goemans1995. ] #problem-def("KColoring")[ Given $G = (V, E)$ and $k$ colors, find $c: V -> {1, ..., k}$ minimizing $|{(u, v) in E : c(u) = c(v)}|$. + Deciding $k$-colorability is NP-complete for $k >= 3$ @garey1979. Best known: $O(n+m)$ for $k=2$ (equivalent to bipartiteness testing by BFS); $O^*(1.3289^n)$ for $k=3$ @beigel2005; $O^*(1.7159^n)$ for $k=4$ @wu2024; $O^*((2-epsilon)^n)$ for $k=5$, the first to break the $2^n$ barrier @zamir2021; $O^*(2^n)$ in general via inclusion-exclusion over independent sets @bjorklund2009. ] #problem-def("MinimumDominatingSet")[ Given $G = (V, E)$ with weights $w: V -> RR$, find $S subset.eq V$ minimizing $sum_(v in S) w(v)$ s.t. $forall v in V: v in S or exists u in S: (u, v) in E$. + Best known: $O^*(1.4969^n)$ via branch-and-reduce with measure and conquer @vanrooij2011. W[2]-complete when parameterized by solution size, making it strictly harder than Vertex Cover in the parameterized hierarchy. ] #problem-def("MaximumMatching")[ Given $G = (V, E)$ with weights $w: E -> RR$, find $M subset.eq E$ maximizing $sum_(e in M) w(e)$ s.t. $forall e_1, e_2 in M: e_1 inter e_2 = emptyset$. + Solvable in polynomial time $O(n^3)$ by Edmonds' blossom algorithm @edmonds1965, which introduced the technique of shrinking odd cycles into pseudo-nodes. Unlike most combinatorial optimization problems on general graphs, maximum matching is not NP-hard. ] #problem-def("TravelingSalesman")[ Given an undirected graph $G=(V,E)$ with edge weights $w: E -> RR$, find an edge set $C subset.eq E$ that forms a cycle visiting every vertex exactly once and minimizes $sum_(e in C) w(e)$. + Best known: $O^*(2^n)$ via Held-Karp dynamic programming @heldkarp1962, requiring $O^*(2^n)$ space. No $O^*((2-epsilon)^n)$ time algorithm is known. ] #problem-def("MaximumClique")[ Given $G = (V, E)$, find $K subset.eq V$ maximizing $|K|$ such that all pairs in $K$ are adjacent: $forall u, v in K: (u, v) in E$. Equivalent to MIS on the complement graph $overline(G)$. + Best known: $O^*(1.1996^n)$ via complement reduction to MIS @xiao2017. Robson's direct algorithm achieves $O^*(1.2109^n)$ @robson1986 using exponential space. ] #problem-def("MaximalIS")[ Given $G = (V, E)$ with vertex weights $w: V -> RR$, find $S subset.eq V$ maximizing $sum_(v in S) w(v)$ such that $S$ is independent ($forall u, v in S: (u, v) in.not E$) and maximal (no vertex $u in V backslash S$ can be added to $S$ while maintaining independence). + Best known: $O^*(3^(n slash 3))$ for enumerating all maximal independent sets @tomita2006. This bound is tight: Moon and Moser @moonmoser1965 showed that every $n$-vertex graph has at most $3^(n slash 3)$ maximal independent sets, achieved by disjoint triangles. ] @@ -348,56 +376,68 @@ In all graph problems below, $G = (V, E)$ denotes an undirected graph with $|V| #problem-def("MaximumSetPacking")[ Given universe $U$, collection $cal(S) = {S_1, ..., S_m}$ with $S_i subset.eq U$, weights $w: cal(S) -> RR$, find $cal(P) subset.eq cal(S)$ maximizing $sum_(S in cal(P)) w(S)$ s.t. $forall S_i, S_j in cal(P): S_i inter S_j = emptyset$. + One of Karp's 21 NP-complete problems @karp1972. Generalizes maximum matching (the special case where all sets have size 2, solvable in polynomial time). The optimization version is as hard to approximate as maximum clique. Best known: $O^*(2^m)$. ] #problem-def("MinimumSetCovering")[ Given universe $U$, collection $cal(S)$ with weights $w: cal(S) -> RR$, find $cal(C) subset.eq cal(S)$ minimizing $sum_(S in cal(C)) w(S)$ s.t. $union.big_(S in cal(C)) S = U$. + Best known: $O^*(2^m)$. The greedy algorithm achieves an $O(ln n)$-approximation where $n = |U|$, which is essentially optimal: cannot be approximated within $(1-o(1)) ln n$ unless P = NP. ] == Optimization Problems #problem-def("SpinGlass")[ Given $n$ spin variables $s_i in {-1, +1}$, pairwise couplings $J_(i j) in RR$, and external fields $h_i in RR$, minimize the Hamiltonian (energy function): $H(bold(s)) = -sum_((i,j)) J_(i j) s_i s_j - sum_i h_i s_i$. + NP-hard on general graphs @barahona1982; best known $O^*(2^n)$. On planar graphs without external field ($h_i = 0$), solvable in polynomial time via reduction to minimum-weight perfect matching. Central to statistical physics and quantum computing. ] #problem-def("QUBO")[ Given $n$ binary variables $x_i in {0, 1}$, upper-triangular matrix $Q in RR^(n times n)$, minimize $f(bold(x)) = sum_(i=1)^n Q_(i i) x_i + sum_(i < j) Q_(i j) x_i x_j$ (using $x_i^2 = x_i$ for binary variables). + Equivalent to the Ising model via the linear substitution $s_i = 2x_i - 1$. The native formulation for quantum annealing hardware and a standard target for penalty-method reductions @glover2019. Best known: $O^*(2^n)$. ] #problem-def("ILP")[ Given $n$ integer variables $bold(x) in ZZ^n$, constraint matrix $A in RR^(m times n)$, bounds $bold(b) in RR^m$, and objective $bold(c) in RR^n$, find $bold(x)$ minimizing $bold(c)^top bold(x)$ subject to $A bold(x) <= bold(b)$ and variable bounds. + Best known: $O^*(n^n)$ @dadush2012. When the number of integer variables $n$ is fixed, solvable in polynomial time by Lenstra's algorithm @lenstra1983 using the geometry of numbers, making ILP fixed-parameter tractable in $n$. ] == Satisfiability Problems #problem-def("Satisfiability")[ Given a CNF formula $phi = and.big_(j=1)^m C_j$ with $m$ clauses over $n$ Boolean variables, where each clause $C_j = or.big_i ell_(j i)$ is a disjunction of literals, find an assignment $bold(x) in {0, 1}^n$ such that $phi(bold(x)) = 1$ (all clauses satisfied). + Best known: $O^*(2^n)$. The Strong Exponential Time Hypothesis (SETH) @impagliazzo2001 conjectures that no $O^*((2-epsilon)^n)$ algorithm exists for general CNF-SAT. Despite this worst-case hardness, conflict-driven clause learning (CDCL) solvers handle large practical instances efficiently. ] #problem-def("KSatisfiability")[ SAT with exactly $k$ literals per clause. + $O(n+m)$ for $k=2$ via implication graph SCC decomposition @aspvall1979. $O^*(1.307^n)$ for $k=3$ via biased-PPSZ @hansen2019. Under SETH, $k$-SAT requires time $O^*(c_k^n)$ with $c_k -> 2$ as $k -> infinity$. ] #problem-def("CircuitSAT")[ Given a Boolean circuit $C$ composed of logic gates (AND, OR, NOT, XOR) with $n$ input variables, find an input assignment $bold(x) in {0,1}^n$ such that $C(bold(x)) = 1$. + NP-complete by the Cook-Levin theorem @cook1971, which established NP-completeness by showing any NP computation can be expressed as a boolean circuit. Reducible to CNF-SAT via the Tseitin transformation. Best known: $O^*(2^n)$. ] #problem-def("Factoring")[ Given a composite integer $N$ and bit sizes $m, n$, find integers $p in [2, 2^m - 1]$ and $q in [2, 2^n - 1]$ such that $p times q = N$. Here $p$ has $m$ bits and $q$ has $n$ bits. + Sub-exponential classically: $e^(O(b^(1 slash 3)(log b)^(2 slash 3)))$ via the General Number Field Sieve @lenstra1993, where $b$ is the bit length. Solvable in polynomial time on a quantum computer by Shor's algorithm @shor1994. Not known to be NP-complete; factoring lies in NP $inter$ co-NP. ] == Specialized Problems #problem-def("BMF")[ Given an $m times n$ boolean matrix $A$ and rank $k$, find boolean matrices $B in {0,1}^(m times k)$ and $C in {0,1}^(k times n)$ minimizing the Hamming distance $d_H (A, B circle.tiny C)$, where the boolean product $(B circle.tiny C)_(i j) = or.big_ell (B_(i ell) and C_(ell j))$. + NP-hard, even to approximate. Arises in data mining, text mining, and recommender systems. Best known: $O^*(2^n)$; practical algorithms use greedy rank-1 extraction. ] #problem-def("PaintShop")[ Given a sequence of $2n$ positions where each of $n$ cars appears exactly twice, assign a binary color to each car (each car's two occurrences receive opposite colors) to minimize the number of color changes between consecutive positions. + NP-hard and APX-hard @epping2004. Arises in automotive manufacturing where color changes require setup time and increase paint waste. A natural benchmark for quantum annealing. Best known: $O^*(2^n)$. ] #problem-def("BicliqueCover")[ Given a bipartite graph $G = (L, R, E)$ and integer $k$, find $k$ bicliques $(L_1, R_1), dots, (L_k, R_k)$ that cover all edges ($E subset.eq union.big_i L_i times R_i$) while minimizing the total size $sum_i (|L_i| + |R_i|)$. + NP-hard; connected to the Boolean rank of binary matrices and nondeterministic communication complexity. Best known: $O^*(2^n)$. ] // Completeness check: warn about problem types in JSON but missing from paper diff --git a/docs/paper/references.bib b/docs/paper/references.bib index fe632703f..add1254c6 100644 --- a/docs/paper/references.bib +++ b/docs/paper/references.bib @@ -80,6 +80,164 @@ @article{nguyen2023 doi = {10.1103/PRXQuantum.4.010316} } +@article{xiao2017, + author = {Mingyu Xiao and Hiroshi Nagamochi}, + title = {Exact Algorithms for Maximum Independent Set}, + journal = {Information and Computation}, + volume = {255}, + pages = {126--146}, + year = {2017}, + doi = {10.1016/j.ic.2017.06.001} +} + +@article{robson1986, + author = {J. M. Robson}, + title = {Algorithms for Maximum Independent Sets}, + journal = {Journal of Algorithms}, + volume = {7}, + number = {3}, + pages = {425--440}, + year = {1986}, + doi = {10.1016/0196-6774(86)90032-5} +} + +@article{vanrooij2011, + author = {Johan M. M. van Rooij and Hans L. Bodlaender}, + title = {Exact algorithms for dominating set}, + journal = {Discrete Applied Mathematics}, + volume = {159}, + number = {17}, + pages = {2147--2164}, + year = {2011}, + doi = {10.1016/j.dam.2011.07.001} +} + +@article{williams2005, + author = {Ryan Williams}, + title = {A new algorithm for optimal 2-constraint satisfaction and its implications}, + journal = {Theoretical Computer Science}, + volume = {348}, + number = {2--3}, + pages = {357--365}, + year = {2005}, + doi = {10.1016/j.tcs.2005.09.023} +} + +@article{tomita2006, + author = {Etsuji Tomita and Akira Tanaka and Haruhisa Takahashi}, + title = {The worst-case time complexity for generating all maximal cliques and computational experiments}, + journal = {Theoretical Computer Science}, + volume = {363}, + number = {1}, + pages = {28--42}, + year = {2006}, + doi = {10.1016/j.tcs.2006.06.015} +} + +@article{heldkarp1962, + author = {Michael Held and Richard M. Karp}, + title = {A Dynamic Programming Approach to Sequencing Problems}, + journal = {Journal of the Society for Industrial and Applied Mathematics}, + volume = {10}, + number = {1}, + pages = {196--210}, + year = {1962}, + doi = {10.1137/0110015} +} + +@article{beigel2005, + author = {Richard Beigel and David Eppstein}, + title = {3-Coloring in Time {$O(1.3289^n)$}}, + journal = {Journal of Algorithms}, + volume = {54}, + number = {2}, + pages = {168--204}, + year = {2005}, + doi = {10.1016/j.jalgor.2004.06.008} +} + +@inproceedings{wu2024, + author = {Pu Wu and Huanyu Gu and Huiqin Jiang and Zehui Shao and Jin Xu}, + title = {A Faster Algorithm for the 4-Coloring Problem}, + booktitle = {European Symposium on Algorithms (ESA)}, + series = {LIPIcs}, + volume = {308}, + pages = {103:1--103:16}, + year = {2024}, + doi = {10.4230/LIPIcs.ESA.2024.103} +} + +@inproceedings{zamir2021, + author = {Or Zamir}, + title = {Breaking the {$2^n$} Barrier for 5-Coloring and 6-Coloring}, + booktitle = {International Colloquium on Automata, Languages, and Programming (ICALP)}, + series = {LIPIcs}, + volume = {198}, + pages = {113:1--113:20}, + year = {2021}, + doi = {10.4230/LIPIcs.ICALP.2021.113} +} + +@article{bjorklund2009, + author = {Andreas Bj\"{o}rklund and Thore Husfeldt and Mikko Koivisto}, + title = {Set Partitioning via Inclusion-Exclusion}, + journal = {SIAM Journal on Computing}, + volume = {39}, + number = {2}, + pages = {546--563}, + year = {2009}, + doi = {10.1137/070683933} +} + +@article{aspvall1979, + author = {Bengt Aspvall and Michael F. Plass and Robert Endre Tarjan}, + title = {A Linear-Time Algorithm for Testing the Truth of Certain Quantified Boolean Formulas}, + journal = {Information Processing Letters}, + volume = {8}, + number = {3}, + pages = {121--123}, + year = {1979}, + doi = {10.1016/0020-0190(79)90002-4} +} + +@inproceedings{hansen2019, + author = {Thomas Dueholm Hansen and Haim Kaplan and Or Zamir and Uri Zwick}, + title = {Faster $k$-{SAT} Algorithms Using Biased-{PPSZ}}, + booktitle = {Proceedings of the 51st Annual ACM SIGACT Symposium on Theory of Computing (STOC)}, + pages = {578--589}, + year = {2019}, + doi = {10.1145/3313276.3316359} +} + +@phdthesis{dadush2012, + author = {Daniel Dadush}, + title = {Integer Programming, Lattice Algorithms, and Deterministic Volume Estimation}, + school = {Georgia Institute of Technology}, + year = {2012} +} + +@incollection{lenstra1993, + author = {Arjen K. Lenstra and Hendrik W. Lenstra and Mark S. Manasse and John M. Pollard}, + title = {The Number Field Sieve}, + booktitle = {The Development of the Number Field Sieve}, + publisher = {Springer}, + series = {Lecture Notes in Mathematics}, + volume = {1554}, + year = {1993}, + doi = {10.1007/BFb0091539} +} + +@inproceedings{impagliazzo2001, + author = {Russell Impagliazzo and Ramamohan Paturi and Francis Zane}, + title = {Which Problems Have Strongly Exponential Complexity?}, + booktitle = {Journal of Computer and System Sciences}, + volume = {63}, + number = {4}, + pages = {512--530}, + year = {2001}, + doi = {10.1006/jcss.2001.1774} +} + @article{pan2025, author = {Xi-Wei Pan and Huan-Hai Zhou and Yi-Ming Lu and Jin-Guo Liu}, title = {Encoding computationally hard problems in triangular {R}ydberg atom arrays}, @@ -89,3 +247,56 @@ @article{pan2025 archivePrefix = {arXiv} } +@article{goemans1995, + author = {Michel X. Goemans and David P. Williamson}, + title = {Improved Approximation Algorithms for Maximum Cut and Satisfiability Problems Using Semidefinite Programming}, + journal = {Journal of the ACM}, + volume = {42}, + number = {6}, + pages = {1115--1145}, + year = {1995}, + doi = {10.1145/227683.227684} +} + +@inproceedings{shor1994, + author = {Peter W. Shor}, + title = {Algorithms for Quantum Computation: Discrete Logarithms and Factoring}, + booktitle = {Proceedings of the 35th Annual Symposium on Foundations of Computer Science (FOCS)}, + pages = {124--134}, + year = {1994}, + doi = {10.1109/SFCS.1994.365700} +} + +@article{moonmoser1965, + author = {J. W. Moon and L. Moser}, + title = {On cliques in graphs}, + journal = {Israel Journal of Mathematics}, + volume = {3}, + number = {1}, + pages = {23--28}, + year = {1965}, + doi = {10.1007/BF02760024} +} + +@inproceedings{lenstra1983, + author = {Hendrik W. Lenstra}, + title = {Integer Programming with a Fixed Number of Variables}, + booktitle = {Mathematics of Operations Research}, + volume = {8}, + number = {4}, + pages = {538--548}, + year = {1983}, + doi = {10.1287/moor.8.4.538} +} + +@article{epping2004, + author = {Thomas Epping and Winfried Hochst\"{a}ttler and Peter Oertel}, + title = {Complexity results on a paint shop problem}, + journal = {Discrete Applied Mathematics}, + volume = {136}, + number = {2--3}, + pages = {217--226}, + year = {2004}, + doi = {10.1016/S0166-218X(03)00442-6} +} + diff --git a/docs/src/reductions/reduction_graph.json b/docs/src/reductions/reduction_graph.json index ecd559478..fdc5dffb3 100644 --- a/docs/src/reductions/reduction_graph.json +++ b/docs/src/reductions/reduction_graph.json @@ -29,7 +29,7 @@ }, "category": "graph", "doc_path": "models/graph/struct.KColoring.html", - "complexity": "2^num_vertices" + "complexity": "num_vertices + num_edges" }, { "name": "KColoring", @@ -39,7 +39,7 @@ }, "category": "graph", "doc_path": "models/graph/struct.KColoring.html", - "complexity": "3^num_vertices" + "complexity": "1.3289^num_vertices" }, { "name": "KColoring", @@ -49,7 +49,7 @@ }, "category": "graph", "doc_path": "models/graph/struct.KColoring.html", - "complexity": "4^num_vertices" + "complexity": "1.7159^num_vertices" }, { "name": "KColoring", @@ -59,7 +59,7 @@ }, "category": "graph", "doc_path": "models/graph/struct.KColoring.html", - "complexity": "5^num_vertices" + "complexity": "2^num_vertices" }, { "name": "KColoring", @@ -69,7 +69,7 @@ }, "category": "graph", "doc_path": "models/graph/struct.KColoring.html", - "complexity": "k^num_vertices" + "complexity": "2^num_vertices" }, { "name": "KSatisfiability", @@ -78,7 +78,7 @@ }, "category": "satisfiability", "doc_path": "models/satisfiability/struct.KSatisfiability.html", - "complexity": "2^num_variables" + "complexity": "num_variables + num_clauses" }, { "name": "KSatisfiability", @@ -206,7 +206,7 @@ }, "category": "graph", "doc_path": "models/graph/struct.MaximumMatching.html", - "complexity": "2^num_vertices" + "complexity": "num_vertices^3" }, { "name": "MaximumSetPacking", @@ -348,11 +348,11 @@ "overhead": [ { "field": "num_variables", - "formula": "num_bits_first * num_bits_second" + "formula": "6 * num_bits_first * num_bits_second + num_bits_first + num_bits_second" }, { "field": "num_assignments", - "formula": "num_bits_first * num_bits_second" + "formula": "6 * num_bits_first * num_bits_second" } ], "doc_path": "rules/factoring_circuit/index.html" @@ -678,7 +678,7 @@ }, { "field": "universe_size", - "formula": "num_vertices" + "formula": "num_edges" } ], "doc_path": "rules/maximumindependentset_maximumsetpacking/index.html" @@ -708,7 +708,7 @@ }, { "field": "universe_size", - "formula": "num_vertices" + "formula": "num_edges" } ], "doc_path": "rules/maximumindependentset_maximumsetpacking/index.html" @@ -839,7 +839,7 @@ }, { "field": "num_edges", - "formula": "num_sets" + "formula": "num_sets^2" } ], "doc_path": "rules/maximumindependentset_maximumsetpacking/index.html" @@ -895,7 +895,7 @@ }, { "field": "num_edges", - "formula": "num_sets" + "formula": "num_sets^2" } ], "doc_path": "rules/maximumindependentset_maximumsetpacking/index.html" @@ -1063,11 +1063,11 @@ "overhead": [ { "field": "num_clauses", - "formula": "num_clauses + num_literals" + "formula": "4 * num_clauses + num_literals" }, { "field": "num_vars", - "formula": "num_vars + num_literals" + "formula": "num_vars + 3 * num_clauses + num_literals" } ], "doc_path": "rules/sat_ksat/index.html" diff --git a/src/models/graph/kcoloring.rs b/src/models/graph/kcoloring.rs index 5b3c160d0..4a6cab029 100644 --- a/src/models/graph/kcoloring.rs +++ b/src/models/graph/kcoloring.rs @@ -184,11 +184,11 @@ pub(crate) fn is_valid_coloring( } crate::declare_variants! { - KColoring => "k^num_vertices", - KColoring => "2^num_vertices", - KColoring => "3^num_vertices", - KColoring => "4^num_vertices", - KColoring => "5^num_vertices", + KColoring => "2^num_vertices", + KColoring => "num_vertices + num_edges", + KColoring => "1.3289^num_vertices", + KColoring => "1.7159^num_vertices", + KColoring => "2^num_vertices", } #[cfg(test)] diff --git a/src/models/graph/maximum_matching.rs b/src/models/graph/maximum_matching.rs index a13abfd24..e3c0ccaa8 100644 --- a/src/models/graph/maximum_matching.rs +++ b/src/models/graph/maximum_matching.rs @@ -220,7 +220,7 @@ where } crate::declare_variants! { - MaximumMatching => "2^num_vertices", + MaximumMatching => "num_vertices^3", } /// Check if a selection of edges forms a valid matching. diff --git a/src/models/satisfiability/ksat.rs b/src/models/satisfiability/ksat.rs index 1273548ac..8f9506f9e 100644 --- a/src/models/satisfiability/ksat.rs +++ b/src/models/satisfiability/ksat.rs @@ -185,7 +185,7 @@ impl SatisfactionProblem for KSatisfiability {} crate::declare_variants! { KSatisfiability => "2^num_variables", - KSatisfiability => "2^num_variables", + KSatisfiability => "num_variables + num_clauses", KSatisfiability => "2^num_variables", } diff --git a/src/rules/factoring_circuit.rs b/src/rules/factoring_circuit.rs index 4dede1826..4be325012 100644 --- a/src/rules/factoring_circuit.rs +++ b/src/rules/factoring_circuit.rs @@ -175,8 +175,8 @@ fn build_multiplier_cell( } #[reduction(overhead = { - num_variables = "num_bits_first * num_bits_second", - num_assignments = "num_bits_first * num_bits_second", + num_variables = "6 * num_bits_first * num_bits_second + num_bits_first + num_bits_second", + num_assignments = "6 * num_bits_first * num_bits_second", })] impl ReduceTo for Factoring { type Result = ReductionFactoringToCircuit; diff --git a/src/rules/maximumindependentset_maximumsetpacking.rs b/src/rules/maximumindependentset_maximumsetpacking.rs index 94f836d13..358fdac80 100644 --- a/src/rules/maximumindependentset_maximumsetpacking.rs +++ b/src/rules/maximumindependentset_maximumsetpacking.rs @@ -36,7 +36,7 @@ where macro_rules! impl_is_to_sp { ($W:ty) => { - #[reduction(overhead = { num_sets = "num_vertices", universe_size = "num_vertices" })] + #[reduction(overhead = { num_sets = "num_vertices", universe_size = "num_edges" })] impl ReduceTo> for MaximumIndependentSet { type Result = ReductionISToSP<$W>; @@ -87,7 +87,7 @@ where macro_rules! impl_sp_to_is { ($W:ty) => { - #[reduction(overhead = { num_vertices = "num_sets", num_edges = "num_sets" })] + #[reduction(overhead = { num_vertices = "num_sets", num_edges = "num_sets^2" })] impl ReduceTo> for MaximumSetPacking<$W> { type Result = ReductionSPToIS<$W>; diff --git a/src/rules/sat_ksat.rs b/src/rules/sat_ksat.rs index 973c3453e..4cf64f437 100644 --- a/src/rules/sat_ksat.rs +++ b/src/rules/sat_ksat.rs @@ -112,8 +112,8 @@ macro_rules! impl_sat_to_ksat { ($ktype:ty, $k:expr) => { #[rustfmt::skip] #[reduction(overhead = { - num_clauses = "num_clauses + num_literals", - num_vars = "num_vars + num_literals", + num_clauses = "4 * num_clauses + num_literals", + num_vars = "num_vars + 3 * num_clauses + num_literals", })] impl ReduceTo> for Satisfiability { type Result = ReductionSATToKSAT<$ktype>; From c24294a9b67922575e04fdadabd9857cb1f40599 Mon Sep 17 00:00:00 2001 From: GiggleLiu Date: Sat, 28 Feb 2026 17:58:41 +0800 Subject: [PATCH 3/3] fix: address PR #106 review comments - Fix KColoring K5 complexity: "2^num_vertices" -> "(2-epsilon)^num_vertices" (Zamir 2021) - Fix Factoring->CircuitSAT num_assignments overhead to include output bit constraints - Add tests for MaximumSetPacking variant cast reductions (One->i32, i32->f64) - Regenerate reduction_graph.json with corrected metadata Co-Authored-By: Claude Opus 4.6 --- docs/src/reductions/reduction_graph.json | 4 +- src/models/graph/kcoloring.rs | 2 +- src/rules/factoring_circuit.rs | 2 +- src/rules/maximumsetpacking_casts.rs | 4 ++ ...maximumindependentset_maximumsetpacking.rs | 11 +++--- .../rules/maximumsetpacking_casts.rs | 39 +++++++++++++++++++ 6 files changed, 53 insertions(+), 9 deletions(-) create mode 100644 src/unit_tests/rules/maximumsetpacking_casts.rs diff --git a/docs/src/reductions/reduction_graph.json b/docs/src/reductions/reduction_graph.json index fdc5dffb3..c24b51dda 100644 --- a/docs/src/reductions/reduction_graph.json +++ b/docs/src/reductions/reduction_graph.json @@ -59,7 +59,7 @@ }, "category": "graph", "doc_path": "models/graph/struct.KColoring.html", - "complexity": "2^num_vertices" + "complexity": "(2-epsilon)^num_vertices" }, { "name": "KColoring", @@ -352,7 +352,7 @@ }, { "field": "num_assignments", - "formula": "6 * num_bits_first * num_bits_second" + "formula": "6 * num_bits_first * num_bits_second + num_bits_first + num_bits_second" } ], "doc_path": "rules/factoring_circuit/index.html" diff --git a/src/models/graph/kcoloring.rs b/src/models/graph/kcoloring.rs index 4a6cab029..7c5515f31 100644 --- a/src/models/graph/kcoloring.rs +++ b/src/models/graph/kcoloring.rs @@ -188,7 +188,7 @@ crate::declare_variants! { KColoring => "num_vertices + num_edges", KColoring => "1.3289^num_vertices", KColoring => "1.7159^num_vertices", - KColoring => "2^num_vertices", + KColoring => "(2-epsilon)^num_vertices", } #[cfg(test)] diff --git a/src/rules/factoring_circuit.rs b/src/rules/factoring_circuit.rs index 4be325012..3160d7c31 100644 --- a/src/rules/factoring_circuit.rs +++ b/src/rules/factoring_circuit.rs @@ -176,7 +176,7 @@ fn build_multiplier_cell( #[reduction(overhead = { num_variables = "6 * num_bits_first * num_bits_second + num_bits_first + num_bits_second", - num_assignments = "6 * num_bits_first * num_bits_second", + num_assignments = "6 * num_bits_first * num_bits_second + num_bits_first + num_bits_second", })] impl ReduceTo for Factoring { type Result = ReductionFactoringToCircuit; diff --git a/src/rules/maximumsetpacking_casts.rs b/src/rules/maximumsetpacking_casts.rs index e993fdb09..e9afd996f 100644 --- a/src/rules/maximumsetpacking_casts.rs +++ b/src/rules/maximumsetpacking_casts.rs @@ -22,3 +22,7 @@ impl_variant_reduction!( src.sets().to_vec(), src.weights_ref().iter().map(|w| w.cast_to_parent()).collect()) ); + +#[cfg(test)] +#[path = "../unit_tests/rules/maximumsetpacking_casts.rs"] +mod tests; diff --git a/src/unit_tests/rules/maximumindependentset_maximumsetpacking.rs b/src/unit_tests/rules/maximumindependentset_maximumsetpacking.rs index 0bb302544..b80e4619e 100644 --- a/src/unit_tests/rules/maximumindependentset_maximumsetpacking.rs +++ b/src/unit_tests/rules/maximumindependentset_maximumsetpacking.rs @@ -172,10 +172,8 @@ fn test_jl_parity_doc_is_to_setpacking() { #[test] fn test_maximumindependentset_one_to_maximumsetpacking_closed_loop() { // Path graph: 0-1-2 with unit weights (MIS = 2: select vertices 0, 2) - let is_problem = MaximumIndependentSet::new( - SimpleGraph::new(3, vec![(0, 1), (1, 2)]), - vec![One; 3], - ); + let is_problem = + MaximumIndependentSet::new(SimpleGraph::new(3, vec![(0, 1), (1, 2)]), vec![One; 3]); let reduction = ReduceTo::>::reduce_to(&is_problem); let sp_problem = reduction.target_problem(); @@ -208,5 +206,8 @@ fn test_maximumsetpacking_one_to_maximumindependentset_closed_loop() { let original_solution = reduction.extract_solution(&is_solutions[0]); assert_eq!(original_solution.len(), 3); let size: usize = original_solution.iter().sum(); - assert_eq!(size, 2, "Max set packing should select 2 non-overlapping sets"); + assert_eq!( + size, 2, + "Max set packing should select 2 non-overlapping sets" + ); } diff --git a/src/unit_tests/rules/maximumsetpacking_casts.rs b/src/unit_tests/rules/maximumsetpacking_casts.rs new file mode 100644 index 000000000..079602986 --- /dev/null +++ b/src/unit_tests/rules/maximumsetpacking_casts.rs @@ -0,0 +1,39 @@ +use super::*; +use crate::rules::traits::ReductionResult; +use crate::rules::ReduceTo; +use crate::solvers::{BruteForce, Solver}; +use crate::traits::Problem; + +#[test] +fn test_maximumsetpacking_one_to_i32_cast_closed_loop() { + let sp_one = + MaximumSetPacking::with_weights(vec![vec![0, 1], vec![1, 2], vec![3, 4]], vec![One; 3]); + + let reduction = ReduceTo::>::reduce_to(&sp_one); + let sp_i32 = reduction.target_problem(); + assert_eq!(sp_i32.weights_ref(), &vec![1i32, 1, 1]); + + let solver = BruteForce::new(); + let target_solution = solver.find_best(sp_i32).unwrap(); + let source_solution = reduction.extract_solution(&target_solution); + + let metric = sp_one.evaluate(&source_solution); + assert!(metric.is_valid()); +} + +#[test] +fn test_maximumsetpacking_i32_to_f64_cast_closed_loop() { + let sp_i32 = + MaximumSetPacking::with_weights(vec![vec![0, 1], vec![1, 2], vec![3, 4]], vec![2i32, 3, 5]); + + let reduction = ReduceTo::>::reduce_to(&sp_i32); + let sp_f64 = reduction.target_problem(); + assert_eq!(sp_f64.weights_ref(), &vec![2.0f64, 3.0, 5.0]); + + let solver = BruteForce::new(); + let target_solution = solver.find_best(sp_f64).unwrap(); + let source_solution = reduction.extract_solution(&target_solution); + + let metric = sp_i32.evaluate(&source_solution); + assert!(metric.is_valid()); +}