1use crate::eval::{VarMap, truth_table};
20use trit_core::algebra::Algebra;
21use trit_core::ast::Expr;
22use trit_core::value::Ternary;
23
24#[derive(Debug, Clone, Copy, PartialEq, Eq)]
26pub struct SimplifyOptions {
27 pub fold_constants: bool,
29 pub max_vars_for_constant_fold: usize,
31 pub expand_implies: bool,
33}
34
35impl Default for SimplifyOptions {
36 fn default() -> Self {
37 Self {
38 fold_constants: true,
39 max_vars_for_constant_fold: 8,
40 expand_implies: false,
41 }
42 }
43}
44
45pub fn simplify(expr: &Expr, alg: Algebra) -> Expr {
47 simplify_with(expr, alg, SimplifyOptions::default())
48}
49
50pub fn simplify_with(expr: &Expr, alg: Algebra, opts: SimplifyOptions) -> Expr {
52 let step1 = rewrite(expr, alg, opts);
53 if !opts.fold_constants {
54 return step1;
55 }
56 let var_count = step1.variables().len();
57 if var_count > opts.max_vars_for_constant_fold {
58 return step1;
59 }
60 match constant_value(&step1, alg) {
61 Some(v) => Expr::Const(v),
62 None => step1,
63 }
64}
65
66pub fn constant_value(expr: &Expr, alg: Algebra) -> Option<Ternary> {
68 if expr.variables().is_empty() {
69 let asg = crate::eval::Assignment::new();
70 return Some(crate::eval::eval(expr, &asg, alg));
71 }
72 let tt = truth_table(expr, alg);
73 let first = *tt.values.first()?;
74 if tt.values.iter().all(|v| *v == first) {
75 Some(first)
76 } else {
77 None
78 }
79}
80
81fn rewrite(expr: &Expr, alg: Algebra, opts: SimplifyOptions) -> Expr {
83 match expr {
84 Expr::Const(_) | Expr::Var(_) => expr.clone(),
85 Expr::Not(a) => {
86 let inner = rewrite(a, alg, opts);
87 if let Expr::Not(x) = &inner {
89 return (**x).clone();
90 }
91 if let Expr::Const(v) = inner {
93 return Expr::Const(alg.not(v));
94 }
95 Expr::logical_not(inner)
96 }
97 Expr::And(a, b) => {
98 let x = rewrite(a, alg, opts);
99 let y = rewrite(b, alg, opts);
100 rewrite_and(x, y, alg)
101 }
102 Expr::Or(a, b) => {
103 let x = rewrite(a, alg, opts);
104 let y = rewrite(b, alg, opts);
105 rewrite_or(x, y, alg)
106 }
107 Expr::Xor(a, b) => {
108 let x = rewrite(a, alg, opts);
109 let y = rewrite(b, alg, opts);
110 rewrite_xor(x, y, alg)
111 }
112 Expr::Implies(a, b) => {
113 let x = rewrite(a, alg, opts);
114 let y = rewrite(b, alg, opts);
115 if opts.expand_implies && alg == Algebra::Kleene {
116 return rewrite_or(Expr::logical_not(x), y, alg);
118 }
119 rewrite_implies(x, y, alg)
120 }
121 Expr::Equiv(a, b) => {
122 let x = rewrite(a, alg, opts);
123 let y = rewrite(b, alg, opts);
124 rewrite_equiv(x, y, alg)
125 }
126 }
127}
128
129fn ordered(a: Expr, b: Expr) -> (Expr, Expr) {
131 let ka = expr_key(&a);
132 let kb = expr_key(&b);
133 if kb < ka { (b, a) } else { (a, b) }
134}
135
136fn expr_key(e: &Expr) -> String {
138 e.to_infix()
139}
140
141fn rewrite_and(x: Expr, y: Expr, alg: Algebra) -> Expr {
143 if let (Expr::Const(a), Expr::Const(b)) = (&x, &y) {
145 return Expr::Const(alg.and(*a, *b));
146 }
147 if alg.is_poisonous() {
149 if x.is_unknown_const() || y.is_unknown_const() {
150 return Expr::Const(Ternary::Unknown);
151 }
152 if let Expr::Const(Ternary::True) = &x {
153 return y;
154 }
155 if let Expr::Const(Ternary::True) = &y {
156 return x;
157 }
158 let (a, b) = ordered(x, y);
160 return Expr::and(a, b);
161 }
162 if let Expr::Const(Ternary::False) = &x {
164 return Expr::Const(Ternary::False);
165 }
166 if let Expr::Const(Ternary::False) = &y {
167 return Expr::Const(Ternary::False);
168 }
169 if let Expr::Const(Ternary::True) = &x {
170 return y;
171 }
172 if let Expr::Const(Ternary::True) = &y {
173 return x;
174 }
175 if x == y {
177 return x;
178 }
179 if let Some(absorbed) = absorb(&x, &y) {
181 return absorbed;
182 }
183 if let Some(absorbed) = absorb(&y, &x) {
184 return absorbed;
185 }
186 let (a, b) = ordered(x, y);
189 Expr::and(a, b)
190}
191
192fn absorb(x: &Expr, y: &Expr) -> Option<Expr> {
194 if let Expr::Or(p, q) = y
195 && (**p == *x || **q == *x)
196 {
197 return Some(x.clone());
198 }
199 None
200}
201
202fn rewrite_or(x: Expr, y: Expr, alg: Algebra) -> Expr {
204 if let (Expr::Const(a), Expr::Const(b)) = (&x, &y) {
205 return Expr::Const(alg.or(*a, *b));
206 }
207 if alg.is_poisonous() {
208 if x.is_unknown_const() || y.is_unknown_const() {
209 return Expr::Const(Ternary::Unknown);
210 }
211 if let Expr::Const(Ternary::False) = &x {
212 return y;
213 }
214 if let Expr::Const(Ternary::False) = &y {
215 return x;
216 }
217 let (a, b) = ordered(x, y);
219 return Expr::or(a, b);
220 }
221 if let Expr::Const(Ternary::True) = &x {
222 return Expr::Const(Ternary::True);
223 }
224 if let Expr::Const(Ternary::True) = &y {
225 return Expr::Const(Ternary::True);
226 }
227 if let Expr::Const(Ternary::False) = &x {
228 return y;
229 }
230 if let Expr::Const(Ternary::False) = &y {
231 return x;
232 }
233 if x == y {
234 return x;
235 }
236 if let Some(absorbed) = absorb(&x, &y) {
237 return absorbed;
238 }
239 if let Some(absorbed) = absorb(&y, &x) {
240 return absorbed;
241 }
242 let (a, b) = ordered(x, y);
243 Expr::or(a, b)
244}
245
246fn rewrite_xor(x: Expr, y: Expr, alg: Algebra) -> Expr {
248 if let (Expr::Const(a), Expr::Const(b)) = (&x, &y) {
249 return Expr::Const(alg.strong_add(*a, *b));
250 }
251 if alg != Algebra::Lukasiewicz && (x.is_unknown_const() || y.is_unknown_const()) {
253 return Expr::Const(Ternary::Unknown);
254 }
255 if alg == Algebra::Lukasiewicz && x.is_unknown_const() && y.is_unknown_const() {
257 return Expr::Const(Ternary::True);
258 }
259 if x == y && alg == Algebra::Kleene {
260 return Expr::Const(Ternary::Unknown);
261 }
262 let (a, b) = ordered(x, y);
263 Expr::xor(a, b)
264}
265
266fn rewrite_implies(x: Expr, y: Expr, alg: Algebra) -> Expr {
268 if let (Expr::Const(a), Expr::Const(b)) = (&x, &y) {
269 return Expr::Const(alg.implies(*a, *b));
270 }
271 if alg != Algebra::Bochvar
273 && let Expr::Const(Ternary::False) = &x
274 {
275 return Expr::Const(Ternary::True);
276 }
277 Expr::implies(x, y)
278}
279
280fn rewrite_equiv(x: Expr, y: Expr, alg: Algebra) -> Expr {
282 if let (Expr::Const(a), Expr::Const(b)) = (&x, &y) {
283 return Expr::Const(alg.equiv(*a, *b));
284 }
285 if x == y {
286 return Expr::Const(Ternary::True);
287 }
288 Expr::equiv(x, y)
289}
290
291pub fn semantic_fingerprint(expr: &Expr, alg: Algebra) -> u64 {
295 let vars = expr.variables();
296 let tt = truth_table(expr, alg);
297 let mut hash = 1469598103934665603u64;
298 for v in &vars {
299 for b in v.as_bytes() {
300 hash ^= u64::from(*b);
301 hash = hash.wrapping_mul(1099511628211);
302 }
303 hash ^= 0xff;
304 hash = hash.wrapping_mul(1099511628211);
305 }
306 for val in &tt.values {
307 hash ^= u64::from(val.as_char() as u8);
308 hash = hash.wrapping_mul(1099511628211);
309 }
310 hash
311}
312
313pub fn semantically_equal(a: &Expr, b: Expr, alg: Algebra) -> bool {
315 let mut va = a.variables();
316 for v in b.variables() {
317 if !va.contains(&v) {
318 va.push(v);
319 }
320 }
321 let mut vb = b.variables();
322 for v in va.iter() {
323 if !vb.contains(v) {
324 vb.push(v.clone());
325 }
326 }
327 let expr_a = rename_to(a, &va);
329 let expr_b = rename_to(&b, &vb);
330 semantic_fingerprint(&expr_a, alg) == semantic_fingerprint(&expr_b, alg)
331}
332
333fn rename_to(expr: &Expr, vars: &[String]) -> Expr {
338 let _ = vars;
339 expr.clone()
340}
341
342#[doc(hidden)]
344pub fn unused_var_map() -> VarMap {
345 VarMap::new()
346}
347
348#[cfg(test)]
349mod tests {
350 use super::*;
351 use trit_core::parser::Parser;
352
353 fn simp(src: &str, alg: Algebra) -> String {
354 let e = Parser::parse(src).unwrap();
355 simplify(&e, alg).to_infix()
356 }
357
358 #[test]
359 fn folds_constants() {
360 assert_eq!(simp("1 & 0", Algebra::Kleene), "0");
361 assert_eq!(simp("1 | 0", Algebra::Kleene), "1");
362 assert_eq!(simp("!1", Algebra::Kleene), "0");
363 }
364
365 #[test]
366 fn folds_semantic_constants() {
367 assert_eq!(simp("x | 1", Algebra::Kleene), "1");
371 assert_eq!(simp("x & 0", Algebra::Kleene), "0");
372 assert_eq!(simp("1 & 0 | 1", Algebra::Kleene), "1");
374 let e = Parser::parse("a & 1 | b & 0").unwrap();
376 assert_eq!(simplify(&e, Algebra::Kleene), Expr::Var("a".into()));
377 }
378
379 #[test]
380 fn applies_idempotence_and_identity() {
381 assert_eq!(simp("a & a", Algebra::Kleene), "a");
382 assert_eq!(simp("a | a", Algebra::Kleene), "a");
383 assert_eq!(simp("a & 1", Algebra::Kleene), "a");
384 assert_eq!(simp("a | 0", Algebra::Kleene), "a");
385 }
386
387 #[test]
388 fn bochvar_poison_differs_from_kleene() {
389 assert_eq!(simp("0 & x", Algebra::Bochvar), "X");
391 assert_eq!(simp("0 & x", Algebra::Kleene), "0");
392 }
393
394 #[test]
395 fn simplification_preserves_semantics() {
396 for src in [
397 "a & b | a",
398 "!(a & b)",
399 "(a => b) & (b => a)",
400 "a ^ b",
401 "a <=> b",
402 "a & 1 | b & 0",
403 ] {
404 let e = Parser::parse(src).unwrap();
405 let s = simplify(&e, Algebra::Kleene);
406 let r = crate::eval::equivalent(&e, &s, Algebra::Kleene);
407 assert!(r.equivalent, "simplify changed semantics: {src} -> {s}");
408 }
409 }
410
411 #[test]
412 fn simplification_preserves_semantics_in_bochvar() {
413 for src in ["a & b | a", "0 & x", "a | 1", "a & x"] {
414 let e = Parser::parse(src).unwrap();
415 let s = simplify(&e, Algebra::Bochvar);
416 let r = crate::eval::equivalent(&e, &s, Algebra::Bochvar);
417 assert!(
418 r.equivalent,
419 "bochvar simplify changed semantics: {src} -> {s}"
420 );
421 }
422 }
423}