1
//! Domain-calculation helpers for expressions.
2
//!
3
//! Expression dispatch and its metadata cache live in `expressions`; arithmetic,
4
//! matrix and collection bounds are calculated here without expanding integer domains.
5

            
6
use std::collections::BTreeSet;
7

            
8
use super::collection_domains::{SizeBounds, min_known, mset_size};
9
use super::comprehension::ComprehensionQualifier;
10
use super::eval::factorial_i32;
11
use super::{
12
    Atom, Domain, DomainPtr, Expression, GroundDomain, HasDomain, MSetAttr, Moo, Range, ReturnType,
13
    Typeable, UnresolvedDomain,
14
};
15

            
16
// for the given matrix literal, return a bounded domain from the min to max of applying op to each
17
// child expression.
18
//
19
// Op must be monotonic.
20
//
21
// Returns none if unbounded
22
19628280
pub(super) fn bounded_i32_domain_for_matrix_literal_monotonic(
23
19628280
    e: &Expression,
24
19628280
    op: fn(i32, i32) -> Option<i32>,
25
19628280
) -> Option<DomainPtr> {
26
    // only care about the elements, not the indices
27
19628280
    let (mut exprs, _) = e.clone().unwrap_matrix_unchecked()?;
28
    //
29
    // here, I assume that op is monotone. This means that the bounds of op([a1,a2],[b1,b2])  for
30
    // the ranges [a1,a2], [b1,b2] will be
31
    // [min(op(a1,b1),op(a2,b1),op(a1,b2),op(a2,b2)),max(op(a1,b1),op(a2,b1),op(a1,b2),op(a2,b2))].
32
    //
33
    // We used to not assume this, and work out the bounds by applying op on the Cartesian product
34
    // of A and B; however, this caused a combinatorial explosion and my computer to run out of
35
    // memory (on the hakank_eprime_xkcd test)...
36
    //Int
37
    // For example, to find the bounds of the intervals [1,4], [1,5] combined using op, we used to do
38
    //  [min(op(1,1), op(1,2),op(1,3),op(1,4),op(1,5),op(2,1)..
39
    //
40
    // +,-,/,* are all monotone, so this assumption should be fine for now...
41

            
42
19426272
    let expr = exprs.pop()?;
43
19426271
    let dom = expr.domain_of()?;
44
19424705
    let resolved = dom.resolve().ok()?;
45
19417484
    let GroundDomain::Int(ranges, _) = resolved.as_ref() else {
46
4496
        return None;
47
    };
48

            
49
19412988
    let (mut current_min, mut current_max) = range_vec_bounds_i32(ranges)?;
50

            
51
19410755
    for expr in exprs {
52
43658896
        let dom = expr.domain_of()?;
53
43657901
        let resolved = dom.resolve().ok()?;
54
43657549
        let GroundDomain::Int(ranges, _) = resolved.as_ref() else {
55
99
            return None;
56
        };
57

            
58
43657450
        let (min, max) = range_vec_bounds_i32(ranges)?;
59

            
60
        // all the possible new values for current_min / current_max
61
43657450
        let minmax = op(min, current_max)?;
62
43657450
        let minmin = op(min, current_min)?;
63
43657450
        let maxmin = op(max, current_min)?;
64
43657450
        let maxmax = op(max, current_max)?;
65
43657449
        let vals = [minmax, minmin, maxmin, maxmax];
66

            
67
43657449
        current_min = *vals
68
43657449
            .iter()
69
43657449
            .min()
70
43657449
            .expect("vals iterator should not be empty, and should have a minimum.");
71
43657449
        current_max = *vals
72
43657449
            .iter()
73
43657449
            .max()
74
43657449
            .expect("vals iterator should not be empty, and should have a maximum.");
75
    }
76

            
77
19409308
    if current_min == current_max {
78
72327
        Some(Domain::int(vec![Range::Single(current_min)]))
79
    } else {
80
19336981
        Some(Domain::int(vec![Range::Bounded(current_min, current_max)]))
81
    }
82
19628280
}
83

            
84
4229
pub(super) fn matrix_element_domain(e: &Expression) -> Option<DomainPtr> {
85
4229
    let (elem_domain, _) = e.domain_of()?.as_matrix()?;
86
4229
    elem_domain.as_ref().as_int()?;
87
4229
    Some(elem_domain)
88
4229
}
89

            
90
455150
pub(super) fn empty_matrix_integer_element_domain(e: &Expression) -> Option<DomainPtr> {
91
455150
    match e {
92
        Expression::TypeAnnotation(_, inner, domain) => {
93
17110
            if !Moo::unwrap_or_clone(inner.clone())
94
17110
                .unwrap_matrix_unchecked()
95
17110
                .is_some_and(|(elems, _)| elems.is_empty())
96
            {
97
                return None;
98
17110
            }
99
17110
            if let ReturnType::Matrix(elem_type) = domain.return_type()
100
17110
                && elem_type.as_ref() == &ReturnType::Int
101
            {
102
17110
                Some(Domain::int_ground(vec![Range::Unbounded]))
103
            } else {
104
                None
105
            }
106
        }
107
        Expression::DomainAnnotation(_, inner, domain) => {
108
4205
            if !Moo::unwrap_or_clone(inner.clone())
109
4205
                .unwrap_matrix_unchecked()
110
4205
                .is_some_and(|(elems, _)| elems.is_empty())
111
            {
112
                return None;
113
4205
            }
114
4205
            let (elem_domain, _) = domain.as_matrix()?;
115
4205
            elem_domain.as_ref().as_int()?;
116
4205
            Some(elem_domain)
117
        }
118
        _ => {
119
433835
            if !e
120
433835
                .clone()
121
433835
                .unwrap_matrix_unchecked()
122
429606
                .is_some_and(|(elems, _)| elems.is_empty())
123
            {
124
433835
                return None;
125
            }
126
            matrix_element_domain(e)
127
        }
128
    }
129
455150
}
130

            
131
// Returns none if unbounded
132
90808558
fn range_vec_bounds_i32(ranges: &Vec<Range<i32>>) -> Option<(i32, i32)> {
133
90808558
    let mut min = i32::MAX;
134
90808558
    let mut max = i32::MIN;
135
90808558
    for r in ranges {
136
93765669
        match r {
137
            Range::Single(i) => {
138
16838214
                if *i < min {
139
13929683
                    min = *i;
140
2908531
                }
141
16838214
                if *i > max {
142
16814545
                    max = *i;
143
23669
                }
144
            }
145
            Range::Bounded(i, j) => {
146
76925218
                if *i < min {
147
76882812
                    min = *i;
148
42406
                }
149
76925218
                if *j > max {
150
76925218
                    max = *j;
151
                }
152
            }
153
2237
            Range::UnboundedR(_) | Range::UnboundedL(_) | Range::Unbounded => return None,
154
        }
155
    }
156
90806321
    Some((min, max))
157
90808558
}
158

            
159
/// Integer ranges without expanding their values. Typed empty domains are
160
/// accepted so arithmetic over an empty operand remains empty.
161
14351864
fn finite_integer_ranges(expr: &Expression) -> Option<Vec<Range<i32>>> {
162
14351864
    let domain = expr.domain_of()?.resolve().ok()?;
163
14351860
    match domain.as_ref() {
164
14351849
        GroundDomain::Int(ranges, _) if ranges.iter().all(Range::is_finite) => Some(ranges.clone()),
165
11
        GroundDomain::Empty(ReturnType::Int) => Some(Vec::new()),
166
        _ => None,
167
    }
168
14351864
}
169

            
170
/// Bounds for operations whose extrema occur at the corners of two intervals.
171
/// Overflow means we cannot supply a sound i32 domain, rather than silently
172
/// omitting values or wrapping an endpoint.
173
1101339
pub(super) fn binary_integer_domain(
174
1101339
    a: &Expression,
175
1101339
    b: &Expression,
176
1101339
    op: fn(i32, i32) -> Option<i32>,
177
1101339
) -> Option<DomainPtr> {
178
1101339
    let a = finite_integer_ranges(a)?;
179
1101339
    let b = finite_integer_ranges(b)?;
180
1101339
    if a.is_empty() || b.is_empty() {
181
6
        return Some(Domain::empty(ReturnType::Int));
182
1101333
    }
183
1101333
    let (a_min, a_max) = range_vec_bounds_i32(&a)?;
184
1101333
    let (b_min, b_max) = range_vec_bounds_i32(&b)?;
185
    let corners = [
186
1101333
        op(a_min, b_min)?,
187
1101330
        op(a_min, b_max)?,
188
1101330
        op(a_max, b_min)?,
189
1101330
        op(a_max, b_max)?,
190
    ];
191
1101330
    Some(Domain::int(vec![Range::new(
192
1101330
        corners.iter().min().copied(),
193
1101330
        corners.iter().max().copied(),
194
1101330
    )]))
195
1101339
}
196

            
197
/// Absolute value maps each input interval to one interval. Retain holes without
198
/// enumerating the integers, and decline inference if abs(MIN) would overflow.
199
304590
pub(super) fn absolute_integer_domain(a: &Expression) -> Option<DomainPtr> {
200
304590
    let mut result = Vec::new();
201
304590
    for range in finite_integer_ranges(a)? {
202
304591
        let (lo, hi) = (*range.low()?, *range.high()?);
203
304591
        let (a, b) = (lo.checked_abs()?, hi.checked_abs()?);
204
304590
        let lower = if lo <= 0 && hi >= 0 { 0 } else { a.min(b) };
205
304590
        result.push(Range::new(Some(lower), Some(a.max(b))));
206
    }
207
304589
    if result.is_empty() {
208
1
        return Some(Domain::empty(ReturnType::Int));
209
304588
    }
210
304588
    result.sort_unstable_by_key(|range| range.low().copied());
211
304588
    let mut merged: Vec<Range<i32>> = Vec::new();
212
304588
    for range in result {
213
304590
        if let Some(last) = merged.last_mut()
214
2
            && let Some(joined) = last.join(&range)
215
        {
216
1
            *last = joined;
217
        } else {
218
304589
            merged.push(range);
219
        }
220
    }
221
304588
    Some(Domain::int(merged))
222
304590
}
223

            
224
/// Floor division is monotonic on each side of zero. Only numerator endpoints
225
/// and the endpoints of each nonzero divisor interval can be extrema. Widen the
226
/// quotient calculation so MIN / -1 does not panic; out-of-i32 results are
227
/// excluded, as in floor_div, without excluding representable neighbouring values.
228
5497518
pub(super) fn division_domain(a: &Expression, b: &Expression, safe: bool) -> Option<DomainPtr> {
229
5497518
    let a = finite_integer_ranges(a)?;
230
5497518
    let b = finite_integer_ranges(b)?;
231
5497518
    let mut result = Vec::new();
232
5497518
    if !a.is_empty() && !b.is_empty() {
233
5497514
        let (a_min, a_max) = range_vec_bounds_i32(&a)?;
234
5497514
        let mut low = i64::MAX;
235
5497514
        let mut high = i64::MIN;
236
5497514
        for range in b {
237
5497516
            let (lo, hi) = (*range.low()?, *range.high()?);
238
            // Splitting at zero is essential: checking only the outer endpoints
239
            // of -N..N would miss the extreme quotients at -1 and 1.
240
5497516
            let endpoints = [lo, hi.min(-1), lo.max(1), hi];
241
5497516
            for divisor in endpoints
242
5497516
                .into_iter()
243
21990064
                .filter(|b| *b != 0 && lo <= *b && *b <= hi)
244
            {
245
16253340
                for dividend in [a_min, a_max] {
246
32506680
                    let remainder = super::floor_mod(dividend, divisor)?;
247
                    let quotient =
248
32506680
                        (i64::from(dividend) - i64::from(remainder)) / i64::from(divisor);
249
32506680
                    low = low.min(quotient);
250
32506680
                    high = high.max(quotient);
251
                }
252
            }
253
        }
254
5497514
        let low = low.max(i64::from(i32::MIN));
255
5497514
        let high = high.min(i64::from(i32::MAX));
256
5497514
        if low <= high {
257
5497420
            result.push(Range::new(
258
5497420
                Some(i32::try_from(low).ok()?),
259
5497420
                Some(i32::try_from(high).ok()?),
260
            ));
261
94
        }
262
4
    }
263
5497518
    if safe {
264
1647585
        result.push(Range::Single(0));
265
3849933
    }
266
5497518
    if result.is_empty() {
267
49
        Some(Domain::empty(ReturnType::Int))
268
    } else {
269
5497469
        Some(Domain::int(Range::squeeze(&result)))
270
    }
271
5497518
}
272

            
273
/// Power extrema occur at base interval endpoints or -1/0/1, and at the first
274
/// and last exponent of each parity. This is independent of the number of base
275
/// values or exponents; checked_pow also takes only logarithmic time in the
276
/// exponent. Keep the existing defined-result semantics (negative exponents and
277
/// 0**0 contribute no values), and decline inference on possible i32 overflow.
278
424733
pub(super) fn power_integer_domain(a: &Expression, b: &Expression) -> Option<DomainPtr> {
279
424733
    let a = finite_integer_ranges(a)?;
280
424733
    let b = finite_integer_ranges(b)?;
281
424729
    let mut low = i32::MAX;
282
424729
    let mut high = i32::MIN;
283
424729
    for base in a {
284
424727
        let (lo, hi) = (*base.low()?, *base.high()?);
285
424727
        for exponent in &b {
286
            let (first, last) = (
287
424727
                i64::from((*exponent.low()?).max(0)),
288
424727
                i64::from(*exponent.high()?),
289
            );
290
424727
            for parity in [0, 1] {
291
849452
                let first = first + i64::from(first % 2 != parity);
292
849452
                let last = last - i64::from(last.rem_euclid(2) != parity);
293
849452
                if first > last {
294
203874
                    continue;
295
645578
                }
296
645578
                for power in [first, last] {
297
1291154
                    let power = u32::try_from(power).ok()?;
298
1291154
                    for value in [lo, hi, -1, 0, 1]
299
1291154
                        .into_iter()
300
6455754
                        .filter(|value| lo <= *value && *value <= hi)
301
                    {
302
3184090
                        if value == 0 && power == 0 {
303
21253
                            continue;
304
3162837
                        }
305
3162837
                        let result = value.checked_pow(power)?;
306
3162833
                        low = low.min(result);
307
3162833
                        high = high.max(result);
308
                    }
309
                }
310
            }
311
        }
312
    }
313
424725
    if low > high {
314
176
        Some(Domain::empty(ReturnType::Int))
315
    } else {
316
424549
        Some(Domain::int(vec![Range::new(Some(low), Some(high))]))
317
    }
318
424733
}
319

            
320
/// Only 0!..12! are representable as i32. Validate endpoints before allocating
321
/// any values, then inspect this fixed set to preserve the exact sparse result.
322
94
pub(super) fn factorial_domain(a: &Expression) -> Option<DomainPtr> {
323
94
    let ranges = finite_integer_ranges(a)?;
324
94
    if ranges.is_empty() {
325
        return Some(Domain::empty(ReturnType::Int));
326
94
    }
327
94
    let (lo, hi) = range_vec_bounds_i32(&ranges)?;
328
94
    if lo < 0 || hi > 12 {
329
2
        return None;
330
92
    }
331
92
    let values = (0..=12)
332
1219
        .filter(|value| ranges.iter().any(|range| range.contains(value)))
333
92
        .map(factorial_i32)
334
        .collect::<Option<BTreeSet<_>>>()?;
335
92
    Some(DomainPtr::from(GroundDomain::from_set_i32(&values)))
336
94
}
337

            
338
/// Sound floor-remainder bounds, computed from range endpoints rather than the
339
/// Cartesian product of operand values. Inference is linear in the number of
340
/// ranges, independent of their cardinality. Holes in the result are permitted:
341
/// the modulo constraint, rather than its auxiliary domain, enforces exactness.
342
9893002
pub(super) fn modulo_domain(a: &Expression, b: &Expression, safe: bool) -> Option<DomainPtr> {
343
9893002
    let a_domain = a.domain_of()?.resolve().ok()?;
344
9892741
    let b_domain = b.domain_of()?.resolve().ok()?;
345
19785480
    let ranges = |domain: &GroundDomain| match domain {
346
19785240
        GroundDomain::Int(ranges, _) => Some(ranges.clone()),
347
236
        GroundDomain::Empty(ReturnType::Int) => Some(Vec::new()),
348
4
        _ => None,
349
19785480
    };
350
9892741
    let a_ranges = ranges(&a_domain)?;
351
9892739
    let b_ranges = ranges(&b_domain)?;
352
1200
    let empty = || {
353
1200
        if safe {
354
368
            Domain::int(vec![Range::Single(0)])
355
        } else {
356
832
            Domain::empty(ReturnType::Int)
357
        }
358
1200
    };
359
9892737
    if a_ranges.is_empty() || b_ranges.is_empty() {
360
240
        return Some(empty());
361
9892497
    }
362
9892497
    let (a_min, a_max) = range_vec_bounds_i32(&a_ranges)?;
363
9892495
    let (b_min, b_max) = range_vec_bounds_i32(&b_ranges)?;
364
9892493
    if b_min == 0 && b_max == 0 {
365
960
        return Some(empty());
366
9891533
    }
367
    // Keep constant inference exact, including MIN % -1 (which is zero).
368
9891533
    if a_min == a_max && b_min == b_max {
369
4322
        let value = super::floor_mod(a_min, b_min)?;
370
4322
        let mut ranges = vec![Range::Single(value)];
371
4322
        if safe && value != 0 {
372
32
            ranges.push(Range::Single(0));
373
4290
        }
374
4322
        return Some(Domain::int(ranges));
375
9887211
    }
376
    // For b > 0, 0 <= a mod b < b; for b < 0, b < a mod b <= 0.
377
    // Add/subtract only on the corresponding sign branch, avoiding overflow
378
    // even when an endpoint is i32::MIN or i32::MAX.
379
9887211
    let lo = if b_min < 0 { b_min + 1 } else { 0 };
380
9887211
    let hi = if b_max > 0 { b_max - 1 } else { 0 };
381
    // If dividend and divisor have the same sign, the magnitude of the
382
    // remainder cannot exceed the magnitude of the dividend either.
383
9887211
    let mut lo = if a_max <= 0 { lo.max(a_min) } else { lo };
384
9887211
    let mut hi = if a_min >= 0 { hi.min(a_max) } else { hi };
385
9887211
    if safe {
386
4411101
        lo = lo.min(0);
387
4411101
        hi = hi.max(0);
388
5476110
    }
389
9887211
    Some(Domain::int(vec![Range::new(Some(lo), Some(hi))]))
390
9893002
}
391

            
392
/// Integer domain bounds for [`Expression::Sum`] over a comprehension.
393
15199274
fn sum_domain_for_comprehension(expr: &Expression) -> Option<DomainPtr> {
394
15199274
    let Expression::Comprehension(_, comp) = expr else {
395
14942041
        return None;
396
    };
397
257233
    let comp = comp.as_ref();
398
257233
    let resolved = comp.return_expression.domain_of()?.resolve().ok()?;
399
252854
    let GroundDomain::Int(return_ranges, _) = resolved.as_ref() else {
400
        return None;
401
    };
402
252854
    let (term_min, term_max) = range_vec_bounds_i32(return_ranges)?;
403

            
404
252854
    let mut term_count = 1i64;
405
252854
    for qual in &comp.qualifiers {
406
351115
        match qual {
407
            ComprehensionQualifier::Generator { ptr } => {
408
345267
                let count = ptr.domain()?.resolve().ok()?.length().ok()?;
409
340946
                term_count = term_count.saturating_mul(count as i64);
410
340946
                if term_count > i32::MAX as i64 {
411
                    return None;
412
340946
                }
413
            }
414
            ComprehensionQualifier::Condition(_)
415
            | ComprehensionQualifier::ExpressionGenerator { .. } => {
416
5848
                return None;
417
            }
418
        }
419
    }
420

            
421
242685
    if term_count == 0 {
422
        return Some(Domain::int(vec![Range::Single(0)]));
423
242685
    }
424

            
425
242685
    let n = term_count as i32;
426
242685
    let sum_min = n.saturating_mul(term_min);
427
242685
    let sum_max = n.saturating_mul(term_max);
428
242685
    if sum_min == sum_max {
429
16
        Some(Domain::int(vec![Range::Single(sum_min)]))
430
    } else {
431
242669
        Some(Domain::int(vec![Range::Bounded(sum_min, sum_max)]))
432
    }
433
15199274
}
434

            
435
15111542
pub(super) fn sum_domain_of_child(child: &Expression) -> Option<DomainPtr> {
436
15111542
    sum_domain_for_comprehension(child).or_else(|| {
437
14868857
        let (elements, _) = child.clone().unwrap_matrix_unchecked()?;
438
14672731
        if elements.len() == 1 {
439
87732
            sum_domain_for_comprehension(&elements[0])
440
        } else {
441
14584999
            None
442
        }
443
14868857
    })
444
15111542
}
445

            
446
30404
pub(super) fn selected_representation(expr: &Expression) -> Option<String> {
447
30404
    match expr {
448
28341
        Expression::Atomic(_, Atom::Reference(reference)) => reference
449
28341
            .repr
450
1305
            .map(|rule| rule.short_name().to_owned())
451
27036
            .or_else(|| {
452
27036
                reference
453
27036
                    .domain_of()
454
27036
                    .representation_preference()
455
27036
                    .map(str::to_owned)
456
27036
            }),
457
        Expression::TypeAnnotation(_, inner, domain)
458
        | Expression::DomainAnnotation(_, inner, domain) => domain
459
            .representation_preference()
460
            .map(str::to_owned)
461
            .or_else(|| selected_representation(inner)),
462
2063
        _ => None,
463
    }
464
30404
}
465

            
466
/// The value-level union of two multisets adds their cardinalities and occurrence counts.
467
///
468
/// [`Domain::union`] instead computes a common type envelope and intentionally drops collection
469
/// attributes, so it is too weak for the domain of an `a union b` expression.
470
70
pub(super) fn mset_union_result_domain(lhs: &Expression, rhs: &Expression) -> Option<DomainPtr> {
471
70
    let lhs_selected = selected_representation(lhs);
472
70
    let rhs_selected = selected_representation(rhs);
473
70
    let (lhs_attrs, lhs_inner) = lhs.domain_of()?.as_mset()?;
474
70
    let (rhs_attrs, rhs_inner) = rhs.domain_of()?.as_mset()?;
475
70
    let lhs_size = mset_size(&lhs_attrs, &lhs_inner);
476
70
    let rhs_size = mset_size(&rhs_attrs, &rhs_inner);
477
70
    if lhs_size.range().is_none() || rhs_size.range().is_none() {
478
        return Some(Domain::empty(ReturnType::MSet(Box::new(
479
            lhs_inner.return_type(),
480
        ))));
481
70
    }
482
70
    let inner = if lhs_size.high == Some(0) {
483
1
        rhs_inner
484
69
    } else if rhs_size.high == Some(0) {
485
1
        lhs_inner
486
    } else {
487
68
        lhs_inner.union(&rhs_inner).ok()?
488
    };
489
70
    let size = SizeBounds::new(
490
70
        lhs_size.low.checked_add(rhs_size.low)?,
491
70
        lhs_size
492
70
            .high
493
70
            .zip(rhs_size.high)
494
61
            .and_then(|(a, b)| a.checked_add(b)),
495
    )
496
    .range()?;
497
70
    let lhs_occurrence = min_known(
498
70
        SizeBounds::from_size(&lhs_attrs.occurrence).high,
499
70
        lhs_size.high,
500
70
    );
501
70
    let rhs_occurrence = min_known(
502
70
        SizeBounds::from_size(&rhs_attrs.occurrence).high,
503
70
        rhs_size.high,
504
70
    );
505
70
    let max_occurrence = lhs_occurrence
506
70
        .zip(rhs_occurrence)
507
61
        .and_then(|(a, b)| a.checked_add(b));
508
70
    let occurrence = SizeBounds::new(0, max_occurrence).range()?;
509
    // minOccur applies only to present values, including the empty multiset vacuously.
510
70
    let occurrence = if max_occurrence == Some(0) {
511
        occurrence
512
    } else {
513
70
        Range::new(Some(1), occurrence.high().copied())
514
    };
515
70
    let representation = match (
516
70
        lhs_selected
517
70
            .as_deref()
518
70
            .or(lhs_attrs.representation.as_deref()),
519
70
        rhs_selected
520
70
            .as_deref()
521
70
            .or(rhs_attrs.representation.as_deref()),
522
70
    ) {
523
        (Some(lhs), Some(rhs)) if lhs == rhs => Some(lhs.to_owned()),
524
59
        (Some(preference), None) | (None, Some(preference)) => Some(preference.to_owned()),
525
11
        _ => None,
526
    };
527

            
528
70
    let mut attrs = MSetAttr::new(size, occurrence);
529
70
    attrs.representation = representation;
530
70
    Some(Domain::mset(attrs, inner))
531
70
}
532

            
533
/// Also handles permutations: `image(perm, x)` returns a value from the permutation's own inner
534
/// domain (a permutation's "codomain" is its domain), so this doubles as the codomain lookup for
535
/// `Expression::Image`/`ImageSet` applied to either a function or a permutation.
536
507852
pub(super) fn get_function_codomain(function: &Moo<Expression>) -> Option<DomainPtr> {
537
507852
    let function_domain = function.domain_of()?;
538
507794
    match function_domain.resolve().as_ref() {
539
        Ok(d) => {
540
507790
            match d.as_ref() {
541
409161
                GroundDomain::Function(_, _, codomain) => Some(codomain.clone().into()),
542
1160
                GroundDomain::Permutation(_, inner) => Some(inner.clone().into()),
543
97469
                GroundDomain::Sequence(_, inner) => Some(inner.clone().into()),
544
                // Not defined for anything other than a function, permutation or sequence
545
                _ => None,
546
            }
547
        }
548
        Err(_) => {
549
4
            match function_domain.as_unresolved()? {
550
                UnresolvedDomain::Function(_, _, codomain) => Some(codomain.clone()),
551
                UnresolvedDomain::Permutation(_, inner) => Some(inner.clone()),
552
4
                UnresolvedDomain::Sequence(_, inner) => Some(inner.clone()),
553
                // Not defined for anything other than a function, permutation or sequence
554
                _ => None,
555
            }
556
        }
557
    }
558
507852
}
559

            
560
#[cfg(test)]
561
mod tests {
562
    use super::*;
563
    use crate::ast::{AbstractLiteral, DeclarationPtr, Literal, Metadata, Name, Reference};
564
    use crate::matrix_expr;
565

            
566
13101
    fn integer_variable(domain: DomainPtr) -> Expression {
567
13101
        Reference::new(DeclarationPtr::new_find(Name::user("x"), domain)).into()
568
13101
    }
569

            
570
    #[test]
571
1
    fn interval_arithmetic_domains_cover_all_small_signed_results() {
572
        type Binary = fn(Metadata, Moo<Expression>, Moo<Expression>) -> Expression;
573
        type Operation = fn(i32, i32) -> Option<i32>;
574
1
        let operations: [(Binary, Operation); 3] = [
575
1
            (Expression::Minus, i32::checked_sub),
576
1
            (Expression::PairwiseSum, i32::checked_add),
577
1
            (Expression::PairwiseProduct, i32::checked_mul),
578
1
        ];
579
1
        for a_min in -3..=3 {
580
7
            for a_max in a_min..=3 {
581
28
                let a = integer_variable(Domain::int(vec![Range::Bounded(a_min, a_max)]));
582
28
                let abs = Expression::Abs(Metadata::new(), Moo::new(a.clone()));
583
28
                let absolute = abs.domain_of().unwrap().resolve().unwrap();
584
28
                for value in a_min..=a_max {
585
84
                    assert!(absolute.contains(&Literal::Int(value.abs())).unwrap());
586
                }
587
28
                for b_min in -3..=3 {
588
196
                    for b_max in b_min..=3 {
589
784
                        let b = integer_variable(Domain::int(vec![Range::Bounded(b_min, b_max)]));
590
784
                        for (construct, operation) in operations {
591
2352
                            let expr = construct(
592
2352
                                Metadata::new(),
593
2352
                                Moo::new(a.clone()),
594
2352
                                Moo::new(b.clone()),
595
2352
                            );
596
2352
                            let domain = expr.domain_of().unwrap().resolve().unwrap();
597
2352
                            for x in a_min..=a_max {
598
7056
                                for y in b_min..=b_max {
599
21168
                                    let result = operation(x, y).unwrap();
600
21168
                                    assert!(
601
21168
                                        domain.contains(&Literal::Int(result)).unwrap(),
602
                                        "{expr}: {result} missing from {domain}"
603
                                    );
604
                                }
605
                            }
606
                        }
607
                    }
608
                }
609
            }
610
        }
611
1
    }
612

            
613
    #[test]
614
1
    fn interval_arithmetic_handles_large_domains_empty_operands_and_overflow() {
615
1
        let wide = integer_variable(Domain::int(vec![Range::Bounded(
616
1
            -1_000_000_000,
617
1
            1_000_000_000,
618
1
        )]));
619
1
        let two = integer_variable(Domain::int(vec![Range::Bounded(-2, 2)]));
620
1
        for expr in [
621
1
            Expression::Minus(
622
1
                Metadata::new(),
623
1
                Moo::new(wide.clone()),
624
1
                Moo::new(wide.clone()),
625
1
            ),
626
1
            Expression::PairwiseSum(
627
1
                Metadata::new(),
628
1
                Moo::new(wide.clone()),
629
1
                Moo::new(wide.clone()),
630
1
            ),
631
1
            Expression::PairwiseProduct(Metadata::new(), Moo::new(wide.clone()), Moo::new(two)),
632
1
        ] {
633
3
            assert_eq!(
634
3
                expr.domain_of(),
635
3
                Some(Domain::int(vec![Range::Bounded(
636
3
                    -2_000_000_000,
637
3
                    2_000_000_000
638
3
                )]))
639
            );
640
        }
641
1
        assert_eq!(
642
1
            Expression::Abs(Metadata::new(), Moo::new(wide.clone())).domain_of(),
643
1
            Some(Domain::int(vec![Range::Bounded(0, 1_000_000_000)]))
644
        );
645
1
        let sparse = integer_variable(Domain::int(vec![
646
1
            Range::Bounded(-8, -5),
647
1
            Range::Bounded(3, 4),
648
1
            Range::Single(0),
649
1
        ]));
650
1
        assert_eq!(
651
1
            Expression::Abs(Metadata::new(), Moo::new(sparse))
652
1
                .domain_of()
653
1
                .unwrap()
654
1
                .resolve()
655
1
                .unwrap()
656
1
                .values_i32()
657
1
                .unwrap(),
658
1
            vec![0, 3, 4, 5, 6, 7, 8]
659
        );
660
1
        let min = integer_variable(Domain::int(vec![Range::Single(i32::MIN)]));
661
1
        let max = integer_variable(Domain::int(vec![Range::Single(i32::MAX)]));
662
1
        let one = integer_variable(Domain::int(vec![Range::Single(1)]));
663
1
        assert_eq!(
664
1
            Expression::Abs(Metadata::new(), Moo::new(min.clone())).domain_of(),
665
1
            None
666
        );
667
1
        for expr in [
668
1
            Expression::Minus(Metadata::new(), Moo::new(min), Moo::new(one.clone())),
669
1
            Expression::PairwiseSum(Metadata::new(), Moo::new(max.clone()), Moo::new(one)),
670
1
            Expression::PairwiseProduct(Metadata::new(), Moo::new(max.clone()), Moo::new(max)),
671
1
        ] {
672
3
            assert_eq!(expr.domain_of(), None);
673
        }
674
1
        let empty = integer_variable(Domain::empty(ReturnType::Int));
675
1
        assert_eq!(
676
1
            Expression::Abs(Metadata::new(), Moo::new(empty.clone())).domain_of(),
677
1
            Some(Domain::empty(ReturnType::Int))
678
        );
679
1
        for construct in [
680
1
            Expression::Minus,
681
1
            Expression::PairwiseSum,
682
1
            Expression::PairwiseProduct,
683
1
        ] {
684
3
            for (a, b) in [(empty.clone(), wide.clone()), (wide.clone(), empty.clone())] {
685
6
                assert_eq!(
686
6
                    construct(Metadata::new(), Moo::new(a), Moo::new(b)).domain_of(),
687
6
                    Some(Domain::empty(ReturnType::Int))
688
                );
689
            }
690
        }
691
1
    }
692

            
693
4066
    fn division_expression(a: DomainPtr, b: DomainPtr, safe: bool) -> Expression {
694
4066
        let (a, b) = (Moo::new(integer_variable(a)), Moo::new(integer_variable(b)));
695
4066
        if safe {
696
2033
            Expression::SafeDiv(Metadata::new(), a, b)
697
        } else {
698
2033
            Expression::UnsafeDiv(Metadata::new(), a, b)
699
        }
700
4066
    }
701

            
702
    #[test]
703
1
    fn division_domain_bounds_cover_all_small_signed_results() {
704
1
        for a_min in -4..=4 {
705
9
            for a_max in a_min..=4 {
706
45
                for b_min in -4..=4 {
707
405
                    for b_max in b_min..=4 {
708
2025
                        for safe in [false, true] {
709
4050
                            let expr = division_expression(
710
4050
                                Domain::int(vec![Range::Bounded(a_min, a_max)]),
711
4050
                                Domain::int(vec![Range::Bounded(b_min, b_max)]),
712
4050
                                safe,
713
4050
                            );
714
4050
                            let domain = expr.domain_of().unwrap().resolve().unwrap();
715
4050
                            let expected: BTreeSet<_> = (a_min..=a_max)
716
14850
                                .flat_map(|a| {
717
14850
                                    (b_min..=b_max)
718
54450
                                        .filter_map(move |b| super::super::floor_div(a, b))
719
14850
                                })
720
4050
                                .chain(safe.then_some(0))
721
                                .collect();
722
4050
                            for value in &expected {
723
17619
                                assert!(
724
17619
                                    domain.contains(&Literal::Int(*value)).unwrap(),
725
                                    "{expr}: {value} missing from {domain}"
726
                                );
727
                            }
728
4050
                            if expected.is_empty() || (a_min == a_max && b_min == b_max) {
729
198
                                assert_eq!(
730
198
                                    domain
731
198
                                        .values_i32()
732
198
                                        .unwrap()
733
198
                                        .into_iter()
734
198
                                        .collect::<BTreeSet<_>>(),
735
198
                                    expected
736
                                );
737
3852
                            }
738
                        }
739
                    }
740
                }
741
            }
742
        }
743
1
    }
744

            
745
    #[test]
746
1
    fn division_domain_bounds_handle_large_sparse_empty_and_extreme_operands() {
747
1
        let wide = Domain::int(vec![Range::Bounded(-1_000_000_000, 1_000_000_000)]);
748
1
        let full = Domain::int(vec![Range::Bounded(i32::MIN, i32::MAX)]);
749
1
        for safe in [false, true] {
750
2
            assert_eq!(
751
2
                division_expression(wide.clone(), wide.clone(), safe).domain_of(),
752
2
                Some(wide.clone())
753
            );
754
2
            assert_eq!(
755
2
                division_expression(full.clone(), full.clone(), safe).domain_of(),
756
2
                Some(full.clone())
757
            );
758
2
            for empty in [
759
2
                Domain::empty(ReturnType::Int),
760
2
                Domain::int(Vec::<Range<i32>>::new()),
761
2
                Domain::int(vec![Range::Single(0)]),
762
2
            ] {
763
6
                let domain = division_expression(wide.clone(), empty, safe)
764
6
                    .domain_of()
765
6
                    .unwrap()
766
6
                    .resolve()
767
6
                    .unwrap();
768
6
                assert_eq!(
769
6
                    domain.values_i32().unwrap(),
770
6
                    if safe { vec![0] } else { vec![] }
771
                );
772
            }
773
2
            let min = Domain::int(vec![Range::Single(i32::MIN)]);
774
2
            let minus_one = Domain::int(vec![Range::Single(-1)]);
775
2
            let domain = division_expression(min, minus_one.clone(), safe)
776
2
                .domain_of()
777
2
                .unwrap()
778
2
                .resolve()
779
2
                .unwrap();
780
2
            assert_eq!(
781
2
                domain.values_i32().unwrap(),
782
2
                if safe { vec![0] } else { vec![] }
783
            );
784
2
            let near_min = Domain::int(vec![Range::Bounded(i32::MIN, i32::MIN + 1)]);
785
2
            let domain = division_expression(near_min, minus_one, safe)
786
2
                .domain_of()
787
2
                .unwrap()
788
2
                .resolve()
789
2
                .unwrap();
790
2
            assert!(domain.contains(&Literal::Int(i32::MAX)).unwrap());
791
2
            let sparse = Domain::int(vec![Range::Single(-9), Range::Bounded(3, 5)]);
792
2
            let domain = division_expression(sparse.clone(), sparse, safe)
793
2
                .domain_of()
794
2
                .unwrap()
795
2
                .resolve()
796
2
                .unwrap();
797
2
            for a in [-9, 3, 4, 5] {
798
8
                for b in [-9, 3, 4, 5] {
799
32
                    assert!(
800
32
                        domain
801
32
                            .contains(&Literal::Int(super::super::floor_div(a, b).unwrap()))
802
32
                            .unwrap()
803
                    );
804
                }
805
            }
806
        }
807
1
    }
808

            
809
    #[test]
810
1
    fn power_domain_bounds_cover_all_small_signed_results() {
811
1
        for a_min in -3..=3 {
812
7
            for a_max in a_min..=3 {
813
28
                for b_min in -2..=5 {
814
224
                    for b_max in b_min..=5 {
815
1008
                        for construct in [Expression::SafePow, Expression::UnsafePow] {
816
                            let a =
817
2016
                                integer_variable(Domain::int(vec![Range::Bounded(a_min, a_max)]));
818
                            let b =
819
2016
                                integer_variable(Domain::int(vec![Range::Bounded(b_min, b_max)]));
820
2016
                            let expr = construct(Metadata::new(), Moo::new(a), Moo::new(b));
821
2016
                            let domain = expr.domain_of().unwrap().resolve().unwrap();
822
2016
                            let expected: BTreeSet<_> = (a_min..=a_max)
823
6048
                                .flat_map(|a: i32| {
824
20160
                                    (b_min..=b_max).filter_map(move |b| {
825
20160
                                        if b >= 0 && (a != 0 || b != 0) {
826
15888
                                            a.checked_pow(b as u32)
827
                                        } else {
828
4272
                                            None
829
                                        }
830
20160
                                    })
831
6048
                                })
832
                                .collect();
833
2016
                            for result in &expected {
834
9106
                                assert!(
835
9106
                                    domain.contains(&Literal::Int(*result)).unwrap(),
836
                                    "{expr}: {result} missing from {domain}"
837
                                );
838
                            }
839
                            // Besides containment, the extrema must be tight.
840
2016
                            let actual = domain.values_i32().unwrap();
841
2016
                            assert_eq!(actual.iter().min(), expected.iter().next());
842
2016
                            assert_eq!(actual.iter().max(), expected.iter().next_back());
843
                        }
844
                    }
845
                }
846
            }
847
        }
848
1
    }
849

            
850
    #[test]
851
1
    fn power_and_factorial_domains_do_not_materialise_large_operands() {
852
1
        for construct in [Expression::SafePow, Expression::UnsafePow] {
853
12
            let power = |base, exponent| {
854
12
                construct(
855
12
                    Metadata::new(),
856
12
                    Moo::new(integer_variable(base)),
857
12
                    Moo::new(integer_variable(exponent)),
858
12
                )
859
12
            };
860
2
            let wide = Domain::int(vec![Range::Bounded(-1_000_000_000, 1_000_000_000)]);
861
2
            let one = Domain::int(vec![Range::Single(1)]);
862
2
            assert_eq!(
863
2
                power(wide.clone(), one.clone()).domain_of(),
864
2
                Some(wide.clone())
865
            );
866
2
            let huge_exponent = Domain::int(vec![Range::Bounded(-1_000_000_000, i32::MAX)]);
867
2
            assert_eq!(
868
2
                power(Domain::int(vec![Range::Bounded(-1, 1)]), huge_exponent).domain_of(),
869
2
                Some(Domain::int(vec![Range::Bounded(-1, 1)]))
870
            );
871
2
            let thirty_one = Domain::int(vec![Range::Single(31)]);
872
2
            assert_eq!(
873
2
                power(Domain::int(vec![Range::Single(-2)]), thirty_one.clone()).domain_of(),
874
2
                Some(Domain::int(vec![Range::Single(i32::MIN)]))
875
            );
876
2
            assert_eq!(
877
2
                power(Domain::int(vec![Range::Single(2)]), thirty_one).domain_of(),
878
2
                None
879
            );
880
2
            assert_eq!(
881
2
                power(wide.clone(), Domain::int(vec![Range::Bounded(0, i32::MAX)])).domain_of(),
882
2
                None
883
            );
884
2
            assert_eq!(
885
2
                power(Domain::empty(ReturnType::Int), one).domain_of(),
886
2
                Some(Domain::empty(ReturnType::Int))
887
            );
888
        }
889
        let factorial =
890
94
            |domain| Expression::Factorial(Metadata::new(), Moo::new(integer_variable(domain)));
891
1
        for domain in [
892
1
            Domain::int(vec![Range::Bounded(0, i32::MAX)]),
893
1
            Domain::int(vec![Range::Bounded(i32::MIN, i32::MAX)]),
894
1
        ] {
895
2
            assert_eq!(factorial(domain).domain_of(), None);
896
        }
897
1
        for low in 0..=12 {
898
13
            for high in low..=12 {
899
91
                let expected = (low..=high)
900
455
                    .map(|n| factorial_i32(n).unwrap())
901
                    .collect::<BTreeSet<_>>();
902
91
                let result = factorial(Domain::int(vec![Range::Bounded(low, high)]))
903
91
                    .domain_of()
904
91
                    .unwrap()
905
91
                    .resolve()
906
91
                    .unwrap();
907
91
                assert_eq!(
908
91
                    result
909
91
                        .values_i32()
910
91
                        .unwrap()
911
91
                        .into_iter()
912
91
                        .collect::<BTreeSet<_>>(),
913
91
                    expected
914
                );
915
            }
916
        }
917
1
        let sparse = Domain::int(vec![Range::Single(0), Range::Single(4), Range::Single(12)]);
918
1
        let values = factorial(sparse)
919
1
            .domain_of()
920
1
            .unwrap()
921
1
            .resolve()
922
1
            .unwrap()
923
1
            .values_i32()
924
1
            .unwrap();
925
1
        assert_eq!(values, vec![1, 24, 479001600]);
926
1
    }
927

            
928
4078
    fn modulo_expression(a: DomainPtr, b: DomainPtr, safe: bool) -> Expression {
929
8156
        let variable = |name: &str, domain| {
930
8156
            Moo::new(Expression::Atomic(
931
8156
                Metadata::new(),
932
8156
                Atom::Reference(Reference::new(DeclarationPtr::new_find(
933
8156
                    Name::user(name),
934
8156
                    domain,
935
8156
                ))),
936
8156
            ))
937
8156
        };
938
4078
        let (a, b) = (variable("a", a), variable("b", b));
939
4078
        if safe {
940
2039
            Expression::SafeMod(Metadata::new(), a, b)
941
        } else {
942
2039
            Expression::UnsafeMod(Metadata::new(), a, b)
943
        }
944
4078
    }
945

            
946
    #[test]
947
1
    fn modulo_domain_bounds_contain_all_small_signed_results() {
948
        // Exhaust all pairs of small intervals, including constants, mixed signs,
949
        // zero-only divisors, and same-sign dividend tightening.
950
1
        for a_min in -4..=4 {
951
9
            for a_max in a_min..=4 {
952
45
                for b_min in -4..=4 {
953
405
                    for b_max in b_min..=4 {
954
2025
                        for safe in [false, true] {
955
4050
                            let domain = modulo_expression(
956
4050
                                Domain::int(vec![Range::Bounded(a_min, a_max)]),
957
4050
                                Domain::int(vec![Range::Bounded(b_min, b_max)]),
958
4050
                                safe,
959
4050
                            )
960
4050
                            .domain_of()
961
4050
                            .unwrap()
962
4050
                            .resolve()
963
4050
                            .unwrap();
964
4050
                            let expected: BTreeSet<_> = (a_min..=a_max)
965
14850
                                .flat_map(|a| {
966
14850
                                    (b_min..=b_max)
967
54450
                                        .filter_map(move |b| super::super::floor_mod(a, b))
968
14850
                                })
969
4050
                                .chain(safe.then_some(0))
970
                                .collect();
971
4050
                            for value in &expected {
972
11485
                                assert!(
973
11485
                                    domain.contains(&Literal::Int(*value)).unwrap(),
974
                                    "{a_min}..{a_max} mod {b_min}..{b_max}, safe={safe}: {value} missing from {domain}"
975
                                );
976
                            }
977
4050
                            if expected.is_empty() {
978
45
                                assert_eq!(domain.len_usize().unwrap(), 0);
979
4005
                            }
980
4050
                            if a_min == a_max && b_min == b_max {
981
162
                                assert_eq!(
982
162
                                    domain
983
162
                                        .values_i32()
984
162
                                        .unwrap()
985
162
                                        .into_iter()
986
162
                                        .collect::<BTreeSet<_>>(),
987
162
                                    expected
988
                                );
989
3888
                            }
990
                        }
991
                    }
992
                }
993
            }
994
        }
995
1
    }
996

            
997
    #[test]
998
1
    fn modulo_domain_bounds_handle_sparse_empty_and_unbounded_domains() {
999
1
        let sparse = Domain::int(vec![Range::Single(-7), Range::Bounded(3, 5)]);
1
        for safe in [false, true] {
2
            let domain = modulo_expression(sparse.clone(), sparse.clone(), safe)
2
                .domain_of()
2
                .unwrap()
2
                .resolve()
2
                .unwrap();
2
            for a in [-7, 3, 4, 5] {
8
                for b in [-7, 3, 4, 5] {
32
                    let value = super::super::floor_mod(a, b).unwrap();
32
                    assert!(domain.contains(&Literal::Int(value)).unwrap());
                }
            }
2
            for empty in [
2
                Domain::empty(ReturnType::Int),
2
                Domain::int(Vec::<Range<i32>>::new()),
2
            ] {
4
                for (a, b) in [(empty.clone(), sparse.clone()), (sparse.clone(), empty)] {
8
                    let domain = modulo_expression(a, b, safe)
8
                        .domain_of()
8
                        .unwrap()
8
                        .resolve()
8
                        .unwrap();
8
                    assert_eq!(
8
                        domain.values_i32().unwrap(),
8
                        if safe { vec![0] } else { vec![] }
                    );
                }
            }
2
            for (a, b) in [
2
                (Domain::int(vec![Range::<i32>::Unbounded]), sparse.clone()),
2
                (sparse.clone(), Domain::int(vec![Range::<i32>::Unbounded])),
2
                (Domain::bool(), sparse.clone()),
2
                (sparse.clone(), Domain::bool()),
2
            ] {
8
                assert_eq!(modulo_expression(a, b, safe).domain_of(), None);
            }
        }
1
    }
    #[test]
1
    fn modulo_domain_bounds_do_not_enumerate_large_domains() {
        // The previous Cartesian-product algorithm needed 400 million pairs for
        // +/-10,000, and could not even allocate the full i32 domain below.
1
        for bound in [100, 10_000, 1_000_000_000] {
3
            let domain = Domain::int(vec![Range::Bounded(-bound, bound)]);
3
            for safe in [false, true] {
6
                assert_eq!(
6
                    modulo_expression(domain.clone(), domain.clone(), safe).domain_of(),
6
                    Some(Domain::int(vec![Range::Bounded(-bound + 1, bound - 1)]))
                );
            }
        }
1
        let full = Domain::int(vec![Range::Bounded(i32::MIN, i32::MAX)]);
1
        for safe in [false, true] {
2
            assert_eq!(
2
                modulo_expression(full.clone(), full.clone(), safe).domain_of(),
2
                Some(Domain::int(vec![Range::Bounded(
2
                    i32::MIN + 1,
2
                    i32::MAX - 1
2
                )]))
            );
2
            let min = Domain::int(vec![Range::Single(i32::MIN)]);
2
            let minus_one = Domain::int(vec![Range::Single(-1)]);
2
            assert_eq!(
2
                modulo_expression(min, minus_one, safe).domain_of(),
2
                Some(Domain::int(vec![Range::Single(0)]))
            );
        }
1
    }
    #[test]
1
    fn test_domain_of_constant_sum() {
1
        let c1 = Expression::Atomic(Metadata::new(), Atom::Literal(Literal::Int(1)));
1
        let c2 = Expression::Atomic(Metadata::new(), Atom::Literal(Literal::Int(2)));
1
        let sum = Expression::Sum(Metadata::new(), Moo::new(matrix_expr![c1, c2]));
1
        assert_eq!(sum.domain_of(), Some(Domain::int(vec![Range::Single(3)])));
1
    }
    #[test]
1
    fn test_domain_of_constant_invalid_type() {
1
        let c1 = Expression::Atomic(Metadata::new(), Atom::Literal(Literal::Int(1)));
1
        let c2 = Expression::Atomic(Metadata::new(), Atom::Literal(Literal::Bool(true)));
1
        let sum = Expression::Sum(Metadata::new(), Moo::new(matrix_expr![c1, c2]));
1
        assert_eq!(sum.domain_of(), None);
1
    }
    #[test]
1
    fn test_domain_of_empty_sum() {
1
        let sum = Expression::Sum(Metadata::new(), Moo::new(matrix_expr![]));
1
        assert_eq!(sum.domain_of(), None);
1
    }
    /// Product domain inference must not panic when bound corner-products overflow i32.
    #[test]
1
    fn test_domain_of_product_overflow_returns_none() {
4
        let mk_var = |name: &str| {
4
            Expression::Atomic(
4
                Metadata::new(),
4
                Atom::Reference(Reference::new(DeclarationPtr::new_find(
4
                    Name::User(name.into()),
4
                    Domain::int(vec![Range::Bounded(0, 711)]),
4
                ))),
4
            )
4
        };
1
        let product = Expression::Product(
1
            Metadata::new(),
1
            Moo::new(matrix_expr![
1
                mk_var("item1"),
1
                mk_var("item2"),
1
                mk_var("item3"),
1
                mk_var("item4")
1
            ]),
1
        );
1
        assert_eq!(product.domain_of(), None);
1
    }
    #[test]
1
    fn mset_union_domain_adds_finite_operand_bounds() {
1
        let lhs = DeclarationPtr::new_find(
1
            Name::user("xs"),
1
            Domain::mset(
1
                MSetAttr::new_max_size(6).with_representation("counts"),
1
                Domain::int(vec![Range::Bounded(1, 999)]),
1
            ),
1
        );
1
        let rhs = Expression::AbstractLiteral(
1
            Metadata::new(),
1
            AbstractLiteral::MSet(vec![1.into(), 2.into()]),
1
        );
1
        let union = Expression::Union(
1
            Metadata::new(),
1
            Moo::new(Expression::from(Reference::new(lhs))),
1
            Moo::new(rhs),
1
        );
1
        let domain = union.domain_of().expect("multiset union has a domain");
1
        let (attrs, inner) = domain.as_mset_ground().expect("ground multiset domain");
1
        assert_eq!(attrs.size, Range::Bounded(2, 8));
1
        assert_eq!(attrs.occurrence, Range::Bounded(1, 8));
1
        assert_eq!(attrs.representation.as_deref(), Some("counts"));
1
        assert_eq!(
1
            inner.as_ref(),
1
            &GroundDomain::Int(vec![Range::Bounded(1, 999)], None)
        );
1
    }
}