1
use std::collections::VecDeque;
2

            
3
use conjure_cp::ast::{Domain, Expression as Expr, IntVal, Range, SymbolTable};
4
use conjure_cp::into_matrix_expr;
5
use conjure_cp::rule_engine::{
6
    ApplicationError::RuleNotApplicable, ApplicationResult, Reduction, register_rule,
7
};
8
use uniplate::Uniplate;
9

            
10
/// Converts a matrix to a list if possible.
11
///
12
/// A list is a matrix with the unbounded domain `int(1..)`. Unlike matrices in general, lists can
13
/// be resized; consequently, a lot more rules apply to them.
14
///
15
/// A matrix can be converted to a list if:
16
///
17
///  1. It has some contiguous domain `int(1..n)`.
18
///
19
///  2. It is a matrix literal (i.e. not a reference to a decision variable).
20
///
21
///  3. Its direct parent is a constraint, not another matrix or `AbstractLiteral`.
22
///
23
///    This prevents the conversion of rows in a 2d matrix from being turned into lists. If were
24
///    to happen, the rows of the matrix might become different lengths, which is invalid!
25
///
26
///  4. The matrix is stored as `Expression` type inside (i.e. not as an `Atom` inside a Minion
27
///     constraint)
28
///
29
/// Because of condition 4, and this rules low priority, this rule will not run post-flattening, so
30
/// matrices that do not need to be converted to lists in order to get them ready for Minion will
31
/// be left alone.
32
#[register_rule(("Base", 2000))]
33
fn matrix_to_list(expr: &Expr, _: &SymbolTable) -> ApplicationResult {
34
    // match on the parent: do not apply this rule to things descended from abstract literal, or
35
    // special language constructs like bubble.
36
    //
37
    // As Minion/ flat constraints do not have expression children, they are automatically
38
    // excluded.
39

            
40
    if matches!(
41
        expr,
42
        Expr::AbstractLiteral(_, _)
43
            | Expr::Bubble(_, _, _)
44
            | Expr::Atomic(_, _)
45
             // not sure if this needs to be excluded, being cautious.
46
            | Expr::DominanceRelation(_, _)
47
    ) {
48
        return Err(RuleNotApplicable);
49
    }
50

            
51
    let mut new_children = VecDeque::new();
52
    let mut any_changes = false;
53
    for child in expr.children() {
54
        // already a list => no change
55
        if child.clone().unwrap_list().is_some() {
56
            new_children.push_back(child);
57
            continue;
58
        }
59

            
60
        // not a matrix => no change
61
        let Some((elems, domain)) = child.clone().unwrap_matrix_unchecked() else {
62
            new_children.push_back(child);
63
            continue;
64
        };
65

            
66
        let Some(ranges) = domain.as_int() else {
67
            new_children.push_back(child);
68
            continue;
69
        };
70

            
71
        // must be domain int(1..n)
72
        let [Range::Bounded(IntVal::Const(1), _)] = ranges[..] else {
73
            new_children.push_back(child);
74
            continue;
75
        };
76

            
77
        any_changes = true;
78
        new_children
79
            .push_back(into_matrix_expr![elems;Domain::int_ground(vec![Range::UnboundedR(1)])]);
80
    }
81

            
82
    let new_expr = expr.with_children(new_children);
83

            
84
    if any_changes {
85
        Ok(Reduction::pure(new_expr))
86
    } else {
87
        Err(RuleNotApplicable)
88
    }
89
}