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.
2829use conjure_core::ast::Expression as Expr;
30use conjure_core::rule_engine::{
31 register_rule, ApplicationError::RuleNotApplicable, ApplicationResult, Reduction,
32};
33use uniplate::{Biplate, Uniplate as _};
34use Expr::*;
3536use crate::ast::{Atom, Literal as Lit, SymbolTable};
37use crate::matrix_expr;
38use crate::metadata::Metadata;
39use std::collections::VecDeque;
4041/// Eliminates double negation
42///
43/// ```text
44/// --x ~> x
45/// ```
46#[register_rule(("Base", 8400))]
47fn elmininate_double_negation(expr: &Expr, _: &SymbolTable) -> ApplicationResult {
48match expr {
49 Neg(_, a) if matches!(**a, Neg(_, _)) => Ok(Reduction::pure(a.children()[0].clone())),
50_ => Err(RuleNotApplicable),
51 }
52}
5354/// Distributes negation over sums
55///
56/// ```text
57/// -(x + y) ~> -x + -y
58/// ```
59#[register_rule(("Base", 8400))]
60fn distribute_negation_over_sum(expr: &Expr, _: &SymbolTable) -> ApplicationResult {
61let inner_expr = match expr {
62 Neg(_, e) if matches!(**e, Sum(_, _)) => Ok(*e.clone()),
63_ => Err(RuleNotApplicable),
64 }?;
6566let mut child_vecs: VecDeque<Vec<Expr>> = inner_expr.children_bi();
6768if child_vecs.is_empty() {
69return Err(RuleNotApplicable);
70 }
7172for child in child_vecs[0].iter_mut() {
73*child = Neg(Metadata::new(), Box::new(child.clone()))
74 }
7576Ok(Reduction::pure(inner_expr.with_children_bi(child_vecs)))
77}
7879/// Simplifies the negation of a product
80///
81/// ```text
82/// -(x * y) ~> -1 * x * y
83/// ```
84#[register_rule(("Base", 8400))]
85fn simplify_negation_of_product(expr: &Expr, _: &SymbolTable) -> ApplicationResult {
86let Expr::Neg(_, expr1) = expr.clone() else {
87return Err(RuleNotApplicable);
88 };
8990let Expr::Product(_, mut factors) = *expr1 else {
91return Err(RuleNotApplicable);
92 };
9394 factors.push(Expr::Atomic(Metadata::new(), Atom::Literal(Lit::Int(-1))));
9596Ok(Reduction::pure(Expr::Product(Metadata::new(), factors)))
97}
9899/// Converts a minus to a sum
100///
101/// ```text
102/// x - y ~> x + -y
103/// ```
104#[register_rule(("Base", 8400))]
105fn minus_to_sum(expr: &Expr, _: &SymbolTable) -> ApplicationResult {
106let (lhs, rhs) = match expr {
107 Minus(_, lhs, rhs) => Ok((lhs.clone(), rhs.clone())),
108_ => Err(RuleNotApplicable),
109 }?;
110111Ok(Reduction::pure(Sum(
112 Metadata::new(),
113 Box::new(matrix_expr![*lhs, Neg(Metadata::new(), rhs)]),
114 )))
115}