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

            
3
use conjure_cp::ast::{Atom, Expression as Expr, Moo, SymbolTable};
4
use conjure_cp::essence_expr;
5
use conjure_cp::into_matrix_expr;
6
use conjure_cp::rule_engine::{
7
    ApplicationError, ApplicationError::RuleNotApplicable, ApplicationResult, Reduction,
8
    register_rule,
9
};
10
use uniplate::Uniplate;
11

            
12
/// Removes double negations
13
///
14
/// ```text
15
/// not(not(a)) = a
16
/// ```
17
#[register_rule(("Base", 8400))]
18
103286
fn remove_double_negation(expr: &Expr, _: &SymbolTable) -> ApplicationResult {
19
103286
    match expr {
20
258
        Expr::Not(_, contents) => match contents.as_ref() {
21
9
            Expr::Not(_, expr_box) => Ok(Reduction::pure(Moo::unwrap_or_clone(expr_box.clone()))),
22
249
            _ => Err(ApplicationError::RuleNotApplicable),
23
        },
24
103028
        _ => Err(ApplicationError::RuleNotApplicable),
25
    }
26
103286
}
27

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

            
45
103286
    match expr {
46
1728
        Expr::Or(_, e) => {
47
1728
            let Some(exprs) = e.as_ref().clone().unwrap_list() else {
48
288
                return Err(RuleNotApplicable);
49
            };
50

            
51
1440
            match find_and(&exprs) {
52
72
                Some(idx) => {
53
72
                    let mut rest = exprs.clone();
54
72
                    let and_expr = rest.remove(idx);
55

            
56
72
                    match and_expr {
57
72
                        Expr::And(metadata, e) => {
58
72
                            let Some(and_exprs) = e.as_ref().clone().unwrap_list() else {
59
3
                                return Err(RuleNotApplicable);
60
                            };
61

            
62
69
                            let mut new_and_contents = Vec::new();
63

            
64
138
                            for e in and_exprs {
65
                                // ToDo: Cloning everything may be a bit inefficient - discuss
66
138
                                let mut new_or_contents = rest.clone();
67
138
                                new_or_contents.push(e.clone());
68
138
                                new_and_contents.push(Expr::Or(
69
138
                                    metadata.clone_dirty(),
70
138
                                    Moo::new(into_matrix_expr![new_or_contents]),
71
138
                                ))
72
                            }
73

            
74
69
                            Ok(Reduction::pure(Expr::And(
75
69
                                metadata.clone_dirty(),
76
69
                                Moo::new(into_matrix_expr![new_and_contents]),
77
69
                            )))
78
                        }
79
                        _ => Err(ApplicationError::RuleNotApplicable),
80
                    }
81
                }
82
1368
                None => Err(ApplicationError::RuleNotApplicable),
83
            }
84
        }
85
101558
        _ => Err(ApplicationError::RuleNotApplicable),
86
    }
