Skip to main content

zincite_check/
selection.rs

1//! Exact selection signatures and flat-array layouts shared by admission and checking.
2
3use super::*;
4
5#[derive(Clone, Copy, Debug, PartialEq, Eq)]
6pub(crate) enum SelectionForm {
7    OneBased,
8    Offset,
9    Matrix,
10    SetMatrix,
11    Conditional,
12}
13
14#[derive(Clone, Copy, Debug)]
15pub(crate) struct SelectionName {
16    pub kind: ValueType,
17    pub form: SelectionForm,
18    pub allows_variables: bool,
19}
20
21impl SelectionName {
22    pub fn from_name(name: &str) -> Option<Self> {
23        Some(match name {
24            "array_bool_element" | "gecode_array_bool_element" | "fzn_array_bool_element" => Self {
25                kind: ValueType::Boolean,
26                form: SelectionForm::OneBased,
27                allows_variables: false,
28            },
29            "array_var_bool_element"
30            | "gecode_array_var_bool_element"
31            | "fzn_array_var_bool_element" => Self {
32                kind: ValueType::Boolean,
33                form: SelectionForm::OneBased,
34                allows_variables: true,
35            },
36            "array_var_bool_element_nonshifted" => Self {
37                kind: ValueType::Boolean,
38                form: SelectionForm::OneBased,
39                allows_variables: true,
40            },
41            "array_int_element" | "gecode_array_int_element" | "fzn_array_int_element" => Self {
42                kind: ValueType::Integer,
43                form: SelectionForm::OneBased,
44                allows_variables: false,
45            },
46            "array_var_int_element"
47            | "gecode_array_var_int_element"
48            | "fzn_array_var_int_element" => Self {
49                kind: ValueType::Integer,
50                form: SelectionForm::OneBased,
51                allows_variables: true,
52            },
53            "array_var_int_element_nonshifted" => Self {
54                kind: ValueType::Integer,
55                form: SelectionForm::OneBased,
56                allows_variables: true,
57            },
58            "array_set_element" | "gecode_array_set_element" | "fzn_array_set_element" => Self {
59                kind: ValueType::IntegerSet,
60                form: SelectionForm::OneBased,
61                allows_variables: false,
62            },
63            "array_var_set_element"
64            | "gecode_array_var_set_element"
65            | "fzn_array_var_set_element" => Self {
66                kind: ValueType::IntegerSet,
67                form: SelectionForm::OneBased,
68                allows_variables: true,
69            },
70            "array_var_set_element_nonshifted" => Self {
71                kind: ValueType::IntegerSet,
72                form: SelectionForm::OneBased,
73                allows_variables: true,
74            },
75            "array_float_element" => Self {
76                kind: ValueType::Float,
77                form: SelectionForm::OneBased,
78                allows_variables: false,
79            },
80            "array_var_float_element" => Self {
81                kind: ValueType::Float,
82                form: SelectionForm::OneBased,
83                allows_variables: true,
84            },
85            "array_var_float_element_nonshifted" => Self {
86                kind: ValueType::Float,
87                form: SelectionForm::OneBased,
88                allows_variables: true,
89            },
90            "gecode_int_element" | "gecode_gecode_int_element" | "fzn_gecode_int_element" => Self {
91                kind: ValueType::Integer,
92                form: SelectionForm::Offset,
93                allows_variables: true,
94            },
95            "gecode_var_int_element"
96            | "gecode_gecode_var_int_element"
97            | "fzn_gecode_var_int_element" => Self {
98                kind: ValueType::Integer,
99                form: SelectionForm::Offset,
100                allows_variables: true,
101            },
102            "gecode_int_element2d" | "gecode_gecode_int_element2d" | "fzn_gecode_int_element2d" => {
103                Self {
104                    kind: ValueType::Integer,
105                    form: SelectionForm::Matrix,
106                    allows_variables: true,
107                }
108            }
109            "gecode_bool_element" | "gecode_gecode_bool_element" | "fzn_gecode_bool_element" => {
110                Self {
111                    kind: ValueType::Boolean,
112                    form: SelectionForm::Offset,
113                    allows_variables: true,
114                }
115            }
116            "gecode_var_bool_element"
117            | "gecode_gecode_var_bool_element"
118            | "fzn_gecode_var_bool_element" => Self {
119                kind: ValueType::Boolean,
120                form: SelectionForm::Offset,
121                allows_variables: true,
122            },
123            "gecode_bool_element2d"
124            | "gecode_gecode_bool_element2d"
125            | "fzn_gecode_bool_element2d" => Self {
126                kind: ValueType::Boolean,
127                form: SelectionForm::Matrix,
128                allows_variables: true,
129            },
130            "gecode_set_element2d" | "gecode_gecode_set_element2d" | "fzn_gecode_set_element2d" => {
131                Self {
132                    kind: ValueType::IntegerSet,
133                    form: SelectionForm::SetMatrix,
134                    allows_variables: true,
135                }
136            }
137            "gecode_bool_ite" | "gecode_gecode_bool_ite" | "fzn_gecode_bool_ite" => Self {
138                kind: ValueType::Boolean,
139                form: SelectionForm::Conditional,
140                allows_variables: true,
141            },
142            "gecode_int_ite" | "gecode_gecode_int_ite" | "fzn_gecode_int_ite" => Self {
143                kind: ValueType::Integer,
144                form: SelectionForm::Conditional,
145                allows_variables: true,
146            },
147            "gecode_set_ite" | "gecode_gecode_set_ite" | "fzn_gecode_set_ite" => Self {
148                kind: ValueType::IntegerSet,
149                form: SelectionForm::Conditional,
150                allows_variables: true,
151            },
152            "gecode_float_ite" | "gecode_gecode_float_ite" | "fzn_gecode_float_ite" => Self {
153                kind: ValueType::Float,
154                form: SelectionForm::Conditional,
155                allows_variables: true,
156            },
157            _ => return None,
158        })
159    }
160
161    #[cfg(feature = "prepare")]
162    pub fn arity(self) -> usize {
163        match self.form {
164            SelectionForm::OneBased => 3,
165            SelectionForm::Offset | SelectionForm::Conditional => 4,
166            SelectionForm::Matrix | SelectionForm::SetMatrix => 6,
167        }
168    }
169
170    #[cfg(feature = "prepare")]
171    pub fn signature(self) -> Vec<prepare::ArgumentType> {
172        let scalar = |kind, is_var| prepare::ArgumentType {
173            kind,
174            is_var,
175            is_array: false,
176        };
177        let array = prepare::ArgumentType {
178            kind: self.kind,
179            is_var: self.allows_variables,
180            is_array: true,
181        };
182        let index = scalar(ValueType::Integer, true);
183        let fixed_int = scalar(ValueType::Integer, false);
184        let result = scalar(self.kind, true);
185        match self.form {
186            SelectionForm::OneBased => vec![index, array, result],
187            SelectionForm::Offset => vec![index, fixed_int, array, result],
188            SelectionForm::Matrix => vec![
189                index.clone(),
190                index,
191                array,
192                scalar(ValueType::IntegerSet, false),
193                scalar(ValueType::IntegerSet, false),
194                result,
195            ],
196            SelectionForm::SetMatrix => vec![
197                array,
198                index.clone(),
199                fixed_int.clone(),
200                index,
201                fixed_int,
202                result,
203            ],
204            SelectionForm::Conditional => {
205                vec![
206                    scalar(ValueType::Boolean, true),
207                    result.clone(),
208                    result.clone(),
209                    result,
210                ]
211            }
212        }
213    }
214}
215
216/// The fixed indexing contract of a flattened element relation.
217#[derive(Clone, Debug, Serialize, Deserialize)]
218#[serde(tag = "kind", rename_all = "snake_case")]
219pub enum ElementLayout {
220    OneBased,
221    Offset {
222        #[serde(
223            serialize_with = "serialize_integer",
224            deserialize_with = "deserialize_integer"
225        )]
226        offset: i64,
227    },
228    Matrix {
229        rows: IntegerSet,
230        columns: IntegerSet,
231    },
232    SetMatrix {
233        #[serde(
234            serialize_with = "serialize_integer",
235            deserialize_with = "deserialize_integer"
236        )]
237        width: i64,
238        #[serde(
239            serialize_with = "serialize_integer",
240            deserialize_with = "deserialize_integer"
241        )]
242        height: i64,
243    },
244}
245
246impl std::fmt::Display for ElementLayout {
247    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
248        match self {
249            Self::OneBased => f.write_str("one-based positions"),
250            Self::Offset { offset } => write!(f, "offset {offset}"),
251            Self::Matrix { rows, columns } => write!(f, "rows {rows}, columns {columns}"),
252            Self::SetMatrix { width, height } => {
253                write!(f, "zero-based matrix with width {width}, height {height}")
254            }
255        }
256    }
257}
258
259pub(crate) enum SelectionPosition {
260    Missing,
261    OutOfRange,
262    Selected(usize),
263}
264
265impl ElementLayout {
266    pub(crate) fn form(&self) -> SelectionForm {
267        match self {
268            Self::OneBased => SelectionForm::OneBased,
269            Self::Offset { .. } => SelectionForm::Offset,
270            Self::Matrix { .. } => SelectionForm::Matrix,
271            Self::SetMatrix { .. } => SelectionForm::SetMatrix,
272        }
273    }
274
275    pub(crate) fn index_count(&self) -> usize {
276        match self {
277            Self::OneBased | Self::Offset { .. } => 1,
278            Self::Matrix { .. } | Self::SetMatrix { .. } => 2,
279        }
280    }
281
282    pub(crate) fn validate(&self, length: usize) -> Result<(), &'static str> {
283        let dimensions = match self {
284            Self::OneBased | Self::Offset { .. } => return Ok(()),
285            Self::Matrix { rows, columns } => {
286                let bounds = |set: &IntegerSet| {
287                    let (lower, upper) = set
288                        .contiguous_bounds()
289                        .ok_or("element index sets must be nonempty and contiguous")?;
290                    usize::try_from(i128::from(upper) - i128::from(lower) + 1)
291                        .map_err(|_| "element dimensions exceed this target's addressable size")
292                };
293                (bounds(rows)?, bounds(columns)?)
294            }
295            Self::SetMatrix { width, height } => {
296                if *width <= 0 || *height <= 0 {
297                    return Err("element width and height must be positive");
298                }
299                let size = |value| {
300                    usize::try_from(value)
301                        .map_err(|_| "element dimensions exceed this target's addressable size")
302                };
303                (size(*height)?, size(*width)?)
304            }
305        };
306        if dimensions.0.checked_mul(dimensions.1) != Some(length) {
307            return Err("element matrix dimensions must match the flat array length");
308        }
309        Ok(())
310    }
311
312    pub(crate) fn position(
313        &self,
314        indices: &[Operand],
315        length: usize,
316        values: &[Option<Value>],
317    ) -> SelectionPosition {
318        let axes = match self {
319            Self::OneBased => vec![(1, length as i128)],
320            Self::Offset { offset } => vec![(i128::from(*offset), length as i128)],
321            Self::Matrix { rows, columns } => [rows, columns]
322                .map(|set| {
323                    let (lower, upper) =
324                        set.contiguous_bounds().expect("admitted matrix index sets");
325                    (i128::from(lower), i128::from(upper) - i128::from(lower) + 1)
326                })
327                .to_vec(),
328            Self::SetMatrix { width, height } => {
329                vec![(0, i128::from(*height)), (0, i128::from(*width))]
330            }
331        };
332        let mut has_missing_index = false;
333        let mut position = 0;
334        for (index, (lower, size)) in indices.iter().zip(axes) {
335            let Some(value) = index.value(values) else {
336                has_missing_index = true;
337                continue;
338            };
339            let relative = i128::from(value.integer()) - lower;
340            if relative < 0 || relative >= size {
341                return SelectionPosition::OutOfRange;
342            }
343            // Admission checks the rectangular product; each axis is checked before flattening.
344            position = position * size as usize + relative as usize;
345        }
346        if has_missing_index {
347            SelectionPosition::Missing
348        } else {
349            SelectionPosition::Selected(position)
350        }
351    }
352}
353
354impl Predicate {
355    pub(crate) fn selected_operand<'a>(&'a self, values: &[Option<Value>]) -> Option<&'a Operand> {
356        match self {
357            Self::Element {
358                indices,
359                elements,
360                layout,
361                ..
362            } => match layout.position(indices, elements.len(), values) {
363                SelectionPosition::Selected(position) => Some(&elements[position]),
364                _ => None,
365            },
366            Self::Conditional {
367                condition,
368                then_value,
369                else_value,
370                ..
371            } => Some(if condition.value(values)?.boolean() {
372                then_value
373            } else {
374                else_value
375            }),
376            _ => None,
377        }
378    }
379}