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 {
50 simplify_with(expr, alg, SimplifyOptions::default())
51}
52
53pub fn simplify_with(expr: &Expr, alg: Algebra, opts: SimplifyOptions) -> Expr {
55 let step1 = rewrite(expr, alg, opts);
56 if !opts.fold_constants {
57 return step1;
58 }
59 let var_count = step1.variables().len();
60 if var_count > opts.max_vars_for_constant_fold {
61 return step1;
62 }
63 match constant_value(&step1, alg) {
64 Some(v) => Expr::Const(v),
65 None => step1,
66 }
67}
68
69pub fn constant_value(expr: &Expr, alg: Algebra) -> Option<Ternary> {
73 if expr.variables().is_empty() {
74 let asg = crate::eval::Assignment::new();
75 return Some(crate::eval::eval(expr, &asg, alg));
76 }
77 let tt = truth_table(expr, alg).ok()?;
78 let first = *tt.values.first()?;
79 if tt.values.iter().all(|v| *v == first) {
80 Some(first)
81 } else {
82 None
83 }
84}
85
86fn rewrite(expr: &Expr, alg: Algebra, opts: SimplifyOptions) -> Expr {
88 match expr {
89 Expr::Const(_) | Expr::Var(_) => expr.clone(),
90 Expr::Not(a) => {
91 let inner = rewrite(a, alg, opts);
92 if let Expr::Not(x) = &inner {
94 return (**x).clone();
95 }
96 if let Expr::Const(v) = inner {
98 return Expr::Const(alg.not(v));
99 }
100 Expr::logical_not(inner)
101 }
102 Expr::And(a, b) => {
103 let x = rewrite(a, alg, opts);
104 let y = rewrite(b, alg, opts);
105 rewrite_and(x, y, alg)
106 }
107 Expr::Or(a, b) => {
108 let x = rewrite(a, alg, opts);
109 let y = rewrite(b, alg, opts);
110 rewrite_or(x, y, alg)
111 }
112 Expr::Xor(a, b) => {
113 let x = rewrite(a, alg, opts);
114 let y = rewrite(b, alg, opts);
115 rewrite_xor(x, y, alg)
116 }
117 Expr::Implies(a, b) => {
118 let x = rewrite(a, alg, opts);
119 let y = rewrite(b, alg, opts);
120 if opts.expand_implies && alg == Algebra::Kleene {
121 return rewrite_or(Expr::logical_not(x), y, alg);
123 }
124 rewrite_implies(x, y, alg)
125 }
126 Expr::Equiv(a, b) => {
127 let x = rewrite(a, alg, opts);
128 let y = rewrite(b, alg, opts);
129 rewrite_equiv(x, y, alg)
130 }
131 }
132}
133
134fn ordered(a: Expr, b: Expr) -> (Expr, Expr) {
136 let ka = expr_key(&a);
137 let kb = expr_key(&b);
138 if kb < ka { (b, a) } else { (a, b) }
139}
140
141fn expr_key(e: &Expr) -> String {
143 e.to_infix()
144}
145
146fn rewrite_and(x: Expr, y: Expr, alg: Algebra) -> Expr {
148 if let (Expr::Const(a), Expr::Const(b)) = (&x, &y) {
150 return Expr::Const(alg.and(*a, *b));
151 }
152 if alg.is_poisonous() {
154 if x.is_unknown_const() || y.is_unknown_const() {
155 return Expr::Const(Ternary::Unknown);
156 }
157 if let Expr::Const(Ternary::True) = &x {
158 return y;
159 }
160 if let Expr::Const(Ternary::True) = &y {
161 return x;
162 }
163 let (a, b) = ordered(x, y);
165 return Expr::and(a, b);
166 }
167 if let Expr::Const(Ternary::False) = &x {
169 return Expr::Const(Ternary::False);
170 }
171 if let Expr::Const(Ternary::False) = &y {
172 return Expr::Const(Ternary::False);
173 }
174 if let Expr::Const(Ternary::True) = &x {
175 return y;
176 }
177 if let Expr::Const(Ternary::True) = &y {
178 return x;
179 }
180 if x == y {
182 return x;
183 }
184 if let Some(absorbed) = absorb(&x, &y) {
186 return absorbed;
187 }
188 if let Some(absorbed) = absorb(&y, &x) {
189 return absorbed;
190 }
191 let (a, b) = ordered(x, y);
194 Expr::and(a, b)
195}
196
197fn absorb(x: &Expr, y: &Expr) -> Option<Expr> {
199 if let Expr::Or(p, q) = y
200 && (**p == *x || **q == *x)
201 {
202 return Some(x.clone());
203 }
204 None
205}
206
207fn absorb_or(x: &Expr, y: &Expr) -> Option<Expr> {
209 if let Expr::And(p, q) = y
210 && (**p == *x || **q == *x)
211 {
212 return Some(x.clone());
213 }
214 None
215}
216
217fn rewrite_or(x: Expr, y: Expr, alg: Algebra) -> Expr {
219 if let (Expr::Const(a), Expr::Const(b)) = (&x, &y) {
220 return Expr::Const(alg.or(*a, *b));
221 }
222 if alg.is_poisonous() {
223 if x.is_unknown_const() || y.is_unknown_const() {
224 return Expr::Const(Ternary::Unknown);
225 }
226 if let Expr::Const(Ternary::False) = &x {
227 return y;
228 }
229 if let Expr::Const(Ternary::False) = &y {
230 return x;
231 }
232 let (a, b) = ordered(x, y);
234 return Expr::or(a, b);
235 }
236 if let Expr::Const(Ternary::True) = &x {
237 return Expr::Const(Ternary::True);
238 }
239 if let Expr::Const(Ternary::True) = &y {
240 return Expr::Const(Ternary::True);
241 }
242 if let Expr::Const(Ternary::False) = &x {
243 return y;
244 }
245 if let Expr::Const(Ternary::False) = &y {
246 return x;
247 }
248 if x == y {
249 return x;
250 }
251 if let Some(absorbed) = absorb_or(&x, &y) {
252 return absorbed;
253 }
254 if let Some(absorbed) = absorb_or(&y, &x) {
255 return absorbed;
256 }
257 let (a, b) = ordered(x, y);
258 Expr::or(a, b)
259}
260
261fn rewrite_xor(x: Expr, y: Expr, alg: Algebra) -> Expr {
263 if let (Expr::Const(a), Expr::Const(b)) = (&x, &y) {
264 return Expr::Const(alg.strong_add(*a, *b));
265 }
266 if alg != Algebra::Lukasiewicz && (x.is_unknown_const() || y.is_unknown_const()) {
268 return Expr::Const(Ternary::Unknown);
269 }
270 if alg == Algebra::Lukasiewicz && x.is_unknown_const() && y.is_unknown_const() {
272 return Expr::Const(Ternary::True);
273 }
274 if x == y && alg != Algebra::Lukasiewicz {
276 return x;
277 }
278 let (a, b) = ordered(x, y);
279 Expr::xor(a, b)
280}
281
282fn rewrite_implies(x: Expr, y: Expr, alg: Algebra) -> Expr {
284 if let (Expr::Const(a), Expr::Const(b)) = (&x, &y) {
285 return Expr::Const(alg.implies(*a, *b));
286 }
287 if alg != Algebra::Bochvar
289 && let Expr::Const(Ternary::False) = &x
290 {
291 return Expr::Const(Ternary::True);
292 }
293 Expr::implies(x, y)
294}
295
296fn rewrite_equiv(x: Expr, y: Expr, alg: Algebra) -> Expr {
298 if let (Expr::Const(a), Expr::Const(b)) = (&x, &y) {
299 return Expr::Const(alg.equiv(*a, *b));
300 }
301 if x == y && alg == Algebra::Lukasiewicz {
303 return Expr::Const(Ternary::True);
304 }
305 Expr::equiv(x, y)
306}
307
308pub fn semantic_fingerprint(expr: &Expr, alg: Algebra) -> u64 {
313 let vars = expr.variables();
314 let Ok(tt) = truth_table(expr, alg) else {
315 return 0;
317 };
318 let mut hash = 1469598103934665603u64;
319 for v in &vars {
320 for b in v.as_bytes() {
321 hash ^= u64::from(*b);
322 hash = hash.wrapping_mul(1099511628211);
323 }
324 hash ^= 0xff;
325 hash = hash.wrapping_mul(1099511628211);
326 }
327 for val in &tt.values {
328 hash ^= u64::from(val.as_char() as u8);
329 hash = hash.wrapping_mul(1099511628211);
330 }
331 hash
332}
333
334pub fn semantically_equal(
336 a: &Expr,
337 b: Expr,
338 alg: Algebra,
339) -> Result<bool, trit_core::limits::LimitError> {
340 Ok(crate::eval::equivalent(a, &b, alg)?.equivalent)
341}
342
343#[doc(hidden)]
345pub fn unused_var_map() -> VarMap {
346 VarMap::new()
347}
348
349#[cfg(test)]
350mod tests {
351 use super::*;
352 use trit_core::parser::Parser;
353
354 fn simp(src: &str, alg: Algebra) -> String {
355 let e = Parser::parse(src).unwrap();
356 simplify(&e, alg).to_infix()
357 }
358
359 #[test]
360 fn folds_constants() {
361 assert_eq!(simp("1 & 0", Algebra::Kleene), "0");
362 assert_eq!(simp("1 | 0", Algebra::Kleene), "1");
363 assert_eq!(simp("!1", Algebra::Kleene), "0");
364 }
365
366 #[test]
367 fn folds_semantic_constants() {
368 assert_eq!(simp("x | 1", Algebra::Kleene), "1");
372 assert_eq!(simp("x & 0", Algebra::Kleene), "0");
373 assert_eq!(simp("1 & 0 | 1", Algebra::Kleene), "1");
375 let e = Parser::parse("a & 1 | b & 0").unwrap();
377 assert_eq!(simplify(&e, Algebra::Kleene), Expr::Var("a".into()));
378 }
379
380 #[test]
381 fn applies_idempotence_and_identity() {
382 assert_eq!(simp("a & a", Algebra::Kleene), "a");
383 assert_eq!(simp("a | a", Algebra::Kleene), "a");
384 assert_eq!(simp("a & 1", Algebra::Kleene), "a");
385 assert_eq!(simp("a | 0", Algebra::Kleene), "a");
386 }
387
388 #[test]
389 fn bochvar_poison_differs_from_kleene() {
390 assert_eq!(simp("0 & x", Algebra::Bochvar), "X");
392 assert_eq!(simp("0 & x", Algebra::Kleene), "0");
393 }
394
395 #[test]
396 fn simplification_preserves_semantics() {
397 for src in [
398 "a & b | a",
399 "!(a & b)",
400 "(a => b) & (b => a)",
401 "a ^ b",
402 "a <=> b",
403 "a & 1 | b & 0",
404 ] {
405 let e = Parser::parse(src).unwrap();
406 let s = simplify(&e, Algebra::Kleene);
407 let r = crate::eval::equivalent(&e, &s, Algebra::Kleene).unwrap();
408 assert!(r.equivalent, "simplify changed semantics: {src} -> {s}");
409 }
410 }
411
412 #[test]
413 fn simplification_preserves_semantics_in_bochvar() {
414 for src in ["a & b | a", "0 & x", "a | 1", "a & x"] {
415 let e = Parser::parse(src).unwrap();
416 let s = simplify(&e, Algebra::Bochvar);
417 let r = crate::eval::equivalent(&e, &s, Algebra::Bochvar).unwrap();
418 assert!(
419 r.equivalent,
420 "bochvar simplify changed semantics: {src} -> {s}"
421 );
422 }
423 }
424
425 #[test]
426 fn absorption_only_removes_matching_operator() {
427 let e = Parser::parse("a | (a | b)").unwrap();
429 let s = simplify(&e, Algebra::Kleene);
430 assert!(
431 crate::eval::equivalent(&e, &s, Algebra::Kleene)
432 .unwrap()
433 .equivalent,
434 "a | (a | b) -> {s}"
435 );
436 assert_eq!(simp("a | (a & b)", Algebra::Kleene), "a");
437 }
438
439 #[test]
440 fn xor_and_equiv_self_respect_unknown() {
441 assert_eq!(simp("a ^ a", Algebra::Kleene), "a");
443 assert_eq!(simp("a ^ a", Algebra::Bochvar), "a");
444 let e = Parser::parse("a <=> a").unwrap();
445 let s = simplify(&e, Algebra::Kleene);
446 assert!(
447 crate::eval::equivalent(&e, &s, Algebra::Kleene)
448 .unwrap()
449 .equivalent,
450 "a ≡ a -> {s}"
451 );
452 assert_eq!(simp("a <=> a", Algebra::Lukasiewicz), "1");
454 }
455
456 #[test]
457 fn semantically_equal_uses_real_equivalence() {
458 let a = Parser::parse("a & b").unwrap();
459 let b = Parser::parse("b & a").unwrap();
460 assert!(semantically_equal(&a, b, Algebra::Kleene).unwrap());
461
462 let a = Parser::parse("a").unwrap();
463 let b = Parser::parse("a & (b | 1)").unwrap();
464 assert!(semantically_equal(&a, b, Algebra::Kleene).unwrap());
465
466 let a = Parser::parse("a | (a | b)").unwrap();
467 let b = Parser::parse("a").unwrap();
468 assert!(!semantically_equal(&a, b, Algebra::Kleene).unwrap());
469 }
470}