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