1use crate::ast::pretty::pretty_vec;
2use crate::ast::{
3 AbstractLiteral, Domain, DomainOpError, FuncAttr, HasDomain, Literal, Moo, RecordEntry,
4 SetAttr, Typeable,
5 domains::{domain::Int, range::Range},
6};
7use crate::range;
8use crate::utils::count_combinations;
9use conjure_cp_core::ast::{Name, ReturnType};
10use itertools::{Itertools, izip};
11use num_traits::ToPrimitive;
12use polyquine::Quine;
13use serde::{Deserialize, Serialize};
14use std::collections::BTreeSet;
15use std::fmt::{Display, Formatter};
16use std::iter::zip;
17use uniplate::Uniplate;
18
19#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, Uniplate, Quine)]
20#[path_prefix(conjure_cp::ast)]
21pub struct RecordEntryGround {
22 pub name: Name,
23 pub domain: Moo<GroundDomain>,
24}
25
26impl From<RecordEntryGround> for RecordEntry {
27 fn from(value: RecordEntryGround) -> Self {
28 RecordEntry {
29 name: value.name,
30 domain: value.domain.into(),
31 }
32 }
33}
34
35impl TryFrom<RecordEntry> for RecordEntryGround {
36 type Error = DomainOpError;
37
38 fn try_from(value: RecordEntry) -> Result<Self, Self::Error> {
39 match value.domain.as_ref() {
40 Domain::Ground(gd) => Ok(RecordEntryGround {
41 name: value.name,
42 domain: gd.clone(),
43 }),
44 Domain::Unresolved(_) => Err(DomainOpError::NotGround),
45 }
46 }
47}
48
49#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, Quine, Uniplate)]
50#[path_prefix(conjure_cp::ast)]
51pub enum GroundDomain {
52 Empty(ReturnType),
54 Bool,
56 Int(Vec<Range<Int>>),
58 Set(SetAttr<Int>, Moo<GroundDomain>),
60 Matrix(Moo<GroundDomain>, Vec<Moo<GroundDomain>>),
63 Tuple(Vec<Moo<GroundDomain>>),
65 Record(Vec<RecordEntryGround>),
67 Function(FuncAttr, Moo<GroundDomain>, Moo<GroundDomain>),
69}
70
71impl GroundDomain {
72 pub fn union(&self, other: &GroundDomain) -> Result<GroundDomain, DomainOpError> {
73 match (self, other) {
74 (GroundDomain::Empty(ty), dom) | (dom, GroundDomain::Empty(ty)) => {
75 if *ty == dom.return_type() {
76 Ok(dom.clone())
77 } else {
78 Err(DomainOpError::WrongType)
79 }
80 }
81 (GroundDomain::Bool, GroundDomain::Bool) => Ok(GroundDomain::Bool),
82 (GroundDomain::Bool, _) | (_, GroundDomain::Bool) => Err(DomainOpError::WrongType),
83 (GroundDomain::Int(r1), GroundDomain::Int(r2)) => {
84 let mut rngs = r1.clone();
85 rngs.extend(r2.clone());
86 Ok(GroundDomain::Int(Range::squeeze(&rngs)))
87 }
88 (GroundDomain::Int(_), _) | (_, GroundDomain::Int(_)) => Err(DomainOpError::WrongType),
89 (GroundDomain::Set(_, in1), GroundDomain::Set(_, in2)) => Ok(GroundDomain::Set(
90 SetAttr::default(),
91 Moo::new(in1.union(in2)?),
92 )),
93 (GroundDomain::Set(_, _), _) | (_, GroundDomain::Set(_, _)) => {
94 Err(DomainOpError::WrongType)
95 }
96 (GroundDomain::Matrix(in1, idx1), GroundDomain::Matrix(in2, idx2)) if idx1 == idx2 => {
97 Ok(GroundDomain::Matrix(
98 Moo::new(in1.union(in2)?),
99 idx1.clone(),
100 ))
101 }
102 (GroundDomain::Matrix(_, _), _) | (_, GroundDomain::Matrix(_, _)) => {
103 Err(DomainOpError::WrongType)
104 }
105 (GroundDomain::Tuple(in1s), GroundDomain::Tuple(in2s)) if in1s.len() == in2s.len() => {
106 let mut inners = Vec::new();
107 for (in1, in2) in zip(in1s, in2s) {
108 inners.push(Moo::new(in1.union(in2)?));
109 }
110 Ok(GroundDomain::Tuple(inners))
111 }
112 (GroundDomain::Tuple(_), _) | (_, GroundDomain::Tuple(_)) => {
113 Err(DomainOpError::WrongType)
114 }
115 #[allow(unreachable_patterns)]
117 (GroundDomain::Record(_), _) | (_, GroundDomain::Record(_)) => {
119 Err(DomainOpError::WrongType)
120 }
121 #[allow(unreachable_patterns)]
122 (GroundDomain::Function(_, _, _), _) | (_, GroundDomain::Function(_, _, _)) => {
124 Err(DomainOpError::WrongType)
125 }
126 }
127 }
128
129 pub fn intersect(&self, other: &GroundDomain) -> Result<GroundDomain, DomainOpError> {
136 match (self, other) {
140 (d @ GroundDomain::Empty(ReturnType::Int), GroundDomain::Int(_)) => Ok(d.clone()),
142 (GroundDomain::Int(_), d @ GroundDomain::Empty(ReturnType::Int)) => Ok(d.clone()),
143 (GroundDomain::Empty(ReturnType::Int), d @ GroundDomain::Empty(ReturnType::Int)) => {
144 Ok(d.clone())
145 }
146
147 (GroundDomain::Set(_, inner1), d @ GroundDomain::Empty(ReturnType::Set(inner2)))
149 if matches!(
150 **inner1,
151 GroundDomain::Int(_) | GroundDomain::Empty(ReturnType::Int)
152 ) && matches!(**inner2, ReturnType::Int) =>
153 {
154 Ok(d.clone())
155 }
156 (d @ GroundDomain::Empty(ReturnType::Set(inner1)), GroundDomain::Set(_, inner2))
157 if matches!(**inner1, ReturnType::Int)
158 && matches!(
159 **inner2,
160 GroundDomain::Int(_) | GroundDomain::Empty(ReturnType::Int)
161 ) =>
162 {
163 Ok(d.clone())
164 }
165 (
166 d @ GroundDomain::Empty(ReturnType::Set(inner1)),
167 GroundDomain::Empty(ReturnType::Set(inner2)),
168 ) if matches!(**inner1, ReturnType::Int) && matches!(**inner2, ReturnType::Int) => {
169 Ok(d.clone())
170 }
171
172 (GroundDomain::Set(_, x), GroundDomain::Set(_, y)) => Ok(GroundDomain::Set(
174 SetAttr::default(),
175 Moo::new((*x).intersect(y)?),
176 )),
177
178 (GroundDomain::Int(_), GroundDomain::Int(_)) => {
179 let mut v: BTreeSet<i32> = BTreeSet::new();
180
181 let v1 = self.values_i32()?;
182 let v2 = other.values_i32()?;
183 for value1 in v1.iter() {
184 if v2.contains(value1) && !v.contains(value1) {
185 v.insert(*value1);
186 }
187 }
188 Ok(GroundDomain::from_set_i32(&v))
189 }
190 _ => Err(DomainOpError::WrongType),
191 }
192 }
193
194 pub fn values(&self) -> Result<Box<dyn Iterator<Item = Literal>>, DomainOpError> {
195 match self {
196 GroundDomain::Empty(_) => Ok(Box::new(vec![].into_iter())),
197 GroundDomain::Bool => Ok(Box::new(
198 vec![Literal::from(true), Literal::from(false)].into_iter(),
199 )),
200 GroundDomain::Int(rngs) => {
201 let rng_iters = rngs
202 .iter()
203 .map(Range::iter)
204 .collect::<Option<Vec<_>>>()
205 .ok_or(DomainOpError::Unbounded)?;
206 Ok(Box::new(
207 rng_iters.into_iter().flat_map(|ri| ri.map(Literal::from)),
208 ))
209 }
210 _ => todo!("Enumerating nested domains is not yet supported"),
211 }
212 }
213
214 pub fn length(&self) -> Result<u64, DomainOpError> {
220 match self {
221 GroundDomain::Empty(_) => Ok(0),
222 GroundDomain::Bool => Ok(2),
223 GroundDomain::Int(ranges) => {
224 if ranges.is_empty() {
225 return Err(DomainOpError::Unbounded);
226 }
227
228 let mut length = 0u64;
229 for range in ranges {
230 if let Some(range_length) = range.length() {
231 length += range_length as u64;
232 } else {
233 return Err(DomainOpError::Unbounded);
234 }
235 }
236 Ok(length)
237 }
238 GroundDomain::Set(set_attr, inner_domain) => {
239 let inner_len = inner_domain.length()?;
240 let (min_sz, max_sz) = match set_attr.size {
241 Range::Unbounded => (0, inner_len),
242 Range::Single(n) => (n as u64, n as u64),
243 Range::UnboundedR(n) => (n as u64, inner_len),
244 Range::UnboundedL(n) => (0, n as u64),
245 Range::Bounded(min, max) => (min as u64, max as u64),
246 };
247 let mut ans = 0u64;
248 for sz in min_sz..=max_sz {
249 let c = count_combinations(inner_len, sz)?;
250 ans = ans.checked_add(c).ok_or(DomainOpError::TooLarge)?;
251 }
252 Ok(ans)
253 }
254 GroundDomain::Tuple(domains) => {
255 let mut ans = 1u64;
256 for domain in domains {
257 ans = ans
258 .checked_mul(domain.length()?)
259 .ok_or(DomainOpError::TooLarge)?;
260 }
261 Ok(ans)
262 }
263 GroundDomain::Record(entries) => {
264 let mut ans = 1u64;
266 for entry in entries {
267 let sz = entry.domain.length()?;
268 ans = ans.checked_mul(sz).ok_or(DomainOpError::TooLarge)?;
269 }
270 Ok(ans)
271 }
272 GroundDomain::Matrix(inner_domain, idx_domains) => {
273 let inner_sz = inner_domain.length()?;
274 let exp = idx_domains.iter().try_fold(1u32, |acc, val| {
275 let len = val.length()? as u32;
276 acc.checked_mul(len).ok_or(DomainOpError::TooLarge)
277 })?;
278 inner_sz.checked_pow(exp).ok_or(DomainOpError::TooLarge)
279 }
280 GroundDomain::Function(_, _, _) => {
281 todo!("Length bound of functions is not yet supported")
282 }
283 }
284 }
285
286 pub fn contains(&self, lit: &Literal) -> Result<bool, DomainOpError> {
287 match self {
290 GroundDomain::Empty(_) => Ok(false),
292 GroundDomain::Bool => match lit {
293 Literal::Bool(_) => Ok(true),
294 _ => Ok(false),
295 },
296 GroundDomain::Int(ranges) => match lit {
297 Literal::Int(x) => {
298 if ranges.is_empty() {
300 return Ok(true);
301 };
302
303 Ok(ranges.iter().any(|range| range.contains(x)))
304 }
305 _ => Ok(false),
306 },
307 GroundDomain::Set(set_attr, inner_domain) => match lit {
308 Literal::AbstractLiteral(AbstractLiteral::Set(lit_elems)) => {
309 let sz = lit_elems.len().to_i32().ok_or(DomainOpError::TooLarge)?;
311 if !set_attr.size.contains(&sz) {
312 return Ok(false);
313 }
314
315 for elem in lit_elems {
316 if !inner_domain.contains(elem)? {
317 return Ok(false);
318 }
319 }
320 Ok(true)
321 }
322 _ => Ok(false),
323 },
324 GroundDomain::Matrix(elem_domain, index_domains) => {
325 match lit {
326 Literal::AbstractLiteral(AbstractLiteral::Matrix(elems, idx_domain)) => {
327 let mut index_domains = index_domains.clone();
331 if index_domains
332 .pop()
333 .expect("a matrix should have at least one index domain")
334 != *idx_domain
335 {
336 return Ok(false);
337 };
338
339 let next_elem_domain = if index_domains.is_empty() {
340 elem_domain.as_ref().clone()
343 } else {
344 GroundDomain::Matrix(elem_domain.clone(), index_domains)
346 };
347
348 for elem in elems {
349 if !next_elem_domain.contains(elem)? {
350 return Ok(false);
351 }
352 }
353
354 Ok(true)
355 }
356 _ => Ok(false),
357 }
358 }
359 GroundDomain::Tuple(elem_domains) => {
360 match lit {
361 Literal::AbstractLiteral(AbstractLiteral::Tuple(literal_elems)) => {
362 if elem_domains.len() != literal_elems.len() {
363 return Ok(false);
364 }
365
366 for (elem_domain, elem) in itertools::izip!(elem_domains, literal_elems) {
368 if !elem_domain.contains(elem)? {
369 return Ok(false);
370 }
371 }
372
373 Ok(true)
374 }
375 _ => Ok(false),
376 }
377 }
378 GroundDomain::Record(entries) => match lit {
379 Literal::AbstractLiteral(AbstractLiteral::Record(lit_entries)) => {
380 if entries.len() != lit_entries.len() {
381 return Ok(false);
382 }
383
384 for (entry, lit_entry) in itertools::izip!(entries, lit_entries) {
385 if entry.name != lit_entry.name
386 || !(entry.domain.contains(&lit_entry.value)?)
387 {
388 return Ok(false);
389 }
390 }
391 Ok(true)
392 }
393 _ => Ok(false),
394 },
395 GroundDomain::Function(func_attr, domain, codomain) => match lit {
396 Literal::AbstractLiteral(AbstractLiteral::Function(lit_elems)) => {
397 let sz = Int::try_from(lit_elems.len()).expect("Should convert");
398 if !func_attr.size.contains(&sz) {
399 return Ok(false);
400 }
401 for lit in lit_elems {
402 let domain_element = &lit.0;
403 let codomain_element = &lit.1;
404 if !domain.contains(domain_element)? {
405 return Ok(false);
406 }
407 if !codomain.contains(codomain_element)? {
408 return Ok(false);
409 }
410 }
411 Ok(true)
412 }
413 _ => Ok(false),
414 },
415 }
416 }
417
418 pub fn values_i32(&self) -> Result<Vec<i32>, DomainOpError> {
425 if let GroundDomain::Empty(ReturnType::Int) = self {
426 return Ok(vec![]);
427 }
428 let GroundDomain::Int(ranges) = self else {
429 return Err(DomainOpError::NotInteger(self.return_type()));
430 };
431
432 if ranges.is_empty() {
433 return Err(DomainOpError::Unbounded);
434 }
435
436 let mut values = vec![];
437 for range in ranges {
438 match range {
439 Range::Single(i) => {
440 values.push(*i);
441 }
442 Range::Bounded(i, j) => {
443 values.extend(*i..=*j);
444 }
445 Range::UnboundedR(_) | Range::UnboundedL(_) | Range::Unbounded => {
446 return Err(DomainOpError::Unbounded);
447 }
448 }
449 }
450
451 Ok(values)
452 }
453
454 pub fn from_set_i32(elements: &BTreeSet<i32>) -> GroundDomain {
493 if elements.is_empty() {
494 return GroundDomain::Empty(ReturnType::Int);
495 }
496 if elements.len() == 1 {
497 return GroundDomain::Int(vec![Range::Single(*elements.first().unwrap())]);
498 }
499
500 let mut elems_iter = elements.iter().copied();
501
502 let mut ranges: Vec<Range<i32>> = vec![];
503
504 let mut lower = elems_iter
509 .next()
510 .expect("if we get here, elements should have => 2 elements");
511 let mut upper = lower;
512
513 for current in elems_iter {
514 if current == upper + 1 {
517 upper = current;
520 } else {
521 if lower == upper {
526 ranges.push(range!(lower));
527 } else {
528 ranges.push(range!(lower..upper));
529 }
530
531 lower = current;
532 upper = current;
533 }
534 }
535
536 if lower == upper {
538 ranges.push(range!(lower));
539 } else {
540 ranges.push(range!(lower..upper));
541 }
542
543 ranges = Range::squeeze(&ranges);
544 GroundDomain::Int(ranges)
545 }
546
547 pub fn apply_i32(
557 &self,
558 op: fn(i32, i32) -> Option<i32>,
559 other: &GroundDomain,
560 ) -> Result<GroundDomain, DomainOpError> {
561 let vs1 = self.values_i32()?;
562 let vs2 = other.values_i32()?;
563
564 let mut set = BTreeSet::new();
565 for (v1, v2) in itertools::iproduct!(vs1, vs2) {
566 if let Some(v) = op(v1, v2) {
567 set.insert(v);
568 }
569 }
570
571 Ok(GroundDomain::from_set_i32(&set))
572 }
573
574 pub fn is_finite(&self) -> bool {
576 for domain in self.universe() {
577 if let GroundDomain::Int(ranges) = domain {
578 if ranges.is_empty() {
579 return false;
580 }
581
582 if ranges.iter().any(|range| {
583 matches!(
584 range,
585 Range::UnboundedL(_) | Range::UnboundedR(_) | Range::Unbounded
586 )
587 }) {
588 return false;
589 }
590 }
591 }
592 true
593 }
594
595 pub fn from_literal_vec(literals: &[Literal]) -> Result<GroundDomain, DomainOpError> {
676 if literals.is_empty() {
679 return Ok(GroundDomain::Empty(ReturnType::Unknown));
680 }
681
682 let first_literal = literals.first().unwrap();
683
684 match first_literal {
685 Literal::Int(_) => {
686 let mut ints = BTreeSet::new();
688 for lit in literals {
689 let Literal::Int(i) = lit else {
690 return Err(DomainOpError::WrongType);
691 };
692
693 ints.insert(*i);
694 }
695
696 Ok(GroundDomain::from_set_i32(&ints))
697 }
698 Literal::Bool(_) => {
699 if literals.iter().any(|x| !matches!(x, Literal::Bool(_))) {
701 Err(DomainOpError::WrongType)
702 } else {
703 Ok(GroundDomain::Bool)
704 }
705 }
706 Literal::AbstractLiteral(AbstractLiteral::Set(_)) => {
707 let mut all_elems = vec![];
708
709 for lit in literals {
710 let Literal::AbstractLiteral(AbstractLiteral::Set(elems)) = lit else {
711 return Err(DomainOpError::WrongType);
712 };
713
714 all_elems.extend(elems.clone());
715 }
716 let elem_domain = GroundDomain::from_literal_vec(&all_elems)?;
717
718 Ok(GroundDomain::Set(SetAttr::default(), Moo::new(elem_domain)))
719 }
720
721 l @ Literal::AbstractLiteral(AbstractLiteral::Matrix(_, _)) => {
722 let mut first_index_domain = vec![];
723 let mut l = l.clone();
725 while let Literal::AbstractLiteral(AbstractLiteral::Matrix(elems, idx)) = l {
726 assert!(
727 !matches!(idx.as_ref(), GroundDomain::Matrix(_, _)),
728 "n-dimensional matrix literals should be represented as a matrix inside a matrix"
729 );
730 first_index_domain.push(idx);
731 l = elems[0].clone();
732 }
733
734 let mut all_elems: Vec<Literal> = vec![];
735
736 for lit in literals {
738 let Literal::AbstractLiteral(AbstractLiteral::Matrix(elems, idx)) = lit else {
739 return Err(DomainOpError::NotGround);
740 };
741
742 all_elems.extend(elems.clone());
743
744 let mut index_domain = vec![idx.clone()];
745 let mut l = elems[0].clone();
746 while let Literal::AbstractLiteral(AbstractLiteral::Matrix(elems, idx)) = l {
747 assert!(
748 !matches!(idx.as_ref(), GroundDomain::Matrix(_, _)),
749 "n-dimensional matrix literals should be represented as a matrix inside a matrix"
750 );
751 index_domain.push(idx);
752 l = elems[0].clone();
753 }
754
755 if index_domain != first_index_domain {
756 return Err(DomainOpError::WrongType);
757 }
758 }
759
760 let mut terminal_elements: Vec<Literal> = vec![];
762 while let Some(elem) = all_elems.pop() {
763 if let Literal::AbstractLiteral(AbstractLiteral::Matrix(elems, _)) = elem {
764 all_elems.extend(elems);
765 } else {
766 terminal_elements.push(elem);
767 }
768 }
769
770 let element_domain = GroundDomain::from_literal_vec(&terminal_elements)?;
771
772 Ok(GroundDomain::Matrix(
773 Moo::new(element_domain),
774 first_index_domain,
775 ))
776 }
777
778 Literal::AbstractLiteral(AbstractLiteral::Tuple(first_elems)) => {
779 let n_fields = first_elems.len();
780
781 let mut elem_domains = vec![];
783
784 for i in 0..n_fields {
785 let mut all_elems = vec![];
786 for lit in literals {
787 let Literal::AbstractLiteral(AbstractLiteral::Tuple(elems)) = lit else {
788 return Err(DomainOpError::NotGround);
789 };
790
791 if elems.len() != n_fields {
792 return Err(DomainOpError::NotGround);
793 }
794
795 all_elems.push(elems[i].clone());
796 }
797
798 elem_domains.push(Moo::new(GroundDomain::from_literal_vec(&all_elems)?));
799 }
800
801 Ok(GroundDomain::Tuple(elem_domains))
802 }
803
804 Literal::AbstractLiteral(AbstractLiteral::Record(first_elems)) => {
805 let n_fields = first_elems.len();
806 let field_names = first_elems.iter().map(|x| x.name.clone()).collect_vec();
807
808 let mut elem_domains = vec![];
810
811 for i in 0..n_fields {
812 let mut all_elems = vec![];
813 for lit in literals {
814 let Literal::AbstractLiteral(AbstractLiteral::Record(elems)) = lit else {
815 return Err(DomainOpError::NotGround);
816 };
817
818 if elems.len() != n_fields {
819 return Err(DomainOpError::NotGround);
820 }
821
822 let elem = elems[i].clone();
823 if elem.name != field_names[i] {
824 return Err(DomainOpError::NotGround);
825 }
826
827 all_elems.push(elem.value);
828 }
829
830 elem_domains.push(Moo::new(GroundDomain::from_literal_vec(&all_elems)?));
831 }
832
833 Ok(GroundDomain::Record(
834 izip!(field_names, elem_domains)
835 .map(|(name, domain)| RecordEntryGround { name, domain })
836 .collect(),
837 ))
838 }
839 Literal::AbstractLiteral(AbstractLiteral::Function(items)) => {
840 if items.is_empty() {
841 return Err(DomainOpError::NotGround);
842 }
843
844 let (x1, y1) = &items[0];
845 let d1 = x1.domain_of();
846 let d1 = d1.as_ground().ok_or(DomainOpError::NotGround)?;
847 let d2 = y1.domain_of();
848 let d2 = d2.as_ground().ok_or(DomainOpError::NotGround)?;
849
850 for (x, y) in items {
852 let dx = x.domain_of();
853 let dx = dx.as_ground().ok_or(DomainOpError::NotGround)?;
854
855 let dy = y.domain_of();
856 let dy = dy.as_ground().ok_or(DomainOpError::NotGround)?;
857
858 if (dx != d1) || (dy != d2) {
859 return Err(DomainOpError::WrongType);
860 }
861 }
862
863 todo!();
864 }
865 }
866 }
867}
868
869impl Typeable for GroundDomain {
870 fn return_type(&self) -> ReturnType {
871 match self {
872 GroundDomain::Empty(ty) => ty.clone(),
873 GroundDomain::Bool => ReturnType::Bool,
874 GroundDomain::Int(_) => ReturnType::Int,
875 GroundDomain::Set(_attr, inner) => ReturnType::Set(Box::new(inner.return_type())),
876 GroundDomain::Matrix(inner, _idx) => ReturnType::Matrix(Box::new(inner.return_type())),
877 GroundDomain::Tuple(inners) => {
878 let mut inner_types = Vec::new();
879 for inner in inners {
880 inner_types.push(inner.return_type());
881 }
882 ReturnType::Tuple(inner_types)
883 }
884 GroundDomain::Record(entries) => {
885 let mut entry_types = Vec::new();
886 for entry in entries {
887 entry_types.push(entry.domain.return_type());
888 }
889 ReturnType::Record(entry_types)
890 }
891 GroundDomain::Function(_, dom, cdom) => {
892 ReturnType::Function(Box::new(dom.return_type()), Box::new(cdom.return_type()))
893 }
894 }
895 }
896}
897
898impl Display for GroundDomain {
899 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
900 match &self {
901 GroundDomain::Empty(ty) => write!(f, "empty({ty:?})"),
902 GroundDomain::Bool => write!(f, "bool"),
903 GroundDomain::Int(ranges) => {
904 if ranges.iter().all(Range::is_lower_or_upper_bounded) {
905 let rngs: String = ranges.iter().map(|r| format!("{r}")).join(", ");
906 write!(f, "int({})", rngs)
907 } else {
908 write!(f, "int")
909 }
910 }
911 GroundDomain::Set(attrs, inner_dom) => write!(f, "set {attrs} of {inner_dom}"),
912 GroundDomain::Matrix(value_domain, index_domains) => {
913 write!(
914 f,
915 "matrix indexed by [{}] of {value_domain}",
916 pretty_vec(&index_domains.iter().collect_vec())
917 )
918 }
919 GroundDomain::Tuple(domains) => {
920 write!(
921 f,
922 "tuple of ({})",
923 pretty_vec(&domains.iter().collect_vec())
924 )
925 }
926 GroundDomain::Record(entries) => {
927 write!(
928 f,
929 "record of ({})",
930 pretty_vec(
931 &entries
932 .iter()
933 .map(|entry| format!("{}: {}", entry.name, entry.domain))
934 .collect_vec()
935 )
936 )
937 }
938 GroundDomain::Function(attribute, domain, codomain) => {
939 write!(f, "function {} {} --> {} ", attribute, domain, codomain)
940 }
941 }
942 }
943}