vortex_expr/forms/nnf.rs
1use vortex_error::{VortexExpect, VortexResult};
2
3use crate::traversal::{FoldDown, FoldUp, FolderMut, Node as _};
4use crate::{BinaryExpr, ExprRef, Not, Operator, not};
5
6/// Return an equivalent expression in Negative Normal Form (NNF).
7///
8/// In NNF, [Not] expressions may only contain terminal nodes such as [Literal](crate::Literal) or
9/// [GetItem](crate::GetItem). They *may not* contain [BinaryExpr], [Not], etc.
10///
11/// # Examples
12///
13/// Double negation is removed entirely:
14///
15/// ```
16/// use vortex_expr::{not, col};
17/// use vortex_expr::forms::nnf::nnf;
18///
19/// let double_negation = not(not(col("a")));
20/// let nnfed = nnf(double_negation);
21/// assert_eq!(&nnfed, &col("a"));
22/// ```
23///
24/// Triple negation becomes single negation:
25///
26/// ```
27/// use vortex_expr::{not, col};
28/// use vortex_expr::forms::nnf::nnf;
29///
30/// let triple_negation = not(not(not(col("a"))));
31/// let nnfed = nnf(triple_negation);
32/// assert_eq!(&nnfed, ¬(col("a")));
33/// ```
34///
35/// Negation at a high-level is pushed to the leaves, likely increasing the total number nodes:
36///
37/// ```
38/// use vortex_expr::{not, col, and, or};
39/// use vortex_expr::forms::nnf::nnf;
40///
41/// assert_eq!(
42/// &nnf(not(and(col("a"), col("b")))),
43/// &or(not(col("a")), not(col("b")))
44/// );
45/// ```
46///
47/// In Vortex, NNF is extended beyond simple Boolean operators to any Boolean-valued operator:
48///
49/// ```
50/// use vortex_expr::{not, col, and, or, lt, lit, gt_eq};
51/// use vortex_expr::forms::nnf::nnf;
52///
53/// assert_eq!(
54/// &nnf(not(and(gt_eq(col("a"), lit(3)), col("b")))),
55/// &or(lt(col("a"), lit(3)), not(col("b")))
56/// );
57/// ```
58pub fn nnf(expr: ExprRef) -> ExprRef {
59 let mut visitor = NNFVisitor::default();
60
61 expr.transform_with_context(&mut visitor, false)
62 .vortex_expect("cannot fail")
63 .result()
64}
65
66#[derive(Default)]
67struct NNFVisitor {}
68
69impl FolderMut for NNFVisitor {
70 type NodeTy = ExprRef;
71 type Out = ExprRef;
72 type Context = bool;
73
74 fn visit_down(
75 &mut self,
76 node: &ExprRef,
77 negating: bool,
78 ) -> VortexResult<FoldDown<ExprRef, bool>> {
79 let node_any = node.as_any();
80 if node_any.is::<Not>() {
81 return Ok(FoldDown::Continue(!negating));
82 } else if let Some(binary_expr) = node_any.downcast_ref::<BinaryExpr>() {
83 match binary_expr.op() {
84 Operator::And | Operator::Or => {
85 return Ok(FoldDown::Continue(negating));
86 }
87 _ => {}
88 }
89 }
90
91 Ok(FoldDown::Continue(negating))
92 }
93
94 fn visit_up(
95 &mut self,
96 node: ExprRef,
97 negating: bool,
98 mut new_children: Vec<ExprRef>,
99 ) -> VortexResult<FoldUp<ExprRef>> {
100 let node_any = node.as_any();
101
102 let new_node = if node_any.is::<Not>() {
103 debug_assert_eq!(new_children.len(), 1);
104 new_children.remove(0)
105 } else if let Some(binary_expr) = node_any.downcast_ref::<BinaryExpr>() {
106 if !negating {
107 node
108 } else {
109 let new_op = match binary_expr.op() {
110 Operator::Eq => Operator::NotEq,
111 Operator::NotEq => Operator::Eq,
112 Operator::Gt => Operator::Lte,
113 Operator::Gte => Operator::Lt,
114 Operator::Lt => Operator::Gte,
115 Operator::Lte => Operator::Gt,
116 Operator::And => Operator::Or,
117 Operator::Or => Operator::And,
118 };
119 let (lhs, rhs) = match binary_expr.op() {
120 Operator::Or | Operator::And => {
121 let mut negated_children = new_children;
122 debug_assert_eq!(negated_children.len(), 2);
123 let rhs = negated_children.remove(1);
124 let lhs = negated_children.remove(0);
125 (lhs, rhs)
126 }
127 _ => (binary_expr.lhs().clone(), binary_expr.rhs().clone()),
128 };
129 BinaryExpr::new_expr(lhs, new_op, rhs)
130 }
131 } else if negating {
132 not(node)
133 } else {
134 node
135 };
136
137 Ok(FoldUp::Continue(new_node))
138 }
139}