1
//! Function attributes constrain mappings, distinct images, and inverse images differently.
2

            
3
use super::collection_domains::{SizeBounds, bounded_set, capacity, min_known, overlap_domain};
4
use super::{
5
    Domain, DomainPtr, Expression, FuncAttr, GroundDomain, IntVal, JectivityAttr, PartialityAttr,
6
    ReturnType, Typeable, eval_constant,
7
};
8

            
9
13509
pub(super) fn function_size(
10
13509
    attrs: &FuncAttr<IntVal>,
11
13509
    domain: &DomainPtr,
12
13509
    codomain: &DomainPtr,
13
13509
) -> SizeBounds {
14
13509
    let mut size = SizeBounds::from_size(&attrs.size).cap(capacity(domain));
15
13509
    if attrs.partiality == PartialityAttr::Total {
16
5986
        size.low = size.low.max(capacity(domain).unwrap_or(0));
17
7523
    }
18
13509
    let codomain_size = capacity(codomain);
19
13509
    if matches!(
20
13509
        attrs.jectivity,
21
        JectivityAttr::Injective | JectivityAttr::Bijective
22
    ) {
23
4035
        size = size.cap(codomain_size);
24
9474
    }
25
13509
    if matches!(
26
13509
        attrs.jectivity,
27
        JectivityAttr::Surjective | JectivityAttr::Bijective
28
    ) {
29
4044
        size.low = size.low.max(codomain_size.unwrap_or(0));
30
9465
    }
31
13509
    if codomain_size == Some(0) {
32
1680
        size = size.cap(Some(0));
33
11829
    }
34
13509
    size
35
13509
}
36

            
37
1990
fn empty_set(inner: &DomainPtr) -> DomainPtr {
38
1990
    Domain::empty(ReturnType::Set(Box::new(inner.return_type())))
39
1990
}
40

            
41
3099
pub(super) fn defined_domain(function: &Expression) -> Option<DomainPtr> {
42
3099
    let (attrs, domain, codomain) = function.domain_of()?.as_function()?;
43
3099
    Some(bounded_set(
44
3099
        function_size(&attrs, &domain, &codomain),
45
3099
        domain,
46
3099
    ))
47
3099
}
48

            
49
3815
pub(super) fn range_domain(function: &Expression) -> Option<DomainPtr> {
50
3815
    let (attrs, domain, codomain) = function.domain_of()?.as_function()?;
51
3815
    let size = function_size(&attrs, &domain, &codomain);
52
3815
    if size.range().is_none() {
53
446
        return Some(empty_set(&codomain));
54
3369
    }
55
3369
    let bounds = match attrs.jectivity {
56
179
        JectivityAttr::Injective | JectivityAttr::Bijective => size,
57
73
        JectivityAttr::Surjective => match capacity(&codomain) {
58
73
            Some(n) => SizeBounds::new(n, Some(n)),
59
            None => SizeBounds::new(i64::from(size.low > 0), size.high),
60
        },
61
3117
        JectivityAttr::None => SizeBounds::new(i64::from(size.low > 0), size.high),
62
    };
63
3369
    Some(bounded_set(bounds, codomain))
64
3815
}
65

            
66
/// Only exact intersections can prove containment or how many keys a restriction retains.
67
2307
fn exact_overlap(a: &DomainPtr, b: &DomainPtr) -> bool {
68
2307
    if a == b {
69
322
        return true;
70
1985
    }
71
1985
    match (a.resolve(), b.resolve()) {
72
        (Ok(a), Ok(b)) => {
73
1983
            a == b
74
1983
                || matches!(
75
1983
                    (a.as_ref(), b.as_ref()),
76
                    (GroundDomain::Int(_, _), GroundDomain::Int(_, _))
77
                        | (GroundDomain::Empty(_), _)
78
                        | (_, GroundDomain::Empty(_))
79
                )
80
        }
81
2
        _ => false,
82
    }
83
2307
}
84

            
85
1315
fn within(a: &DomainPtr, b: &DomainPtr) -> bool {
86
1315
    if a == b {
87
322
        return true;
88
993
    }
89
993
    if !exact_overlap(a, b) {
90
1
        return false;
91
992
    }
92
992
    capacity(a).is_some_and(|n| capacity(&overlap_domain(a, b)) == Some(n))
93
1315
}
94

            
95
2779
pub(super) fn pre_image_domain(function: &Expression, image: &Expression) -> Option<DomainPtr> {
96
2779
    let (attrs, domain, codomain) = function.domain_of()?.as_function()?;
97
2779
    let size = function_size(&attrs, &domain, &codomain);
98
2779
    let image_domain = image.domain_of()?;
99
2779
    if size.range().is_none() || capacity(&image_domain) == Some(0) {
100
1544
        return Some(empty_set(&domain));
101
1235
    }
102
1235
    if capacity(&overlap_domain(&image_domain, &codomain)) == Some(0) {
103
653
        return Some(bounded_set(SizeBounds::new(0, Some(0)), domain));
104
582
    }
105
582
    let is_within = eval_constant(image)
106
581
        .and_then(|value| codomain.resolve().ok()?.contains(&value).ok())
107
1
        .unwrap_or_else(|| within(&image_domain, &codomain));
108
582
    let injective = matches!(
109
582
        attrs.jectivity,
110
        JectivityAttr::Injective | JectivityAttr::Bijective
111
    );
112
582
    let surjective = matches!(
113
582
        attrs.jectivity,
114
        JectivityAttr::Surjective | JectivityAttr::Bijective
115
    );
116
582
    let mut low = i64::from(surjective && is_within);
117
582
    let mut high = size.high;
118
582
    if injective {
119
259
        high = min_known(high, Some(1));
120
323
    }
121
582
    if surjective && let Some(n) = capacity(&codomain) {
122
137
        high = min_known(high, size.high.map(|max| (max - n + 1).max(0)));
123
445
    }
124
    // With a singleton codomain all mappings have the queried image.
125
582
    if is_within && capacity(&codomain) == Some(1) {
126
98
        low = size.low;
127
484
    }
128
582
    Some(bounded_set(SizeBounds::new(low, high), domain))
129
2779
}
130

            
131
89
pub(super) fn image_set_domain(function: &Expression, argument: &Expression) -> Option<DomainPtr> {
132
89
    let (attrs, domain, codomain) = function.domain_of()?.as_function()?;
133
89
    let size = function_size(&attrs, &domain, &codomain);
134
89
    let argument_domain = argument.domain_of()?;
135
89
    if size.range().is_none() || capacity(&argument_domain) == Some(0) {
136
        return Some(empty_set(&codomain));
137
89
    }
138
89
    if size.high == Some(0) || capacity(&overlap_domain(&argument_domain, &domain)) == Some(0) {
139
1
        return Some(bounded_set(SizeBounds::new(0, Some(0)), codomain));
140
88
    }
141
88
    let is_within = eval_constant(argument)
142
88
        .and_then(|value| domain.resolve().ok()?.contains(&value).ok())
143
        .unwrap_or_else(|| within(&argument_domain, &domain));
144
88
    let total = attrs.partiality == PartialityAttr::Total
145
87
        || capacity(&domain).is_some_and(|n| size.low == n);
146
88
    Some(bounded_set(
147
88
        SizeBounds::new(i64::from(total && is_within), Some(1)),
148
88
        codomain,
149
    ))
150
89
}
151

            
152
2914
pub(super) fn restricted_domain(
153
2914
    function: &Expression,
154
2914
    restriction: &Expression,
155
2914
) -> Option<DomainPtr> {
156
2914
    let (attrs, domain, codomain) = function.domain_of()?.as_function()?;
157
2914
    let requested = restriction.domain_of()?;
158
2914
    let retained = overlap_domain(&domain, &requested);
159
2914
    let source_size = function_size(&attrs, &domain, &codomain);
160
2914
    if source_size.range().is_none() {
161
1600
        return Some(Domain::empty(ReturnType::Function(
162
1600
            Box::new(domain.return_type()),
163
1600
            Box::new(codomain.return_type()),
164
1600
        )));
165
1314
    }
166
1314
    let unchanged = within(&domain, &requested);
167
1314
    let exact = exact_overlap(&domain, &requested);
168
1314
    let low = if unchanged {
169
334
        source_size.low
170
980
    } else if exact {
171
979
        capacity(&domain)
172
979
            .zip(capacity(&retained))
173
979
            .map(|(old, new)| (source_size.low - (old - new)).max(0))
174
979
            .unwrap_or(0)
175
    } else {
176
1
        0
177
    };
178
1314
    let size = SizeBounds::new(low, source_size.high)
179
1314
        .cap(capacity(&retained))
180
1314
        .range()?;
181
1314
    let partiality = if exact {
182
1313
        attrs.partiality
183
    } else {
184
1
        PartialityAttr::Partial
185
    };
186
1314
    let jectivity = match attrs.jectivity {
187
217
        JectivityAttr::Bijective if !unchanged => JectivityAttr::Injective,
188
277
        JectivityAttr::Surjective if !unchanged => JectivityAttr::None,
189
932
        other => other,
190
    };
191
1314
    Some(Domain::function(
192
1314
        FuncAttr {
193
1314
            size,
194
1314
            partiality,
195
1314
            jectivity,
196
1314
            representation: attrs.representation,
197
1314
        },
198
1314
        retained,
199
1314
        codomain,
200
1314
    ))
201
2914
}
202

            
203
#[cfg(test)]
204
mod tests {
205
    use super::*;
206
    use crate::ast::{
207
        AbstractLiteral, DeclarationPtr, Literal, Metadata, Moo, Name, Range, Reference,
208
    };
209
    use std::collections::BTreeSet;
210

            
211
3655
    fn variable(name: &str, domain: DomainPtr) -> Expression {
212
3655
        Reference::new(DeclarationPtr::new_find(Name::user(name), domain)).into()
213
3655
    }
214

            
215
1546
    fn ints(n: i32) -> DomainPtr {
216
1546
        Domain::int((0..n).map(Range::Single).collect())
217
1546
    }
218

            
219
8324
    fn set(values: impl Iterator<Item = i32>) -> Literal {
220
8324
        Literal::AbstractLiteral(AbstractLiteral::Set(
221
8324
            values
222
8324
                .collect::<BTreeSet<_>>()
223
8324
                .into_iter()
224
8324
                .map(Literal::Int)
225
8324
                .collect(),
226
8324
        ))
227
8324
    }
228

            
229
    #[test]
230
1
    fn function_inference_covers_every_small_mapping() {
231
1
        for domain_len in 0..=3 {
232
4
            for codomain_len in 0..=3 {
233
16
                for partiality in [PartialityAttr::Partial, PartialityAttr::Total] {
234
32
                    for jectivity in [
235
32
                        JectivityAttr::None,
236
32
                        JectivityAttr::Injective,
237
32
                        JectivityAttr::Surjective,
238
32
                        JectivityAttr::Bijective,
239
32
                    ] {
240
128
                        for size in [
241
128
                            Range::Unbounded,
242
128
                            Range::Single(0),
243
128
                            Range::Single(1),
244
128
                            Range::UnboundedR(2),
245
128
                            Range::UnboundedL(2),
246
128
                            Range::Bounded(1, 2),
247
128
                        ] {
248
768
                            let source = variable(
249
768
                                "f",
250
768
                                Domain::function(
251
768
                                    FuncAttr {
252
768
                                        size: size.clone(),
253
768
                                        partiality: partiality.clone(),
254
768
                                        jectivity: jectivity.clone(),
255
768
                                        representation: None,
256
768
                                    },
257
768
                                    ints(domain_len),
258
768
                                    ints(codomain_len),
259
768
                                ),
260
768
                            );
261
768
                            let defined = defined_domain(&source).unwrap().resolve().unwrap();
262
768
                            let range = range_domain(&source).unwrap().resolve().unwrap();
263
                            let cardinality =
264
768
                                Expression::Card(Metadata::new(), Moo::new(source.clone()))
265
768
                                    .domain_of()
266
768
                                    .unwrap()
267
768
                                    .resolve()
268
768
                                    .unwrap();
269
768
                            let images = (-1..=codomain_len)
270
2688
                                .map(|image| {
271
2688
                                    (
272
2688
                                        image,
273
2688
                                        pre_image_domain(&source, &Literal::Int(image).into())
274
2688
                                            .unwrap()
275
2688
                                            .resolve()
276
2688
                                            .unwrap(),
277
2688
                                    )
278
2688
                                })
279
                                .collect::<Vec<_>>();
280
768
                            let restrictions = (0i32..1 << domain_len)
281
2880
                                .map(|mask| {
282
2880
                                    let target = Domain::int(
283
2880
                                        (0..domain_len)
284
6528
                                            .filter(|i| mask & (1 << i) != 0)
285
2880
                                            .map(Range::Single)
286
                                            .collect(),
287
                                    );
288
2880
                                    (
289
2880
                                        mask,
290
2880
                                        restricted_domain(&source, &variable("D", target))
291
2880
                                            .unwrap()
292
2880
                                            .resolve()
293
2880
                                            .unwrap(),
294
2880
                                    )
295
2880
                                })
296
                                .collect::<Vec<_>>();
297
768
                            for mut code in 0..(codomain_len + 1).pow(domain_len as u32) {
298
6912
                                let mut pairs = Vec::new();
299
6912
                                for key in 0..domain_len {
300
17760
                                    let value = code % (codomain_len + 1);
301
17760
                                    code /= codomain_len + 1;
302
17760
                                    if value > 0 {
303
12288
                                        pairs.push((key, value - 1));
304
5472
                                    }
305
                                }
306
6912
                                let n = pairs.len() as i32;
307
6912
                                if !size.contains(&n)
308
3904
                                    || (partiality == PartialityAttr::Total && n != domain_len)
309
                                {
310
4304
                                    continue;
311
2608
                                }
312
                                let distinct =
313
2608
                                    pairs.iter().map(|(_, v)| v).collect::<BTreeSet<_>>().len()
314
                                        as i32;
315
2608
                                if matches!(
316
2608
                                    jectivity,
317
                                    JectivityAttr::Injective | JectivityAttr::Bijective
318
1304
                                ) && distinct != n
319
                                {
320
480
                                    continue;
321
2128
                                }
322
2128
                                if matches!(
323
2128
                                    jectivity,
324
                                    JectivityAttr::Surjective | JectivityAttr::Bijective
325
1064
                                ) && distinct != codomain_len
326
                                {
327
802
                                    continue;
328
1326
                                }
329
1326
                                assert!(
330
1326
                                    defined
331
2204
                                        .contains(&set(pairs.iter().map(|(k, _)| *k)))
332
                                        .unwrap(),
333
                                    "defined({source}): {pairs:?} outside {defined}"
334
                                );
335
1326
                                assert!(
336
2204
                                    range.contains(&set(pairs.iter().map(|(_, v)| *v))).unwrap(),
337
                                    "range({source}): {pairs:?} outside {range}"
338
                                );
339
1326
                                assert!(cardinality.contains(&Literal::Int(n)).unwrap());
340
1326
                                for (image, domain) in &images {
341
5672
                                    assert!(
342
5672
                                        domain
343
5672
                                            .contains(&set(pairs
344
5672
                                                .iter()
345
9908
                                                .filter(|(_, v)| v == image)
346
2204
                                                .map(|(k, _)| *k)))
347
                                            .unwrap(),
348
                                        "preImage({source},{image}): {pairs:?} outside {domain}"
349
                                    );
350
                                }
351
1326
                                for (mask, domain) in &restrictions {
352
7904
                                    let pairs = pairs
353
7904
                                        .iter()
354
14784
                                        .filter(|(k, _)| mask & (1 << k) != 0)
355
7392
                                        .map(|(k, v)| (Literal::Int(*k), Literal::Int(*v)))
356
                                        .collect::<Vec<_>>();
357
                                    let value =
358
7904
                                        Literal::AbstractLiteral(AbstractLiteral::Function(pairs));
359
7904
                                    assert!(
360
7904
                                        domain.contains(&value).unwrap(),
361
                                        "restrict({source},{mask}): {value} outside {domain}"
362
                                    );
363
7904
                                    let GroundDomain::Function(attrs, _, _) = domain.as_ref()
364
                                    else {
365
                                        unreachable!()
366
                                    };
367
                                    let Literal::AbstractLiteral(AbstractLiteral::Function(pairs)) =
368
7904
                                        value
369
                                    else {
370
                                        unreachable!()
371
                                    };
372
7904
                                    if attrs.partiality == PartialityAttr::Total {
373
1550
                                        assert_eq!(pairs.len() as u32, mask.count_ones());
374
6354
                                    }
375
7904
                                    if matches!(
376
7904
                                        attrs.jectivity,
377
                                        JectivityAttr::Surjective | JectivityAttr::Bijective
378
                                    ) {
379
                                        let mut images =
380
262
                                            pairs.iter().map(|(_, v)| v).collect::<Vec<_>>();
381
296
                                        images.sort_by(|a, b| a.essence_cmp(b));
382
262
                                        images.dedup();
383
262
                                        assert_eq!(images.len() as i32, codomain_len);
384
7642
                                    }
385
                                }
386
                            }
387
                        }
388
                    }
389
                }
390
            }
391
        }
392
1
    }
393

            
394
    #[test]
395
1
    fn restriction_drops_surjectivity_and_reduces_minimum_size() {
396
1
        let attrs = FuncAttr {
397
1
            size: Range::Single(8),
398
1
            partiality: PartialityAttr::Partial,
399
1
            jectivity: JectivityAttr::Surjective,
400
1
            representation: None,
401
1
        };
402
1
        let source = variable("f", Domain::function(attrs, ints(10), ints(3)));
403
1
        let result = restricted_domain(&source, &variable("D", ints(5))).unwrap();
404
1
        let (attrs, _, _) = result.as_function().unwrap();
405
1
        assert_eq!(attrs.size.resolve().unwrap(), Range::Bounded(3, 5));
406
1
        assert_eq!(attrs.jectivity, JectivityAttr::None);
407
1
    }
408

            
409
    #[test]
410
1
    fn preimage_query_can_be_outside_a_bijective_codomain() {
411
1
        let source = variable(
412
1
            "f",
413
1
            Domain::function(
414
1
                FuncAttr {
415
1
                    size: Range::<i32>::Unbounded,
416
1
                    partiality: PartialityAttr::Total,
417
1
                    jectivity: JectivityAttr::Bijective,
418
1
                    representation: None,
419
1
                },
420
1
                ints(2),
421
1
                ints(2),
422
1
            ),
423
1
        );
424
1
        let outside = pre_image_domain(&source, &Literal::Int(2).into()).unwrap();
425
1
        assert_eq!(outside.as_set_ground().unwrap().0.size, Range::Single(0));
426
1
        let query = variable("q", Domain::int(vec![Range::Bounded(0, 2)]));
427
1
        let mixed = pre_image_domain(&source, &query).unwrap();
428
1
        assert_eq!(mixed.as_set_ground().unwrap().0.size, Range::Bounded(0, 1));
429
1
    }
430

            
431
    #[test]
432
1
    fn image_set_is_singleton_only_for_guaranteed_defined_arguments() {
433
1
        let source = variable(
434
1
            "f",
435
1
            Domain::function(
436
1
                FuncAttr {
437
1
                    size: Range::<i32>::Unbounded,
438
1
                    partiality: PartialityAttr::Total,
439
1
                    jectivity: JectivityAttr::None,
440
1
                    representation: None,
441
1
                },
442
1
                ints(2),
443
1
                ints(3),
444
1
            ),
445
1
        );
446
1
        let inside = image_set_domain(&source, &Literal::Int(0).into()).unwrap();
447
1
        assert_eq!(inside.as_set_ground().unwrap().0.size, Range::Single(1));
448
1
        let outside = image_set_domain(&source, &Literal::Int(2).into()).unwrap();
449
1
        assert_eq!(outside.as_set_ground().unwrap().0.size, Range::Single(0));
450
1
    }
451

            
452
    #[test]
453
1
    fn symbolic_restriction_does_not_preserve_totality_or_surjectivity() {
454
1
        let n = IntVal::Reference(Reference::new(DeclarationPtr::new_given(
455
1
            Name::user("n"),
456
1
            ints(4),
457
1
        )));
458
1
        let target = Domain::int(vec![Range::Bounded(IntVal::from(0), n)]);
459
1
        let source = variable(
460
1
            "f",
461
1
            Domain::function(
462
1
                FuncAttr {
463
1
                    size: Range::<i32>::Unbounded,
464
1
                    partiality: PartialityAttr::Total,
465
1
                    jectivity: JectivityAttr::Bijective,
466
1
                    representation: None,
467
1
                },
468
1
                ints(3),
469
1
                ints(3),
470
1
            ),
471
1
        );
472
1
        let result = restricted_domain(&source, &variable("D", target)).unwrap();
473
1
        let (attrs, _, _) = result.as_function().unwrap();
474
1
        assert_eq!(attrs.size.resolve().unwrap(), Range::Bounded(0, 3));
475
1
        assert_eq!(attrs.partiality, PartialityAttr::Partial);
476
1
        assert_eq!(attrs.jectivity, JectivityAttr::Injective);
477
1
    }
478
}