From 5707218190a714ac460e5301d5e78f41e7320dc9 Mon Sep 17 00:00:00 2001 From: Xiwei Pan Date: Fri, 18 Sep 2026 00:34:14 +0800 Subject: [PATCH] Fix set splitting reduction for repeated elements --- src/models/set/set_splitting.rs | 5 ++- src/rules/setsplitting_betweenness.rs | 13 ++++++-- .../rules/setsplitting_betweenness.rs | 31 +++++++++++++++++++ 3 files changed, 46 insertions(+), 3 deletions(-) diff --git a/src/models/set/set_splitting.rs b/src/models/set/set_splitting.rs index 1c7f46a76..eaf326f36 100644 --- a/src/models/set/set_splitting.rs +++ b/src/models/set/set_splitting.rs @@ -68,6 +68,9 @@ fn normalize_subsets(universe_size: usize, subsets: &[Vec]) -> (usize, Ve for subset in subsets { let mut remainder = subset.clone(); + // Repeated occurrences represent the same set element. + remainder.sort_unstable(); + remainder.dedup(); while remainder.len() > 3 { let positive_aux = next_element; let negative_aux = next_element + 1; @@ -154,7 +157,7 @@ impl SetSplitting { (universe_size, size2, size3) } - /// Universe size after decomposing all subsets to size 2 or 3. + /// Universe size after deduplicating subsets and decomposing sizes above 3. pub fn normalized_universe_size(&self) -> usize { self.normalized_stats().0 } diff --git a/src/rules/setsplitting_betweenness.rs b/src/rules/setsplitting_betweenness.rs index a62f799d4..7e6ec7963 100644 --- a/src/rules/setsplitting_betweenness.rs +++ b/src/rules/setsplitting_betweenness.rs @@ -1,11 +1,12 @@ //! Reduction from Set Splitting to Betweenness. //! -//! Decompose each subset to size 2 or 3 using complementarity pairs, then +//! Deduplicate each subset and decompose sizes above 3 using complementarity pairs, then //! place a single pole element `p` in the Betweenness instance. A size-2 //! subset `{u, v}` becomes `(u, p, v)`, forcing opposite sides of the pole. //! A size-3 subset `{u, v, w}` becomes `(u, d, v)` and `(d, p, w)` with one //! fresh auxiliary element `d`, which is satisfiable exactly when the three //! elements are not monochromatic with respect to the pole. +//! A singleton becomes two incompatible order constraints, preserving infeasibility. use crate::models::misc::Betweenness; use crate::models::set::SetSplitting; @@ -63,6 +64,14 @@ impl ReduceTo for SetSplitting { for subset in normalized_subsets { match subset.as_slice() { + [u] => { + // A singleton cannot contain both colors. These orders + // cannot both hold for three distinct elements. + let auxiliary = num_elements; + num_elements += 1; + triples.push((*u, pole, auxiliary)); + triples.push((pole, *u, auxiliary)); + } [u, v] => triples.push((*u, pole, *v)), [u, v, w] => { let auxiliary = num_elements; @@ -75,7 +84,7 @@ impl ReduceTo for SetSplitting { SetSplitting, Betweenness, >( - "normalized subset must contain two or three elements" + "normalized subset must contain one, two or three elements", )); } } diff --git a/src/unit_tests/rules/setsplitting_betweenness.rs b/src/unit_tests/rules/setsplitting_betweenness.rs index b43fa7fb1..75a56fb01 100644 --- a/src/unit_tests/rules/setsplitting_betweenness.rs +++ b/src/unit_tests/rules/setsplitting_betweenness.rs @@ -31,6 +31,37 @@ fn test_setsplitting_to_betweenness_closed_loop() { ); } +#[test] +fn test_repeated_elements_preserve_splittability() { + for (subset, feasible) in [ + (vec![0, 0, 1], true), + (vec![1, 0, 1, 0, 1], true), + (vec![0, 0], false), + (vec![1, 1, 1, 1], false), + ] { + let source = SetSplitting::new(2, vec![subset]); + assert_eq!( + BruteForce::new().solve(&source).unwrap().is_some(), + feasible + ); + let reduction = ReduceTo::::reduce_to(&source).unwrap(); + assert_eq!( + BruteForce::new() + .solve(reduction.target_problem()) + .unwrap() + .is_some(), + feasible + ); + if feasible { + assert_satisfaction_round_trip_from_satisfaction_target( + &source, + &reduction, + "repeated elements", + ); + } + } +} + #[test] fn test_setsplitting_to_betweenness_issue_yes_instance_structure() { let source = issue_yes_instance();