1
//! Normalising rules for negations and minus operations.
2
//!
3
//!
4
//! ```text
5
//! 1. --x ~> x  (eliminate_double_negation)
6
//! 2. -( x + y ) ~> -x + -y (distribute_negation_over_addition)
7
//! 3. x - b ~>  x + -b (minus_to_sum)
8
//! 4. -(x*y) ~> -1 * x * y (simplify_negation_of_product
9
//! ```
10
//!
11
//! ## Rationale for `x - y ~> x + -y`
12
//!
13
//! I normalise `Minus` expressions into sums of negations.
14
//!
15
//! Once all negations are in one sum expression, partial evaluation becomes easier, and we can do
16
//! further normalisations like collecting like terms, removing nesting, and giving things an
17
//! ordering.
18
//!
19
//! Converting to a sum is especially helpful for converting the model to Minion as:
20
//!
21
//! 1. normalise_associative_commutative concatenates nested sums, reducing the
22
//!    amount of flattening we need to do to convert this to Minion (reducing the number of
23
//!    auxiliary variables needed).
24
//!
25
//! 2. A sum of variables with constant coefficients can be trivially converted into the
26
//!    weightedsumgeq and weightedsumleq constraints. A negated number is just a number
27
//!    with a coefficient of -1.
28

            
29
use crate::utils::{single_vec_child, with_single_vec_child};
30
use conjure_cp::essence_expr;
31
use conjure_cp::{
32
    ast::Metadata,
33
    ast::{Expression as Expr, Moo, ReturnType::Set, SymbolTable, Typeable},
34
    into_matrix_expr,
35
    rule_engine::{
36
        ApplicationError::RuleNotApplicable, ApplicationResult, Reduction, register_rule,
37
    },
38
};
39

            
40
/// Eliminates double negation
41
///
42
/// ```text
43
/// --x ~> x
44
/// ```
45
#[register_rule("Base", 8400, [Neg])]
46
1599841
fn elmininate_double_negation(expr: &Expr, _: &SymbolTable) -> ApplicationResult {
47
1599841
    match expr {
48
3318
        Expr::Neg(_, a) => match a.as_ref() {
49
36
            Expr::Neg(_, inner) => Ok(Reduction::pure(Moo::unwrap_or_clone(inner.clone()))),
50
3282
            _ => Err(RuleNotApplicable),
51
        },
52
1596523
        _ => Err(RuleNotApplicable),
53
    }
54
1599841
}
55

            
56
/// Distributes negation over sums
57
///
58
/// ```text
59
/// -(x + y) ~> -x + -y
60
/// ```
61
#[register_rule("Base", 8400, [Neg])]
62
1599841
fn distribute_negation_over_sum(expr: &Expr, _: &SymbolTable) -> ApplicationResult {
63
3318
    let inner_expr = match expr {
64
3318
        Expr::Neg(_, e) if matches!(**e, Expr::Sum(_, _)) => Ok(e),
65
1599829
        _ => Err(RuleNotApplicable),
66
1599829
    }?;
67

            
68
12
    let mut children = single_vec_child(inner_expr.as_ref()).ok_or(RuleNotApplicable)?;
69
36
    for child in children.iter_mut() {
70
36
        *child = essence_expr!(-&child);
71
36
    }
72

            
73
12
    Ok(Reduction::pure(with_single_vec_child(
74
12
        inner_expr.as_ref(),
75
12
        children,
76
12
    )))
77
1599841
}
78

            
79
/// Simplifies the negation of a product
80
///
81
/// ```text
82
/// -(x * y) ~> -1 * x * y
83
/// ```
84
#[register_rule("Base", 8400, [Neg])]
85
1599841
fn simplify_negation_of_product(expr: &Expr, _: &SymbolTable) -> ApplicationResult {
86
1599841
    let Expr::Neg(_, expr1) = expr.clone() else {
87
1596523
        return Err(RuleNotApplicable);
88
    };
89

            
90
3318
    let Expr::Product(_, factors) = Moo::unwrap_or_clone(expr1) else {
91
3300
        return Err(RuleNotApplicable);
92
    };
93

            
94
18
    let mut factors = Moo::unwrap_or_clone(factors)
95
18
        .unwrap_list()
96
18
        .ok_or(RuleNotApplicable)?;
97

            
98
6
    factors.push(essence_expr!(-1));
99

            
100
6
    Ok(Reduction::pure(Expr::Product(
101
6
        Metadata::new(),
102
6
        Moo::new(into_matrix_expr!(factors)),
103
6
    )))
104
1599841
}
105

            
106
/// Converts a minus to a sum
107
///
108
/// ```text
109
/// x - y ~> x + -y
110
/// ```
111
/// does not apply to sets.
112
/// TODO: need rule to define set difference as a special case of minus, comprehensions needed
113
/// return type and domain of minus need to be altered too, see expressions.rs
114
#[register_rule("Base", 8400, [Minus])]
115
1599841
fn minus_to_sum(expr: &Expr, _: &SymbolTable) -> ApplicationResult {
116
1599841
    let (lhs, rhs) = match expr {
117
1904
        Expr::Minus(_, lhs, rhs) => {
118
1904
            if matches!(lhs.as_ref().return_type(), Set(_)) {
119
                return Err(RuleNotApplicable);
120
1904
            }
121
1904
            if matches!(rhs.as_ref().return_type(), Set(_)) {
122
                return Err(RuleNotApplicable);
123
1904
            }
124
1904
            (lhs.clone(), rhs.clone())
125
        }
126
1597937
        _ => return Err(RuleNotApplicable),
127
    };
128

            
129
1904
    Ok(Reduction::pure(essence_expr!(&lhs + (-&rhs))))
130
1599841
}