1
//! Retain safe lookup definitions until library clause generation.
2
use conjure_cp::ast::{
3
    Expression as Expr, GroundDomain, Metadata, Moo, Reference, SatEncodingDecision, SymbolTable,
4
};
5
use conjure_cp::rule_engine::{
6
    ApplicationError::RuleNotApplicable, ApplicationResult, RuleEffect, register_rule,
7
};
8

            
9
/// Materialise scalar matrix entries with their actual numeric index labels.
10
728392
pub(super) fn matrix_data(subject: &Expr) -> Option<(Vec<Expr>, Vec<i64>)> {
11
728392
    let subject = crate::shared::utils::table_operand(subject);
12
728392
    let list = subject.is_list();
13
728392
    let (entries, domain) = subject.unwrap_matrix_unchecked()?;
14
544551
    let labels = if list {
15
368431
        (1..=entries.len())
16
2490887
            .map(|value| i64::try_from(value).ok())
17
            .collect::<Option<Vec<_>>>()?
18
    } else {
19
176120
        let domain = domain.resolve().ok()?;
20
176120
        match domain.as_ref() {
21
            GroundDomain::Bool => vec![0, 1],
22
176120
            domain => domain
23
176120
                .values_i32()
24
176120
                .ok()?
25
                .into_iter()
26
176120
                .map(i64::from)
27
                .collect(),
28
        }
29
    };
30
544551
    (entries.len() == labels.len()).then_some((entries, labels))
31
728392
}
32

            
33
/// Hoist a total safe value; bubbling continues to govern undefined unsafe expressions.
34
#[register_rule("SAT", 18400, [SafeIndex])]
35
9090676
fn introduce_element(expr: &Expr, symbols: &SymbolTable) -> ApplicationResult {
36
9090676
    let Expr::SafeIndex(_, subject, indices) = expr else {
37
21
        return Err(RuleNotApplicable);
38
    };
39
9090655
    if indices.len() != 1 || conjure_cp::ast::eval_constant(&indices[0]).is_some() {
40
8839418
        return Err(RuleNotApplicable);
41
251237
    }
42
251237
    let (entries, _) = matrix_data(subject).ok_or(RuleNotApplicable)?;
43
67396
    if entries.is_empty() {
44
        return Err(RuleNotApplicable);
45
67396
    }
46
67396
    let domain = expr.domain_of().ok_or(RuleNotApplicable)?;
47
67396
    let mut symbols = symbols.clone();
48
67396
    let value = Reference::new(symbols.gen_find_auxiliary(&domain));
49
67396
    let value: Expr = value.into();
50
67396
    let definition = Expr::SatElement(
51
67396
        Metadata::new(),
52
67396
        subject.clone(),
53
67396
        Moo::new(indices[0].clone()),
54
67396
        Moo::new(value.clone()),
55
67396
    );
56
67396
    Ok(RuleEffect::new(value, vec![definition], symbols))
57
9090676
}
58

            
59
/// Select whole-value equalities through the same element algorithms as scalar values.
60
#[register_rule("SAT", 18400, [SatElement])]
61
389347
fn introduce_compound_element(expr: &Expr, symbols: &SymbolTable) -> ApplicationResult {
62
389347
    let Expr::SatElement(_, subject, index, value) = expr else {
63
21
        return Err(RuleNotApplicable);
64
    };
65
389326
    let domain = value.domain_of().ok_or(RuleNotApplicable)?;
66
389326
    if domain.is_int() || domain.is_bool() {
67
384384
        return Err(RuleNotApplicable);
68
4942
    }
69
4942
    let (entries, labels) = matrix_data(subject).ok_or(RuleNotApplicable)?;
70
4942
    let mut symbols = symbols.clone();
71
4942
    let mut definitions = Vec::new();
72
4942
    let comparisons = entries
73
4942
        .into_iter()
74
9884
        .map(|entry| {
75
9884
            let output = super::boolean::create_bool_aux(&mut symbols);
76
9884
            definitions.push(Expr::Iff(
77
9884
                Metadata::new(),
78
9884
                Moo::new(output.clone()),
79
9884
                Moo::new(Expr::Eq(Metadata::new(), value.clone(), Moo::new(entry))),
80
9884
            ));
81
9884
            output
82
9884
        })
83
        .collect();
84
4942
    let labels = labels
85
4942
        .into_iter()
86
9884
        .map(|label| i32::try_from(label).map(conjure_cp::ast::Range::Single))
87
        .collect::<Result<Vec<_>, _>>()
88
        .map_err(|_| RuleNotApplicable)?;
89
    let comparisons =
90
4942
        conjure_cp::into_matrix_expr![comparisons; conjure_cp::ast::Domain::int(labels)];
91
4942
    Ok(RuleEffect::new(
92
4942
        Expr::SatElement(
93
4942
            Metadata::new(),
94
4942
            Moo::new(comparisons),
95
4942
            index.clone(),
96
4942
            Moo::new(true.into()),
97
4942
        ),
98
4942
        definitions,
99
4942
        symbols,
100
4942
    ))
101
389347
}
102

            
103
/// Numeric views retain actual sparse values and physical representation invariants.
104
#[register_rule("SAT", 18500, [SatElement])]
105
469518
fn select_element(expr: &Expr, symbols: &SymbolTable) -> ApplicationResult {
106
469518
    let Expr::SatElement(_, subject, index, value) = expr else {
107
21
        return Err(RuleNotApplicable);
108
    };
109
469497
    let (entries, labels) = matrix_data(subject).ok_or(RuleNotApplicable)?;
110
469497
    let index = super::pseudo_boolean::integer_view(index).ok_or(RuleNotApplicable)?;
111
157773
    let value = super::pseudo_boolean::integer_view(value).ok_or(RuleNotApplicable)?;
112
100709
    let entries = entries
113
100709
        .iter()
114
100709
        .zip(labels)
115
407757
        .map(|(entry, label)| super::pseudo_boolean::integer_view(entry).map(|view| (label, view)))
116
        .collect::<Option<Vec<_>>>()
117
100709
        .ok_or(RuleNotApplicable)?;
118
67389
    Ok(RuleEffect::sat(
119
67389
        true.into(),
120
67389
        vec![SatEncodingDecision::Element {
121
67389
            index_view: index,
122
67389
            value,
123
67389
            entries,
124
67389
            encoding: None,
125
67389
            pb_encoding: None,
126
67389
        }],
127
67389
        symbols.clone(),
128
67389
    ))
129
469518
}