1
//! Materialise linear operands through equalities handled by the selected PB provider.
2
use conjure_cp::ast::{Expression as Expr, Metadata, Moo, Reference, SATIntEncoding, SymbolTable};
3
use conjure_cp::rule_engine::{
4
    ApplicationError::RuleNotApplicable, ApplicationResult, RuleEffect, register_rule,
5
};
6
use uniplate::Uniplate;
7

            
8
/// Leave linear comparisons intact; only circuits need an intermediate represented value.
9
#[register_rule("SAT", 9450, [Root])]
10
2467918
fn materialise_linear_operand(expr: &Expr, symbols: &SymbolTable) -> ApplicationResult {
11
2467918
    let mut path = Vec::new();
12
2467918
    let input = find_operand(expr, false, &mut path).ok_or(RuleNotApplicable)?;
13
5229
    let domain = input.domain_of().ok_or(RuleNotApplicable)?;
14
5229
    let mut symbols = symbols.clone();
15
5229
    let result: Expr = Reference::new(symbols.gen_find_auxiliary(&domain)).into();
16
5229
    let definition = Expr::Eq(Metadata::new(), Moo::new(result.clone()), Moo::new(input));
17
5229
    let rewritten = replace_at(expr, &path, result);
18
5229
    Ok(RuleEffect::new(rewritten, vec![definition], symbols))
19
2467918
}
20

            
21
172148426
fn find_operand(expr: &Expr, needed: bool, path: &mut Vec<usize>) -> Option<Expr> {
22
    // Code bits contain no numeric operands; binders must be expanded before hoisting.
23
172148426
    if matches!(
24
172148426
        expr,
25
        Expr::Atomic(..) | Expr::SATInt(..) | Expr::Comprehension(..)
26
    ) {
27
88546881
        return None;
28
83601545
    }
29
83601545
    if needed
30
848204
        && is_linear_operation(expr)
31
11389
        && !free_negation(expr)
32
11389
        && super::pseudo_boolean::integer_view(expr).is_some_and(|view| !view.terms.is_empty())
33
    {
34
5229
        return Some(expr.clone());
35
83596316
    }
36
83596316
    let nonlinear = matches!(
37
83596316
        expr,
38
        Expr::Abs(..)
39
            | Expr::Min(..)
40
            | Expr::Max(..)
41
            | Expr::SafeDiv(..)
42
            | Expr::SafeMod(..)
43
            | Expr::SafePow(..)
44
80932137
    ) || (matches!(expr, Expr::Product(..))
45
199913
        && super::pseudo_boolean::integer_view(expr).is_none());
46
    // List wrappers pass the enclosing operation's requirement to each numeric operand.
47
83596316
    let children_needed = nonlinear
48
80754659
        || (needed
49
336000
            && matches!(
50
336000
                expr,
51
                Expr::AbstractLiteral(_, conjure_cp::ast::AbstractLiteral::Matrix(..))
52
            ));
53
83596316
    for (index, child) in expr.children().iter().enumerate() {
54
169680508
        path.push(index);
55
169680508
        if let Some(found) = find_operand(child, children_needed, path) {
56
20804
            return Some(found);
57
169659704
        }
58
169659704
        path.pop();
59
    }
60
83575512
    None
61
172148426
}
62

            
63
848204
fn is_linear_operation(expr: &Expr) -> bool {
64
848204
    matches!(
65
848204
        expr,
66
        Expr::Sum(..) | Expr::Minus(..) | Expr::Neg(..) | Expr::Product(..)
67
    )
68
848204
}
69

            
70
11389
fn free_negation(expr: &Expr) -> bool {
71
11389
    matches!(expr, Expr::Neg(_, input)
72
840
        if matches!(input.as_ref(), Expr::SATInt(_, SATIntEncoding::Direct | SATIntEncoding::Order, ..)))
73
11389
}
74

            
75
26033
fn replace_at(expr: &Expr, path: &[usize], replacement: Expr) -> Expr {
76
26033
    let Some((&index, tail)) = path.split_first() else {
77
5229
        return replacement;
78
    };
79
20804
    let mut children = expr.children();
80
20804
    children[index] = replace_at(&children[index], tail, replacement);
81
20804
    expr.with_children(children)
82
26033
}