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
15364500
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
15364500
    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
1690800
        Expr::AbstractLiteral(_, _) => Err(RuleNotApplicable),
24
71280
        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
818400
        Expr::UnsafeIndex(_, _, _) => Err(RuleNotApplicable),
30
52600
        Expr::UnsafeSlice(_, _, _) => Err(RuleNotApplicable),
31
720
        Expr::Table(_, _, _) => Err(RuleNotApplicable),
32
855480
        Expr::SafeIndex(_, subject, indices) => {
33
            // partially evaluate matrix literals indexed by a constant.
34

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

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

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

            
76
                // if the declaration's domain is a subset of domain, expr is always true.
77
8480
                if &intersection == decl_domain.as_ref() {
78
7040
                    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
1440
                else if let Ok(values_in_domain) = intersection.values_i32()
88
1440
                    && values_in_domain.is_empty()
89
                {
90
                    Ok(Reduction::pure(Expr::Atomic(Metadata::new(), false.into())))
91
                } else {
92
1440
                    Err(RuleNotApplicable)
93
                }
94
23520
            } 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
23520
                Err(RuleNotApplicable)
108
            }
109
        }
110
34600
        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
34600
            if let Expr::Atomic(_, Atom::Literal(Lit::Bool(true))) = cond.as_ref() {
115
19280
                Ok(Reduction::pure(Moo::unwrap_or_clone(expr.clone())))
116
            } else {
117
15320
                Err(RuleNotApplicable)
118
            }
119
        }
120
6674380
        Expr::Atomic(_, _) => Err(RuleNotApplicable),
121
1040
        Expr::ToInt(_, expression) => {
122
1040
            if expression.return_type() == ReturnType::Int {
123
                Ok(Reduction::pure(Moo::unwrap_or_clone(expression.clone())))
124
            } else {
125
1040
                Err(RuleNotApplicable)
126
            }
127
        }
128
6280
        Expr::Abs(m, e) => match e.as_ref() {
129
80
            Expr::Neg(_, inner) => Ok(Reduction::pure(Expr::Abs(m.clone(), inner.clone()))),
130
6200
            _ => Err(RuleNotApplicable),
131
        },
132
654440
        Expr::Sum(m, vec) => {
133
654440
            let vec = Moo::unwrap_or_clone(vec.clone())
134
654440
                .unwrap_list()
135
654440
                .ok_or(RuleNotApplicable)?;
136
399440
            let mut acc = 0;
137
399440
            let mut n_consts = 0;
138
399440
            let mut new_vec: Vec<Expr> = Vec::new();
139
954280
            for expr in vec {
140
248080
                if let Expr::Atomic(_, Atom::Literal(Lit::Int(x))) = expr {
141
248080
                    acc += x;
142
248080
                    n_consts += 1;
143
706200
                } else {
144
706200
                    new_vec.push(expr);
145
706200
                }
146
            }
147
399440
            if acc != 0 {
148
222560
                new_vec.push(Expr::Atomic(
149
222560
                    Default::default(),
150
222560
                    Atom::Literal(Lit::Int(acc)),
151
222560
                ));
152
222560
            }
153

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

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

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

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

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

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

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

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

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

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

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

            
294
99300
            let mut has_changed = false;
295

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

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

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

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

            
322
99300
            if !has_changed {
323
99060
                return Err(RuleNotApplicable);
324
240
            }
325

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

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

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

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

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

            
403
312540
            if !has_changed {
404
289020
                Err(RuleNotApplicable)
405
            } else {
406
23520
                if new_vec.is_empty() {
407
80
                    new_vec.push(true.into());
408
23440
                }
409
23520
                Ok(Reduction::pure(Expr::Root(Metadata::new(), new_vec)))
410
            }
411
        }
412
130120
        Expr::Imply(_m, x, y) => {
413
130120
            if let Expr::Atomic(_, Atom::Literal(Lit::Bool(x))) = x.as_ref() {
414
81320
                if *x {
415
                    // (true) -> y ~~> y
416
840
                    return Ok(Reduction::pure(Moo::unwrap_or_clone(y.clone())));
417
                } else {
418
                    // (false) -> y ~~> true
419
80480
                    return Ok(Reduction::pure(Expr::Atomic(Metadata::new(), true.into())));
420
                }
421
48800
            };
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
48800
            if x.identical_atom_to(y.as_ref()) {
430
40
                return Ok(Reduction::pure(true.into()));
431
48760
            }
432

            
433
48760
            Err(RuleNotApplicable)
434
        }
