1#[derive(Clone, Debug, PartialEq, Eq)]
16pub struct XorEquation {
17 pub vars: Vec<usize>,
19 pub rhs: bool,
21}
22
23impl XorEquation {
24 pub fn new(vars: impl Into<Vec<usize>>, rhs: bool) -> Self {
26 XorEquation {
27 vars: vars.into(),
28 rhs,
29 }
30 }
31}
32
33#[derive(Clone, Debug, PartialEq, Eq)]
35pub enum XorOutcome {
36 Sat(Vec<bool>),
38 Unsat(Vec<usize>),
41}
42
43#[inline]
44fn get(bits: &[u64], i: usize) -> bool {
45 (bits[i / 64] >> (i % 64)) & 1 == 1
46}
47
48#[inline]
49fn flip(bits: &mut [u64], i: usize) {
50 bits[i / 64] ^= 1u64 << (i % 64);
51}
52
53#[inline]
54fn xor_assign(dst: &mut [u64], src: &[u64]) {
55 for (d, s) in dst.iter_mut().zip(src) {
56 *d ^= *s;
57 }
58}
59
60#[inline]
61fn is_zero(bits: &[u64]) -> bool {
62 bits.iter().all(|&w| w == 0)
63}
64
65fn set_indices(bits: &[u64]) -> Vec<usize> {
66 let mut out = Vec::new();
67 for (w, &word) in bits.iter().enumerate() {
68 let mut b = word;
69 while b != 0 {
70 let t = b.trailing_zeros() as usize;
71 out.push(w * 64 + t);
72 b &= b - 1;
73 }
74 }
75 out
76}
77
78#[derive(Clone)]
79struct Row {
80 lhs: Vec<u64>, rhs: bool,
82 prov: Vec<u64>, }
84
85pub fn solve(equations: &[XorEquation], num_vars: usize) -> XorOutcome {
88 let nb = num_vars.div_ceil(64).max(1);
89 let pb = equations.len().div_ceil(64).max(1);
90 let mut rows: Vec<Row> = equations
91 .iter()
92 .enumerate()
93 .map(|(i, eq)| {
94 let mut lhs = vec![0u64; nb];
95 for &v in &eq.vars {
96 if v < num_vars {
97 flip(&mut lhs, v); }
99 }
100 let mut prov = vec![0u64; pb];
101 flip(&mut prov, i);
102 Row {
103 lhs,
104 rhs: eq.rhs,
105 prov,
106 }
107 })
108 .collect();
109
110 let mut pivot_for_col = vec![usize::MAX; num_vars];
111 let mut rank = 0;
112 for c in 0..num_vars {
113 let Some(p) = (rank..rows.len()).find(|&i| get(&rows[i].lhs, c)) else {
114 continue;
115 };
116 rows.swap(rank, p);
117 let pivot = rows[rank].clone();
118 for (i, row) in rows.iter_mut().enumerate() {
119 if i != rank && get(&row.lhs, c) {
120 xor_assign(&mut row.lhs, &pivot.lhs);
121 row.rhs ^= pivot.rhs;
122 xor_assign(&mut row.prov, &pivot.prov);
123 }
124 }
125 pivot_for_col[c] = rank;
126 rank += 1;
127 }
128
129 for row in &rows {
131 if is_zero(&row.lhs) && row.rhs {
132 return XorOutcome::Unsat(set_indices(&row.prov));
133 }
134 }
135
136 let mut assignment = vec![false; num_vars];
139 for c in 0..num_vars {
140 let pr = pivot_for_col[c];
141 if pr != usize::MAX {
142 assignment[c] = rows[pr].rhs;
143 }
144 }
145 XorOutcome::Sat(assignment)
146}
147
148pub fn satisfies(equations: &[XorEquation], assignment: &[bool]) -> bool {
150 equations.iter().all(|eq| {
151 let ones = eq
152 .vars
153 .iter()
154 .filter(|&&v| v < assignment.len() && assignment[v])
155 .count();
156 (ones % 2 == 1) == eq.rhs
157 })
158}
159
160pub fn is_refutation(equations: &[XorEquation], num_vars: usize, refutation: &[usize]) -> bool {
163 if refutation.is_empty() {
164 return false;
165 }
166 let nb = num_vars.div_ceil(64).max(1);
167 let mut lhs = vec![0u64; nb];
168 let mut rhs = false;
169 for &idx in refutation {
170 let Some(eq) = equations.get(idx) else {
171 return false;
172 };
173 for &v in &eq.vars {
174 if v < num_vars {
175 flip(&mut lhs, v);
176 }
177 }
178 rhs ^= eq.rhs;
179 }
180 is_zero(&lhs) && rhs
181}
182
183pub fn refute_via_parity(e: &crate::ProofExpr) -> bool {
195 use std::collections::HashMap;
196 let mut idx: HashMap<String, usize> = HashMap::new();
197 let mut clauses: Vec<Vec<(usize, bool)>> = Vec::new();
198 if !collect_clauses(e, &mut clauses, &mut idx) {
199 return false;
200 }
201 let mut groups: HashMap<Vec<usize>, Vec<Vec<(usize, bool)>>> = HashMap::new();
203 for c in clauses {
204 let mut vars: Vec<usize> = c.iter().map(|&(v, _)| v).collect();
205 vars.sort_unstable();
206 vars.dedup();
207 if vars.len() != c.len() {
208 continue; }
210 groups.entry(vars).or_default().push(c);
211 }
212 let num_vars = idx.len();
213 let mut eqs = Vec::new();
214 for (vars, group) in groups {
215 let k = vars.len();
216 if k == 0 || k > 31 || group.len() != (1usize << (k - 1)) {
217 continue; }
219 let pos: HashMap<usize, usize> = vars.iter().enumerate().map(|(i, &v)| (v, i)).collect();
220 let mut forbidden = std::collections::HashSet::new();
221 let mut parity: Option<u32> = None;
222 let mut clean = true;
223 for c in &group {
224 let mut a = 0u32;
226 for &(v, positive) in c {
227 if !positive {
228 a |= 1 << pos[&v];
229 }
230 }
231 if !forbidden.insert(a) {
232 clean = false; break;
234 }
235 let p = a.count_ones() % 2;
236 match parity {
237 None => parity = Some(p),
238 Some(q) if q != p => {
239 clean = false; break;
241 }
242 _ => {}
243 }
244 }
245 if !clean {
246 continue;
247 }
248 let p = parity.unwrap_or(0);
250 eqs.push(XorEquation::new(vars, p == 0));
251 }
252 if eqs.is_empty() {
253 return false;
254 }
255 matches!(solve(&eqs, num_vars), XorOutcome::Unsat(_))
256}
257
258fn collect_clauses(
262 e: &crate::ProofExpr,
263 out: &mut Vec<Vec<(usize, bool)>>,
264 idx: &mut std::collections::HashMap<String, usize>,
265) -> bool {
266 use crate::ProofExpr;
267 match e {
268 ProofExpr::And(l, r) => collect_clauses(l, out, idx) && collect_clauses(r, out, idx),
269 other => {
270 let mut lits = Vec::new();
271 if collect_literals(other, true, &mut lits, idx) {
272 out.push(lits);
273 true
274 } else {
275 false
276 }
277 }
278 }
279}
280
281fn collect_literals(
282 e: &crate::ProofExpr,
283 positive: bool,
284 out: &mut Vec<(usize, bool)>,
285 idx: &mut std::collections::HashMap<String, usize>,
286) -> bool {
287 use crate::ProofExpr;
288 match e {
289 ProofExpr::Atom(name) => {
290 let n = idx.len();
291 let v = *idx.entry(name.clone()).or_insert(n);
292 out.push((v, positive));
293 true
294 }
295 ProofExpr::Not(inner) => match inner.as_ref() {
296 ProofExpr::Atom(name) => {
297 let n = idx.len();
298 let v = *idx.entry(name.clone()).or_insert(n);
299 out.push((v, !positive));
300 true
301 }
302 _ => false,
303 },
304 ProofExpr::Or(l, r) if positive => {
305 collect_literals(l, positive, out, idx) && collect_literals(r, positive, out, idx)
306 }
307 _ => false,
308 }
309}
310
311#[cfg(test)]
312#[path = "../../logicaffeine_tests/unit/logicaffeine_proof/xorsat/tests.rs"]
313mod tests;