1#![allow(dead_code)]
2use crate::ast::{
3 AbstractLiteral, Atom, DeclarationKind, Expression as Expr, Field, Literal as Lit, Metadata,
4 Moo,
5 comprehension::{Comprehension, ComprehensionQualifier},
6 matrix,
7};
8use crate::bug_assert;
9use crate::into_matrix;
10use itertools::{Itertools as _, izip};
11use std::cmp::Ordering as CmpOrdering;
12use std::collections::HashSet;
13
14use super::partial_eval::{run_partial_evaluator, run_partial_evaluator_local};
15
16pub(crate) fn factorial_i32(n: i32) -> Option<i32> {
17 if n < 0 {
18 return None;
19 }
20
21 (1..=n).try_fold(1_i32, i32::checked_mul)
22}
23
24fn eval_constant_set(expr: &Expr) -> Option<Vec<Lit>> {
25 let Lit::AbstractLiteral(AbstractLiteral::Set(values)) = eval_constant(expr)? else {
26 return None;
27 };
28
29 Some(values)
30}
31
32fn equal_constant_literals(lhs: &Lit, rhs: &Lit) -> Option<bool> {
37 match (lhs, rhs) {
38 (Lit::Int(_), Lit::Int(_)) | (Lit::Bool(_), Lit::Bool(_)) => {
39 Some(lhs.essence_cmp(rhs) == CmpOrdering::Equal)
40 }
41 (Lit::AbstractLiteral(lhs_abstract), Lit::AbstractLiteral(rhs_abstract))
42 if std::mem::discriminant(lhs_abstract) == std::mem::discriminant(rhs_abstract) =>
43 {
44 Some(lhs.essence_cmp(rhs) == CmpOrdering::Equal)
45 }
46 _ => None,
47 }
48}
49
50pub fn eval_constant_local(expr: &Expr) -> Option<Lit> {
56 if !has_only_locally_evaluable_operands(expr) {
57 return None;
58 }
59
60 eval_constant(expr)
61}
62
63pub fn normalise_evaluator_local(expr: &Expr) -> Option<Expr> {
82 match expr {
83 Expr::Root(_, exprs) => normalise_root_constraints_selective_deep(exprs, None),
84 Expr::Atomic(_, Atom::Literal(Lit::AbstractLiteral(_))) => None,
87 _ => fold_constant_expression_local(expr).or_else(|| {
88 run_partial_evaluator_local(expr)
89 .ok()
90 .map(|reduction| reduction.new_expression)
91 }),
92 }
93 .filter(|new_expr| new_expr != expr)
94}
95
96pub fn normalise_root_constraints_local(exprs: &[Expr]) -> Option<Expr> {
98 if exprs.is_empty() {
99 return Some(Expr::Root(Metadata::new(), vec![true.into()]));
100 }
101
102 let root = Expr::Root(Metadata::new(), exprs.to_vec());
103 run_partial_evaluator_local(&root)
104 .ok()
105 .map(|reduction| reduction.new_expression)
106 .filter(|new_root| new_root != &root)
107}
108
109pub fn normalise_root_selective_deep_expr(
111 root: &Expr,
112 only_constraint: Option<usize>,
113) -> Option<Expr> {
114 let Expr::Root(_, exprs) = root else {
115 return None;
116 };
117
118 normalise_root_constraints_selective_deep(exprs, only_constraint)
119}
120
121pub fn normalise_root_constraints_deep(root: &Expr) -> Option<Expr> {
122 normalise_root_selective_deep_expr(root, None)
123}
124
125pub fn finish_root_evaluator_normalisation(root: &Expr) -> Option<Expr> {
133 let Expr::Root(_, exprs) = root else {
134 return None;
135 };
136
137 let mut current = Expr::Root(Metadata::new(), exprs.clone());
138 let mut changed = false;
139 while let Expr::Root(_, current_exprs) = ¤t {
140 let Some(next) = normalise_root_constraints_local(current_exprs) else {
141 break;
142 };
143 current = next;
144 changed = true;
145 }
146 changed.then_some(current)
147}
148
149pub fn normalise_root_constraint_deep(constraint: &Expr) -> Option<Expr> {
155 if constraint_skips_deep_root_normalisation(constraint) {
156 return None;
157 }
158
159 normalise_constraint_deep_to_fixpoint(constraint)
160}
161
162fn normalise_root_constraints_selective_deep(
163 exprs: &[Expr],
164 only_constraint: Option<usize>,
165) -> Option<Expr> {
166 if exprs.is_empty() {
167 return Some(Expr::Root(Metadata::new(), vec![true.into()]));
168 }
169
170 let mut normalised: Vec<(usize, Expr)> = Vec::new();
174 for (index, constraint) in exprs.iter().enumerate() {
175 if only_constraint.is_some_and(|only| only != index) {
176 continue;
177 }
178
179 if let Some(replacement) = normalise_root_constraint_deep(constraint) {
180 normalised.push((index, replacement));
181 }
182 }
183
184 if normalised.is_empty() {
185 return None;
186 }
187
188 let mut constraints = exprs.to_vec();
189 for (index, replacement) in normalised {
190 constraints[index] = replacement;
191 }
192
193 Some(Expr::Root(Metadata::new(), constraints))
194}
195
196fn constraint_skips_deep_root_normalisation(expr: &Expr) -> bool {
201 match expr {
202 Expr::FlatProductEq(_, _, _, _)
203 | Expr::FlatSumLeq(_, _, _)
204 | Expr::FlatSumGeq(_, _, _)
205 | Expr::FlatMinEq(_, _, _)
206 | Expr::FlatIneq(_, _, _, _)
207 | Expr::FlatMinusEq(_, _, _)
208 | Expr::FlatAbsEq(_, _, _)
209 | Expr::FlatAllDiff(_, _)
210 | Expr::SmtDistinct(_, _)
211 | Expr::FlatWeightedSumLeq(_, _, _, _)
212 | Expr::FlatWeightedSumGeq(_, _, _, _)
213 | Expr::FlatWatchedLiteral(_, _, _)
214 | Expr::MinionDivEqUndefZero(_, _, _, _)
215 | Expr::MinionModuloEqUndefZero(_, _, _, _)
216 | Expr::MinionPow(_, _, _, _)
217 | Expr::MinionReify(_, _, _)
218 | Expr::MinionReifyImply(_, _, _)
219 | Expr::MinionWInIntervalSet(_, _, _)
220 | Expr::MinionWInSet(_, _, _)
221 | Expr::MinionElementOne(_, _, _, _) => true,
222 Expr::AuxDeclaration(_, _, inner) => matches!(
223 inner.as_ref(),
224 Expr::Product(_, _) | Expr::FlatProductEq(_, _, _, _)
225 ),
226 _ => false,
227 }
228}
229
230fn fold_constant_expression_deep(expr: &Expr) -> Option<Expr> {
231 let constant = eval_constant(expr)?;
232 fold_constant_expression(expr, constant).filter(|folded| folded != expr)
233}
234
235fn normalise_constraint_deep_to_fixpoint(constraint: &Expr) -> Option<Expr> {
236 if matches!(constraint, Expr::Atomic(_, Atom::Literal(_))) {
237 return None;
238 }
239
240 let mut current = constraint.clone();
241 let mut changed = false;
242
243 while let Some(step) = fold_constant_expression_deep(¤t)
244 .or_else(|| partial_evaluator_deep_step(¤t))
245 .filter(|step| step != ¤t)
246 {
247 current = step;
248 changed = true;
249 }
250
251 changed
252 .then_some(current)
253 .filter(|current| current != constraint)
254}
255
256fn partial_evaluator_deep_step(expr: &Expr) -> Option<Expr> {
257 run_partial_evaluator(expr)
258 .ok()
259 .map(|reduction| reduction.new_expression)
260 .filter(|new_expr| new_expr != expr)
261}
262
263fn fold_constant_expression_local(expr: &Expr) -> Option<Expr> {
265 let constant = match expr {
266 Expr::Comprehension(_, _) => eval_constant(expr)?,
269 _ => eval_constant_local(expr)?,
270 };
271 fold_constant_expression(expr, constant).filter(|folded| folded != expr)
272}
273
274fn fold_constant_expression(expr: &Expr, constant: Lit) -> Option<Expr> {
275 if let Expr::Atomic(_, Atom::Literal(existing)) = expr
276 && existing == &constant
277 {
278 return None;
279 }
280
281 if matches!(
282 (expr, constant.clone()),
283 (
284 Expr::Atomic(_, Atom::Reference(_)),
285 Lit::AbstractLiteral(AbstractLiteral::Matrix(_, _))
286 )
287 ) {
288 return None;
289 }
290
291 let folded = Expr::Atomic(Metadata::new(), Atom::Literal(constant));
292 if let Expr::TypeAnnotation(_, _, domain) = expr
293 && let Expr::Atomic(
294 _,
295 Atom::Literal(Lit::AbstractLiteral(AbstractLiteral::Matrix(elems, _))),
296 ) = &folded
297 && elems.is_empty()
298 {
299 return Some(Expr::TypeAnnotation(
300 Metadata::new(),
301 Moo::new(folded),
302 domain.clone(),
303 ));
304 }
305
306 if let Expr::DomainAnnotation(_, _, domain) = expr
307 && let Expr::Atomic(
308 _,
309 Atom::Literal(Lit::AbstractLiteral(AbstractLiteral::Matrix(elems, _))),
310 ) = &folded
311 && elems.is_empty()
312 {
313 return Some(Expr::DomainAnnotation(
314 Metadata::new(),
315 Moo::new(folded),
316 domain.clone(),
317 ));
318 }
319
320 if let Expr::Comprehension(_, comprehension) = expr
321 && let Expr::Atomic(
322 _,
323 Atom::Literal(Lit::AbstractLiteral(AbstractLiteral::Matrix(elems, _))),
324 ) = &folded
325 && elems.is_empty()
326 && let Some(domain) = comprehension.domain_of()
327 {
328 return Some(Expr::DomainAnnotation(
329 Metadata::new(),
330 Moo::new(folded),
331 domain,
332 ));
333 }
334
335 Some(folded)
336}
337
338fn has_only_locally_evaluable_operands(expr: &Expr) -> bool {
339 match expr {
340 Expr::Atomic(_, Atom::Literal(_)) => true,
341 Expr::Atomic(_, Atom::Reference(_)) => true,
345 Expr::AbstractLiteral(_, lit) => abstract_literal_children_are_locally_evaluable(lit),
346 Expr::TypeAnnotation(_, inner, _) | Expr::DomainAnnotation(_, inner, _) => {
347 is_locally_evaluable_expr(inner.as_ref())
348 }
349 Expr::Comprehension(_, _) | Expr::Root(_, _) => false,
350 _ => {
354 let mut all_constant = true;
355 expr.for_each_expr_child(&mut |child| {
356 all_constant = all_constant && is_locally_evaluable_expr(child);
357 });
358 all_constant
359 }
360 }
361}
362
363fn is_locally_evaluable_expr(expr: &Expr) -> bool {
364 match expr {
365 Expr::Atomic(_, Atom::Literal(_)) => true,
366 Expr::Atomic(_, Atom::Reference(_)) => true,
367 Expr::AbstractLiteral(_, lit) => abstract_literal_children_are_locally_evaluable(lit),
368 Expr::TypeAnnotation(_, inner, _) | Expr::DomainAnnotation(_, inner, _) => {
369 is_locally_evaluable_expr(inner.as_ref())
370 }
371 _ => false,
372 }
373}
374
375fn abstract_literal_children_are_locally_evaluable(lit: &AbstractLiteral<Expr>) -> bool {
376 match lit {
377 AbstractLiteral::Set(items)
378 | AbstractLiteral::MSet(items)
379 | AbstractLiteral::Tuple(items)
380 | AbstractLiteral::Matrix(items, _) => items.iter().all(is_locally_evaluable_expr),
381 AbstractLiteral::Record(fields) => fields
382 .iter()
383 .all(|field| is_locally_evaluable_expr(&field.value)),
384 AbstractLiteral::Sequence(items) => items.iter().all(is_locally_evaluable_expr),
385 AbstractLiteral::Function(items) => items
386 .iter()
387 .all(|(from, to)| is_locally_evaluable_expr(from) && is_locally_evaluable_expr(to)),
388 AbstractLiteral::Relation(items) => items
389 .iter()
390 .all(|tuple| tuple.iter().all(is_locally_evaluable_expr)),
391 AbstractLiteral::Partition(parts) => parts
392 .iter()
393 .all(|part| part.iter().all(is_locally_evaluable_expr)),
394 AbstractLiteral::Permutation(cycles) => cycles
395 .iter()
396 .all(|cycle| cycle.iter().all(is_locally_evaluable_expr)),
397 AbstractLiteral::Variant(field) => is_locally_evaluable_expr(&field.value),
398 }
399}
400
401pub fn eval_constant(expr: &Expr) -> Option<Lit> {
406 match expr {
407 Expr::TypeAnnotation(_, expr, _) | Expr::DomainAnnotation(_, expr, _) => {
408 eval_constant(expr)
409 }
410 Expr::Supset(_, a, b) => {
411 let (
412 Lit::AbstractLiteral(AbstractLiteral::Set(a)),
413 Lit::AbstractLiteral(AbstractLiteral::Set(b)),
414 ) = (eval_constant(a.as_ref())?, eval_constant(b.as_ref())?)
415 else {
416 return None;
417 };
418
419 let a_set: HashSet<Lit> = a.iter().cloned().collect();
420 let b_set: HashSet<Lit> = b.iter().cloned().collect();
421
422 if a_set.difference(&b_set).count() > 0 {
423 Some(Lit::Bool(a_set.is_superset(&b_set)))
424 } else {
425 Some(Lit::Bool(false))
426 }
427 }
428 Expr::SupsetEq(_, a, b) => {
429 let (
430 Lit::AbstractLiteral(AbstractLiteral::Set(a)),
431 Lit::AbstractLiteral(AbstractLiteral::Set(b)),
432 ) = (eval_constant(a.as_ref())?, eval_constant(b.as_ref())?)
433 else {
434 return None;
435 };
436
437 Some(Lit::Bool(
438 a.iter()
439 .cloned()
440 .collect::<HashSet<Lit>>()
441 .is_superset(&b.iter().cloned().collect::<HashSet<Lit>>()),
442 ))
443 }
444 Expr::Subset(_, a, b) => {
445 let (
446 Lit::AbstractLiteral(AbstractLiteral::Set(a)),
447 Lit::AbstractLiteral(AbstractLiteral::Set(b)),
448 ) = (eval_constant(a.as_ref())?, eval_constant(b.as_ref())?)
449 else {
450 return None;
451 };
452
453 let a_set: HashSet<Lit> = a.iter().cloned().collect();
454 let b_set: HashSet<Lit> = b.iter().cloned().collect();
455
456 if b_set.difference(&a_set).count() > 0 {
457 Some(Lit::Bool(a_set.is_subset(&b_set)))
458 } else {
459 Some(Lit::Bool(false))
460 }
461 }
462 Expr::SubsetEq(_, a, b) => {
463 let (
464 Lit::AbstractLiteral(AbstractLiteral::Set(a)),
465 Lit::AbstractLiteral(AbstractLiteral::Set(b)),
466 ) = (eval_constant(a.as_ref())?, eval_constant(b.as_ref())?)
467 else {
468 return None;
469 };
470
471 Some(Lit::Bool(
472 a.iter()
473 .cloned()
474 .collect::<HashSet<Lit>>()
475 .is_subset(&b.iter().cloned().collect::<HashSet<Lit>>()),
476 ))
477 }
478 Expr::Intersect(_, a, b) => {
479 let (
480 Lit::AbstractLiteral(AbstractLiteral::Set(a)),
481 Lit::AbstractLiteral(AbstractLiteral::Set(b)),
482 ) = (eval_constant(a.as_ref())?, eval_constant(b.as_ref())?)
483 else {
484 return None;
485 };
486
487 let mut res: Vec<Lit> = Vec::new();
488 for lit in a {
489 if b.contains(&lit) && !res.contains(&lit) {
490 res.push(lit);
491 }
492 }
493 Some(Lit::AbstractLiteral(AbstractLiteral::Set(res)))
494 }
495 Expr::Difference(_, a, b) => {
496 let (
497 Lit::AbstractLiteral(AbstractLiteral::Set(a)),
498 Lit::AbstractLiteral(AbstractLiteral::Set(b)),
499 ) = (eval_constant(a.as_ref())?, eval_constant(b.as_ref())?)
500 else {
501 return None;
502 };
503
504 let mut res: Vec<Lit> = Vec::new();
505 for lit in a {
506 if !b.contains(&lit) && !res.contains(&lit) {
507 res.push(lit);
508 }
509 }
510 Some(Lit::AbstractLiteral(AbstractLiteral::Set(res)))
511 }
512 Expr::Union(_, a, b) => {
513 let (
514 Lit::AbstractLiteral(AbstractLiteral::Set(a)),
515 Lit::AbstractLiteral(AbstractLiteral::Set(b)),
516 ) = (eval_constant(a.as_ref())?, eval_constant(b.as_ref())?)
517 else {
518 return None;
519 };
520
521 let mut res: Vec<Lit> = Vec::new();
522 for lit in a {
523 res.push(lit);
524 }
525 for lit in b {
526 if !res.contains(&lit) {
527 res.push(lit);
528 }
529 }
530 Some(Lit::AbstractLiteral(AbstractLiteral::Set(res)))
531 }
532 Expr::In(_, a, b) => {
533 let member = eval_constant(a)?;
534 let collection = eval_constant(b)?;
535 let values = generator_values_from_constant_collection(&collection)?;
536 Some(Lit::Bool(values.iter().any(|value| {
537 value.essence_cmp(&member) == CmpOrdering::Equal
538 })))
539 }
540 Expr::FromSolution(_, _) => None,
541 Expr::DominanceRelation(_, _) => None,
542 Expr::InDomain(_, e, domain) => {
543 let Expr::Atomic(_, Atom::Literal(lit)) = e.as_ref() else {
544 return None;
545 };
546
547 domain.contains(lit).ok().map(Into::into)
548 }
549 Expr::Atomic(_, Atom::Literal(c)) => Some(c.clone()),
550 Expr::Atomic(_, Atom::Reference(reference)) => reference.resolve_constant(),
551 Expr::AbstractLiteral(_, a) => Some(Lit::AbstractLiteral(a.clone().into_literals()?)),
552 Expr::Comprehension(_, comprehension) => {
553 eval_constant_comprehension(comprehension.as_ref())
554 }
555 Expr::RecordField(_, rec, fld_name) => match eval_constant(rec.as_ref())? {
556 Lit::AbstractLiteral(AbstractLiteral::Record(ents)) => {
557 for Field { name, value } in ents {
558 if name.eq(fld_name) {
559 return Some(value);
560 }
561 }
562 None
563 }
564 Lit::AbstractLiteral(AbstractLiteral::Variant(field)) if field.name == *fld_name => {
565 Some(field.value.clone())
566 }
567 _ => None,
568 },
569 Expr::UnsafeIndex(_, subject, indices) | Expr::SafeIndex(_, subject, indices) => {
570 let subject: Lit = eval_constant(subject.as_ref())?;
571 let indices: Vec<Lit> = indices
572 .iter()
573 .map(eval_constant)
574 .collect::<Option<Vec<Lit>>>()?;
575
576 match subject {
577 Lit::AbstractLiteral(subject @ AbstractLiteral::Matrix(_, _)) => {
578 matrix::flatten_enumerate(subject)
579 .find(|(i, _)| i == &indices)
580 .map(|(_, x)| x)
581 }
582 Lit::AbstractLiteral(subject @ AbstractLiteral::Tuple(_)) => {
583 let AbstractLiteral::Tuple(elems) = subject else {
584 return None;
585 };
586
587 bug_assert!(indices.len() == 1, "nested tuples not supported yet");
588
589 let Lit::Int(index) = indices[0].clone() else {
590 return None;
591 };
592
593 if elems.len() < index as usize || index < 1 {
594 return None;
595 }
596
597 let item = elems[index as usize - 1].clone();
599
600 Some(item)
601 }
602 Lit::AbstractLiteral(subject @ AbstractLiteral::Record(_)) => {
603 let AbstractLiteral::Record(elems) = subject else {
604 return None;
605 };
606
607 bug_assert!(indices.len() == 1, "nested record not supported yet");
608
609 let Lit::Int(index) = indices[0].clone() else {
610 return None;
611 };
612
613 if elems.len() < index as usize || index < 1 {
614 return None;
615 }
616
617 let item = elems[index as usize - 1].clone();
619 Some(item.value)
620 }
621 _ => None,
622 }
623 }
624 Expr::UnsafeSlice(_, subject, indices) | Expr::SafeSlice(_, subject, indices) => {
625 let subject: Lit = eval_constant(subject.as_ref())?;
626 let Lit::AbstractLiteral(subject @ AbstractLiteral::Matrix(_, _)) = subject else {
627 return None;
628 };
629
630 let hole_dim = indices
631 .iter()
632 .cloned()
633 .position(|x| x.is_none())
634 .expect("slice expression should have a hole dimension");
635
636 let missing_domain = matrix::index_domains(&subject)[hole_dim].clone();
637
638 let indices: Vec<Option<Lit>> = indices
639 .iter()
640 .cloned()
641 .map(|x| {
642 match x {
645 Some(x) => eval_constant(&x).map(Some),
646 None => Some(None),
647 }
648 })
649 .collect::<Option<Vec<Option<Lit>>>>()?;
650
651 let indices_in_slice: Vec<Vec<Lit>> = missing_domain
652 .values()
653 .ok()?
654 .map(|i| {
655 let mut indices = indices.clone();
656 indices[hole_dim] = Some(i);
657 indices.into_iter().map(|x| x.unwrap()).collect_vec()
660 })
661 .collect_vec();
662
663 let elems = matrix::flatten_enumerate(subject)
665 .filter(|(i, _)| indices_in_slice.contains(i))
666 .map(|(_, elem)| elem)
667 .collect();
668
669 Some(Lit::AbstractLiteral(into_matrix![elems]))
670 }
671 Expr::Abs(_, e) => un_op::<i32, i32>(|a| a.abs(), e).map(Lit::Int),
672 Expr::Eq(_, a, b) => Some(Lit::Bool(equal_constant_literals(
673 &eval_constant(a)?,
674 &eval_constant(b)?,
675 )?)),
676 Expr::Neq(_, a, b) => Some(Lit::Bool(!equal_constant_literals(
677 &eval_constant(a)?,
678 &eval_constant(b)?,
679 )?)),
680 Expr::Lt(_, a, b) => bin_op::<i32, bool>(|a, b| a < b, a, b).map(Lit::Bool),
681 Expr::Gt(_, a, b) => bin_op::<i32, bool>(|a, b| a > b, a, b).map(Lit::Bool),
682 Expr::Leq(_, a, b) => bin_op::<i32, bool>(|a, b| a <= b, a, b).map(Lit::Bool),
683 Expr::Geq(_, a, b) => bin_op::<i32, bool>(|a, b| a >= b, a, b).map(Lit::Bool),
684 Expr::Not(_, expr) => un_op::<bool, bool>(|e| !e, expr).map(Lit::Bool),
685 Expr::And(_, e) => {
686 vec_lit_op::<bool, bool>(|e| e.iter().all(|&e| e), e.as_ref()).map(Lit::Bool)
687 }
688 Expr::Table(_, _, _) => None,
689 Expr::NegativeTable(_, _, _) => None,
690 Expr::AtLeast(_, _, _, _) => None,
691 Expr::AtMost(_, _, _, _) => None,
692 Expr::Gcc(_, _, _, _) | Expr::GccWeak(_, _, _, _) => None,
693 Expr::Root(_, _) => None,
694 Expr::Or(_, es) => {
695 for e in es.unwrap_list_cow()?.iter() {
697 if let Expr::Atomic(_, Atom::Literal(Lit::Bool(true))) = e {
698 return Some(Lit::Bool(true));
699 };
700 }
701
702 vec_lit_op::<bool, bool>(|e| e.iter().any(|&e| e), es.as_ref()).map(Lit::Bool)
703 }
704 Expr::CatchUndef(_, a, _) => eval_constant(a),
707 Expr::Imply(_, a, b) => bin_op::<bool, bool>(|a, b| !a || b, a, b).map(Lit::Bool),
708 Expr::Iff(_, a, b) => bin_op::<bool, bool>(|a, b| a == b, a, b).map(Lit::Bool),
709 Expr::Sum(_, exprs) => vec_lit_op::<i32, i32>(|e| e.iter().sum(), exprs).map(Lit::Int),
710 Expr::Product(_, exprs) => {
711 vec_lit_op::<i32, i32>(|e| e.iter().product(), exprs).map(Lit::Int)
712 }
713 Expr::FlatIneq(_, a, b, c) => {
714 let a: i32 = a.try_into().ok()?;
715 let b: i32 = b.try_into().ok()?;
716 let c: i32 = c.try_into().ok()?;
717
718 Some(Lit::Bool(a <= b + c))
719 }
720 Expr::FlatSumGeq(_, exprs, a) => {
721 let sum = exprs.iter().try_fold(0, |acc, atom: &Atom| {
722 let n: i32 = atom.try_into().ok()?;
723 let acc = acc + n;
724 Some(acc)
725 })?;
726
727 Some(Lit::Bool(sum >= a.try_into().ok()?))
728 }
729 Expr::FlatSumLeq(_, exprs, a) => {
730 let sum = exprs.iter().try_fold(0, |acc, atom: &Atom| {
731 let n: i32 = atom.try_into().ok()?;
732 let acc = acc + n;
733 Some(acc)
734 })?;
735
736 Some(Lit::Bool(sum >= a.try_into().ok()?))
737 }
738 Expr::FlatMinEq(_, vars, result) => {
739 let min = vars
740 .iter()
741 .try_fold(None, |acc: Option<i32>, atom: &Atom| {
742 let n: i32 = atom.try_into().ok()?;
743 Some(Some(acc.map_or(n, |m| m.min(n))))
744 })??;
745 let result: i32 = result.try_into().ok()?;
746 Some(Lit::Bool(min == result))
747 }
748 Expr::Min(_, e) => {
749 opt_vec_lit_op::<i32, i32>(|e| e.iter().min().copied(), e.as_ref()).map(Lit::Int)
750 }
751 Expr::Max(_, e) => {
752 opt_vec_lit_op::<i32, i32>(|e| e.iter().max().copied(), e.as_ref()).map(Lit::Int)
753 }
754 Expr::UnsafeDiv(_, a, b) | Expr::SafeDiv(_, a, b) => {
755 if unwrap_expr::<i32>(b)? == 0 {
756 return None;
757 }
758 bin_op::<i32, i32>(|a, b| ((a as f32) / (b as f32)).floor() as i32, a, b).map(Lit::Int)
759 }
760 Expr::UnsafeMod(_, a, b) | Expr::SafeMod(_, a, b) => {
761 if unwrap_expr::<i32>(b)? == 0 {
762 return None;
763 }
764 bin_op::<i32, i32>(|a, b| a - b * (a as f32 / b as f32).floor() as i32, a, b)
765 .map(Lit::Int)
766 }
767 Expr::Substring(_, s, t) => match (s.as_ref(), t.as_ref()) {
768 (
769 Expr::Atomic(_, Atom::Literal(Lit::AbstractLiteral(AbstractLiteral::Sequence(s)))),
770 Expr::Atomic(_, Atom::Literal(Lit::AbstractLiteral(AbstractLiteral::Sequence(t)))),
771 ) => {
772 if s.len() > t.len() {
773 return Some(Lit::Bool(false));
774 }
775
776 let found = t.windows(s.len()).any(|window| window == s.as_slice());
777 Some(Lit::Bool(found))
778 }
779 _ => None,
780 },
781 Expr::Subsequence(_, s, t) => match (s.as_ref(), t.as_ref()) {
782 (
783 Expr::Atomic(_, Atom::Literal(Lit::AbstractLiteral(AbstractLiteral::Sequence(s)))),
784 Expr::Atomic(_, Atom::Literal(Lit::AbstractLiteral(AbstractLiteral::Sequence(t)))),
785 ) => {
786 let mut i = 0;
787 let mut j = 0;
788
789 while i < s.len() && j < t.len() {
790 if s[i] == t[j] {
791 i += 1;
792 }
793 j += 1;
794 }
795
796 Some(Lit::Bool(i == s.len()))
797 }
798 _ => None,
799 },
800 Expr::MinionDivEqUndefZero(_, a, b, c) => {
801 let a: i32 = a.try_into().ok()?;
803 let b: i32 = b.try_into().ok()?;
804 let c: i32 = c.try_into().ok()?;
805
806 if b == 0 {
807 return None;
808 }
809
810 let a = a as f32;
811 let b = b as f32;
812 let div: i32 = (a / b).floor() as i32;
813 Some(Lit::Bool(div == c))
814 }
815 Expr::Bubble(_, a, b) => bin_op::<bool, bool>(|a, b| a && b, a, b).map(Lit::Bool),
816 Expr::MinionReify(_, a, b) => {
817 let result = eval_constant(a)?;
818
819 let result: bool = result.try_into().ok()?;
820 let b: bool = b.try_into().ok()?;
821
822 Some(Lit::Bool(b == result))
823 }
824 Expr::MinionReifyImply(_, a, b) => {
825 let result = eval_constant(a)?;
826
827 let result: bool = result.try_into().ok()?;
828 let b: bool = b.try_into().ok()?;
829
830 if b {
831 Some(Lit::Bool(result))
832 } else {
833 Some(Lit::Bool(true))
834 }
835 }
836 Expr::MinionModuloEqUndefZero(_, a, b, c) => {
837 let a: i32 = a.try_into().ok()?;
845 let b: i32 = b.try_into().ok()?;
846 let c: i32 = c.try_into().ok()?;
847
848 if b == 0 {
849 return None;
850 }
851
852 let modulo = a - b * (a as f32 / b as f32).floor() as i32;
853 Some(Lit::Bool(modulo == c))
854 }
855 Expr::MinionPow(_, a, b, c) => {
856 let a: i32 = a.try_into().ok()?;
859 let b: i32 = b.try_into().ok()?;
860 let c: i32 = c.try_into().ok()?;
861
862 if a <= 0 {
863 return None;
864 }
865
866 if b <= 0 {
867 return None;
868 }
869
870 if c <= 0 {
871 return None;
872 }
873
874 Some(Lit::Bool(a ^ b == c))
875 }
876 Expr::MinionWInSet(_, _, _) => None,
877 Expr::MinionWInIntervalSet(_, x, intervals) => {
878 let x_lit: &Lit = x.try_into().ok()?;
879
880 let x_lit = match x_lit.clone() {
881 Lit::Int(i) => Some(i),
882 Lit::Bool(true) => Some(1),
883 Lit::Bool(false) => Some(0),
884 _ => None,
885 }?;
886
887 let mut intervals = intervals.iter();
888 while let Some(lower) = intervals.next() {
889 let Some(upper) = intervals.next() else {
890 break;
891 };
892 if &x_lit >= lower && &x_lit <= upper {
893 return Some(Lit::Bool(true));
894 }
895 }
896
897 Some(Lit::Bool(false))
898 }
899 Expr::Flatten(_, _, _) => {
900 None
902 }
903 Expr::AllDiff(_, e) | Expr::SmtDistinct(_, e) => {
905 let es = e.unwrap_list_cow()?;
906 let mut lits: HashSet<Lit> = HashSet::new();
907 for expr in es.iter() {
908 let Expr::Atomic(_, Atom::Literal(x)) = expr else {
909 return None;
910 };
911 match x {
912 Lit::Int(_) | Lit::Bool(_) => {
913 if lits.contains(x) {
914 return Some(Lit::Bool(false));
915 } else {
916 lits.insert(x.clone());
917 }
918 }
919 Lit::AbstractLiteral(_) => return None, }
921 }
922 Some(Lit::Bool(true))
923 }
924 Expr::FlatAllDiff(_, es) => {
925 let mut lits: HashSet<Lit> = HashSet::new();
926 for atom in es {
927 let Atom::Literal(x) = atom else {
928 return None;
929 };
930
931 match x {
932 Lit::Int(_) | Lit::Bool(_) => {
933 if lits.contains(x) {
934 return Some(Lit::Bool(false));
935 } else {
936 lits.insert(x.clone());
937 }
938 }
939 Lit::AbstractLiteral(_) => return None, }
941 }
942 Some(Lit::Bool(true))
943 }
944 Expr::FlatWatchedLiteral(_, _, _) => None,
945 Expr::AuxDeclaration(_, _, _) => None,
946 Expr::Neg(_, a) => match eval_constant(a.as_ref())? {
947 Lit::Int(a) => Some(Lit::Int(-a)),
948 _ => None,
949 },
950 Expr::Factorial(_, a) => match eval_constant(a.as_ref())? {
951 Lit::Int(a) => factorial_i32(a).map(Lit::Int),
952 _ => None,
953 },
954 Expr::Minus(_, a, b) => bin_op::<i32, i32>(|a, b| a - b, a, b).map(Lit::Int),
955 Expr::FlatMinusEq(_, a, b) => {
956 let a: i32 = a.try_into().ok()?;
957 let b: i32 = b.try_into().ok()?;
958 Some(Lit::Bool(a == -b))
959 }
960 Expr::FlatProductEq(_, a, b, c) => {
961 let a: i32 = a.try_into().ok()?;
962 let b: i32 = b.try_into().ok()?;
963 let c: i32 = c.try_into().ok()?;
964 Some(Lit::Bool(a * b == c))
965 }
966 Expr::FlatWeightedSumLeq(_, cs, vs, total) => {
967 let cs: Vec<i32> = cs
968 .iter()
969 .map(|x| TryInto::<i32>::try_into(x).ok())
970 .collect::<Option<Vec<i32>>>()?;
971 let vs: Vec<i32> = vs
972 .iter()
973 .map(|x| TryInto::<i32>::try_into(x).ok())
974 .collect::<Option<Vec<i32>>>()?;
975 let total: i32 = total.try_into().ok()?;
976
977 let sum: i32 = izip!(cs, vs).fold(0, |acc, (c, v)| acc + (c * v));
978
979 Some(Lit::Bool(sum <= total))
980 }
981 Expr::FlatWeightedSumGeq(_, cs, vs, total) => {
982 let cs: Vec<i32> = cs
983 .iter()
984 .map(|x| TryInto::<i32>::try_into(x).ok())
985 .collect::<Option<Vec<i32>>>()?;
986 let vs: Vec<i32> = vs
987 .iter()
988 .map(|x| TryInto::<i32>::try_into(x).ok())
989 .collect::<Option<Vec<i32>>>()?;
990 let total: i32 = total.try_into().ok()?;
991
992 let sum: i32 = izip!(cs, vs).fold(0, |acc, (c, v)| acc + (c * v));
993
994 Some(Lit::Bool(sum >= total))
995 }
996 Expr::FlatAbsEq(_, x, y) => {
997 let x: i32 = x.try_into().ok()?;
998 let y: i32 = y.try_into().ok()?;
999
1000 Some(Lit::Bool(x == y.abs()))
1001 }
1002 Expr::UnsafePow(_, a, b) | Expr::SafePow(_, a, b) => {
1003 let a: &Atom = a.try_into().ok()?;
1004 let a: i32 = a.try_into().ok()?;
1005
1006 let b: &Atom = b.try_into().ok()?;
1007 let b: i32 = b.try_into().ok()?;
1008
1009 if (a != 0 || b != 0) && b >= 0 {
1010 Some(Lit::Int(a.pow(b as u32)))
1011 } else {
1012 None
1013 }
1014 }
1015 Expr::Metavar(_, _) => None,
1016 Expr::MinionElementOne(_, _, _, _) => None,
1017 Expr::ToInt(_, expression) => {
1018 let lit = eval_constant(expression.as_ref())?;
1019 match lit {
1020 Lit::Int(_) => Some(lit),
1021 Lit::Bool(true) => Some(Lit::Int(1)),
1022 Lit::Bool(false) => Some(Lit::Int(0)),
1023 _ => None,
1024 }
1025 }
1026 Expr::SATInt(_, _, _, _) => {
1027 None
1035 }
1036 Expr::PairwiseSum(_, a, b) => {
1037 match (eval_constant(a.as_ref())?, eval_constant(b.as_ref())?) {
1038 (Lit::Int(a_int), Lit::Int(b_int)) => Some(Lit::Int(a_int + b_int)),
1039 _ => None,
1040 }
1041 }
1042 Expr::PairwiseProduct(_, a, b) => {
1043 match (eval_constant(a.as_ref())?, eval_constant(b.as_ref())?) {
1044 (Lit::Int(a_int), Lit::Int(b_int)) => Some(Lit::Int(a_int * b_int)),
1045 _ => None,
1046 }
1047 }
1048 Expr::Defined(_, f) => {
1049 let Lit::AbstractLiteral(AbstractLiteral::Function(pairs)) = eval_constant(f)? else {
1050 return None;
1051 };
1052 Some(Lit::AbstractLiteral(AbstractLiteral::Set(
1053 pairs.into_iter().map(|(key, _)| key).collect(),
1054 )))
1055 }
1056 Expr::Range(_, f) => {
1057 let Lit::AbstractLiteral(AbstractLiteral::Function(pairs)) = eval_constant(f)? else {
1058 return None;
1059 };
1060 let mut values = Vec::new();
1061 for (_, value) in pairs {
1062 if !values.contains(&value) {
1063 values.push(value);
1064 }
1065 }
1066 Some(Lit::AbstractLiteral(AbstractLiteral::Set(values)))
1067 }
1068 Expr::Image(_, f, arg) => {
1069 let arg = eval_constant(arg)?;
1070 match eval_constant(f)? {
1071 Lit::AbstractLiteral(AbstractLiteral::Function(pairs)) => pairs
1072 .into_iter()
1073 .find(|(key, _)| *key == arg)
1074 .map(|(_, value)| value),
1075 Lit::AbstractLiteral(AbstractLiteral::Sequence(values)) => {
1078 let Lit::Int(index) = arg else {
1079 return None;
1080 };
1081 usize::try_from(index)
1082 .ok()
1083 .filter(|index| *index >= 1)
1084 .and_then(|index| values.get(index - 1).cloned())
1085 }
1086 Lit::AbstractLiteral(AbstractLiteral::Permutation(cycles)) => {
1090 for cycle in &cycles {
1091 if let Some(pos) = cycle.iter().position(|x| *x == arg) {
1092 return Some(cycle[(pos + 1) % cycle.len()].clone());
1093 }
1094 }
1095 Some(arg)
1096 }
1097 _ => None,
1098 }
1099 }
1100 Expr::PreImage(_, f, img) => {
1101 let Lit::AbstractLiteral(AbstractLiteral::Function(pairs)) = eval_constant(f)? else {
1102 return None;
1103 };
1104 let img = eval_constant(img)?;
1105 let mut keys = Vec::new();
1106 for (key, value) in pairs {
1107 if value == img && !keys.contains(&key) {
1108 keys.push(key);
1109 }
1110 }
1111 Some(Lit::AbstractLiteral(AbstractLiteral::Set(keys)))
1112 }
1113 Expr::ImageSet(_, _, _) => None,
1116 Expr::Inverse(_, _, _) => None,
1117 Expr::PermInverse(_, _) => None,
1118 Expr::Compose(_, _, _) => None,
1119 Expr::Restrict(_, _, _) => None,
1120 Expr::ToSet(_, _) => None,
1121 Expr::ToMSet(_, _) => None,
1122 Expr::ToRelation(_, _) => None,
1123 Expr::Active(_, variant, alternative) => {
1124 let Lit::AbstractLiteral(AbstractLiteral::Variant(field)) =
1125 eval_constant(variant.as_ref())?
1126 else {
1127 return None;
1128 };
1129 Some(Lit::Bool(field.name == *alternative))
1130 }
1131 Expr::RelationProj(_, _, _) => todo!(),
1132 Expr::Apart(_, _, _) => None,
1137 Expr::Together(_, _, _) => None,
1138 Expr::Participants(_, _) => None,
1139 Expr::Party(_, _, _) => None,
1140 Expr::Parts(_, _) => None,
1141 Expr::Card(_, collection) => {
1142 let Lit::AbstractLiteral(collection) = eval_constant(collection)? else {
1143 return None;
1144 };
1145 let length = match collection {
1146 AbstractLiteral::Set(values)
1147 | AbstractLiteral::MSet(values)
1148 | AbstractLiteral::Sequence(values)
1149 | AbstractLiteral::Matrix(values, _) => values.len(),
1150 AbstractLiteral::Function(entries) => entries.len(),
1151 AbstractLiteral::Relation(entries) => entries.len(),
1152 AbstractLiteral::Permutation(cycles) => cycles.iter().flatten().count(),
1158 _ => return None,
1159 };
1160 i32::try_from(length).ok().map(Lit::Int)
1161 }
1162 Expr::LexLt(_, a, b) => {
1163 let lt = vec_expr_pairs_op::<i32, _>(a, b, |pairs, (a_len, b_len)| {
1164 pairs
1165 .iter()
1166 .find_map(|(a, b)| match a.cmp(b) {
1167 CmpOrdering::Less => Some(true), CmpOrdering::Greater => Some(false), CmpOrdering::Equal => None, })
1171 .unwrap_or(a_len < b_len) })?;
1173 Some(lt.into())
1174 }
1175 Expr::LexLeq(_, a, b) => {
1176 let lt = vec_expr_pairs_op::<i32, _>(a, b, |pairs, (a_len, b_len)| {
1177 pairs
1178 .iter()
1179 .find_map(|(a, b)| match a.cmp(b) {
1180 CmpOrdering::Less => Some(true),
1181 CmpOrdering::Greater => Some(false),
1182 CmpOrdering::Equal => None,
1183 })
1184 .unwrap_or(a_len <= b_len) })?;
1186 Some(lt.into())
1187 }
1188 Expr::LexGt(_, a, b) => eval_constant(&Expr::LexLt(Metadata::new(), b.clone(), a.clone())),
1189 Expr::LexGeq(_, a, b) => {
1190 eval_constant(&Expr::LexLeq(Metadata::new(), b.clone(), a.clone()))
1191 }
1192 Expr::FlatLexLt(_, a, b) => {
1193 let lt = atoms_pairs_op::<i32, _>(a, b, |pairs, (a_len, b_len)| {
1194 pairs
1195 .iter()
1196 .find_map(|(a, b)| match a.cmp(b) {
1197 CmpOrdering::Less => Some(true),
1198 CmpOrdering::Greater => Some(false),
1199 CmpOrdering::Equal => None,
1200 })
1201 .unwrap_or(a_len < b_len)
1202 })?;
1203 Some(lt.into())
1204 }
1205 Expr::FlatLexLeq(_, a, b) => {
1206 let lt = atoms_pairs_op::<i32, _>(a, b, |pairs, (a_len, b_len)| {
1207 pairs
1208 .iter()
1209 .find_map(|(a, b)| match a.cmp(b) {
1210 CmpOrdering::Less => Some(true),
1211 CmpOrdering::Greater => Some(false),
1212 CmpOrdering::Equal => None,
1213 })
1214 .unwrap_or(a_len <= b_len)
1215 })?;
1216 Some(lt.into())
1217 }
1218 Expr::AllDifferentExcept(_, _, _) | Expr::ElementId(_, _, _) => None,
1219 Expr::AttributeAsConstraint(_, _, _, _) => None,
1222 }
1223}
1224
1225pub fn un_op<T, A>(f: fn(T) -> A, a: &Expr) -> Option<A>
1226where
1227 T: TryFrom<Lit>,
1228{
1229 let a = unwrap_expr::<T>(a)?;
1230 Some(f(a))
1231}
1232
1233pub fn bin_op<T, A>(f: fn(T, T) -> A, a: &Expr, b: &Expr) -> Option<A>
1234where
1235 T: TryFrom<Lit>,
1236{
1237 let a = unwrap_expr::<T>(a)?;
1238 let b = unwrap_expr::<T>(b)?;
1239 Some(f(a, b))
1240}
1241
1242#[allow(dead_code)]
1243pub fn tern_op<T, A>(f: fn(T, T, T) -> A, a: &Expr, b: &Expr, c: &Expr) -> Option<A>
1244where
1245 T: TryFrom<Lit>,
1246{
1247 let a = unwrap_expr::<T>(a)?;
1248 let b = unwrap_expr::<T>(b)?;
1249 let c = unwrap_expr::<T>(c)?;
1250 Some(f(a, b, c))
1251}
1252
1253pub fn vec_op<T, A>(f: fn(Vec<T>) -> A, a: &[Expr]) -> Option<A>
1254where
1255 T: TryFrom<Lit>,
1256{
1257 let a = a.iter().map(unwrap_expr).collect::<Option<Vec<T>>>()?;
1258 Some(f(a))
1259}
1260
1261pub fn vec_lit_op<T, A>(f: fn(Vec<T>) -> A, a: &Expr) -> Option<A>
1262where
1263 T: TryFrom<Lit>,
1264{
1265 Some(f(eval_list_items(a)?))
1266}
1267
1268type PairsCallback<T, A> = fn(Vec<(T, T)>, (usize, usize)) -> A;
1269
1270fn vec_expr_pairs_op<T, A>(a: &Expr, b: &Expr, f: PairsCallback<T, A>) -> Option<A>
1273where
1274 T: TryFrom<Lit>,
1275{
1276 let a_exprs = a.clone().unwrap_matrix_unchecked()?.0;
1277 let b_exprs = b.clone().unwrap_matrix_unchecked()?.0;
1278 let lens = (a_exprs.len(), b_exprs.len());
1279
1280 let lit_pairs = std::iter::zip(a_exprs, b_exprs)
1281 .map(|(a, b)| Some((unwrap_expr(&a)?, unwrap_expr(&b)?)))
1282 .collect::<Option<Vec<(T, T)>>>()?;
1283 Some(f(lit_pairs, lens))
1284}
1285
1286fn atoms_pairs_op<T, A>(a: &[Atom], b: &[Atom], f: PairsCallback<T, A>) -> Option<A>
1288where
1289 T: TryFrom<Atom>,
1290{
1291 let lit_pairs = Iterator::zip(a.iter(), b.iter())
1292 .map(|(a, b)| Some((a.clone().try_into().ok()?, b.clone().try_into().ok()?)))
1293 .collect::<Option<Vec<(T, T)>>>()?;
1294 Some(f(lit_pairs, (a.len(), b.len())))
1295}
1296
1297pub fn opt_vec_op<T, A>(f: fn(Vec<T>) -> Option<A>, a: &[Expr]) -> Option<A>
1298where
1299 T: TryFrom<Lit>,
1300{
1301 let a = a.iter().map(unwrap_expr).collect::<Option<Vec<T>>>()?;
1302 f(a)
1303}
1304
1305pub fn opt_vec_lit_op<T, A>(f: fn(Vec<T>) -> Option<A>, a: &Expr) -> Option<A>
1306where
1307 T: TryFrom<Lit>,
1308{
1309 f(eval_list_items(a)?)
1310}
1311
1312#[allow(dead_code)]
1313pub fn flat_op<T, A>(f: fn(Vec<T>, T) -> A, a: &[Expr], b: &Expr) -> Option<A>
1314where
1315 T: TryFrom<Lit>,
1316{
1317 let a = a.iter().map(unwrap_expr).collect::<Option<Vec<T>>>()?;
1318 let b = unwrap_expr::<T>(b)?;
1319 Some(f(a, b))
1320}
1321
1322pub fn unwrap_expr<T: TryFrom<Lit>>(expr: &Expr) -> Option<T> {
1323 let c = eval_constant(expr)?;
1324 TryInto::<T>::try_into(c).ok()
1325}
1326
1327fn eval_list_items<T>(expr: &Expr) -> Option<Vec<T>>
1328where
1329 T: TryFrom<Lit>,
1330{
1331 if let Some((items, _)) = expr.unwrap_matrix_unchecked_ref() {
1332 return items.iter().map(unwrap_expr).collect();
1333 }
1334
1335 let collection = eval_constant(expr)?;
1336 generator_values_from_constant_collection(&collection)?
1337 .into_iter()
1338 .map(TryInto::try_into)
1339 .collect::<Result<Vec<_>, _>>()
1340 .ok()
1341}
1342
1343fn eval_constant_comprehension(comprehension: &Comprehension) -> Option<Lit> {
1344 let mut values = Vec::new();
1345 eval_comprehension_qualifiers(comprehension, 0, &mut values)?;
1346 Some(Lit::AbstractLiteral(
1347 AbstractLiteral::matrix_implied_indices(values),
1348 ))
1349}
1350
1351fn eval_comprehension_qualifiers(
1352 comprehension: &Comprehension,
1353 qualifier_index: usize,
1354 values: &mut Vec<Lit>,
1355) -> Option<()> {
1356 if qualifier_index == comprehension.qualifiers.len() {
1357 values.push(eval_constant(&comprehension.return_expression)?);
1358 return Some(());
1359 }
1360
1361 match &comprehension.qualifiers[qualifier_index] {
1362 ComprehensionQualifier::Generator { ptr } => {
1363 let domain = ptr.domain()?;
1364 let generator_values = domain
1365 .resolve()
1366 .and_then(|x| x.values())
1367 .ok()?
1368 .collect_vec();
1369
1370 for value in generator_values {
1371 with_temporary_quantified_binding(ptr, &value, || {
1372 eval_comprehension_qualifiers(comprehension, qualifier_index + 1, values)
1373 })?;
1374 }
1375 }
1376 ComprehensionQualifier::ExpressionGenerator { ptr } => {
1377 let expr = ptr.as_quantified_expr()?.clone();
1379 let generator_values = generator_values_from_expr(&expr)?;
1380
1381 for value in generator_values {
1382 with_temporary_quantified_binding(ptr, &value, || {
1383 eval_comprehension_qualifiers(comprehension, qualifier_index + 1, values)
1384 })?;
1385 }
1386 }
1387 ComprehensionQualifier::Condition(condition) => match eval_constant(condition)? {
1388 Lit::Bool(true) => {
1389 eval_comprehension_qualifiers(comprehension, qualifier_index + 1, values)?
1390 }
1391 Lit::Bool(false) => {}
1392 _ => return None,
1393 },
1394 }
1395
1396 Some(())
1397}
1398
1399pub fn generator_values_from_expr(expr: &Expr) -> Option<Vec<Lit>> {
1404 generator_values_from_constant_collection(&eval_constant(expr)?)
1405}
1406
1407pub(crate) fn generator_values_from_constant_collection(lit: &Lit) -> Option<Vec<Lit>> {
1408 match lit {
1409 Lit::AbstractLiteral(AbstractLiteral::Set(values))
1410 | Lit::AbstractLiteral(AbstractLiteral::MSet(values))
1411 | Lit::AbstractLiteral(AbstractLiteral::Tuple(values)) => Some(values.clone()),
1412 Lit::AbstractLiteral(AbstractLiteral::Matrix(values, _)) => Some(values.clone()),
1413 Lit::AbstractLiteral(list) => list.unwrap_list().cloned(),
1414 _ => None,
1415 }
1416}
1417
1418fn with_temporary_quantified_binding<T>(
1419 quantified: &crate::ast::DeclarationPtr,
1420 value: &Lit,
1421 f: impl FnOnce() -> Option<T>,
1422) -> Option<T> {
1423 let mut targets = vec![quantified.clone()];
1424 if let DeclarationKind::Quantified(inner) = &*quantified.kind()
1425 && let Some(generator) = inner.generator()
1426 {
1427 targets.push(generator.clone());
1428 }
1429
1430 let mut originals = Vec::with_capacity(targets.len());
1431 for mut target in targets {
1432 let old_kind = target.replace_kind(DeclarationKind::TemporaryValueLetting(Expr::Atomic(
1433 Metadata::new(),
1434 Atom::Literal(value.clone()),
1435 )));
1436 originals.push((target, old_kind));
1437 }
1438
1439 let result = f();
1440
1441 for (mut target, old_kind) in originals.into_iter().rev() {
1442 let _ = target.replace_kind(old_kind);
1443 }
1444
1445 result
1446}
1447
1448#[cfg(test)]
1449mod tests {
1450 use super::*;
1451 use crate::matrix_expr;
1452
1453 fn int_lit(value: i32) -> Expr {
1454 Expr::Atomic(Metadata::new(), Atom::Literal(Lit::Int(value)))
1455 }
1456
1457 fn bool_lit(value: bool) -> Expr {
1458 Expr::Atomic(Metadata::new(), Atom::Literal(Lit::Bool(value)))
1459 }
1460
1461 fn variant_lit(alternative: &str, value: Expr) -> Expr {
1462 Expr::AbstractLiteral(
1463 Metadata::new(),
1464 AbstractLiteral::Variant(Moo::new(Field {
1465 name: crate::ast::Name::user(alternative),
1466 value,
1467 })),
1468 )
1469 }
1470
1471 fn root(exprs: Vec<Expr>) -> Expr {
1472 Expr::Root(Metadata::new(), exprs)
1473 }
1474
1475 #[test]
1476 fn evaluates_active_on_variant_literals() {
1477 let variant = Moo::new(variant_lit("value", int_lit(2)));
1478 let active = Expr::Active(
1479 Metadata::new(),
1480 variant.clone(),
1481 crate::ast::Name::user("value"),
1482 );
1483 let inactive = Expr::Active(Metadata::new(), variant, crate::ast::Name::user("flag"));
1484
1485 assert_eq!(eval_constant(&active), Some(Lit::Bool(true)));
1486 assert_eq!(eval_constant(&inactive), Some(Lit::Bool(false)));
1487 assert!(run_partial_evaluator_local(&active).is_ok());
1488
1489 let field = Expr::RecordField(
1490 Metadata::new(),
1491 Moo::new(variant_lit("value", int_lit(2))),
1492 crate::ast::Name::user("value"),
1493 );
1494 assert_eq!(eval_constant(&field), Some(Lit::Int(2)));
1495 }
1496
1497 #[test]
1498 fn local_root_partial_eval_strips_true_constraints() {
1499 let expr = root(vec![bool_lit(true), int_lit(1)]);
1500 let Expr::Root(_, exprs) = &expr else {
1501 panic!("expected root");
1502 };
1503 let normalised = normalise_root_constraints_local(exprs).unwrap();
1504 assert_eq!(normalised, root(vec![int_lit(1)]));
1505 }
1506
1507 #[test]
1508 fn local_root_partial_eval_propagates_false() {
1509 let expr = root(vec![bool_lit(false), int_lit(1)]);
1510 let Expr::Root(_, exprs) = &expr else {
1511 panic!("expected root");
1512 };
1513 let normalised = normalise_root_constraints_local(exprs).unwrap();
1514 assert_eq!(normalised, root(vec![bool_lit(false)]));
1515 }
1516
1517 #[test]
1518 fn deep_root_normalisation_folds_ground_constraint() {
1519 let expr = root(vec![Expr::Sum(
1520 Metadata::new(),
1521 Moo::new(matrix_expr![int_lit(1), int_lit(2), int_lit(3)]),
1522 )]);
1523 let normalised = normalise_root_constraints_deep(&expr).unwrap();
1524 assert_eq!(normalised, root(vec![int_lit(6)]));
1525 }
1526
1527 #[test]
1528 fn deep_root_normalisation_applies_partial_eval_steps() {
1529 let expr = root(vec![Expr::Or(
1530 Metadata::new(),
1531 Moo::new(matrix_expr![bool_lit(false), int_lit(1)]),
1532 )]);
1533 let normalised = normalise_root_constraints_deep(&expr).unwrap();
1534 assert_eq!(
1535 normalised,
1536 root(vec![Expr::Or(
1537 Metadata::new(),
1538 Moo::new(matrix_expr![int_lit(1)]),
1539 )])
1540 );
1541 }
1542
1543 #[test]
1544 fn selective_deep_root_normalisation_skips_solver_flat_constraints() {
1545 let flat = Expr::FlatProductEq(
1546 Metadata::new(),
1547 Moo::new(Atom::Literal(Lit::Int(1))),
1548 Moo::new(Atom::Literal(Lit::Int(2))),
1549 Moo::new(Atom::Literal(Lit::Int(3))),
1550 );
1551 let expr = root(vec![bool_lit(true), flat]);
1552 assert!(normalise_root_constraints_deep(&expr).is_none());
1553 }
1554
1555 #[test]
1556 fn deep_root_normalisation_terminates_on_already_folded_constraint() {
1557 let expr = root(vec![int_lit(5)]);
1558 assert!(normalise_root_constraints_deep(&expr).is_none());
1559 }
1560
1561 #[test]
1562 fn local_evaluator_normalisation_terminates_on_already_folded_literal() {
1563 let expr = int_lit(5);
1564 assert!(normalise_evaluator_local(&expr).is_none());
1565 }
1566
1567 #[test]
1568 fn constant_set_cardinality_is_folded() {
1569 let set = Expr::Atomic(
1570 Metadata::new(),
1571 Atom::Literal(Lit::AbstractLiteral(AbstractLiteral::Set(vec![
1572 Lit::Int(1),
1573 Lit::Int(2),
1574 ]))),
1575 );
1576 let cardinality = Expr::Card(Metadata::new(), Moo::new(set));
1577
1578 assert_eq!(eval_constant(&cardinality), Some(Lit::Int(2)));
1579 }
1580
1581 #[test]
1582 fn constant_set_minimum_and_maximum_are_folded() {
1583 let set = Expr::from(Lit::AbstractLiteral(AbstractLiteral::Set(vec![
1584 Lit::Int(4),
1585 Lit::Int(1),
1586 Lit::Int(3),
1587 ])));
1588
1589 assert_eq!(
1590 eval_constant(&Expr::Min(Metadata::new(), Moo::new(set.clone()))),
1591 Some(Lit::Int(1))
1592 );
1593 assert_eq!(
1594 eval_constant(&Expr::Max(Metadata::new(), Moo::new(set))),
1595 Some(Lit::Int(4))
1596 );
1597 }
1598
1599 #[test]
1600 fn constant_set_membership_supports_composite_values() {
1601 let member =
1602 Lit::AbstractLiteral(AbstractLiteral::Tuple(vec![Lit::Bool(true), Lit::Int(2)]));
1603 let set = Lit::AbstractLiteral(AbstractLiteral::Set(vec![member.clone()]));
1604 let membership = Expr::In(
1605 Metadata::new(),
1606 Moo::new(Expr::from(member)),
1607 Moo::new(Expr::from(set)),
1608 );
1609
1610 assert_eq!(eval_constant(&membership), Some(Lit::Bool(true)));
1611 }
1612
1613 #[test]
1614 fn constant_equality_supports_composite_values() {
1615 let tuple = Lit::AbstractLiteral(AbstractLiteral::Tuple(vec![Lit::Int(1), Lit::Int(3)]));
1616 let equal = Expr::Eq(
1617 Metadata::new(),
1618 Moo::new(Expr::from(tuple.clone())),
1619 Moo::new(Expr::from(tuple)),
1620 );
1621 let unequal = Expr::Neq(
1622 Metadata::new(),
1623 Moo::new(Expr::from(Lit::AbstractLiteral(AbstractLiteral::Tuple(
1624 vec![Lit::Int(1), Lit::Int(3)],
1625 )))),
1626 Moo::new(Expr::from(Lit::AbstractLiteral(AbstractLiteral::Tuple(
1627 vec![Lit::Int(1), Lit::Int(4)],
1628 )))),
1629 );
1630
1631 assert_eq!(eval_constant(&equal), Some(Lit::Bool(true)));
1632 assert_eq!(eval_constant(&unequal), Some(Lit::Bool(true)));
1633
1634 let set_equality = Expr::Eq(
1635 Metadata::new(),
1636 Moo::new(Expr::from(Lit::AbstractLiteral(AbstractLiteral::Set(
1637 vec![Lit::Int(1), Lit::Int(2)],
1638 )))),
1639 Moo::new(Expr::from(Lit::AbstractLiteral(AbstractLiteral::Set(
1640 vec![Lit::Int(2), Lit::Int(1)],
1641 )))),
1642 );
1643 assert_eq!(eval_constant(&set_equality), Some(Lit::Bool(true)));
1644 }
1645
1646 #[test]
1647 fn finish_root_normalisation_applies_local_root_rules() {
1648 let expr = root(vec![bool_lit(true), int_lit(1)]);
1649 let normalised = finish_root_evaluator_normalisation(&expr).unwrap();
1650 assert_eq!(normalised, root(vec![int_lit(1)]));
1651 }
1652
1653 #[test]
1654 fn selective_deep_only_constraint_folds_target_index() {
1655 let ground = Expr::Sum(
1656 Metadata::new(),
1657 Moo::new(matrix_expr![int_lit(1), int_lit(2), int_lit(3)]),
1658 );
1659 let untouched = Expr::Sum(
1660 Metadata::new(),
1661 Moo::new(matrix_expr![int_lit(4), int_lit(5)]),
1662 );
1663 let expr = root(vec![ground, untouched.clone()]);
1664 let normalised = normalise_root_selective_deep_expr(&expr, Some(0)).unwrap();
1665 assert_eq!(normalised, root(vec![int_lit(6), untouched]));
1666 }
1667
1668 #[test]
1669 fn finish_root_normalisation_flattens_top_level_and() {
1670 let expr = root(vec![Expr::And(
1671 Metadata::new(),
1672 Moo::new(matrix_expr![bool_lit(true), int_lit(1), int_lit(2)]),
1673 )]);
1674 let normalised = finish_root_evaluator_normalisation(&expr).unwrap();
1675 assert_eq!(normalised, root(vec![int_lit(1), int_lit(2)]));
1676 }
1677
1678 fn permutation_lit(cycles: Vec<Vec<Expr>>) -> Expr {
1679 Expr::AbstractLiteral(Metadata::new(), AbstractLiteral::Permutation(cycles))
1680 }
1681
1682 #[test]
1683 fn evaluates_image_on_a_permutation_literal_moved_point() {
1684 let p = Moo::new(permutation_lit(vec![
1685 vec![int_lit(1), int_lit(3), int_lit(5)],
1686 vec![int_lit(2), int_lit(4)],
1687 ]));
1688 let image_1 = Expr::Image(Metadata::new(), p.clone(), Moo::new(int_lit(1)));
1689 let image_3 = Expr::Image(Metadata::new(), p.clone(), Moo::new(int_lit(3)));
1690 let image_5 = Expr::Image(Metadata::new(), p.clone(), Moo::new(int_lit(5)));
1691 let image_2 = Expr::Image(Metadata::new(), p.clone(), Moo::new(int_lit(2)));
1692 let image_4 = Expr::Image(Metadata::new(), p, Moo::new(int_lit(4)));
1693
1694 assert_eq!(eval_constant(&image_1), Some(Lit::Int(3)));
1695 assert_eq!(eval_constant(&image_3), Some(Lit::Int(5)));
1696 assert_eq!(eval_constant(&image_5), Some(Lit::Int(1)));
1697 assert_eq!(eval_constant(&image_2), Some(Lit::Int(4)));
1698 assert_eq!(eval_constant(&image_4), Some(Lit::Int(2)));
1699 }
1700
1701 #[test]
1702 fn evaluates_image_on_a_permutation_literal_fixed_point() {
1703 let p = Moo::new(permutation_lit(vec![vec![int_lit(1), int_lit(2)]]));
1704 let image_3 = Expr::Image(Metadata::new(), p, Moo::new(int_lit(3)));
1705
1706 assert_eq!(eval_constant(&image_3), Some(Lit::Int(3)));
1708 }
1709}