87
103286
}
88

            
89
/// Distributes `not` over `and` by De Morgan's Law
90
///
91
/// ```text
92
/// not(and(a, b)) ~> or(not(a), not(b))
93
/// ```
94
#[register_rule(("Base", 8400))]
95
103289
fn distribute_not_over_and(expr: &Expr, _: &SymbolTable) -> ApplicationResult {
96
432336
    for child in expr.universe() {
97
431694
        if matches!(
98
432336
            child,
99
            Expr::UnsafeDiv(_, _, _) | Expr::Bubble(_, _, _) | Expr::UnsafeMod(_, _, _)
100
        ) {
101
642
            return Err(RuleNotApplicable);
102
431694
        }
103
    }
104
102647
    match expr {
105
237
        Expr::Not(_, contents) => match contents.as_ref() {
106
66
            Expr::And(metadata, e) => {
107
66
                let Some(exprs) = e.as_ref().clone().unwrap_list() else {
108
36
                    return Err(RuleNotApplicable);
109
                };
110

            
111
30
                if exprs.len() == 1 {
112
                    let single_expr = exprs[0].clone();
113
                    return Ok(Reduction::pure(essence_expr!(!&single_expr)));
114
30
                }
115

            
116
30
                let mut new_exprs = Vec::new();
117
69
                for e in exprs {
118
69
                    new_exprs.push(essence_expr!(!&e));
119
69
                }
120
30
                Ok(Reduction::pure(Expr::Or(
121
30
                    metadata.clone(),
122
30
                    Moo::new(into_matrix_expr![new_exprs]),
123
30
                )))
124
            }
125
171
            _ => Err(ApplicationError::RuleNotApplicable),
126
        },
127
102410
        _ => Err(ApplicationError::RuleNotApplicable),
128
    }
129
103289
}
130

            
131
/// Distributes `not` over `or` by De Morgan's Law
132
///
133
/// ```text
134
/// not(or(a, b)) ~> and(not(a), not(b))
135
/// ```
136
#[register_rule(("Base", 8400))]
137
103289
fn distribute_not_over_or(expr: &Expr, _: &SymbolTable) -> ApplicationResult {
138
103289
    match expr {
139
261
        Expr::Not(_, contents) => match contents.as_ref() {
140
3
            Expr::Or(metadata, e) => {
141
3
                let Some(exprs) = e.as_ref().clone().unwrap_list() else {
142
                    return Err(RuleNotApplicable);
143
                };
144

            
145
3
                if exprs.len() == 1 {
146
                    let single_expr = exprs[0].clone();
147
                    return Ok(Reduction::pure(essence_expr!(!&single_expr)));
148
3
                }
149

            
150
3
                let mut new_exprs = Vec::new();
151

            
152
6
                for e in exprs {
153
6
                    new_exprs.push(essence_expr!(!&e));
154
6
                }
155

            
156
3
                Ok(Reduction::pure(Expr::And(
157
3
                    metadata.clone(),
158
3
                    Moo::new(into_matrix_expr![new_exprs]),
159
3
                )))
160
            }
161
258
            _ => Err(ApplicationError::RuleNotApplicable),
162
        },
163
103028
        _ => Err(ApplicationError::RuleNotApplicable),
164
    }
165
103289
}
166

            
167
/// Removes ands with a single argument.
168
///
169
/// ```text
170
/// and([a]) ~> a
171
/// ```
172
#[register_rule(("Base", 8800))]
173

            
174
113729
fn remove_unit_vector_and(expr: &Expr, _: &SymbolTable) -> ApplicationResult {
175
113729
    match expr {
176
1209
        Expr::And(_, e) => {
177
1209
            let Some(exprs) = e.as_ref().clone().unwrap_list() else {
178
732
                return Err(RuleNotApplicable);
179
            };
180

            
181
477
            if exprs.len() == 1 {
182
36
                return Ok(Reduction::pure(exprs[0].clone()));
183
441
            }
184

            
185
441
            Err(ApplicationError::RuleNotApplicable)
186
        }
187
112520
        _ => Err(ApplicationError::RuleNotApplicable),
188
    }
189
113729
}
190

            
191
/// Removes ors with a single argument.
192
///
193
/// ```text
194
/// or([a]) ~> a
195
/// ```
196
#[register_rule(("Base", 8800))]
197
113729
fn remove_unit_vector_or(expr: &Expr, _: &SymbolTable) -> ApplicationResult {
198
113729
    let Expr::Or(_, e) = expr else {
199
111737
        return Err(RuleNotApplicable);
200
    };
201

            
202
1992
    let Some(exprs) = e.as_ref().clone().unwrap_list() else {
203
291
        return Err(RuleNotApplicable);
204
    };
205

            
206
    // do not conflict with unwrap_nested_or rule.
207
1701
    if exprs.len() != 1 || matches!(exprs[0], Expr::Or(_, _)) {
208
1683
        return Err(RuleNotApplicable);
209
18
    }
210

            
211
18
    Ok(Reduction::pure(exprs[0].clone()))
212
113729
}
213

            
214
/// Applies the contrapositive of implication.
215
///
216
/// ```text
217
/// !p -> !q ~> q -> p
218
/// ```
219
/// where p,q are safe.
220
#[register_rule(("Base", 8800))]
221
113726
fn normalise_implies_contrapositive(expr: &Expr, _: &SymbolTable) -> ApplicationResult {
222
113726
    let Expr::Imply(_, e1, e2) = expr else {
223
113246
        return Err(RuleNotApplicable);
224
    };
225

            
226
480
    let Expr::Not(_, p) = e1.as_ref() else {
227
477
        return Err(RuleNotApplicable);
228
    };
229

            
230
3
    let Expr::Not(_, q) = e2.as_ref() else {
231
        return Err(RuleNotApplicable);
232
    };
233

            
234
    // we only negate e1, e2 if they are safe.
235
3
    if !e1.is_safe() || !e2.is_safe() {
236
        return Err(RuleNotApplicable);
237
3
    }
238

            
239
3
    Ok(Reduction::pure(essence_expr!(&q -> &p)))
240
113726
}
241

            
242
/// Simplifies the negation of implication.
243
///
244
/// ```text
245
/// !(p->q) ~> p /\ !q
246
/// ```,
247
///
248
/// where p->q is safe
249
#[register_rule(("Base", 8800))]
250
113726
fn normalise_implies_negation(expr: &Expr, _: &SymbolTable) -> ApplicationResult {
251
113726
    let Expr::Not(_, e1) = expr else {
252
113360
        return Err(RuleNotApplicable);
253
    };
254

            
255
366
    let Expr::Imply(_, p, q) = e1.as_ref() else {
256
363
        return Err(RuleNotApplicable);
257
    };
258

            
259
    // p->q must be safe to negate
260
3
    if !e1.is_safe() {
261
        return Err(RuleNotApplicable);
262
3
    }
263

            
264
3
    Ok(Reduction::pure(essence_expr!(r"&p /\ !&q")))
265
113726
}
266

            
267
/// Applies left distributivity to implication.
268
///
269
/// ```text
270
/// ((r -> p) -> (r->q)) ~> (r -> (p -> q))
271
/// ```
272
///
273
/// This rule relies on CSE to unify the two instances of `r` to a single atom; therefore, it might
274
/// not work as well when optimisations are disabled.
275
///
276
/// Has a higher priority than `normalise_implies_uncurry` as this should apply first. See the
277
/// docstring for `normalise_implies_uncurry`.
278
#[register_rule(("Base", 8800))]
279
113726
fn normalise_implies_left_distributivity(expr: &Expr, _: &SymbolTable) -> ApplicationResult {
280
113726
    let Expr::Imply(_, e1, e2) = expr else {
281
113246
        return Err(RuleNotApplicable);
282
    };
283

            
284
480
    let Expr::Imply(_, r1, p) = e1.as_ref() else {
285
477
        return Err(RuleNotApplicable);
286
    };
287

            
288
3
    let Expr::Imply(_, r2, q) = e2.as_ref() else {
289
        return Err(RuleNotApplicable);
290
    };
291

            
292
    // Instead of checking deep equality, let CSE unify them to a common variable and check for
293
    // that.
294

            
295
3
    let r1_atom: &Atom = r1.as_ref().try_into().or(Err(RuleNotApplicable))?;
296
3
    let r2_atom: &Atom = r2.as_ref().try_into().or(Err(RuleNotApplicable))?;
297

            
298
3
    if !(r1_atom == r2_atom) {
299
        return Err(RuleNotApplicable);
300
3
    }
301

            
302
3
    Ok(Reduction::pure(essence_expr!(&r1 -> (&p -> &q))))
303
113726
}
304

            
305
/// Applies import-export to implication, i.e. uncurrying.
306
///
307
/// ```text
308
/// p -> (q -> r) ~> (p/\q) -> r
309
/// ```
310
///
311
/// This rule has a lower priority of 8400 to allow distributivity, contraposition, etc. to
312
/// apply first.
313
///
314
/// For example, we want to do:
315
///
316
/// ```text
317
/// ((r -> p) -> (r -> q)) ~> (r -> (p -> q))  [left-distributivity]
318
/// (r -> (p -> q)) ~> (r/\p) ~> q [uncurry]
319
/// ```
320
///
321
/// not
322
///
323
/// ```text
324
/// ((r->p) -> (r->q)) ~> ((r->p) /\ r) -> q) ~> [uncurry]
325
/// ```
326
///
327
/// # Rationale
328
///
329
/// With this rule, I am assuming (without empirical evidence) that and is a cheaper constraint
330
/// than implication (in particular, Minion's reifyimply constraint).
331
#[register_rule(("Base", 8400))]
332
103283
fn normalise_implies_uncurry(expr: &Expr, _: &SymbolTable) -> ApplicationResult {
333
103283
    let Expr::Imply(_, p, e1) = expr else {
334
102860
        return Err(RuleNotApplicable);
335
    };
336

            
337
423
    let Expr::Imply(_, q, r) = e1.as_ref() else {
338
414
        return Err(RuleNotApplicable);
339
    };
340

            
341
9
    Ok(Reduction::pure(essence_expr!(r"(&p /\ &q) -> &r")))
342
103283
}