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
1915260
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
1915260
    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
148420
        Expr::AbstractLiteral(_, _) => Err(RuleNotApplicable),
24
12340
        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
41320
        Expr::UnsafeIndex(_, _, _) => Err(RuleNotApplicable),
30
5440
        Expr::UnsafeSlice(_, _, _) => Err(RuleNotApplicable),
31
161180
        Expr::SafeIndex(_, subject, indices) => {
32
            // partially evaluate matrix literals indexed by a constant.
33

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

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

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

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

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

            
75
                // if the declaration's domain is a subset of domain, expr is always true.
76
1960
                if &intersection == decl_domain.as_ref() {
77
1240
                    Ok(Reduction::pure(Expr::Atomic(Metadata::new(), true.into())))
78
                }
79
                // if no elements of declaration's domain are in the domain (i.e. they have no
80
                // intersection), expr is always false.
81
                //
82
                // Only check this when the intersection is a finite integer domain, as we
83
                // currently don't have a way to check whether other domain kinds are empty or not.
84
                //
85
                // we should expand this to cover more domain types in the future.
86
720
                else if let Ok(values_in_domain) = intersection.values_i32()
87
720
                    && values_in_domain.is_empty()
88
                {
89
                    Ok(Reduction::pure(Expr::Atomic(Metadata::new(), false.into())))
90
                } else {
91
720
                    Err(RuleNotApplicable)
92
                }
93
5880
            } else if let Expr::Atomic(_, Atom::Literal(lit)) = x.as_ref() {
94
                if domain
95
                    .resolve()
96
                    .ok_or(RuleNotApplicable)?
97
                    .contains(lit)
98
                    .ok()
99
                    .ok_or(RuleNotApplicable)?
100
                {
101
                    Ok(Reduction::pure(Expr::Atomic(Metadata::new(), true.into())))
102
                } else {
103
                    Ok(Reduction::pure(Expr::Atomic(Metadata::new(), false.into())))
104
                }
105
            } else {
106
5880
                Err(RuleNotApplicable)
107
            }
108
        }
109
9500
        Expr::Bubble(_, expr, cond) => {
110
            // definition of bubble is "expr is valid as long as cond is true"
111
            //
112
            // check if cond is true and pop the bubble!
113
9500
            if let Expr::Atomic(_, Atom::Literal(Lit::Bool(true))) = cond.as_ref() {
114
3520
                Ok(Reduction::pure(Moo::unwrap_or_clone(expr.clone())))
115
            } else {
116
5980
                Err(RuleNotApplicable)
117
            }
118
        }
119
763350
        Expr::Atomic(_, _) => Err(RuleNotApplicable),
120
        Expr::Scope(_, _) => 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
2580
        Expr::Abs(m, e) => match e.as_ref() {
129
40
            Expr::Neg(_, inner) => Ok(Reduction::pure(Expr::Abs(m.clone(), inner.clone()))),
130
2540
            _ => Err(RuleNotApplicable),
131
        },
132
61020
        Expr::Sum(m, vec) => {
133
61020
            let vec = Moo::unwrap_or_clone(vec.clone())
134
61020
                .unwrap_list()
135
61020
                .ok_or(RuleNotApplicable)?;
136
28600
            let mut acc = 0;
137
28600
            let mut n_consts = 0;
138
28600
            let mut new_vec: Vec<Expr> = Vec::new();
139
66380
            for expr in vec {
140
20800
                if let Expr::Atomic(_, Atom::Literal(Lit::Int(x))) = expr {
141
20800
                    acc += x;
142
20800
                    n_consts += 1;
143
45580
                } else {
144
45580
                    new_vec.push(expr);
145
45580
                }
146
            }
147
28600
            if acc != 0 {
148
18640
                new_vec.push(Expr::Atomic(
149
18640
                    Default::default(),
150
18640
                    Atom::Literal(Lit::Int(acc)),
151
18640
                ));
152
18640
            }
153

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

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

            
180
6240
            if n_consts == 0 {
181
3000
                return Err(RuleNotApplicable);
182
3240
            }
183

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

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

            
210
2280
        Expr::Min(m, e) => {
211
2280
            let Some(vec) = Moo::unwrap_or_clone(e.clone()).unwrap_list() else {
212
1880
                return Err(RuleNotApplicable);
213
            };
214
400
            let mut acc: Option<i32> = None;
215
400
            let mut n_consts = 0;
216
400
            let mut new_vec: Vec<Expr> = Vec::new();
217
920
            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
840
                } else {
231
840
                    new_vec.push(expr);
232
840
                }
233
            }
