Skip to content

Commit ffd9805

Browse files
authored
Merge pull request #1195 from peda-cos/fix-set-splitting-naesat-1100
Add SetSplitting → NAE-SAT clause-per-subset reduction
2 parents 48a9571 + 40a445d commit ffd9805

4 files changed

Lines changed: 381 additions & 0 deletions

File tree

‎docs/paper/reductions.typ‎

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14486,6 +14486,65 @@ The following reductions to Integer Linear Programming are straightforward formu
1448614486
_Solution extraction._ Direct: $x_i = 1$ iff variable $i$ is true.
1448714487
]
1448814488

14489+
#let ss_nae = load-example("SetSplitting", "NAESatisfiability")
14490+
#let ss_nae_sol = ss_nae.solutions.at(0)
14491+
#reduction-rule("SetSplitting", "NAESatisfiability",
14492+
example: true,
14493+
example-caption: [One NAE clause per subset],
14494+
extra: [
14495+
#pred-commands(
14496+
"pred create --example " + rule-spec(ss_nae) + " -o set-splitting.json",
14497+
"pred reduce set-splitting.json --via route.json -o bundle.json",
14498+
"pred solve bundle.json",
14499+
"pred evaluate set-splitting.json --config " + cli-config(ss_nae_sol.source_config),
14500+
)
14501+
14502+
The canonical source has $#ss_nae.source.instance.universe_size$ elements and
14503+
subsets #{ss_nae.source.instance.subsets.map(s => [${#s.map(str).join(", ")}$]).join(", ")}.
14504+
It produces $#ss_nae.target.instance.num_vars$ Boolean variables and
14505+
$#ss_nae.target.instance.clauses.len()$ NAE clauses.
14506+
Under the stored target witness $(#fmt-values(ss_nae_sol.target_config))$,
14507+
the subsets have truth values
14508+
#{ss_nae.source.instance.subsets.map(s => [$(#fmt-values(s.map(u => ss_nae_sol.target_config.at(u))))$]).join(", ")}.
14509+
Each contains both values, so the same vector
14510+
$(#fmt-values(ss_nae_sol.source_config))$ is a valid splitting.
14511+
],
14512+
)[
14513+
Encode each universe element by a Boolean variable and each subset by its
14514+
not-all-equal constraint @schaefer1978. The target has exactly $n$ variables
14515+
and $m$ clauses for a source universe of size $n$ with $m$ subset vectors.
14516+
][
14517+
_Construction._ For $U = {0, dots, n-1}$, introduce $x_u$ for each $u in U$.
14518+
In each subset vector, retain the first occurrence of each distinct element.
14519+
If the resulting list is $(u_1, dots, u_k)$ with $k >= 2$, emit the NAE clause
14520+
$(x_(u_1), dots, x_(u_k))$. If only one distinct element $u$ remains, emit
14521+
$(x_u, x_u)$. Source construction excludes empty subset vectors; an empty
14522+
family of subsets produces an empty formula. Unused elements retain their
14523+
variables. Positive literal $x_u$ is represented by the signed integer $u+1$;
14524+
universes outside the target literal domain return a construction error.
14525+
14526+
_Correctness._ ($arrow.r.double$) In a valid splitting, every subset contains
14527+
both colors. Removing repeated occurrences preserves these colors, so assigning
14528+
each $x_u$ its element's color satisfies every emitted NAE clause.
14529+
($arrow.l.double$) In any satisfying target assignment, every clause has both
14530+
truth values. Giving each element its variable's value therefore splits every
14531+
source subset. A subset containing only one distinct element is unsplittable;
14532+
its repeated-literal clause is unsatisfiable, preserving the equivalence.
14533+
14534+
_Parameter bounds._ Each clause uses at most $n$ distinct variables, so the
14535+
sum of clause-variable counts is at most $n m$. A clause has at most $2n$
14536+
literal occurrences, including the repeated-literal case. Thus the total
14537+
literal count $L$ is at most $2 n m$, and the sum of within-clause literal-pair
14538+
counts is at most $L(L-1)/2 <= 2 n m (2 n m + 1)$. For an empty universe, the
14539+
accepted family is empty and all these counts are zero. Deduplication by
14540+
scanning previously retained elements takes polynomial time in the source
14541+
representation length.
14542+
14543+
_Solution extraction._ Validate the target witness and return it unchanged.
14544+
The assignment to every original element is preserved, with no auxiliary
14545+
variables to discard.
14546+
]
14547+
1448914548
#reduction-rule("SetSplitting", "ILP")[
1449014549
Each subset must contain at least one element of each color, encoded as a pair of linear inequalities per subset.
1449114550
][

‎src/rules/mod.rs‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,7 @@ pub(crate) mod satisfiability_maximum2satisfiability;
166166
pub(crate) mod satisfiability_naesatisfiability;
167167
pub(crate) mod satisfiability_nontautology;
168168
pub(crate) mod setsplitting_betweenness;
169+
pub(crate) mod setsplitting_naesatisfiability;
169170
mod spinglass_casts;
170171
pub(crate) mod spinglass_maxcut;
171172
pub(crate) mod spinglass_qubo;
@@ -471,6 +472,7 @@ pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::Ru
471472
optimallineararrangement_sequencingtominimizeweightedcompletiontime::canonical_rule_example_specs(),
472473
);
473474
specs.extend(setsplitting_betweenness::canonical_rule_example_specs());
475+
specs.extend(setsplitting_naesatisfiability::canonical_rule_example_specs());
474476
specs.extend(satisfiability_integralflowhomologousarcs::canonical_rule_example_specs());
475477
specs.extend(satisfiability_naesatisfiability::canonical_rule_example_specs());
476478
specs.extend(sat_circuitsat::canonical_rule_example_specs());
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
//! Reduction from Set Splitting to NAE-Satisfiability.
2+
//!
3+
//! Create one Boolean variable for each universe element. Each source subset
4+
//! becomes one NAE clause over the distinct elements it contains. A subset
5+
//! whose entries all equal a single element cannot be split, so emitting the
6+
//! clause `(x_u, x_u)` makes the target an exact mirror of the source.
7+
8+
use crate::models::formula::{CNFClause, NAESatisfiability};
9+
use crate::models::set::SetSplitting;
10+
use crate::reduction;
11+
use crate::rules::traits::{ReduceTo, ReductionResult};
12+
13+
#[derive(Debug, Clone)]
14+
pub struct ReductionSetSplittingToNAESAT {
15+
target: NAESatisfiability,
16+
source_universe_size: usize,
17+
}
18+
19+
impl ReductionResult for ReductionSetSplittingToNAESAT {
20+
type Source = SetSplitting;
21+
type Target = NAESatisfiability;
22+
23+
fn target_problem(&self) -> &NAESatisfiability {
24+
&self.target
25+
}
26+
27+
fn extract_solution(
28+
&self,
29+
target_solution: &<Self::Target as crate::traits::Problem>::Solution,
30+
) -> crate::rules::ExtractionResult<<Self::Source as crate::traits::Problem>::Solution> {
31+
crate::rules::traits::validate_target_witness(
32+
self.target_problem(),
33+
target_solution,
34+
|value| value.0,
35+
"target witness does not satisfy the target problem",
36+
)?;
37+
38+
Ok(target_solution[..self.source_universe_size].to_vec())
39+
}
40+
}
41+
42+
#[crate::aggregate_reduction(identity)]
43+
impl crate::rules::AggregateReductionResult for ReductionSetSplittingToNAESAT {}
44+
45+
#[reduction(transform = {
46+
exact {
47+
num_vars = "universe_size",
48+
num_clauses = "num_subsets",
49+
},
50+
upper_bound {
51+
num_clause_variables = "universe_size * num_subsets",
52+
num_literals = "2 * universe_size * num_subsets",
53+
num_literal_pairs = "2 * universe_size * num_subsets * (2 * universe_size * num_subsets + 1)",
54+
},
55+
})]
56+
impl ReduceTo<NAESatisfiability> for SetSplitting {
57+
type Result = ReductionSetSplittingToNAESAT;
58+
59+
fn reduce_to(&self) -> Result<Self::Result, crate::rules::ReductionError> {
60+
let universe_size = self.universe_size();
61+
crate::rules::sat_helpers::SatVariableAllocator::new(
62+
"SetSplitting -> NAESatisfiability",
63+
universe_size,
64+
)
65+
.map_err(<Self as ReduceTo<NAESatisfiability>>::target_construction)?;
66+
let mut clauses = Vec::with_capacity(self.num_subsets());
67+
68+
for subset in self.subsets() {
69+
let mut distinct: Vec<i64> = Vec::new();
70+
for &element in subset {
71+
// SetSplitting validates element < universe_size; the allocator
72+
// above checks that every resulting SAT variable fits i64.
73+
let literal = i64::try_from(element + 1).expect("validated SAT variable");
74+
if !distinct.contains(&literal) {
75+
distinct.push(literal);
76+
}
77+
}
78+
// Source construction rejects empty subsets, but permits repeated
79+
// occurrences of a single element. Such a subset is unsplittable.
80+
if distinct.len() == 1 {
81+
distinct.push(distinct[0]);
82+
}
83+
clauses.push(CNFClause::new(distinct));
84+
}
85+
let target = NAESatisfiability::try_new(universe_size, clauses)
86+
.map_err(<Self as ReduceTo<NAESatisfiability>>::target_construction)?;
87+
Ok(ReductionSetSplittingToNAESAT {
88+
target,
89+
source_universe_size: universe_size,
90+
})
91+
}
92+
}
93+
94+
#[cfg(feature = "example-db")]
95+
pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::RuleExampleSpec> {
96+
use crate::export::SolutionPair;
97+
98+
vec![crate::example_db::specs::RuleExampleSpec {
99+
id: "setsplitting_to_naesatisfiability",
100+
build: || {
101+
crate::example_db::specs::rule_example_with_witness::<_, NAESatisfiability>(
102+
SetSplitting::new(4, vec![vec![0, 1], vec![1, 2, 3], vec![0, 2, 3]]),
103+
SolutionPair {
104+
source_config: serde_json::json!(vec![true, false, true, false]),
105+
target_config: serde_json::json!(vec![true, false, true, false]),
106+
},
107+
)
108+
},
109+
}]
110+
}
111+
112+
#[cfg(test)]
113+
#[path = "../unit_tests/rules/setsplitting_naesatisfiability.rs"]
114+
mod tests;

0 commit comments

Comments
 (0)