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
15309510
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
15309510
    match expr {
16
        Expr::Union(_, _, _) => Err(RuleNotApplicable),
17
1640
        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
1622214
        Expr::AbstractLiteral(_, _) => Err(RuleNotApplicable),
24
69496
        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
821466
        Expr::UnsafeIndex(_, _, _) => Err(RuleNotApplicable),
30
47840
        Expr::UnsafeSlice(_, _, _) => Err(RuleNotApplicable),
31
720
        Expr::Table(_, _, _) => Err(RuleNotApplicable),
32
120
        Expr::NegativeTable(_, _, _) => Err(RuleNotApplicable),
33
840794
        Expr::SafeIndex(_, subject, indices) => {
34
            // partially evaluate matrix literals indexed by a constant.
35

            
36
            // subject must be a matrix literal
37
840794
            let (es, index_domain) = Moo::unwrap_or_clone(subject.clone())
38
840794
                .unwrap_matrix_unchecked()
39
840794
                .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
189388
            if indices.len() != 1 {
46
560
                return Err(RuleNotApplicable);
47
188828
            }
48

            
49
            // the index must be a number
50
188828
            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
54000
        Expr::SafeSlice(_, _, _) => Err(RuleNotApplicable),
64
27164
        Expr::InDomain(_, x, domain) => {
65
27164
            if let Expr::Atomic(_, Atom::Reference(decl)) = x.as_ref() {
66
7964
                let decl_domain = decl
67
7964
                    .domain()
68
7964
                    .ok_or(RuleNotApplicable)?
69
7964
                    .resolve()
70
7964
                    .ok_or(RuleNotApplicable)?;
71
7964
                let domain = domain.resolve().ok_or(RuleNotApplicable)?;
72

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

            
77
                // if the declaration's domain is a subset of domain, expr is always true.
78
7964
                if &intersection == decl_domain.as_ref() {
79
6344
                    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
1620
                else if let Ok(values_in_domain) = intersection.values_i32()
89
1620
                    && values_in_domain.is_empty()
90
                {
91
                    Ok(Reduction::pure(Expr::Atomic(Metadata::new(), false.into())))
92
                } else {
93
1620
                    Err(RuleNotApplicable)
94
                }
95
19200
            } 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
19200
                Err(RuleNotApplicable)
109
            }
110
        }
111
35672
        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
35672
            if let Expr::Atomic(_, Atom::Literal(Lit::Bool(true))) = cond.as_ref() {
116
18992
                Ok(Reduction::pure(Moo::unwrap_or_clone(expr.clone())))
117
            } else {
118
16680
                Err(RuleNotApplicable)
119
            }
120
        }
121
6949336
        Expr::Atomic(_, _) => Err(RuleNotApplicable),
122
760
        Expr::ToInt(_, expression) => {
123
760
            if expression.return_type() == ReturnType::Int {
124
                Ok(Reduction::pure(Moo::unwrap_or_clone(expression.clone())))
125
            } else {
126
760
                Err(RuleNotApplicable)
127
            }
128
        }
129
5640
        Expr::Abs(m, e) => match e.as_ref() {
130
80
            Expr::Neg(_, inner) => Ok(Reduction::pure(Expr::Abs(m.clone(), inner.clone()))),
131
5560
            _ => Err(RuleNotApplicable),
132
        },
133
599732
        Expr::Sum(m, vec) => {
134
599732
            let vec = Moo::unwrap_or_clone(vec.clone())
135
599732
                .unwrap_list()
136
599732
                .ok_or(RuleNotApplicable)?;
137
459488
            let mut acc = 0;
138
459488
            let mut n_consts = 0;
139
459488
            let mut new_vec: Vec<Expr> = Vec::new();
140
1075016
            for expr in vec {
141
296160
                if let Expr::Atomic(_, Atom::Literal(Lit::Int(x))) = expr {
142
296160
                    acc += x;
143
296160
                    n_consts += 1;
144
778856
                } else {
145
778856
                    new_vec.push(expr);
146
778856
                }
147
            }
148
459488
            if acc != 0 {
149
265600
                new_vec.push(Expr::Atomic(
150
265600
                    Default::default(),
151
265600
                    Atom::Literal(Lit::Int(acc)),
152
265600
                ));
153
266688
            }
154

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

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

            
181
154460
            if n_consts == 0 {
182
65360
                return Err(RuleNotApplicable);
183
89100
            }
184

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

            
191
89100
            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
89100
            } else if n_consts == 1 {
203
                // acc !=0, only one constant
204
89060
                Err(RuleNotApplicable)
205
            } else {
206
                // acc !=0, multiple constants found
207
40
                Ok(Reduction::pure(new_product))
208
            }
209
        }
