diff --git a/src/unit_tests/models/graph/kcoloring.rs b/src/unit_tests/models/graph/kcoloring.rs index c710cdb32..7bfe2fc9a 100644 --- a/src/unit_tests/models/graph/kcoloring.rs +++ b/src/unit_tests/models/graph/kcoloring.rs @@ -178,3 +178,13 @@ fn test_jl_parity_evaluation() { assert_eq!(rust_sat, jl_best, "KColoring satisfying solutions mismatch"); } } + +#[test] +fn test_is_valid_solution() { + // Path graph: 0-1-2, 3-coloring + let problem = KColoring::::new(SimpleGraph::new(3, vec![(0, 1), (1, 2)])); + // Valid: neighbors have different colors + assert!(problem.is_valid_solution(&[0, 1, 0])); + // Invalid: adjacent vertices 0 and 1 have same color + assert!(!problem.is_valid_solution(&[0, 0, 1])); +} diff --git a/src/unit_tests/models/graph/max_cut.rs b/src/unit_tests/models/graph/max_cut.rs index 4e2509f0c..3d7059c62 100644 --- a/src/unit_tests/models/graph/max_cut.rs +++ b/src/unit_tests/models/graph/max_cut.rs @@ -128,3 +128,12 @@ fn test_jl_parity_evaluation() { assert_eq!(rust_best, jl_best, "MaxCut best solutions mismatch"); } } + +#[test] +fn test_cut_size_method() { + let problem = MaxCut::new(SimpleGraph::new(3, vec![(0, 1), (1, 2), (0, 2)]), vec![1, 2, 3]); + // Partition {0} vs {1, 2}: cuts edges (0,1)=1 and (0,2)=3 + assert_eq!(problem.cut_size(&[0, 1, 1]), 4); + // All same partition: no edges cut + assert_eq!(problem.cut_size(&[0, 0, 0]), 0); +} diff --git a/src/unit_tests/models/graph/maximal_is.rs b/src/unit_tests/models/graph/maximal_is.rs index 64ab76358..22069621a 100644 --- a/src/unit_tests/models/graph/maximal_is.rs +++ b/src/unit_tests/models/graph/maximal_is.rs @@ -163,3 +163,13 @@ fn test_jl_parity_evaluation() { assert_eq!(rust_best, jl_best, "MaximalIS best solutions mismatch"); } } + +#[test] +fn test_is_valid_solution() { + // Path graph: 0-1-2 + let problem = MaximalIS::new(SimpleGraph::new(3, vec![(0, 1), (1, 2)]), vec![1i32; 3]); + // Valid: {0, 2} is maximal (independent and no vertex can be added) + assert!(problem.is_valid_solution(&[1, 0, 1])); + // Invalid: {0} is independent but not maximal (vertex 2 can be added) + assert!(!problem.is_valid_solution(&[1, 0, 0])); +} diff --git a/src/unit_tests/models/graph/maximum_clique.rs b/src/unit_tests/models/graph/maximum_clique.rs index 32f65b806..9ef849209 100644 --- a/src/unit_tests/models/graph/maximum_clique.rs +++ b/src/unit_tests/models/graph/maximum_clique.rs @@ -269,3 +269,18 @@ fn test_clique_problem() { assert_eq!(p.evaluate(&[1, 0, 0]), SolutionSize::Valid(1)); assert_eq!(p.direction(), Direction::Maximize); } + +#[test] +fn test_is_valid_solution() { + // Triangle: 0-1-2 all connected + let problem = MaximumClique::new( + SimpleGraph::new(3, vec![(0, 1), (1, 2), (0, 2)]), + vec![1i32; 3], + ); + // Valid: all three form a clique + assert!(problem.is_valid_solution(&[1, 1, 1])); + // Now path graph: 0-1-2 (no 0-2 edge) + let problem2 = MaximumClique::new(SimpleGraph::new(3, vec![(0, 1), (1, 2)]), vec![1i32; 3]); + // Invalid: {0, 2} not adjacent + assert!(!problem2.is_valid_solution(&[1, 0, 1])); +} diff --git a/src/unit_tests/models/graph/maximum_independent_set.rs b/src/unit_tests/models/graph/maximum_independent_set.rs index b5cc3d049..842870f14 100644 --- a/src/unit_tests/models/graph/maximum_independent_set.rs +++ b/src/unit_tests/models/graph/maximum_independent_set.rs @@ -161,3 +161,13 @@ fn test_jl_parity_evaluation() { assert_eq!(rust_best, jl_best, "IS best solutions mismatch"); } } + +#[test] +fn test_is_valid_solution() { + // Path graph: 0-1-2 + let problem = MaximumIndependentSet::new(SimpleGraph::new(3, vec![(0, 1), (1, 2)]), vec![1i32; 3]); + // Valid: {0, 2} is independent + assert!(problem.is_valid_solution(&[1, 0, 1])); + // Invalid: {0, 1} are adjacent + assert!(!problem.is_valid_solution(&[1, 1, 0])); +} diff --git a/src/unit_tests/models/graph/maximum_matching.rs b/src/unit_tests/models/graph/maximum_matching.rs index a71a51215..745cc8050 100644 --- a/src/unit_tests/models/graph/maximum_matching.rs +++ b/src/unit_tests/models/graph/maximum_matching.rs @@ -153,3 +153,16 @@ fn test_jl_parity_evaluation() { assert_eq!(rust_best, jl_best, "Matching best solutions mismatch"); } } + +#[test] +fn test_is_valid_solution() { + // Triangle: edges (0,1), (1,2), (0,2) — config is per edge + let problem = MaximumMatching::new( + SimpleGraph::new(3, vec![(0, 1), (1, 2), (0, 2)]), + vec![1i32; 3], + ); + // Valid: select edge (0,1) only — no shared vertices + assert!(problem.is_valid_solution(&[1, 0, 0])); + // Invalid: select edges (0,1) and (1,2) — vertex 1 shared + assert!(!problem.is_valid_solution(&[1, 1, 0])); +} diff --git a/src/unit_tests/models/graph/minimum_dominating_set.rs b/src/unit_tests/models/graph/minimum_dominating_set.rs index c99f3a733..432f4a280 100644 --- a/src/unit_tests/models/graph/minimum_dominating_set.rs +++ b/src/unit_tests/models/graph/minimum_dominating_set.rs @@ -158,3 +158,13 @@ fn test_jl_parity_evaluation() { assert_eq!(rust_best, jl_best, "DS best solutions mismatch"); } } + +#[test] +fn test_is_valid_solution() { + // Path graph: 0-1-2 + let problem = MinimumDominatingSet::new(SimpleGraph::new(3, vec![(0, 1), (1, 2)]), vec![1i32; 3]); + // Valid: {1} dominates all vertices (0 and 2 are neighbors of 1) + assert!(problem.is_valid_solution(&[0, 1, 0])); + // Invalid: {0} doesn't dominate vertex 2 + assert!(!problem.is_valid_solution(&[1, 0, 0])); +} diff --git a/src/unit_tests/models/graph/minimum_vertex_cover.rs b/src/unit_tests/models/graph/minimum_vertex_cover.rs index 509b5e803..396105fb5 100644 --- a/src/unit_tests/models/graph/minimum_vertex_cover.rs +++ b/src/unit_tests/models/graph/minimum_vertex_cover.rs @@ -144,3 +144,13 @@ fn test_jl_parity_evaluation() { assert_eq!(rust_best, jl_best, "VC best solutions mismatch"); } } + +#[test] +fn test_is_valid_solution() { + // Path graph: 0-1-2 + let problem = MinimumVertexCover::new(SimpleGraph::new(3, vec![(0, 1), (1, 2)]), vec![1i32; 3]); + // Valid: {1} covers both edges + assert!(problem.is_valid_solution(&[0, 1, 0])); + // Invalid: {0} doesn't cover edge (1,2) + assert!(!problem.is_valid_solution(&[1, 0, 0])); +} diff --git a/src/unit_tests/models/graph/traveling_salesman.rs b/src/unit_tests/models/graph/traveling_salesman.rs index e1047e243..87d471227 100644 --- a/src/unit_tests/models/graph/traveling_salesman.rs +++ b/src/unit_tests/models/graph/traveling_salesman.rs @@ -222,3 +222,16 @@ fn test_brute_force_triangle_weighted() { assert_eq!(solutions[0], vec![1, 1, 1]); assert_eq!(problem.evaluate(&solutions[0]), SolutionSize::Valid(30)); } + +#[test] +fn test_is_valid_solution() { + // K3 triangle: edges (0,1), (0,2), (1,2) — config is per edge + let problem = TravelingSalesman::new( + SimpleGraph::new(3, vec![(0, 1), (0, 2), (1, 2)]), + vec![1, 2, 3], + ); + // Valid: select all 3 edges forms Hamiltonian cycle 0-1-2-0 + assert!(problem.is_valid_solution(&[1, 1, 1])); + // Invalid: select only 2 edges — not a cycle + assert!(!problem.is_valid_solution(&[1, 1, 0])); +} diff --git a/src/unit_tests/models/satisfiability/sat.rs b/src/unit_tests/models/satisfiability/sat.rs index 42f2e25f1..cd346f859 100644 --- a/src/unit_tests/models/satisfiability/sat.rs +++ b/src/unit_tests/models/satisfiability/sat.rs @@ -194,3 +194,16 @@ fn test_jl_parity_evaluation() { } } } + +#[test] +fn test_is_valid_solution() { + // (x1 OR x2) AND (NOT x1 OR x3) + let problem = Satisfiability::new( + 3, + vec![CNFClause::new(vec![1, 2]), CNFClause::new(vec![-1, 3])], + ); + // Valid: x1=F, x2=T, x3=T → (T) AND (T) = T + assert!(problem.is_valid_solution(&[0, 1, 1])); + // Invalid: x1=T, x2=F, x3=F → (T) AND (F) = F + assert!(!problem.is_valid_solution(&[1, 0, 0])); +} diff --git a/src/unit_tests/models/set/maximum_set_packing.rs b/src/unit_tests/models/set/maximum_set_packing.rs index 6f65c4d6b..8242c3a9f 100644 --- a/src/unit_tests/models/set/maximum_set_packing.rs +++ b/src/unit_tests/models/set/maximum_set_packing.rs @@ -136,3 +136,13 @@ fn test_jl_parity_evaluation() { assert_eq!(rust_best, jl_best, "SetPacking best solutions mismatch"); } } + +#[test] +fn test_is_valid_solution() { + // Sets: {0,1}, {1,2}, {3,4} + let problem = MaximumSetPacking::::new(vec![vec![0, 1], vec![1, 2], vec![3, 4]]); + // Valid: select sets 0 and 2 (disjoint: {0,1} and {3,4}) + assert!(problem.is_valid_solution(&[1, 0, 1])); + // Invalid: select sets 0 and 1 (share element 1) + assert!(!problem.is_valid_solution(&[1, 1, 0])); +} diff --git a/src/unit_tests/models/set/minimum_set_covering.rs b/src/unit_tests/models/set/minimum_set_covering.rs index fa9036e54..c04aac0b0 100644 --- a/src/unit_tests/models/set/minimum_set_covering.rs +++ b/src/unit_tests/models/set/minimum_set_covering.rs @@ -106,3 +106,13 @@ fn test_jl_parity_evaluation() { assert_eq!(rust_best, jl_best, "SetCovering best solutions mismatch"); } } + +#[test] +fn test_is_valid_solution() { + // Universe: {0,1,2,3}, Sets: {0,1}, {1,2}, {2,3} + let problem = MinimumSetCovering::::new(4, vec![vec![0, 1], vec![1, 2], vec![2, 3]]); + // Valid: all sets selected covers {0,1,2,3} + assert!(problem.is_valid_solution(&[1, 1, 1])); + // Invalid: only set 1 ({1,2}) doesn't cover 0 and 3 + assert!(!problem.is_valid_solution(&[0, 1, 0])); +} diff --git a/src/unit_tests/models/specialized/biclique_cover.rs b/src/unit_tests/models/specialized/biclique_cover.rs index 10cfbaacf..bb59df538 100644 --- a/src/unit_tests/models/specialized/biclique_cover.rs +++ b/src/unit_tests/models/specialized/biclique_cover.rs @@ -230,3 +230,16 @@ fn test_jl_parity_evaluation() { assert_eq!(rust_best, jl_best, "BicliqueCover best solutions mismatch"); } } + +#[test] +fn test_is_valid_solution() { + use crate::topology::BipartiteGraph; + // Single edge (0,0) with 1 biclique + let graph = BipartiteGraph::new(1, 1, vec![(0, 0)]); + let problem = BicliqueCover::new(graph, 1); + // 2 vertices (left_0, right_0), 1 biclique → config length = 2 + // Valid: both vertices in biclique 0 → covers edge (0,0) + assert!(problem.is_valid_solution(&[1, 1])); + // Invalid: only left vertex in biclique → doesn't form complete bipartite subgraph covering edge + assert!(!problem.is_valid_solution(&[1, 0])); +} diff --git a/src/unit_tests/models/specialized/circuit.rs b/src/unit_tests/models/specialized/circuit.rs index e786ef196..6e75834d6 100644 --- a/src/unit_tests/models/specialized/circuit.rs +++ b/src/unit_tests/models/specialized/circuit.rs @@ -235,3 +235,18 @@ fn test_circuit_sat_problem() { // c=1, x=1, y=0: c = 1 AND 0 = 0 != 1 => not satisfied assert!(!p.evaluate(&[1, 1, 0])); } + +#[test] +fn test_is_valid_solution() { + // c = x AND y + let circuit = Circuit::new(vec![Assignment::new( + vec!["c".to_string()], + BooleanExpr::and(vec![BooleanExpr::var("x"), BooleanExpr::var("y")]), + )]); + let problem = CircuitSAT::new(circuit); + // Variables sorted: c, x, y + // Valid: c=1, x=1, y=1 (c = 1 AND 1 = 1) + assert!(problem.is_valid_solution(&[1, 1, 1])); + // Invalid: c=1, x=1, y=0 (c = 1 AND 0 = 0, but c=1) + assert!(!problem.is_valid_solution(&[1, 1, 0])); +} diff --git a/src/unit_tests/models/specialized/factoring.rs b/src/unit_tests/models/specialized/factoring.rs index 1acd08928..ed6997037 100644 --- a/src/unit_tests/models/specialized/factoring.rs +++ b/src/unit_tests/models/specialized/factoring.rs @@ -96,3 +96,13 @@ fn test_jl_parity_evaluation() { assert_eq!(rust_best, jl_best, "Factoring best solutions mismatch"); } } + +#[test] +fn test_is_valid_solution() { + // Factor 15 = 3 × 5, 3 bits each + let problem = Factoring::new(3, 3, 15); + // Valid: 3 = [1,1,0], 5 = [1,0,1] → config = [1,1,0,1,0,1] + assert!(problem.is_valid_solution(&[1, 1, 0, 1, 0, 1])); + // Invalid: 2 = [0,1,0], 3 = [1,1,0] → 2*3=6 ≠ 15 + assert!(!problem.is_valid_solution(&[0, 1, 0, 1, 1, 0])); +} diff --git a/src/unit_tests/variant.rs b/src/unit_tests/variant.rs index 850bc15e0..90b582e28 100644 --- a/src/unit_tests/variant.rs +++ b/src/unit_tests/variant.rs @@ -243,7 +243,7 @@ fn test_kvalue_kn() { // --- Graph type VariantParam tests --- use crate::topology::HyperGraph; -use crate::topology::{Graph, SimpleGraph, UnitDiskGraph}; +use crate::topology::{BipartiteGraph, Graph, PlanarGraph, SimpleGraph, UnitDiskGraph}; #[test] fn test_simple_graph_variant_param() { @@ -266,6 +266,20 @@ fn test_hyper_graph_variant_param() { assert_eq!(HyperGraph::PARENT_VALUE, None); } +#[test] +fn test_planar_graph_variant_param() { + assert_eq!(PlanarGraph::CATEGORY, "graph"); + assert_eq!(PlanarGraph::VALUE, "PlanarGraph"); + assert_eq!(PlanarGraph::PARENT_VALUE, Some("SimpleGraph")); +} + +#[test] +fn test_bipartite_graph_variant_param() { + assert_eq!(BipartiteGraph::CATEGORY, "graph"); + assert_eq!(BipartiteGraph::VALUE, "BipartiteGraph"); + assert_eq!(BipartiteGraph::PARENT_VALUE, Some("SimpleGraph")); +} + #[test] fn test_simple_graph_cast_to_parent() { let sg = SimpleGraph::new(3, vec![(0, 1), (1, 2)]); diff --git a/tests/suites/reductions.rs b/tests/suites/reductions.rs index 1dd7e76c7..314c8defd 100644 --- a/tests/suites/reductions.rs +++ b/tests/suites/reductions.rs @@ -422,8 +422,8 @@ mod topology_tests { } } -// TruthTable integration tests moved to src/unit_tests/truth_table.rs -// (truth_table module is now pub(crate)) +// TruthTable integration tests removed (module is now pub(crate)); +// equivalent coverage exists in src/unit_tests/truth_table.rs /// Tests for QUBO reductions against ground truth JSON. mod qubo_reductions {