Skip to main content

conjure_cp_core/ast/domains/
ground.rs

1use crate::ast::domains::attrs::{PartitionAttr, PermutationAttr};
2use crate::ast::domains::{JectivityAttr, MSetAttr, PartialityAttr, SequenceAttr};
3use crate::ast::pretty::pretty_vec;
4use crate::ast::{
5    AbstractLiteral, DomainOpError, FuncAttr, Literal, Moo, Name, RelAttr, SetAttr, Typeable,
6    domains::{domain::Int, range::Range},
7    matrix,
8    records::Field,
9};
10use crate::bug_assert;
11use crate::range;
12use crate::utils::{
13    count_combinations, count_permutations, derangements, restricted_partition_count,
14    stirling_second_kind,
15};
16use conjure_cp_core::ast::ReturnType;
17use funcmap::FuncMap;
18use itertools::{Itertools, izip};
19use num_traits::ToPrimitive;
20use polyquine::Quine;
21use serde::{Deserialize, Serialize};
22use std::collections::{BTreeMap, BTreeSet};
23use std::fmt::{Display, Formatter};
24use std::iter::zip;
25use uniplate::Uniplate;
26
27pub(super) type FieldGround = Field<Moo<GroundDomain>>;
28
29#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, Quine, Uniplate)]
30#[path_prefix(conjure_cp::ast)]
31/// Variants use the project-wide type/domain ordering; keep broad matches in the same order.
32pub enum GroundDomain {
33    /// An empty domain of a given type
34    Empty(ReturnType),
35    /// A boolean value (true / false)
36    Bool,
37    /// An integer value in the given ranges (e.g. int(1, 3..5))
38    Int(Vec<Range<Int>>),
39    /// A tuple of N elements, each with its own domain
40    Tuple(Vec<Moo<GroundDomain>>),
41    /// A record
42    Record(Vec<FieldGround>),
43    /// A variant domain with its domain options (reusing field entries)
44    Variant(Vec<FieldGround>),
45    /// An N-dimensional matrix of elements drawn from the inner domain,
46    /// and indices from the n index domains
47    Matrix(Moo<GroundDomain>, Vec<Moo<GroundDomain>>),
48    /// A sequence of elements drawn from the inner domain
49    Sequence(SequenceAttr, Moo<GroundDomain>),
50    /// A set of elements drawn from the inner domain
51    Set(SetAttr<Int>, Moo<GroundDomain>),
52    /// A multiset of elements drawn from the inner domain
53    MSet(MSetAttr<Int>, Moo<GroundDomain>),
54    /// A function with a domain and codomain
55    Function(FuncAttr, Moo<GroundDomain>, Moo<GroundDomain>),
56    /// A relation as a set of tuples
57    Relation(RelAttr, Vec<Moo<GroundDomain>>),
58    /// A partition
59    Partition(PartitionAttr, Moo<GroundDomain>),
60    /// A permutation
61    Permutation(PermutationAttr, Moo<GroundDomain>),
62}
63
64/// Counts partitions of `n` labelled elements into unlabelled blocks that are *all* exactly
65/// `block_size` (a `regular` partition), i.e. `n / (block_size! ^ k) / k!` for `k = n /
66/// block_size` -- computed by choosing the `k` same-size groups one at a time (dividing out the
67/// groups' own arbitrary ordering with one final `/ k!`) rather than via raw factorials, so
68/// intermediate values stay as small as the final answer allows.
69fn regular_partition_count(n: u64, block_size: u64) -> Result<u64, DomainOpError> {
70    if n == 0 {
71        return Ok(1);
72    }
73    if block_size == 0 || !n.is_multiple_of(block_size) {
74        return Ok(0);
75    }
76    let num_parts = n / block_size;
77    let mut numerator = 1u64;
78    let mut remaining = n;
79    for _ in 0..num_parts {
80        let choose = count_combinations(remaining, block_size)?;
81        numerator = numerator
82            .checked_mul(choose)
83            .ok_or(DomainOpError::TooLarge)?;
84        remaining -= block_size;
85    }
86    let num_parts_factorial = (1..=num_parts)
87        .try_fold(1u64, |acc, x| acc.checked_mul(x))
88        .ok_or(DomainOpError::TooLarge)?;
89    numerator
90        .checked_div(num_parts_factorial)
91        .ok_or(DomainOpError::TooLarge)
92}
93
94/// Every unordered partition of `elements` into blocks whose size falls in `[block_min,
95/// block_max]` (both inclusive; `block_min` is floored to `1`, since a block can't be empty).
96///
97/// Built by always rooting the block containing the *first* remaining element (`elements` is
98/// consumed in its given order), then recursing on whatever's left -- the same canonical
99/// construction [`restricted_partition_count`] counts without generating, so the two stay
100/// consistent by construction. Each returned partition is a `Vec` of blocks in the order they
101/// were rooted; each block is a `Vec` of elements in `[root, ..combination order]`.
102fn restricted_partitions(
103    elements: &[Literal],
104    block_min: usize,
105    block_max: usize,
106) -> Vec<Vec<Vec<Literal>>> {
107    let block_min = block_min.max(1);
108    if elements.is_empty() {
109        return vec![vec![]];
110    }
111    let (first, rest) = elements.split_first().expect("checked non-empty above");
112
113    let max_extra = block_max.saturating_sub(1).min(rest.len());
114    if block_min.saturating_sub(1) > max_extra {
115        return vec![];
116    }
117
118    let mut results = vec![];
119    for extra in block_min.saturating_sub(1)..=max_extra {
120        for combo in rest.iter().cloned().combinations(extra) {
121            let mut block = vec![first.clone()];
122            block.extend(combo.iter().cloned());
123
124            let remaining: Vec<Literal> = rest
125                .iter()
126                .filter(|elem| !combo.contains(elem))
127                .cloned()
128                .collect();
129
130            for sub_partition in restricted_partitions(&remaining, block_min, block_max) {
131                let mut whole = vec![block.clone()];
132                whole.extend(sub_partition);
133                results.push(whole);
134            }
135        }
136    }
137    results
138}
139
140/// Every permutation of `n` in `0..len` (representing positions in `elements`) whose number of
141/// non-fixed positions falls in `[moved_min, moved_max]`, returned as the concrete value each
142/// position maps to (i.e. `result[i]` is what `elements[i]` is sent to).
143fn restricted_permutations(
144    elements: &[Literal],
145    moved_min: usize,
146    moved_max: usize,
147) -> impl Iterator<Item = Vec<Literal>> + '_ {
148    let n = elements.len();
149    (0..n)
150        .permutations(n)
151        .filter(move |perm| {
152            let moved = perm.iter().enumerate().filter(|&(i, p)| i != *p).count();
153            moved >= moved_min && moved <= moved_max
154        })
155        .map(move |perm| perm.into_iter().map(|i| elements[i].clone()).collect())
156}
157
158/// Converts a full position-to-position permutation mapping (`elements[i]` maps to `mapped[i]`)
159/// into cycle notation, omitting fixed points -- mirrors
160/// `PermutationAsFunction`'s own `up()` (`crates/conjure-cp-rules/src/types/permutation/
161/// as_function/representation.rs`), duplicated here since domain-level enumeration lives in a
162/// lower crate that representation can't depend on.
163fn permutation_mapping_to_cycles(elements: &[Literal], mapped: &[Literal]) -> Vec<Vec<Literal>> {
164    let forward: std::collections::HashMap<&Literal, &Literal> =
165        elements.iter().zip(mapped.iter()).collect();
166    let mut visited: std::collections::HashSet<&Literal> = std::collections::HashSet::new();
167    let mut cycles = vec![];
168    for start in elements {
169        if visited.contains(start) {
170            continue;
171        }
172        let image = forward[start];
173        if image == start {
174            visited.insert(start);
175            continue;
176        }
177        let mut cycle = vec![start.clone()];
178        visited.insert(start);
179        let mut current = image;
180        while current != start {
181            visited.insert(current);
182            cycle.push(current.clone());
183            current = forward[current];
184        }
185        cycles.push(cycle);
186    }
187    cycles
188}
189
190/// The size attribute covering both sizes, for a union of two sequence domains.
191///
192/// Sizes have to survive the union: without a maximum, nothing downstream can tell how many
193/// positions a sequence has -- iterating one, for instance, needs to know the range of positions.
194fn union_sequence_sizes(left: &Range<i32>, right: &Range<i32>) -> Range<i32> {
195    let bounds = |range: &Range<i32>| match range {
196        Range::Single(size) => (*size, Some(*size)),
197        Range::Bounded(min, max) => (*min, Some(*max)),
198        Range::UnboundedL(max) => (0, Some(*max)),
199        Range::UnboundedR(min) => (*min, None),
200        Range::Unbounded => (0, None),
201    };
202
203    let (left_min, left_max) = bounds(left);
204    let (right_min, right_max) = bounds(right);
205    let min = left_min.min(right_min);
206
207    match (left_max, right_max) {
208        (Some(left_max), Some(right_max)) => {
209            let max = left_max.max(right_max);
210            if min == max {
211                Range::Single(min)
212            } else {
213                Range::Bounded(min, max)
214            }
215        }
216        _ => Range::UnboundedR(min),
217    }
218}
219
220impl GroundDomain {
221    pub fn union(&self, other: &GroundDomain) -> Result<GroundDomain, DomainOpError> {
222        // Keep implemented variants before `todo!` variants so mixed-domain unions report a type
223        // error instead of entering an unsupported implementation. Each group uses declaration
224        // order.
225        match (self, other) {
226            (GroundDomain::Empty(ty), dom) | (dom, GroundDomain::Empty(ty)) => {
227                if *ty == dom.return_type() {
228                    Ok(dom.clone())
229                } else {
230                    Err(DomainOpError::WrongType)
231                }
232            }
233            (GroundDomain::Bool, GroundDomain::Bool) => Ok(GroundDomain::Bool),
234            (GroundDomain::Bool, _) | (_, GroundDomain::Bool) => Err(DomainOpError::WrongType),
235            (GroundDomain::Int(r1), GroundDomain::Int(r2)) => {
236                let mut rngs = r1.clone();
237                rngs.extend(r2.clone());
238                Ok(GroundDomain::Int(Range::squeeze(&rngs)))
239            }
240            (GroundDomain::Int(_), _) | (_, GroundDomain::Int(_)) => Err(DomainOpError::WrongType),
241            (GroundDomain::Tuple(in1s), GroundDomain::Tuple(in2s)) if in1s.len() == in2s.len() => {
242                let mut inners = Vec::new();
243                for (in1, in2) in zip(in1s, in2s) {
244                    inners.push(Moo::new(in1.union(in2)?));
245                }
246                Ok(GroundDomain::Tuple(inners))
247            }
248            (GroundDomain::Tuple(_), _) | (_, GroundDomain::Tuple(_)) => {
249                Err(DomainOpError::WrongType)
250            }
251            (GroundDomain::Record(in1s), GroundDomain::Record(in2s))
252                if in1s.len() == in2s.len() =>
253            {
254                let lhs_fields: BTreeMap<&Name, &Moo<GroundDomain>> =
255                    in1s.iter().map(|x| (&x.name, &x.value)).collect();
256                let rhs_fields: BTreeMap<&Name, &Moo<GroundDomain>> =
257                    in2s.iter().map(|x| (&x.name, &x.value)).collect();
258                let mut new_fields = Vec::with_capacity(in1s.len());
259                for (n, d) in lhs_fields {
260                    let d2 = rhs_fields.get(&n).ok_or(DomainOpError::WrongType)?;
261                    let dom = d.union(d2)?;
262                    new_fields.push(Field {
263                        name: n.clone(),
264                        value: dom.into(),
265                    });
266                }
267                Ok(GroundDomain::Record(new_fields))
268            }
269            (GroundDomain::Record(_), _) | (_, GroundDomain::Record(_)) => {
270                Err(DomainOpError::WrongType)
271            }
272            (GroundDomain::Matrix(in1, idx1), GroundDomain::Matrix(in2, idx2)) if idx1 == idx2 => {
273                Ok(GroundDomain::Matrix(
274                    Moo::new(in1.union(in2)?),
275                    idx1.clone(),
276                ))
277            }
278            (GroundDomain::Matrix(_, _), _) | (_, GroundDomain::Matrix(_, _)) => {
279                Err(DomainOpError::WrongType)
280            }
281            (GroundDomain::Set(_, in1), GroundDomain::Set(_, in2)) => Ok(GroundDomain::Set(
282                SetAttr::default(),
283                Moo::new(in1.union(in2)?),
284            )),
285            (GroundDomain::Set(_, _), _) | (_, GroundDomain::Set(_, _)) => {
286                Err(DomainOpError::WrongType)
287            }
288            (GroundDomain::MSet(_, in1), GroundDomain::MSet(_, in2)) => Ok(GroundDomain::MSet(
289                MSetAttr::default(),
290                Moo::new(in1.union(in2)?),
291            )),
292            (GroundDomain::Sequence(attr1, in1), GroundDomain::Sequence(attr2, in2)) => {
293                Ok(GroundDomain::Sequence(
294                    SequenceAttr {
295                        size: union_sequence_sizes(&attr1.size, &attr2.size),
296                        ..SequenceAttr::default()
297                    },
298                    Moo::new(in1.union(in2)?),
299                ))
300            }
301            (GroundDomain::Sequence(_, _), _) | (_, GroundDomain::Sequence(_, _)) => {
302                Err(DomainOpError::WrongType)
303            }
304            (GroundDomain::Relation(_, in1s), GroundDomain::Relation(_, in2s)) => {
305                let mut inners = Vec::new();
306                for (in1, in2) in zip(in1s, in2s) {
307                    inners.push(Moo::new(in1.union(in2)?));
308                }
309                Ok(GroundDomain::Relation(RelAttr::default(), inners))
310            }
311            (GroundDomain::Relation(..), _) | (_, GroundDomain::Relation(..)) => {
312                Err(DomainOpError::WrongType)
313            }
314            #[allow(unreachable_patterns)]
315            (GroundDomain::Variant(_), _) | (_, GroundDomain::Variant(_)) => {
316                todo!("union variant domains")
317            }
318            #[allow(unreachable_patterns)]
319            (GroundDomain::Function(..), _) | (_, GroundDomain::Function(..)) => {
320                todo!("union function domains")
321            }
322            #[allow(unreachable_patterns)]
323            (GroundDomain::Partition(..), _) | (_, GroundDomain::Partition(..)) => {
324                todo!("union partition domains")
325            }
326            #[allow(unreachable_patterns)]
327            (GroundDomain::Permutation(..), _) | (_, GroundDomain::Permutation(..)) => {
328                todo!("union permutation domains")
329            }
330        }
331    }
332
333    /// Calculates the intersection of two domains.
334    ///
335    /// # Errors
336    ///
337    ///  - [`DomainOpError::Unbounded`] if either of the input domains are unbounded.
338    ///  - [`DomainOpError::WrongType`] if the input domains are different types, or are not integer or set domains.
339    pub fn intersect(&self, other: &GroundDomain) -> Result<GroundDomain, DomainOpError> {
340        // TODO: does not consider unbounded domains yet
341        // needs to be tested once comprehension rules are written
342
343        match (self, other) {
344            // one or more arguments is an empty int domain
345            (d @ GroundDomain::Empty(ReturnType::Int), GroundDomain::Int(_)) => Ok(d.clone()),
346            (GroundDomain::Int(_), d @ GroundDomain::Empty(ReturnType::Int)) => Ok(d.clone()),
347            (GroundDomain::Empty(ReturnType::Int), d @ GroundDomain::Empty(ReturnType::Int)) => {
348                Ok(d.clone())
349            }
350
351            // one or more arguments is an empty set(int) domain
352            (GroundDomain::Set(_, inner1), d @ GroundDomain::Empty(ReturnType::Set(inner2)))
353                if matches!(
354                    **inner1,
355                    GroundDomain::Int(_) | GroundDomain::Empty(ReturnType::Int)
356                ) && matches!(**inner2, ReturnType::Int) =>
357            {
358                Ok(d.clone())
359            }
360            (d @ GroundDomain::Empty(ReturnType::Set(inner1)), GroundDomain::Set(_, inner2))
361                if matches!(**inner1, ReturnType::Int)
362                    && matches!(
363                        **inner2,
364                        GroundDomain::Int(_) | GroundDomain::Empty(ReturnType::Int)
365                    ) =>
366            {
367                Ok(d.clone())
368            }
369            (
370                d @ GroundDomain::Empty(ReturnType::Set(inner1)),
371                GroundDomain::Empty(ReturnType::Set(inner2)),
372            ) if matches!(**inner1, ReturnType::Int) && matches!(**inner2, ReturnType::Int) => {
373                Ok(d.clone())
374            }
375
376            // both arguments are non-empy
377            (GroundDomain::Set(_, x), GroundDomain::Set(_, y)) => Ok(GroundDomain::Set(
378                SetAttr::default(),
379                Moo::new((*x).intersect(y)?),
380            )),
381
382            (GroundDomain::Int(_), GroundDomain::Int(_)) => {
383                let mut v: BTreeSet<i32> = BTreeSet::new();
384
385                let v1 = self.values_i32()?;
386                let v2 = other.values_i32()?;
387                for value1 in v1.iter() {
388                    if v2.contains(value1) && !v.contains(value1) {
389                        v.insert(*value1);
390                    }
391                }
392                Ok(GroundDomain::from_set_i32(&v))
393            }
394            (GroundDomain::Relation(_, _), GroundDomain::Relation(_, _)) => {
395                todo!("Relation union not yet supported")
396            }
397            _ => Err(DomainOpError::WrongType),
398        }
399    }
400
401    pub fn values(&self) -> Result<Box<dyn Iterator<Item = Literal>>, DomainOpError> {
402        match self {
403            GroundDomain::Empty(_) => Ok(Box::new(vec![].into_iter())),
404            GroundDomain::Bool => Ok(Box::new(
405                vec![Literal::from(false), Literal::from(true)].into_iter(),
406            )),
407            GroundDomain::Int(rngs) => {
408                let rng_iters = rngs
409                    .iter()
410                    .map(Range::iter)
411                    .collect::<Option<Vec<_>>>()
412                    .ok_or(DomainOpError::Unbounded)?;
413                Ok(Box::new(
414                    rng_iters.into_iter().flat_map(|ri| ri.map(Literal::from)),
415                ))
416            }
417            GroundDomain::Tuple(elem_doms) => {
418                // Collect the possible values for each element
419                let elem_value_pools: Vec<Vec<Literal>> = elem_doms
420                    .iter()
421                    .map(|d| d.values().map(|it| it.collect()))
422                    .collect::<Result<_, _>>()?;
423
424                // Generate all combinations in lexicographic order
425                let iter = elem_value_pools
426                    .into_iter()
427                    .multi_cartesian_product()
428                    .map(|elems| Literal::AbstractLiteral(AbstractLiteral::Tuple(elems)));
429
430                Ok(Box::new(iter))
431            }
432            GroundDomain::Record(entries) => {
433                // Sort entries by name
434                let mut sorted: Vec<&_> = entries.iter().collect();
435                sorted.sort_by(|a, b| a.name.cmp(&b.name));
436
437                let names: Vec<_> = sorted.iter().map(|e| e.name.clone()).collect();
438                let value_pools: Vec<Vec<Literal>> = sorted
439                    .iter()
440                    .map(|e| e.value.values().map(|it| it.collect()))
441                    .collect::<Result<_, _>>()?;
442
443                // Generate all combinations in lexicographic order
444                let iter = value_pools
445                    .into_iter()
446                    .multi_cartesian_product()
447                    .map(move |vals| {
448                        let record_entries = names
449                            .iter()
450                            .cloned()
451                            .zip(vals)
452                            .map(|(name, value)| Field { name, value })
453                            .collect();
454                        Literal::AbstractLiteral(AbstractLiteral::Record(record_entries))
455                    });
456
457                Ok(Box::new(iter))
458            }
459            GroundDomain::Variant(entries) => {
460                let values = entries
461                    .iter()
462                    .map(|entry| {
463                        let name = entry.name.clone();
464                        entry.value.values().map(|values| {
465                            values.map(move |value| {
466                                Literal::AbstractLiteral(AbstractLiteral::Variant(Moo::new(
467                                    Field {
468                                        name: name.clone(),
469                                        value,
470                                    },
471                                )))
472                            })
473                        })
474                    })
475                    .collect::<Result<Vec<_>, _>>()?;
476                Ok(Box::new(values.into_iter().flatten()))
477            }
478            GroundDomain::Matrix(elem_dom, idx_doms) => {
479                let shape = matrix::shape_of_dom(self)?;
480                let idx_doms = idx_doms.clone();
481
482                // Collect all possible element values
483                let elem_values: Vec<Literal> = elem_dom.values()?.collect();
484
485                // Generate all possible cell assignments in lexicographic order
486                let iter = std::iter::repeat_n(elem_values, shape.size)
487                    .multi_cartesian_product()
488                    .map(move |flat_elems| {
489                        matrix::unflatten_matrix::<Literal>(&flat_elems, &idx_doms, &shape.strides)
490                    });
491
492                Ok(Box::new(iter))
493            }
494            GroundDomain::Sequence(attrs, inner_dom) => {
495                if attrs.jectivity != JectivityAttr::None {
496                    // Injective/surjective/bijective sequence enumeration is not yet needed by any
497                    // in-scope case (the two exhaustive tests using it are deferred pending enum
498                    // support), so it is left unimplemented rather than guessed at untested.
499                    todo!("Enumerating jective sequence domains is not yet supported")
500                }
501
502                let min_sz = attrs.size.low().copied().unwrap_or(0).max(0);
503                let max_sz = attrs.size.high().copied().ok_or(DomainOpError::Unbounded)?;
504
505                let pool = inner_dom.values()?.collect_vec();
506
507                let iter = (min_sz..=max_sz)
508                    .flat_map(move |sz| {
509                        std::iter::repeat_n(pool.clone(), sz.max(0) as usize)
510                            .multi_cartesian_product()
511                    })
512                    .map(|elems| Literal::AbstractLiteral(AbstractLiteral::Sequence(elems)));
513
514                Ok(Box::new(iter))
515            }
516            GroundDomain::Set(attrs, inner_dom) => {
517                let n: Int = inner_dom.len_usize()?.try_into()?;
518                let min_sz = attrs.size.low().copied().unwrap_or(0);
519                let max_sz = attrs.size.high().copied().unwrap_or(n);
520
521                let pool = inner_dom.values()?.collect_vec();
522
523                Ok(Box::new(
524                    (min_sz..=max_sz)
525                        .flat_map(move |sz| pool.clone().into_iter().combinations(sz as usize))
526                        .map(|elems| Literal::AbstractLiteral(AbstractLiteral::Set(elems))),
527                ))
528            }
529            GroundDomain::MSet(..) => todo!("Enumerating multi-set domains is not yet supported"),
530            GroundDomain::Function(..) => {
531                todo!("Enumerating function domains is not yet supported")
532            }
533            GroundDomain::Relation(..) => {
534                todo!("Enumerating relation domains is not yet supported")
535            }
536            GroundDomain::Partition(attr, inner_dom) => {
537                let elements: Vec<Literal> = inner_dom.values()?.collect();
538                let n = elements.len();
539
540                let block_lo = attr.part_len.low().copied().unwrap_or(1).max(1) as usize;
541                let block_hi = attr
542                    .part_len
543                    .high()
544                    .copied()
545                    .map(|h| (h.max(0) as usize).min(n))
546                    .unwrap_or(n);
547                let parts_lo = attr.num_parts.low().copied().unwrap_or(0).max(0) as usize;
548                let parts_hi = attr
549                    .num_parts
550                    .high()
551                    .copied()
552                    .map(|h| (h.max(0) as usize).min(n))
553                    .unwrap_or(n);
554                let is_regular = attr.is_regular;
555
556                let partitions = if block_lo > block_hi {
557                    vec![]
558                } else {
559                    restricted_partitions(&elements, block_lo, block_hi)
560                };
561                let iter = partitions.into_iter().filter(move |parts| {
562                    let k = parts.len();
563                    if k < parts_lo || k > parts_hi {
564                        return false;
565                    }
566                    !is_regular
567                        || parts
568                            .first()
569                            .is_none_or(|first| parts.iter().all(|p| p.len() == first.len()))
570                });
571                Ok(Box::new(iter.map(|parts| {
572                    Literal::AbstractLiteral(AbstractLiteral::Partition(parts))
573                })))
574            }
575            GroundDomain::Permutation(attr, inner_dom) => {
576                let elements: Vec<Literal> = inner_dom.values()?.collect();
577                let n = elements.len();
578
579                let moved_lo = attr.num_moved.low().copied().unwrap_or(0).max(0) as usize;
580                let moved_hi = attr
581                    .num_moved
582                    .high()
583                    .copied()
584                    .map(|h| (h.max(0) as usize).min(n))
585                    .unwrap_or(n);
586
587                if moved_lo > moved_hi {
588                    return Ok(Box::new(std::iter::empty()));
589                }
590                let mappings: Vec<Vec<Literal>> =
591                    restricted_permutations(&elements, moved_lo, moved_hi).collect();
592                let values = mappings.into_iter().map(move |mapped| {
593                    let cycles = permutation_mapping_to_cycles(&elements, &mapped);
594                    Literal::AbstractLiteral(AbstractLiteral::Permutation(cycles))
595                });
596                Ok(Box::new(values.collect_vec().into_iter()))
597            }
598        }
599    }
600
601    /// Gets the length of this domain.
602    ///
603    /// # Errors
604    ///
605    /// - [`DomainOpError::Unbounded`] if the input domain is of infinite size.
606    pub fn length(&self) -> Result<u64, DomainOpError> {
607        match self {
608            GroundDomain::Empty(_) => Ok(0),
609            GroundDomain::Bool => Ok(2),
610            GroundDomain::Int(ranges) => {
611                if ranges.is_empty() {
612                    return Ok(0);
613                }
614
615                let mut length = 0u64;
616                for range in ranges {
617                    if let Some(range_length) = range.length() {
618                        length += range_length as u64;
619                    } else {
620                        return Err(DomainOpError::Unbounded);
621                    }
622                }
623                Ok(length)
624            }
625            GroundDomain::Tuple(domains) => {
626                let mut ans = 1u64;
627                for domain in domains {
628                    ans = ans
629                        .checked_mul(domain.length()?)
630                        .ok_or(DomainOpError::TooLarge)?;
631                }
632                Ok(ans)
633            }
634            GroundDomain::Record(entries) => {
635                // A record is just a named tuple
636                let mut ans = 1u64;
637                for entry in entries {
638                    let sz = entry.value.length()?;
639                    ans = ans.checked_mul(sz).ok_or(DomainOpError::TooLarge)?;
640                }
641                Ok(ans)
642            }
643            GroundDomain::Variant(entries) => {
644                let mut ans = 0u64;
645                for entry in entries {
646                    let sz = entry.value.length()?;
647                    // Only one field can be in the variant at once
648                    ans = ans.checked_add(sz).ok_or(DomainOpError::TooLarge)?;
649                }
650                Ok(ans)
651            }
652            GroundDomain::Matrix(inner_domain, idx_domains) => {
653                let inner_sz = inner_domain.length()?;
654                let exp = idx_domains.iter().try_fold(1u32, |acc, val| {
655                    let len = val.length()? as u32;
656                    acc.checked_mul(len).ok_or(DomainOpError::TooLarge)
657                })?;
658                inner_sz.checked_pow(exp).ok_or(DomainOpError::TooLarge)
659            }
660            GroundDomain::Sequence(seq_attr, inner_domain) => {
661                if seq_attr.jectivity != JectivityAttr::None {
662                    // See the matching note on `values()` above: not yet needed by any in-scope case.
663                    todo!("Length bound of jective sequences is not yet supported");
664                }
665
666                let inner_len = inner_domain.length()?;
667                let min_sz = seq_attr.size.low().copied().unwrap_or(0).max(0) as u64;
668                let max_sz = seq_attr
669                    .size
670                    .high()
671                    .copied()
672                    .ok_or(DomainOpError::Unbounded)? as u64;
673
674                if min_sz > max_sz {
675                    return Ok(0);
676                }
677
678                let mut ans = 0u64;
679                for sz in min_sz..=max_sz {
680                    let sz: u32 = sz.try_into().map_err(|_| DomainOpError::TooLarge)?;
681                    let c = inner_len.checked_pow(sz).ok_or(DomainOpError::TooLarge)?;
682                    ans = ans.checked_add(c).ok_or(DomainOpError::TooLarge)?;
683                }
684                Ok(ans)
685            }
686            GroundDomain::Set(set_attr, inner_domain) => {
687                let inner_len = inner_domain.length()?;
688                let (min_sz, max_sz) = match set_attr.size {
689                    Range::Unbounded => (0, inner_len),
690                    Range::Single(n) => (n as u64, n as u64),
691                    Range::UnboundedR(n) => (n as u64, inner_len),
692                    Range::UnboundedL(n) => (0, n as u64),
693                    Range::Bounded(min, max) => (min as u64, max as u64),
694                };
695                // Attributes may overshoot the inner domain (e.g. maxSize 3 of int(1..2));
696                // only cardinalities that fit can contribute members.
697                let max_sz = max_sz.min(inner_len);
698                if min_sz > max_sz {
699                    return Ok(0);
700                }
701                let mut ans = 0u64;
702                for sz in min_sz..=max_sz {
703                    let c = count_combinations(inner_len, sz)?;
704                    ans = ans.checked_add(c).ok_or(DomainOpError::TooLarge)?;
705                }
706                Ok(ans)
707            }
708            GroundDomain::MSet(mset_attr, inner_domain) => {
709                let inner_len = inner_domain.length()?;
710                let (min_sz, max_sz) = match mset_attr.size {
711                    Range::Unbounded => (0, inner_len),
712                    Range::Single(n) => (n as u64, n as u64),
713                    Range::UnboundedR(n) => (n as u64, inner_len),
714                    Range::UnboundedL(n) => (0, n as u64),
715                    Range::Bounded(min, max) => (min as u64, max as u64),
716                };
717                let mut ans = 0u64;
718                for sz in min_sz..=max_sz {
719                    // need  "multichoose", ((n  k)) == (n+k-1  k)
720                    // Where n=inner_len and k=sz
721                    let c = count_combinations(inner_len + sz - 1, sz)?;
722                    ans = ans.checked_add(c).ok_or(DomainOpError::TooLarge)?;
723                }
724                Ok(ans)
725            }
726            GroundDomain::Function(attr, domain, codomain) => {
727                let domain_len = domain.length()?;
728                let codomain_len = codomain.length()?;
729
730                match (attr.partiality.clone(), attr.jectivity.clone()) {
731                    (PartialityAttr::Total, JectivityAttr::None) => {
732                        let exp: u32 = domain_len.try_into()?;
733                        codomain_len.checked_pow(exp).ok_or(DomainOpError::TooLarge)
734                    }
735                    (PartialityAttr::Total, JectivityAttr::Injective) => {
736                        if domain_len > codomain_len {
737                            return Ok(0);
738                        }
739                        Ok(count_permutations(codomain_len, domain_len)?)
740                    }
741                    (PartialityAttr::Total, JectivityAttr::Bijective) => {
742                        if domain_len != codomain_len {
743                            return Ok(0);
744                        }
745                        Ok(count_permutations(domain_len, domain_len)?)
746                    }
747                    (PartialityAttr::Total, JectivityAttr::Surjective) => {
748                        let partitions = stirling_second_kind(domain_len, codomain_len)?;
749                        let arrangements = count_permutations(codomain_len, codomain_len)?;
750                        partitions
751                            .checked_mul(arrangements)
752                            .ok_or(DomainOpError::TooLarge)
753                    }
754                    (PartialityAttr::Partial, jectivity) => {
755                        let (min_sz, max_sz) = match attr.size {
756                            Range::Unbounded => (0, domain_len),
757                            Range::Single(n) => (n as u64, n as u64),
758                            Range::UnboundedR(n) => (n as u64, domain_len),
759                            Range::UnboundedL(n) => (0, n as u64),
760                            Range::Bounded(min, max) => (min as u64, max as u64),
761                        };
762                        let max_sz = max_sz.min(domain_len);
763                        if min_sz > max_sz {
764                            return Ok(0);
765                        }
766
767                        let mut ans = 0u64;
768                        for sz in min_sz..=max_sz {
769                            // Choose which `sz` domain elements are defined, then count the ways
770                            // to map exactly those `sz` elements per the jectivity requirement.
771                            let choose = count_combinations(domain_len, sz)?;
772                            let assign = match jectivity {
773                                JectivityAttr::None => codomain_len
774                                    .checked_pow(sz.try_into()?)
775                                    .ok_or(DomainOpError::TooLarge)?,
776                                JectivityAttr::Injective => {
777                                    if sz > codomain_len {
778                                        0
779                                    } else {
780                                        count_permutations(codomain_len, sz)?
781                                    }
782                                }
783                                JectivityAttr::Bijective => {
784                                    if sz != codomain_len {
785                                        0
786                                    } else {
787                                        count_permutations(codomain_len, codomain_len)?
788                                    }
789                                }
790                                JectivityAttr::Surjective => {
791                                    let partitions = stirling_second_kind(sz, codomain_len)?;
792                                    let arrangements =
793                                        count_permutations(codomain_len, codomain_len)?;
794                                    partitions
795                                        .checked_mul(arrangements)
796                                        .ok_or(DomainOpError::TooLarge)?
797                                }
798                            };
799                            let term = choose.checked_mul(assign).ok_or(DomainOpError::TooLarge)?;
800                            ans = ans.checked_add(term).ok_or(DomainOpError::TooLarge)?;
801                        }
802                        Ok(ans)
803                    }
804                }
805            }
806            GroundDomain::Relation(attr, domains) => {
807                let dom_sizes_result: Result<Vec<u64>, DomainOpError> =
808                    domains.iter().map(|x| x.length()).collect();
809                let dom_sizes = dom_sizes_result?;
810                let inner_len: u64 = dom_sizes.iter().product();
811
812                let (min_sz, max_sz) = match attr.size {
813                    Range::Unbounded => (0, inner_len),
814                    Range::Single(n) => (n as u64, n as u64),
815                    Range::UnboundedR(n) => (n as u64, inner_len),
816                    Range::UnboundedL(n) => (0, n as u64),
817                    Range::Bounded(min, max) => (min as u64, max as u64),
818                };
819                let max_sz = max_sz.min(inner_len);
820                if min_sz > max_sz {
821                    return Ok(0);
822                }
823
824                let mut ans = 0u64;
825                for sz in min_sz..=max_sz {
826                    let c = count_combinations(inner_len, sz)?;
827                    ans = ans.checked_add(c).ok_or(DomainOpError::TooLarge)?;
828                }
829                Ok(ans)
830            }
831            GroundDomain::Partition(attr, inner_domain) => {
832                let n = inner_domain.length()?;
833                let block_lo = attr.part_len.low().copied().unwrap_or(1).max(1) as u64;
834                let block_hi = attr
835                    .part_len
836                    .high()
837                    .copied()
838                    .map(|h| (h.max(0) as u64).min(n))
839                    .unwrap_or(n);
840                let parts_lo = attr.num_parts.low().copied().unwrap_or(0).max(0) as u64;
841                let parts_hi = attr
842                    .num_parts
843                    .high()
844                    .copied()
845                    .map(|h| (h.max(0) as u64).min(n))
846                    .unwrap_or(n);
847                if block_lo > block_hi || parts_lo > parts_hi {
848                    return Ok(0);
849                }
850
851                if attr.is_regular {
852                    let mut ans = 0u64;
853                    for block_size in block_lo..=block_hi {
854                        if n % block_size != 0 {
855                            continue;
856                        }
857                        let num_parts = n / block_size;
858                        if num_parts < parts_lo || num_parts > parts_hi {
859                            continue;
860                        }
861                        let c = regular_partition_count(n, block_size)?;
862                        ans = ans.checked_add(c).ok_or(DomainOpError::TooLarge)?;
863                    }
864                    Ok(ans)
865                } else {
866                    let mut ans = 0u64;
867                    for num_parts in parts_lo..=parts_hi {
868                        let c = restricted_partition_count(n, num_parts, block_lo, block_hi)?;
869                        ans = ans.checked_add(c).ok_or(DomainOpError::TooLarge)?;
870                    }
871                    Ok(ans)
872                }
873            }
874            GroundDomain::Permutation(attr, inner_domain) => {
875                let n = inner_domain.length()?;
876                let moved_lo = attr.num_moved.low().copied().unwrap_or(0).max(0) as u64;
877                let moved_hi = attr
878                    .num_moved
879                    .high()
880                    .copied()
881                    .map(|h| (h.max(0) as u64).min(n))
882                    .unwrap_or(n);
883                if moved_lo > moved_hi {
884                    return Ok(0);
885                }
886
887                let mut ans = 0u64;
888                for moved in moved_lo..=moved_hi {
889                    let choose = count_combinations(n, moved)?;
890                    let derange = derangements(moved)?;
891                    let term = choose.checked_mul(derange).ok_or(DomainOpError::TooLarge)?;
892                    ans = ans.checked_add(term).ok_or(DomainOpError::TooLarge)?;
893                }
894                Ok(ans)
895            }
896        }
897    }
898
899    /// Get size of this domain as a [usize]
900    pub fn len_usize(&self) -> Result<usize, DomainOpError> {
901        self.length()?
902            .try_into()
903            .map_err(|_| DomainOpError::TooLarge)
904    }
905
906    pub fn contains(&self, lit: &Literal) -> Result<bool, DomainOpError> {
907        // not adding a generic wildcard condition for all domains, so that this gives a compile
908        // error when a domain is added.
909        match self {
910            // empty domain can't contain anything
911            GroundDomain::Empty(_) => Ok(false),
912            GroundDomain::Bool => match lit {
913                Literal::Bool(_) => Ok(true),
914                _ => Ok(false),
915            },
916            GroundDomain::Int(ranges) => match lit {
917                Literal::Int(x) => {
918                    if ranges.is_empty() {
919                        return Ok(false);
920                    };
921
922                    Ok(ranges.iter().any(|range| range.contains(x)))
923                }
924                _ => Ok(false),
925            },
926            GroundDomain::Tuple(elem_domains) => {
927                match lit {
928                    Literal::AbstractLiteral(AbstractLiteral::Tuple(literal_elems)) => {
929                        if elem_domains.len() != literal_elems.len() {
930                            return Ok(false);
931                        }
932
933                        // for every element in the tuple literal, check if it is in the corresponding domain
934                        for (elem_domain, elem) in itertools::izip!(elem_domains, literal_elems) {
935                            if !elem_domain.contains(elem)? {
936                                return Ok(false);
937                            }
938                        }
939
940                        Ok(true)
941                    }
942                    _ => Ok(false),
943                }
944            }
945            GroundDomain::Record(entries) => match lit {
946                Literal::AbstractLiteral(AbstractLiteral::Record(lit_entries)) => {
947                    if entries.len() != lit_entries.len() {
948                        return Ok(false);
949                    }
950
951                    for (entry, lit_entry) in itertools::izip!(entries, lit_entries) {
952                        if entry.name != lit_entry.name
953                            || !(entry.value.contains(&lit_entry.value)?)
954                        {
955                            return Ok(false);
956                        }
957                    }
958                    Ok(true)
959                }
960                _ => Ok(false),
961            },
962            GroundDomain::Variant(entries) => match lit {
963                Literal::AbstractLiteral(AbstractLiteral::Variant(lit_entry)) => {
964                    let Some(entry) = entries.iter().find(|entry| entry.name == lit_entry.name)
965                    else {
966                        return Ok(false);
967                    };
968                    entry.value.contains(&lit_entry.value)
969                }
970                _ => Ok(false),
971            },
972            GroundDomain::Matrix(elem_domain, index_domains) => {
973                match lit {
974                    Literal::AbstractLiteral(AbstractLiteral::Matrix(elems, idx_domain)) => {
975                        // Matrix literals are represented as nested 1d matrices, so the elements of
976                        // the matrix literal will be the inner dimensions of the matrix.
977
978                        if elems.is_empty()
979                            && index_domains
980                                .iter()
981                                .any(|index_domain| index_domain.length() == Ok(0))
982                        {
983                            return Ok(true);
984                        }
985
986                        let Some((current_index_domain, remaining_index_domains)) =
987                            index_domains.split_first()
988                        else {
989                            panic!("a matrix should have at least one index domain");
990                        };
991
992                        if *current_index_domain != *idx_domain {
993                            return Ok(false);
994                        };
995
996                        let next_elem_domain = if remaining_index_domains.is_empty() {
997                            // Base case - we have a 1D row. Now check if all elements in the
998                            // literal are in this row's element domain.
999                            elem_domain.as_ref().clone()
1000                        } else {
1001                            // Otherwise, go down a dimension (e.g. 2D matrix inside a 3D tensor)
1002                            GroundDomain::Matrix(
1003                                elem_domain.clone(),
1004                                remaining_index_domains.to_vec(),
1005                            )
1006                        };
1007
1008                        for elem in elems {
1009                            if !next_elem_domain.contains(elem)? {
1010                                return Ok(false);
1011                            }
1012                        }
1013
1014                        Ok(true)
1015                    }
1016                    _ => Ok(false),
1017                }
1018            }
1019            GroundDomain::Sequence(seq_attr, inner_dom) => match lit {
1020                Literal::AbstractLiteral(AbstractLiteral::Sequence(elems)) => {
1021                    let sz = elems.len().to_i32().ok_or(DomainOpError::TooLarge)?;
1022                    if !seq_attr.size.contains(&sz) {
1023                        return Ok(false);
1024                    }
1025
1026                    for elem in elems {
1027                        if !inner_dom.contains(elem)? {
1028                            return Ok(false);
1029                        }
1030                    }
1031                    Ok(true)
1032                }
1033                _ => Ok(false),
1034            },
1035            GroundDomain::Set(set_attr, inner_domain) => match lit {
1036                Literal::AbstractLiteral(AbstractLiteral::Set(lit_elems)) => {
1037                    // check if the literal's size is allowed by the set attribute
1038                    let sz = lit_elems.len().to_i32().ok_or(DomainOpError::TooLarge)?;
1039                    if !set_attr.size.contains(&sz) {
1040                        return Ok(false);
1041                    }
1042
1043                    for elem in lit_elems {
1044                        if !inner_domain.contains(elem)? {
1045                            return Ok(false);
1046                        }
1047                    }
1048                    Ok(true)
1049                }
1050                _ => Ok(false),
1051            },
1052            GroundDomain::MSet(mset_attr, inner_domain) => match lit {
1053                Literal::AbstractLiteral(AbstractLiteral::MSet(lit_elems)) => {
1054                    // check if the literal's size is allowed by the mset attribute
1055                    let sz = lit_elems.len().to_i32().ok_or(DomainOpError::TooLarge)?;
1056                    if !mset_attr.size.contains(&sz) {
1057                        return Ok(false);
1058                    }
1059
1060                    for elem in lit_elems {
1061                        if !inner_domain.contains(elem)? {
1062                            return Ok(false);
1063                        }
1064                    }
1065                    Ok(true)
1066                }
1067                _ => Ok(false),
1068            },
1069            GroundDomain::Function(func_attr, domain, codomain) => match lit {
1070                Literal::AbstractLiteral(AbstractLiteral::Function(lit_elems)) => {
1071                    let sz = Int::try_from(lit_elems.len()).expect("Should convert");
1072                    if !func_attr.size.contains(&sz) {
1073                        return Ok(false);
1074                    }
1075                    for lit in lit_elems {
1076                        let domain_element = &lit.0;
1077                        let codomain_element = &lit.1;
1078                        if !domain.contains(domain_element)? {
1079                            return Ok(false);
1080                        }
1081                        if !codomain.contains(codomain_element)? {
1082                            return Ok(false);
1083                        }
1084                    }
1085                    Ok(true)
1086                }
1087                _ => Ok(false),
1088            },
1089            GroundDomain::Relation(rel_attr, inner_domains) => match lit {
1090                Literal::AbstractLiteral(AbstractLiteral::Relation(lit_elems)) => {
1091                    // check if the literal's size is allowed by the attributes
1092                    let sz = lit_elems.len().to_i32().ok_or(DomainOpError::TooLarge)?;
1093                    if !rel_attr.size.contains(&sz) {
1094                        return Ok(false);
1095                    }
1096
1097                    for elem_tuple in lit_elems {
1098                        if elem_tuple.len() == inner_domains.len() {
1099                            for (elem, inner_dom) in elem_tuple.iter().zip(inner_domains.iter()) {
1100                                if !inner_dom.contains(elem)? {
1101                                    return Ok(false);
1102                                }
1103                            }
1104                        } else {
1105                            return Ok(false);
1106                        }
1107                    }
1108                    Ok(true)
1109                }
1110                _ => Ok(false),
1111            },
1112            GroundDomain::Partition(attr, dom) => match lit {
1113                Literal::AbstractLiteral(AbstractLiteral::Partition(lit_elems)) => {
1114                    // let sz = lit_elems.len().to_i32().ok_or(DomainOpError::TooLarge)?;
1115                    let sz: i32 = lit_elems
1116                        .iter()
1117                        .flatten()
1118                        .count()
1119                        .to_i32()
1120                        .ok_or(DomainOpError::TooLarge)?;
1121
1122                    let min: Option<i32> = match (attr.num_parts.low(), attr.part_len.low()) {
1123                        (Some(x), Some(y)) => Some(x * y),
1124                        _ => None,
1125                    };
1126
1127                    let max: Option<i32> = match (attr.num_parts.high(), attr.part_len.high()) {
1128                        (Some(x), Some(y)) => Some(x * y),
1129                        _ => None,
1130                    };
1131
1132                    let rng = Range::new(min, max);
1133                    if !rng.contains(&sz) {
1134                        return Ok(false);
1135                    }
1136
1137                    for elem in lit_elems.iter().flatten() {
1138                        if !dom.contains(elem)? {
1139                            return Ok(false);
1140                        }
1141                    }
1142                    Ok(true)
1143                }
1144                _ => Ok(false),
1145            },
1146            GroundDomain::Permutation(attr, dom) => match lit {
1147                Literal::AbstractLiteral(AbstractLiteral::Permutation(cycles)) => {
1148                    // numMoved is the count of moved points (elements mentioned in some cycle),
1149                    // matching cycle notation's "unmentioned = fixed point" semantics -- not the
1150                    // inner domain's own size.
1151                    let sz: i32 = cycles
1152                        .iter()
1153                        .flatten()
1154                        .count()
1155                        .to_i32()
1156                        .ok_or(DomainOpError::TooLarge)?;
1157                    if !attr.num_moved.contains(&sz) {
1158                        return Ok(false);
1159                    }
1160
1161                    for elem in cycles.iter().flatten() {
1162                        if !dom.contains(elem)? {
1163                            return Ok(false);
1164                        }
1165                    }
1166                    Ok(true)
1167                }
1168                _ => Ok(false),
1169            },
1170        }
1171    }
1172
1173    /// Returns a list of all possible values in an integer domain.
1174    ///
1175    /// # Errors
1176    ///
1177    /// - [`DomainOpError::NotInteger`] if the domain is not an integer domain.
1178    /// - [`DomainOpError::Unbounded`] if the domain is unbounded.
1179    pub fn values_i32(&self) -> Result<Vec<i32>, DomainOpError> {
1180        if let GroundDomain::Empty(ReturnType::Int) = self {
1181            return Ok(vec![]);
1182        }
1183        let GroundDomain::Int(ranges) = self else {
1184            return Err(DomainOpError::NotInteger(self.return_type()));
1185        };
1186
1187        if ranges.is_empty() {
1188            return Ok(vec![]);
1189        }
1190
1191        let mut values = vec![];
1192        for range in ranges {
1193            match range {
1194                Range::Single(i) => {
1195                    values.push(*i);
1196                }
1197                Range::Bounded(i, j) => {
1198                    values.extend(*i..=*j);
1199                }
1200                Range::UnboundedR(_) | Range::UnboundedL(_) | Range::Unbounded => {
1201                    return Err(DomainOpError::Unbounded);
1202                }
1203            }
1204        }
1205
1206        Ok(values)
1207    }
1208
1209    /// Creates an [`Domain::Int`] containing the given integers.
1210    ///
1211    /// # Examples
1212    ///
1213    /// ```
1214    /// use conjure_cp_core::ast::{GroundDomain, Range};
1215    /// use conjure_cp_core::{domain_int_ground,range};
1216    /// use std::collections::BTreeSet;
1217    ///
1218    /// let elements = BTreeSet::from([1,2,3,4,5]);
1219    ///
1220    /// let domain = GroundDomain::from_set_i32(&elements);
1221    ///
1222    /// assert_eq!(domain,domain_int_ground!(1..5));
1223    /// ```
1224    ///
1225    /// ```
1226    /// use conjure_cp_core::ast::{GroundDomain,Range};
1227    /// use conjure_cp_core::{domain_int_ground,range};
1228    /// use std::collections::BTreeSet;
1229    ///
1230    /// let elements = BTreeSet::from([1,2,4,5,7,8,9,10]);
1231    ///
1232    /// let domain = GroundDomain::from_set_i32(&elements);
1233    ///
1234    /// assert_eq!(domain,domain_int_ground!(1..2,4..5,7..10));
1235    /// ```
1236    ///
1237    /// ```
1238    /// use conjure_cp_core::ast::{GroundDomain,Range,ReturnType};
1239    /// use std::collections::BTreeSet;
1240    ///
1241    /// let elements = BTreeSet::from([]);
1242    ///
1243    /// let domain = GroundDomain::from_set_i32(&elements);
1244    ///
1245    /// assert!(matches!(domain,GroundDomain::Empty(ReturnType::Int)))
1246    /// ```
1247    pub fn from_set_i32(elements: &BTreeSet<i32>) -> GroundDomain {
1248        if elements.is_empty() {
1249            return GroundDomain::Empty(ReturnType::Int);
1250        }
1251        if elements.len() == 1 {
1252            return GroundDomain::Int(vec![Range::Single(*elements.first().unwrap())]);
1253        }
1254
1255        let mut elems_iter = elements.iter().copied();
1256
1257        let mut ranges: Vec<Range<i32>> = vec![];
1258
1259        // Loop over the elements in ascending order, turning all sequential runs of
1260        // numbers into ranges.
1261
1262        // the bounds of the current run of numbers.
1263        let mut lower = elems_iter
1264            .next()
1265            .expect("if we get here, elements should have => 2 elements");
1266        let mut upper = lower;
1267
1268        for current in elems_iter {
1269            // As elements is a BTreeSet, current is always strictly larger than lower.
1270
1271            if current == upper + 1 {
1272                // current is part of the current run - we now have the run lower..current
1273                //
1274                upper = current;
1275            } else {
1276                // the run lower..upper has ended.
1277                //
1278                // Add the run lower..upper to the domain, and start a new run.
1279
1280                if lower == upper {
1281                    ranges.push(range!(lower));
1282                } else {
1283                    ranges.push(range!(lower..upper));
1284                }
1285
1286                lower = current;
1287                upper = current;
1288            }
1289        }
1290
1291        // add the final run to the domain
1292        if lower == upper {
1293            ranges.push(range!(lower));
1294        } else {
1295            ranges.push(range!(lower..upper));
1296        }
1297
1298        ranges = Range::squeeze(&ranges);
1299        GroundDomain::Int(ranges)
1300    }
1301
1302    /// Returns the domain that is the result of applying a binary operation to two integer domains.
1303    ///
1304    /// The given operator may return `None` if the operation is not defined for its arguments.
1305    /// Undefined values will not be included in the resulting domain.
1306    ///
1307    /// # Errors
1308    ///
1309    /// - [`DomainOpError::Unbounded`] if either of the input domains are unbounded.
1310    /// - [`DomainOpError::NotInteger`] if either of the input domains are not integers.
1311    pub fn apply_i32(
1312        &self,
1313        op: fn(i32, i32) -> Option<i32>,
1314        other: &GroundDomain,
1315    ) -> Result<GroundDomain, DomainOpError> {
1316        let vs1 = self.values_i32()?;
1317        let vs2 = other.values_i32()?;
1318
1319        let mut set = BTreeSet::new();
1320        for (v1, v2) in itertools::iproduct!(vs1, vs2) {
1321            if let Some(v) = op(v1, v2) {
1322                set.insert(v);
1323            }
1324        }
1325
1326        Ok(GroundDomain::from_set_i32(&set))
1327    }
1328
1329    /// Returns true if the domain is finite.
1330    pub fn is_finite(&self) -> bool {
1331        for domain in self.universe() {
1332            if let GroundDomain::Int(ranges) = domain
1333                && ranges.iter().any(|range| {
1334                    matches!(
1335                        range,
1336                        Range::UnboundedL(_) | Range::UnboundedR(_) | Range::Unbounded
1337                    )
1338                })
1339            {
1340                return false;
1341            }
1342        }
1343        true
1344    }
1345
1346    /// For a vector of literals, creates a domain that contains all the elements.
1347    ///
1348    /// The literals must all be of the same type.
1349    ///
1350    /// For abstract literals, this method merges the element domains of the literals, but not the
1351    /// index domains. Thus, for fixed-sized abstract literals (matrices, tuples, records, etc.),
1352    /// all literals in the vector must also have the same size / index domain:
1353    ///
1354    /// + Matrices: all literals must have the same index domain.
1355    /// + Tuples: all literals must have the same number of elements.
1356    /// + Records: all literals must have the same fields.
1357    ///
1358    /// # Errors
1359    ///
1360    /// - [DomainOpError::WrongType] if the input literals are of a different type to
1361    ///   each-other, as described above.
1362    ///
1363    /// # Examples
1364    ///
1365    /// ```
1366    /// use conjure_cp_core::ast::{Range, Literal, ReturnType, GroundDomain};
1367    ///
1368    /// let domain = GroundDomain::from_literal_vec(&vec![]);
1369    /// assert_eq!(domain,Ok(GroundDomain::Empty(ReturnType::Unknown)));
1370    /// ```
1371    ///
1372    /// ```
1373    /// use conjure_cp_core::ast::{GroundDomain,Range,Literal, AbstractLiteral};
1374    /// use conjure_cp_core::{domain_int_ground, range, matrix};
1375    ///
1376    /// // `[1,2;int(2..3)], [4,5; int(2..3)]` has domain
1377    /// // `matrix indexed by [int(2..3)] of int(1..2,4..5)`
1378    ///
1379    /// let matrix_1 = Literal::AbstractLiteral(matrix![Literal::Int(1),Literal::Int(2);domain_int_ground!(2..3)]);
1380    /// let matrix_2 = Literal::AbstractLiteral(matrix![Literal::Int(4),Literal::Int(5);domain_int_ground!(2..3)]);
1381    ///
1382    /// let domain = GroundDomain::from_literal_vec(&vec![matrix_1,matrix_2]);
1383    ///
1384    /// let expected_domain = Ok(GroundDomain::Matrix(
1385    ///     domain_int_ground!(1..2,4..5),vec![domain_int_ground!(2..3)]));
1386    ///
1387    /// assert_eq!(domain,expected_domain);
1388    /// ```
1389    ///
1390    /// ```
1391    /// use conjure_cp_core::ast::{GroundDomain,Range,Literal, AbstractLiteral,DomainOpError};
1392    /// use conjure_cp_core::{domain_int_ground, range, matrix};
1393    ///
1394    /// // `[1,2;int(2..3)], [4,5; int(1..2)]` cannot be combined
1395    /// // `matrix indexed by [int(2..3)] of int(1..2,4..5)`
1396    ///
1397    /// let matrix_1 = Literal::AbstractLiteral(matrix![Literal::Int(1),Literal::Int(2);domain_int_ground!(2..3)]);
1398    /// let matrix_2 = Literal::AbstractLiteral(matrix![Literal::Int(4),Literal::Int(5);domain_int_ground!(1..2)]);
1399    ///
1400    /// let domain = GroundDomain::from_literal_vec(&vec![matrix_1,matrix_2]);
1401    ///
1402    /// assert_eq!(domain,Err(DomainOpError::WrongType));
1403    /// ```
1404    ///
1405    /// ```
1406    /// use conjure_cp_core::ast::{GroundDomain,Range,Literal, AbstractLiteral};
1407    /// use conjure_cp_core::{domain_int_ground,range, matrix};
1408    ///
1409    /// // `[[1,2; int(1..2)];int(2)], [[4,5; int(1..2)]; int(2)]` has domain
1410    /// // `matrix indexed by [int(2),int(1..2)] of int(1..2,4..5)`
1411    ///
1412    ///
1413    /// let matrix_1 = Literal::AbstractLiteral(matrix![Literal::AbstractLiteral(matrix![Literal::Int(1),Literal::Int(2);domain_int_ground!(1..2)]); domain_int_ground!(2)]);
1414    /// let matrix_2 = Literal::AbstractLiteral(matrix![Literal::AbstractLiteral(matrix![Literal::Int(4),Literal::Int(5);domain_int_ground!(1..2)]); domain_int_ground!(2)]);
1415    ///
1416    /// let domain = GroundDomain::from_literal_vec(&vec![matrix_1,matrix_2]);
1417    ///
1418    /// let expected_domain = Ok(GroundDomain::Matrix(
1419    ///     domain_int_ground!(1..2,4..5),
1420    ///     vec![domain_int_ground!(2),domain_int_ground!(1..2)]));
1421    ///
1422    /// assert_eq!(domain,expected_domain);
1423    /// ```
1424    ///
1425    ///
1426    pub fn from_literal_vec(literals: &[Literal]) -> Result<GroundDomain, DomainOpError> {
1427        // TODO: use proptest to test this better?
1428
1429        if literals.is_empty() {
1430            return Ok(GroundDomain::Empty(ReturnType::Unknown));
1431        }
1432
1433        let first_literal = literals.first().unwrap();
1434
1435        match first_literal {
1436            Literal::Int(_) => {
1437                // check all literals are ints, then pass this to Domain::from_set_i32.
1438                let mut ints = BTreeSet::new();
1439                for lit in literals {
1440                    let Literal::Int(i) = lit else {
1441                        return Err(DomainOpError::WrongType);
1442                    };
1443
1444                    ints.insert(*i);
1445                }
1446
1447                Ok(GroundDomain::from_set_i32(&ints))
1448            }
1449            Literal::Bool(_) => {
1450                // check all literals are bools
1451                if literals.iter().any(|x| !matches!(x, Literal::Bool(_))) {
1452                    Err(DomainOpError::WrongType)
1453                } else {
1454                    Ok(GroundDomain::Bool)
1455                }
1456            }
1457            Literal::AbstractLiteral(AbstractLiteral::Set(_)) => {
1458                let mut all_elems = vec![];
1459
1460                for lit in literals {
1461                    let Literal::AbstractLiteral(AbstractLiteral::Set(elems)) = lit else {
1462                        return Err(DomainOpError::WrongType);
1463                    };
1464
1465                    all_elems.extend(elems.clone());
1466                }
1467                let elem_domain = GroundDomain::from_literal_vec(&all_elems)?;
1468
1469                Ok(GroundDomain::Set(SetAttr::default(), Moo::new(elem_domain)))
1470            }
1471            Literal::AbstractLiteral(AbstractLiteral::MSet(_)) => {
1472                let mut all_elems = vec![];
1473
1474                for lit in literals {
1475                    let Literal::AbstractLiteral(AbstractLiteral::MSet(elems)) = lit else {
1476                        return Err(DomainOpError::WrongType);
1477                    };
1478
1479                    all_elems.extend(elems.clone());
1480                }
1481                let elem_domain = GroundDomain::from_literal_vec(&all_elems)?;
1482
1483                Ok(GroundDomain::MSet(
1484                    MSetAttr::default(),
1485                    Moo::new(elem_domain),
1486                ))
1487            }
1488            Literal::AbstractLiteral(AbstractLiteral::Partition(_)) => {
1489                todo!("Need to figure out how this is going to work")
1490            }
1491            Literal::AbstractLiteral(AbstractLiteral::Permutation(_)) => {
1492                todo!("Need to figure out how this is going to work")
1493            }
1494            l @ Literal::AbstractLiteral(AbstractLiteral::Matrix(_, _)) => {
1495                let mut first_index_domain = vec![];
1496                // flatten index domains of n-d matrix into list
1497                let mut l = l.clone();
1498                while let Literal::AbstractLiteral(AbstractLiteral::Matrix(elems, idx)) = l {
1499                    bug_assert!(
1500                        !matches!(idx.as_ref(), GroundDomain::Matrix(_, _)),
1501                        "n-dimensional matrix literals should be represented as a matrix inside a matrix"
1502                    );
1503                    first_index_domain.push(idx);
1504                    let Some(first_elem) = elems.first() else {
1505                        break;
1506                    };
1507                    l = first_elem.clone();
1508                }
1509
1510                let mut all_elems: Vec<Literal> = vec![];
1511
1512                // check types and index domains
1513                for lit in literals {
1514                    let Literal::AbstractLiteral(AbstractLiteral::Matrix(elems, idx)) = lit else {
1515                        return Err(DomainOpError::NotGround);
1516                    };
1517
1518                    all_elems.extend(elems.clone());
1519
1520                    let mut index_domain = vec![idx.clone()];
1521                    let Some(first_elem) = elems.first() else {
1522                        if index_domain != first_index_domain {
1523                            return Err(DomainOpError::WrongType);
1524                        }
1525                        continue;
1526                    };
1527                    let mut l = first_elem.clone();
1528                    while let Literal::AbstractLiteral(AbstractLiteral::Matrix(elems, idx)) = l {
1529                        bug_assert!(
1530                            !matches!(idx.as_ref(), GroundDomain::Matrix(_, _)),
1531                            "n-dimensional matrix literals should be represented as a matrix inside a matrix"
1532                        );
1533                        index_domain.push(idx);
1534                        let Some(first_elem) = elems.first() else {
1535                            break;
1536                        };
1537                        l = first_elem.clone();
1538                    }
1539
1540                    if index_domain != first_index_domain {
1541                        return Err(DomainOpError::WrongType);
1542                    }
1543                }
1544
1545                // extract all the terminal elements (those that are not nested matrix literals) from the matrix literal.
1546                let mut terminal_elements: Vec<Literal> = vec![];
1547                while let Some(elem) = all_elems.pop() {
1548                    if let Literal::AbstractLiteral(AbstractLiteral::Matrix(elems, _)) = elem {
1549                        all_elems.extend(elems);
1550                    } else {
1551                        terminal_elements.push(elem);
1552                    }
1553                }
1554
1555                let element_domain = GroundDomain::from_literal_vec(&terminal_elements)?;
1556
1557                Ok(GroundDomain::Matrix(
1558                    Moo::new(element_domain),
1559                    first_index_domain,
1560                ))
1561            }
1562
1563            Literal::AbstractLiteral(AbstractLiteral::Tuple(first_elems)) => {
1564                let n_fields = first_elems.len();
1565
1566                // for each field, calculate the element domain and add it to this list
1567                let mut elem_domains = vec![];
1568
1569                for i in 0..n_fields {
1570                    let mut all_elems = vec![];
1571                    for lit in literals {
1572                        let Literal::AbstractLiteral(AbstractLiteral::Tuple(elems)) = lit else {
1573                            return Err(DomainOpError::NotGround);
1574                        };
1575
1576                        if elems.len() != n_fields {
1577                            return Err(DomainOpError::NotGround);
1578                        }
1579
1580                        all_elems.push(elems[i].clone());
1581                    }
1582
1583                    elem_domains.push(Moo::new(GroundDomain::from_literal_vec(&all_elems)?));
1584                }
1585
1586                Ok(GroundDomain::Tuple(elem_domains))
1587            }
1588
1589            Literal::AbstractLiteral(AbstractLiteral::Sequence(_)) => {
1590                let mut all_elems = vec![];
1591                let mut lengths = Vec::new();
1592
1593                for lit in literals {
1594                    let Literal::AbstractLiteral(AbstractLiteral::Sequence(elems)) = lit else {
1595                        return Err(DomainOpError::WrongType);
1596                    };
1597
1598                    lengths.push(i32::try_from(elems.len()).map_err(|_| DomainOpError::TooLarge)?);
1599                    all_elems.extend(elems.clone());
1600                }
1601                let elem_domain = GroundDomain::from_literal_vec(&all_elems)?;
1602
1603                // These literals are known values, so their lengths bound the size attribute --
1604                // without which nothing downstream can tell how many positions to iterate over.
1605                let min = lengths.iter().copied().min().unwrap_or(0);
1606                let max = lengths.iter().copied().max().unwrap_or(0);
1607                let size = if min == max {
1608                    Range::Single(min)
1609                } else {
1610                    Range::Bounded(min, max)
1611                };
1612
1613                Ok(GroundDomain::Sequence(
1614                    SequenceAttr {
1615                        size,
1616                        ..SequenceAttr::default()
1617                    },
1618                    Moo::new(elem_domain),
1619                ))
1620            }
1621
1622            Literal::AbstractLiteral(AbstractLiteral::Record(first_elems)) => {
1623                let n_fields = first_elems.len();
1624                let field_names = first_elems.iter().map(|x| x.name.clone()).collect_vec();
1625
1626                // for each field, calculate the element domain and add it to this list
1627                let mut elem_domains = vec![];
1628
1629                for i in 0..n_fields {
1630                    let mut all_elems = vec![];
1631                    for lit in literals {
1632                        let Literal::AbstractLiteral(AbstractLiteral::Record(elems)) = lit else {
1633                            return Err(DomainOpError::NotGround);
1634                        };
1635
1636                        if elems.len() != n_fields {
1637                            return Err(DomainOpError::NotGround);
1638                        }
1639
1640                        let elem = elems[i].clone();
1641                        if elem.name != field_names[i] {
1642                            return Err(DomainOpError::NotGround);
1643                        }
1644
1645                        all_elems.push(elem.value);
1646                    }
1647
1648                    elem_domains.push(Moo::new(GroundDomain::from_literal_vec(&all_elems)?));
1649                }
1650
1651                Ok(GroundDomain::Record(
1652                    izip!(field_names, elem_domains)
1653                        .map(|(name, value)| FieldGround { name, value })
1654                        .collect(),
1655                ))
1656            }
1657            Literal::AbstractLiteral(AbstractLiteral::Function(_)) => {
1658                let mut all_keys = vec![];
1659                let mut all_values = vec![];
1660
1661                for lit in literals {
1662                    let Literal::AbstractLiteral(AbstractLiteral::Function(pairs)) = lit else {
1663                        return Err(DomainOpError::WrongType);
1664                    };
1665
1666                    for (key, value) in pairs {
1667                        all_keys.push(key.clone());
1668                        all_values.push(value.clone());
1669                    }
1670                }
1671
1672                let domain = GroundDomain::from_literal_vec(&all_keys)?;
1673                let codomain = GroundDomain::from_literal_vec(&all_values)?;
1674
1675                Ok(GroundDomain::Function(
1676                    FuncAttr::default(),
1677                    Moo::new(domain),
1678                    Moo::new(codomain),
1679                ))
1680            }
1681            Literal::AbstractLiteral(AbstractLiteral::Variant(_)) => {
1682                let mut alternatives: Vec<(Name, Vec<Literal>)> = Vec::new();
1683                for literal in literals {
1684                    let Literal::AbstractLiteral(AbstractLiteral::Variant(field)) = literal else {
1685                        return Err(DomainOpError::WrongType);
1686                    };
1687                    if let Some((_, values)) = alternatives
1688                        .iter_mut()
1689                        .find(|(name, _)| name == &field.name)
1690                    {
1691                        values.push(field.value.clone());
1692                    } else {
1693                        alternatives.push((field.name.clone(), vec![field.value.clone()]));
1694                    }
1695                }
1696
1697                Ok(GroundDomain::Variant(
1698                    alternatives
1699                        .into_iter()
1700                        .map(|(name, values)| {
1701                            Ok(FieldGround {
1702                                name,
1703                                value: Moo::new(GroundDomain::from_literal_vec(&values)?),
1704                            })
1705                        })
1706                        .collect::<Result<Vec<_>, DomainOpError>>()?,
1707                ))
1708            }
1709            Literal::AbstractLiteral(AbstractLiteral::Relation(_)) => {
1710                let mut columns: Vec<Vec<Literal>> = vec![];
1711                for lit in literals {
1712                    let Literal::AbstractLiteral(AbstractLiteral::Relation(tuples)) = lit else {
1713                        return Err(DomainOpError::WrongType);
1714                    };
1715                    for tuple in tuples {
1716                        if columns.is_empty() {
1717                            columns = vec![Vec::new(); tuple.len()];
1718                        }
1719                        if tuple.len() != columns.len() {
1720                            return Err(DomainOpError::NotGround);
1721                        }
1722                        for (column, field) in columns.iter_mut().zip(tuple) {
1723                            column.push(field.clone());
1724                        }
1725                    }
1726                }
1727
1728                let inner_domains = columns
1729                    .iter()
1730                    .map(|column| GroundDomain::from_literal_vec(column).map(Moo::new))
1731                    .collect::<Result<Vec<_>, _>>()?;
1732
1733                Ok(GroundDomain::Relation(RelAttr::default(), inner_domains))
1734            }
1735        }
1736    }
1737
1738    pub fn element_domain(&self) -> Option<Moo<GroundDomain>> {
1739        match self {
1740            GroundDomain::Matrix(inner, _) => Some(inner.clone()),
1741            GroundDomain::Set(_, inner) => Some(inner.clone()),
1742            GroundDomain::MSet(_, inner) => Some(inner.clone()),
1743            GroundDomain::Relation(_, inner_doms) => {
1744                Some(Moo::new(GroundDomain::Tuple(inner_doms.clone())))
1745            }
1746            // A sequence is a function from int(1..|s|), and iterating a function yields its
1747            // pairs, so iterating a sequence yields (position, value).
1748            GroundDomain::Sequence(attr, inner) => {
1749                let max = match attr.size {
1750                    Range::Single(max) | Range::UnboundedL(max) | Range::Bounded(_, max) => max,
1751                    Range::UnboundedR(_) | Range::Unbounded => return None,
1752                };
1753                Some(Moo::new(GroundDomain::Tuple(vec![
1754                    Moo::new(GroundDomain::Int(vec![Range::Bounded(1, max)])),
1755                    inner.clone(),
1756                ])))
1757            }
1758            _ => None,
1759        }
1760    }
1761
1762    /// True if any domain in this tree has a representation preference.
1763    pub fn has_representation_preference(&self) -> bool {
1764        match self {
1765            GroundDomain::Empty(_) => false,
1766            GroundDomain::Bool => false,
1767            GroundDomain::Int(_) => false,
1768            GroundDomain::Tuple(inners) => inners.iter().any(|d| d.has_representation_preference()),
1769            GroundDomain::Record(entries) => entries
1770                .iter()
1771                .any(|f| f.value.has_representation_preference()),
1772            GroundDomain::Variant(entries) => entries
1773                .iter()
1774                .any(|f| f.value.has_representation_preference()),
1775            GroundDomain::Matrix(inner, idxs) => {
1776                inner.has_representation_preference()
1777                    || idxs.iter().any(|d| d.has_representation_preference())
1778            }
1779            GroundDomain::Sequence(attr, inner) => {
1780                attr.representation.is_some() || inner.has_representation_preference()
1781            }
1782            GroundDomain::Set(attr, inner) => {
1783                attr.representation.is_some() || inner.has_representation_preference()
1784            }
1785            GroundDomain::MSet(attr, inner) => {
1786                attr.representation.is_some() || inner.has_representation_preference()
1787            }
1788            GroundDomain::Function(_, dom, cdom) => {
1789                dom.has_representation_preference() || cdom.has_representation_preference()
1790            }
1791            GroundDomain::Relation(_, inners) => {
1792                inners.iter().any(|d| d.has_representation_preference())
1793            }
1794            GroundDomain::Partition(_, inner) => inner.has_representation_preference(),
1795            GroundDomain::Permutation(_, inner) => inner.has_representation_preference(),
1796        }
1797    }
1798
1799    /// Format this domain in Essence type style, omitting size attributes and integer ranges.
1800    pub fn as_type_string(&self) -> String {
1801        match self {
1802            GroundDomain::Empty(ty) => format!("empty({ty})"),
1803            GroundDomain::Bool => "bool".to_string(),
1804            GroundDomain::Int(_) => "int".to_string(),
1805            GroundDomain::Tuple(inners) => {
1806                format!(
1807                    "tuple ({})",
1808                    inners.iter().map(|d| d.as_type_string()).join(", ")
1809                )
1810            }
1811            GroundDomain::Record(entries) => {
1812                let inners = entries
1813                    .iter()
1814                    .map(|f| format!("{}: {}", f.name, f.value.as_type_string()))
1815                    .join(", ");
1816                format!("record {{{inners}}}")
1817            }
1818            GroundDomain::Variant(entries) => {
1819                let inners = entries
1820                    .iter()
1821                    .map(|f| format!("{}: {}", f.name, f.value.as_type_string()))
1822                    .join(", ");
1823                format!("variant {{{inners}}}")
1824            }
1825            GroundDomain::Matrix(inner, idxs) => {
1826                let idxs = idxs.iter().map(|d| d.as_type_string()).join(", ");
1827                format!("matrix indexed by [{idxs}] of {}", inner.as_type_string())
1828            }
1829            GroundDomain::Sequence(_, inner) => format!("sequence of {}", inner.as_type_string()),
1830            GroundDomain::Set(attrs, inner) => {
1831                let mut out = String::from("set");
1832                if let Some(repr) = &attrs.representation {
1833                    out.push_str(" (representation ");
1834                    out.push_str(repr);
1835                    out.push(')');
1836                }
1837                out.push_str(" of ");
1838                out.push_str(&inner.as_type_string());
1839                out
1840            }
1841            GroundDomain::MSet(attrs, inner) => {
1842                let mut out = String::from("mset");
1843                if let Some(repr) = &attrs.representation {
1844                    out.push_str(" (representation ");
1845                    out.push_str(repr);
1846                    out.push(')');
1847                }
1848                out.push_str(" of ");
1849                out.push_str(&inner.as_type_string());
1850                out
1851            }
1852            GroundDomain::Function(_, dom, cdom) => {
1853                format!(
1854                    "function {} --> {}",
1855                    dom.as_type_string(),
1856                    cdom.as_type_string()
1857                )
1858            }
1859            GroundDomain::Relation(_, inners) => {
1860                format!(
1861                    "relation of ({})",
1862                    inners.iter().map(|d| d.as_type_string()).join(" * ")
1863                )
1864            }
1865            GroundDomain::Partition(_, inner) => {
1866                format!("partition from {}", inner.as_type_string())
1867            }
1868            GroundDomain::Permutation(_, inner) => {
1869                format!("permutation of {}", inner.as_type_string())
1870            }
1871        }
1872    }
1873}
1874
1875impl Typeable for GroundDomain {
1876    fn return_type(&self) -> ReturnType {
1877        match self {
1878            GroundDomain::Empty(ty) => ty.clone(),
1879            GroundDomain::Bool => ReturnType::Bool,
1880            GroundDomain::Int(_) => ReturnType::Int,
1881            GroundDomain::Tuple(inners) => {
1882                let mut inner_types = Vec::new();
1883                for inner in inners {
1884                    inner_types.push(inner.return_type());
1885                }
1886                ReturnType::Tuple(inner_types)
1887            }
1888            GroundDomain::Record(entries) => {
1889                let mut entry_types = Vec::new();
1890                for entry in entries {
1891                    entry_types.push(entry.clone().func_map(|x| x.return_type()));
1892                }
1893                ReturnType::Record(entry_types)
1894            }
1895            GroundDomain::Variant(entries) => {
1896                let mut entry_types = Vec::new();
1897                for entry in entries {
1898                    entry_types.push(entry.clone().func_map(|x| x.return_type()));
1899                }
1900                ReturnType::Variant(entry_types)
1901            }
1902            GroundDomain::Matrix(inner, _idx) => ReturnType::Matrix(Box::new(inner.return_type())),
1903            GroundDomain::Sequence(_attr, inner) => {
1904                ReturnType::Sequence(Box::new(inner.return_type()))
1905            }
1906            GroundDomain::Set(_attr, inner) => ReturnType::Set(Box::new(inner.return_type())),
1907            GroundDomain::MSet(_attr, inner) => ReturnType::MSet(Box::new(inner.return_type())),
1908            GroundDomain::Function(_, dom, cdom) => {
1909                ReturnType::Function(Box::new(dom.return_type()), Box::new(cdom.return_type()))
1910            }
1911            GroundDomain::Relation(_, inners) => {
1912                let mut inner_types = Vec::new();
1913                for inner in inners {
1914                    inner_types.push(inner.return_type());
1915                }
1916                ReturnType::Relation(inner_types)
1917            }
1918            GroundDomain::Partition(_, inner) => {
1919                ReturnType::Partition(Box::new(inner.return_type()))
1920            }
1921            GroundDomain::Permutation(_, inner) => {
1922                ReturnType::Permutation(Box::new(inner.return_type()))
1923            }
1924        }
1925    }
1926}
1927
1928impl Display for FieldGround {
1929    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1930        write!(f, "{}: {}", self.name, self.value)
1931    }
1932}
1933
1934impl Display for GroundDomain {
1935    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1936        match &self {
1937            GroundDomain::Empty(ty) => write!(f, "empty({ty})"),
1938            GroundDomain::Bool => write!(f, "bool"),
1939            GroundDomain::Int(ranges) => {
1940                if ranges.iter().all(Range::is_lower_or_upper_bounded) {
1941                    let rngs: String = ranges.iter().map(|r| format!("{r}")).join(", ");
1942                    write!(f, "int({})", rngs)
1943                } else {
1944                    write!(f, "int")
1945                }
1946            }
1947            GroundDomain::Tuple(domains) => {
1948                write!(f, "tuple ({})", domains.iter().join(", "))
1949            }
1950            GroundDomain::Record(entries) => {
1951                let inners = entries.iter().map(|t| format!("{}", t)).join(", ");
1952                write!(f, "record {{{inners}}}",)
1953            }
1954            GroundDomain::Variant(entries) => {
1955                let inners = entries.iter().map(|t| format!("{}", t)).join(", ");
1956                write!(f, "variant {{{inners}}}",)
1957            }
1958            GroundDomain::Matrix(value_domain, index_domains) => {
1959                write!(
1960                    f,
1961                    "matrix indexed by {} of {value_domain}",
1962                    pretty_vec(&index_domains.iter().collect_vec())
1963                )
1964            }
1965            GroundDomain::Sequence(attrs, inner_dom) => {
1966                write!(f, "sequence {attrs} of {inner_dom}")
1967            }
1968            GroundDomain::Set(attrs, inner_dom) => {
1969                write!(f, "set")?;
1970                let attrs = attrs.to_string();
1971                if attrs.is_empty() {
1972                    write!(f, " of {inner_dom}")
1973                } else {
1974                    write!(f, " {attrs} of {inner_dom}")
1975                }
1976            }
1977            GroundDomain::MSet(attrs, inner_dom) => {
1978                write!(f, "mset")?;
1979                let attrs = attrs.to_string();
1980                if attrs.is_empty() {
1981                    write!(f, " of {inner_dom}")
1982                } else {
1983                    write!(f, " {attrs} of {inner_dom}")
1984                }
1985            }
1986            GroundDomain::Function(attribute, domain, codomain) => {
1987                write!(f, "function {} {} --> {} ", attribute, domain, codomain)
1988            }
1989            GroundDomain::Relation(attrs, domains) => {
1990                write!(f, "relation {} of ({})", attrs, domains.iter().join(" * "))
1991            }
1992            GroundDomain::Partition(attrs, inner) => {
1993                write!(f, "partition {attrs} from {inner}")
1994            }
1995            GroundDomain::Permutation(attrs, inner) => {
1996                write!(f, "permutation {attrs} of {inner}")
1997            }
1998        }
1999    }
2000}
2001
2002#[cfg(test)]
2003mod tests {
2004    use super::*;
2005    use crate::ast::Name;
2006    use crate::{domain_int_ground, matrix_lit};
2007
2008    #[test]
2009    fn matrix_values_1d_bool_of_bool() {
2010        // matrix indexed by [bool] of bool
2011        // 2 cells, 2 possible values => 2^2 = 4 matrices
2012        let dom = GroundDomain::Matrix(
2013            Moo::new(GroundDomain::Bool),
2014            vec![Moo::new(GroundDomain::Bool)],
2015        );
2016
2017        let values: Vec<Literal> = dom.values().unwrap().collect();
2018
2019        assert_eq!(values.len(), 4);
2020        assert_eq!(
2021            values[0],
2022            matrix_lit![false, false; Moo::new(GroundDomain::Bool)]
2023        );
2024        assert_eq!(
2025            values[1],
2026            matrix_lit![false, true; Moo::new(GroundDomain::Bool)]
2027        );
2028        assert_eq!(
2029            values[2],
2030            matrix_lit![true, false; Moo::new(GroundDomain::Bool)]
2031        );
2032        assert_eq!(
2033            values[3],
2034            matrix_lit![true, true; Moo::new(GroundDomain::Bool)]
2035        );
2036    }
2037
2038    #[test]
2039    fn matrix_values_1d_int() {
2040        // matrix indexed by [int(1..2)] of int(0..1)
2041        // 2 cells, 2 possible values => 4 matrices
2042        let dom = GroundDomain::Matrix(domain_int_ground!(0..1), vec![domain_int_ground!(1..2)]);
2043
2044        let values: Vec<Literal> = dom.values().unwrap().collect();
2045
2046        assert_eq!(values.len(), 4);
2047        assert_eq!(values[0], matrix_lit![0, 0; domain_int_ground!(1..2)]);
2048        assert_eq!(values[1], matrix_lit![0, 1; domain_int_ground!(1..2)]);
2049        assert_eq!(values[2], matrix_lit![1, 0; domain_int_ground!(1..2)]);
2050        assert_eq!(values[3], matrix_lit![1, 1; domain_int_ground!(1..2)]);
2051    }
2052
2053    #[test]
2054    fn matrix_values_2d_lexicographic() {
2055        // matrix indexed by [int(1..2), int(1..2)] of int(0..1)
2056        // 4 cells, 2 possible values => 2^4 = 16 matrices
2057        let dom = GroundDomain::Matrix(
2058            domain_int_ground!(0..1),
2059            vec![domain_int_ground!(1..2), domain_int_ground!(1..2)],
2060        );
2061
2062        let values: Vec<Literal> = dom.values().unwrap().collect();
2063
2064        assert_eq!(values.len(), 16);
2065
2066        // First: [[0,0],[0,0]]
2067        assert_eq!(
2068            values[0],
2069            matrix_lit![[0, 0], [0, 0]; [domain_int_ground!(1..2), domain_int_ground!(1..2)]]
2070        );
2071        // Second: [[0,0],[0,1]]
2072        assert_eq!(
2073            values[1],
2074            matrix_lit![[0, 0], [0, 1]; [domain_int_ground!(1..2), domain_int_ground!(1..2)]]
2075        );
2076        // Third: [[0,0],[1,0]]
2077        assert_eq!(
2078            values[2],
2079            matrix_lit![[0, 0], [1, 0]; [domain_int_ground!(1..2), domain_int_ground!(1..2)]]
2080        );
2081        // Fourth: [[0,0],[1,1]]
2082        assert_eq!(
2083            values[3],
2084            matrix_lit![[0, 0], [1, 1]; [domain_int_ground!(1..2), domain_int_ground!(1..2)]]
2085        );
2086        // Last: [[1,1],[1,1]]
2087        assert_eq!(
2088            values[15],
2089            matrix_lit![[1, 1], [1, 1]; [domain_int_ground!(1..2), domain_int_ground!(1..2)]]
2090        );
2091    }
2092
2093    #[test]
2094    fn matrix_values_count_matches_length() {
2095        // matrix indexed by [int(1..3)] of int(0..1)
2096        // 3 cells, 2 possible values => 2^3 = 8 matrices
2097        let dom = GroundDomain::Matrix(domain_int_ground!(0..1), vec![domain_int_ground!(1..3)]);
2098
2099        let count = dom.values().unwrap().count();
2100        let length = dom.length().unwrap();
2101
2102        assert_eq!(count as u64, length);
2103    }
2104
2105    #[test]
2106    fn tuple_values_two_bools() {
2107        // tuple of (bool, bool) => 2*2 = 4 values
2108        let dom = GroundDomain::Tuple(vec![
2109            Moo::new(GroundDomain::Bool),
2110            Moo::new(GroundDomain::Bool),
2111        ]);
2112
2113        let values: Vec<Literal> = dom.values().unwrap().collect();
2114
2115        assert_eq!(values.len(), 4);
2116        let t = |a, b| {
2117            Literal::AbstractLiteral(AbstractLiteral::Tuple(vec![
2118                Literal::Bool(a),
2119                Literal::Bool(b),
2120            ]))
2121        };
2122        assert_eq!(values[0], t(false, false));
2123        assert_eq!(values[1], t(false, true));
2124        assert_eq!(values[2], t(true, false));
2125        assert_eq!(values[3], t(true, true));
2126    }
2127
2128    #[test]
2129    fn tuple_values_mixed_domains() {
2130        // tuple of (bool, int(0..2)) => 2*3 = 6 values, lexicographic
2131        let dom = GroundDomain::Tuple(vec![Moo::new(GroundDomain::Bool), domain_int_ground!(0..2)]);
2132
2133        let values: Vec<Literal> = dom.values().unwrap().collect();
2134
2135        assert_eq!(values.len(), 6);
2136        let t = |b: bool, i: i32| {
2137            Literal::AbstractLiteral(AbstractLiteral::Tuple(vec![
2138                Literal::Bool(b),
2139                Literal::Int(i),
2140            ]))
2141        };
2142        // bool false first, then ints 0,1,2
2143        assert_eq!(values[0], t(false, 0));
2144        assert_eq!(values[1], t(false, 1));
2145        assert_eq!(values[2], t(false, 2));
2146        // then bool true
2147        assert_eq!(values[3], t(true, 0));
2148        assert_eq!(values[4], t(true, 1));
2149        assert_eq!(values[5], t(true, 2));
2150    }
2151
2152    #[test]
2153    fn tuple_values_count_matches_length() {
2154        let dom = GroundDomain::Tuple(vec![
2155            domain_int_ground!(1..3),
2156            Moo::new(GroundDomain::Bool),
2157            domain_int_ground!(0..1),
2158        ]);
2159        let count = dom.values().unwrap().count();
2160        let length = dom.length().unwrap();
2161        assert_eq!(count as u64, length);
2162    }
2163
2164    #[test]
2165    fn record_values_lexicographic_by_name() {
2166        // record {b: bool, a: int(0..1)}
2167        // Entries should be ordered by name: a first, then b
2168        let dom = GroundDomain::Record(vec![
2169            Field {
2170                name: Name::user("b"),
2171                value: Moo::new(GroundDomain::Bool),
2172            },
2173            Field {
2174                name: Name::user("a"),
2175                value: domain_int_ground!(0..1),
2176            },
2177        ]);
2178
2179        let values: Vec<Literal> = dom.values().unwrap().collect();
2180
2181        // 2 * 2 = 4 values
2182        assert_eq!(values.len(), 4);
2183
2184        // Entries should be sorted by name: "a" before "b"
2185        let r = |a_val: i32, b_val: bool| {
2186            Literal::AbstractLiteral(AbstractLiteral::Record(vec![
2187                Field {
2188                    name: Name::user("a"),
2189                    value: Literal::Int(a_val),
2190                },
2191                Field {
2192                    name: Name::user("b"),
2193                    value: Literal::Bool(b_val),
2194                },
2195            ]))
2196        };
2197
2198        // "a" (int) varies slowest, "b" (bool) varies fastest
2199        assert_eq!(values[0], r(0, false));
2200        assert_eq!(values[1], r(0, true));
2201        assert_eq!(values[2], r(1, false));
2202        assert_eq!(values[3], r(1, true));
2203    }
2204
2205    #[test]
2206    fn record_values_count_matches_length() {
2207        let dom = GroundDomain::Record(vec![
2208            Field {
2209                name: Name::user("x"),
2210                value: domain_int_ground!(1..3),
2211            },
2212            Field {
2213                name: Name::user("y"),
2214                value: Moo::new(GroundDomain::Bool),
2215            },
2216        ]);
2217        let count = dom.values().unwrap().count();
2218        let length = dom.length().unwrap();
2219        assert_eq!(count as u64, length);
2220    }
2221
2222    #[test]
2223    fn variant_values_follow_alternative_order_and_match_length() {
2224        let dom = GroundDomain::Variant(vec![
2225            Field {
2226                name: Name::user("flag"),
2227                value: Moo::new(GroundDomain::Bool),
2228            },
2229            Field {
2230                name: Name::user("value"),
2231                value: domain_int_ground!(2..3),
2232            },
2233        ]);
2234        let values = dom.values().unwrap().collect::<Vec<_>>();
2235        assert_eq!(values.len() as u64, dom.length().unwrap());
2236        assert_eq!(values.len(), 4);
2237        assert!(matches!(
2238            &values[0],
2239            Literal::AbstractLiteral(AbstractLiteral::Variant(field))
2240                if field.name == Name::user("flag") && field.value == Literal::Bool(false)
2241        ));
2242        assert!(matches!(
2243            &values[3],
2244            Literal::AbstractLiteral(AbstractLiteral::Variant(field))
2245                if field.name == Name::user("value") && field.value == Literal::Int(3)
2246        ));
2247        assert!(dom.contains(&values[2]).unwrap());
2248    }
2249
2250    #[test]
2251    fn infers_variant_domain_from_all_observed_alternatives() {
2252        let variant = |name: &str, value: i32| {
2253            Literal::AbstractLiteral(AbstractLiteral::Variant(Moo::new(Field {
2254                name: Name::user(name),
2255                value: Literal::Int(value),
2256            })))
2257        };
2258        let domain =
2259            GroundDomain::from_literal_vec(&[variant("a", 10), variant("a", 13), variant("b", 7)])
2260                .unwrap();
2261        let GroundDomain::Variant(fields) = domain else {
2262            panic!("expected variant domain");
2263        };
2264
2265        assert_eq!(fields.len(), 2);
2266        assert_eq!(fields[0].name, Name::user("a"));
2267        assert!(fields[0].value.contains(&Literal::Int(10)).unwrap());
2268        assert!(fields[0].value.contains(&Literal::Int(13)).unwrap());
2269        assert_eq!(fields[1].name, Name::user("b"));
2270        assert!(fields[1].value.contains(&Literal::Int(7)).unwrap());
2271    }
2272
2273    fn set_lit(elems: Vec<i32>) -> Literal {
2274        Literal::AbstractLiteral(AbstractLiteral::Set(
2275            elems.into_iter().map(Literal::Int).collect(),
2276        ))
2277    }
2278
2279    #[test]
2280    fn set_values_unbounded() {
2281        // set of int(1..3) => all 2^3 = 8 subsets, in order of ascending size
2282        let dom = GroundDomain::Set(SetAttr::default(), domain_int_ground!(1..3));
2283
2284        let values: Vec<Literal> = dom.values().unwrap().collect();
2285
2286        assert_eq!(values.len(), 8);
2287        assert_eq!(values[0], set_lit(vec![])); // size 0
2288        assert_eq!(values[1], set_lit(vec![1])); // size 1
2289        assert_eq!(values[2], set_lit(vec![2]));
2290        assert_eq!(values[3], set_lit(vec![3]));
2291        assert_eq!(values[4], set_lit(vec![1, 2])); // size 2
2292        assert_eq!(values[5], set_lit(vec![1, 3]));
2293        assert_eq!(values[6], set_lit(vec![2, 3]));
2294        assert_eq!(values[7], set_lit(vec![1, 2, 3])); // size 3
2295    }
2296
2297    #[test]
2298    fn set_values_fixed_size() {
2299        // set (size 2) of int(1..3) => the 3 two-element subsets
2300        let dom = GroundDomain::Set(SetAttr::new_size(2), domain_int_ground!(1..3));
2301
2302        let values: Vec<Literal> = dom.values().unwrap().collect();
2303
2304        assert_eq!(values.len(), 3);
2305        assert_eq!(values[0], set_lit(vec![1, 2]));
2306        assert_eq!(values[1], set_lit(vec![1, 3]));
2307        assert_eq!(values[2], set_lit(vec![2, 3]));
2308    }
2309
2310    #[test]
2311    fn set_values_bounded_size() {
2312        // set (minSize 1, maxSize 2) of int(1..3) => subsets of size 1 and 2
2313        let dom = GroundDomain::Set(SetAttr::new_min_max_size(1, 2), domain_int_ground!(1..3));
2314
2315        let values: Vec<Literal> = dom.values().unwrap().collect();
2316
2317        assert_eq!(values.len(), 6);
2318        assert_eq!(values[0], set_lit(vec![1]));
2319        assert_eq!(values[1], set_lit(vec![2]));
2320        assert_eq!(values[2], set_lit(vec![3]));
2321        assert_eq!(values[3], set_lit(vec![1, 2]));
2322        assert_eq!(values[4], set_lit(vec![1, 3]));
2323        assert_eq!(values[5], set_lit(vec![2, 3]));
2324    }
2325
2326    #[test]
2327    fn set_length_clamps_max_size_to_inner_domain() {
2328        // maxSize 3 of int(1..2) is effectively maxSize 2: 2^2 = 4 subsets.
2329        let dom = GroundDomain::Set(SetAttr::new_max_size(3), domain_int_ground!(1..2));
2330        assert_eq!(dom.length().unwrap(), 4);
2331    }
2332
2333    #[test]
2334    fn set_values_count_matches_length() {
2335        let dom = GroundDomain::Set(SetAttr::default(), domain_int_ground!(1..4));
2336        let count = dom.values().unwrap().count();
2337        let length = dom.length().unwrap();
2338        assert_eq!(count as u64, length);
2339    }
2340
2341    fn func_attr(partiality: PartialityAttr, jectivity: JectivityAttr) -> FuncAttr {
2342        FuncAttr {
2343            size: Range::Unbounded,
2344            partiality,
2345            jectivity,
2346        }
2347    }
2348
2349    #[test]
2350    fn total_bijective_function_length_is_factorial_of_the_shared_size() {
2351        let dom = GroundDomain::Function(
2352            func_attr(PartialityAttr::Total, JectivityAttr::Bijective),
2353            domain_int_ground!(1..3),
2354            domain_int_ground!(1..3),
2355        );
2356        assert_eq!(dom.length().unwrap(), 6); // 3!
2357    }
2358
2359    #[test]
2360    fn total_bijective_function_length_is_zero_for_mismatched_sizes() {
2361        let dom = GroundDomain::Function(
2362            func_attr(PartialityAttr::Total, JectivityAttr::Bijective),
2363            domain_int_ground!(1..3),
2364            domain_int_ground!(1..2),
2365        );
2366        assert_eq!(dom.length().unwrap(), 0);
2367    }
2368
2369    #[test]
2370    fn total_injective_function_length_is_a_falling_factorial() {
2371        // 2 domain elements injectively into 4 codomain elements: 4*3 = 12.
2372        let dom = GroundDomain::Function(
2373            func_attr(PartialityAttr::Total, JectivityAttr::Injective),
2374            domain_int_ground!(1..2),
2375            domain_int_ground!(1..4),
2376        );
2377        assert_eq!(dom.length().unwrap(), 12);
2378    }
2379
2380    #[test]
2381    fn total_surjective_function_length_uses_stirling_numbers() {
2382        // 3 domain elements onto 2 codomain elements: S(3,2)=3 partitions, * 2! = 6.
2383        let dom = GroundDomain::Function(
2384            func_attr(PartialityAttr::Total, JectivityAttr::Surjective),
2385            domain_int_ground!(1..3),
2386            domain_int_ground!(1..2),
2387        );
2388        assert_eq!(dom.length().unwrap(), 6);
2389    }
2390
2391    #[test]
2392    fn partial_injective_function_length_sums_over_defined_sizes() {
2393        // 3 domain elements, 2 codomain elements, up to 2 defined: sum over sz=0..=2 of
2394        // C(3,sz) * P(2,sz) = 1*1 + 3*2 + 3*2 = 13.
2395        let mut attr = func_attr(PartialityAttr::Partial, JectivityAttr::Injective);
2396        attr.size = Range::Bounded(0, 2);
2397        let dom = GroundDomain::Function(attr, domain_int_ground!(1..3), domain_int_ground!(1..2));
2398        assert_eq!(dom.length().unwrap(), 13);
2399    }
2400
2401    fn partition_attr(num_parts: Range<i32>, part_len: Range<i32>) -> PartitionAttr {
2402        PartitionAttr {
2403            num_parts,
2404            part_len,
2405            is_regular: false,
2406        }
2407    }
2408
2409    fn partition_lit(parts: Vec<Vec<i32>>) -> Literal {
2410        Literal::AbstractLiteral(AbstractLiteral::Partition(
2411            parts
2412                .into_iter()
2413                .map(|part| part.into_iter().map(Literal::Int).collect())
2414                .collect(),
2415        ))
2416    }
2417
2418    #[test]
2419    fn partition_contains_accepts_a_literal_whose_size_exactly_matches_num_parts_times_part_len() {
2420        // num_parts=2, part_len=2 => exactly 4 covered elements is valid.
2421        let dom = GroundDomain::Partition(
2422            partition_attr(Range::Single(2), Range::Single(2)),
2423            domain_int_ground!(1..6),
2424        );
2425        let lit = partition_lit(vec![vec![1, 2], vec![3, 4]]);
2426        assert!(
2427            dom.contains(&lit).unwrap(),
2428            "a 4-element partition literal should be a valid member of a \
2429             (numParts 2, partSize 2) domain"
2430        );
2431    }
2432
2433    #[test]
2434    fn partition_contains_rejects_a_literal_with_the_wrong_covered_size() {
2435        // num_parts=2, part_len=2 requires exactly 4 covered elements; 3 should be rejected.
2436        let dom = GroundDomain::Partition(
2437            partition_attr(Range::Single(2), Range::Single(2)),
2438            domain_int_ground!(1..6),
2439        );
2440        let lit = partition_lit(vec![vec![1, 2, 3]]);
2441        assert!(
2442            !dom.contains(&lit).unwrap(),
2443            "a 3-element partition literal should not be a valid member of a \
2444             (numParts 2, partSize 2) domain, which requires exactly 4 covered elements"
2445        );
2446    }
2447
2448    #[test]
2449    fn partition_contains_accepts_any_size_when_attributes_are_unbounded() {
2450        // Regression: an unattributed partition domain (Range::Unbounded for both num_parts and
2451        // part_len) must not reject every literal outright -- Range::Unbounded.contains() is
2452        // always true, which an inverted condition would misread as "always out of range".
2453        let dom = GroundDomain::Partition(
2454            partition_attr(Range::Unbounded, Range::Unbounded),
2455            domain_int_ground!(1..6),
2456        );
2457        let lit = partition_lit(vec![vec![1, 2], vec![3, 4, 5, 6]]);
2458        assert!(
2459            dom.contains(&lit).unwrap(),
2460            "an unattributed partition domain should accept a literal covering its whole inner \
2461             domain"
2462        );
2463    }
2464
2465    #[test]
2466    fn partition_length_unattributed_matches_the_bell_number() {
2467        // Bell(4) = 15: every way to partition a 4-element set, no size/count restriction.
2468        let dom = GroundDomain::Partition(
2469            partition_attr(Range::Unbounded, Range::Unbounded),
2470            domain_int_ground!(1..4),
2471        );
2472        assert_eq!(dom.length().unwrap(), 15);
2473    }
2474
2475    #[test]
2476    fn partition_length_fixed_num_parts_matches_stirling_second_kind() {
2477        // S(4, 2) = 7: partitioning 4 elements into exactly 2 unlabelled non-empty blocks, no
2478        // block-size restriction -- this is restricted_partition_count's block_min=1 case, which
2479        // should agree with stirling_second_kind exactly.
2480        let dom = GroundDomain::Partition(
2481            partition_attr(Range::Single(2), Range::Unbounded),
2482            domain_int_ground!(1..4),
2483        );
2484        assert_eq!(dom.length().unwrap(), stirling_second_kind(4, 2).unwrap());
2485        assert_eq!(dom.length().unwrap(), 7);
2486    }
2487
2488    #[test]
2489    fn partition_length_regular_fixed_block_size_matches_a_hand_computed_multinomial() {
2490        // 6 elements into regular blocks of size 3: 6! / (3!^2 * 2!) = 720 / 72 = 10.
2491        let mut attr = partition_attr(Range::Unbounded, Range::Single(3));
2492        attr.is_regular = true;
2493        let dom = GroundDomain::Partition(attr, domain_int_ground!(1..6));
2494        assert_eq!(dom.length().unwrap(), 10);
2495    }
2496
2497    #[test]
2498    fn partition_values_count_matches_length_and_every_value_is_a_valid_member() {
2499        let dom = GroundDomain::Partition(
2500            partition_attr(Range::Bounded(2, 3), Range::Unbounded),
2501            domain_int_ground!(1..4),
2502        );
2503        let values: Vec<Literal> = dom.values().unwrap().collect();
2504        assert_eq!(values.len() as u64, dom.length().unwrap());
2505        for value in &values {
2506            assert!(dom.contains(value).unwrap());
2507        }
2508    }
2509
2510    fn permutation_attr(num_moved: Range<i32>) -> PermutationAttr {
2511        PermutationAttr { num_moved }
2512    }
2513
2514    #[test]
2515    fn permutation_length_unattributed_matches_factorial() {
2516        // 4! = 24: every bijection of a 4-element set onto itself, no numMoved restriction.
2517        let dom =
2518            GroundDomain::Permutation(permutation_attr(Range::Unbounded), domain_int_ground!(1..4));
2519        assert_eq!(dom.length().unwrap(), 24);
2520    }
2521
2522    #[test]
2523    fn permutation_length_fully_moved_matches_the_derangement_number() {
2524        // D(4) = 9: permutations of 4 elements with no fixed points at all.
2525        let dom =
2526            GroundDomain::Permutation(permutation_attr(Range::Single(4)), domain_int_ground!(1..4));
2527        assert_eq!(dom.length().unwrap(), 9);
2528    }
2529
2530    #[test]
2531    fn permutation_values_count_matches_length_and_every_value_is_a_valid_member() {
2532        let dom = GroundDomain::Permutation(
2533            permutation_attr(Range::Bounded(1, 2)),
2534            domain_int_ground!(1..3),
2535        );
2536        let values: Vec<Literal> = dom.values().unwrap().collect();
2537        assert_eq!(values.len() as u64, dom.length().unwrap());
2538        for value in &values {
2539            assert!(dom.contains(value).unwrap());
2540        }
2541    }
2542}