1
use conjure_cp::ast::{DomainPtr, GroundDomain, Metadata};
2
use conjure_cp::ast::{Expression, Moo, SymbolTable};
3
use conjure_cp::rule_engine::{
4
    ApplicationError, ApplicationError::RuleNotApplicable, ApplicationResult, Reduction,
5
    register_rule,
6
};
7
use conjure_cp::{bug, into_matrix_expr};
8
use itertools::{Itertools as _, izip};
9

            
10
/// Converts an unsafe index to a safe index using a bubble expression.
11
#[register_rule(("Bubble", 6000))]
12
726246
fn index_to_bubble(expr: &Expression, _: &SymbolTable) -> ApplicationResult {
13
726246
    let Expression::UnsafeIndex(_, subject, indices) = expr else {
14
722730
        return Err(RuleNotApplicable);
15
    };
16

            
17
3516
    let domain = subject
18
3516
        .domain_of()
19
3516
        .ok_or(ApplicationError::DomainError)?
20
3516
        .resolve()
21
3516
        .ok_or(RuleNotApplicable)?;
22

            
23
    // TODO: tuple, this is a hack right now just to avoid the rule being applied to tuples, but could we safely modify the rule to
24
    // handle tuples as well?
25
3516
    if matches!(domain.as_ref(), GroundDomain::Tuple(_))
26
3516
        || matches!(domain.as_ref(), GroundDomain::Record(_))
27
    {
28
        return Err(RuleNotApplicable);
29
3516
    }
30

            
31
3516
    let GroundDomain::Matrix(_, index_domains) = domain.as_ref() else {
32
        bug!(
33
            "subject of an index expression should have a matrix domain. subject: {:?}, with domain: {:?}",
34
            subject,
35
            domain.as_ref()
36
        );
37
    };
38

            
39
3516
    assert_eq!(
40
3516
        index_domains.len(),
41
3516
        indices.len(),
42
        "in an index expression, there should be the same number of indices as the subject has index domains"
43
    );
44

            
45
3516
    let bubble_constraints = Moo::new(into_matrix_expr![
46
3516
        izip!(index_domains, indices)
47
5436
            .map(|(domain, index)| {
48
5436
                Expression::InDomain(
49
5436
                    Metadata::new(),
50
5436
                    Moo::new(index.clone()),
51
5436
                    DomainPtr::from(domain.clone()),
52
5436
                )
53
5436
            })
54
3516
            .collect_vec()
55
    ]);
56

            
57
3516
    let new_expr = Moo::new(Expression::SafeIndex(
58
3516
        Metadata::new(),
59
3516
        subject.clone(),
60
3516
        indices.clone(),
61
3516
    ));
62

            
63
3516
    Ok(Reduction::pure(Expression::Bubble(
64
3516
        Metadata::new(),
65
3516
        new_expr,
66
3516
        Moo::new(Expression::And(Metadata::new(), bubble_constraints)),
67
3516
    )))
68
726246
}
69

            
70
/// Converts an unsafe slice to a safe slice using a bubble expression.
71
#[register_rule(("Bubble", 6000))]
72
726246
fn slice_to_bubble(expr: &Expression, _: &SymbolTable) -> ApplicationResult {
73
726246
    let Expression::UnsafeSlice(_, subject, indices) = expr else {
74
725466
        return Err(RuleNotApplicable);
75
    };
76

            
77
780
    let domain = subject
78
780
        .domain_of()
79
780
        .ok_or(ApplicationError::DomainError)?
80
780
        .resolve()
81
780
        .ok_or(RuleNotApplicable)?;
82

            
83
780
    let GroundDomain::Matrix(_, index_domains) = domain.as_ref() else {
84
        bug!(
85
            "subject of a slice expression should have a matrix domain. subject: {:?}, with domain: {:?}",
86
            subject,
87
            domain
88
        );
89
    };
90

            
91
780
    assert_eq!(
92
780
        index_domains.len(),
93
780
        indices.len(),
94
        "in a slice expression, there should be the same number of indices as the subject has index domains"
95
    );
96

            
97
    // the wildcard dimension doesn't need a constraint.
98
780
    let bubble_constraints = Moo::new(into_matrix_expr![
99
780
        izip!(index_domains, indices)
100
1548
            .filter_map(|(domain, index)| {
101
1548
                index
102
1548
                    .clone()
103
                    // TODO(perf): This pattern of "take something with a ground domain G and re-wrap it in Moo(Domain::Ground(G))" is fairly common...
104
1548
                    .map(|index| {
105
768
                        Expression::InDomain(
106
768
                            Metadata::new(),
107
768
                            Moo::new(index),
108
768
                            DomainPtr::from(domain.clone()),
109
768
                        )
110
768
                    })
111
1548
            })
112
780
            .collect_vec()
113
    ]);
114

            
115
780
    let new_expr = Moo::new(Expression::SafeSlice(
116
780
        Metadata::new(),
117
780
        subject.clone(),
118
780
        indices.clone(),
119
780
    ));
120

            
121
780
    Ok(Reduction::pure(Expression::Bubble(
122
780
        Metadata::new(),
123
780
        new_expr,
124
780
        Moo::new(Expression::And(Metadata::new(), bubble_constraints)),
125
780
    )))
126
726246
}