1
//! Normalising rules for `Neq` and `Eq`.
2

            
3
use conjure_cp::ast::{Expression as Expr, SymbolTable, Typeable, try_lower_bool_atom_eq_true};
4
use conjure_cp::rule_engine::{
5
    ApplicationError::RuleNotApplicable, ApplicationResult, RuleEffect, register_rule,
6
};
7

            
8
use conjure_cp::ast::ReturnType::{Matrix, Set};
9
use conjure_cp::essence_expr;
10

            
11
/// Normalises boolean `x = true` to `x` before Minion flattening of nested equalities.
12
///
13
/// ```text
14
/// x = true  ~>  x
15
/// true = x  ~>  x
16
/// ```
17
/// where `x` is a non-literal boolean atom.
18
///
19
/// The same lowering is applied by the partial evaluator's `Eq` arm; this rule remains as an
20
/// explicit Base-priority oracle for any sites that still reach ordinary rewriting.
21
#[register_rule("Base", 9000, [Eq])]
22
3774401
fn bool_atom_eq_true(expr: &Expr, _: &SymbolTable) -> ApplicationResult {
23
3774401
    try_lower_bool_atom_eq_true(expr)
24
3774401
        .map(RuleEffect::pure)
25
3774401
        .ok_or(RuleNotApplicable)
26
3774401
}
27

            
28
/// Converts a negated `Neq` to an `Eq`
29
///
30
/// ```text
31
/// not(neq(x)) ~> eq(x)
32
/// ```
33
#[register_rule("Base", 8800, [Not])]
34
437526
fn negated_neq_to_eq(expr: &Expr, _: &SymbolTable) -> ApplicationResult {
35
437526
    match expr {
36
433105
        Expr::Not(_, a) => match a.as_ref() {
37
91
            Expr::Neq(_, b, c) if (b.is_safe() && c.is_safe()) => {
38
91
                Ok(RuleEffect::pure(essence_expr!(&b = &c)))
39
            }
40
433014
            _ => Err(RuleNotApplicable),
41
        },
42
4421
        _ => Err(RuleNotApplicable),
43
    }
44
437526
}
45

            
46
/// Converts a negated `Eq` to an `Neq`
47
///
48
/// ```text
49
/// not(eq(x)) ~> neq(x)
50
/// ```
51
/// don't want this to apply to sets
52
///
53
/// Also can't apply to matrices, since undefinedness between two matrices with different domains
54
/// causes a != b to actually have a different meaning than !(a = b)
55
#[register_rule("Base", 8800, [Not])]
56
437526
fn negated_eq_to_neq(expr: &Expr, _: &SymbolTable) -> ApplicationResult {
57
437526
    match expr {
58
433105
        Expr::Not(_, a) => match a.as_ref() {
59
120938
            Expr::Eq(_, b, c) if (b.is_safe() && c.is_safe()) => {
60
86491
                if matches!(b.as_ref().return_type(), Set(_) | Matrix(_)) {
61
812
                    return Err(RuleNotApplicable);
62
85679
                }
63
85679
                if matches!(c.as_ref().return_type(), Set(_) | Matrix(_)) {
64
                    return Err(RuleNotApplicable);
65
85679
                }
66
85679
                Ok(RuleEffect::pure(essence_expr!(&b != &c)))
67
            }
68
346614
            _ => Err(RuleNotApplicable),
69
        },
70
4421
        _ => Err(RuleNotApplicable),
71
    }
72
437526
}
73

            
74
#[cfg(test)]
75
mod tests {
76
    use super::*;
77
    use conjure_cp::ast::{
78
        Atom, DeclarationPtr, Domain, Literal as Lit, Metadata, Moo, Name, Range, Reference,
79
        run_partial_evaluator,
80
    };
81
    use conjure_cp::rule_engine::{ApplicationError, get_rule_by_name};
82

            
83
    /// Boolean decision-variable atomic expression.
84
3
    fn bool_atom(name: &str) -> Expr {
85
3
        Expr::Atomic(
86
3
            Metadata::new(),
87
3
            Atom::Reference(Reference::new(DeclarationPtr::new_find(
88
3
                Name::user(name),
89
3
                Domain::bool(),
90
3
            ))),
91
3
        )
92
3
    }
93

            
94
    /// Boolean literal atomic expression.
95
4
    fn bool_lit(value: bool) -> Expr {
96
4
        Expr::Atomic(Metadata::new(), Atom::Literal(Lit::Bool(value)))
97
4
    }
98

            
99
    #[test]
100
1
    fn bool_atom_eq_true_lowers_variable_equals_true() {
101
1
        let x = bool_atom("x");
102
1
        let expr = Expr::Eq(
103
1
            Metadata::new(),
104
1
            Moo::new(x.clone()),
105
1
            Moo::new(bool_lit(true)),
106
1
        );
107
1
        let lowered = try_lower_bool_atom_eq_true(&expr).expect("should lower");
108
1
        assert_eq!(lowered, x);
109

            
110
1
        let rule = get_rule_by_name("bool_atom_eq_true").expect("rule registered");
111
1
        let result = rule
112
1
            .apply(&expr, &SymbolTable::new())
113
1
            .expect("rule applies");
114
1
        assert_eq!(result.new_expression, x);
115

            
116
1
        let pe = run_partial_evaluator(&expr).expect("partial evaluator Eq arm lowers");
117
1
        assert_eq!(pe.new_expression, x);
118
1
    }
119

            
120
    #[test]
121
1
    fn bool_atom_eq_true_lowers_true_equals_variable() {
122
1
        let x = bool_atom("x");
123
1
        let expr = Expr::Eq(
124
1
            Metadata::new(),
125
1
            Moo::new(bool_lit(true)),
126
1
            Moo::new(x.clone()),
127
1
        );
128
1
        let lowered = try_lower_bool_atom_eq_true(&expr).expect("should lower");
129
1
        assert_eq!(lowered, x);
130
1
    }
131

            
132
    #[test]
133
1
    fn bool_atom_eq_true_refuses_false_equality() {
134
1
        let expr = Expr::Eq(
135
1
            Metadata::new(),
136
1
            Moo::new(bool_atom("x")),
137
1
            Moo::new(bool_lit(false)),
138
1
        );
139
1
        assert!(try_lower_bool_atom_eq_true(&expr).is_none());
140
1
        let rule = get_rule_by_name("bool_atom_eq_true").expect("rule registered");
141
1
        let err = rule.apply(&expr, &SymbolTable::new()).unwrap_err();
142
1
        assert!(matches!(err, ApplicationError::RuleNotApplicable));
143
1
    }
144

            
145
    #[test]
146
1
    fn bool_atom_eq_true_refuses_integer_atom() {
147
1
        let int_atom = Expr::Atomic(
148
1
            Metadata::new(),
149
1
            Atom::Reference(Reference::new(DeclarationPtr::new_find(
150
1
                Name::user("n"),
151
1
                Domain::int(vec![Range::Bounded(1, 3)]),
152
1
            ))),
153
1
        );
154
1
        let expr = Expr::Eq(
155
1
            Metadata::new(),
156
1
            Moo::new(int_atom),
157
1
            Moo::new(bool_lit(true)),
158
1
        );
159
1
        assert!(try_lower_bool_atom_eq_true(&expr).is_none());
160
1
    }
161
}