210

            
211
13620
        Expr::Min(m, e) => {
212
13620
            let Some(vec) = Moo::unwrap_or_clone(e.clone()).unwrap_list() else {
213
11940
                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
12420
        Expr::Max(m, e) => {
251
12420
            let Some(vec) = Moo::unwrap_or_clone(e.clone()).unwrap_list() else {
252
10900
                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
20860
        Expr::Not(_, _) => Err(RuleNotApplicable),
290
201334
        Expr::Or(m, e) => {
291
201334
            let Some(terms) = Moo::unwrap_or_clone(e.clone()).unwrap_list() else {
292
97586
                return Err(RuleNotApplicable);
293
            };
294

            
295
103748
            let mut has_changed = false;
296

            
297
            // 2. boolean literals
298
103748
            let mut new_terms = vec![];
299
216084
            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
215844
                } else {
309
215844
                    new_terms.push(expr);
310
215844
                }
311
            }
312

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

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

            
323
103708
            if !has_changed {
324
103468
                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
203164
        Expr::And(_, e) => {
333
203164
            let Some(vec) = Moo::unwrap_or_clone(e.clone()).unwrap_list() else {
334
100748
                return Err(RuleNotApplicable);
335
            };
336
102416
            let mut new_vec: Vec<Expr> = Vec::new();
337
102416
            let mut has_const: bool = false;
338
281594
            for expr in vec {
339
81960
                if let Expr::Atomic(_, Atom::Literal(Lit::Bool(x))) = expr {
340
81960
                    has_const = true;
341
81960
                    if !x {
342
360
                        return Ok(Reduction::pure(Expr::Atomic(
343
360
                            Default::default(),
344
360
                            Atom::Literal(Lit::Bool(false)),
345
360
                        )));
346
81600
                    }
347
199634
                } else {
348
199634
                    new_vec.push(expr);
349
199634
                }
350
            }
351

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

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

            
373
304272
            let mut new_vec: Vec<Expr> = Vec::new();
374
304272
            let mut has_changed: bool = false;
375
1773704
            for expr in es {
376
1840
                match expr {
377
1840
                    Expr::Atomic(_, Atom::Literal(Lit::Bool(x))) => {
378
1840
                        has_changed = true;
379
1840
                        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
1720
                        }
389
                        // remove trues
390
                    }
391

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

            
404
304152
            if !has_changed {
405
280080
                Err(RuleNotApplicable)
406
            } else {
407
24072
                if new_vec.is_empty() {
408
100
                    new_vec.push(true.into());
409
23972
                }
410
24072
                Ok(Reduction::pure(Expr::Root(Metadata::new(), new_vec)))
411
            }
412
        }
413
130450
        Expr::Imply(_m, x, y) => {
414
130450
            if let Expr::Atomic(_, Atom::Literal(Lit::Bool(x))) = x.as_ref() {
415
81326
                if *x {
416
                    // (true) -> y ~~> y
417
846
                    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
49124
            };
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
49124
            if x.identical_atom_to(y.as_ref()) {
431
40
                return Ok(Reduction::pure(true.into()));
432
49084
            }
433

            
434
49084
            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
661032
        Expr::Eq(_, _, _) => Err(RuleNotApplicable),
469
439126
        Expr::Neq(_, _, _) => Err(RuleNotApplicable),
470
100940
        Expr::Geq(_, _, _) => Err(RuleNotApplicable),
471
205952
        Expr::Leq(_, _, _) => Err(RuleNotApplicable),
472
6140
        Expr::Gt(_, _, _) => Err(RuleNotApplicable),
473
64800
        Expr::Lt(_, _, _) => Err(RuleNotApplicable),
474
24060
        Expr::SafeDiv(_, _, _) => Err(RuleNotApplicable),
475
20740
        Expr::UnsafeDiv(_, _, _) => Err(RuleNotApplicable),
476
7280
        Expr::Flatten(_, _, _) => Err(RuleNotApplicable), // TODO: check if anything can be done here
477
63220
        Expr::AllDiff(m, e) => {
478
63220
            let Some(vec) = Moo::unwrap_or_clone(e.clone()).unwrap_list() else {
479
56740
                return Err(RuleNotApplicable);
480
            };
481

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

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

            
496
            // nothing has changed
497
6480
            Err(RuleNotApplicable)
498
        }
499
44640
        Expr::Neg(_, _) => Err(RuleNotApplicable),
500
88576
        Expr::AuxDeclaration(_, _, _) => Err(RuleNotApplicable),
501
2480
        Expr::UnsafeMod(_, _, _) => Err(RuleNotApplicable),
502
9720
        Expr::SafeMod(_, _, _) => Err(RuleNotApplicable),
503
3532
        Expr::UnsafePow(_, _, _) => Err(RuleNotApplicable),
504
10588
        Expr::SafePow(_, _, _) => Err(RuleNotApplicable),
505
94948
        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
32400
        Expr::FlatAllDiff(_, _) => Err(RuleNotApplicable),
510
1720
        Expr::FlatAbsEq(_, _, _) => Err(RuleNotApplicable),
511
77128
        Expr::FlatIneq(_, _, _, _) => Err(RuleNotApplicable),
512
3600
        Expr::FlatMinusEq(_, _, _) => Err(RuleNotApplicable),
513
3520
        Expr::FlatProductEq(_, _, _, _) => Err(RuleNotApplicable),
514
156260
        Expr::FlatSumLeq(_, _, _) => Err(RuleNotApplicable),
515
162420
        Expr::FlatSumGeq(_, _, _) => Err(RuleNotApplicable),
516
2640
        Expr::FlatWatchedLiteral(_, _, _) => Err(RuleNotApplicable),
517
39280
        Expr::FlatWeightedSumLeq(_, _, _, _) => Err(RuleNotApplicable),
518
39600
        Expr::FlatWeightedSumGeq(_, _, _, _) => Err(RuleNotApplicable),
519
6100
        Expr::MinionDivEqUndefZero(_, _, _, _) => Err(RuleNotApplicable),
520
2320
        Expr::MinionModuloEqUndefZero(_, _, _, _) => Err(RuleNotApplicable),
521
3520
        Expr::MinionPow(_, _, _, _) => Err(RuleNotApplicable),
522
72480
        Expr::MinionReify(_, _, _) => Err(RuleNotApplicable),
523
27916
        Expr::MinionReifyImply(_, _, _) => Err(RuleNotApplicable),
524
1020
        Expr::MinionWInIntervalSet(_, _, _) => Err(RuleNotApplicable),
525
320
        Expr::MinionWInSet(_, _, _) => Err(RuleNotApplicable),
526
41452
        Expr::MinionElementOne(_, _, _, _) => Err(RuleNotApplicable),
527
449780
        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
37640
        Expr::LexLeq(_, _, _) => Err(RuleNotApplicable),
539
40
        Expr::LexGt(_, _, _) => Err(RuleNotApplicable),
540
80
        Expr::LexGeq(_, _, _) => Err(RuleNotApplicable),
541
160
        Expr::FlatLexLt(_, _, _) => Err(RuleNotApplicable),
542
320
        Expr::FlatLexLeq(_, _, _) => Err(RuleNotApplicable),
543
    }
544
15309510
}
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
103748
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
103748
    let mut p_implies_q: Vec<(&Expr, &Expr)> = vec![];
561

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

            
565
215844
    for term in or_terms.iter() {
566
215844
        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
640
            if let Expr::Not(_, q_1) = q.as_ref() {
571
20
                p_implies_not_q.push((p.as_ref(), q_1.as_ref()));
572
620
            } else {
573
620
                p_implies_q.push((p.as_ref(), q.as_ref()));
574
620
            }
575
215204
        }
576
    }
577

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

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

            
592
103708
    false
593
103748
}