Skip to main content

tensorlogic_ir/expr/
normal_forms.rs

1//! Normal form transformations for logical expressions.
2//!
3//! This module provides conversions to standard logical normal forms:
4//! - **CNF (Conjunctive Normal Form)**: A conjunction of disjunctions (AND of ORs)
5//! - **DNF (Disjunctive Normal Form)**: A disjunction of conjunctions (OR of ANDs)
6//!
7//! # Algorithm
8//!
9//! The transformation follows these steps:
10//! 1. Eliminate implications: `A → B` becomes `¬A ∨ B`
11//! 2. Push negations inward using De Morgan's laws
12//! 3. Distribute operators (CNF: OR over AND, DNF: AND over OR)
13//! 4. Flatten nested operators of the same type
14//!
15//! # Limitations
16//!
17//! - Quantifiers, arithmetic operations, and aggregations are treated as atomic predicates
18//! - Normal form transformation can lead to exponential blowup in expression size
19//! - For complex expressions, consider using `to_nnf` (Negation Normal Form) first
20
21use super::TLExpr;
22
23/// Convert an expression to Negation Normal Form (NNF).
24///
25/// NNF is a normalized form where:
26/// - Negations are pushed inward to appear only before predicates
27/// - Implications are eliminated
28/// - Only AND, OR, NOT (before predicates), predicates, and quantifiers remain
29///
30/// This is an intermediate step for CNF/DNF conversion.
31///
32/// # Examples
33///
34/// ```
35/// use tensorlogic_ir::{TLExpr, Term};
36///
37/// // ¬(A ∧ B) becomes (¬A ∨ ¬B)
38/// let a = TLExpr::pred("A", vec![]);
39/// let b = TLExpr::pred("B", vec![]);
40/// let expr = TLExpr::negate(TLExpr::and(a.clone(), b.clone()));
41/// let nnf = tensorlogic_ir::to_nnf(&expr);
42/// // Result: OR(NOT(A), NOT(B))
43/// ```
44pub fn to_nnf(expr: &TLExpr) -> TLExpr {
45    match expr {
46        // Base cases - already in NNF
47        TLExpr::Pred { .. } => expr.clone(),
48        TLExpr::Constant(_) => expr.clone(),
49
50        // Logical operators
51        TLExpr::And(l, r) => TLExpr::and(to_nnf(l), to_nnf(r)),
52        TLExpr::Or(l, r) => TLExpr::or(to_nnf(l), to_nnf(r)),
53
54        // Implication elimination: A → B becomes ¬A ∨ B
55        TLExpr::Imply(l, r) => {
56            let not_l = push_negation_inward(&TLExpr::negate((**l).clone()));
57            let r_nnf = to_nnf(r);
58            TLExpr::or(not_l, r_nnf)
59        }
60
61        // Push negation inward
62        TLExpr::Not(inner) => push_negation_inward(&TLExpr::Not(Box::new((**inner).clone()))),
63
64        // Quantifiers: convert body to NNF
65        TLExpr::Exists { var, domain, body } => TLExpr::exists(var, domain, to_nnf(body)),
66        TLExpr::ForAll { var, domain, body } => TLExpr::forall(var, domain, to_nnf(body)),
67
68        // Score: convert inner expression
69        TLExpr::Score(inner) => TLExpr::score(to_nnf(inner)),
70
71        // Modal and temporal logic operators: convert body to NNF
72        TLExpr::Box(inner) => TLExpr::modal_box(to_nnf(inner)),
73        TLExpr::Diamond(inner) => TLExpr::modal_diamond(to_nnf(inner)),
74        TLExpr::Next(inner) => TLExpr::next(to_nnf(inner)),
75        TLExpr::Eventually(inner) => TLExpr::eventually(to_nnf(inner)),
76        TLExpr::Always(inner) => TLExpr::always(to_nnf(inner)),
77        TLExpr::Until { before, after } => TLExpr::until(to_nnf(before), to_nnf(after)),
78
79        // Arithmetic, comparison, and other operations are treated as atomic
80        // (they don't have logical structure to normalize)
81        TLExpr::Add(l, r) => TLExpr::add(to_nnf(l), to_nnf(r)),
82        TLExpr::Sub(l, r) => TLExpr::sub(to_nnf(l), to_nnf(r)),
83        TLExpr::Mul(l, r) => TLExpr::mul(to_nnf(l), to_nnf(r)),
84        TLExpr::Div(l, r) => TLExpr::div(to_nnf(l), to_nnf(r)),
85        TLExpr::Pow(l, r) => TLExpr::pow(to_nnf(l), to_nnf(r)),
86        TLExpr::Mod(l, r) => TLExpr::modulo(to_nnf(l), to_nnf(r)),
87        TLExpr::Min(l, r) => TLExpr::min(to_nnf(l), to_nnf(r)),
88        TLExpr::Max(l, r) => TLExpr::max(to_nnf(l), to_nnf(r)),
89
90        // Unary operations
91        TLExpr::Abs(e) => TLExpr::abs(to_nnf(e)),
92        TLExpr::Floor(e) => TLExpr::floor(to_nnf(e)),
93        TLExpr::Ceil(e) => TLExpr::ceil(to_nnf(e)),
94        TLExpr::Round(e) => TLExpr::round(to_nnf(e)),
95        TLExpr::Sqrt(e) => TLExpr::sqrt(to_nnf(e)),
96        TLExpr::Exp(e) => TLExpr::exp(to_nnf(e)),
97        TLExpr::Log(e) => TLExpr::log(to_nnf(e)),
98        TLExpr::Sin(e) => TLExpr::sin(to_nnf(e)),
99        TLExpr::Cos(e) => TLExpr::cos(to_nnf(e)),
100        TLExpr::Tan(e) => TLExpr::tan(to_nnf(e)),
101
102        // Comparisons
103        TLExpr::Eq(l, r) => TLExpr::eq(to_nnf(l), to_nnf(r)),
104        TLExpr::Lt(l, r) => TLExpr::lt(to_nnf(l), to_nnf(r)),
105        TLExpr::Gt(l, r) => TLExpr::gt(to_nnf(l), to_nnf(r)),
106        TLExpr::Lte(l, r) => TLExpr::lte(to_nnf(l), to_nnf(r)),
107        TLExpr::Gte(l, r) => TLExpr::gte(to_nnf(l), to_nnf(r)),
108
109        // Conditionals
110        TLExpr::IfThenElse {
111            condition,
112            then_branch,
113            else_branch,
114        } => TLExpr::if_then_else(to_nnf(condition), to_nnf(then_branch), to_nnf(else_branch)),
115
116        // Aggregations and Let bindings
117        TLExpr::Aggregate {
118            op,
119            var,
120            domain,
121            body,
122            group_by,
123        } => TLExpr::Aggregate {
124            op: op.clone(),
125            var: var.clone(),
126            domain: domain.clone(),
127            body: Box::new(to_nnf(body)),
128            group_by: group_by.clone(),
129        },
130        TLExpr::Let { var, value, body } => TLExpr::let_binding(var, to_nnf(value), to_nnf(body)),
131
132        // Fuzzy logic operators: pass through (treat as atomic for boolean structure)
133        TLExpr::TNorm { kind, left, right } => TLExpr::tnorm(*kind, to_nnf(left), to_nnf(right)),
134        TLExpr::TCoNorm { kind, left, right } => {
135            TLExpr::tconorm(*kind, to_nnf(left), to_nnf(right))
136        }
137        TLExpr::FuzzyNot { kind, expr } => TLExpr::fuzzy_not(*kind, to_nnf(expr)),
138        TLExpr::FuzzyImplication {
139            kind,
140            premise,
141            conclusion,
142        } => TLExpr::fuzzy_imply(*kind, to_nnf(premise), to_nnf(conclusion)),
143
144        // Probabilistic operators: pass through
145        TLExpr::SoftExists {
146            var,
147            domain,
148            body,
149            temperature,
150        } => TLExpr::soft_exists(var, domain, to_nnf(body), *temperature),
151        TLExpr::SoftForAll {
152            var,
153            domain,
154            body,
155            temperature,
156        } => TLExpr::soft_forall(var, domain, to_nnf(body), *temperature),
157        TLExpr::WeightedRule { weight, rule } => TLExpr::weighted_rule(*weight, to_nnf(rule)),
158        TLExpr::ProbabilisticChoice { alternatives } => TLExpr::probabilistic_choice(
159            alternatives.iter().map(|(p, e)| (*p, to_nnf(e))).collect(),
160        ),
161
162        // Extended temporal logic: pass through
163        TLExpr::Release { released, releaser } => {
164            TLExpr::release(to_nnf(released), to_nnf(releaser))
165        }
166        TLExpr::WeakUntil { before, after } => TLExpr::weak_until(to_nnf(before), to_nnf(after)),
167        TLExpr::StrongRelease { released, releaser } => {
168            TLExpr::strong_release(to_nnf(released), to_nnf(releaser))
169        }
170
171        // Beta.1 enhancements: pass through (treat as atomic for NNF purposes)
172        TLExpr::Lambda {
173            var,
174            var_type,
175            body,
176        } => TLExpr::lambda(var.clone(), var_type.clone(), to_nnf(body)),
177        TLExpr::Apply { function, argument } => TLExpr::apply(to_nnf(function), to_nnf(argument)),
178        TLExpr::SetMembership { element, set } => {
179            TLExpr::set_membership(to_nnf(element), to_nnf(set))
180        }
181        TLExpr::SetUnion { left, right } => TLExpr::set_union(to_nnf(left), to_nnf(right)),
182        TLExpr::SetIntersection { left, right } => {
183            TLExpr::set_intersection(to_nnf(left), to_nnf(right))
184        }
185        TLExpr::SetDifference { left, right } => {
186            TLExpr::set_difference(to_nnf(left), to_nnf(right))
187        }
188        TLExpr::SetCardinality { set } => TLExpr::set_cardinality(to_nnf(set)),
189        TLExpr::EmptySet => expr.clone(),
190        TLExpr::SetComprehension {
191            var,
192            domain,
193            condition,
194        } => TLExpr::set_comprehension(var.clone(), domain.clone(), to_nnf(condition)),
195        TLExpr::CountingExists {
196            var,
197            domain,
198            body,
199            min_count,
200        } => TLExpr::counting_exists(var.clone(), domain.clone(), to_nnf(body), *min_count),
201        TLExpr::CountingForAll {
202            var,
203            domain,
204            body,
205            min_count,
206        } => TLExpr::counting_forall(var.clone(), domain.clone(), to_nnf(body), *min_count),
207        TLExpr::ExactCount {
208            var,
209            domain,
210            body,
211            count,
212        } => TLExpr::exact_count(var.clone(), domain.clone(), to_nnf(body), *count),
213        TLExpr::Majority { var, domain, body } => {
214            TLExpr::majority(var.clone(), domain.clone(), to_nnf(body))
215        }
216        TLExpr::LeastFixpoint { var, body } => TLExpr::least_fixpoint(var.clone(), to_nnf(body)),
217        TLExpr::GreatestFixpoint { var, body } => {
218            TLExpr::greatest_fixpoint(var.clone(), to_nnf(body))
219        }
220        TLExpr::Nominal { .. } => expr.clone(),
221        TLExpr::At { nominal, formula } => TLExpr::at(nominal.clone(), to_nnf(formula)),
222        TLExpr::Somewhere { formula } => TLExpr::somewhere(to_nnf(formula)),
223        TLExpr::Everywhere { formula } => TLExpr::everywhere(to_nnf(formula)),
224        TLExpr::AllDifferent { .. } => expr.clone(),
225        TLExpr::GlobalCardinality {
226            variables,
227            values,
228            min_occurrences,
229            max_occurrences,
230        } => TLExpr::global_cardinality(
231            variables.clone(),
232            values.iter().map(to_nnf).collect(),
233            min_occurrences.clone(),
234            max_occurrences.clone(),
235        ),
236        TLExpr::Abducible { .. } => expr.clone(),
237        TLExpr::Explain { formula } => TLExpr::explain(to_nnf(formula)),
238    }
239}
240
241/// Push negation inward using De Morgan's laws.
242fn push_negation_inward(expr: &TLExpr) -> TLExpr {
243    match expr {
244        // Double negation elimination: ¬¬A = A
245        TLExpr::Not(inner) => match &**inner {
246            TLExpr::Not(inner2) => to_nnf(inner2),
247            // De Morgan's laws
248            TLExpr::And(l, r) => {
249                // ¬(A ∧ B) = ¬A ∨ ¬B
250                let not_l = push_negation_inward(&TLExpr::negate((**l).clone()));
251                let not_r = push_negation_inward(&TLExpr::negate((**r).clone()));
252                TLExpr::or(not_l, not_r)
253            }
254            TLExpr::Or(l, r) => {
255                // ¬(A ∨ B) = ¬A ∧ ¬B
256                let not_l = push_negation_inward(&TLExpr::negate((**l).clone()));
257                let not_r = push_negation_inward(&TLExpr::negate((**r).clone()));
258                TLExpr::and(not_l, not_r)
259            }
260            // ¬(A → B) = A ∧ ¬B
261            TLExpr::Imply(l, r) => {
262                let l_nnf = to_nnf(l);
263                let not_r = push_negation_inward(&TLExpr::negate((**r).clone()));
264                TLExpr::and(l_nnf, not_r)
265            }
266            // Quantifier negation: ¬∃x.P(x) = ∀x.¬P(x), ¬∀x.P(x) = ∃x.¬P(x)
267            TLExpr::Exists { var, domain, body } => {
268                let not_body = push_negation_inward(&TLExpr::negate((**body).clone()));
269                TLExpr::forall(var, domain, not_body)
270            }
271            TLExpr::ForAll { var, domain, body } => {
272                let not_body = push_negation_inward(&TLExpr::negate((**body).clone()));
273                TLExpr::exists(var, domain, not_body)
274            }
275            // For atomic predicates and other expressions, keep the negation
276            _ => expr.clone(),
277        },
278        // Non-negation: convert to NNF
279        _ => to_nnf(expr),
280    }
281}
282
283/// Convert an expression to Conjunctive Normal Form (CNF).
284///
285/// CNF is a conjunction of disjunctions: `(A ∨ B ∨ C) ∧ (D ∨ E) ∧ ...`
286///
287/// **Warning**: CNF conversion can lead to exponential blowup in expression size.
288/// For expressions with many nested operators, consider limiting the depth or
289/// using approximation techniques.
290///
291/// # Examples
292///
293/// ```
294/// use tensorlogic_ir::{TLExpr, Term};
295///
296/// // (A ∧ B) ∨ C becomes (A ∨ C) ∧ (B ∨ C)
297/// let a = TLExpr::pred("A", vec![]);
298/// let b = TLExpr::pred("B", vec![]);
299/// let c = TLExpr::pred("C", vec![]);
300/// let expr = TLExpr::or(TLExpr::and(a, b), c);
301/// let cnf = tensorlogic_ir::to_cnf(&expr);
302/// // Result: AND(OR(A, C), OR(B, C))
303/// ```
304pub fn to_cnf(expr: &TLExpr) -> TLExpr {
305    let nnf = to_nnf(expr);
306    cnf_distribute(&nnf)
307}
308
309/// Distribute OR over AND to achieve CNF.
310fn cnf_distribute(expr: &TLExpr) -> TLExpr {
311    match expr {
312        TLExpr::And(l, r) => {
313            let l_cnf = cnf_distribute(l);
314            let r_cnf = cnf_distribute(r);
315            TLExpr::and(l_cnf, r_cnf)
316        }
317        TLExpr::Or(l, r) => {
318            let l_cnf = cnf_distribute(l);
319            let r_cnf = cnf_distribute(r);
320            distribute_or_over_and(&l_cnf, &r_cnf)
321        }
322        TLExpr::Exists { var, domain, body } => TLExpr::exists(var, domain, cnf_distribute(body)),
323        TLExpr::ForAll { var, domain, body } => TLExpr::forall(var, domain, cnf_distribute(body)),
324        _ => expr.clone(),
325    }
326}
327
328/// Helper function to distribute OR over AND.
329/// (A ∧ B) ∨ C = (A ∨ C) ∧ (B ∨ C)
330/// A ∨ (B ∧ C) = (A ∨ B) ∧ (A ∨ C)
331fn distribute_or_over_and(left: &TLExpr, right: &TLExpr) -> TLExpr {
332    match (left, right) {
333        // (A ∧ B) ∨ C = (A ∨ C) ∧ (B ∨ C)
334        (TLExpr::And(a, b), c) => {
335            let left_part = distribute_or_over_and(a, c);
336            let right_part = distribute_or_over_and(b, c);
337            TLExpr::and(left_part, right_part)
338        }
339        // A ∨ (B ∧ C) = (A ∨ B) ∧ (A ∨ C)
340        (a, TLExpr::And(b, c)) => {
341            let left_part = distribute_or_over_and(a, b);
342            let right_part = distribute_or_over_and(a, c);
343            TLExpr::and(left_part, right_part)
344        }
345        // Base case: A ∨ B (both are not ANDs)
346        (a, b) => TLExpr::or(a.clone(), b.clone()),
347    }
348}
349
350/// Convert an expression to Disjunctive Normal Form (DNF).
351///
352/// DNF is a disjunction of conjunctions: `(A ∧ B ∧ C) ∨ (D ∧ E) ∨ ...`
353///
354/// **Warning**: DNF conversion can lead to exponential blowup in expression size.
355/// For expressions with many nested operators, consider limiting the depth or
356/// using approximation techniques.
357///
358/// # Examples
359///
360/// ```
361/// use tensorlogic_ir::{TLExpr, Term};
362///
363/// // (A ∨ B) ∧ C becomes (A ∧ C) ∨ (B ∧ C)
364/// let a = TLExpr::pred("A", vec![]);
365/// let b = TLExpr::pred("B", vec![]);
366/// let c = TLExpr::pred("C", vec![]);
367/// let expr = TLExpr::and(TLExpr::or(a, b), c);
368/// let dnf = tensorlogic_ir::to_dnf(&expr);
369/// // Result: OR(AND(A, C), AND(B, C))
370/// ```
371pub fn to_dnf(expr: &TLExpr) -> TLExpr {
372    let nnf = to_nnf(expr);
373    dnf_distribute(&nnf)
374}
375
376/// Distribute AND over OR to achieve DNF.
377fn dnf_distribute(expr: &TLExpr) -> TLExpr {
378    match expr {
379        TLExpr::Or(l, r) => {
380            let l_dnf = dnf_distribute(l);
381            let r_dnf = dnf_distribute(r);
382            TLExpr::or(l_dnf, r_dnf)
383        }
384        TLExpr::And(l, r) => {
385            let l_dnf = dnf_distribute(l);
386            let r_dnf = dnf_distribute(r);
387            distribute_and_over_or(&l_dnf, &r_dnf)
388        }
389        TLExpr::Exists { var, domain, body } => TLExpr::exists(var, domain, dnf_distribute(body)),
390        TLExpr::ForAll { var, domain, body } => TLExpr::forall(var, domain, dnf_distribute(body)),
391        _ => expr.clone(),
392    }
393}
394
395/// Helper function to distribute AND over OR.
396/// (A ∨ B) ∧ C = (A ∧ C) ∨ (B ∧ C)
397/// A ∧ (B ∨ C) = (A ∧ B) ∨ (A ∧ C)
398fn distribute_and_over_or(left: &TLExpr, right: &TLExpr) -> TLExpr {
399    match (left, right) {
400        // (A ∨ B) ∧ C = (A ∧ C) ∨ (B ∧ C)
401        (TLExpr::Or(a, b), c) => {
402            let left_part = distribute_and_over_or(a, c);
403            let right_part = distribute_and_over_or(b, c);
404            TLExpr::or(left_part, right_part)
405        }
406        // A ∧ (B ∨ C) = (A ∧ B) ∨ (A ∧ C)
407        (a, TLExpr::Or(b, c)) => {
408            let left_part = distribute_and_over_or(a, b);
409            let right_part = distribute_and_over_or(a, c);
410            TLExpr::or(left_part, right_part)
411        }
412        // Base case: A ∧ B (both are not ORs)
413        (a, b) => TLExpr::and(a.clone(), b.clone()),
414    }
415}
416
417/// Check if an expression is in Conjunctive Normal Form (CNF).
418///
419/// An expression is in CNF if it's a conjunction of disjunctions where
420/// negations appear only before predicates.
421pub fn is_cnf(expr: &TLExpr) -> bool {
422    match expr {
423        TLExpr::And(l, r) => is_cnf(l) && is_cnf(r),
424        TLExpr::Or(l, r) => is_cnf_clause(l) && is_cnf_clause(r),
425        TLExpr::Not(inner) => is_literal(inner),
426        TLExpr::Pred { .. } => true,
427        TLExpr::Exists { body, .. } | TLExpr::ForAll { body, .. } => is_cnf(body),
428        TLExpr::Constant(_) => true,
429        _ => false, // Arithmetic, comparisons, etc. are treated as atomic
430    }
431}
432
433/// Check if an expression is a CNF clause (disjunction of literals).
434fn is_cnf_clause(expr: &TLExpr) -> bool {
435    match expr {
436        TLExpr::Or(l, r) => is_cnf_clause(l) && is_cnf_clause(r),
437        TLExpr::Not(inner) => is_literal(inner),
438        TLExpr::Pred { .. } => true,
439        TLExpr::Constant(_) => true,
440        _ => false,
441    }
442}
443
444/// Check if an expression is in Disjunctive Normal Form (DNF).
445///
446/// An expression is in DNF if it's a disjunction of conjunctions where
447/// negations appear only before predicates.
448pub fn is_dnf(expr: &TLExpr) -> bool {
449    match expr {
450        TLExpr::Or(l, r) => is_dnf(l) && is_dnf(r),
451        TLExpr::And(l, r) => is_dnf_clause(l) && is_dnf_clause(r),
452        TLExpr::Not(inner) => is_literal(inner),
453        TLExpr::Pred { .. } => true,
454        TLExpr::Exists { body, .. } | TLExpr::ForAll { body, .. } => is_dnf(body),
455        TLExpr::Constant(_) => true,
456        _ => false,
457    }
458}
459
460/// Check if an expression is a DNF clause (conjunction of literals).
461fn is_dnf_clause(expr: &TLExpr) -> bool {
462    match expr {
463        TLExpr::And(l, r) => is_dnf_clause(l) && is_dnf_clause(r),
464        TLExpr::Not(inner) => is_literal(inner),
465        TLExpr::Pred { .. } => true,
466        TLExpr::Constant(_) => true,
467        _ => false,
468    }
469}
470
471/// Check if an expression is a literal (predicate or constant).
472fn is_literal(expr: &TLExpr) -> bool {
473    matches!(expr, TLExpr::Pred { .. } | TLExpr::Constant(_))
474}
475
476#[cfg(test)]
477mod tests {
478    use super::*;
479    use crate::Term;
480
481    fn pred_a() -> TLExpr {
482        TLExpr::pred("A", vec![])
483    }
484
485    fn pred_b() -> TLExpr {
486        TLExpr::pred("B", vec![])
487    }
488
489    fn pred_c() -> TLExpr {
490        TLExpr::pred("C", vec![])
491    }
492
493    #[test]
494    fn test_nnf_double_negation() {
495        // ¬¬A = A
496        let expr = TLExpr::negate(TLExpr::negate(pred_a()));
497        let nnf = to_nnf(&expr);
498        assert_eq!(nnf, pred_a());
499    }
500
501    #[test]
502    fn test_nnf_de_morgan_and() {
503        // ¬(A ∧ B) = ¬A ∨ ¬B
504        let expr = TLExpr::negate(TLExpr::and(pred_a(), pred_b()));
505        let nnf = to_nnf(&expr);
506        assert!(matches!(nnf, TLExpr::Or(_, _)));
507        if let TLExpr::Or(l, r) = nnf {
508            assert!(matches!(*l, TLExpr::Not(_)));
509            assert!(matches!(*r, TLExpr::Not(_)));
510        }
511    }
512
513    #[test]
514    fn test_nnf_de_morgan_or() {
515        // ¬(A ∨ B) = ¬A ∧ ¬B
516        let expr = TLExpr::negate(TLExpr::or(pred_a(), pred_b()));
517        let nnf = to_nnf(&expr);
518        assert!(matches!(nnf, TLExpr::And(_, _)));
519        if let TLExpr::And(l, r) = nnf {
520            assert!(matches!(*l, TLExpr::Not(_)));
521            assert!(matches!(*r, TLExpr::Not(_)));
522        }
523    }
524
525    #[test]
526    fn test_nnf_implication() {
527        // A → B = ¬A ∨ B
528        let expr = TLExpr::imply(pred_a(), pred_b());
529        let nnf = to_nnf(&expr);
530        assert!(matches!(nnf, TLExpr::Or(_, _)));
531    }
532
533    #[test]
534    fn test_nnf_quantifier_negation() {
535        // ¬∃x.P(x) = ∀x.¬P(x)
536        let exists_expr = TLExpr::exists("x", "D", pred_a());
537        let neg_exists = TLExpr::negate(exists_expr);
538        let nnf = to_nnf(&neg_exists);
539        assert!(matches!(nnf, TLExpr::ForAll { .. }));
540
541        // ¬∀x.P(x) = ∃x.¬P(x)
542        let forall_expr = TLExpr::forall("x", "D", pred_a());
543        let neg_forall = TLExpr::negate(forall_expr);
544        let nnf = to_nnf(&neg_forall);
545        assert!(matches!(nnf, TLExpr::Exists { .. }));
546    }
547
548    #[test]
549    fn test_cnf_simple_distribution() {
550        // (A ∧ B) ∨ C = (A ∨ C) ∧ (B ∨ C)
551        let expr = TLExpr::or(TLExpr::and(pred_a(), pred_b()), pred_c());
552        let cnf = to_cnf(&expr);
553
554        // Verify it's in CNF form
555        assert!(is_cnf(&cnf));
556        // Should have top-level AND
557        assert!(matches!(cnf, TLExpr::And(_, _)));
558    }
559
560    #[test]
561    fn test_cnf_already_in_cnf() {
562        // (A ∨ B) ∧ C is already in CNF
563        let expr = TLExpr::and(TLExpr::or(pred_a(), pred_b()), pred_c());
564        let cnf = to_cnf(&expr);
565        assert!(is_cnf(&cnf));
566    }
567
568    #[test]
569    fn test_dnf_simple_distribution() {
570        // (A ∨ B) ∧ C = (A ∧ C) ∨ (B ∧ C)
571        let expr = TLExpr::and(TLExpr::or(pred_a(), pred_b()), pred_c());
572        let dnf = to_dnf(&expr);
573
574        // Verify it's in DNF form
575        assert!(is_dnf(&dnf));
576        // Should have top-level OR
577        assert!(matches!(dnf, TLExpr::Or(_, _)));
578    }
579
580    #[test]
581    fn test_dnf_already_in_dnf() {
582        // (A ∧ B) ∨ C is already in DNF
583        let expr = TLExpr::or(TLExpr::and(pred_a(), pred_b()), pred_c());
584        let dnf = to_dnf(&expr);
585        assert!(is_dnf(&dnf));
586    }
587
588    #[test]
589    fn test_is_cnf() {
590        // (A ∨ B) ∧ (C ∨ ¬D) is CNF
591        let d = TLExpr::pred("D", vec![]);
592        let expr = TLExpr::and(
593            TLExpr::or(pred_a(), pred_b()),
594            TLExpr::or(pred_c(), TLExpr::negate(d)),
595        );
596        assert!(is_cnf(&expr));
597
598        // (A ∧ B) ∨ C is not CNF
599        let not_cnf = TLExpr::or(TLExpr::and(pred_a(), pred_b()), pred_c());
600        assert!(!is_cnf(&not_cnf));
601    }
602
603    #[test]
604    fn test_is_dnf() {
605        // (A ∧ B) ∨ (C ∧ ¬D) is DNF
606        let d = TLExpr::pred("D", vec![]);
607        let expr = TLExpr::or(
608            TLExpr::and(pred_a(), pred_b()),
609            TLExpr::and(pred_c(), TLExpr::negate(d)),
610        );
611        assert!(is_dnf(&expr));
612
613        // (A ∨ B) ∧ C is not DNF
614        let not_dnf = TLExpr::and(TLExpr::or(pred_a(), pred_b()), pred_c());
615        assert!(!is_dnf(&not_dnf));
616    }
617
618    #[test]
619    fn test_complex_cnf_conversion() {
620        // ((A ∨ B) ∧ C) ∨ D should convert to CNF
621        let expr = TLExpr::or(
622            TLExpr::and(TLExpr::or(pred_a(), pred_b()), pred_c()),
623            TLExpr::pred("D", vec![]),
624        );
625        let cnf = to_cnf(&expr);
626        assert!(is_cnf(&cnf));
627    }
628
629    #[test]
630    fn test_complex_dnf_conversion() {
631        // ((A ∧ B) ∨ C) ∧ D should convert to DNF
632        let expr = TLExpr::and(
633            TLExpr::or(TLExpr::and(pred_a(), pred_b()), pred_c()),
634            TLExpr::pred("D", vec![]),
635        );
636        let dnf = to_dnf(&expr);
637        assert!(is_dnf(&dnf));
638    }
639
640    #[test]
641    fn test_cnf_with_negations() {
642        // ¬A ∨ (B ∧ ¬C) should convert to (¬A ∨ B) ∧ (¬A ∨ ¬C)
643        let expr = TLExpr::or(
644            TLExpr::negate(pred_a()),
645            TLExpr::and(pred_b(), TLExpr::negate(pred_c())),
646        );
647        let cnf = to_cnf(&expr);
648        assert!(is_cnf(&cnf));
649    }
650
651    #[test]
652    fn test_dnf_with_negations() {
653        // ¬A ∧ (B ∨ ¬C) should convert to (¬A ∧ B) ∨ (¬A ∧ ¬C)
654        let expr = TLExpr::and(
655            TLExpr::negate(pred_a()),
656            TLExpr::or(pred_b(), TLExpr::negate(pred_c())),
657        );
658        let dnf = to_dnf(&expr);
659        assert!(is_dnf(&dnf));
660    }
661
662    #[test]
663    fn test_cnf_with_quantifiers() {
664        // ∀x. (A(x) ∨ B(x)) should preserve quantifier
665        let pred_ax = TLExpr::pred("A", vec![Term::var("x")]);
666        let pred_bx = TLExpr::pred("B", vec![Term::var("x")]);
667        let expr = TLExpr::forall("x", "D", TLExpr::or(pred_ax, pred_bx));
668        let cnf = to_cnf(&expr);
669        assert!(matches!(cnf, TLExpr::ForAll { .. }));
670    }
671
672    #[test]
673    fn test_literal_identification() {
674        assert!(is_literal(&pred_a()));
675        assert!(is_literal(&TLExpr::constant(42.0)));
676        assert!(!is_literal(&TLExpr::and(pred_a(), pred_b())));
677        assert!(!is_literal(&TLExpr::negate(pred_a())));
678    }
679}