1
//! Normalising rules for boolean operations (not, and, or, ->).
2

            
3
use conjure_core::ast::Expression as Expr;
4
use conjure_core::metadata::Metadata;
5
use conjure_core::rule_engine::{
6
    register_rule, ApplicationError, ApplicationError::RuleNotApplicable, ApplicationResult,
7
    Reduction,
8
};
9
use uniplate::Uniplate;
10

            
11
use Expr::*;
12

            
13
use crate::ast::{Atom, SymbolTable};
14

            
15
/// Removes double negations
16
///
17
/// ```text
18
/// not(not(a)) = a
19
/// ```
20
#[register_rule(("Base", 8400))]
21
256258
fn remove_double_negation(expr: &Expr, _: &SymbolTable) -> ApplicationResult {
22
256258
    match expr {
23
3060
        Not(_, contents) => match contents.as_ref() {
24
51
            Not(_, expr_box) => Ok(Reduction::pure(*expr_box.clone())),
25
3009
            _ => Err(ApplicationError::RuleNotApplicable),
26
        },
27
253198
        _ => Err(ApplicationError::RuleNotApplicable),
28
    }
29
256258
}
30

            
31
/// Distributes `ands` contained in `ors`
32
///
33
/// ```text
34
/// or(and(a, b), c) ~> and(or(a, c), or(b, c))
35
/// ```
36
#[register_rule(("Base", 8400))]
37
256258
fn distribute_or_over_and(expr: &Expr, _: &SymbolTable) -> ApplicationResult {
38
10625
    fn find_and(exprs: &[Expr]) -> Option<usize> {
39
        // ToDo: may be better to move this to some kind of utils module?
40
30141
        for (i, e) in exprs.iter().enumerate() {
41
30141
            if let And(_, _) = e {
42
34
                return Some(i);
43
30107
            }
44
        }
45
10591
        None
46
10625
    }
47

            
48
256258
    match expr {
49
10625
        Or(_, exprs) => match find_and(exprs) {
50
34
            Some(idx) => {
51
34
                let mut rest = exprs.clone();
52
34
                let and_expr = rest.remove(idx);
53
34

            
54
34
                match and_expr {
55
34
                    And(metadata, and_exprs) => {
56
34
                        let mut new_and_contents = Vec::new();
57

            
58
102
                        for e in and_exprs {
59
                            // ToDo: Cloning everything may be a bit inefficient - discuss
60
68
                            let mut new_or_contents = rest.clone();
61
68
                            new_or_contents.push(e.clone());
62
68
                            new_and_contents.push(Or(metadata.clone_dirty(), new_or_contents))
63
                        }
64

            
65
34
                        Ok(Reduction::pure(And(
66
34
                            metadata.clone_dirty(),
67
34
                            new_and_contents,
68
34
                        )))
69
                    }
70
                    _ => Err(ApplicationError::RuleNotApplicable),
71
                }
72
            }
73
10591
            None => Err(ApplicationError::RuleNotApplicable),
74
        },
75
245633
        _ => Err(ApplicationError::RuleNotApplicable),
76
    }
77
256258
}
78

            
79
/// Distributes `not` over `and` by De Morgan's Law
80
///
81
/// ```text
82
/// not(and(a, b)) ~> or(not(a), not(b))
83
/// ```
84
#[register_rule(("Base", 8400))]
85
256275
fn distribute_not_over_and(expr: &Expr, _: &SymbolTable) -> ApplicationResult {
86
888794
    for child in expr.universe() {
87
878101
        if matches!(
88
888794
            child,
89
            Expr::UnsafeDiv(_, _, _) | Expr::Bubble(_, _, _) | Expr::UnsafeMod(_, _, _)
90
        ) {
91
10693
            return Err(RuleNotApplicable);
92
878101
        }
93
    }
94
245582
    match expr {
95
2941
        Not(_, contents) => match contents.as_ref() {
96
119
            And(metadata, exprs) => {
97
119
                if exprs.len() == 1 {
98
                    let single_expr = exprs[0].clone();
99
                    return Ok(Reduction::pure(Not(
100
                        Metadata::new(),
101
                        Box::new(single_expr.clone()),
102
                    )));
103
119
                }
104
119
                let mut new_exprs = Vec::new();
105
391
                for e in exprs {
106
272
                    new_exprs.push(Not(metadata.clone(), Box::new(e.clone())));
107
272
                }
108
119
                Ok(Reduction::pure(Or(metadata.clone(), new_exprs)))
109
            }
110
2822
            _ => Err(ApplicationError::RuleNotApplicable),
111
        },
112
242641
        _ => Err(ApplicationError::RuleNotApplicable),
113
    }
114
256275
}
115

            
116
/// Distributes `not` over `or` by De Morgan's Law
117
///
118
/// ```text
119
/// not(or(a, b)) ~> and(not(a), not(b))
120
/// ```
121
#[register_rule(("Base", 8400))]
122
256275
fn distribute_not_over_or(expr: &Expr, _: &SymbolTable) -> ApplicationResult {
123
256275
    match expr {
124
3077
        Not(_, contents) => match contents.as_ref() {
125
17
            Or(metadata, exprs) => {
126
17
                if exprs.len() == 1 {
127
                    let single_expr = exprs[0].clone();
128
                    return Ok(Reduction::pure(Not(
129
                        Metadata::new(),
130
                        Box::new(single_expr.clone()),
131
                    )));
132
17
                }
133
17
                let mut new_exprs = Vec::new();
134
51
                for e in exprs {
135
34
                    new_exprs.push(Not(metadata.clone(), Box::new(e.clone())));
136
34
                }
137
17
                Ok(Reduction::pure(And(metadata.clone(), new_exprs)))
138
            }
139
3060
            _ => Err(ApplicationError::RuleNotApplicable),
140
        },
141
253198
        _ => Err(ApplicationError::RuleNotApplicable),
142
    }
143
256275
}
144

            
145
/// Removes ands with a single argument.
146
///
147
/// ```text
148
/// or([a]) ~> a
149
/// ```
150
#[register_rule(("Base", 8800))]
151
294882
fn remove_unit_vector_and(expr: &Expr, _: &SymbolTable) -> ApplicationResult {
152
294882
    match expr {
153
5661
        And(_, exprs) => {
154
5661
            if exprs.len() == 1 {
155
884
                return Ok(Reduction::pure(exprs[0].clone()));
156
4777
            }
157
4777
            Err(ApplicationError::RuleNotApplicable)
158
        }
159
289221
        _ => Err(ApplicationError::RuleNotApplicable),
160
    }
161
294882
}
162

            
163
/// Removes ors with a single argument.
164
///
165
/// ```text
166
/// or([a]) ~> a
167
/// ```
168
#[register_rule(("Base", 8800))]
169
294882
fn remove_unit_vector_or(expr: &Expr, _: &SymbolTable) -> ApplicationResult {
170
12036
    match expr {
171
12036
        // do not conflict with unwrap_nested_or rule.
172
12036
        Or(_, exprs) if exprs.len() == 1 && !matches!(exprs[0], Or(_, _)) => {
173
17
            Ok(Reduction::pure(exprs[0].clone()))
174
        }
175
294865
        _ => Err(ApplicationError::RuleNotApplicable),
176
    }
177
294882
}
178

            
179
/// Applies the contrapositive of implication.
180
///
181
/// ```text
182
/// !p -> !q ~> q -> p
183
/// ```
184
/// where p,q are safe.
185
#[register_rule(("Base", 8800))]
186
294865
fn normalise_implies_contrapositive(expr: &Expr, _: &SymbolTable) -> ApplicationResult {
187
294865
    let Expr::Imply(_, e1, e2) = expr else {
188
282336
        return Err(RuleNotApplicable);
189
    };
190

            
191
12529
    let Expr::Not(_, p) = e1.as_ref() else {
192
12478
        return Err(RuleNotApplicable);
193
    };
194

            
195
51
    let Expr::Not(_, q) = e2.as_ref() else {
196
34
        return Err(RuleNotApplicable);
197
    };
198

            
199
    // we only negate e1, e2 if they are safe.
200
17
    if !e1.is_safe() || !e2.is_safe() {
201
        return Err(RuleNotApplicable);
202
17
    }
203
17

            
204
17
    Ok(Reduction::pure(Expr::Imply(
205
17
        Metadata::new(),
206
17
        q.clone(),
207
17
        p.clone(),
208
17
    )))
209
294865
}
210

            
211
/// Simplifies the negation of implication.
212
///
213
/// ```text
214
/// !(p->q) ~> p /\ !q
215
/// ```,
216
///
217
/// where p->q is safe
218
#[register_rule(("Base", 8800))]
219
294865
fn normalise_implies_negation(expr: &Expr, _: &SymbolTable) -> ApplicationResult {
220
294865
    let Expr::Not(_, e1) = expr else {
221
290921
        return Err(RuleNotApplicable);
222
    };
223

            
224
3944
    let Expr::Imply(_, p, q) = e1.as_ref() else {
225
3927
        return Err(RuleNotApplicable);
226
    };
227

            
228
    // p->q must be safe to negate
229
17
    if !e1.is_safe() {
230
        return Err(RuleNotApplicable);
231
17
    }
232
17

            
233
17
    Ok(Reduction::pure(Expr::And(
234
17
        Metadata::new(),
235
17
        vec![*p.clone(), Expr::Not(Metadata::new(), q.clone())],
236
17
    )))
237
294865
}
238

            
239
/// Applies left distributivity to implication.
240
///
241
/// ```text
242
/// ((r -> p) -> (r->q)) ~> (r -> (p -> q))
243
/// ```
244
///
245
/// This rule relies on CSE to unify the two instances of `r` to a single atom; therefore, it might
246
/// not work as well when optimisations are disabled.
247
///
248
/// Has a higher priority than `normalise_implies_uncurry` as this should apply first. See the
249
/// docstring for `normalise_implies_uncurry`.
250
#[register_rule(("Base", 8800))]
251
294865
fn normalise_implies_left_distributivity(expr: &Expr, _: &SymbolTable) -> ApplicationResult {
252
294865
    let Expr::Imply(_, e1, e2) = expr else {
253
282336
        return Err(RuleNotApplicable);
254
    };
255

            
256
12529
    let Expr::Imply(_, r1, p) = e1.as_ref() else {
257
12512
        return Err(RuleNotApplicable);
258
    };
259

            
260
17
    let Expr::Imply(_, r2, q) = e2.as_ref() else {
261
        return Err(RuleNotApplicable);
262
    };
263

            
264
    // Instead of checking deep equality, let CSE unify them to a common variable and check for
265
    // that.
266

            
267
17
    let r1_atom: &Atom = r1.as_ref().try_into().or(Err(RuleNotApplicable))?;
268
17
    let r2_atom: &Atom = r2.as_ref().try_into().or(Err(RuleNotApplicable))?;
269

            
270
17
    if !(r1_atom == r2_atom) {
271
        return Err(RuleNotApplicable);
272
17
    }
273
17

            
274
17
    Ok(Reduction::pure(Expr::Imply(
275
17
        Metadata::new(),
276
17
        r1.clone(),
277
17
        Box::new(Expr::Imply(Metadata::new(), p.clone(), q.clone())),
278
17
    )))
279
294865
}
280

            
281
/// Applies import-export to implication, i.e. uncurrying.
282
///
283
/// ```text
284
/// p -> (q -> r) ~> (p/\q) -> r
285
/// ```
286
///
287
/// This rule has a lower priority of 8400 to allow distributivity, contraposition, etc. to
288
/// apply first.
289
///
290
/// For example, we want to do:
291
///
292
/// ```text
293
/// ((r -> p) -> (r -> q)) ~> (r -> (p -> q))  [left-distributivity]
294
/// (r -> (p -> q)) ~> (r/\p) ~> q [uncurry]
295
/// ```
296
///
297
/// not
298
///
299
/// ```text
300
/// ((r->p) -> (r->q)) ~> ((r->p) /\ r) -> q) ~> [uncurry]
301
/// ```
302
///
303
/// # Rationale
304
///
305
/// With this rule, I am assuming (without empirical evidence) that and is a cheaper constraint
306
/// than implication (in particular, Minion's reifyimply constraint).
307
#[register_rule(("Base", 8400))]
308
256241
fn normalise_implies_uncurry(expr: &Expr, _: &SymbolTable) -> ApplicationResult {
309
256241
    let Expr::Imply(_, p, e1) = expr else {
310
246449
        return Err(RuleNotApplicable);
311
    };
312

            
313
9792
    let Expr::Imply(_, q, r) = e1.as_ref() else {
314
9758
        return Err(RuleNotApplicable);
315
    };
316

            
317
34
    Ok(Reduction::pure(Expr::Imply(
318
34
        Metadata::new(),
319
34
        Box::new(Expr::And(Metadata::new(), vec![*p.clone(), *q.clone()])),
320
34
        r.clone(),
321
34
    )))
322
256241
}