234

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

            
239
400
            if n_consts <= 1 {
240
400
                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
1080
        Expr::Max(m, e) => {
250
1080
            let Some(vec) = Moo::unwrap_or_clone(e.clone()).unwrap_list() else {
251
880
                return Err(RuleNotApplicable);
252
            };
253

            
254
200
            let mut acc: Option<i32> = None;
255
200
            let mut n_consts = 0;
256
200
            let mut new_vec: Vec<Expr> = Vec::new();
257
440
            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
400
                } else {
271
400
                    new_vec.push(expr);
272
400
                }
273
            }
274

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

            
279
200
            if n_consts <= 1 {
280
200
                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
6760
        Expr::Not(_, _) => Err(RuleNotApplicable),
289
36960
        Expr::Or(m, e) => {
290
36960
            let Some(terms) = Moo::unwrap_or_clone(e.clone()).unwrap_list() else {
291
4860
                return Err(RuleNotApplicable);
292
            };
293

            
294
32100
            let mut has_changed = false;
295

            
296
            // 2. boolean literals
297
32100
            let mut new_terms = vec![];
298
91400
            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
91280
                } else {
308
91280
                    new_terms.push(expr);
309
91280
                }
310
            }
311

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

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

            
322
32100
            if !has_changed {
323
31980
                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
29800
        Expr::And(_, e) => {
332
29800
            let Some(vec) = Moo::unwrap_or_clone(e.clone()).unwrap_list() else {
333
14360
                return Err(RuleNotApplicable);
334
            };
335
15440
            let mut new_vec: Vec<Expr> = Vec::new();
336
15440
            let mut has_const: bool = false;
337
30420
            for expr in vec {
338
640
                if let Expr::Atomic(_, Atom::Literal(Lit::Bool(x))) = expr {
339
640
                    has_const = true;
340
640
                    if !x {
341
180
                        return Ok(Reduction::pure(Expr::Atomic(
342
180
                            Default::default(),
343
180
                            Atom::Literal(Lit::Bool(false)),
344
180
                        )));
345
460
                    }
346
29780
                } else {
347
29780
                    new_vec.push(expr);
348
29780
                }
349
            }
350

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

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

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

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

            
403
59440
            if !has_changed {
404
54680
                Err(RuleNotApplicable)
405
            } else {
406
4760
                if new_vec.is_empty() {
407
40
                    new_vec.push(true.into());
408
4720
                }
409
4760
                Ok(Reduction::pure(Expr::Root(Metadata::new(), new_vec)))
410
            }
411
        }
412
9500
        Expr::Imply(_m, x, y) => {
413
9500
            if let Expr::Atomic(_, Atom::Literal(Lit::Bool(x))) = x.as_ref() {
414
220
                if *x {
415
                    // (true) -> y ~~> y
416
160
                    return Ok(Reduction::pure(Moo::unwrap_or_clone(y.clone())));
417
                } else {
418
                    // (false) -> y ~~> true
419
60
                    return Ok(Reduction::pure(Expr::Atomic(Metadata::new(), true.into())));
420
                }
421
9280
            };
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
9280
            if x.identical_atom_to(y.as_ref()) {
430
20
                return Ok(Reduction::pure(true.into()));
431
9260
            }
432

            
433
9260
            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
140140
        Expr::Eq(_, _, _) => Err(RuleNotApplicable),
468
31680
        Expr::Neq(_, _, _) => Err(RuleNotApplicable),
469
5960
        Expr::Geq(_, _, _) => Err(RuleNotApplicable),
470
15160
        Expr::Leq(_, _, _) => Err(RuleNotApplicable),
471
980
        Expr::Gt(_, _, _) => Err(RuleNotApplicable),
472
31660
        Expr::Lt(_, _, _) => Err(RuleNotApplicable),
473
12100
        Expr::SafeDiv(_, _, _) => Err(RuleNotApplicable),
474
3400
        Expr::UnsafeDiv(_, _, _) => Err(RuleNotApplicable),
475
1860
        Expr::Flatten(_, _, _) => Err(RuleNotApplicable), // TODO: check if anything can be done here
476
25760
        Expr::AllDiff(m, e) => {
477
25760
            let Some(vec) = Moo::unwrap_or_clone(e.clone()).unwrap_list() else {
478
23540
                return Err(RuleNotApplicable);
479
            };
480

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

            
483
            // check for duplicate constant values which would fail the constraint
484
9180
            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
9180
                }
493
            }
494

            
495
            // nothing has changed
496
2220
            Err(RuleNotApplicable)
497
        }
