1
use crate::ast::domains::attrs::{PartitionAttr, PermutationAttr};
2
use crate::ast::domains::{JectivityAttr, MSetAttr, PartialityAttr, SequenceAttr};
3
use crate::ast::enumerated::EnumeratedType;
4
use crate::ast::pretty::pretty_vec;
5
use crate::ast::unnamed::UnnamedType;
6
use crate::ast::{
7
    AbstractLiteral, DomainOpError, FuncAttr, Literal, Moo, Name, RelAttr, SetAttr, Typeable,
8
    domains::{domain::Int, range::Range},
9
    matrix,
10
    records::Field,
11
};
12
use crate::bug_assert;
13
use crate::range;
14
use crate::utils::{
15
    count_combinations, count_permutations, derangements, restricted_partition_count,
16
    stirling_second_kind,
17
};
18
use conjure_cp_core::ast::ReturnType;
19
use funcmap::FuncMap;
20
use itertools::{Itertools, izip};
21
use num_traits::ToPrimitive;
22
use polyquine::Quine;
23
use serde::{Deserialize, Serialize};
24
use std::collections::{BTreeMap, BTreeSet};
25
use std::fmt::{Display, Formatter};
26
use std::iter::zip;
27
use uniplate::Uniplate;
28

            
29
pub(super) type FieldGround = Field<Moo<GroundDomain>>;
30

            
31
/// Writes an integer domain, e.g. `int(1..4)`, `int(representation order, 1..4)`, or just `int`
32
/// when it has unbounded ranges and no representation preference.
33
31671793
pub(super) fn write_int_domain<A>(
34
31671793
    f: &mut Formatter<'_>,
35
31671793
    ranges: &[Range<A>],
36
31671793
    representation: Option<&str>,
37
31671793
) -> std::fmt::Result
38
31671793
where
39
31671793
    Range<A>: Display,
40
{
41
31671793
    let mut args = Vec::new();
42
31671793
    if let Some(representation) = representation {
43
3462
        args.push(format!("representation {representation}"));
44
31668331
    }
45
31671793
    let bounded = ranges.iter().all(Range::is_lower_or_upper_bounded);
46
31671793
    if bounded {
47
35489596
        args.extend(ranges.iter().map(|r| r.to_string()));
48
240634
    }
49
    // An int with unbounded ranges and nothing to say is just `int`; an empty one is `int()`.
50
31671793
    if !bounded && args.is_empty() {
51
240510
        write!(f, "int")
52
    } else {
53
31431283
        write!(f, "int({})", args.join(", "))
54
    }
55
31671793
}
56

            
57
/// ` (representation X)`, for domains whose attribute list holds nothing else; empty without a
58
/// preference.
59
1565089
pub(super) fn representation_attribute(representation: &Option<String>) -> String {
60
1565089
    match representation {
61
696
        Some(representation) => format!(" (representation {representation})"),
62
1564393
        None => String::new(),
63
    }
64
1565089
}
65

            
66
/// The Essence type-style spelling of an integer domain: `int` or `int(representation order)`.
67
1598
pub(super) fn int_type_string(representation: Option<&str>) -> String {
68
1598
    match representation {
69
        Some(representation) => format!("int(representation {representation})"),
70
1598
        None => "int".to_string(),
71
    }
72
1598
}
73

            
74
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, Quine, Uniplate)]
75
#[path_prefix(conjure_cp::ast)]
76
/// Variants use the project-wide type/domain ordering; keep broad matches in the same order.
77
pub enum GroundDomain {
78
    /// An empty domain of a given type
79
    Empty(ReturnType),
80
    /// A boolean value (true / false)
81
    Bool,
82
    /// An integer value in the given ranges (e.g. int(1, 3..5)), with an optional representation
83
    /// preference (e.g. `int(representation order, 1..5)`)
84
    Int(Vec<Range<Int>>, Option<String>),
85
    /// A tuple of N elements, each with its own domain, and an optional representation preference
86
    Tuple(Vec<Moo<GroundDomain>>, Option<String>),
87
    /// A record, with an optional representation preference
88
    Record(Vec<FieldGround>, Option<String>),
89
    /// A variant domain with its domain options (reusing field entries), and an optional
90
    /// representation preference
91
    Variant(Vec<FieldGround>, Option<String>),
92
    /// An N-dimensional matrix of elements drawn from the inner domain,
93
    /// and indices from the n index domains, with an optional representation preference
94
    Matrix(Moo<GroundDomain>, Vec<Moo<GroundDomain>>, Option<String>),
95
    /// A sequence of elements drawn from the inner domain
96
    Sequence(SequenceAttr, Moo<GroundDomain>),
97
    /// A set of elements drawn from the inner domain
98
    Set(SetAttr<Int>, Moo<GroundDomain>),
99
    /// A multiset of elements drawn from the inner domain
100
    MSet(MSetAttr<Int>, Moo<GroundDomain>),
101
    /// A function with a domain and codomain
102
    Function(FuncAttr, Moo<GroundDomain>, Moo<GroundDomain>),
103
    /// A relation as a set of tuples
104
    Relation(RelAttr, Vec<Moo<GroundDomain>>),
105
    /// An enumerated type
106
    EnumeratedType(Moo<EnumeratedType>, Vec<Range<u32>>),
107
    /// An unnamed type
108
    UnnamedType(Moo<UnnamedType>),
109
    /// A partition
110
    Partition(PartitionAttr, Moo<GroundDomain>),
111
    /// A permutation
112
    Permutation(PermutationAttr, Moo<GroundDomain>),
113
}
114

            
115
/// Counts partitions of `n` labelled elements into unlabelled blocks that are *all* exactly
116
/// `block_size` (a `regular` partition), i.e. `n / (block_size! ^ k) / k!` for `k = n /
117
/// block_size` -- computed by choosing the `k` same-size groups one at a time (dividing out the
118
/// groups' own arbitrary ordering with one final `/ k!`) rather than via raw factorials, so
119
/// intermediate values stay as small as the final answer allows.
120
1
fn regular_partition_count(n: u64, block_size: u64) -> Result<u64, DomainOpError> {
121
1
    if n == 0 {
122
        return Ok(1);
123
1
    }
124
1
    if block_size == 0 || !n.is_multiple_of(block_size) {
125
        return Ok(0);
126
1
    }
127
1
    let num_parts = n / block_size;
128
1
    let mut numerator = 1u64;
129
1
    let mut remaining = n;
130
1
    for _ in 0..num_parts {
131
2
        let choose = count_combinations(remaining, block_size)?;
132
2
        numerator = numerator
133
2
            .checked_mul(choose)
134
2
            .ok_or(DomainOpError::TooLarge)?;
135
2
        remaining -= block_size;
136
    }
137
1
    let num_parts_factorial = (1..=num_parts)
138
2
        .try_fold(1u64, |acc, x| acc.checked_mul(x))
139
1
        .ok_or(DomainOpError::TooLarge)?;
140
1
    numerator
141
1
        .checked_div(num_parts_factorial)
142
1
        .ok_or(DomainOpError::TooLarge)
143
1
}
144

            
145
/// Every unordered partition of `elements` into blocks whose size falls in `[block_min,
146
/// block_max]` (both inclusive; `block_min` is floored to `1`, since a block can't be empty).
147
///
148
/// Built by always rooting the block containing the *first* remaining element (`elements` is
149
/// consumed in its given order), then recursing on whatever's left -- the same canonical
150
/// construction [`restricted_partition_count`] counts without generating, so the two stay
151
/// consistent by construction. Each returned partition is a `Vec` of blocks in the order they
152
/// were rooted; each block is a `Vec` of elements in `[root, ..combination order]`.
153
30
fn restricted_partitions(
154
30
    elements: &[Literal],
155
30
    block_min: usize,
156
30
    block_max: usize,
157
30
) -> Vec<Vec<Vec<Literal>>> {
158
30
    let block_min = block_min.max(1);
159
30
    if elements.is_empty() {
160
15
        return vec![vec![]];
161
15
    }
162
15
    let (first, rest) = elements.split_first().expect("checked non-empty above");
163

            
164
15
    let max_extra = block_max.saturating_sub(1).min(rest.len());
165
15
    if block_min.saturating_sub(1) > max_extra {
166
        return vec![];
167
15
    }
168

            
169
15
    let mut results = vec![];
170
15
    for extra in block_min.saturating_sub(1)..=max_extra {
171
24
        for combo in rest.iter().cloned().combinations(extra) {
172
29
            let mut block = vec![first.clone()];
173
29
            block.extend(combo.iter().cloned());
174

            
175
29
            let remaining: Vec<Literal> = rest
176
29
                .iter()
177
40
                .filter(|elem| !combo.contains(elem))
178
                .cloned()
179
                .collect();
180

            
181
29
            for sub_partition in restricted_partitions(&remaining, block_min, block_max) {
182
37
                let mut whole = vec![block.clone()];
183
37
                whole.extend(sub_partition);
184
37
                results.push(whole);
185
            }
186
        }
187
    }
188
15
    results
189
30
}
190

            
191
/// Every permutation of `n` in `0..len` (representing positions in `elements`) whose number of
192
/// non-fixed positions falls in `[moved_min, moved_max]`, returned as the concrete value each
193
/// position maps to (i.e. `result[i]` is what `elements[i]` is sent to).
194
1
fn restricted_permutations(
195
1
    elements: &[Literal],
196
1
    moved_min: usize,
197
1
    moved_max: usize,
198
1
) -> impl Iterator<Item = Vec<Literal>> + '_ {
199
1
    let n = elements.len();
200
1
    (0..n)
201
1
        .permutations(n)
202
6
        .filter(move |perm| {
203
18
            let moved = perm.iter().enumerate().filter(|&(i, p)| i != *p).count();
204
6
            moved >= moved_min && moved <= moved_max
205
6
        })
206
9
        .map(move |perm| perm.into_iter().map(|i| elements[i].clone()).collect())
