1use super::*;
3use std::collections::BTreeMap;
4
5#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
6#[serde(rename_all = "snake_case")]
7pub enum SetAggregate {
8 Union,
9 Intersection,
10 Partition,
11 SequenceUnion,
12}
13
14#[derive(Clone, Copy, Debug, PartialEq, Eq)]
15pub(crate) enum SetGlobalName {
16 Aggregate(SetAggregate),
17 Convex,
18 Hull,
19 Sequence,
20 AtMostOne,
21 Weights,
22 Extremum { maximum: bool, mode: RelationMode },
23}
24impl SetGlobalName {
25 pub fn from_name(name: &str) -> Option<Self> {
26 for base in [
28 name,
29 name.strip_prefix("gecode_").unwrap_or(""),
30 name.strip_prefix("fzn_").unwrap_or(""),
31 ] {
32 let found = match base {
33 "array_set_union" => Self::Aggregate(SetAggregate::Union),
34 "array_set_partition" => Self::Aggregate(SetAggregate::Partition),
35 "array_set_seq_union" => Self::Aggregate(SetAggregate::SequenceUnion),
36 "gecode_array_set_intersect" => Self::Aggregate(SetAggregate::Intersection),
37 "set_convex" => Self::Convex,
38 "array_set_seq" => Self::Sequence,
39 "gecode_set_convex_hull" => Self::Hull,
40 "gecode_set_atmost_one" => Self::AtMostOne,
41 "gecode_set_weights" => Self::Weights,
42 "gecode_set_min" => Self::Extremum {
43 maximum: false,
44 mode: RelationMode::Plain,
45 },
46 "gecode_set_max" => Self::Extremum {
47 maximum: true,
48 mode: RelationMode::Plain,
49 },
50 "gecode_set_min_reif" => Self::Extremum {
51 maximum: false,
52 mode: RelationMode::Reified,
53 },
54 "gecode_set_max_reif" => Self::Extremum {
55 maximum: true,
56 mode: RelationMode::Reified,
57 },
58 "gecode_set_min_imp" => Self::Extremum {
59 maximum: false,
60 mode: RelationMode::Implied,
61 },
62 "gecode_set_max_imp" => Self::Extremum {
63 maximum: true,
64 mode: RelationMode::Implied,
65 },
66 _ => continue,
67 };
68 return Some(found);
69 }
70 None
71 }
72 #[cfg(feature = "prepare")]
73 pub fn signature(self) -> Vec<prepare::ArgumentType> {
74 let scalar = |kind, is_var| prepare::ArgumentType {
75 kind,
76 is_var,
77 is_array: false,
78 };
79 let array = |kind, is_var| prepare::ArgumentType {
80 kind,
81 is_var,
82 is_array: true,
83 };
84 let set = scalar(ValueType::IntegerSet, true);
85 let sets = array(ValueType::IntegerSet, true);
86 match self {
87 Self::Aggregate(_) => vec![sets, set],
88 Self::Convex => vec![set],
89 Self::Hull => vec![set.clone(), set],
90 Self::Sequence => vec![sets],
91 Self::AtMostOne => vec![sets, scalar(ValueType::Integer, false)],
92 Self::Weights => vec![
93 array(ValueType::Integer, false),
94 array(ValueType::Integer, false),
95 set,
96 scalar(ValueType::Integer, true),
97 ],
98 Self::Extremum { mode, .. } => {
99 let mut args = vec![set, scalar(ValueType::Integer, true)];
100 if mode != RelationMode::Plain {
101 args.push(scalar(ValueType::Boolean, true));
102 }
103 args
104 }
105 }
106 }
107}
108
109#[derive(Debug, Serialize, Deserialize)]
110pub(crate) enum SetGlobal {
111 Aggregate {
112 operation: SetAggregate,
113 entries: Vec<Operand>,
114 result: Operand,
115 },
116 Convex {
117 set: Operand,
118 },
119 Hull {
120 set: Operand,
121 result: Operand,
122 },
123 Sequence {
124 entries: Vec<Operand>,
125 },
126 AtMostOne {
127 entries: Vec<Operand>,
128 #[serde(
129 serialize_with = "serialize_integer",
130 deserialize_with = "deserialize_integer"
131 )]
132 cardinality: i64,
133 },
134 Weights {
135 #[serde(
136 serialize_with = "serialize_integers",
137 deserialize_with = "deserialize_integers"
138 )]
139 elements: Vec<i64>,
140 #[serde(
141 serialize_with = "serialize_integers",
142 deserialize_with = "deserialize_integers"
143 )]
144 weights: Vec<i64>,
145 set: Operand,
146 result: Operand,
147 },
148 Extremum {
149 set: Operand,
150 integer: Operand,
151 maximum: bool,
152 condition: RelationCondition,
153 },
154}
155
156#[derive(Clone, Debug, Serialize)]
158pub struct SetArgument {
159 pub position: Option<usize>,
160 pub value: Option<ArgumentValue>,
161 pub unresolved: Option<Box<VariableRef>>,
162}
163#[derive(Clone, Debug, Serialize)]
164pub struct SetPair {
165 pub first: usize,
166 pub second: usize,
167}
168#[derive(Clone, Debug, Serialize)]
169#[serde(tag = "kind", rename_all = "snake_case")]
170pub enum SetCardinalityFault {
171 Cardinality {
172 position: usize,
173 #[serde(serialize_with = "serialize_integer")]
174 actual: i64,
175 },
176 Intersection {
177 first: usize,
178 second: usize,
179 #[serde(serialize_with = "serialize_integer")]
180 actual: i64,
181 },
182}
183#[derive(Clone, Debug, Serialize)]
184pub struct SetWeight {
185 pub position: usize,
186 #[serde(serialize_with = "serialize_integer")]
187 pub element: i64,
188 #[serde(serialize_with = "serialize_integer")]
189 pub weight: i64,
190}
191#[derive(Clone, Debug, Serialize)]
192#[serde(tag = "operation", rename_all = "snake_case")]
193pub enum SetGlobalFinding {
194 Aggregate {
195 aggregate: SetAggregate,
196 entries: Vec<SetArgument>,
197 result: SetArgument,
198 expected: Option<IntegerSet>,
199 conflicts: Vec<SetPair>,
200 },
201 Convex {
202 set: SetArgument,
203 hull: IntegerSet,
204 },
205 Hull {
206 set: SetArgument,
207 result: SetArgument,
208 expected: IntegerSet,
209 },
210 Sequence {
211 entries: Vec<SetArgument>,
212 conflicts: Vec<SetPair>,
213 },
214 AtMostOne {
215 entries: Vec<SetArgument>,
216 #[serde(serialize_with = "serialize_integer")]
217 cardinality: i64,
218 faults: Vec<SetCardinalityFault>,
219 },
220 Weights {
221 rows: Vec<SetWeight>,
222 set: SetArgument,
223 result: SetArgument,
224 uncovered: IntegerSet,
225 #[serde(serialize_with = "serialize_optional_integer")]
226 expected: Option<i64>,
227 },
228 Extremum {
229 set: SetArgument,
230 integer: SetArgument,
231 maximum: bool,
232 #[serde(serialize_with = "serialize_optional_integer")]
233 expected: Option<i64>,
234 truth: bool,
235 condition: RelationConditionValue,
236 },
237}
238
239impl SetGlobal {
240 pub fn name(&self) -> SetGlobalName {
241 match self {
242 Self::Aggregate { operation, .. } => SetGlobalName::Aggregate(*operation),
243 Self::Convex { .. } => SetGlobalName::Convex,
244 Self::Hull { .. } => SetGlobalName::Hull,
245 Self::Sequence { .. } => SetGlobalName::Sequence,
246 Self::AtMostOne { .. } => SetGlobalName::AtMostOne,
247 Self::Weights { .. } => SetGlobalName::Weights,
248 Self::Extremum {
249 maximum, condition, ..
250 } => SetGlobalName::Extremum {
251 maximum: *maximum,
252 mode: condition.mode(),
253 },
254 }
255 }
256 pub fn validate_shape(&self) -> Result<(), &'static str> {
257 match self {
258 Self::AtMostOne { cardinality, .. } if *cardinality < 0 => {
259 Err("set cardinality must be nonnegative")
260 }
261 Self::Weights {
262 elements, weights, ..
263 } if elements.len() != weights.len() => {
264 Err("set element and weight arrays must have equal lengths")
265 }
266 _ => Ok(()),
267 }
268 }
269 pub fn operands(&self) -> Vec<&Operand> {
270 match self {
271 Self::Aggregate {
272 entries, result, ..
273 } => entries.iter().chain(std::iter::once(result)).collect(),
274 Self::Convex { set } => vec![set],
275 Self::Hull { set, result } | Self::Weights { set, result, .. } => vec![set, result],
276 Self::Sequence { entries } | Self::AtMostOne { entries, .. } => {
277 entries.iter().collect()
278 }
279 Self::Extremum {
280 set,
281 integer,
282 condition,
283 ..
284 } => [set, integer]
285 .into_iter()
286 .chain(condition.operand())
287 .collect(),
288 }
289 }
290 pub fn inactive(&self, values: &[Option<Value>]) -> bool {
291 matches!(self, Self::Extremum { condition, .. } if condition.inactive(values))
292 }
293 pub fn required_operands(&self, values: &[Option<Value>]) -> Vec<&Operand> {
294 match self {
295 Self::Aggregate {
296 operation, entries, ..
297 } if !aggregate_conflicts(*operation, entries, values).is_empty() => Vec::new(),
298 Self::Sequence { entries }
299 if entries.len() < 2 || !sequence_conflicts(entries, values).is_empty() =>
300 {
301 Vec::new()
302 }
303 Self::AtMostOne {
304 entries,
305 cardinality,
306 } if entries.iter().any(|entry| {
307 entry
308 .value(values)
309 .is_some_and(|value| value.set().cardinality() != Some(*cardinality))
310 }) || has_intersection_fault(entries, values) =>
311 {
312 Vec::new()
313 }
314 Self::Weights { elements, set, .. }
315 if set
316 .value(values)
317 .is_some_and(|value| !uncovered(elements, value.set()).is_empty()) =>
318 {
319 vec![set]
320 }
321 Self::Extremum { set, condition, .. }
322 if set
323 .value(values)
324 .is_some_and(|value| value.set().is_empty()) =>
325 {
326 std::iter::once(set).chain(condition.operand()).collect()
327 }
328 _ => self.operands(),
329 }
330 }
331 fn output(&self) -> Option<&Operand> {
332 match self {
333 Self::Aggregate { result, .. }
334 | Self::Hull { result, .. }
335 | Self::Weights { result, .. } => Some(result),
336 Self::Extremum {
337 integer,
338 condition: RelationCondition::Plain,
339 ..
340 } => Some(integer),
341 Self::Extremum {
342 condition: RelationCondition::Reified { result },
343 ..
344 } => Some(result),
345 _ => None,
346 }
347 }
348 pub fn can_define(&self, output: usize) -> bool {
349 matches!(self.output(), Some(Operand::Variable(slot)) if *slot == output)
350 && self
351 .operands()
352 .iter()
353 .filter(|operand| matches!(operand, Operand::Variable(slot) if *slot == output))
354 .count()
355 == 1
356 }
357 pub fn derive(
358 &self,
359 values: &[Option<Value>],
360 constraint: &Constraint,
361 ) -> Result<Option<Value>, CheckError> {
362 Ok(match self {
363 Self::Aggregate {
364 operation, entries, ..
365 } => aggregate(*operation, entries, values).map(Value::IntegerSet),
366 Self::Hull { set, .. } => set
367 .value(values)
368 .map(|value| Value::IntegerSet(value.set().hull())),
369 Self::Weights {
370 elements,
371 weights,
372 set,
373 ..
374 } => {
375 let Some(set) = set.value(values) else {
376 return Ok(None);
377 };
378 weighted_sum(elements, weights, set.set(), constraint)?.map(Value::Integer)
379 }
380 Self::Extremum {
381 set,
382 integer,
383 maximum,
384 condition,
385 } => {
386 let Some(set) = set.value(values) else {
387 return Ok(None);
388 };
389 let best = extremum(set.set(), *maximum);
390 match condition {
391 RelationCondition::Plain => best.map(Value::Integer),
392 RelationCondition::Reified { .. } => {
393 let truth = match best {
394 None => Some(false),
395 Some(best) => {
396 integer.value(values).map(|value| value.integer() == best)
397 }
398 };
399 truth.map(Value::Boolean)
400 }
401 RelationCondition::Implied { .. } => None,
402 }
403 }
404 _ => None,
405 })
406 }
407}
408fn aggregate(
409 operation: SetAggregate,
410 entries: &[Operand],
411 values: &[Option<Value>],
412) -> Option<IntegerSet> {
413 let mut concrete = entries
414 .iter()
415 .map(|entry| entry.value(values).map(Value::set));
416 let mut result = if operation == SetAggregate::Intersection {
417 match concrete.next() {
418 Some(first) => first?.clone(),
419 None => IntegerSet::interval(GECODE_SET_MIN, GECODE_SET_MAX),
420 }
421 } else {
422 IntegerSet::empty()
423 };
424 for set in concrete {
425 result = if operation == SetAggregate::Intersection {
426 result.intersection(set?)
427 } else {
428 result.union(set?)
429 };
430 }
431 Some(result)
432}
433fn aggregate_conflicts(
434 operation: SetAggregate,
435 entries: &[Operand],
436 values: &[Option<Value>],
437) -> Vec<SetPair> {
438 match operation {
439 SetAggregate::Partition => pairs(entries, values, |a, b| !a.is_disjoint(b)),
440 SetAggregate::SequenceUnion => sequence_conflicts(entries, values),
441 _ => Vec::new(),
442 }
443}
444fn pairs(
445 entries: &[Operand],
446 values: &[Option<Value>],
447 conflict: impl Fn(&IntegerSet, &IntegerSet) -> bool,
448) -> Vec<SetPair> {
449 let mut conflicts = Vec::new();
450 for (i, entry) in entries.iter().enumerate() {
451 let Some(a) = entry.value(values) else {
452 continue;
453 };
454 for (j, other) in entries.iter().enumerate().skip(i + 1) {
455 if let Some(b) = other.value(values)
456 && conflict(a.set(), b.set())
457 {
458 conflicts.push(SetPair {
459 first: i + 1,
460 second: j + 1,
461 });
462 }
463 }
464 }
465 conflicts
466}
467fn sequence_conflicts(entries: &[Operand], values: &[Option<Value>]) -> Vec<SetPair> {
468 pairs(entries, values, |a, b| match (a.maximum(), b.minimum()) {
469 (Some(a), Some(b)) => a >= b,
470 _ => false,
471 })
472}
473fn has_intersection_fault(entries: &[Operand], values: &[Option<Value>]) -> bool {
474 !pairs(entries, values, |a, b| {
475 a.intersection(b).cardinality().is_none_or(|size| size > 1)
476 })
477 .is_empty()
478}
479fn cardinality_faults(
480 entries: &[Operand],
481 cardinality: i64,
482 values: &[Option<Value>],
483 constraint: &Constraint,
484) -> Result<Vec<SetCardinalityFault>, CheckError> {
485 let size = |set: &IntegerSet| {
486 set.cardinality()
487 .ok_or_else(|| constraint.execution_error("set cardinality exceeds i64::MAX"))
488 };
489 let mut faults = Vec::new();
490 for (i, entry) in entries.iter().enumerate() {
491 let Some(a) = entry.value(values) else {
492 continue;
493 };
494 let actual = size(a.set())?;
495 if actual != cardinality {
496 faults.push(SetCardinalityFault::Cardinality {
497 position: i + 1,
498 actual,
499 });
500 }
501 for (j, other) in entries.iter().enumerate().skip(i + 1) {
502 if let Some(b) = other.value(values) {
503 let actual = size(&a.set().intersection(b.set()))?;
504 if actual > 1 {
505 faults.push(SetCardinalityFault::Intersection {
506 first: i + 1,
507 second: j + 1,
508 actual,
509 });
510 }
511 }
512 }
513 }
514 Ok(faults)
515}
516fn extremum(set: &IntegerSet, maximum: bool) -> Option<i64> {
517 if maximum {
518 set.maximum()
519 } else {
520 set.minimum()
521 }
522}
523fn uncovered(elements: &[i64], selected: &IntegerSet) -> IntegerSet {
524 selected.difference(&IntegerSet::from_values(elements.iter().copied()))
525}
526fn weighted_sum(
527 elements: &[i64],
528 weights: &[i64],
529 selected: &IntegerSet,
530 constraint: &Constraint,
531) -> Result<Option<i64>, CheckError> {
532 if !uncovered(elements, selected).is_empty() {
533 return Ok(None);
534 }
535 let mut mapping = BTreeMap::new();
537 for (&element, &weight) in elements.iter().zip(weights) {
538 mapping.entry(element).or_insert(weight);
539 }
540 let mut sum = 0i64;
541 for (element, weight) in mapping {
542 if selected.contains(element) {
543 sum = sum.checked_add(weight).ok_or_else(|| {
544 constraint.execution_error("checked i64 overflow in set weighted sum")
545 })?;
546 }
547 }
548 Ok(Some(sum))
549}
550impl PreparedModel {
551 fn set_argument(
552 &self,
553 operand: &Operand,
554 position: Option<usize>,
555 values: &[Option<Value>],
556 ) -> SetArgument {
557 SetArgument {
558 position,
559 value: self.argument(operand, values),
560 unresolved: match operand {
561 Operand::Variable(slot) if values[*slot].is_none() => {
562 Some(Box::new(self.slots[*slot].reference.clone()))
563 }
564 _ => None,
565 },
566 }
567 }
568 pub(crate) fn evaluate_set_global(
569 &self,
570 constraint: &Constraint,
571 global: &SetGlobal,
572 values: &[Option<Value>],
573 ) -> Result<Option<ViolationKind>, CheckError> {
574 let argument = |operand: &Operand| self.set_argument(operand, None, values);
575 let arguments = |entries: &[Operand]| {
576 entries
577 .iter()
578 .enumerate()
579 .map(|(i, entry)| self.set_argument(entry, Some(i + 1), values))
580 .collect()
581 };
582 let finding = match global {
583 SetGlobal::Aggregate {
584 operation,
585 entries,
586 result,
587 } => {
588 let expected = aggregate(*operation, entries, values);
589 let conflicts = aggregate_conflicts(*operation, entries, values);
590 let mismatch = expected
591 .as_ref()
592 .zip(result.value(values))
593 .is_some_and(|(expected, actual)| expected != actual.set());
594 if !mismatch && conflicts.is_empty() {
595 return Ok(None);
596 }
597 SetGlobalFinding::Aggregate {
598 aggregate: *operation,
599 entries: arguments(entries),
600 result: argument(result),
601 expected,
602 conflicts,
603 }
604 }
605 SetGlobal::Convex { set } => {
606 let value = set.value(values).expect("required convex set").set();
607 if value.is_convex() {
608 return Ok(None);
609 }
610 SetGlobalFinding::Convex {
611 set: argument(set),
612 hull: value.hull(),
613 }
614 }
615 SetGlobal::Hull { set, result } => {
616 let expected = set.value(values).expect("required hull set").set().hull();
617 if result.value(values).expect("required hull result").set() == &expected {
618 return Ok(None);
619 }
620 SetGlobalFinding::Hull {
621 set: argument(set),
622 result: argument(result),
623 expected,
624 }
625 }
626 SetGlobal::Sequence { entries } => {
627 let conflicts = sequence_conflicts(entries, values);
628 if conflicts.is_empty() {
629 return Ok(None);
630 }
631 SetGlobalFinding::Sequence {
632 entries: arguments(entries),
633 conflicts,
634 }
635 }
636 SetGlobal::AtMostOne {
637 entries,
638 cardinality,
639 } => {
640 let faults = cardinality_faults(entries, *cardinality, values, constraint)?;
641 if faults.is_empty() {
642 return Ok(None);
643 }
644 SetGlobalFinding::AtMostOne {
645 entries: arguments(entries),
646 cardinality: *cardinality,
647 faults,
648 }
649 }
650 SetGlobal::Weights {
651 elements,
652 weights,
653 set,
654 result,
655 } => {
656 let selected = set.value(values).expect("required weighted set").set();
657 let missing = uncovered(elements, selected);
658 let expected = weighted_sum(elements, weights, selected, constraint)?;
659 let mismatch = expected
660 .zip(result.value(values))
661 .is_some_and(|(expected, actual)| expected != actual.integer());
662 if missing.is_empty() && !mismatch {
663 return Ok(None);
664 }
665 let rows = elements
666 .iter()
667 .zip(weights)
668 .enumerate()
669 .map(|(i, (&element, &weight))| SetWeight {
670 position: i + 1,
671 element,
672 weight,
673 })
674 .collect();
675 SetGlobalFinding::Weights {
676 rows,
677 set: argument(set),
678 result: argument(result),
679 uncovered: missing,
680 expected,
681 }
682 }
683 SetGlobal::Extremum {
684 set,
685 integer,
686 maximum,
687 condition,
688 } => {
689 let expected = extremum(
690 set.value(values).expect("required extremum set").set(),
691 *maximum,
692 );
693 let truth = expected.is_some_and(|best| {
694 integer
695 .value(values)
696 .expect("required nonempty extremum integer")
697 .integer()
698 == best
699 });
700 if condition.satisfied(truth, values) {
701 return Ok(None);
702 }
703 SetGlobalFinding::Extremum {
704 set: argument(set),
705 integer: argument(integer),
706 maximum: *maximum,
707 expected,
708 truth,
709 condition: self.condition_value(condition, values),
710 }
711 }
712 };
713 Ok(Some(ViolationKind::SetGlobal { finding }))
714 }
715}