1
use conjure_cp::{
2
    ast::{GroundDomain, Name, Range},
3
    domain_tightening::tighten_domains_from_constraints,
4
    instantiate::instantiate_model,
5
    parse::tree_sitter::parse_essence,
6
    rule_engine::{resolve_rule_sets, rewrite_model},
7
    settings::{QuantifiedExpander, RewriteConfig, SolverFamily, set_comprehension_expander},
8
};
9
use conjure_cp_rules::representation::{MatrixComponents, SequenceExplicit};
10

            
11
#[allow(unused_imports)]
12
use conjure_cp_rules;
13

            
14
2
fn instantiated_gchq_style_model() -> conjure_cp::Model {
15
2
    let (problem, _) = parse_essence(
16
2
        r#"
17
2
language Essence 1.3
18
2
given n : int
19
2
letting ADDR be domain int(1..n)
20
2
given clues : matrix indexed by [ADDR] of sequence (maxSize n) of ADDR
21
2
find locs : matrix indexed by [ADDR] of sequence (maxSize n) of ADDR
22
2
such that
23
2
    forAll row : ADDR . |locs[row]| = |clues[row]|
24
2
"#,
25
2
    )
26
2
    .expect("parse problem");
27
2
    let (params, _) = parse_essence(
28
2
        r#"
29
2
language Essence 1.3
30
2
letting n be 5
31
2
letting clues be [
32
2
    sequence(1, 2, 3),
33
2
    sequence(1, 1, 1, 1, 1),
34
2
    sequence(1),
35
2
    sequence(1, 2),
36
2
    sequence(2, 2, 2, 2)
37
2
]
38
2
"#,
39
2
    )
40
2
    .expect("parse params");
41
2
    instantiate_model(problem, params).expect("instantiate")
42
2
}
43

            
44
1
fn sequence_sizes(name: &str, model: &conjure_cp::Model) -> Vec<Range<i32>> {
45
1
    let decl = model
46
1
        .symbols()
47
1
        .lookup_local(&Name::user(name))
48
        .unwrap_or_else(|| panic!("missing find `{name}`"));
49
1
    let var = decl.as_find().expect("expected a find");
50
1
    let domains = var
51
1
        .element_domains
52
1
        .as_ref()
53
        .unwrap_or_else(|| panic!("expected per-cell domains on `{name}`"));
54
1
    domains
55
1
        .iter()
56
5
        .map(|domain| {
57
5
            let GroundDomain::Sequence(attr, _) = domain.as_ground().unwrap() else {
58
                panic!("expected a sequence element domain");
59
            };
60
5
            attr.size.clone()
61
5
        })
62
        .collect()
63
1
}
64

            
65
#[test]
66
1
fn tightens_gchq_style_matrix_of_sequences_after_param_instantiation() {
67
1
    let mut model = instantiated_gchq_style_model();
68
1
    tighten_domains_from_constraints(&mut model);
69

            
70
1
    assert_eq!(
71
1
        sequence_sizes("locs", &model),
72
1
        vec![
73
1
            Range::Single(3),
74
1
            Range::Single(5),
75
1
            Range::Single(1),
76
1
            Range::Single(2),
77
1
            Range::Single(4)
78
        ]
79
    );
80
1
}
81

            
82
#[test]
83
1
fn rewrites_gchq_style_rows_as_fixed_length_sequences() {
84
1
    set_comprehension_expander(QuantifiedExpander::Native);
85
1
    let model = instantiated_gchq_style_model();
86
1
    let rule_sets = resolve_rule_sets(SolverFamily::Minion, &["Base", "Bubble"]).unwrap();
87
1
    let rewritten = rewrite_model(&model, &rule_sets, RewriteConfig::optimised()).unwrap();
88
1
    let locs = rewritten
89
1
        .symbols()
90
1
        .lookup_local(&Name::user("locs"))
91
1
        .expect("locs");
92
1
    let components = locs
93
1
        .get_repr::<MatrixComponents>()
94
1
        .expect("MatrixComponents on locs");
95
1
    let bounds: Vec<(i32, i32)> = components
96
1
        .elements
97
1
        .iter()
98
5
        .map(|elem| {
99
5
            let sequence = elem
100
5
                .get_repr::<SequenceExplicit>()
101
                .unwrap_or_else(|| panic!("{} should be SequenceExplicit", elem.name()));
102
5
            assert!(
103
5
                sequence.length.is_none(),
104
                "{} kept a length marker, so the row was not treated as fixed-length",
105
                elem.name()
106
            );
107
5
            sequence.size_bounds
108
5
        })
109
        .collect();
110
1
    assert_eq!(bounds, vec![(3, 3), (5, 5), (1, 1), (2, 2), (4, 4)]);
111
1
}