207
1
}
208

            
209
/// Converts a full position-to-position permutation mapping (`elements[i]` maps to `mapped[i]`)
210
/// into cycle notation, omitting fixed points -- mirrors
211
/// `PermutationAsFunction`'s own `up()` (`crates/conjure-cp-rules/src/types/permutation/
212
/// as_function/representation.rs`), duplicated here since domain-level enumeration lives in a
213
/// lower crate that representation can't depend on.
214
3
fn permutation_mapping_to_cycles(elements: &[Literal], mapped: &[Literal]) -> Vec<Vec<Literal>> {
215
    let forward: std::collections::HashMap<&Literal, &Literal> =
216
3
        elements.iter().zip(mapped.iter()).collect();
217
3
    let mut visited: std::collections::HashSet<&Literal> = std::collections::HashSet::new();
218
3
    let mut cycles = vec![];
219
3
    for start in elements {
220
9
        if visited.contains(start) {
221
3
            continue;
222
6
        }
223
6
        let image = forward[start];
224
6
        if image == start {
225
3
            visited.insert(start);
226
3
            continue;
227
3
        }
228
3
        let mut cycle = vec![start.clone()];
229
3
        visited.insert(start);
230
3
        let mut current = image;
231
6
        while current != start {
232
3
            visited.insert(current);
233
3
            cycle.push(current.clone());
234
3
            current = forward[current];
235
        }
236
3
        cycles.push(cycle);
237
    }
238
3
    cycles
239
3
}
240

            
241
/// The size attribute covering both sizes, for a union of two sequence domains.
242
///
243
/// Sizes have to survive the union: without a maximum, nothing downstream can tell how many
244
/// positions a sequence has -- iterating one, for instance, needs to know the range of positions.
245
12702
fn union_sequence_sizes(left: &Range<i32>, right: &Range<i32>) -> Range<i32> {
246
25404
    let bounds = |range: &Range<i32>| match range {
247
25056
        Range::Single(size) => (*size, Some(*size)),
248
348
        Range::Bounded(min, max) => (*min, Some(*max)),
249
        Range::UnboundedL(max) => (0, Some(*max)),
250
        Range::UnboundedR(min) => (*min, None),
251
        Range::Unbounded => (0, None),
252
25404
    };
253

            
254
12702
    let (left_min, left_max) = bounds(left);
255
12702
    let (right_min, right_max) = bounds(right);
256
12702
    let min = left_min.min(right_min);
257

            
258
12702
    match (left_max, right_max) {
259
        (Some(left_max), Some(right_max)) => {
260
12702
            let max = left_max.max(right_max);
261
12702
            if min == max {
262
12238
                Range::Single(min)
263
            } else {
264
464
                Range::Bounded(min, max)
265
            }
266
        }
267
        _ => Range::UnboundedR(min),
268
    }
269
12702
}
270

            
271
impl GroundDomain {
272
3734339
    pub fn union(&self, other: &GroundDomain) -> Result<GroundDomain, DomainOpError> {
273
        // Keep implemented variants before `todo!` variants so mixed-domain unions report a type
274
        // error instead of entering an unsupported implementation. Each group uses declaration
275
        // order.
276
3734339
        match (self, other) {
277
            (GroundDomain::UnnamedType(lhs), GroundDomain::UnnamedType(rhs)) if lhs == rhs => {
278
                Ok(self.clone())
279
            }
280
            (GroundDomain::EnumeratedType(_, _) | GroundDomain::UnnamedType(_), _)
281
            | (_, GroundDomain::EnumeratedType(_, _) | GroundDomain::UnnamedType(_)) => {
282
                Err(DomainOpError::WrongType)
283
            }
284
            (GroundDomain::Empty(ty), dom) | (dom, GroundDomain::Empty(ty)) => {
285
                if *ty == dom.return_type() {
286
                    Ok(dom.clone())
287
                } else {
288
                    Err(DomainOpError::WrongType)
289
                }
290
            }
291
177248
            (GroundDomain::Bool, GroundDomain::Bool) => Ok(GroundDomain::Bool),
292
            (GroundDomain::Bool, _) | (_, GroundDomain::Bool) => Err(DomainOpError::WrongType),
293
            (GroundDomain::Int(r1, _), GroundDomain::Int(r2, _)) => {
294
2994232
                let mut rngs = r1.clone();
295
2994232
                rngs.extend(r2.clone());
296
2994232
                Ok(GroundDomain::Int(Range::squeeze(&rngs), None))
297
            }
298
            (GroundDomain::Int(_, _), _) | (_, GroundDomain::Int(_, _)) => {
299
                Err(DomainOpError::WrongType)
300
            }
301
            (GroundDomain::Tuple(in1s, _), GroundDomain::Tuple(in2s, _))
302
182033
                if in1s.len() == in2s.len() =>
303
            {
304
182033
                let mut inners = Vec::new();
305
182033
                for (in1, in2) in zip(in1s, in2s) {
306
364066
                    inners.push(Moo::new(in1.union(in2)?));
307
                }
308
182033
                Ok(GroundDomain::Tuple(inners, None))
309
            }
310
            (GroundDomain::Tuple(_, _), _) | (_, GroundDomain::Tuple(_, _)) => {
311
                Err(DomainOpError::WrongType)
312
            }
313
            (GroundDomain::Record(in1s, _), GroundDomain::Record(in2s, _))
314
2117
                if in1s.len() == in2s.len() =>
315
            {
316
                let lhs_fields: BTreeMap<&Name, &Moo<GroundDomain>> =
317
4234
                    in1s.iter().map(|x| (&x.name, &x.value)).collect();
318
                let rhs_fields: BTreeMap<&Name, &Moo<GroundDomain>> =
319
4234
                    in2s.iter().map(|x| (&x.name, &x.value)).collect();
320
2117
                let mut new_fields = Vec::with_capacity(in1s.len());
321
2117
                for (n, d) in lhs_fields {
322
4234
                    let d2 = rhs_fields.get(&n).ok_or(DomainOpError::WrongType)?;
323
4234
                    let dom = d.union(d2)?;
324
4234
                    new_fields.push(Field {
325
4234
                        name: n.clone(),
326
4234
                        value: dom.into(),
327
4234
                    });
328
                }
329
2117
                Ok(GroundDomain::Record(new_fields, None))
330
            }
331
            (GroundDomain::Record(_, _), _) | (_, GroundDomain::Record(_, _)) => {
332
                Err(DomainOpError::WrongType)
333
            }
334
            (GroundDomain::Matrix(in1, idx1, _), GroundDomain::Matrix(in2, idx2, _))
335
291651
                if idx1 == idx2 =>
336
            {
337
                Ok(GroundDomain::Matrix(
338
291651
                    Moo::new(in1.union(in2)?),
339
291651
                    idx1.clone(),
340
291651
                    None,
341
                ))
342
            }
343
            (GroundDomain::Matrix(_, _, _), _) | (_, GroundDomain::Matrix(_, _, _)) => {
344
                Err(DomainOpError::WrongType)
345
            }
346
            (GroundDomain::Set(_, in1), GroundDomain::Set(_, in2)) => Ok(GroundDomain::Set(
347
74356
                SetAttr::default(),
348
74356
                Moo::new(in1.union(in2)?),
349
            )),
350
            (GroundDomain::Set(_, _), _) | (_, GroundDomain::Set(_, _)) => {
351
                Err(DomainOpError::WrongType)
352
            }
353
            (GroundDomain::MSet(_, in1), GroundDomain::MSet(_, in2)) => Ok(GroundDomain::MSet(
354
                MSetAttr::default(),
355
                Moo::new(in1.union(in2)?),
356
            )),
357
            (GroundDomain::Sequence(attr1, in1), GroundDomain::Sequence(attr2, in2)) => {
358
                Ok(GroundDomain::Sequence(
359
12702
                    SequenceAttr {
360
12702
                        size: union_sequence_sizes(&attr1.size, &attr2.size),
361
12702
                        ..SequenceAttr::default()
362
12702
                    },
363
12702
                    Moo::new(in1.union(in2)?),
364
                ))
365
            }
366
            (GroundDomain::Sequence(_, _), _) | (_, GroundDomain::Sequence(_, _)) => {
367
                Err(DomainOpError::WrongType)
368
            }
369
            (GroundDomain::Relation(_, in1s), GroundDomain::Relation(_, in2s)) => {
370
                let mut inners = Vec::new();
371
                for (in1, in2) in zip(in1s, in2s) {
372
                    inners.push(Moo::new(in1.union(in2)?));
373
                }
374
                Ok(GroundDomain::Relation(RelAttr::default(), inners))
375
            }
376
            (GroundDomain::Relation(..), _) | (_, GroundDomain::Relation(..)) => {
377
                Err(DomainOpError::WrongType)
378
            }
379
            #[allow(unreachable_patterns)]
380
            (GroundDomain::Variant(_, _), _) | (_, GroundDomain::Variant(_, _)) => {
381
                todo!("union variant domains")
382
            }
383
            #[allow(unreachable_patterns)]
384
            (GroundDomain::Function(..), _) | (_, GroundDomain::Function(..)) => {
385
                todo!("union function domains")
386
            }
387
            #[allow(unreachable_patterns)]
388
            (GroundDomain::Partition(..), _) | (_, GroundDomain::Partition(..)) => {
389
                todo!("union partition domains")
390
            }
391
            #[allow(unreachable_patterns)]
392
            (GroundDomain::Permutation(..), _) | (_, GroundDomain::Permutation(..)) => {
393
                todo!("union permutation domains")
394
            }
395
        }
396
3734339
    }
397

            
398
    /// Calculates the intersection of two domains.
399
    ///
400
    /// # Errors
401
    ///
402
    ///  - [`DomainOpError::Unbounded`] if either of the input domains are unbounded.
403
    ///  - [`DomainOpError::WrongType`] if the input domains are different types, or are not integer or set domains.
404
968256
    pub fn intersect(&self, other: &GroundDomain) -> Result<GroundDomain, DomainOpError> {
405
        // TODO: does not consider unbounded domains yet
406
        // needs to be tested once comprehension rules are written
407

            
408
968256
        match (self, other) {
409
            (GroundDomain::UnnamedType(lhs), GroundDomain::UnnamedType(rhs)) if lhs == rhs => {
410
                Ok(self.clone())
411
            }
412
            // one or more arguments is an empty int domain
413
87
            (d @ GroundDomain::Empty(ReturnType::Int), GroundDomain::Int(_, _)) => Ok(d.clone()),
414
1
            (GroundDomain::Int(_, _), d @ GroundDomain::Empty(ReturnType::Int)) => Ok(d.clone()),
415
            (GroundDomain::Empty(ReturnType::Int), d @ GroundDomain::Empty(ReturnType::Int)) => {
416
                Ok(d.clone())
417
            }
418

            
419
            // one or more arguments is an empty set(int) domain
420
            (GroundDomain::Set(_, inner1), d @ GroundDomain::Empty(ReturnType::Set(inner2)))
421
                if matches!(
422
                    **inner1,
423
                    GroundDomain::Int(_, _) | GroundDomain::Empty(ReturnType::Int)
424
                ) && matches!(**inner2, ReturnType::Int) =>
425
            {
426
                Ok(d.clone())
427
            }
428
            (d @ GroundDomain::Empty(ReturnType::Set(inner1)), GroundDomain::Set(_, inner2))
429
                if matches!(**inner1, ReturnType::Int)
430
                    && matches!(
431
                        **inner2,
432
                        GroundDomain::Int(_, _) | GroundDomain::Empty(ReturnType::Int)
433
                    ) =>
434
            {
435
                Ok(d.clone())
436
            }
437
            (
438
                d @ GroundDomain::Empty(ReturnType::Set(inner1)),
439
                GroundDomain::Empty(ReturnType::Set(inner2)),
440
            ) if matches!(**inner1, ReturnType::Int) && matches!(**inner2, ReturnType::Int) => {
441
                Ok(d.clone())
442
            }
443

            
444
            // both arguments are non-empy
445
            (GroundDomain::Set(_, x), GroundDomain::Set(_, y)) => Ok(GroundDomain::Set(
446
                SetAttr::default(),
447
                Moo::new((*x).intersect(y)?),
448
            )),
449

            
450
            (GroundDomain::Int(a, _), GroundDomain::Int(b, _)) => {
451
                // Intersect intervals, not their expanded values. In particular,
452
                // full machine-sized domains must not allocate billions of ints.
453
2086090
                if a.iter().chain(b).any(|range| !range.is_finite()) {
454
2
                    return Err(DomainOpError::Unbounded);
455
968166
                }
456
968166
                let mut intersections = Vec::new();
457
968166
                for left in a {
458
1009824
                    for right in b {
459
1221021
                        let lo = *left
460
1221021
                            .low()
461
1221021
                            .ok_or(DomainOpError::Unbounded)?
462
1221021
                            .max(right.low().ok_or(DomainOpError::Unbounded)?);
463
1221021
                        let hi = *left
464
1221021
                            .high()
465
1221021
                            .ok_or(DomainOpError::Unbounded)?
466
1221021
                            .min(right.high().ok_or(DomainOpError::Unbounded)?);
467
1221021
                        if lo <= hi {
468
1028972
                            intersections.push((lo, hi));
469
192049
                        }
470
                    }
471
                }
472
968166
                intersections.sort_unstable();
473
968166
                let mut merged: Vec<(i32, i32)> = Vec::new();
474
968166
                for (lo, hi) in intersections {
475
1028972
                    if let Some(last) = merged.last_mut()
476
65026
                        && i64::from(lo) <= i64::from(last.1) + 1
477
                    {
478
6147
                        last.1 = last.1.max(hi);
479
                    } else {
480
1022825
                        merged.push((lo, hi));
481
                    }
482
                }
483
968166
                if merged.is_empty() {
484
4220
                    Ok(GroundDomain::Empty(ReturnType::Int))
485
                } else {
486
                    Ok(GroundDomain::Int(
487
963946
                        merged
488
963946
                            .into_iter()
489
1022825
                            .map(|(lo, hi)| Range::new(Some(lo), Some(hi)))
490
                            .collect(),
491
963946
                        None,
492
                    ))
493
                }
494
            }
495
            (GroundDomain::Relation(_, _), GroundDomain::Relation(_, _)) => {
496
                todo!("Relation union not yet supported")
497
            }
498
            _ => Err(DomainOpError::WrongType),
499
        }
500
968256
    }
501

            
502
24350727
    pub fn values(&self) -> Result<Box<dyn Iterator<Item = Literal>>, DomainOpError> {
503
24350727
        match self {
504
            GroundDomain::Empty(_) => Ok(Box::new(vec![].into_iter())),
505
            GroundDomain::EnumeratedType(ty, ranges) => {
506
8
                let values = enum_indices(ty, ranges)
507
8
                    .map(|index| {
508
8
                        Literal::EnumVariant(crate::ast::enumerated::EnumVariant::new(
509
8
                            ty.clone(),
510
8
                            index,
511
8
                        ))
512
8
                    })
513
                    .collect::<Vec<_>>();
514
8
                Ok(Box::new(values.into_iter()))
515
            }
516
            GroundDomain::UnnamedType(ty) => {
517
40
                let ty = ty.clone();
518
80
                Ok(Box::new((1..=ty.size).map(move |value| {
519
80
                    Literal::Unnamed(crate::ast::unnamed::UnnamedValue {
520
80
                        ty: ty.clone(),
521
80
                        value,
522
80
                    })
523
80
                })))
524
            }
525
61547
            GroundDomain::Bool => Ok(Box::new(
526
61547
                vec![Literal::from(false), Literal::from(true)].into_iter(),
527
61547
            )),
528
            GroundDomain::Int(rngs, _) => {
529
23901439
                let rng_iters = rngs
530
23901439
                    .iter()
531
23901439
                    .map(Range::iter)
532
23901439
                    .collect::<Option<Vec<_>>>()
533
23901439
                    .ok_or(DomainOpError::Unbounded)?;
534
23850250
                Ok(Box::new(
535
24035792
                    rng_iters.into_iter().flat_map(|ri| ri.map(Literal::from)),
536
                ))
537
            }
538
            GroundDomain::Tuple(elem_doms, _) => {
539
                // Collect the possible values for each element
540
308244
                let elem_value_pools: Vec<Vec<Literal>> = elem_doms
541
308244
                    .iter()
542
616779
                    .map(|d| d.values().map(|it| it.collect()))
543
                    .collect::<Result<_, _>>()?;
544

            
545
                // Generate all combinations in lexicographic order
546
308244
                let iter = elem_value_pools
547
308244
                    .into_iter()
548
308244
                    .multi_cartesian_product()
549
1249458
                    .map(|elems| Literal::AbstractLiteral(AbstractLiteral::Tuple(elems)));
550

            
551
308244
                Ok(Box::new(iter))
552
            }
553
            GroundDomain::Record(entries, _) => {
554
                // Sort entries by name
555
6092
                let mut sorted: Vec<&_> = entries.iter().collect();
556
6092
                sorted.sort_by(|a, b| a.name.cmp(&b.name));
557

            
558
12184
                let names: Vec<_> = sorted.iter().map(|e| e.name.clone()).collect();
559
6092
                let value_pools: Vec<Vec<Literal>> = sorted
560
6092
                    .iter()
561
12184
                    .map(|e| e.value.values().map(|it| it.collect()))
562
                    .collect::<Result<_, _>>()?;
563

            
564
                // Generate all combinations in lexicographic order
565
6092
                let iter = value_pools
566
6092
                    .into_iter()
567
6092
                    .multi_cartesian_product()
568
24370
                    .map(move |vals| {
569
24370
                        let record_entries = names
570
24370
                            .iter()
571
24370
                            .cloned()
572
24370
                            .zip(vals)
573
48740
                            .map(|(name, value)| Field { name, value })
574
                            .collect();
575
24370
                        Literal::AbstractLiteral(AbstractLiteral::Record(record_entries))
576
24370
                    });
577

            
578
6092
                Ok(Box::new(iter))
579
            }
580
            GroundDomain::Variant(entries, _) => {
581
88
                let values = entries
582
88
                    .iter()
583
176
                    .map(|entry| {
584
176
                        let name = entry.name.clone();
585
176
                        entry.value.values().map(|values| {
586
265
                            values.map(move |value| {
587
265
                                Literal::AbstractLiteral(AbstractLiteral::Variant(Moo::new(
588
265
                                    Field {
589
265
                                        name: name.clone(),
590
265
                                        value,
591
265
                                    },
592
265
                                )))
593
265
                            })
594
176
                        })
595
176
                    })
596
                    .collect::<Result<Vec<_>, _>>()?;
597
88
                Ok(Box::new(values.into_iter().flatten()))
598
            }
599
            GroundDomain::Matrix(elem_dom, idx_doms, _) => {
600
62714
                let shape = matrix::shape_of_dom(self)?;
601
62714
                let idx_doms = idx_doms.clone();
602

            
603
                // Collect all possible element values
604
62714
                let elem_values: Vec<Literal> = elem_dom.values()?.collect();
605

            
606
                // Generate all possible cell assignments in lexicographic order
607
62714
                let iter = std::iter::repeat_n(elem_values, shape.size)
608
62714
                    .multi_cartesian_product()
609
4513504
                    .map(move |flat_elems| {
610
4513504
                        matrix::unflatten_matrix::<Literal>(&flat_elems, &idx_doms, &shape.strides)
611
4513504
                    });
612

            
613
62714
                Ok(Box::new(iter))
614
            }
615
            GroundDomain::Sequence(attrs, inner_dom) => {
616
                if attrs.jectivity != JectivityAttr::None {
617
                    // Injective/surjective/bijective sequence enumeration is not yet needed by any
618
                    // in-scope case (the two exhaustive tests using it are deferred pending enum
619
                    // support), so it is left unimplemented rather than guessed at untested.
620
                    todo!("Enumerating jective sequence domains is not yet supported")
621
                }
622

            
623
                let min_sz = attrs.size.low().copied().unwrap_or(0).max(0);
624
                let max_sz = attrs.size.high().copied().ok_or(DomainOpError::Unbounded)?;
625

            
626
                let pool = inner_dom.values()?.collect_vec();
627

            
628
                let iter = (min_sz..=max_sz)
629
                    .flat_map(move |sz| {
630
                        std::iter::repeat_n(pool.clone(), sz.max(0) as usize)
631
                            .multi_cartesian_product()
632
                    })
633
                    .map(|elems| Literal::AbstractLiteral(AbstractLiteral::Sequence(elems)));
634

            
635
                Ok(Box::new(iter))
636
            }
637
            GroundDomain::Set(attrs, inner_dom) => {
638
10553
                let n: Int = inner_dom.len_usize()?.try_into()?;
639
10553
                let min_sz = attrs.size.low().copied().unwrap_or(0);
640
10553
                let max_sz = attrs.size.high().copied().unwrap_or(n);
641

            
642
10553
                let pool = inner_dom.values()?.collect_vec();
643

            
644
10553
                Ok(Box::new(
645
10553
                    (min_sz..=max_sz)
646
15455
                        .flat_map(move |sz| pool.clone().into_iter().combinations(sz as usize))
647
31555
                        .map(|elems| Literal::AbstractLiteral(AbstractLiteral::Set(elems))),
648
                ))
649
            }
650
            GroundDomain::MSet(..) => todo!("Enumerating multi-set domains is not yet supported"),
651
            GroundDomain::Function(..) => {
652
                todo!("Enumerating function domains is not yet supported")
653
            }
654
            GroundDomain::Relation(..) => {
655
                todo!("Enumerating relation domains is not yet supported")
656
            }
657
            GroundDomain::Partition(attr, inner_dom) => {
658
1
                let elements: Vec<Literal> = inner_dom.values()?.collect();
659
1
                let n = elements.len();
660

            
661
1
                let block_lo = attr.part_len.low().copied().unwrap_or(1).max(1) as usize;
662
1
                let block_hi = attr
663
1
                    .part_len
664
1
                    .high()
665
1
                    .copied()
666
                    .map(|h| (h.max(0) as usize).min(n))
667
1
                    .unwrap_or(n);
668
1
                let parts_lo = attr.num_parts.low().copied().unwrap_or(0).max(0) as usize;
669
1
                let parts_hi = attr
670
1
                    .num_parts
671
1
                    .high()
672
1
                    .copied()
673
1
                    .map(|h| (h.max(0) as usize).min(n))
674
1
                    .unwrap_or(n);
675
1
                let is_regular = attr.is_regular;
676

            
677
1
                let partitions = if block_lo > block_hi {
678
                    vec![]
679
                } else {
680
1
                    restricted_partitions(&elements, block_lo, block_hi)
681
                };
682
15
                let iter = partitions.into_iter().filter(move |parts| {
683
15
                    let k = parts.len();
684
15
                    if k < parts_lo || k > parts_hi {
685
2
                        return false;
686
13
                    }
687
13
                    !is_regular
688
                        || parts
689
                            .first()
690
                            .is_none_or(|first| parts.iter().all(|p| p.len() == first.len()))
691
15
                });
692
13
                Ok(Box::new(iter.map(|parts| {
693
13
                    Literal::AbstractLiteral(AbstractLiteral::Partition(parts))
694
13
                })))
695
            }
696
            GroundDomain::Permutation(attr, inner_dom) => {
697
1
                let elements: Vec<Literal> = inner_dom.values()?.collect();
698
1
                let n = elements.len();
699

            
700
1
                let moved_lo = attr.num_moved.low().copied().unwrap_or(0).max(0) as usize;
701
1
                let moved_hi = attr
702
1
                    .num_moved
703
1
                    .high()
704
1
                    .copied()
705
1
                    .map(|h| (h.max(0) as usize).min(n))
706
1
                    .unwrap_or(n);
707

            
708
1
                if moved_lo > moved_hi {
709
                    return Ok(Box::new(std::iter::empty()));
710
1
                }
711
                let mappings: Vec<Vec<Literal>> =
712
1
                    restricted_permutations(&elements, moved_lo, moved_hi).collect();
713
3
                let values = mappings.into_iter().map(move |mapped| {
714
3
                    let cycles = permutation_mapping_to_cycles(&elements, &mapped);
715
3
                    Literal::AbstractLiteral(AbstractLiteral::Permutation(cycles))
716
3
                });
717
1
                Ok(Box::new(values.collect_vec().into_iter()))
718
            }
719
        }
720
24350727
    }
721

            
722
    /// Gets the length of this domain.
723
    ///
724
    /// # Errors
725
    ///
726
    /// - [`DomainOpError::Unbounded`] if the input domain is of infinite size.
727
13905463
    pub fn length(&self) -> Result<u64, DomainOpError> {
728
13905463
        match self {
729
48
            GroundDomain::Empty(_) => Ok(0),
730
5090535
            GroundDomain::Bool => Ok(2),
731
            GroundDomain::Int(ranges, _) => {
732
7456893
                if ranges.is_empty() {
733
271
                    return Ok(0);
734
7456622
                }
735

            
736
7456622
                let mut length = 0u64;
737
7456622
                for range in ranges {
738
7648325
                    if let Some(range_length) = range.length() {
739
7648315
                        length += range_length as u64;
740
                    } else {
741
10
                        return Err(DomainOpError::Unbounded);
742
                    }
743
                }
744
7456612
                Ok(length)
745
            }
746
            GroundDomain::Tuple(domains, _) => {
747
394402
                let mut ans = 1u64;
748
394402
                for domain in domains {
749
792024
                    ans = ans
750
792024
                        .checked_mul(domain.length()?)
751
792024
                        .ok_or(DomainOpError::TooLarge)?;
752
                }
753
394402
                Ok(ans)
754
            }
755
            GroundDomain::Record(entries, _) => {
756
                // A record is just a named tuple
757
5802
                let mut ans = 1u64;
758
5802
                for entry in entries {
759
11604
                    let sz = entry.value.length()?;
760
11604
                    ans = ans.checked_mul(sz).ok_or(DomainOpError::TooLarge)?;
761
                }
762
5802
                Ok(ans)
763
            }
764
            GroundDomain::Variant(entries, _) => {
765
22824
                let mut ans = 0u64;
766
22824
                for entry in entries {
767
45648
                    let sz = entry.value.length()?;
768
                    // Only one field can be in the variant at once
769
45648
                    ans = ans.checked_add(sz).ok_or(DomainOpError::TooLarge)?;
770
                }
771
22824
                Ok(ans)
772
            }
773
            GroundDomain::Matrix(inner_domain, idx_domains, _) => {
774
780960
                let inner_sz = inner_domain.length()?;
775
882859
                let exp = idx_domains.iter().try_fold(1u32, |acc, val| {
776
882859
                    let len = val.length()? as u32;
777
882859
                    acc.checked_mul(len).ok_or(DomainOpError::TooLarge)
778
882859
                })?;
779
780960
                inner_sz.checked_pow(exp).ok_or(DomainOpError::TooLarge)
780
            }
781
            GroundDomain::Sequence(seq_attr, inner_domain) => {
782
145
                if seq_attr.jectivity != JectivityAttr::None {
783
                    // See the matching note on `values()` above: not yet needed by any in-scope case.
784
                    todo!("Length bound of jective sequences is not yet supported");
785
145
                }
786

            
787
145
                let inner_len = inner_domain.length()?;
788
145
                let min_sz = seq_attr.size.low().copied().unwrap_or(0).max(0) as u64;
789
145
                let max_sz = seq_attr
790
145
                    .size
791
145
                    .high()
792
145
                    .copied()
793
145
                    .ok_or(DomainOpError::Unbounded)? as u64;
794

            
795
145
                if min_sz > max_sz {
796
                    return Ok(0);
797
145
                }
798

            
799
145
                let mut ans = 0u64;
800
145
                for sz in min_sz..=max_sz {
801
725
                    let sz: u32 = sz.try_into().map_err(|_| DomainOpError::TooLarge)?;
802
725
                    let c = inner_len.checked_pow(sz).ok_or(DomainOpError::TooLarge)?;
803
725
                    ans = ans.checked_add(c).ok_or(DomainOpError::TooLarge)?;
804
                }
805
145
                Ok(ans)
806
            }
807
            GroundDomain::Set(set_attr, inner_domain) => {
808
106067
                let inner_len = inner_domain.length()?;
809
106065
                let (min_sz, max_sz) = match set_attr.size {
810
13897
                    Range::Unbounded => (0, inner_len),
811
78417
                    Range::Single(n) => (n as u64, n as u64),
812
1
                    Range::UnboundedR(n) => (n as u64, inner_len),
813
9138
                    Range::UnboundedL(n) => (0, n as u64),
814
4612
                    Range::Bounded(min, max) => (min as u64, max as u64),
815
                };
816
                // Attributes may overshoot the inner domain (e.g. maxSize 3 of int(1..2));
817
                // only cardinalities that fit can contribute members.
818
106065
                let max_sz = max_sz.min(inner_len);
819
106065
                if min_sz > max_sz {
820
                    return Ok(0);
821
106065
                }
822
106065
                let mut ans = 0u64;
823
106065
                for sz in min_sz..=max_sz {
824
167759
                    let c = count_combinations(inner_len, sz)?;
825
167758
                    ans = ans.checked_add(c).ok_or(DomainOpError::TooLarge)?;
826
                }
827
106064
                Ok(ans)
828
            }
829
            GroundDomain::MSet(mset_attr, inner_domain) => {
830
                let inner_len = inner_domain.length()?;
831
                let (min_sz, max_sz) = match mset_attr.size {
832
                    Range::Unbounded => (0, inner_len),
833
                    Range::Single(n) => (n as u64, n as u64),
834
                    Range::UnboundedR(n) => (n as u64, inner_len),
835
                    Range::UnboundedL(n) => (0, n as u64),
836
                    Range::Bounded(min, max) => (min as u64, max as u64),
837
                };
838
                let mut ans = 0u64;
839
                for sz in min_sz..=max_sz {
840
                    // need  "multichoose", ((n  k)) == (n+k-1  k)
841
                    // Where n=inner_len and k=sz
842
                    let c = count_combinations(inner_len + sz - 1, sz)?;
843
                    ans = ans.checked_add(c).ok_or(DomainOpError::TooLarge)?;
844
                }
845
                Ok(ans)
846
            }
847
            GroundDomain::Function(attr, domain, codomain) => {
848
817
                let domain_len = domain.length()?;
849
817
                let codomain_len = codomain.length()?;
850

            
851
817
                match (attr.partiality.clone(), attr.jectivity.clone()) {
852
                    (PartialityAttr::Total, JectivityAttr::None) => {
853
                        let exp: u32 = domain_len.try_into()?;
854
                        codomain_len.checked_pow(exp).ok_or(DomainOpError::TooLarge)
855
                    }
856
                    (PartialityAttr::Total, JectivityAttr::Injective) => {
857
1
                        if domain_len > codomain_len {
858
                            return Ok(0);
859
1
                        }
860
1
                        Ok(count_permutations(codomain_len, domain_len)?)
861
                    }
862
                    (PartialityAttr::Total, JectivityAttr::Bijective) => {
863
814
                        if domain_len != codomain_len {
864
1
                            return Ok(0);
865
813
                        }
866
813
                        Ok(count_permutations(domain_len, domain_len)?)
867
                    }
868
                    (PartialityAttr::Total, JectivityAttr::Surjective) => {
869
1
                        let partitions = stirling_second_kind(domain_len, codomain_len)?;
870
1
                        let arrangements = count_permutations(codomain_len, codomain_len)?;
871
1
                        partitions
872
1
                            .checked_mul(arrangements)
873
1
                            .ok_or(DomainOpError::TooLarge)
874
                    }
875
                    (PartialityAttr::Partial, jectivity) => {
876
1
                        let (min_sz, max_sz) = match attr.size {
877
                            Range::Unbounded => (0, domain_len),
878
                            Range::Single(n) => (n as u64, n as u64),
879
                            Range::UnboundedR(n) => (n as u64, domain_len),
880
                            Range::UnboundedL(n) => (0, n as u64),
881
1
                            Range::Bounded(min, max) => (min as u64, max as u64),
882
                        };
883
1
                        let max_sz = max_sz.min(domain_len);
884
1
                        if min_sz > max_sz {
885
                            return Ok(0);
886
1
                        }
887

            
888
1
                        let mut ans = 0u64;
889
1
                        for sz in min_sz..=max_sz {
890
                            // Choose which `sz` domain elements are defined, then count the ways
891
                            // to map exactly those `sz` elements per the jectivity requirement.
892
3
                            let choose = count_combinations(domain_len, sz)?;
893
3
                            let assign = match jectivity {
894
                                JectivityAttr::None => codomain_len
895
                                    .checked_pow(sz.try_into()?)
896
                                    .ok_or(DomainOpError::TooLarge)?,
897
                                JectivityAttr::Injective => {
898
3
                                    if sz > codomain_len {
899
                                        0
900
                                    } else {
901
3
                                        count_permutations(codomain_len, sz)?
902
                                    }
903
                                }
904
                                JectivityAttr::Bijective => {
905
                                    if sz != codomain_len {
906
                                        0
907
                                    } else {
908
                                        count_permutations(codomain_len, codomain_len)?
909
                                    }
910
                                }
911
                                JectivityAttr::Surjective => {
912
                                    let partitions = stirling_second_kind(sz, codomain_len)?;
913
                                    let arrangements =
914
                                        count_permutations(codomain_len, codomain_len)?;
915
                                    partitions
916
                                        .checked_mul(arrangements)
917
                                        .ok_or(DomainOpError::TooLarge)?
918
                                }
919
                            };
920
3
                            let term = choose.checked_mul(assign).ok_or(DomainOpError::TooLarge)?;
921
3
                            ans = ans.checked_add(term).ok_or(DomainOpError::TooLarge)?;
922
                        }
923
1
                        Ok(ans)
924
                    }
925
                }
926
            }
927
            GroundDomain::Relation(attr, domains) => {
928
                let dom_sizes_result: Result<Vec<u64>, DomainOpError> =
929
93844
                    domains.iter().map(|x| x.length()).collect();
930
46922
                let dom_sizes = dom_sizes_result?;
931
46922
                let inner_len: u64 = dom_sizes.iter().product();
932

            
933
46922
                let (min_sz, max_sz) = match attr.size {
934
580
                    Range::Unbounded => (0, inner_len),
935
45559
                    Range::Single(n) => (n as u64, n as u64),
936
                    Range::UnboundedR(n) => (n as u64, inner_len),
937
464
                    Range::UnboundedL(n) => (0, n as u64),
938
319
                    Range::Bounded(min, max) => (min as u64, max as u64),
939
                };
940
46922
                let max_sz = max_sz.min(inner_len);
941
46922
                if min_sz > max_sz {
942
                    return Ok(0);
943
46922
                }
944

            
945
46922
                let mut ans = 0u64;
946
46922
                for sz in min_sz..=max_sz {
947
50518
                    let c = count_combinations(inner_len, sz)?;
948
50518
                    ans = ans.checked_add(c).ok_or(DomainOpError::TooLarge)?;
949
                }
950
46922
                Ok(ans)
951
            }
952
            GroundDomain::Partition(attr, inner_domain) => {
953
5
                let n = inner_domain.length()?;
954
5
                let block_lo = attr.part_len.low().copied().unwrap_or(1).max(1) as u64;
955
5
                let block_hi = attr
956
5
                    .part_len
957
5
                    .high()
958
5
                    .copied()
959
1
                    .map(|h| (h.max(0) as u64).min(n))
960
5
                    .unwrap_or(n);
961
5
                let parts_lo = attr.num_parts.low().copied().unwrap_or(0).max(0) as u64;
962
5
                let parts_hi = attr
963
5
                    .num_parts
964
5
                    .high()
965
5
                    .copied()
966
3
                    .map(|h| (h.max(0) as u64).min(n))
967
5
                    .unwrap_or(n);
968
5
                if block_lo > block_hi || parts_lo > parts_hi {
969
                    return Ok(0);
970
5
                }
971

            
972
5
                if attr.is_regular {
973
1
                    let mut ans = 0u64;
974
1
                    for block_size in block_lo..=block_hi {
975
1
                        if n % block_size != 0 {
976
                            continue;
977
1
                        }
978
1
                        let num_parts = n / block_size;
979
1
                        if num_parts < parts_lo || num_parts > parts_hi {
980
                            continue;
981
1
                        }
982
1
                        let c = regular_partition_count(n, block_size)?;
983
1
                        ans = ans.checked_add(c).ok_or(DomainOpError::TooLarge)?;
984
                    }
985
1
                    Ok(ans)
986
                } else {
987
4
                    let mut ans = 0u64;
988
4
                    for num_parts in parts_lo..=parts_hi {
989
9
                        let c = restricted_partition_count(n, num_parts, block_lo, block_hi)?;
990
9
                        ans = ans.checked_add(c).ok_or(DomainOpError::TooLarge)?;
991
                    }
992
4
                    Ok(ans)
993
                }
994
            }
995
            GroundDomain::Permutation(attr, inner_domain) => {
996
3
                let n = inner_domain.length()?;
997
3
                let moved_lo = attr.num_moved.low().copied().unwrap_or(0).max(0) as u64;
998
3
                let moved_hi = attr
999
3
                    .num_moved
3
                    .high()
3
                    .copied()
2
                    .map(|h| (h.max(0) as u64).min(n))
3
                    .unwrap_or(n);
3
                if moved_lo > moved_hi {
                    return Ok(0);
3
                }
3
                let mut ans = 0u64;
3
                for moved in moved_lo..=moved_hi {
8
                    let choose = count_combinations(n, moved)?;
8
                    let derange = derangements(moved)?;
8
                    let term = choose.checked_mul(derange).ok_or(DomainOpError::TooLarge)?;
8
                    ans = ans.checked_add(term).ok_or(DomainOpError::TooLarge)?;
                }
3
                Ok(ans)
            }
8
            GroundDomain::EnumeratedType(et, ranges) => Ok(enum_indices(et, ranges).count() as u64),
32
            GroundDomain::UnnamedType(ty) => Ok(ty.size as u64),
        }
13905463
    }
    /// Get size of this domain as a [usize]
89104
    pub fn len_usize(&self) -> Result<usize, DomainOpError> {
89104
        self.length()?
            .try_into()
            .map_err(|_| DomainOpError::TooLarge)
89104
    }
64533569
    pub fn contains(&self, lit: &Literal) -> Result<bool, DomainOpError> {
        // not adding a generic wildcard condition for all domains, so that this gives a compile
        // error when a domain is added.
64533569
        match self {
            // empty domain can't contain anything
            GroundDomain::Empty(_) => Ok(false),
1655374
            GroundDomain::Bool => match lit {
1655366
                Literal::Bool(_) => Ok(true),
8
                _ => Ok(false),
            },
47321064
            GroundDomain::Int(ranges, _) => match lit {
                Literal::Int(x) => {
47321064
                    if ranges.is_empty() {
                        return Ok(false);
47321064
                    };
47967499
                    Ok(ranges.iter().any(|range| range.contains(x)))
                }
                _ => Ok(false),
            },
            GroundDomain::Tuple(elem_domains, _) => {
7878343
                match lit {
                    Literal::AbstractLiteral(AbstractLiteral::Tuple(literal_elems)) => {
7878343
                        if elem_domains.len() != literal_elems.len() {
                            return Ok(false);
7878343
                        }
                        // for every element in the tuple literal, check if it is in the corresponding domain
7878343
                        for (elem_domain, elem) in itertools::izip!(elem_domains, literal_elems) {
15851690
                            if !elem_domain.contains(elem)? {
                                return Ok(false);
15851690
                            }
                        }
7878343
                        Ok(true)
                    }
                    _ => Ok(false),
                }
            }
362766
            GroundDomain::Record(entries, _) => match lit {
                Literal::AbstractLiteral(AbstractLiteral::Record(lit_entries)) => {
362766
                    if entries.len() != lit_entries.len() {
                        return Ok(false);
362766
                    }
362766
                    let fields = lit_entries
362766
                        .iter()
725532
                        .map(|field| (&field.name, &field.value))
                        .collect::<BTreeMap<_, _>>();
362766
                    if fields.len() != entries.len() {
                        return Ok(false);
362766
                    }
362766
                    for entry in entries {
725532
                        let Some(value) = fields.get(&entry.name) else {
                            return Ok(false);
                        };
725532
                        if !entry.value.contains(value)? {
1
                            return Ok(false);
725531
                        }
                    }
362765
                    Ok(true)
                }
                _ => Ok(false),
            },
567705
            GroundDomain::Variant(entries, _) => match lit {
                Literal::AbstractLiteral(AbstractLiteral::Variant(lit_entry)) => {
845265
                    let Some(entry) = entries.iter().find(|entry| entry.name == lit_entry.name)
                    else {
                        return Ok(false);
                    };
567705
                    entry.value.contains(&lit_entry.value)
                }
                _ => Ok(false),
            },
            GroundDomain::Matrix(elem_domain, index_domains, _) => {
5195757
                match lit {
                    Literal::AbstractLiteral(AbstractLiteral::Matrix(elems, idx_domain)) => {
                        // Matrix literals are represented as nested 1d matrices, so the elements of
                        // the matrix literal will be the inner dimensions of the matrix.
5195757
                        if elems.is_empty()
87
                            && index_domains
87
                                .iter()
87
                                .any(|index_domain| index_domain.length() == Ok(0))
                        {
87
                            return Ok(true);
5195670
                        }
                        let Some((current_index_domain, remaining_index_domains)) =
5195670
                            index_domains.split_first()
                        else {
                            panic!("a matrix should have at least one index domain");
                        };
5195670
                        if *current_index_domain != *idx_domain {
                            return Ok(false);
5195670
                        };
5195670
                        let next_elem_domain = if remaining_index_domains.is_empty() {
                            // Base case - we have a 1D row. Now check if all elements in the
                            // literal are in this row's element domain.
3389771
                            elem_domain.as_ref().clone()
                        } else {
                            // Otherwise, go down a dimension (e.g. 2D matrix inside a 3D tensor)
1805899
                            GroundDomain::Matrix(
1805899
                                elem_domain.clone(),
1805899
                                remaining_index_domains.to_vec(),
1805899
                                None,
1805899
                            )
                        };
5195670
                        for elem in elems {
10871118
                            if !next_elem_domain.contains(elem)? {
                                return Ok(false);
10871118
                            }
                        }
5195670
                        Ok(true)
                    }
                    _ => Ok(false),
                }
            }
336313
            GroundDomain::Sequence(seq_attr, inner_dom) => match lit {
                Literal::AbstractLiteral(AbstractLiteral::Sequence(elems)) => {
336313
                    let sz = elems.len().to_i32().ok_or(DomainOpError::TooLarge)?;
336313
                    if !seq_attr.size.contains(&sz) {
                        return Ok(false);
336313
                    }
336313
                    for elem in elems {
37584
                        if !inner_dom.contains(elem)? {
                            return Ok(false);
37584
                        }
                    }
336313
                    Ok(true)
                }
                _ => Ok(false),
            },
507974
            GroundDomain::Set(set_attr, inner_domain) => match lit {
                Literal::AbstractLiteral(AbstractLiteral::Set(lit_elems)) => {
                    // check if the literal's size is allowed by the set attribute
507974
                    let sz = crate::ast::literals::distinct_literal_count(lit_elems)
507974
                        .to_i32()
507974
                        .ok_or(DomainOpError::TooLarge)?;
507974
                    if !set_attr.size.contains(&sz) {
                        return Ok(false);
507974
                    }
507974
                    for elem in lit_elems {
991495
                        if !inner_domain.contains(elem)? {
87
                            return Ok(false);
991408
                        }
                    }
507887
                    Ok(true)
                }
                _ => Ok(false),
            },
686778
            GroundDomain::MSet(mset_attr, inner_domain) => match lit {
                Literal::AbstractLiteral(AbstractLiteral::MSet(lit_elems)) => {
                    // check if the literal's size is allowed by the mset attribute
686778
                    let sz = lit_elems.len().to_i32().ok_or(DomainOpError::TooLarge)?;
686778
                    if !mset_attr.size.contains(&sz) {
                        return Ok(false);
686778
                    }
686778
                    for elem in lit_elems {
915704
                        if !inner_domain.contains(elem)? {
                            return Ok(false);
915704
                        }
                    }
686778
                    Ok(true)
                }
                _ => Ok(false),
            },
7904
            GroundDomain::Function(func_attr, domain, codomain) => match lit {
                Literal::AbstractLiteral(AbstractLiteral::Function(lit_elems)) => {
7904
                    let sz = Int::try_from(lit_elems.len()).expect("Should convert");
7904
                    if !func_attr.size.contains(&sz) {
                        return Ok(false);
7904
                    }
7904
                    for lit in lit_elems {
7392
                        let domain_element = &lit.0;
7392
                        let codomain_element = &lit.1;
7392
                        if !domain.contains(domain_element)? {
                            return Ok(false);
7392
                        }
7392
                        if !codomain.contains(codomain_element)? {
                            return Ok(false);
7392
                        }
                    }
7904
                    Ok(true)
                }
                _ => Ok(false),
            },
2117
            GroundDomain::Relation(rel_attr, inner_domains) => match lit {
                Literal::AbstractLiteral(AbstractLiteral::Relation(lit_elems)) => {
                    // check if the literal's size is allowed by the attributes
2117
                    let sz = lit_elems.len().to_i32().ok_or(DomainOpError::TooLarge)?;
2117
                    if !rel_attr.size.contains(&sz) {
                        return Ok(false);
2117
                    }
2117
                    for elem_tuple in lit_elems {
6351
                        if elem_tuple.len() == inner_domains.len() {
6351
                            for (elem, inner_dom) in elem_tuple.iter().zip(inner_domains.iter()) {
12702
                                if !inner_dom.contains(elem)? {
                                    return Ok(false);
12702
                                }
                            }
                        } else {
                            return Ok(false);
                        }
                    }
2117
                    Ok(true)
                }
                _ => Ok(false),
            },
16
            GroundDomain::Partition(attr, dom) => match lit {
                Literal::AbstractLiteral(AbstractLiteral::Partition(lit_elems)) => {
                    // let sz = lit_elems.len().to_i32().ok_or(DomainOpError::TooLarge)?;
16
                    let sz: i32 = lit_elems
16
                        .iter()
16
                        .flatten()
16
                        .count()
16
                        .to_i32()
16
                        .ok_or(DomainOpError::TooLarge)?;
16
                    let min: Option<i32> = match (attr.num_parts.low(), attr.part_len.low()) {
2
                        (Some(x), Some(y)) => Some(x * y),
14
                        _ => None,
                    };
16
                    let max: Option<i32> = match (attr.num_parts.high(), attr.part_len.high()) {
2
                        (Some(x), Some(y)) => Some(x * y),
14
                        _ => None,
                    };
16
                    let rng = Range::new(min, max);
16
                    if !rng.contains(&sz) {
1
                        return Ok(false);
15
                    }
15
                    for elem in lit_elems.iter().flatten() {
62
                        if !dom.contains(elem)? {
                            return Ok(false);
62
                        }
                    }
15
                    Ok(true)
                }
                _ => Ok(false),
            },
            GroundDomain::EnumeratedType(ty, ranges) => Ok(matches!(lit,
                Literal::EnumVariant(value) if value.ty == *ty && (value.variant as usize) < ty.len()
                && (ranges.is_empty() || ranges.iter().any(|range| range.contains(&value.variant)))
            )),
            GroundDomain::UnnamedType(ty) => Ok(
                matches!(lit, Literal::Unnamed(value) if value.ty == *ty && (1..=ty.size).contains(&value.value)),
            ),
11458
            GroundDomain::Permutation(attr, dom) => match lit {
                Literal::AbstractLiteral(AbstractLiteral::Permutation(cycles)) => {
                    // numMoved is the count of moved points (elements mentioned in some cycle),
                    // matching cycle notation's "unmentioned = fixed point" semantics -- not the
                    // inner domain's own size.
11458
                    let sz: i32 = cycles
11458
                        .iter()
11458
                        .flatten()
11458
                        .count()
11458
                        .to_i32()
11458
                        .ok_or(DomainOpError::TooLarge)?;
11458
                    if !attr.num_moved.contains(&sz) {
                        return Ok(false);
11458
                    }
11458
                    for elem in cycles.iter().flatten() {
34545
                        if !dom.contains(elem)? {
                            return Ok(false);
34545
                        }
                    }
11458
                    Ok(true)
                }
                _ => Ok(false),
            },
        }
64533569
    }
    /// Returns a list of all possible values in an integer domain.
    ///
    /// # Errors
    ///
    /// - [`DomainOpError::NotInteger`] if the domain is not an integer domain.
    /// - [`DomainOpError::Unbounded`] if the domain is unbounded.
827480
    pub fn values_i32(&self) -> Result<Vec<i32>, DomainOpError> {
827480
        if let GroundDomain::Empty(ReturnType::Int) = self {
4453
            return Ok(vec![]);
823027
        }
823027
        let GroundDomain::Int(ranges, _) = self else {
            return Err(DomainOpError::NotInteger(self.return_type()));
        };
823027
        if ranges.is_empty() {
            return Ok(vec![]);
823027
        }
823027
        let mut values = vec![];
823027
        for range in ranges {
862088
            match range {
                Range::Single(i) => {
81627
                    values.push(*i);
                }
                Range::Bounded(i, j) => {
780461
                    values.extend(*i..=*j);
                }
                Range::UnboundedR(_) | Range::UnboundedL(_) | Range::Unbounded => {
                    return Err(DomainOpError::Unbounded);
                }
            }
        }
823027
        Ok(values)
827480
    }
    /// Creates an [`Domain::Int`] containing the given integers.
    ///
    /// # Examples
    ///
    /// ```
    /// use conjure_cp_core::ast::{GroundDomain, Range};
    /// use conjure_cp_core::{domain_int_ground,range};
    /// use std::collections::BTreeSet;
    ///
    /// let elements = BTreeSet::from([1,2,3,4,5]);
    ///
    /// let domain = GroundDomain::from_set_i32(&elements);
    ///
    /// assert_eq!(domain,domain_int_ground!(1..5));
    /// ```
    ///
    /// ```
    /// use conjure_cp_core::ast::{GroundDomain,Range};
    /// use conjure_cp_core::{domain_int_ground,range};
    /// use std::collections::BTreeSet;
    ///
    /// let elements = BTreeSet::from([1,2,4,5,7,8,9,10]);
    ///
    /// let domain = GroundDomain::from_set_i32(&elements);
    ///
    /// assert_eq!(domain,domain_int_ground!(1..2,4..5,7..10));
    /// ```
    ///
    /// ```
    /// use conjure_cp_core::ast::{GroundDomain,Range,ReturnType};
    /// use std::collections::BTreeSet;
    ///
    /// let elements = BTreeSet::from([]);
    ///
    /// let domain = GroundDomain::from_set_i32(&elements);
    ///
    /// assert!(matches!(domain,GroundDomain::Empty(ReturnType::Int)))
    /// ```
1444461
    pub fn from_set_i32(elements: &BTreeSet<i32>) -> GroundDomain {
1444461
        if elements.is_empty() {
29
            return GroundDomain::Empty(ReturnType::Int);
1444432
        }
1444432
        if elements.len() == 1 {
698990
            return GroundDomain::Int(vec![Range::Single(*elements.first().unwrap())], None);
745442
        }
745442
        let mut elems_iter = elements.iter().copied();
745442
        let mut ranges: Vec<Range<i32>> = vec![];
        // Loop over the elements in ascending order, turning all sequential runs of
        // numbers into ranges.
        // the bounds of the current run of numbers.
745442
        let mut lower = elems_iter
745442
            .next()
745442
            .expect("if we get here, elements should have => 2 elements");
745442
        let mut upper = lower;
745442
        for current in elems_iter {
            // As elements is a BTreeSet, current is always strictly larger than lower.
1911098
            if current == upper + 1 {
                // current is part of the current run - we now have the run lower..current
                //
1821618
                upper = current;
            } else {
                // the run lower..upper has ended.
                //
                // Add the run lower..upper to the domain, and start a new run.
89480
                if lower == upper {
89053
                    ranges.push(range!(lower));
                } else {
427
                    ranges.push(range!(lower..upper));
                }
89480
                lower = current;
89480
                upper = current;
            }
        }
        // add the final run to the domain
745442
        if lower == upper {
79654
            ranges.push(range!(lower));
        } else {
665788
            ranges.push(range!(lower..upper));
        }
745442
        ranges = Range::squeeze(&ranges);
745442
        GroundDomain::Int(ranges, None)
1444461
    }
    /// Returns the domain that is the result of applying a binary operation to two integer domains.
    ///
    /// The given operator may return `None` if the operation is not defined for its arguments.
    /// Undefined values will not be included in the resulting domain.
    ///
    /// # Errors
    ///
    /// - [`DomainOpError::Unbounded`] if either of the input domains are unbounded.
    /// - [`DomainOpError::NotInteger`] if either of the input domains are not integers.
2
    pub fn apply_i32(
2
        &self,
2
        op: fn(i32, i32) -> Option<i32>,
2
        other: &GroundDomain,
2
    ) -> Result<GroundDomain, DomainOpError> {
2
        let vs1 = self.values_i32()?;
2
        let vs2 = other.values_i32()?;
2
        let mut set = BTreeSet::new();
2
        for (v1, v2) in itertools::iproduct!(vs1, vs2) {
32
            if let Some(v) = op(v1, v2) {
28
                set.insert(v);
4
            }
        }
2
        Ok(GroundDomain::from_set_i32(&set))
2
    }
    /// Returns true if the domain is finite.
    pub fn is_finite(&self) -> bool {
        for domain in self.universe() {
            if let GroundDomain::Int(ranges, _) = domain
                && ranges.iter().any(|range| {
                    matches!(
                        range,
                        Range::UnboundedL(_) | Range::UnboundedR(_) | Range::Unbounded
                    )
                })
            {
                return false;
            }
        }
        true
    }
    /// For a vector of literals, creates a domain that contains all the elements.
    ///
    /// The literals must all be of the same type.
    ///
    /// For abstract literals, this method merges the element domains of the literals, but not the
    /// index domains. Thus, for fixed-sized abstract literals (matrices, tuples, records, etc.),
    /// all literals in the vector must also have the same size / index domain:
    ///
    /// + Matrices: all literals must have the same index domain.
    /// + Tuples: all literals must have the same number of elements.
    /// + Records: all literals must have the same fields.
    ///
    /// # Errors
    ///
    /// - [DomainOpError::WrongType] if the input literals are of a different type to
    ///   each-other, as described above.
    ///
    /// # Examples
    ///
    /// ```
    /// use conjure_cp_core::ast::{Range, Literal, ReturnType, GroundDomain};
    ///
    /// let domain = GroundDomain::from_literal_vec(&vec![]);
    /// assert_eq!(domain,Ok(GroundDomain::Empty(ReturnType::Unknown)));
    /// ```
    ///
    /// ```
    /// use conjure_cp_core::ast::{GroundDomain,Range,Literal, AbstractLiteral};
    /// use conjure_cp_core::{domain_int_ground, range, matrix};
    ///
    /// // `[1,2;int(2..3)], [4,5; int(2..3)]` has domain
    /// // `matrix indexed by [int(2..3)] of int(1..2,4..5)`
    ///
    /// let matrix_1 = Literal::AbstractLiteral(matrix![Literal::Int(1),Literal::Int(2);domain_int_ground!(2..3)]);
    /// let matrix_2 = Literal::AbstractLiteral(matrix![Literal::Int(4),Literal::Int(5);domain_int_ground!(2..3)]);
    ///
    /// let domain = GroundDomain::from_literal_vec(&vec![matrix_1,matrix_2]);
    ///
    /// let expected_domain = Ok(GroundDomain::Matrix(
    ///     domain_int_ground!(1..2,4..5),vec![domain_int_ground!(2..3)],None));
    ///
    /// assert_eq!(domain,expected_domain);
    /// ```
    ///
    /// ```
    /// use conjure_cp_core::ast::{GroundDomain,Range,Literal, AbstractLiteral,DomainOpError};
    /// use conjure_cp_core::{domain_int_ground, range, matrix};
    ///
    /// // `[1,2;int(2..3)], [4,5; int(1..2)]` cannot be combined
    /// // `matrix indexed by [int(2..3)] of int(1..2,4..5)`
    ///
    /// let matrix_1 = Literal::AbstractLiteral(matrix![Literal::Int(1),Literal::Int(2);domain_int_ground!(2..3)]);
    /// let matrix_2 = Literal::AbstractLiteral(matrix![Literal::Int(4),Literal::Int(5);domain_int_ground!(1..2)]);
    ///
    /// let domain = GroundDomain::from_literal_vec(&vec![matrix_1,matrix_2]);
    ///
    /// assert_eq!(domain,Err(DomainOpError::WrongType));
    /// ```
    ///
    /// ```
    /// use conjure_cp_core::ast::{GroundDomain,Range,Literal, AbstractLiteral};
    /// use conjure_cp_core::{domain_int_ground,range, matrix};
    ///
    /// // `[[1,2; int(1..2)];int(2)], [[4,5; int(1..2)]; int(2)]` has domain
    /// // `matrix indexed by [int(2),int(1..2)] of int(1..2,4..5)`
    ///
    ///
    /// let matrix_1 = Literal::AbstractLiteral(matrix![Literal::AbstractLiteral(matrix![Literal::Int(1),Literal::Int(2);domain_int_ground!(1..2)]); domain_int_ground!(2)]);
    /// let matrix_2 = Literal::AbstractLiteral(matrix![Literal::AbstractLiteral(matrix![Literal::Int(4),Literal::Int(5);domain_int_ground!(1..2)]); domain_int_ground!(2)]);
    ///
    /// let domain = GroundDomain::from_literal_vec(&vec![matrix_1,matrix_2]);
    ///
    /// let expected_domain = Ok(GroundDomain::Matrix(
    ///     domain_int_ground!(1..2,4..5),
    ///     vec![domain_int_ground!(2),domain_int_ground!(1..2)],
    ///     None));
    ///
    /// assert_eq!(domain,expected_domain);
    /// ```
    ///
    ///
3706649
    pub fn from_literal_vec(literals: &[Literal]) -> Result<GroundDomain, DomainOpError> {
        // TODO: use proptest to test this better?
3706649
        if literals.is_empty() {
179568
            return Ok(GroundDomain::Empty(ReturnType::Unknown));
3527081
        }
3527081
        let first_literal = literals.first().unwrap();
3527081
        match first_literal {
            Literal::Int(_) => {
                // check all literals are ints, then pass this to Domain::from_set_i32.
1444280
                let mut ints = BTreeSet::new();
1444280
                for lit in literals {
3508890
                    let Literal::Int(i) = lit else {
                        return Err(DomainOpError::WrongType);
                    };
3508890
                    ints.insert(*i);
                }
1444280
                Ok(GroundDomain::from_set_i32(&ints))
            }
            Literal::Bool(_) => {
                // check all literals are bools
807389
                if literals.iter().any(|x| !matches!(x, Literal::Bool(_))) {
                    Err(DomainOpError::WrongType)
                } else {
392921
                    Ok(GroundDomain::Bool)
                }
            }
            Literal::Unnamed(first) => {
                if literals
                    .iter()
                    .all(|value| matches!(value, Literal::Unnamed(value) if value.ty == first.ty))
                {
                    Ok(GroundDomain::UnnamedType(first.ty.clone()))
                } else {
                    Err(DomainOpError::WrongType)
                }
            }
            Literal::EnumVariant(first_ev) => {
                let mut variants = BTreeSet::new();
                for lit in literals {
                    let Literal::EnumVariant(current_ev) = lit else {
                        return Err(DomainOpError::WrongType);
                    };
                    if current_ev.ty != first_ev.ty {
                        return Err(DomainOpError::WrongType);
                    }
                    variants.insert(current_ev.variant);
                }
                Ok(GroundDomain::EnumeratedType(
                    first_ev.ty.clone(),
                    variants.into_iter().map(Range::Single).collect(),
                ))
            }
            Literal::AbstractLiteral(AbstractLiteral::Set(_)) => {
138029
                let mut all_elems = vec![];
138029
                for lit in literals {
138551
                    let Literal::AbstractLiteral(AbstractLiteral::Set(elems)) = lit else {
                        return Err(DomainOpError::WrongType);
                    };
138551
                    all_elems.extend(elems.clone());
                }
138029
                let elem_domain = GroundDomain::from_literal_vec(&all_elems)?;
138029
                Ok(GroundDomain::Set(SetAttr::default(), Moo::new(elem_domain)))
            }
            Literal::AbstractLiteral(AbstractLiteral::MSet(_)) => {
242295
                let mut all_elems = vec![];
242295
                for lit in literals {
242295
                    let Literal::AbstractLiteral(AbstractLiteral::MSet(elems)) = lit else {
                        return Err(DomainOpError::WrongType);
                    };
242295
                    all_elems.extend(elems.clone());
                }
242295
                let elem_domain = GroundDomain::from_literal_vec(&all_elems)?;
242295
                Ok(GroundDomain::MSet(
242295
                    MSetAttr::default(),
242295
                    Moo::new(elem_domain),
242295
                ))
            }
            Literal::AbstractLiteral(AbstractLiteral::Partition(_)) => {
                todo!("Need to figure out how this is going to work")
            }
            Literal::AbstractLiteral(AbstractLiteral::Permutation(_)) => {
                todo!("Need to figure out how this is going to work")
            }
            l @ Literal::AbstractLiteral(AbstractLiteral::Matrix(_, _)) => {
932236
                let mut first_index_domain = vec![];
                // flatten index domains of n-d matrix into list
932236
                let mut l = l.clone();
1864618
                while let Literal::AbstractLiteral(AbstractLiteral::Matrix(elems, idx)) = l {
                    bug_assert!(
932382
                        !matches!(idx.as_ref(), GroundDomain::Matrix(_, _, _)),
                        "n-dimensional matrix literals should be represented as a matrix inside a matrix"
                    );
932382
                    first_index_domain.push(idx);
932382
                    let Some(first_elem) = elems.first() else {
                        break;
                    };
932382
                    l = first_elem.clone();
                }
932236
                let mut all_elems: Vec<Literal> = vec![];
                // check types and index domains
932236
                for lit in literals {
932323
                    let Literal::AbstractLiteral(AbstractLiteral::Matrix(elems, idx)) = lit else {
                        return Err(DomainOpError::NotGround);
                    };
932323
                    all_elems.extend(elems.clone());
932323
                    let mut index_domain = vec![idx.clone()];
932323
                    let Some(first_elem) = elems.first() else {
                        if index_domain != first_index_domain {
                            return Err(DomainOpError::WrongType);
                        }
                        continue;
                    };
932323
                    let mut l = first_elem.clone();
932498
                    while let Literal::AbstractLiteral(AbstractLiteral::Matrix(elems, idx)) = l {
                        bug_assert!(
175
                            !matches!(idx.as_ref(), GroundDomain::Matrix(_, _, _)),
                            "n-dimensional matrix literals should be represented as a matrix inside a matrix"
                        );
175
                        index_domain.push(idx);
175
                        let Some(first_elem) = elems.first() else {
                            break;
                        };
175
                        l = first_elem.clone();
                    }
932323
                    if index_domain != first_index_domain {
29
                        return Err(DomainOpError::WrongType);
932294
                    }
                }
                // extract all the terminal elements (those that are not nested matrix literals) from the matrix literal.
932207
                let mut terminal_elements: Vec<Literal> = vec![];
3992301
                while let Some(elem) = all_elems.pop() {
3060094
                    if let Literal::AbstractLiteral(AbstractLiteral::Matrix(elems, _)) = elem {
669
                        all_elems.extend(elems);
                    } else {
3059425
                        terminal_elements.push(elem);
                    }
                }
932207
                let element_domain = GroundDomain::from_literal_vec(&terminal_elements)?;
932207
                Ok(GroundDomain::Matrix(
932207
                    Moo::new(element_domain),
932207
                    first_index_domain,
932207
                    None,
932207
                ))
            }
            Literal::AbstractLiteral(AbstractLiteral::Tuple(first_elems)) => {
267902
                let n_fields = first_elems.len();
                // for each field, calculate the element domain and add it to this list
267902
                let mut elem_domains = vec![];
267902
                for i in 0..n_fields {
628372
                    let mut all_elems = vec![];
628372
                    for lit in literals {
636724
                        let Literal::AbstractLiteral(AbstractLiteral::Tuple(elems)) = lit else {
                            return Err(DomainOpError::NotGround);
                        };
636724
                        if elems.len() != n_fields {
                            return Err(DomainOpError::NotGround);
636724
                        }
636724
                        all_elems.push(elems[i].clone());
                    }
628372
                    elem_domains.push(Moo::new(GroundDomain::from_literal_vec(&all_elems)?));
                }
267902
                Ok(GroundDomain::Tuple(elem_domains, None))
            }
            Literal::AbstractLiteral(AbstractLiteral::Sequence(_)) => {
70441
                let mut all_elems = vec![];
70441
                let mut lengths = Vec::new();
70441
                for lit in literals {
70441
                    let Literal::AbstractLiteral(AbstractLiteral::Sequence(elems)) = lit else {
                        return Err(DomainOpError::WrongType);
                    };
70441
                    lengths.push(i32::try_from(elems.len()).map_err(|_| DomainOpError::TooLarge)?);
70441
                    all_elems.extend(elems.clone());
                }
70441
                let elem_domain = GroundDomain::from_literal_vec(&all_elems)?;
                // These literals are known values, so their lengths bound the size attribute --
                // without which nothing downstream can tell how many positions to iterate over.
70441
                let min = lengths.iter().copied().min().unwrap_or(0);
70441
                let max = lengths.iter().copied().max().unwrap_or(0);
70441
                let size = if min == max {
70441
                    Range::Single(min)
                } else {
                    Range::Bounded(min, max)
                };
70441
                Ok(GroundDomain::Sequence(
70441
                    SequenceAttr {
70441
                        size,
70441
                        ..SequenceAttr::default()
70441
                    },
70441
                    Moo::new(elem_domain),
70441
                ))
            }
            Literal::AbstractLiteral(AbstractLiteral::Record(first_elems)) => {
22040
                let n_fields = first_elems.len();
44080
                let field_names = first_elems.iter().map(|x| x.name.clone()).collect_vec();
                // for each field, calculate the element domain and add it to this list
22040
                let mut elem_domains = vec![];
22040
                for i in 0..n_fields {
44080
                    let mut all_elems = vec![];
44080
                    for lit in literals {
44080
                        let Literal::AbstractLiteral(AbstractLiteral::Record(elems)) = lit else {
                            return Err(DomainOpError::NotGround);
                        };
44080
                        if elems.len() != n_fields {
                            return Err(DomainOpError::NotGround);
44080
                        }
44080
                        let elem = elems[i].clone();
44080
                        if elem.name != field_names[i] {
                            return Err(DomainOpError::NotGround);
44080
                        }
44080
                        all_elems.push(elem.value);
                    }
44080
                    elem_domains.push(Moo::new(GroundDomain::from_literal_vec(&all_elems)?));
                }
                Ok(GroundDomain::Record(
22040
                    izip!(field_names, elem_domains)
44080
                        .map(|(name, value)| FieldGround { name, value })
                        .collect(),
22040
                    None,
                ))
            }
            Literal::AbstractLiteral(AbstractLiteral::Function(_)) => {
                let mut all_keys = vec![];
                let mut all_values = vec![];
                for lit in literals {
                    let Literal::AbstractLiteral(AbstractLiteral::Function(pairs)) = lit else {
                        return Err(DomainOpError::WrongType);
                    };
                    for (key, value) in pairs {
                        all_keys.push(key.clone());
                        all_values.push(value.clone());
                    }
                }
                let domain = GroundDomain::from_literal_vec(&all_keys)?;
                let codomain = GroundDomain::from_literal_vec(&all_values)?;
                Ok(GroundDomain::Function(
                    FuncAttr::default(),
                    Moo::new(domain),
                    Moo::new(codomain),
                ))
            }
            Literal::AbstractLiteral(AbstractLiteral::Variant(_)) => {
16937
                let mut alternatives: Vec<(Name, Vec<Literal>)> = Vec::new();
16937
                for literal in literals {
42343
                    let Literal::AbstractLiteral(AbstractLiteral::Variant(field)) = literal else {
                        return Err(DomainOpError::WrongType);
                    };
42343
                    if let Some((_, values)) = alternatives
42343
                        .iter_mut()
33874
                        .find(|(name, _)| name == &field.name)
                    {
16937
                        values.push(field.value.clone());
                    } else {
25406
                        alternatives.push((field.name.clone(), vec![field.value.clone()]));
                    }
                }
                Ok(GroundDomain::Variant(
16937
                    alternatives
16937
                        .into_iter()
25406
                        .map(|(name, values)| {
                            Ok(FieldGround {
25406
                                name,
25406
                                value: Moo::new(GroundDomain::from_literal_vec(&values)?),
                            })
25406
                        })
                        .collect::<Result<Vec<_>, DomainOpError>>()?,
16937
                    None,
                ))
            }
            Literal::AbstractLiteral(AbstractLiteral::Relation(_)) => {
                let mut columns: Vec<Vec<Literal>> = vec![];
                for lit in literals {
                    let Literal::AbstractLiteral(AbstractLiteral::Relation(tuples)) = lit else {
                        return Err(DomainOpError::WrongType);
                    };
                    for tuple in tuples {
                        if columns.is_empty() {
                            columns = vec![Vec::new(); tuple.len()];
                        }
                        if tuple.len() != columns.len() {
                            return Err(DomainOpError::NotGround);
                        }
                        for (column, field) in columns.iter_mut().zip(tuple) {
                            column.push(field.clone());
                        }
                    }
                }
                let inner_domains = columns
                    .iter()
                    .map(|column| GroundDomain::from_literal_vec(column).map(Moo::new))
                    .collect::<Result<Vec<_>, _>>()?;
                Ok(GroundDomain::Relation(RelAttr::default(), inner_domains))
            }
        }
3706649
    }
176871
    pub fn element_domain(&self) -> Option<Moo<GroundDomain>> {
176871
        match self {
7656
            GroundDomain::Matrix(inner, _, _) => Some(inner.clone()),
166315
            GroundDomain::Set(_, inner) => Some(inner.clone()),
            GroundDomain::MSet(_, inner) => Some(inner.clone()),
            GroundDomain::Relation(_, inner_doms) => {
2900
                Some(Moo::new(GroundDomain::Tuple(inner_doms.clone(), None)))
            }
            // A sequence is a function from int(1..|s|), and iterating a function yields its
            // pairs, so iterating a sequence yields (position, value).
            GroundDomain::Sequence(attr, inner) => {
                let max = match attr.size {
                    Range::Single(max) | Range::UnboundedL(max) | Range::Bounded(_, max) => max,
                    Range::UnboundedR(_) | Range::Unbounded => return None,
                };
                Some(Moo::new(GroundDomain::Tuple(
                    vec![
                        Moo::new(GroundDomain::Int(vec![Range::Bounded(1, max)], None)),
                        inner.clone(),
                    ],
                    None,
                )))
            }
            _ => None,
        }
176871
    }
    /// True if any domain in this tree has a representation preference.
90
    pub fn has_representation_preference(&self) -> bool {
90
        match self {
            GroundDomain::Empty(_)
            | GroundDomain::EnumeratedType(_, _)
            | GroundDomain::UnnamedType(_) => false,
            GroundDomain::Bool => false,
            GroundDomain::Int(_, representation) => representation.is_some(),
            GroundDomain::Tuple(inners, representation) => {
                representation.is_some() || inners.iter().any(|d| d.has_representation_preference())
            }
            GroundDomain::Record(entries, representation) => {
30
                representation.is_some()
30
                    || entries
30
                        .iter()
30
                        .any(|f| f.value.has_representation_preference())
            }
            GroundDomain::Variant(entries, representation) => {
                representation.is_some()
                    || entries
                        .iter()
                        .any(|f| f.value.has_representation_preference())
            }
            GroundDomain::Matrix(inner, idxs, representation) => {
30
                representation.is_some()
30
                    || inner.has_representation_preference()
                    || idxs.iter().any(|d| d.has_representation_preference())
            }
            GroundDomain::Sequence(attr, inner) => {
                attr.representation.is_some() || inner.has_representation_preference()
            }
            GroundDomain::Set(attr, inner) => {
                attr.representation.is_some() || inner.has_representation_preference()
            }
            GroundDomain::MSet(attr, inner) => {
30
                attr.representation.is_some() || inner.has_representation_preference()
            }
            GroundDomain::Function(attr, dom, cdom) => {
                attr.representation.is_some()
                    || dom.has_representation_preference()
                    || cdom.has_representation_preference()
            }
            GroundDomain::Relation(attr, inners) => {
                attr.representation.is_some()
                    || inners.iter().any(|d| d.has_representation_preference())
            }
            GroundDomain::Partition(attr, inner) => {
                attr.representation.is_some() || inner.has_representation_preference()
            }
            GroundDomain::Permutation(attr, inner) => {
                attr.representation.is_some() || inner.has_representation_preference()
            }
        }
90
    }
    /// Format this domain in Essence type style, omitting size attributes and integer ranges.
2443
    pub fn as_type_string(&self) -> String {
2443
        match self {
            GroundDomain::Empty(ty) => format!("empty({ty})"),
            GroundDomain::EnumeratedType(ty, _) => ty.to_string(),
            GroundDomain::UnnamedType(_) => "unnamed type".to_string(),
            GroundDomain::Bool => "bool".to_string(),
1598
            GroundDomain::Int(_, representation) => int_type_string(representation.as_deref()),
            GroundDomain::Tuple(inners, representation) => {
                let args = representation
                    .iter()
                    .map(|r| format!("representation {r}"))
                    .chain(inners.iter().map(|d| d.as_type_string()))
                    .join(", ");
                format!("tuple ({args})")
            }
            GroundDomain::Record(entries, representation) => {
                let inners = entries
                    .iter()
                    .map(|f| format!("{}: {}", f.name, f.value.as_type_string()))
                    .join(", ");
                format!(
                    "record{} {{{inners}}}",
                    representation_attribute(representation)
                )
            }
            GroundDomain::Variant(entries, representation) => {
                let inners = entries
                    .iter()
                    .map(|f| format!("{}: {}", f.name, f.value.as_type_string()))
                    .join(", ");
                format!(
                    "variant{} {{{inners}}}",
                    representation_attribute(representation)
                )
            }
            GroundDomain::Matrix(inner, idxs, representation) => {
725
                let idxs = idxs.iter().map(|d| d.as_type_string()).join(", ");
725
                format!(
725
                    "matrix{} indexed by [{idxs}] of {}",
725
                    representation_attribute(representation),
725
                    inner.as_type_string()
                )
            }
            GroundDomain::Sequence(attrs, inner) => format!(
                "sequence{} of {}",
                representation_attribute(&attrs.representation),
                inner.as_type_string()
            ),
            GroundDomain::Set(attrs, inner) => {
119
                let mut out = String::from("set");
119
                if let Some(repr) = &attrs.representation {
119
                    out.push_str(" (representation ");
119
                    out.push_str(repr);
119
                    out.push(')');
                }
119
                out.push_str(" of ");
119
                out.push_str(&inner.as_type_string());
119
                out
            }
            GroundDomain::MSet(attrs, inner) => {
1
                let mut out = String::from("mset");
1
                if let Some(repr) = &attrs.representation {
1
                    out.push_str(" (representation ");
1
                    out.push_str(repr);
1
                    out.push(')');
                }
1
                out.push_str(" of ");
1
                out.push_str(&inner.as_type_string());
1
                out
            }
            GroundDomain::Function(attr, dom, cdom) => {
                format!(
                    "function{} {} --> {}",
                    representation_attribute(&attr.representation),
                    dom.as_type_string(),
                    cdom.as_type_string()
                )
            }
            GroundDomain::Relation(attr, inners) => {
                format!(
                    "relation{} of ({})",
                    representation_attribute(&attr.representation),
                    inners.iter().map(|d| d.as_type_string()).join(" * ")
                )
            }
            GroundDomain::Partition(attr, inner) => {
                format!(
                    "partition{} from {}",
                    representation_attribute(&attr.representation),
                    inner.as_type_string()
                )
            }
            GroundDomain::Permutation(attr, inner) => {
                format!(
                    "permutation{} of {}",
                    representation_attribute(&attr.representation),
                    inner.as_type_string()
                )
            }
        }
2443
    }
}
impl Typeable for GroundDomain {
3339136639
    fn return_type(&self) -> ReturnType {
3339136639
        match self {
238179
            GroundDomain::Empty(ty) => ty.clone(),
2829636869
            GroundDomain::Bool => ReturnType::Bool,
384734967
            GroundDomain::Int(_, _) => ReturnType::Int,
            GroundDomain::Tuple(inners, _) => {
20650009
                let mut inner_types = Vec::new();
20650009
                for inner in inners {
42746633
                    inner_types.push(inner.return_type());
                }
20650009
                ReturnType::Tuple(inner_types)
            }
            GroundDomain::Record(entries, _) => {
3117588
                let mut entry_types = Vec::new();
3117588
                for entry in entries {
6235175
                    entry_types.push(entry.clone().func_map(|x| x.return_type()));
                }
3117588
                entry_types.sort();
3117588
                ReturnType::Record(entry_types)
            }
            GroundDomain::Variant(entries, _) => {
5710100
                let mut entry_types = Vec::new();
5710100
                for entry in entries {
11420316
                    entry_types.push(entry.clone().func_map(|x| x.return_type()));
                }
5710100
                ReturnType::Variant(entry_types)
            }
            GroundDomain::Matrix(inner, _idx, _) => {
62301127
                ReturnType::Matrix(Box::new(inner.return_type()))
            }
            GroundDomain::Sequence(_attr, inner) => {
2952722
                ReturnType::Sequence(Box::new(inner.return_type()))
            }
18719445
            GroundDomain::Set(_attr, inner) => ReturnType::Set(Box::new(inner.return_type())),
6238956
            GroundDomain::MSet(_attr, inner) => ReturnType::MSet(Box::new(inner.return_type())),
            GroundDomain::Function(_, dom, cdom) => {
2736472
                ReturnType::Function(Box::new(dom.return_type()), Box::new(cdom.return_type()))
            }
            GroundDomain::Relation(_, inners) => {
2072025
                let mut inner_types = Vec::new();
2072025
                for inner in inners {
4145765
                    inner_types.push(inner.return_type());
                }
2072025
                ReturnType::Relation(inner_types)
            }
            GroundDomain::Partition(_, inner) => {
493
                ReturnType::Partition(Box::new(inner.return_type()))
            }
            GroundDomain::Permutation(_, inner) => {
27579
                ReturnType::Permutation(Box::new(inner.return_type()))
            }
100
            GroundDomain::EnumeratedType(et, _) => ReturnType::EnumeratedType(et.clone()),
            // TODO: Ensure bounded range on type level
8
            GroundDomain::UnnamedType(ty) => ReturnType::UnnamedType(ty.clone()),
        }
3339136639
    }
}
impl Display for FieldGround {
217182
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
217182
        write!(f, "{}: {}", self.name, self.value)
217182
    }
}
impl Display for GroundDomain {
37301349
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
37301349
        match &self {
8
            GroundDomain::Empty(ty) => write!(f, "empty({ty})"),
3671727
            GroundDomain::Bool => write!(f, "bool"),
            GroundDomain::Int(ranges, representation) => {
31023107
                write_int_domain(f, ranges, representation.as_deref())
            }
            GroundDomain::Tuple(domains, representation) => {
268548
                let args = representation
268548
                    .iter()
348
                    .map(|r| format!("representation {r}"))
537593
                    .chain(domains.iter().map(|d| d.to_string()))
268548
                    .join(", ");
268548
                write!(f, "tuple ({args})")
            }
            GroundDomain::Record(entries, representation) => {
197288
                let inners = entries.iter().map(|t| format!("{}", t)).join(", ");
98659
                let attrs = representation_attribute(representation);
98659
                write!(f, "record{attrs} {{{inners}}}",)
            }
            GroundDomain::Variant(entries, representation) => {
19894
                let inners = entries.iter().map(|t| format!("{}", t)).join(", ");
9889
                let attrs = representation_attribute(representation);
9889
                write!(f, "variant{attrs} {{{inners}}}",)
            }
            GroundDomain::Matrix(value_domain, index_domains, representation) => {
1143483
                write!(
1143483
                    f,
1143483
                    "matrix{} indexed by {} of {value_domain}",
1143483
                    representation_attribute(representation),
1143483
                    pretty_vec(&index_domains.iter().collect_vec())
                )
            }
            GroundDomain::Sequence(attrs, inner_dom) => {
118175
                write!(f, "sequence {attrs} of {inner_dom}")
            }
            GroundDomain::Set(attrs, inner_dom) => {
657356
                write!(f, "set")?;
657356
                let attrs = attrs.to_string();
657356
                if attrs.is_empty() {
222656
                    write!(f, " of {inner_dom}")
                } else {
434700
                    write!(f, " {attrs} of {inner_dom}")
                }
            }
            GroundDomain::MSet(attrs, inner_dom) => {
178633
                write!(f, "mset")?;
178633
                let attrs = attrs.to_string();
178633
                if attrs.is_empty() {
58
                    write!(f, " of {inner_dom}")
                } else {
178575
                    write!(f, " {attrs} of {inner_dom}")
                }
            }
            GroundDomain::Function(attribute, domain, codomain) => {
98692
                write!(f, "function {} {} --> {} ", attribute, domain, codomain)
            }
            GroundDomain::Relation(attrs, domains) => {
8310
                write!(f, "relation {} of ({})", attrs, domains.iter().join(" * "))
            }
            GroundDomain::Partition(attrs, inner) => {
1992
                write!(f, "partition {attrs} from {inner}")
            }
            GroundDomain::EnumeratedType(et, ranges) => {
48
                if !ranges.is_empty() && ranges.iter().all(Range::is_lower_or_upper_bounded) {
40
                    let ranges = ranges
40
                        .iter()
40
                        .cloned()
40
                        .map(|range| {
40
                            range.func_map(|index| {
40
                                et.variants
40
                                    .get(index as usize)
40
                                    .map(ToString::to_string)
                                    .unwrap_or_else(|| index.to_string())
40
                            })
40
                        })
40
                        .join(", ");
40
                    write!(f, "{}({ranges})", et.name)
                } else {
8
                    write!(f, "{}", et.name)
                }
            }
            GroundDomain::UnnamedType(ty) => {
44
                write!(f, "{}", ty.name)
            }
            GroundDomain::Permutation(attrs, inner) => {
22678
                write!(f, "permutation {attrs} of {inner}")
            }
        }
37301349
    }
}
#[cfg(test)]
mod tests {
    use super::*;
    use crate::ast::Name;
    use crate::{domain_int_ground, matrix_lit};
    #[test]
1
    fn integer_intersection_matches_all_small_sparse_sets() {
1
        for a_mask in 0..128 {
            let a = GroundDomain::Int(
128
                (-3..=3)
896
                    .filter(|i| a_mask & (1 << (i + 3)) != 0)
128
                    .map(Range::Single)
                    .collect(),
128
                None,
            );
128
            for b_mask in 0..128 {
                let b = GroundDomain::Int(
16384
                    (-3..=3)
114688
                        .filter(|i| b_mask & (1 << (i + 3)) != 0)
16384
                        .map(Range::Single)
                        .collect(),
16384
                    None,
                );
16384
                let expected: BTreeSet<_> = (-3..=3)
114688
                    .filter(|i| (a_mask & b_mask) & (1 << (i + 3)) != 0)
                    .collect();
16384
                let actual = a
16384
                    .intersect(&b)
16384
                    .unwrap()
16384
                    .values_i32()
16384
                    .unwrap()
16384
                    .into_iter()
16384
                    .collect::<BTreeSet<_>>();
16384
                assert_eq!(actual, expected);
            }
        }
1
    }
    #[test]
1
    fn integer_intersection_handles_large_overlapping_and_empty_ranges() {
1
        let full = GroundDomain::Int(vec![Range::Bounded(i32::MIN, i32::MAX)], None);
1
        let positive = GroundDomain::Int(vec![Range::Bounded(0, i32::MAX)], None);
1
        assert_eq!(full.intersect(&positive).unwrap(), positive);
1
        assert_eq!(full.intersect(&full).unwrap(), full);
1
        let empty = GroundDomain::Empty(ReturnType::Int);
1
        assert_eq!(full.intersect(&empty).unwrap(), empty);
1
        let unsorted = GroundDomain::Int(
1
            vec![
1
                Range::Bounded(5, 10),
1
                Range::Bounded(-8, 6),
1
                Range::Single(i32::MAX),
1
            ],
1
            None,
1
        );
1
        let overlap = GroundDomain::Int(
1
            vec![
1
                Range::Bounded(0, 12),
1
                Range::Bounded(3, 8),
1
                Range::Single(i32::MAX),
1
            ],
1
            None,
1
        );
1
        assert_eq!(
1
            unsorted.intersect(&overlap).unwrap(),
1
            GroundDomain::Int(vec![Range::Bounded(0, 10), Range::Single(i32::MAX)], None)
        );
1
        let negative = GroundDomain::Int(vec![Range::Bounded(i32::MIN, -1)], None);
1
        assert_eq!(negative.intersect(&positive).unwrap(), empty);
1
        let unbounded = GroundDomain::Int(vec![Range::Unbounded], None);
1
        assert!(matches!(
1
            full.intersect(&unbounded),
            Err(DomainOpError::Unbounded)
        ));
1
        let no_ranges = GroundDomain::Int(Vec::new(), None);
1
        assert!(matches!(
1
            no_ranges.intersect(&unbounded),
            Err(DomainOpError::Unbounded)
        ));
1
    }
    #[test]
1
    fn matrix_values_1d_bool_of_bool() {
        // matrix indexed by [bool] of bool
        // 2 cells, 2 possible values => 2^2 = 4 matrices
1
        let dom = GroundDomain::Matrix(
1
            Moo::new(GroundDomain::Bool),
1
            vec![Moo::new(GroundDomain::Bool)],
1
            None,
1
        );
1
        let values: Vec<Literal> = dom.values().unwrap().collect();
1
        assert_eq!(values.len(), 4);
1
        assert_eq!(
1
            values[0],
1
            matrix_lit![false, false; Moo::new(GroundDomain::Bool)]
        );
1
        assert_eq!(
1
            values[1],
1
            matrix_lit![false, true; Moo::new(GroundDomain::Bool)]
        );
1
        assert_eq!(
1
            values[2],
1
            matrix_lit![true, false; Moo::new(GroundDomain::Bool)]
        );
1
        assert_eq!(
1
            values[3],
1
            matrix_lit![true, true; Moo::new(GroundDomain::Bool)]
        );
1
    }
    #[test]
1
    fn matrix_values_1d_int() {
        // matrix indexed by [int(1..2)] of int(0..1)
        // 2 cells, 2 possible values => 4 matrices
1
        let dom = GroundDomain::Matrix(
1
            domain_int_ground!(0..1),
1
            vec![domain_int_ground!(1..2)],
1
            None,
1
        );
1
        let values: Vec<Literal> = dom.values().unwrap().collect();
1
        assert_eq!(values.len(), 4);
1
        assert_eq!(values[0], matrix_lit![0, 0; domain_int_ground!(1..2)]);
1
        assert_eq!(values[1], matrix_lit![0, 1; domain_int_ground!(1..2)]);
1
        assert_eq!(values[2], matrix_lit![1, 0; domain_int_ground!(1..2)]);
1
        assert_eq!(values[3], matrix_lit![1, 1; domain_int_ground!(1..2)]);
1
    }
    #[test]
1
    fn matrix_values_2d_lexicographic() {
        // matrix indexed by [int(1..2), int(1..2)] of int(0..1)
        // 4 cells, 2 possible values => 2^4 = 16 matrices
1
        let dom = GroundDomain::Matrix(
1
            domain_int_ground!(0..1),
1
            vec![domain_int_ground!(1..2), domain_int_ground!(1..2)],
1
            None,
1
        );
1
        let values: Vec<Literal> = dom.values().unwrap().collect();
1
        assert_eq!(values.len(), 16);
        // First: [[0,0],[0,0]]
1
        assert_eq!(
1
            values[0],
1
            matrix_lit![[0, 0], [0, 0]; [domain_int_ground!(1..2), domain_int_ground!(1..2)]]
        );
        // Second: [[0,0],[0,1]]
1
        assert_eq!(
1
            values[1],
1
            matrix_lit![[0, 0], [0, 1]; [domain_int_ground!(1..2), domain_int_ground!(1..2)]]
        );
        // Third: [[0,0],[1,0]]
1
        assert_eq!(
1
            values[2],
1
            matrix_lit![[0, 0], [1, 0]; [domain_int_ground!(1..2), domain_int_ground!(1..2)]]
        );
        // Fourth: [[0,0],[1,1]]
1
        assert_eq!(
1
            values[3],
1
            matrix_lit![[0, 0], [1, 1]; [domain_int_ground!(1..2), domain_int_ground!(1..2)]]
        );
        // Last: [[1,1],[1,1]]
1
        assert_eq!(
1
            values[15],
1
            matrix_lit![[1, 1], [1, 1]; [domain_int_ground!(1..2), domain_int_ground!(1..2)]]
        );
1
    }
    #[test]
1
    fn matrix_values_count_matches_length() {
        // matrix indexed by [int(1..3)] of int(0..1)
        // 3 cells, 2 possible values => 2^3 = 8 matrices
1
        let dom = GroundDomain::Matrix(
1
            domain_int_ground!(0..1),
1
            vec![domain_int_ground!(1..3)],
1
            None,
1
        );
1
        let count = dom.values().unwrap().count();
1
        let length = dom.length().unwrap();
1
        assert_eq!(count as u64, length);
1
    }
    #[test]
1
    fn tuple_values_two_bools() {
        // tuple of (bool, bool) => 2*2 = 4 values
1
        let dom = GroundDomain::Tuple(
1
            vec![Moo::new(GroundDomain::Bool), Moo::new(GroundDomain::Bool)],
1
            None,
1
        );
1
        let values: Vec<Literal> = dom.values().unwrap().collect();
1
        assert_eq!(values.len(), 4);
4
        let t = |a, b| {
4
            Literal::AbstractLiteral(AbstractLiteral::Tuple(vec![
4
                Literal::Bool(a),
4
                Literal::Bool(b),
4
            ]))
4
        };
1
        assert_eq!(values[0], t(false, false));
1
        assert_eq!(values[1], t(false, true));
1
        assert_eq!(values[2], t(true, false));
1
        assert_eq!(values[3], t(true, true));
1
    }
    #[test]
1
    fn tuple_values_mixed_domains() {
        // tuple of (bool, int(0..2)) => 2*3 = 6 values, lexicographic
1
        let dom = GroundDomain::Tuple(
1
            vec![Moo::new(GroundDomain::Bool), domain_int_ground!(0..2)],
1
            None,
1
        );
1
        let values: Vec<Literal> = dom.values().unwrap().collect();
1
        assert_eq!(values.len(), 6);
6
        let t = |b: bool, i: i32| {
6
            Literal::AbstractLiteral(AbstractLiteral::Tuple(vec![
6
                Literal::Bool(b),
6
                Literal::Int(i),
6
            ]))
6
        };
        // bool false first, then ints 0,1,2
1
        assert_eq!(values[0], t(false, 0));
1
        assert_eq!(values[1], t(false, 1));
1
        assert_eq!(values[2], t(false, 2));
        // then bool true
1
        assert_eq!(values[3], t(true, 0));
1
        assert_eq!(values[4], t(true, 1));
1
        assert_eq!(values[5], t(true, 2));
1
    }
    #[test]
1
    fn tuple_values_count_matches_length() {
1
        let dom = GroundDomain::Tuple(
1
            vec![
1
                domain_int_ground!(1..3),
1
                Moo::new(GroundDomain::Bool),
1
                domain_int_ground!(0..1),
1
            ],
1
            None,
1
        );
1
        let count = dom.values().unwrap().count();
1
        let length = dom.length().unwrap();
1
        assert_eq!(count as u64, length);
1
    }
    #[test]
1
    fn record_values_lexicographic_by_name() {
        // record {b: bool, a: int(0..1)}
        // Entries should be ordered by name: a first, then b
1
        let dom = GroundDomain::Record(
1
            vec![
1
                Field {
1
                    name: Name::user("b"),
1
                    value: Moo::new(GroundDomain::Bool),
1
                },
1
                Field {
1
                    name: Name::user("a"),
1
                    value: domain_int_ground!(0..1),
1
                },
1
            ],
1
            None,
1
        );
1
        let values: Vec<Literal> = dom.values().unwrap().collect();
        // 2 * 2 = 4 values
1
        assert_eq!(values.len(), 4);
        // Entries should be sorted by name: "a" before "b"
5
        let r = |a_val: i32, b_val: bool| {
5
            Literal::AbstractLiteral(AbstractLiteral::Record(vec![
5
                Field {
5
                    name: Name::user("a"),
5
                    value: Literal::Int(a_val),
5
                },
5
                Field {
5
                    name: Name::user("b"),
5
                    value: Literal::Bool(b_val),
5
                },
5
            ]))
5
        };
        // "a" (int) varies slowest, "b" (bool) varies fastest
1
        assert_eq!(values[0], r(0, false));
1
        assert_eq!(values[1], r(0, true));
1
        assert_eq!(values[2], r(1, false));
1
        assert_eq!(values[3], r(1, true));
1
        for value in &values {
4
            assert!(dom.contains(value).unwrap(), "{value}");
        }
1
        assert!(!dom.contains(&r(2, false)).unwrap());
1
    }
    #[test]
1
    fn record_values_count_matches_length() {
1
        let dom = GroundDomain::Record(
1
            vec![
1
                Field {
1
                    name: Name::user("x"),
1
                    value: domain_int_ground!(1..3),
1
                },
1
                Field {
1
                    name: Name::user("y"),
1
                    value: Moo::new(GroundDomain::Bool),
1
                },
1
            ],
1
            None,
1
        );
1
        let count = dom.values().unwrap().count();
1
        let length = dom.length().unwrap();
1
        assert_eq!(count as u64, length);
1
    }
    #[test]
1
    fn variant_values_follow_alternative_order_and_match_length() {
1
        let dom = GroundDomain::Variant(
1
            vec![
1
                Field {
1
                    name: Name::user("flag"),
1
                    value: Moo::new(GroundDomain::Bool),
1
                },
1
                Field {
1
                    name: Name::user("value"),
1
                    value: domain_int_ground!(2..3),
1
                },
1
            ],
1
            None,
1
        );
1
        let values = dom.values().unwrap().collect::<Vec<_>>();
1
        assert_eq!(values.len() as u64, dom.length().unwrap());
1
        assert_eq!(values.len(), 4);
1
        assert!(matches!(
1
            &values[0],
            Literal::AbstractLiteral(AbstractLiteral::Variant(field))
1
                if field.name == Name::user("flag") && field.value == Literal::Bool(false)
        ));
1
        assert!(matches!(
1
            &values[3],
            Literal::AbstractLiteral(AbstractLiteral::Variant(field))
1
                if field.name == Name::user("value") && field.value == Literal::Int(3)
        ));
1
        assert!(dom.contains(&values[2]).unwrap());
1
    }
    #[test]
1
    fn infers_variant_domain_from_all_observed_alternatives() {
3
        let variant = |name: &str, value: i32| {
3
            Literal::AbstractLiteral(AbstractLiteral::Variant(Moo::new(Field {
3
                name: Name::user(name),
3
                value: Literal::Int(value),
3
            })))
3
        };
        let domain =
1
            GroundDomain::from_literal_vec(&[variant("a", 10), variant("a", 13), variant("b", 7)])
1
                .unwrap();
1
        let GroundDomain::Variant(fields, _) = domain else {
            panic!("expected variant domain");
        };
1
        assert_eq!(fields.len(), 2);
1
        assert_eq!(fields[0].name, Name::user("a"));
1
        assert!(fields[0].value.contains(&Literal::Int(10)).unwrap());
1
        assert!(fields[0].value.contains(&Literal::Int(13)).unwrap());
1
        assert_eq!(fields[1].name, Name::user("b"));
1
        assert!(fields[1].value.contains(&Literal::Int(7)).unwrap());
1
    }
17
    fn set_lit(elems: Vec<i32>) -> Literal {
17
        Literal::AbstractLiteral(AbstractLiteral::Set(
17
            elems.into_iter().map(Literal::Int).collect(),
17
        ))
17
    }
    #[test]
1
    fn set_values_unbounded() {
        // set of int(1..3) => all 2^3 = 8 subsets, in order of ascending size
1
        let dom = GroundDomain::Set(SetAttr::default(), domain_int_ground!(1..3));
1
        let values: Vec<Literal> = dom.values().unwrap().collect();
1
        assert_eq!(values.len(), 8);
1
        assert_eq!(values[0], set_lit(vec![])); // size 0
1
        assert_eq!(values[1], set_lit(vec![1])); // size 1
1
        assert_eq!(values[2], set_lit(vec![2]));
1
        assert_eq!(values[3], set_lit(vec![3]));
1
        assert_eq!(values[4], set_lit(vec![1, 2])); // size 2
1
        assert_eq!(values[5], set_lit(vec![1, 3]));
1
        assert_eq!(values[6], set_lit(vec![2, 3]));
1
        assert_eq!(values[7], set_lit(vec![1, 2, 3])); // size 3
1
    }
    #[test]
1
    fn set_values_fixed_size() {
        // set (size 2) of int(1..3) => the 3 two-element subsets
1
        let dom = GroundDomain::Set(SetAttr::new_size(2), domain_int_ground!(1..3));
1
        let values: Vec<Literal> = dom.values().unwrap().collect();
1
        assert_eq!(values.len(), 3);
1
        assert_eq!(values[0], set_lit(vec![1, 2]));
1
        assert_eq!(values[1], set_lit(vec![1, 3]));
1
        assert_eq!(values[2], set_lit(vec![2, 3]));
1
    }
    #[test]
1
    fn set_values_bounded_size() {
        // set (minSize 1, maxSize 2) of int(1..3) => subsets of size 1 and 2
1
        let dom = GroundDomain::Set(SetAttr::new_min_max_size(1, 2), domain_int_ground!(1..3));
1
        let values: Vec<Literal> = dom.values().unwrap().collect();
1
        assert_eq!(values.len(), 6);
1
        assert_eq!(values[0], set_lit(vec![1]));
1
        assert_eq!(values[1], set_lit(vec![2]));
1
        assert_eq!(values[2], set_lit(vec![3]));
1
        assert_eq!(values[3], set_lit(vec![1, 2]));
1
        assert_eq!(values[4], set_lit(vec![1, 3]));
1
        assert_eq!(values[5], set_lit(vec![2, 3]));
1
    }
    #[test]
1
    fn set_length_clamps_max_size_to_inner_domain() {
        // maxSize 3 of int(1..2) is effectively maxSize 2: 2^2 = 4 subsets.
1
        let dom = GroundDomain::Set(SetAttr::new_max_size(3), domain_int_ground!(1..2));
1
        assert_eq!(dom.length().unwrap(), 4);
1
    }
    #[test]
1
    fn set_values_count_matches_length() {
1
        let dom = GroundDomain::Set(SetAttr::default(), domain_int_ground!(1..4));
1
        let count = dom.values().unwrap().count();
1
        let length = dom.length().unwrap();
1
        assert_eq!(count as u64, length);
1
    }
5
    fn func_attr(partiality: PartialityAttr, jectivity: JectivityAttr) -> FuncAttr {
5
        FuncAttr {
5
            representation: None,
5
            size: Range::Unbounded,
5
            partiality,
5
            jectivity,
5
        }
5
    }
    #[test]
1
    fn total_bijective_function_length_is_factorial_of_the_shared_size() {
1
        let dom = GroundDomain::Function(
1
            func_attr(PartialityAttr::Total, JectivityAttr::Bijective),
1
            domain_int_ground!(1..3),
1
            domain_int_ground!(1..3),
1
        );
1
        assert_eq!(dom.length().unwrap(), 6); // 3!
1
    }
    #[test]
1
    fn total_bijective_function_length_is_zero_for_mismatched_sizes() {
1
        let dom = GroundDomain::Function(
1
            func_attr(PartialityAttr::Total, JectivityAttr::Bijective),
1
            domain_int_ground!(1..3),
1
            domain_int_ground!(1..2),
1
        );
1
        assert_eq!(dom.length().unwrap(), 0);
1
    }
    #[test]
1
    fn total_injective_function_length_is_a_falling_factorial() {
        // 2 domain elements injectively into 4 codomain elements: 4*3 = 12.
1
        let dom = GroundDomain::Function(
1
            func_attr(PartialityAttr::Total, JectivityAttr::Injective),
1
            domain_int_ground!(1..2),
1
            domain_int_ground!(1..4),
1
        );
1
        assert_eq!(dom.length().unwrap(), 12);
1
    }
    #[test]
1
    fn total_surjective_function_length_uses_stirling_numbers() {
        // 3 domain elements onto 2 codomain elements: S(3,2)=3 partitions, * 2! = 6.
1
        let dom = GroundDomain::Function(
1
            func_attr(PartialityAttr::Total, JectivityAttr::Surjective),
1
            domain_int_ground!(1..3),
1
            domain_int_ground!(1..2),
1
        );
1
        assert_eq!(dom.length().unwrap(), 6);
1
    }
    #[test]
1
    fn partial_injective_function_length_sums_over_defined_sizes() {
        // 3 domain elements, 2 codomain elements, up to 2 defined: sum over sz=0..=2 of
        // C(3,sz) * P(2,sz) = 1*1 + 3*2 + 3*2 = 13.
1
        let mut attr = func_attr(PartialityAttr::Partial, JectivityAttr::Injective);
1
        attr.size = Range::Bounded(0, 2);
1
        let dom = GroundDomain::Function(attr, domain_int_ground!(1..3), domain_int_ground!(1..2));
1
        assert_eq!(dom.length().unwrap(), 13);
1
    }
7
    fn partition_attr(num_parts: Range<i32>, part_len: Range<i32>) -> PartitionAttr {
7
        PartitionAttr {
7
            representation: None,
7
            num_parts,
7
            part_len,
7
            is_regular: false,
7
        }
7
    }
3
    fn partition_lit(parts: Vec<Vec<i32>>) -> Literal {
        Literal::AbstractLiteral(AbstractLiteral::Partition(
3
            parts
3
                .into_iter()
5
                .map(|part| part.into_iter().map(Literal::Int).collect())
                .collect(),
        ))
3
    }
    #[test]
1
    fn partition_contains_accepts_a_literal_whose_size_exactly_matches_num_parts_times_part_len() {
        // num_parts=2, part_len=2 => exactly 4 covered elements is valid.
1
        let dom = GroundDomain::Partition(
1
            partition_attr(Range::Single(2), Range::Single(2)),
1
            domain_int_ground!(1..6),
1
        );
1
        let lit = partition_lit(vec![vec![1, 2], vec![3, 4]]);
1
        assert!(
1
            dom.contains(&lit).unwrap(),
            "a 4-element partition literal should be a valid member of a \
             (numParts 2, partSize 2) domain"
        );
1
    }
    #[test]
    fn partition_contains_rejects_a_literal_with_the_wrong_covered_size() {
        // num_parts=2, part_len=2 requires exactly 4 covered elements; 3 should be rejected.
1
        let dom = GroundDomain::Partition(
1
            partition_attr(Range::Single(2), Range::Single(2)),
1
            domain_int_ground!(1..6),
1
        );
1
        let lit = partition_lit(vec![vec![1, 2, 3]]);
1
        assert!(
1
            !dom.contains(&lit).unwrap(),
            "a 3-element partition literal should not be a valid member of a \
             (numParts 2, partSize 2) domain, which requires exactly 4 covered elements"
        );
1
    }
    #[test]
1
    fn partition_contains_accepts_any_size_when_attributes_are_unbounded() {
        // Regression: an unattributed partition domain (Range::Unbounded for both num_parts and
        // part_len) must not reject every literal outright -- Range::Unbounded.contains() is
        // always true, which an inverted condition would misread as "always out of range".
1
        let dom = GroundDomain::Partition(
1
            partition_attr(Range::Unbounded, Range::Unbounded),
1
            domain_int_ground!(1..6),
1
        );
1
        let lit = partition_lit(vec![vec![1, 2], vec![3, 4, 5, 6]]);
1
        assert!(
1
            dom.contains(&lit).unwrap(),
            "an unattributed partition domain should accept a literal covering its whole inner \
             domain"
        );
1
    }
    #[test]
1
    fn partition_length_unattributed_matches_the_bell_number() {
        // Bell(4) = 15: every way to partition a 4-element set, no size/count restriction.
1
        let dom = GroundDomain::Partition(
1
            partition_attr(Range::Unbounded, Range::Unbounded),
1
            domain_int_ground!(1..4),
1
        );
1
        assert_eq!(dom.length().unwrap(), 15);
1
    }
    #[test]
1
    fn partition_length_fixed_num_parts_matches_stirling_second_kind() {
        // S(4, 2) = 7: partitioning 4 elements into exactly 2 unlabelled non-empty blocks, no
        // block-size restriction -- this is restricted_partition_count's block_min=1 case, which
        // should agree with stirling_second_kind exactly.
1
        let dom = GroundDomain::Partition(
1
            partition_attr(Range::Single(2), Range::Unbounded),
1
            domain_int_ground!(1..4),
1
        );
1
        assert_eq!(dom.length().unwrap(), stirling_second_kind(4, 2).unwrap());
1
        assert_eq!(dom.length().unwrap(), 7);
1
    }
    #[test]
1
    fn partition_length_regular_fixed_block_size_matches_a_hand_computed_multinomial() {
        // 6 elements into regular blocks of size 3: 6! / (3!^2 * 2!) = 720 / 72 = 10.
1
        let mut attr = partition_attr(Range::Unbounded, Range::Single(3));
1
        attr.is_regular = true;
1
        let dom = GroundDomain::Partition(attr, domain_int_ground!(1..6));
1
        assert_eq!(dom.length().unwrap(), 10);
1
    }
    #[test]
1
    fn partition_values_count_matches_length_and_every_value_is_a_valid_member() {
1
        let dom = GroundDomain::Partition(
1
            partition_attr(Range::Bounded(2, 3), Range::Unbounded),
1
            domain_int_ground!(1..4),
1
        );
1
        let values: Vec<Literal> = dom.values().unwrap().collect();
1
        assert_eq!(values.len() as u64, dom.length().unwrap());
1
        for value in &values {
13
            assert!(dom.contains(value).unwrap());
        }
1
    }
3
    fn permutation_attr(num_moved: Range<i32>) -> PermutationAttr {
3
        PermutationAttr {
3
            representation: None,
3
            num_moved,
3
        }
3
    }
    #[test]
1
    fn permutation_length_unattributed_matches_factorial() {
        // 4! = 24: every bijection of a 4-element set onto itself, no numMoved restriction.
        let dom =
1
            GroundDomain::Permutation(permutation_attr(Range::Unbounded), domain_int_ground!(1..4));
1
        assert_eq!(dom.length().unwrap(), 24);
1
    }
    #[test]
1
    fn permutation_length_fully_moved_matches_the_derangement_number() {
        // D(4) = 9: permutations of 4 elements with no fixed points at all.
        let dom =
1
            GroundDomain::Permutation(permutation_attr(Range::Single(4)), domain_int_ground!(1..4));
1
        assert_eq!(dom.length().unwrap(), 9);
1
    }
    #[test]
1
    fn permutation_values_count_matches_length_and_every_value_is_a_valid_member() {
1
        let dom = GroundDomain::Permutation(
1
            permutation_attr(Range::Bounded(1, 2)),
1
            domain_int_ground!(1..3),
1
        );
1
        let values: Vec<Literal> = dom.values().unwrap().collect();
1
        assert_eq!(values.len() as u64, dom.length().unwrap());
1
        for value in &values {
3
            assert!(dom.contains(value).unwrap());
        }
1
    }
}
16
fn enum_indices<'a>(
16
    ty: &'a Moo<EnumeratedType>,
16
    ranges: &'a [Range<u32>],
16
) -> impl Iterator<Item = u32> + 'a {
16
    (0..ty.len() as u32)
32
        .filter(move |index| ranges.is_empty() || ranges.iter().any(|range| range.contains(index)))
16
}