1
use std::collections::HashSet;
2

            
3
use crate::ast::Typeable;
4
use crate::{
5
    ast::{Atom, Expression as Expr, Literal as Lit, Metadata, Moo, ReturnType},
6
    into_matrix_expr,
7
    rule_engine::{ApplicationError::RuleNotApplicable, ApplicationResult, Reduction},
8
};
9
use itertools::iproduct;
10

            
11
16301720
pub fn run_partial_evaluator(expr: &Expr) -> ApplicationResult {
12
    // NOTE: If nothing changes, we must return RuleNotApplicable, or the rewriter will try this
13
    // rule infinitely!
14
    // This is why we always check whether we found a constant or not.
15
16301720
    match expr {
16
        Expr::Union(_, _, _) => Err(RuleNotApplicable),
17
2320
        Expr::In(_, _, _) => Err(RuleNotApplicable),
18
        Expr::Intersect(_, _, _) => Err(RuleNotApplicable),
19
        Expr::Supset(_, _, _) => Err(RuleNotApplicable),
20
        Expr::SupsetEq(_, _, _) => Err(RuleNotApplicable),
21
        Expr::Subset(_, _, _) => Err(RuleNotApplicable),
22
        Expr::SubsetEq(_, _, _) => Err(RuleNotApplicable),
23
1762668
        Expr::AbstractLiteral(_, _) => Err(RuleNotApplicable),
24
71312
        Expr::Comprehension(_, _) => Err(RuleNotApplicable),
25
120
        Expr::AbstractComprehension(_, _) => Err(RuleNotApplicable),
26
        Expr::DominanceRelation(_, _) => Err(RuleNotApplicable),
27
        Expr::FromSolution(_, _) => Err(RuleNotApplicable),
28
        Expr::Metavar(_, _) => Err(RuleNotApplicable),
29
821052
        Expr::UnsafeIndex(_, _, _) => Err(RuleNotApplicable),
30
52600
        Expr::UnsafeSlice(_, _, _) => Err(RuleNotApplicable),
31
1440
        Expr::Table(_, _, _) => Err(RuleNotApplicable),
32
240
        Expr::NegativeTable(_, _, _) => Err(RuleNotApplicable),
33
860028
        Expr::SafeIndex(_, subject, indices) => {
34
            // partially evaluate matrix literals indexed by a constant.
35

            
36
            // subject must be a matrix literal
37
860028
            let (es, index_domain) = Moo::unwrap_or_clone(subject.clone())
38
860028
                .unwrap_matrix_unchecked()
39
860028
                .ok_or(RuleNotApplicable)?;
40

            
41
            // must be indexing a 1d matrix.
42
            //
43
            // for n-d matrices, wait for the `remove_dimension_from_matrix_indexing` rule to run
44
            // first. This reduces n-d indexing operations to 1d.
45
194856
            if indices.len() != 1 {
46
560
                return Err(RuleNotApplicable);
47
194296
            }
48

            
49
            // the index must be a number
50
194296
            let index: i32 = (&indices[0]).try_into().map_err(|_| RuleNotApplicable)?;
51

            
52
            // index domain must be a single integer range with a lower bound
53
            if let Some(ranges) = index_domain.as_int_ground()
54
                && ranges.len() == 1
55
                && let Some(from) = ranges[0].low()
56
            {
57
                let zero_indexed_index = index - from;
58
                Ok(Reduction::pure(es[zero_indexed_index as usize].clone()))
59
            } else {
60
                Err(RuleNotApplicable)
61
            }
62
        }
63
73320
        Expr::SafeSlice(_, _, _) => Err(RuleNotApplicable),
64
32048
        Expr::InDomain(_, x, domain) => {
65
32048
            if let Expr::Atomic(_, Atom::Reference(decl)) = x.as_ref() {
66
8528
                let decl_domain = decl
67
8528
                    .domain()
68
8528
                    .ok_or(RuleNotApplicable)?
69
8528
                    .resolve()
70
8528
                    .ok_or(RuleNotApplicable)?;
71
8528
                let domain = domain.resolve().ok_or(RuleNotApplicable)?;
72

            
73
8528
                let intersection = decl_domain
74
8528
                    .intersect(&domain)
75
8528
                    .map_err(|_| RuleNotApplicable)?;
76

            
77
                // if the declaration's domain is a subset of domain, expr is always true.
78
8528
                if &intersection == decl_domain.as_ref() {
79
7088
                    Ok(Reduction::pure(Expr::Atomic(Metadata::new(), true.into())))
80
                }
81
                // if no elements of declaration's domain are in the domain (i.e. they have no
82
                // intersection), expr is always false.
83
                //
84
                // Only check this when the intersection is a finite integer domain, as we
85
                // currently don't have a way to check whether other domain kinds are empty or not.
86
                //
87
                // we should expand this to cover more domain types in the future.
88
1440
                else if let Ok(values_in_domain) = intersection.values_i32()
89
1440
                    && values_in_domain.is_empty()
90
                {
91
                    Ok(Reduction::pure(Expr::Atomic(Metadata::new(), false.into())))
92
                } else {
93
1440
                    Err(RuleNotApplicable)
94
                }
95
23520
            } else if let Expr::Atomic(_, Atom::Literal(lit)) = x.as_ref() {
96
                if domain
97
                    .resolve()
98
                    .ok_or(RuleNotApplicable)?
99
                    .contains(lit)
100
                    .ok()
101
                    .ok_or(RuleNotApplicable)?
102
                {
103
                    Ok(Reduction::pure(Expr::Atomic(Metadata::new(), true.into())))
104
                } else {
105
                    Ok(Reduction::pure(Expr::Atomic(Metadata::new(), false.into())))
106
                }
107
            } else {
108
23520
                Err(RuleNotApplicable)
109
            }
110
        }
111
37704
        Expr::Bubble(_, expr, cond) => {
112
            // definition of bubble is "expr is valid as long as cond is true"
113
            //
114
            // check if cond is true and pop the bubble!
115
37704
            if let Expr::Atomic(_, Atom::Literal(Lit::Bool(true))) = cond.as_ref() {
116
19424
                Ok(Reduction::pure(Moo::unwrap_or_clone(expr.clone())))
117
            } else {
118
18280
                Err(RuleNotApplicable)
119
            }
120
        }
121
7353320
        Expr::Atomic(_, _) => Err(RuleNotApplicable),
122
1040
        Expr::ToInt(_, expression) => {
123
1040
            if expression.return_type() == ReturnType::Int {
124
                Ok(Reduction::pure(Moo::unwrap_or_clone(expression.clone())))
125
            } else {
126
1040
                Err(RuleNotApplicable)
127
            }
128
        }
129
6280
        Expr::Abs(m, e) => match e.as_ref() {
130
80
            Expr::Neg(_, inner) => Ok(Reduction::pure(Expr::Abs(m.clone(), inner.clone()))),
131
6200
            _ => Err(RuleNotApplicable),
132
        },
133
657664
        Expr::Sum(m, vec) => {
134
657664
            let vec = Moo::unwrap_or_clone(vec.clone())
135
657664
                .unwrap_list()
136
657664
                .ok_or(RuleNotApplicable)?;
137
401616
            let mut acc = 0;
138
401616
            let mut n_consts = 0;
139
401616
            let mut new_vec: Vec<Expr> = Vec::new();
140
958632
            for expr in vec {
141
250240
                if let Expr::Atomic(_, Atom::Literal(Lit::Int(x))) = expr {
142
250240
                    acc += x;
143
250240
                    n_consts += 1;
144
708392
                } else {
145
708392
                    new_vec.push(expr);
146
708392
                }
147
            }
148
401616
            if acc != 0 {
149
222560
                new_vec.push(Expr::Atomic(
150
222560
                    Default::default(),
151
222560
                    Atom::Literal(Lit::Int(acc)),
152
222560
                ));
153
224736
            }
154

            
155
401616
            if n_consts <= 1 {
156
397856
                Err(RuleNotApplicable)
157
            } else {
158
3760
                Ok(Reduction::pure(Expr::Sum(
159
3760
                    m.clone(),
160
3760
                    Moo::new(into_matrix_expr![new_vec]),
161
3760
                )))
162
            }
163
        }
164

            
165
202960
        Expr::Product(m, vec) => {
166
202960
            let mut acc = 1;
167
202960
            let mut n_consts = 0;
168
202960
            let mut new_vec: Vec<Expr> = Vec::new();
169
202960
            let vec = Moo::unwrap_or_clone(vec.clone())
170
202960
                .unwrap_list()
171
202960
                .ok_or(RuleNotApplicable)?;
172
246080
            for expr in vec {
173
80280
                if let Expr::Atomic(_, Atom::Literal(Lit::Int(x))) = expr {
174
80280
                    acc *= x;
175
80280
                    n_consts += 1;
176
165800
                } else {
177
165800
                    new_vec.push(expr);
178
165800
                }
179
            }
180

            
181
125000
            if n_consts == 0 {
182
44720
                return Err(RuleNotApplicable);
183
80280
            }
184

            
185
80280
            new_vec.push(Expr::Atomic(
186
80280
                Default::default(),
187
80280
                Atom::Literal(Lit::Int(acc)),
188
80280
            ));
189
80280
            let new_product = Expr::Product(m.clone(), Moo::new(into_matrix_expr![new_vec]));
190

            
191
80280
            if acc == 0 {
192
                // if safe, 0 * exprs ~> 0
193
                // otherwise, just return 0* exprs
194
                if new_product.is_safe() {
195
                    Ok(Reduction::pure(Expr::Atomic(
196
                        Default::default(),
197
                        Atom::Literal(Lit::Int(0)),
198
                    )))
199
                } else {
200
                    Ok(Reduction::pure(new_product))
201
                }
202
80280
            } else if n_consts == 1 {
203
                // acc !=0, only one constant
204
80280
                Err(RuleNotApplicable)
205
            } else {
206
                // acc !=0, multiple constants found
207
                Ok(Reduction::pure(new_product))
208
            }
209
        }
210

            
211
13920
        Expr::Min(m, e) => {
212
13920
            let Some(vec) = Moo::unwrap_or_clone(e.clone()).unwrap_list() else {
213
12240
                return Err(RuleNotApplicable);
214
            };
215
1680
            let mut acc: Option<i32> = None;
216
1680
            let mut n_consts = 0;
217
1680
            let mut new_vec: Vec<Expr> = Vec::new();
218
3600
            for expr in vec {
219
160
                if let Expr::Atomic(_, Atom::Literal(Lit::Int(x))) = expr {
220
160
                    n_consts += 1;
221
160
                    acc = match acc {
222
                        Some(i) => {
223
                            if i > x {
224
                                Some(x)
225
                            } else {
226
                                Some(i)
227
                            }
228
                        }
229
160
                        None => Some(x),
230
                    };
231
3440
                } else {
232
3440
                    new_vec.push(expr);
233
3440
                }
234
            }
235

            
236
1680
            if let Some(i) = acc {
237
160
                new_vec.push(Expr::Atomic(Default::default(), Atom::Literal(Lit::Int(i))));
238
1520
            }
239

            
240
1680
            if n_consts <= 1 {
241
1680
                Err(RuleNotApplicable)
242
            } else {
243
                Ok(Reduction::pure(Expr::Min(
244
                    m.clone(),
245
                    Moo::new(into_matrix_expr![new_vec]),
246
                )))
247
            }
248
        }
249

            
250
12480
        Expr::Max(m, e) => {
251
12480
            let Some(vec) = Moo::unwrap_or_clone(e.clone()).unwrap_list() else {
252
10960
                return Err(RuleNotApplicable);
253
            };
254

            
255
1520
            let mut acc: Option<i32> = None;
256
1520
            let mut n_consts = 0;
257
1520
            let mut new_vec: Vec<Expr> = Vec::new();
258
3120
            for expr in vec {
259
80
                if let Expr::Atomic(_, Atom::Literal(Lit::Int(x))) = expr {
260
80
                    n_consts += 1;
261
80
                    acc = match acc {
262
                        Some(i) => {
263
                            if i < x {
264
                                Some(x)
265
                            } else {
266
                                Some(i)
267
                            }
268
                        }
269
80
                        None => Some(x),
270
                    };
271
3040
                } else {
272
3040
                    new_vec.push(expr);
273
3040
                }
274
            }
275

            
276
1520
            if let Some(i) = acc {
277
80
                new_vec.push(Expr::Atomic(Default::default(), Atom::Literal(Lit::Int(i))));
278
1440
            }
279

            
280
1520
            if n_consts <= 1 {
281
1520
                Err(RuleNotApplicable)
282
            } else {
283
                Ok(Reduction::pure(Expr::Max(
284
                    m.clone(),
285
                    Moo::new(into_matrix_expr![new_vec]),
286
                )))
287
            }
288
        }
289
21280
        Expr::Not(_, _) => Err(RuleNotApplicable),
290
211528
        Expr::Or(m, e) => {
291
211528
            let Some(terms) = Moo::unwrap_or_clone(e.clone()).unwrap_list() else {
292
102772
                return Err(RuleNotApplicable);
293
            };
294

            
295
108756
            let mut has_changed = false;
296

            
297
            // 2. boolean literals
298
108756
            let mut new_terms = vec![];
299
230428
            for expr in terms {
300
240
                if let Expr::Atomic(_, Atom::Literal(Lit::Bool(x))) = expr {
301
240
                    has_changed = true;
302

            
303
                    // true ~~> entire or is true
304
                    // false ~~> remove false from the or
305
240
                    if x {
306
                        return Ok(Reduction::pure(true.into()));
307
240
                    }
308
230188
                } else {
309
230188
                    new_terms.push(expr);
310
230188
                }
311
            }
312

            
313
            // 2. check pairwise tautologies.
314
108756
            if check_pairwise_or_tautologies(&new_terms) {
315
                return Ok(Reduction::pure(true.into()));
316
108756
            }
317

            
318
            // 3. empty or ~~> false
319
108756
            if new_terms.is_empty() {
320
                return Ok(Reduction::pure(false.into()));
321
108756
            }
322

            
323
108756
            if !has_changed {
324
108516
                return Err(RuleNotApplicable);
325
240
            }
326

            
327
240
            Ok(Reduction::pure(Expr::Or(
328
240
                m.clone(),
329
240
                Moo::new(into_matrix_expr![new_terms]),
330
240
            )))
331
        }
332
248668
        Expr::And(_, e) => {
333
248668
            let Some(vec) = Moo::unwrap_or_clone(e.clone()).unwrap_list() else {
334
144216
                return Err(RuleNotApplicable);
335
            };
336
104452
            let mut new_vec: Vec<Expr> = Vec::new();
337
104452
            let mut has_const: bool = false;
338
283228
            for expr in vec {
339
82320
                if let Expr::Atomic(_, Atom::Literal(Lit::Bool(x))) = expr {
340
82320
                    has_const = true;
341
82320
                    if !x {
342
360
                        return Ok(Reduction::pure(Expr::Atomic(
343
360
                            Default::default(),
344
360
                            Atom::Literal(Lit::Bool(false)),
345
360
                        )));
346
81960
                    }
347
200908
                } else {
348
200908
                    new_vec.push(expr);
349
200908
                }
350
            }
351

            
352
104092
            if !has_const {
353
102372
                Err(RuleNotApplicable)
354
            } else {
355
1720
                Ok(Reduction::pure(Expr::And(
356
1720
                    Metadata::new(),
357
1720
                    Moo::new(into_matrix_expr![new_vec]),
358
1720
                )))
359
            }
360
        }
361

            
362
        // similar to And, but booleans are returned wrapped in Root.
363
481312
        Expr::Root(_, es) => {
364
481312
            match es.as_slice() {
365
481312
                [] => Err(RuleNotApplicable),
366
                // want to unwrap nested ands
367
479372
                [Expr::And(_, _)] => Ok(()),
368
                // root([true]) / root([false]) are already evaluated
369
150048
                [_] => Err(RuleNotApplicable),
370
318244
                [_, _, ..] => Ok(()),
371
151988
            }?;
372

            
373
329324
            let mut new_vec: Vec<Expr> = Vec::new();
374
329324
            let mut has_changed: bool = false;
375
1912148
            for expr in es {
376
1880
                match expr {
377
1880
                    Expr::Atomic(_, Atom::Literal(Lit::Bool(x))) => {
378
1880
                        has_changed = true;
379
1880
                        if !x {
380
                            // false
381
120
                            return Ok(Reduction::pure(Expr::Root(
382
120
                                Metadata::new(),
383
120
                                vec![Expr::Atomic(
384
120
                                    Default::default(),
385
120
                                    Atom::Literal(Lit::Bool(false)),
386
120
                                )],
387
120
                            )));
388
1760
                        }
389
                        // remove trues
390
                    }
391

            
392
                    // flatten ands in root
393
143936
                    Expr::And(_, vecs) => match Moo::unwrap_or_clone(vecs.clone()).unwrap_list() {
394
25624
                        Some(mut list) => {
395
25624
                            has_changed = true;
396
25624
                            new_vec.append(&mut list);
397
25624
                        }
398
118312
                        None => new_vec.push(expr.clone()),
399
                    },
400
1766332
                    _ => new_vec.push(expr.clone()),
401
                }
402
            }
403

            
404
329204
            if !has_changed {
405
303220
                Err(RuleNotApplicable)
406
            } else {
407
25984
                if new_vec.is_empty() {
408
80
                    new_vec.push(true.into());
409
25904
                }
410
25984
                Ok(Reduction::pure(Expr::Root(Metadata::new(), new_vec)))
411
            }
412
        }
413
130620
        Expr::Imply(_m, x, y) => {
414
130620
            if let Expr::Atomic(_, Atom::Literal(Lit::Bool(x))) = x.as_ref() {
415
81332
                if *x {
416
                    // (true) -> y ~~> y
417
852
                    return Ok(Reduction::pure(Moo::unwrap_or_clone(y.clone())));
418
                } else {
419
                    // (false) -> y ~~> true
420
80480
                    return Ok(Reduction::pure(Expr::Atomic(Metadata::new(), true.into())));
421
                }
422
49288
            };
423

            
424
            // reflexivity: p -> p ~> true
425

            
426
            // instead of checking syntactic equivalence of a possibly deep expression,
427
            // let identical-CSE turn them into identical variables first. Then, check if they are
428
            // identical variables.
429

            
430
49288
            if x.identical_atom_to(y.as_ref()) {
431
40
                return Ok(Reduction::pure(true.into()));
432
49248
            }
433

            
434
49248
            Err(RuleNotApplicable)
435
        }
436
2280
        Expr::Iff(_m, x, y) => {
437
2280
            if let Expr::Atomic(_, Atom::Literal(Lit::Bool(x))) = x.as_ref() {
438
40
                if *x {
439
                    // (true) <-> y ~~> y
440
                    return Ok(Reduction::pure(Moo::unwrap_or_clone(y.clone())));
441
                } else {
442
                    // (false) <-> y ~~> !y
443
40
                    return Ok(Reduction::pure(Expr::Not(Metadata::new(), y.clone())));
444
                }
445
2240
            };
446
2240
            if let Expr::Atomic(_, Atom::Literal(Lit::Bool(y))) = y.as_ref() {
447
                if *y {
448
                    // x <-> (true) ~~> x
449
                    return Ok(Reduction::pure(Moo::unwrap_or_clone(x.clone())));
450
                } else {
451
                    // x <-> (false) ~~> !x
452
                    return Ok(Reduction::pure(Expr::Not(Metadata::new(), x.clone())));
453
                }
454
2240
            };
455

            
456
            // reflexivity: p <-> p ~> true
457

            
458
            // instead of checking syntactic equivalence of a possibly deep expression,
459
            // let identical-CSE turn them into identical variables first. Then, check if they are
460
            // identical variables.
461

            
462
2240
            if x.identical_atom_to(y.as_ref()) {
463
40
                return Ok(Reduction::pure(true.into()));
464
2200
            }
465

            
466
2200
            Err(RuleNotApplicable)
467
        }
468
707000
        Expr::Eq(_, _, _) => Err(RuleNotApplicable),
469
453092
        Expr::Neq(_, _, _) => Err(RuleNotApplicable),
470
106520
        Expr::Geq(_, _, _) => Err(RuleNotApplicable),
471
214864
        Expr::Leq(_, _, _) => Err(RuleNotApplicable),
472
5280
        Expr::Gt(_, _, _) => Err(RuleNotApplicable),
473
64080
        Expr::Lt(_, _, _) => Err(RuleNotApplicable),
474
24480
        Expr::SafeDiv(_, _, _) => Err(RuleNotApplicable),
475
21240
        Expr::UnsafeDiv(_, _, _) => Err(RuleNotApplicable),
476
7280
        Expr::Flatten(_, _, _) => Err(RuleNotApplicable), // TODO: check if anything can be done here
477
64440
        Expr::AllDiff(m, e) => {
478
64440
            let Some(vec) = Moo::unwrap_or_clone(e.clone()).unwrap_list() else {
479
57760
                return Err(RuleNotApplicable);
480
            };
481

            
482
6680
            let mut consts: HashSet<i32> = HashSet::new();
483

            
484
            // check for duplicate constant values which would fail the constraint
485
25320
            for expr in vec {
486
1080
                if let Expr::Atomic(_, Atom::Literal(Lit::Int(x))) = expr
487
1080
                    && !consts.insert(x)
488
                {
489
                    return Ok(Reduction::pure(Expr::Atomic(
490
                        m.clone(),
491
                        Atom::Literal(Lit::Bool(false)),
492
                    )));
493
25320
                }
494
            }
495

            
496
            // nothing has changed
497
6680
            Err(RuleNotApplicable)
498
        }
499
49280
        Expr::Neg(_, _) => Err(RuleNotApplicable),
500
99952
        Expr::AuxDeclaration(_, _, _) => Err(RuleNotApplicable),
501
2480
        Expr::UnsafeMod(_, _, _) => Err(RuleNotApplicable),
502
9720
        Expr::SafeMod(_, _, _) => Err(RuleNotApplicable),
503
3664
        Expr::UnsafePow(_, _, _) => Err(RuleNotApplicable),
504
11976
        Expr::SafePow(_, _, _) => Err(RuleNotApplicable),
505
95136
        Expr::Minus(_, _, _) => Err(RuleNotApplicable),
506

            
507
        // As these are in a low level solver form, I'm assuming that these have already been
508
        // simplified and partially evaluated.
509
36040
        Expr::FlatAllDiff(_, _) => Err(RuleNotApplicable),
510
1640
        Expr::FlatAbsEq(_, _, _) => Err(RuleNotApplicable),
511
95616
        Expr::FlatIneq(_, _, _, _) => Err(RuleNotApplicable),
512
3680
        Expr::FlatMinusEq(_, _, _) => Err(RuleNotApplicable),
513
2320
        Expr::FlatProductEq(_, _, _, _) => Err(RuleNotApplicable),
514
186800
        Expr::FlatSumLeq(_, _, _) => Err(RuleNotApplicable),
515
191520
        Expr::FlatSumGeq(_, _, _) => Err(RuleNotApplicable),
516
4160
        Expr::FlatWatchedLiteral(_, _, _) => Err(RuleNotApplicable),
517
42800
        Expr::FlatWeightedSumLeq(_, _, _, _) => Err(RuleNotApplicable),
518
43360
        Expr::FlatWeightedSumGeq(_, _, _, _) => Err(RuleNotApplicable),
519
7320
        Expr::MinionDivEqUndefZero(_, _, _, _) => Err(RuleNotApplicable),
520
2320
        Expr::MinionModuloEqUndefZero(_, _, _, _) => Err(RuleNotApplicable),
521
3760
        Expr::MinionPow(_, _, _, _) => Err(RuleNotApplicable),
522
84960
        Expr::MinionReify(_, _, _) => Err(RuleNotApplicable),
523
29312
        Expr::MinionReifyImply(_, _, _) => Err(RuleNotApplicable),
524
1320
        Expr::MinionWInIntervalSet(_, _, _) => Err(RuleNotApplicable),
525
640
        Expr::MinionWInSet(_, _, _) => Err(RuleNotApplicable),
526
41984
        Expr::MinionElementOne(_, _, _, _) => Err(RuleNotApplicable),
527
472640
        Expr::SATInt(_, _, _, _) => Err(RuleNotApplicable),
528
        Expr::PairwiseSum(_, _, _) => Err(RuleNotApplicable),
529
        Expr::PairwiseProduct(_, _, _) => Err(RuleNotApplicable),
530
        Expr::Defined(_, _) => todo!(),
531
        Expr::Range(_, _) => todo!(),
532
        Expr::Image(_, _, _) => todo!(),
533
        Expr::ImageSet(_, _, _) => todo!(),
534
        Expr::PreImage(_, _, _) => todo!(),
535
        Expr::Inverse(_, _, _) => todo!(),
536
        Expr::Restrict(_, _, _) => todo!(),
537
800
        Expr::LexLt(_, _, _) => Err(RuleNotApplicable),
538
49560
        Expr::LexLeq(_, _, _) => Err(RuleNotApplicable),
539
        Expr::LexGt(_, _, _) => Err(RuleNotApplicable),
540
        Expr::LexGeq(_, _, _) => Err(RuleNotApplicable),
541
160
        Expr::FlatLexLt(_, _, _) => Err(RuleNotApplicable),
542
320
        Expr::FlatLexLeq(_, _, _) => Err(RuleNotApplicable),
543
    }
544
16301720
}
545

            
546
/// Checks for tautologies involving pairs of terms inside an or, returning true if one is found.
547
///
548
/// This applies the following rules:
549
///
550
/// ```text
551
/// (p->q) \/ (q->p) ~> true    [totality of implication]
552
/// (p->q) \/ (p-> !q) ~> true  [conditional excluded middle]
553
/// ```
554
///
555
108756
fn check_pairwise_or_tautologies(or_terms: &[Expr]) -> bool {
556
    // Collect terms that are structurally identical to the rule input.
557
    // Then, try the rules on these terms, also checking the other conditions of the rules.
558

            
559
    // stores (p,q) in p -> q
560
108756
    let mut p_implies_q: Vec<(&Expr, &Expr)> = vec![];
561

            
562
    // stores (p,q) in p -> !q
563
108756
    let mut p_implies_not_q: Vec<(&Expr, &Expr)> = vec![];
564

            
565
230188
    for term in or_terms.iter() {
566
230188
        if let Expr::Imply(_, p, q) = term {
567
            // we use identical_atom_to for equality later on, so these sets are mutually exclusive.
568
            //
569
            // in general however, p -> !q would be in p_implies_q as (p,!q)
570
560
            if let Expr::Not(_, q_1) = q.as_ref() {
571
                p_implies_not_q.push((p.as_ref(), q_1.as_ref()));
572
560
            } else {
573
560
                p_implies_q.push((p.as_ref(), q.as_ref()));
574
560
            }
575
229628
        }
576
    }
577

            
578
    // `(p->q) \/ (q->p) ~> true    [totality of implication]`
579
108756
    for ((p1, q1), (q2, p2)) in iproduct!(p_implies_q.iter(), p_implies_q.iter()) {
580
880
        if p1.identical_atom_to(p2) && q1.identical_atom_to(q2) {
581
            return true;
582
880
        }
583
    }
584

            
585
    // `(p->q) \/ (p-> !q) ~> true`    [conditional excluded middle]
586
108756
    for ((p1, q1), (p2, q2)) in iproduct!(p_implies_q.iter(), p_implies_not_q.iter()) {
587
        if p1.identical_atom_to(p2) && q1.identical_atom_to(q2) {
588
            return true;
589
        }
590
    }
591

            
592
108756
    false
593
108756
}