498
10580
        Expr::Neg(_, _) => Err(RuleNotApplicable),
499
23220
        Expr::AuxDeclaration(_, _, _) => Err(RuleNotApplicable),
500
1120
        Expr::UnsafeMod(_, _, _) => Err(RuleNotApplicable),
501
4800
        Expr::SafeMod(_, _, _) => Err(RuleNotApplicable),
502
2420
        Expr::UnsafePow(_, _, _) => Err(RuleNotApplicable),
503
8300
        Expr::SafePow(_, _, _) => Err(RuleNotApplicable),
504
2320
        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
4760
        Expr::FlatAllDiff(_, _) => Err(RuleNotApplicable),
509
820
        Expr::FlatAbsEq(_, _, _) => Err(RuleNotApplicable),
510
27640
        Expr::FlatIneq(_, _, _, _) => Err(RuleNotApplicable),
511
1800
        Expr::FlatMinusEq(_, _, _) => Err(RuleNotApplicable),
512
560
        Expr::FlatProductEq(_, _, _, _) => Err(RuleNotApplicable),
513
34660
        Expr::FlatSumLeq(_, _, _) => Err(RuleNotApplicable),
514
35540
        Expr::FlatSumGeq(_, _, _) => Err(RuleNotApplicable),
515
1840
        Expr::FlatWatchedLiteral(_, _, _) => Err(RuleNotApplicable),
516
3480
        Expr::FlatWeightedSumLeq(_, _, _, _) => Err(RuleNotApplicable),
517
3480
        Expr::FlatWeightedSumGeq(_, _, _, _) => Err(RuleNotApplicable),
518
3580
        Expr::MinionDivEqUndefZero(_, _, _, _) => Err(RuleNotApplicable),
519
1120
        Expr::MinionModuloEqUndefZero(_, _, _, _) => Err(RuleNotApplicable),
520
2280
        Expr::MinionPow(_, _, _, _) => Err(RuleNotApplicable),
521
14920
        Expr::MinionReify(_, _, _) => Err(RuleNotApplicable),
522
4020
        Expr::MinionReifyImply(_, _, _) => Err(RuleNotApplicable),
523
660
        Expr::MinionWInIntervalSet(_, _, _) => Err(RuleNotApplicable),
524
320
        Expr::MinionWInSet(_, _, _) => Err(RuleNotApplicable),
525
3180
        Expr::MinionElementOne(_, _, _, _) => Err(RuleNotApplicable),
526
        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
3900
        Expr::LexLeq(_, _, _) => Err(RuleNotApplicable),
538
        Expr::LexGt(_, _, _) => Err(RuleNotApplicable),
539
        Expr::LexGeq(_, _, _) => Err(RuleNotApplicable),
540
80
        Expr::FlatLexLt(_, _, _) => Err(RuleNotApplicable),
541
80
        Expr::FlatLexLeq(_, _, _) => Err(RuleNotApplicable),
542
    }
543
1915260
}
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
32100
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
32100
    let mut p_implies_q: Vec<(&Expr, &Expr)> = vec![];
560

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

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

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

            
584
    // `(p->q) \/ (p-> !q) ~> true`    [conditional excluded middle]
585
32100
    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
32100
    false
592
32100
}