Skip to main content

zincite_check/
sets.rs

1//! Compact finite integer sets. Canonical ranges never overlap or touch.
2
3use serde::{Deserialize, Serialize};
4use std::cmp::Ordering;
5
6/// Gecode Set::Limits, fixed by the pinned solver's integer limits.
7pub const GECODE_SET_MIN: i64 = -1073741822;
8pub const GECODE_SET_MAX: i64 = 1073741822;
9pub const GECODE_SET_CARDINALITY: i64 = 2147483645;
10
11use super::RelationMode;
12#[cfg(feature = "prepare")]
13use super::{ValueType, prepare::ArgumentType};
14
15#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
16#[serde(deny_unknown_fields)]
17struct IntegerRange {
18    #[serde(
19        serialize_with = "super::serialize_integer",
20        deserialize_with = "super::deserialize_integer"
21    )]
22    lower: i64,
23    #[serde(
24        serialize_with = "super::serialize_integer",
25        deserialize_with = "super::deserialize_integer"
26    )]
27    upper: i64,
28}
29
30/// A finite set of i64 values stored as sorted, disjoint inclusive ranges.
31/// JSON endpoints are decimal strings; loading rejects noncanonical ranges.
32#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)]
33pub struct IntegerSet {
34    ranges: Vec<IntegerRange>,
35}
36
37impl IntegerSet {
38    pub fn empty() -> Self {
39        Self::default()
40    }
41
42    /// A reversed interval denotes the empty set, as in MiniZinc.
43    pub fn interval(lower: i64, upper: i64) -> Self {
44        if lower > upper {
45            Self::empty()
46        } else {
47            Self {
48                ranges: vec![IntegerRange { lower, upper }],
49            }
50        }
51    }
52
53    pub fn from_values(values: impl IntoIterator<Item = i64>) -> Self {
54        Self::normalize(
55            values
56                .into_iter()
57                .map(|value| IntegerRange {
58                    lower: value,
59                    upper: value,
60                })
61                .collect(),
62        )
63    }
64
65    fn normalize(mut ranges: Vec<IntegerRange>) -> Self {
66        ranges.sort_unstable_by_key(|range| range.lower);
67        let mut merged: Vec<IntegerRange> = Vec::new();
68        for range in ranges {
69            if let Some(previous) = merged.last_mut()
70                && i128::from(range.lower) <= i128::from(previous.upper) + 1
71            {
72                previous.upper = previous.upper.max(range.upper);
73            } else {
74                merged.push(range);
75            }
76        }
77        Self { ranges: merged }
78    }
79
80    pub(crate) fn contiguous_bounds(&self) -> Option<(i64, i64)> {
81        match self.ranges.as_slice() {
82            [range] => Some((range.lower, range.upper)),
83            _ => None,
84        }
85    }
86
87    pub fn minimum(&self) -> Option<i64> {
88        self.ranges.first().map(|range| range.lower)
89    }
90
91    pub fn maximum(&self) -> Option<i64> {
92        self.ranges.last().map(|range| range.upper)
93    }
94
95    pub fn hull(&self) -> Self {
96        match (self.minimum(), self.maximum()) {
97            (Some(lower), Some(upper)) => Self::interval(lower, upper),
98            _ => Self::empty(),
99        }
100    }
101
102    pub fn is_convex(&self) -> bool {
103        self.ranges.len() <= 1
104    }
105
106    pub fn is_empty(&self) -> bool {
107        self.ranges.is_empty()
108    }
109
110    pub fn contains(&self, value: i64) -> bool {
111        let index = self.ranges.partition_point(|range| range.upper < value);
112        self.ranges
113            .get(index)
114            .is_some_and(|range| range.lower <= value)
115    }
116
117    pub fn is_subset(&self, other: &Self) -> bool {
118        let mut index = 0;
119        for range in &self.ranges {
120            while other
121                .ranges
122                .get(index)
123                .is_some_and(|candidate| candidate.upper < range.lower)
124            {
125                index += 1;
126            }
127            if !other.ranges.get(index).is_some_and(|candidate| {
128                candidate.lower <= range.lower && range.upper <= candidate.upper
129            }) {
130                return false;
131            }
132        }
133        true
134    }
135
136    pub fn union(&self, other: &Self) -> Self {
137        Self::normalize(self.ranges.iter().chain(&other.ranges).cloned().collect())
138    }
139
140    pub fn intersection(&self, other: &Self) -> Self {
141        let mut ranges = Vec::new();
142        let (mut left, mut right) = (0, 0);
143        while let (Some(a), Some(b)) = (self.ranges.get(left), other.ranges.get(right)) {
144            let (lower, upper) = (a.lower.max(b.lower), a.upper.min(b.upper));
145            if lower <= upper {
146                ranges.push(IntegerRange { lower, upper });
147            }
148            if a.upper < b.upper {
149                left += 1;
150            } else {
151                right += 1;
152            }
153        }
154        Self { ranges }
155    }
156
157    pub fn difference(&self, other: &Self) -> Self {
158        let mut ranges = Vec::new();
159        let mut right = 0;
160        for range in &self.ranges {
161            let mut lower = i128::from(range.lower);
162            let upper = i128::from(range.upper);
163            while other
164                .ranges
165                .get(right)
166                .is_some_and(|r| i128::from(r.upper) < lower)
167            {
168                right += 1;
169            }
170            let mut index = right;
171            while let Some(excluded) = other.ranges.get(index) {
172                if i128::from(excluded.lower) > upper {
173                    break;
174                }
175                if lower < i128::from(excluded.lower) {
176                    ranges.push(IntegerRange {
177                        lower: lower as i64,
178                        upper: excluded.lower - 1,
179                    });
180                }
181                lower = lower.max(i128::from(excluded.upper) + 1);
182                if lower > upper {
183                    break;
184                }
185                index += 1;
186            }
187            right = index;
188            if lower <= upper {
189                ranges.push(IntegerRange {
190                    lower: lower as i64,
191                    upper: range.upper,
192                });
193            }
194        }
195        Self { ranges }
196    }
197
198    pub fn symmetric_difference(&self, other: &Self) -> Self {
199        self.difference(other).union(&other.difference(self))
200    }
201
202    pub fn is_disjoint(&self, other: &Self) -> bool {
203        self.intersection(other).is_empty()
204    }
205
206    /// Compare the sorted element sequences, skipping each shared interval.
207    pub fn lex_cmp(&self, other: &Self) -> Ordering {
208        let (mut left, mut right) = (self.ranges.iter(), other.ranges.iter());
209        let (mut a, mut b) = (left.next().cloned(), right.next().cloned());
210        loop {
211            match (&mut a, &mut b) {
212                (None, None) => return Ordering::Equal,
213                (None, Some(_)) => return Ordering::Less,
214                (Some(_), None) => return Ordering::Greater,
215                (Some(x), Some(y)) => {
216                    if x.lower != y.lower {
217                        return x.lower.cmp(&y.lower);
218                    }
219                    match x.upper.cmp(&y.upper) {
220                        Ordering::Equal => {
221                            a = left.next().cloned();
222                            b = right.next().cloned();
223                        }
224                        Ordering::Less => {
225                            y.lower = x.upper + 1;
226                            a = left.next().cloned();
227                        }
228                        Ordering::Greater => {
229                            x.lower = y.upper + 1;
230                            b = right.next().cloned();
231                        }
232                    }
233                }
234            }
235        }
236    }
237
238    /// Gecode compares membership bits from the smallest differing element: 0 < 1.
239    pub fn characteristic_cmp(&self, other: &Self) -> Ordering {
240        let difference = self.symmetric_difference(other);
241        match difference.ranges.first() {
242            None => Ordering::Equal,
243            Some(range) if self.contains(range.lower) => Ordering::Greater,
244            Some(_) => Ordering::Less,
245        }
246    }
247
248    /// Callers must establish a small cardinality before enumerating members.
249    pub(crate) fn members(&self) -> impl Iterator<Item = i64> + '_ {
250        self.ranges
251            .iter()
252            .flat_map(|range| range.lower..=range.upper)
253    }
254
255    /// None means the mathematical cardinality exceeds i64::MAX.
256    pub fn cardinality(&self) -> Option<i64> {
257        let size: i128 = self
258            .ranges
259            .iter()
260            .map(|range| i128::from(range.upper) - i128::from(range.lower) + 1)
261            .sum();
262        i64::try_from(size).ok()
263    }
264}
265
266impl<'de> Deserialize<'de> for IntegerSet {
267    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
268        #[derive(Deserialize)]
269        #[serde(deny_unknown_fields)]
270        struct SetFile {
271            ranges: Vec<IntegerRange>,
272        }
273        let set = SetFile::deserialize(deserializer)?;
274        if set.ranges.iter().any(|range| range.lower > range.upper)
275            || set
276                .ranges
277                .windows(2)
278                .any(|pair| i128::from(pair[0].upper) + 1 >= i128::from(pair[1].lower))
279        {
280            return Err(serde::de::Error::custom(
281                "integer set ranges must be sorted, nonempty, disjoint and nonadjacent",
282            ));
283        }
284        Ok(Self { ranges: set.ranges })
285    }
286}
287
288impl std::fmt::Display for IntegerSet {
289    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
290        f.write_str("{")?;
291        for (index, range) in self.ranges.iter().enumerate() {
292            if index > 0 {
293                f.write_str(", ")?;
294            }
295            if range.lower == range.upper {
296                write!(f, "{}", range.lower)?;
297            } else {
298                write!(f, "{}..{}", range.lower, range.upper)?;
299            }
300        }
301        f.write_str("}")
302    }
303}
304
305/// Named MiniZinc order uses sorted elements; native codes use membership bits.
306#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
307#[serde(rename_all = "snake_case")]
308pub enum SetRelation {
309    Equal,
310    NotEqual,
311    Subset,
312    Superset,
313    LessEqual,
314    Less,
315    Disjoint,
316    Complement,
317    CharacteristicLessEqual,
318    CharacteristicLess,
319    CharacteristicGreaterEqual,
320    CharacteristicGreater,
321}
322
323impl SetRelation {
324    pub(crate) fn from_code(code: i64) -> Option<Self> {
325        Some(match code {
326            1 => Self::Equal,
327            2 => Self::NotEqual,
328            3 => Self::Subset,
329            4 => Self::Superset,
330            5 => Self::Disjoint,
331            6 => Self::Complement,
332            7 => Self::CharacteristicLessEqual,
333            8 => Self::CharacteristicLess,
334            9 => Self::CharacteristicGreaterEqual,
335            10 => Self::CharacteristicGreater,
336            _ => return None,
337        })
338    }
339
340    pub(crate) fn holds(self, left: &IntegerSet, right: &IntegerSet) -> bool {
341        match self {
342            Self::Equal => left == right,
343            Self::NotEqual => left != right,
344            Self::Subset => left.is_subset(right),
345            Self::Superset => right.is_subset(left),
346            Self::LessEqual => left.lex_cmp(right).is_le(),
347            Self::Less => left.lex_cmp(right).is_lt(),
348            Self::Disjoint => left.is_disjoint(right),
349            Self::Complement => {
350                let universe = IntegerSet::interval(GECODE_SET_MIN, GECODE_SET_MAX);
351                left.is_subset(&universe)
352                    && right.is_subset(&universe)
353                    && left.is_disjoint(right)
354                    && left.union(right) == universe
355            }
356            Self::CharacteristicLessEqual => left.characteristic_cmp(right).is_le(),
357            Self::CharacteristicLess => left.characteristic_cmp(right).is_lt(),
358            Self::CharacteristicGreaterEqual => left.characteristic_cmp(right).is_ge(),
359            Self::CharacteristicGreater => left.characteristic_cmp(right).is_gt(),
360        }
361    }
362}
363
364impl std::fmt::Display for SetRelation {
365    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
366        f.write_str(match self {
367            Self::Equal => "=",
368            Self::NotEqual => "!=",
369            Self::Subset => "subset",
370            Self::Superset => "superset",
371            Self::LessEqual => "<= (sorted elements)",
372            Self::Less => "< (sorted elements)",
373            Self::Disjoint => "disjoint",
374            Self::Complement => "complement (-1073741822..1073741822)",
375            Self::CharacteristicLessEqual => "<= (characteristic order)",
376            Self::CharacteristicLess => "< (characteristic order)",
377            Self::CharacteristicGreaterEqual => ">= (characteristic order)",
378            Self::CharacteristicGreater => "> (characteristic order)",
379        })
380    }
381}
382
383#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
384#[serde(rename_all = "snake_case")]
385pub enum SetOperation {
386    Union,
387    Intersection,
388    Difference,
389    SymmetricDifference,
390}
391
392impl SetOperation {
393    pub(crate) fn calculate(self, left: &IntegerSet, right: &IntegerSet) -> IntegerSet {
394        match self {
395            Self::Union => left.union(right),
396            Self::Intersection => left.intersection(right),
397            Self::Difference => left.difference(right),
398            Self::SymmetricDifference => left.symmetric_difference(right),
399        }
400    }
401}
402
403impl std::fmt::Display for SetOperation {
404    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
405        f.write_str(match self {
406            Self::Union => "union",
407            Self::Intersection => "intersect",
408            Self::Difference => "diff",
409            Self::SymmetricDifference => "symdiff",
410        })
411    }
412}
413
414#[derive(Clone, Copy, Debug, PartialEq, Eq)]
415pub(crate) enum SetKind {
416    Relation(SetRelation),
417    Membership { fixed: bool },
418    Cardinality,
419    Operation(SetOperation),
420    NativeRelation,
421}
422
423#[derive(Clone, Copy, Debug, PartialEq, Eq)]
424pub(crate) struct SetName {
425    pub kind: SetKind,
426    pub mode: RelationMode,
427}
428
429impl SetName {
430    pub fn from_name(name: &str) -> Option<Self> {
431        use RelationMode::*;
432        use SetKind::*;
433        use SetOperation::*;
434        use SetRelation::*;
435        let (kind, mode) = match name {
436            "disjoint" | "gecode_disjoint" | "fzn_disjoint" => (Relation(Disjoint), Plain),
437            "set_eq" | "gecode_set_eq" | "fzn_set_eq" => (Relation(Equal), Plain),
438            "set_eq_reif" | "gecode_set_eq_reif" | "fzn_set_eq_reif" => (Relation(Equal), Reified),
439            "set_ne" | "gecode_set_ne" | "fzn_set_ne" => (Relation(NotEqual), Plain),
440            "set_ne_reif" | "gecode_set_ne_reif" | "fzn_set_ne_reif" => {
441                (Relation(NotEqual), Reified)
442            }
443            "set_le" | "gecode_set_le" | "fzn_set_le" => (Relation(LessEqual), Plain),
444            "set_le_reif" | "gecode_set_le_reif" | "fzn_set_le_reif" => {
445                (Relation(LessEqual), Reified)
446            }
447            "set_lt" | "gecode_set_lt" | "fzn_set_lt" => (Relation(Less), Plain),
448            "set_lt_reif" | "gecode_set_lt_reif" | "fzn_set_lt_reif" => (Relation(Less), Reified),
449            "set_subset" | "gecode_set_subset" | "fzn_set_subset" => (Relation(Subset), Plain),
450            "set_subset_reif" | "gecode_set_subset_reif" | "fzn_set_subset_reif" => {
451                (Relation(Subset), Reified)
452            }
453            "set_superset" | "gecode_set_superset" | "fzn_set_superset" => {
454                (Relation(Superset), Plain)
455            }
456            "set_superset_reif" | "gecode_set_superset_reif" | "fzn_set_superset_reif" => {
457                (Relation(Superset), Reified)
458            }
459            "equal" | "gecode_equal" | "fzn_equal" => (Relation(Equal), Plain),
460            "equal_reif" | "gecode_equal_reif" | "fzn_equal_reif" => (Relation(Equal), Reified),
461            "set_in" | "gecode_set_in" | "fzn_set_in" => (Membership { fixed: false }, Plain),
462            "set_in_reif" | "gecode_set_in_reif" | "fzn_set_in_reif" => {
463                (Membership { fixed: false }, Reified)
464            }
465            "set_in_imp" | "gecode_set_in_imp" | "fzn_set_in_imp" => {
466                (Membership { fixed: false }, Implied)
467            }
468            "int_in" | "gecode_int_in" | "fzn_int_in" => (Membership { fixed: true }, Plain),
469            "int_in_reif" | "gecode_int_in_reif" | "fzn_int_in_reif" => {
470                (Membership { fixed: true }, Reified)
471            }
472            "int_in_imp" | "gecode_int_in_imp" | "fzn_int_in_imp" => {
473                (Membership { fixed: true }, Implied)
474            }
475            "set_union" | "gecode_set_union" | "fzn_set_union" => (Operation(Union), Plain),
476            "set_intersect" | "gecode_set_intersect" | "fzn_set_intersect" => {
477                (Operation(Intersection), Plain)
478            }
479            "set_diff" | "gecode_set_diff" | "fzn_set_diff" => (Operation(Difference), Plain),
480            "set_symdiff" | "gecode_set_symdiff" | "fzn_set_symdiff" => {
481                (Operation(SymmetricDifference), Plain)
482            }
483            "set_card" | "gecode_set_card" | "fzn_set_card" => (Cardinality, Plain),
484            "gecode_set_rel_imp_fzn"
485            | "gecode_gecode_set_rel_imp_fzn"
486            | "fzn_gecode_set_rel_imp_fzn" => (NativeRelation, Implied),
487            "set_eq_imp" => (Relation(Equal), Implied),
488            "set_ne_imp" => (Relation(NotEqual), Implied),
489            "set_le_imp" => (Relation(LessEqual), Implied),
490            "set_lt_imp" => (Relation(Less), Implied),
491            "set_subset_imp" => (Relation(Subset), Implied),
492            "set_superset_imp" => (Relation(Superset), Implied),
493            _ => return None,
494        };
495        Some(Self { kind, mode })
496    }
497
498    #[cfg(feature = "prepare")]
499    pub fn signature(self, element: ValueType) -> Vec<ArgumentType> {
500        let scalar = |kind, is_var| ArgumentType {
501            kind,
502            is_var,
503            is_array: false,
504        };
505        let set = scalar(ValueType::IntegerSet, true);
506        let mut arguments = match self.kind {
507            SetKind::Relation(_) => vec![set; 2],
508            SetKind::Membership { fixed } => vec![
509                scalar(element, true),
510                scalar(
511                    ValueType::IntegerSet,
512                    !fixed && element != ValueType::Boolean,
513                ),
514            ],
515            SetKind::Cardinality => vec![set, scalar(ValueType::Integer, true)],
516            SetKind::Operation(_) => vec![set; 3],
517            SetKind::NativeRelation => vec![set.clone(), scalar(ValueType::Integer, false), set],
518        };
519        if self.mode != RelationMode::Plain {
520            arguments.push(scalar(ValueType::Boolean, true));
521        }
522        arguments
523    }
524}