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
7726890
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
7726890
    match expr {
16
        Expr::Union(_, _, _) => Err(RuleNotApplicable),
17
1160
        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
849694
        Expr::AbstractLiteral(_, _) => Err(RuleNotApplicable),
24
35656
        Expr::Comprehension(_, _) => Err(RuleNotApplicable),
25
60
        Expr::AbstractComprehension(_, _) => Err(RuleNotApplicable),
26
        Expr::DominanceRelation(_, _) => Err(RuleNotApplicable),
27
        Expr::FromSolution(_, _) => Err(RuleNotApplicable),
28
        Expr::Metavar(_, _) => Err(RuleNotApplicable),
29
410526
        Expr::UnsafeIndex(_, _, _) => Err(RuleNotApplicable),
30
26300
        Expr::UnsafeSlice(_, _, _) => Err(RuleNotApplicable),
31
360
        Expr::Table(_, _, _) => Err(RuleNotApplicable),
32
430014
        Expr::SafeIndex(_, subject, indices) => {
33
            // partially evaluate matrix literals indexed by a constant.
34

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

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

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

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

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

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

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

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

            
180
62500
            if n_consts == 0 {
181
22360
                return Err(RuleNotApplicable);
182
40140
            }
183

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

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

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

            
235
840
            if let Some(i) = acc {
236
80
                new_vec.push(Expr::Atomic(Default::default(), Atom::Literal(Lit::Int(i))));
237
760
            }
238

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

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

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

            
275
760
            if let Some(i) = acc {
276
40
                new_vec.push(Expr::Atomic(Default::default(), Atom::Literal(Lit::Int(i))));
277
720
            }
278

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

            
294
49628
            let mut has_changed = false;
295

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

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

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

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

            
322
49628
            if !has_changed {
323
49508
                return Err(RuleNotApplicable);
324
120
            }
325

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

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

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

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

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

            
403
156672
            if !has_changed {
404
144880
                Err(RuleNotApplicable)
405
            } else {
406
11792
                if new_vec.is_empty() {
407
40
                    new_vec.push(true.into());
408
11752
                }
409
11792
                Ok(Reduction::pure(Expr::Root(Metadata::new(), new_vec)))
410
            }
411
        }
412
65310
        Expr::Imply(_m, x, y) => {
413
65310
            if let Expr::Atomic(_, Atom::Literal(Lit::Bool(x))) = x.as_ref() {
414
40666
                if *x {
415
                    // (true) -> y ~~> y
416
426
                    return Ok(Reduction::pure(Moo::unwrap_or_clone(y.clone())));
417
                } else {
418
                    // (false) -> y ~~> true
419
40240
                    return Ok(Reduction::pure(Expr::Atomic(Metadata::new(), true.into())));
420
                }
421
24644
            };
422

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

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

            
429
24644
            if x.identical_atom_to(y.as_ref()) {
430
20
                return Ok(Reduction::pure(true.into()));
431
24624
            }
432

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

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

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

            
461
1120
            if x.identical_atom_to(y.as_ref()) {
462
20
                return Ok(Reduction::pure(true.into()));
463
1100
            }
464

            
465
1100
            Err(RuleNotApplicable)
466
        }
467
346580
        Expr::Eq(_, _, _) => Err(RuleNotApplicable),
468
225126
        Expr::Neq(_, _, _) => Err(RuleNotApplicable),
469
51640
        Expr::Geq(_, _, _) => Err(RuleNotApplicable),
470
105572
        Expr::Leq(_, _, _) => Err(RuleNotApplicable),
471
2860
        Expr::Gt(_, _, _) => Err(RuleNotApplicable),
472
32220
        Expr::Lt(_, _, _) => Err(RuleNotApplicable),
473
12100
        Expr::SafeDiv(_, _, _) => Err(RuleNotApplicable),
474
3400
        Expr::UnsafeDiv(_, _, _) => Err(RuleNotApplicable),
475
3640
        Expr::Flatten(_, _, _) => Err(RuleNotApplicable), // TODO: check if anything can be done here
476
32220
        Expr::AllDiff(m, e) => {
477
32220
            let Some(vec) = Moo::unwrap_or_clone(e.clone()).unwrap_list() else {
478
28880
                return Err(RuleNotApplicable);
479
            };
480

            
481
3340
            let mut consts: HashSet<i32> = HashSet::new();
482

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

            
495
            // nothing has changed
496
3340
            Err(RuleNotApplicable)
497
        }
498
23220
        Expr::Neg(_, _) => Err(RuleNotApplicable),
499
50016
        Expr::AuxDeclaration(_, _, _) => Err(RuleNotApplicable),
500
1240
        Expr::UnsafeMod(_, _, _) => Err(RuleNotApplicable),
501
4860
        Expr::SafeMod(_, _, _) => Err(RuleNotApplicable),
502
1832
        Expr::UnsafePow(_, _, _) => Err(RuleNotApplicable),
503
5988
        Expr::SafePow(_, _, _) => Err(RuleNotApplicable),
504
47568
        Expr::Minus(_, _, _) => Err(RuleNotApplicable),
505

            
506
        // As these are in a low level solver form, I'm assuming that these have already been
507
        // simplified and partially evaluated.
508
18020
        Expr::FlatAllDiff(_, _) => Err(RuleNotApplicable),
509
820
        Expr::FlatAbsEq(_, _, _) => Err(RuleNotApplicable),
510
47808
        Expr::FlatIneq(_, _, _, _) => Err(RuleNotApplicable),
511
1800
        Expr::FlatMinusEq(_, _, _) => Err(RuleNotApplicable),
512
1160
        Expr::FlatProductEq(_, _, _, _) => Err(RuleNotApplicable),
513
93400
        Expr::FlatSumLeq(_, _, _) => Err(RuleNotApplicable),
514
95760
        Expr::FlatSumGeq(_, _, _) => Err(RuleNotApplicable),
515
2080
        Expr::FlatWatchedLiteral(_, _, _) => Err(RuleNotApplicable),
516
21400
        Expr::FlatWeightedSumLeq(_, _, _, _) => Err(RuleNotApplicable),
517
21680
        Expr::FlatWeightedSumGeq(_, _, _, _) => Err(RuleNotApplicable),
518
3580
        Expr::MinionDivEqUndefZero(_, _, _, _) => Err(RuleNotApplicable),
519
1160
        Expr::MinionModuloEqUndefZero(_, _, _, _) => Err(RuleNotApplicable),
520
1880
        Expr::MinionPow(_, _, _, _) => Err(RuleNotApplicable),
521
42480
        Expr::MinionReify(_, _, _) => Err(RuleNotApplicable),
522
14656
        Expr::MinionReifyImply(_, _, _) => Err(RuleNotApplicable),
523
660
        Expr::MinionWInIntervalSet(_, _, _) => Err(RuleNotApplicable),
524
320
        Expr::MinionWInSet(_, _, _) => Err(RuleNotApplicable),
525
20992
        Expr::MinionElementOne(_, _, _, _) => Err(RuleNotApplicable),
526
208220
        Expr::SATInt(_, _, _, _) => Err(RuleNotApplicable),
527
        Expr::PairwiseSum(_, _, _) => Err(RuleNotApplicable),
528
        Expr::PairwiseProduct(_, _, _) => Err(RuleNotApplicable),
529
        Expr::Defined(_, _) => todo!(),
530
        Expr::Range(_, _) => todo!(),
531
        Expr::Image(_, _, _) => todo!(),
532
        Expr::ImageSet(_, _, _) => todo!(),
533
        Expr::PreImage(_, _, _) => todo!(),
534
        Expr::Inverse(_, _, _) => todo!(),
535
        Expr::Restrict(_, _, _) => todo!(),
536
400
        Expr::LexLt(_, _, _) => Err(RuleNotApplicable),
537
24780
        Expr::LexLeq(_, _, _) => Err(RuleNotApplicable),
538
        Expr::LexGt(_, _, _) => Err(RuleNotApplicable),
539
        Expr::LexGeq(_, _, _) => Err(RuleNotApplicable),
540
80
        Expr::FlatLexLt(_, _, _) => Err(RuleNotApplicable),
541
160
        Expr::FlatLexLeq(_, _, _) => Err(RuleNotApplicable),
542
    }
543
7726890
}
544

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

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

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

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

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

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

            
591
49628
    false
592
49628
}