1use crate::models::formula::{
4 Assignment, BooleanExpr, BooleanOp, CNFClause, CircuitSAT, Satisfiability,
5};
6use crate::reduction;
7use crate::rules::traits::{ReduceTo, ReductionResult};
8use std::collections::HashMap;
9
10#[derive(Debug, Clone, PartialEq, Eq)]
11enum NormalizedExpr {
12 Var(String),
13 Const(bool),
14 Not(Box<NormalizedExpr>),
15 And(Box<NormalizedExpr>, Box<NormalizedExpr>),
16 Or(Box<NormalizedExpr>, Box<NormalizedExpr>),
17 Xor(Box<NormalizedExpr>, Box<NormalizedExpr>),
18}
19
20#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21enum EncodedTerm {
22 Const(bool),
23 Var(i32),
24}
25
26#[derive(Debug, Clone)]
27struct TseitinEncoding {
28 num_vars: usize,
29 clauses: Vec<CNFClause>,
30}
31
32#[derive(Debug)]
33struct TseitinEncoder {
34 source_var_ids: HashMap<String, i32>,
35 clauses: Vec<CNFClause>,
36 next_var: i32,
37}
38
39impl TseitinEncoder {
40 fn new(source: &CircuitSAT) -> Self {
41 let source_var_ids = source
42 .variable_names()
43 .iter()
44 .enumerate()
45 .map(|(index, name)| (name.clone(), index as i32 + 1))
46 .collect();
47 Self {
48 source_var_ids,
49 clauses: Vec::new(),
50 next_var: source.num_variables() as i32 + 1,
51 }
52 }
53
54 fn encode_problem(mut self, source: &CircuitSAT) -> TseitinEncoding {
55 for assignment in &source.circuit().assignments {
56 self.encode_assignment(assignment);
57 }
58
59 TseitinEncoding {
60 num_vars: (self.next_var - 1) as usize,
61 clauses: self.clauses,
62 }
63 }
64
65 fn encode_assignment(&mut self, assignment: &Assignment) {
66 if assignment.outputs.is_empty() {
67 return;
68 }
69
70 let root = self.encode_expr(&normalize_expr(&assignment.expr));
71 match root {
72 EncodedTerm::Const(value) => {
73 let literal_sign = if value { 1 } else { -1 };
74 for output in &assignment.outputs {
75 let output_var = self.source_var(output);
76 self.push_clause(vec![literal_sign * output_var]);
77 }
78 }
79 EncodedTerm::Var(root_var) => {
80 for output in &assignment.outputs {
81 let output_var = self.source_var(output);
82 self.push_equivalence(output_var, root_var);
83 }
84 }
85 }
86 }
87
88 fn encode_expr(&mut self, expr: &NormalizedExpr) -> EncodedTerm {
89 match expr {
90 NormalizedExpr::Var(name) => EncodedTerm::Var(self.source_var(name)),
91 NormalizedExpr::Const(value) => EncodedTerm::Const(*value),
92 NormalizedExpr::Not(inner) => match self.encode_expr(inner) {
93 EncodedTerm::Const(value) => EncodedTerm::Const(!value),
94 EncodedTerm::Var(child_var) => {
95 let gate_var = self.allocate_auxiliary_var();
96 self.push_clause(vec![-gate_var, -child_var]);
97 self.push_clause(vec![gate_var, child_var]);
98 EncodedTerm::Var(gate_var)
99 }
100 },
101 NormalizedExpr::And(left, right) => {
102 let left_term = self.encode_expr(left);
103 let right_term = self.encode_expr(right);
104 let left_var = self.expect_var(left_term, "AND left input");
105 let right_var = self.expect_var(right_term, "AND right input");
106 let gate_var = self.allocate_auxiliary_var();
107 self.push_clause(vec![-gate_var, left_var]);
108 self.push_clause(vec![-gate_var, right_var]);
109 self.push_clause(vec![gate_var, -left_var, -right_var]);
110 EncodedTerm::Var(gate_var)
111 }
112 NormalizedExpr::Or(left, right) => {
113 let left_term = self.encode_expr(left);
114 let right_term = self.encode_expr(right);
115 let left_var = self.expect_var(left_term, "OR left input");
116 let right_var = self.expect_var(right_term, "OR right input");
117 let gate_var = self.allocate_auxiliary_var();
118 self.push_clause(vec![gate_var, -left_var]);
119 self.push_clause(vec![gate_var, -right_var]);
120 self.push_clause(vec![-gate_var, left_var, right_var]);
121 EncodedTerm::Var(gate_var)
122 }
123 NormalizedExpr::Xor(left, right) => {
124 let left_term = self.encode_expr(left);
125 let right_term = self.encode_expr(right);
126 let left_var = self.expect_var(left_term, "XOR left input");
127 let right_var = self.expect_var(right_term, "XOR right input");
128 let gate_var = self.allocate_auxiliary_var();
129 self.push_clause(vec![-left_var, -right_var, -gate_var]);
130 self.push_clause(vec![left_var, right_var, -gate_var]);
131 self.push_clause(vec![left_var, -right_var, gate_var]);
132 self.push_clause(vec![-left_var, right_var, gate_var]);
133 EncodedTerm::Var(gate_var)
134 }
135 }
136 }
137
138 fn expect_var(&self, term: EncodedTerm, context: &str) -> i32 {
139 match term {
140 EncodedTerm::Var(var) => var,
141 EncodedTerm::Const(_) => {
142 panic!("normalized Tseitin encoding produced a constant for {context}")
143 }
144 }
145 }
146
147 fn source_var(&self, name: &str) -> i32 {
148 *self
149 .source_var_ids
150 .get(name)
151 .unwrap_or_else(|| panic!("CircuitSAT variable {name:?} missing from source ordering"))
152 }
153
154 fn allocate_auxiliary_var(&mut self) -> i32 {
155 let var = self.next_var;
156 self.next_var += 1;
157 var
158 }
159
160 fn push_equivalence(&mut self, left: i32, right: i32) {
161 self.push_clause(vec![-left, right]);
162 self.push_clause(vec![left, -right]);
163 }
164
165 fn push_clause(&mut self, literals: Vec<i32>) {
166 self.clauses.push(CNFClause::new(literals));
167 }
168}
169
170fn make_and(left: NormalizedExpr, right: NormalizedExpr) -> NormalizedExpr {
171 NormalizedExpr::And(Box::new(left), Box::new(right))
172}
173
174fn make_or(left: NormalizedExpr, right: NormalizedExpr) -> NormalizedExpr {
175 NormalizedExpr::Or(Box::new(left), Box::new(right))
176}
177
178fn make_xor(left: NormalizedExpr, right: NormalizedExpr) -> NormalizedExpr {
179 NormalizedExpr::Xor(Box::new(left), Box::new(right))
180}
181
182fn build_balanced_expr(
183 mut items: Vec<NormalizedExpr>,
184 combine: fn(NormalizedExpr, NormalizedExpr) -> NormalizedExpr,
185) -> NormalizedExpr {
186 if items.len() == 1 {
187 return items.pop().expect("single item exists");
188 }
189
190 let right = items.split_off(items.len() / 2);
191 combine(
192 build_balanced_expr(items, combine),
193 build_balanced_expr(right, combine),
194 )
195}
196
197fn normalize_expr(expr: &BooleanExpr) -> NormalizedExpr {
198 match &expr.op {
199 BooleanOp::Var(name) => NormalizedExpr::Var(name.clone()),
200 BooleanOp::Const(value) => NormalizedExpr::Const(*value),
201 BooleanOp::Not(inner) => match normalize_expr(inner) {
202 NormalizedExpr::Const(value) => NormalizedExpr::Const(!value),
203 NormalizedExpr::Not(grandchild) => *grandchild,
204 normalized => NormalizedExpr::Not(Box::new(normalized)),
205 },
206 BooleanOp::And(args) => normalize_nary_gate(args, false, true, make_and),
207 BooleanOp::Or(args) => normalize_nary_gate(args, true, false, make_or),
208 BooleanOp::Xor(args) => {
209 let mut parity = false;
210 let mut normalized_args = Vec::new();
211
212 for arg in args {
213 match normalize_expr(arg) {
214 NormalizedExpr::Const(value) => parity ^= value,
215 normalized => normalized_args.push(normalized),
216 }
217 }
218
219 match normalized_args.len() {
220 0 => NormalizedExpr::Const(parity),
221 1 => {
222 let base = normalized_args.pop().expect("single item exists");
223 if parity {
224 NormalizedExpr::Not(Box::new(base))
225 } else {
226 base
227 }
228 }
229 _ => {
230 let base = build_balanced_expr(normalized_args, make_xor);
231 if parity {
232 NormalizedExpr::Not(Box::new(base))
233 } else {
234 base
235 }
236 }
237 }
238 }
239 }
240}
241
242fn normalize_nary_gate(
243 args: &[BooleanExpr],
244 absorbing_value: bool,
245 identity_value: bool,
246 combine: fn(NormalizedExpr, NormalizedExpr) -> NormalizedExpr,
247) -> NormalizedExpr {
248 let mut normalized_args = Vec::new();
249
250 for arg in args {
251 match normalize_expr(arg) {
252 NormalizedExpr::Const(value) if value == absorbing_value => {
253 return NormalizedExpr::Const(absorbing_value)
254 }
255 NormalizedExpr::Const(value) if value == identity_value => {}
256 normalized => normalized_args.push(normalized),
257 }
258 }
259
260 match normalized_args.len() {
261 0 => NormalizedExpr::Const(identity_value),
262 1 => normalized_args.pop().expect("single item exists"),
263 _ => build_balanced_expr(normalized_args, combine),
264 }
265}
266
267fn build_tseitin_encoding(source: &CircuitSAT) -> TseitinEncoding {
268 TseitinEncoder::new(source).encode_problem(source)
269}
270
271impl CircuitSAT {
272 pub fn tseitin_num_vars(&self) -> usize {
273 build_tseitin_encoding(self).num_vars
274 }
275
276 pub fn tseitin_num_clauses(&self) -> usize {
277 build_tseitin_encoding(self).clauses.len()
278 }
279}
280
281#[derive(Debug, Clone)]
283pub struct ReductionCircuitSATToSAT {
284 target: Satisfiability,
285 source_var_count: usize,
286}
287
288impl ReductionResult for ReductionCircuitSATToSAT {
289 type Source = CircuitSAT;
290 type Target = Satisfiability;
291
292 fn target_problem(&self) -> &Self::Target {
293 &self.target
294 }
295
296 fn extract_solution(&self, target_solution: &[usize]) -> Vec<usize> {
297 target_solution
298 .iter()
299 .take(self.source_var_count)
300 .copied()
301 .collect()
302 }
303}
304
305#[reduction(
306 overhead = {
307 num_vars = "tseitin_num_vars",
308 num_clauses = "tseitin_num_clauses",
309 }
310)]
311impl ReduceTo<Satisfiability> for CircuitSAT {
312 type Result = ReductionCircuitSATToSAT;
313
314 fn reduce_to(&self) -> Self::Result {
315 let encoding = build_tseitin_encoding(self);
316 ReductionCircuitSATToSAT {
317 target: Satisfiability::new(encoding.num_vars, encoding.clauses),
318 source_var_count: self.num_variables(),
319 }
320 }
321}
322
323#[cfg(any(test, feature = "example-db"))]
324fn issue_example_source() -> CircuitSAT {
325 use crate::models::formula::Circuit;
326
327 CircuitSAT::new(Circuit::new(vec![Assignment::new(
328 vec!["r".to_string()],
329 BooleanExpr::or(vec![
330 BooleanExpr::and(vec![BooleanExpr::var("x1"), BooleanExpr::var("x2")]),
331 BooleanExpr::and(vec![
332 BooleanExpr::not(BooleanExpr::var("x3")),
333 BooleanExpr::var("x4"),
334 ]),
335 ]),
336 )]))
337}
338
339#[cfg(feature = "example-db")]
340pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::RuleExampleSpec> {
341 use crate::export::SolutionPair;
342 use crate::solvers::BruteForce;
343
344 vec![crate::example_db::specs::RuleExampleSpec {
345 id: "circuitsat_to_satisfiability",
346 build: || {
347 let source = issue_example_source();
348 let source_config = vec![1, 1, 1, 0, 1];
349 let reduction = ReduceTo::<Satisfiability>::reduce_to(&source);
350 let target_config = BruteForce::new()
351 .find_all_witnesses(reduction.target_problem())
352 .into_iter()
353 .find(|candidate| reduction.extract_solution(candidate) == source_config)
354 .expect("canonical CircuitSAT -> Satisfiability example must be satisfiable");
355
356 crate::example_db::specs::assemble_rule_example(
357 &source,
358 reduction.target_problem(),
359 vec![SolutionPair {
360 source_config,
361 target_config,
362 }],
363 )
364 },
365 }]
366}
367
368#[cfg(test)]
369#[path = "../unit_tests/rules/circuit_sat.rs"]
370mod tests;