435
2280
        Expr::Iff(_m, x, y) => {
436
2280
            if let Expr::Atomic(_, Atom::Literal(Lit::Bool(x))) = x.as_ref() {
437
40
                if *x {
438
                    // (true) <-> y ~~> y
439
                    return Ok(Reduction::pure(Moo::unwrap_or_clone(y.clone())));
440
                } else {
441
                    // (false) <-> y ~~> !y
442
40
                    return Ok(Reduction::pure(Expr::Not(Metadata::new(), y.clone())));
443
                }
444
2240
            };
445
2240
            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
2240
            };
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
2240
            if x.identical_atom_to(y.as_ref()) {
462
40
                return Ok(Reduction::pure(true.into()));
463
2200
            }
464

            
465
2200
            Err(RuleNotApplicable)
466
        }
467
692420
        Expr::Eq(_, _, _) => Err(RuleNotApplicable),
468
444240
        Expr::Neq(_, _, _) => Err(RuleNotApplicable),
469
103280
        Expr::Geq(_, _, _) => Err(RuleNotApplicable),
470
209160
        Expr::Leq(_, _, _) => Err(RuleNotApplicable),
471
5720
        Expr::Gt(_, _, _) => Err(RuleNotApplicable),
472
64440
        Expr::Lt(_, _, _) => Err(RuleNotApplicable),
473
24200
        Expr::SafeDiv(_, _, _) => Err(RuleNotApplicable),
474
6800
        Expr::UnsafeDiv(_, _, _) => Err(RuleNotApplicable),
475
7280
        Expr::Flatten(_, _, _) => Err(RuleNotApplicable), // TODO: check if anything can be done here
476
64440
        Expr::AllDiff(m, e) => {
477
64440
            let Some(vec) = Moo::unwrap_or_clone(e.clone()).unwrap_list() else {
478
57760
                return Err(RuleNotApplicable);
479
            };
480

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

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

            
495
            // nothing has changed
496
6680
            Err(RuleNotApplicable)
497
        }
498
46660
        Expr::Neg(_, _) => Err(RuleNotApplicable),
499
99600
        Expr::AuxDeclaration(_, _, _) => Err(RuleNotApplicable),
500
2480
        Expr::UnsafeMod(_, _, _) => Err(RuleNotApplicable),
501
9720
        Expr::SafeMod(_, _, _) => Err(RuleNotApplicable),
502
3400
        Expr::UnsafePow(_, _, _) => Err(RuleNotApplicable),
503
9080
        Expr::SafePow(_, _, _) => Err(RuleNotApplicable),
504
94680
        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
36040
        Expr::FlatAllDiff(_, _) => Err(RuleNotApplicable),
509
1640
        Expr::FlatAbsEq(_, _, _) => Err(RuleNotApplicable),
510
95360
        Expr::FlatIneq(_, _, _, _) => Err(RuleNotApplicable),
511
3640
        Expr::FlatMinusEq(_, _, _) => Err(RuleNotApplicable),
512
2320
        Expr::FlatProductEq(_, _, _, _) => Err(RuleNotApplicable),
513
184320
        Expr::FlatSumLeq(_, _, _) => Err(RuleNotApplicable),
514
189040
        Expr::FlatSumGeq(_, _, _) => Err(RuleNotApplicable),
515
4160
        Expr::FlatWatchedLiteral(_, _, _) => Err(RuleNotApplicable),
516
42800
        Expr::FlatWeightedSumLeq(_, _, _, _) => Err(RuleNotApplicable),
517
43360
        Expr::FlatWeightedSumGeq(_, _, _, _) => Err(RuleNotApplicable),
518
7160
        Expr::MinionDivEqUndefZero(_, _, _, _) => Err(RuleNotApplicable),
519
2320
        Expr::MinionModuloEqUndefZero(_, _, _, _) => Err(RuleNotApplicable),
520
3560
        Expr::MinionPow(_, _, _, _) => Err(RuleNotApplicable),
521
83680
        Expr::MinionReify(_, _, _) => Err(RuleNotApplicable),
522
28680
        Expr::MinionReifyImply(_, _, _) => Err(RuleNotApplicable),
523
1320
        Expr::MinionWInIntervalSet(_, _, _) => Err(RuleNotApplicable),
524
640
        Expr::MinionWInSet(_, _, _) => Err(RuleNotApplicable),
525
39800
        Expr::MinionElementOne(_, _, _, _) => Err(RuleNotApplicable),
526
416440
        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
800
        Expr::LexLt(_, _, _) => Err(RuleNotApplicable),
537
49560
        Expr::LexLeq(_, _, _) => Err(RuleNotApplicable),
538
        Expr::LexGt(_, _, _) => Err(RuleNotApplicable),
539
        Expr::LexGeq(_, _, _) => Err(RuleNotApplicable),
540
160
        Expr::FlatLexLt(_, _, _) => Err(RuleNotApplicable),
541
320
        Expr::FlatLexLeq(_, _, _) => Err(RuleNotApplicable),
542
    }
543
15364500
}
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
99300
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
99300
    let mut p_implies_q: Vec<(&Expr, &Expr)> = vec![];
560

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

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

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

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