1#[cfg(test)]
69use std::sync::Arc;
70
71use crate::ast::Value;
72use crate::iteration::comprehension::ast::Comprehension;
73use crate::iteration::comprehension::cardinality::{Interval, ProductMeasure};
74use crate::iteration::comprehension::eval_source::{EvalContext, NoneRead, evaluate_reading};
75use crate::iteration::comprehension::measure::AxisMeasure;
76use crate::iteration::comprehension::metadata::{IndexFn, cycle_length};
77use crate::iteration::comprehension::predicate::CompiledPredicate;
78use crate::iteration::comprehension::source::Source;
79use crate::iteration::comprehension::strategies::{Selection, ranked_filter, shape_input};
80use crate::iteration::comprehension::strategy::StrategyName;
81#[cfg(test)]
82use crate::kernel::PolydatKernel;
83use crate::kernel::interp::Lookup;
84
85pub type RuntimeTuple = Vec<(String, Value)>;
94
95struct EvaluatedNode {
105 tuples: Vec<RuntimeTuple>,
106 index_fn: Option<IndexFn>,
107}
108
109#[derive(Debug, Clone)]
111pub enum RuntimeError {
112 SourceEval {
115 var: String,
117 source: String,
119 message: String,
121 },
122 FilterEval {
124 predicate: String,
126 message: String,
128 },
129 OrderEval {
131 strategy: StrategyName,
133 message: String,
135 },
136 StrategyRejectsInput {
139 strategy: StrategyName,
141 index_fn: Option<IndexFn>,
143 },
144 UnsupportedShape(String),
149 ZipLengthMismatch {
153 lengths: Vec<u64>,
155 },
156}
157
158impl std::fmt::Display for RuntimeError {
159 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
160 match self {
161 RuntimeError::SourceEval {
162 var,
163 source,
164 message,
165 } => {
166 write!(f, "for_each clause '{var} in {source}': {message}")
167 }
168 RuntimeError::FilterEval { predicate, message } => {
169 write!(f, "comprehension filter '{predicate}': {message}")
170 }
171 RuntimeError::OrderEval { strategy, message } => {
172 write!(f, "order strategy {strategy:?}: {message}")
173 }
174 RuntimeError::StrategyRejectsInput { strategy, index_fn } => write!(
175 f,
176 "order strategy {strategy:?} rejects input shape {index_fn:?} \
177 (V4: per-strategy IndexFn contract; see comprehension_forms.md §3.6's \
178 strategy table)"
179 ),
180 RuntimeError::UnsupportedShape(msg) => write!(f, "{msg}"),
181 RuntimeError::ZipLengthMismatch { lengths } => {
182 write!(f, "zip strict: child lengths differ ({lengths:?})")
183 }
184 }
185 }
186}
187
188impl std::error::Error for RuntimeError {}
189
190pub fn evaluate_for_iteration(
199 comp: &Comprehension,
200 scope: &dyn Lookup,
201) -> Result<Vec<RuntimeTuple>, RuntimeError> {
202 evaluate_indexed(comp, scope).map(|t| t.to_vec())
203}
204
205pub fn evaluate_for_iteration_reported(
220 comp: &Comprehension,
221 scope: &dyn Lookup,
222) -> Result<EvaluatedIteration, RuntimeError> {
223 evaluate_for_iteration_with_none_reads(comp, scope).map(|(evaluated, _)| evaluated)
224}
225
226pub fn evaluate_for_iteration_with_none_reads(
235 comp: &Comprehension,
236 scope: &dyn Lookup,
237) -> Result<(EvaluatedIteration, NoneReads), RuntimeError> {
238 let mut state = EvalState::new(comp, scope);
239 let (node, _) = state.index_node(comp, &[])?;
240 let tuples = IndexedTuples { node }.to_vec();
241 Ok(state.finish(tuples))
242}
243
244pub fn evaluate_indexed(
264 comp: &Comprehension,
265 scope: &dyn Lookup,
266) -> Result<IndexedTuples, RuntimeError> {
267 let mut state = EvalState::new(comp, scope);
268 let (node, _) = state.index_node(comp, &[])?;
269 Ok(IndexedTuples { node })
270}
271
272pub fn evaluate_for_iteration_materialized(
280 comp: &Comprehension,
281 scope: &dyn Lookup,
282) -> Result<EvaluatedIteration, RuntimeError> {
283 evaluate_for_iteration_materialized_with_none_reads(comp, scope).map(|(evaluated, _)| evaluated)
284}
285
286pub fn evaluate_for_iteration_materialized_with_none_reads(
290 comp: &Comprehension,
291 scope: &dyn Lookup,
292) -> Result<(EvaluatedIteration, NoneReads), RuntimeError> {
293 let mut state = EvalState::new(comp, scope);
294 let tuples = state.evaluate_node(comp, &[])?.tuples;
295 Ok(state.finish(tuples))
296}
297
298#[derive(Debug, Clone)]
301pub struct IndexedTuples {
302 node: Indexed,
303}
304
305impl IndexedTuples {
306 pub fn len(&self) -> u64 {
308 self.node.len()
309 }
310
311 pub fn is_empty(&self) -> bool {
313 self.len() == 0
314 }
315
316 pub fn get(&self, i: u64) -> Option<RuntimeTuple> {
318 if i >= self.len() {
319 return None;
320 }
321 let mut out = RuntimeTuple::new();
322 self.node.append_at(i, &mut out);
323 Some(out)
324 }
325
326 pub fn iter(&self) -> impl Iterator<Item = RuntimeTuple> + '_ {
328 (0..self.len()).filter_map(|i| self.get(i))
329 }
330
331 pub fn to_vec(&self) -> Vec<RuntimeTuple> {
333 self.iter().collect()
334 }
335}
336
337#[derive(Debug, Clone)]
340enum Indexed {
341 Tuples(Vec<RuntimeTuple>),
344 Clause { name: String, values: ClauseValues },
346 Product {
350 children: Vec<Indexed>,
351 lens: Vec<u64>,
352 len: u64,
353 },
354 Lockstep { children: Vec<Indexed>, len: u64 },
356 Cycle { children: Vec<Indexed>, len: u64 },
360 Concat { children: Vec<Indexed>, len: u64 },
362 Select {
364 child: Box<Indexed>,
365 selection: Selection,
366 },
367}
368
369#[derive(Debug, Clone)]
371enum ClauseValues {
372 Range { lo: i64, step: i64, len: u64 },
374 List(Vec<Value>),
376}
377
378impl ClauseValues {
379 fn len(&self) -> u64 {
380 match self {
381 ClauseValues::Range { len, .. } => *len,
382 ClauseValues::List(values) => values.len() as u64,
383 }
384 }
385
386 fn at(&self, i: u64) -> Value {
387 match self {
388 ClauseValues::Range { lo, step, .. } => {
391 Value::U64((i128::from(*lo) + i128::from(i) * i128::from(*step)) as i64 as u64)
392 }
393 ClauseValues::List(values) => values[i as usize].clone(),
394 }
395 }
396}
397
398impl Indexed {
399 fn len(&self) -> u64 {
400 match self {
401 Indexed::Tuples(tuples) => tuples.len() as u64,
402 Indexed::Clause { values, .. } => values.len(),
403 Indexed::Product { len, .. }
404 | Indexed::Lockstep { len, .. }
405 | Indexed::Cycle { len, .. }
406 | Indexed::Concat { len, .. } => *len,
407 Indexed::Select { selection, .. } => selection.len(),
408 }
409 }
410
411 fn append_at(&self, i: u64, out: &mut RuntimeTuple) {
414 match self {
415 Indexed::Tuples(tuples) => out.extend(tuples[i as usize].iter().cloned()),
416 Indexed::Clause { name, values } => out.push((name.clone(), values.at(i))),
417 Indexed::Product { children, lens, .. } => {
418 let mut digits = vec![0u64; lens.len()];
419 let mut rest = i;
420 for (d, len) in digits.iter_mut().zip(lens).rev() {
421 *d = rest % len;
422 rest /= len;
423 }
424 for (child, d) in children.iter().zip(digits) {
425 child.append_at(d, out);
426 }
427 }
428 Indexed::Lockstep { children, .. } => {
429 for child in children {
430 child.append_at(i, out);
431 }
432 }
433 Indexed::Cycle { children, .. } => {
434 for child in children {
435 child.append_at(i % child.len(), out);
436 }
437 }
438 Indexed::Concat { children, .. } => {
439 let mut offset = i;
440 for child in children {
441 let len = child.len();
442 if offset < len {
443 child.append_at(offset, out);
444 return;
445 }
446 offset -= len;
447 }
448 }
449 Indexed::Select { child, selection } => {
450 if let Some(p) = selection.get(i) {
451 child.append_at(p, out);
452 }
453 }
454 }
455 }
456}
457
458#[derive(Debug, Clone, PartialEq, Eq)]
478pub struct ClauseYield {
479 pub var: String,
481 pub source: Option<String>,
483 pub evaluations: usize,
485 pub values: usize,
487}
488
489#[derive(Debug, Clone, Default, PartialEq, Eq)]
500pub struct NoneReads {
501 per_clause: Vec<Vec<NoneRead>>,
502}
503
504impl NoneReads {
505 pub fn clause(&self, index: usize) -> &[NoneRead] {
508 self.per_clause.get(index).map_or(&[], Vec::as_slice)
509 }
510
511 pub fn iter(&self) -> impl Iterator<Item = &[NoneRead]> {
513 self.per_clause.iter().map(Vec::as_slice)
514 }
515
516 pub fn is_empty(&self) -> bool {
518 self.per_clause.iter().all(Vec::is_empty)
519 }
520}
521
522#[derive(Debug, Clone)]
525pub struct EvaluatedIteration {
526 pub tuples: Vec<RuntimeTuple>,
528 pub clauses: Vec<ClauseYield>,
530}
531
532struct EvalState<'a> {
533 scope: &'a dyn Lookup,
536 yields: Vec<ClauseYield>,
540 none_reads: Vec<Vec<NoneRead>>,
543 by_leaf: std::collections::HashMap<usize, usize>,
548 mult: usize,
553}
554
555impl<'a> EvalState<'a> {
556 fn new(comp: &Comprehension, scope: &'a dyn Lookup) -> Self {
558 let mut state = EvalState {
559 scope,
560 yields: Vec::new(),
561 none_reads: Vec::new(),
562 by_leaf: std::collections::HashMap::new(),
563 mult: 1,
564 };
565 state.enumerate_leaves(comp);
566 state
567 }
568
569 fn finish(self, tuples: Vec<RuntimeTuple>) -> (EvaluatedIteration, NoneReads) {
572 (
573 EvaluatedIteration {
574 tuples,
575 clauses: self.yields,
576 },
577 NoneReads {
578 per_clause: self.none_reads,
579 },
580 )
581 }
582
583 fn enumerate_leaves(&mut self, node: &Comprehension) {
584 match node {
585 Comprehension::Clause { name, source } => {
586 self.by_leaf
587 .insert(std::ptr::from_ref(source) as usize, self.yields.len());
588 self.yields.push(ClauseYield {
589 var: name.clone(),
590 source: source.to_text(),
591 evaluations: 0,
592 values: 0,
593 });
594 self.none_reads.push(Vec::new());
595 }
596 Comprehension::Cartesian { children }
597 | Comprehension::Zip { children, .. }
598 | Comprehension::Union { children } => {
599 for child in children {
600 self.enumerate_leaves(child);
601 }
602 }
603 Comprehension::Filter { child, .. } | Comprehension::Order { child, .. } => {
604 self.enumerate_leaves(child);
605 }
606 }
607 }
608
609 fn record_yield(&mut self, source: &Source, values: usize, reads_none: Vec<NoneRead>) {
613 if let Some(&i) = self.by_leaf.get(&(std::ptr::from_ref(source) as usize)) {
614 let clause = &mut self.yields[i];
615 clause.evaluations = clause.evaluations.saturating_add(self.mult);
616 clause.values = clause
617 .values
618 .saturating_add(values.saturating_mul(self.mult));
619 if !reads_none.is_empty() {
620 let reads = &mut self.none_reads[i];
621 reads.extend(reads_none);
622 reads.sort();
623 reads.dedup();
624 }
625 }
626 }
627}
628
629impl EvalState<'_> {
630 fn evaluate_node(
631 &mut self,
632 node: &Comprehension,
633 prefix: &[(String, Value)],
634 ) -> Result<EvaluatedNode, RuntimeError> {
635 match node {
636 Comprehension::Clause { name, source } => self.evaluate_clause(name, source, prefix),
637 Comprehension::Cartesian { children } => self.evaluate_cartesian(children, prefix),
638 Comprehension::Zip { children, mode } => self.evaluate_zip(children, *mode, prefix),
639 Comprehension::Union { children } => self.evaluate_union(children, prefix),
640 Comprehension::Filter { child, predicate } => {
641 let inner = self.evaluate_node(child, prefix)?;
642 self.apply_filter(inner, predicate)
643 }
644 Comprehension::Order {
645 child,
646 strategy,
647 truncation,
648 seed,
649 } => {
650 let child = shape_input(child, *strategy);
653 if has_continuous_axis(child) {
658 let sampled =
659 self.sample_space(child, prefix, *strategy, *truncation, *seed)?;
660 return Ok(EvaluatedNode {
661 index_fn: Some(selected(sampled.tuples.len() as u64)),
662 tuples: sampled.tuples,
663 });
664 }
665 if let Some((child, predicate)) = ranked_filter(child, *strategy) {
668 let inner = self.evaluate_node(child, prefix)?;
669 let predicate = CompiledPredicate::new(predicate);
670 let mut survivors = Vec::new();
671 for (p, tuple) in inner.tuples.iter().enumerate() {
672 if predicate.keeps(tuple, self.scope)? {
673 survivors.push(p as u64);
674 }
675 }
676 let selection = surviving_selection(
677 *strategy,
678 inner.index_fn.as_ref(),
679 inner.tuples.len() as u64,
680 *truncation,
681 *seed,
682 &survivors,
683 )?;
684 return Ok(EvaluatedNode {
685 tuples: selection
686 .iter()
687 .map(|p| inner.tuples[p as usize].clone())
688 .collect(),
689 index_fn: Some(selected(selection.len())),
690 });
691 }
692 let inner = self.evaluate_node(child, prefix)?;
693 self.apply_order(inner, *strategy, *truncation, *seed)
694 }
695 }
696 }
697
698 fn evaluate_source(
701 &mut self,
702 name: &str,
703 source: &Source,
704 prefix: &[(String, Value)],
705 ) -> Result<crate::iteration::comprehension::eval_source::EvaluatedSource, RuntimeError> {
706 let ctx = EvalContext {
707 var_name: name,
708 scope: self.scope,
709 prefix,
710 };
711 let (evaluated, reads_none) =
712 evaluate_reading(source, Some(&ctx)).map_err(|e| match e {
713 crate::iteration::comprehension::eval_source::EvalError::EvalFailed {
714 var,
715 source,
716 message,
717 } => RuntimeError::SourceEval {
718 var,
719 source,
720 message,
721 },
722 crate::iteration::comprehension::eval_source::EvalError::NeedsContext => {
723 RuntimeError::UnsupportedShape(format!(
724 "clause '{name}': source requires kernel context but evaluator \
725 provided none — internal bug in runtime walker"
726 ))
727 }
728 })?;
729 self.record_yield(source, evaluated.values.len(), reads_none);
730 Ok(evaluated)
731 }
732
733 fn evaluate_clause(
734 &mut self,
735 name: &str,
736 source: &Source,
737 prefix: &[(String, Value)],
738 ) -> Result<EvaluatedNode, RuntimeError> {
739 let evaluated = self.evaluate_source(name, source, prefix)?;
740
741 if evaluated.values.is_empty() {
742 return Ok(EvaluatedNode {
748 tuples: Vec::new(),
749 index_fn: Some(evaluated.index_fn),
750 });
751 }
752 let tuples: Vec<RuntimeTuple> = evaluated
753 .values
754 .into_iter()
755 .map(|v| vec![(name.to_string(), v)])
756 .collect();
757 Ok(EvaluatedNode {
758 tuples,
759 index_fn: Some(evaluated.index_fn),
760 })
761 }
762
763 fn evaluate_cartesian(
764 &mut self,
765 children: &[Comprehension],
766 prefix: &[(String, Value)],
767 ) -> Result<EvaluatedNode, RuntimeError> {
768 if children.is_empty() {
769 return Ok(EvaluatedNode {
770 tuples: vec![Vec::new()],
771 index_fn: Some(IndexFn::Lattice {
772 axis_sizes: vec![1],
773 }),
774 });
775 }
776 let mut child_index_fns: Vec<(Option<IndexFn>, u64)> = Vec::with_capacity(children.len());
777 let mut dependent_observed = false;
778 let result_tuples = self.evaluate_cartesian_rec(
779 children.len(),
780 children,
781 prefix,
782 &mut child_index_fns,
783 &mut dependent_observed,
784 )?;
785
786 let combined = if dependent_observed {
792 None
793 } else {
794 let index_fns: Vec<Option<IndexFn>> =
795 child_index_fns.into_iter().map(|(idx, _)| idx).collect();
796 combine_cartesian_index_fn(&index_fns)
797 };
798 Ok(EvaluatedNode {
799 tuples: result_tuples,
800 index_fn: combined,
801 })
802 }
803
804 fn evaluate_cartesian_rec(
805 &mut self,
806 child_count: usize,
807 children: &[Comprehension],
808 prefix: &[(String, Value)],
809 child_index_fns: &mut Vec<(Option<IndexFn>, u64)>,
810 dependent_observed: &mut bool,
811 ) -> Result<Vec<RuntimeTuple>, RuntimeError> {
812 if children.is_empty() {
813 return Ok(vec![Vec::new()]);
814 }
815 let (head, tail) = children.split_first().unwrap();
816 let head_eval = self.evaluate_node(head, prefix)?;
817 let head_axis_len = head_eval.tuples.len() as u64;
818 let depth = child_count - children.len();
823 match child_index_fns.get(depth) {
824 None => child_index_fns.push((head_eval.index_fn.clone(), head_axis_len)),
825 Some((_, first)) if *first != head_axis_len => *dependent_observed = true,
826 Some(_) => {}
827 }
828
829 if tail.is_empty() {
830 return Ok(head_eval.tuples);
831 }
832 let mut out = Vec::new();
833 for head_tuple in head_eval.tuples {
834 let mut extended_prefix: Vec<(String, Value)> = prefix.to_vec();
835 extended_prefix.extend(head_tuple.iter().cloned());
836 let tail_tuples = self.evaluate_cartesian_rec(
837 child_count,
838 tail,
839 &extended_prefix,
840 child_index_fns,
841 dependent_observed,
842 )?;
843 for tail_tuple in tail_tuples {
844 let mut merged = head_tuple.clone();
845 merged.extend(tail_tuple);
846 out.push(merged);
847 }
848 }
849 Ok(out)
850 }
851
852 fn evaluate_zip(
853 &mut self,
854 children: &[Comprehension],
855 mode: crate::iteration::comprehension::strategy::ZipMode,
856 prefix: &[(String, Value)],
857 ) -> Result<EvaluatedNode, RuntimeError> {
858 use crate::iteration::comprehension::strategy::ZipMode;
859 if children.is_empty() {
860 return Ok(EvaluatedNode {
861 tuples: vec![Vec::new()],
862 index_fn: Some(IndexFn::Lockstep { length: 1 }),
863 });
864 }
865 let per_child: Vec<EvaluatedNode> = children
866 .iter()
867 .map(|c| self.evaluate_node(c, prefix))
868 .collect::<Result<_, _>>()?;
869 let lengths: Vec<usize> = per_child.iter().map(|n| n.tuples.len()).collect();
870 let iter_count = match mode {
871 ZipMode::Strict => {
872 let first = lengths.first().copied().unwrap_or(0);
873 if lengths.iter().any(|&n| n != first) {
874 return Err(RuntimeError::ZipLengthMismatch {
875 lengths: lengths.iter().map(|&n| n as u64).collect(),
876 });
877 }
878 first
879 }
880 ZipMode::Truncate => lengths.iter().copied().min().unwrap_or(0),
881 ZipMode::Cycle => {
882 let counts: Vec<u64> = lengths.iter().map(|&n| n as u64).collect();
883 cycle_length(&counts) as usize
884 }
885 };
886 let mut tuples = Vec::with_capacity(iter_count);
887 for i in 0..iter_count {
888 let mut bindings: RuntimeTuple = Vec::new();
889 for (child, &len) in per_child.iter().zip(lengths.iter()) {
890 let idx = match mode {
891 ZipMode::Cycle => i % len,
892 _ => i,
893 };
894 bindings.extend(child.tuples[idx].iter().cloned());
895 }
896 tuples.push(bindings);
897 }
898 let index_fn = match mode {
899 ZipMode::Strict | ZipMode::Truncate => Some(IndexFn::Lockstep {
900 length: iter_count as u64,
901 }),
902 ZipMode::Cycle => Some(IndexFn::Modular {
903 axis_sizes: lengths.iter().map(|n| *n as u64).collect(),
904 }),
905 };
906 Ok(EvaluatedNode { tuples, index_fn })
907 }
908
909 fn evaluate_union(
910 &mut self,
911 children: &[Comprehension],
912 prefix: &[(String, Value)],
913 ) -> Result<EvaluatedNode, RuntimeError> {
914 let mut tuples = Vec::new();
915 let mut segment_sizes = Vec::with_capacity(children.len());
916 let mut all_segments_addressable = true;
917 for child in children {
918 let sub = self.evaluate_node(child, prefix)?;
919 segment_sizes.push(sub.tuples.len() as u64);
920 if sub.index_fn.is_none() {
921 all_segments_addressable = false;
922 }
923 tuples.extend(sub.tuples);
924 }
925 let index_fn = if all_segments_addressable {
926 Some(IndexFn::Concatenation { segment_sizes })
927 } else {
928 None
929 };
930 Ok(EvaluatedNode { tuples, index_fn })
931 }
932
933 fn apply_filter(
934 &mut self,
935 input: EvaluatedNode,
936 predicate: &str,
937 ) -> Result<EvaluatedNode, RuntimeError> {
938 let predicate = CompiledPredicate::new(predicate);
939 let mut out = Vec::with_capacity(input.tuples.len());
940 for tuple in input.tuples {
941 if predicate.keeps(&tuple, self.scope)? {
942 out.push(tuple);
943 }
944 }
945 Ok(EvaluatedNode {
948 tuples: out,
949 index_fn: None,
950 })
951 }
952
953 fn sample_space(
966 &mut self,
967 child: &Comprehension,
968 prefix: &[(String, Value)],
969 strategy: StrategyName,
970 truncation: Option<u64>,
971 seed: Option<u64>,
972 ) -> Result<EvaluatedNode, RuntimeError> {
973 let mut space = SampleSpace::default();
974 self.collect_sample_space(child, prefix, &mut space, &mut Vec::new())?;
975 let discrete_axes: Vec<u64> = space
976 .axes
977 .iter()
978 .filter_map(|a| match a {
979 SampleAxis::Discrete(tuples) => Some(tuples.len() as u64),
980 SampleAxis::Continuous { .. } => None,
981 })
982 .collect();
983 if discrete_axes.contains(&0) {
984 return Ok(EvaluatedNode {
985 tuples: Vec::new(),
986 index_fn: None,
987 });
988 }
989 let (intervals, measures): (Vec<Interval>, Vec<ProductMeasure>) = space
990 .axes
991 .iter()
992 .filter_map(|a| match a {
993 SampleAxis::Continuous {
994 interval, measure, ..
995 } => Some((
996 interval.clone(),
997 match measure {
998 AxisMeasure::Uniform => ProductMeasure::Uniform,
999 AxisMeasure::Named { name, .. } => ProductMeasure::Named(*name),
1000 },
1001 )),
1002 SampleAxis::Discrete(_) => None,
1003 })
1004 .unzip();
1005 let sequence = !matches!(strategy, StrategyName::Extrema);
1006 let index_fn = if !sequence {
1011 IndexFn::Lattice {
1012 axis_sizes: space
1013 .axes
1014 .iter()
1015 .map(|a| match a {
1016 SampleAxis::Discrete(tuples) => tuples.len() as u64,
1017 SampleAxis::Continuous { .. } => 2,
1018 })
1019 .collect(),
1020 }
1021 } else if discrete_axes.is_empty() {
1022 IndexFn::Continuous {
1023 intervals,
1024 measure: ProductMeasure::Product(measures),
1025 }
1026 } else {
1027 IndexFn::Hybrid {
1028 discrete_axes,
1029 continuous_axes: intervals,
1030 measure: ProductMeasure::Product(measures),
1031 }
1032 };
1033
1034 let mut want = truncation;
1035 let mut rounds = 0;
1036 loop {
1037 let multi_indices = draw_sample(&index_fn, strategy, want, seed)?;
1038 let drawn = multi_indices.len() as u64;
1039 let tuples = multi_indices
1040 .iter()
1041 .map(|mi| space.realize(mi, strategy))
1042 .collect();
1043 let mut node = EvaluatedNode {
1044 tuples,
1045 index_fn: None,
1046 };
1047 for predicate in &space.predicates {
1048 node = self.apply_filter(node, predicate)?;
1049 }
1050 let (Some(n), Some(asked)) = (truncation, want) else {
1051 return Ok(node);
1052 };
1053 let enough = node.tuples.len() as u64 >= n;
1054 let exhausted = drawn < asked;
1055 if !sequence {
1056 return Ok(node);
1057 }
1058 if enough || exhausted || rounds >= SAMPLE_ROUNDS {
1059 node.tuples.truncate(n as usize);
1060 return Ok(node);
1061 }
1062
1063 want = Some(asked.saturating_mul(2));
1064 rounds += 1;
1065 }
1066 }
1067
1068 fn collect_sample_space(
1076 &mut self,
1077 c: &Comprehension,
1078 prefix: &[(String, Value)],
1079 space: &mut SampleSpace,
1080 bound: &mut Vec<String>,
1081 ) -> Result<(), RuntimeError> {
1082 let measure_error = |name: &str, message: String| RuntimeError::SourceEval {
1083 var: name.to_string(),
1084 source: "<continuous>".to_string(),
1085 message,
1086 };
1087 match c {
1088 Comprehension::Clause {
1089 name,
1090 source: Source::ContinuousInterval { interval, measure },
1091 } => {
1092 let measure =
1093 AxisMeasure::from_product(measure, 0).map_err(|m| measure_error(name, m))?;
1094 space.axes.push(SampleAxis::Continuous {
1095 name: name.clone(),
1096 interval: interval.clone(),
1097 measure,
1098 });
1099 bound.push(name.clone());
1100 }
1101 Comprehension::Clause {
1102 name,
1103 source:
1104 Source::Distribution {
1105 distribution,
1106 support,
1107 params,
1108 },
1109 } => {
1110 let measure = AxisMeasure::named(*distribution, params)
1111 .map_err(|m| measure_error(name, m))?;
1112 space.axes.push(SampleAxis::Continuous {
1113 name: name.clone(),
1114 interval: support.clone(),
1115 measure,
1116 });
1117 bound.push(name.clone());
1118 }
1119 Comprehension::Clause { name, source } => {
1120 let references = c.source_names_read();
1121 if let Some(dep) = bound.iter().find(|b| references.contains(*b)) {
1122 return Err(RuntimeError::UnsupportedShape(format!(
1123 "clause '{name}' references '{dep}' beside a continuous axis; \
1124 a sampled cartesian is independent (comprehension_forms.md §5, V4)"
1125 )));
1126 }
1127 let node = self.evaluate_clause(name, source, prefix)?;
1128 space.axes.push(SampleAxis::Discrete(node.tuples));
1129 bound.push(name.clone());
1130 }
1131 Comprehension::Cartesian { children } => {
1132 for child in children {
1133 self.collect_sample_space(child, prefix, space, bound)?;
1134 }
1135 }
1136 Comprehension::Filter { child, predicate } => {
1137 self.collect_sample_space(child, prefix, space, bound)?;
1138 space.predicates.push(predicate.clone());
1139 }
1140 Comprehension::Zip { .. }
1141 | Comprehension::Union { .. }
1142 | Comprehension::Order { .. } => {
1143 let node = self.evaluate_node(c, prefix)?;
1144 bound.extend(c.coordinate_names());
1145 space.axes.push(SampleAxis::Discrete(node.tuples));
1146 }
1147 }
1148 Ok(())
1149 }
1150
1151 fn apply_order(
1152 &mut self,
1153 input: EvaluatedNode,
1154 strategy: StrategyName,
1155 truncation: Option<u64>,
1156 seed: Option<u64>,
1157 ) -> Result<EvaluatedNode, RuntimeError> {
1158 let selection = order_selection(
1162 strategy,
1163 input.index_fn.as_ref(),
1164 input.tuples.len() as u64,
1165 truncation,
1166 seed,
1167 )?;
1168 let out = selection
1169 .iter()
1170 .map(|p| input.tuples[p as usize].clone())
1171 .collect();
1172 Ok(EvaluatedNode {
1173 tuples: out,
1174 index_fn: order_output(strategy, truncation, input.index_fn, selection.len()),
1175 })
1176 }
1177}
1178
1179fn selected(len: u64) -> IndexFn {
1183 IndexFn::Lattice {
1184 axis_sizes: vec![len],
1185 }
1186}
1187
1188fn order_output(
1193 strategy: StrategyName,
1194 truncation: Option<u64>,
1195 input: Option<IndexFn>,
1196 len: u64,
1197) -> Option<IndexFn> {
1198 match (strategy, truncation) {
1199 (StrategyName::Lex, None) => input,
1200 (StrategyName::Lex, Some(_)) => input.map(|_| selected(len)),
1201 _ => Some(selected(len)),
1202 }
1203}
1204
1205impl EvalState<'_> {
1210 fn index_node(
1211 &mut self,
1212 node: &Comprehension,
1213 prefix: &[(String, Value)],
1214 ) -> Result<(Indexed, Option<IndexFn>), RuntimeError> {
1215 match node {
1216 Comprehension::Clause { name, source } => self.index_clause(name, source, prefix),
1217 Comprehension::Cartesian { children } => self.index_cartesian(children, prefix),
1218 Comprehension::Zip { children, mode } => self.index_zip(children, *mode, prefix),
1219 Comprehension::Union { children } => self.index_union(children, prefix),
1220 Comprehension::Filter { child, predicate } => {
1221 let (inner, _) = self.index_node(child, prefix)?;
1224 let predicate = CompiledPredicate::new(predicate);
1225 let mut kept = Vec::new();
1226 let mut tuple = RuntimeTuple::new();
1227 for i in 0..inner.len() {
1228 tuple.clear();
1229 inner.append_at(i, &mut tuple);
1230 if predicate.keeps(&tuple, self.scope)? {
1231 kept.push(tuple.clone());
1232 }
1233 }
1234 Ok((Indexed::Tuples(kept), None))
1235 }
1236 Comprehension::Order {
1237 child,
1238 strategy,
1239 truncation,
1240 seed,
1241 } => {
1242 let child = shape_input(child, *strategy);
1243 if has_continuous_axis(child) {
1244 let sampled =
1245 self.sample_space(child, prefix, *strategy, *truncation, *seed)?;
1246 let len = sampled.tuples.len() as u64;
1247 return Ok((Indexed::Tuples(sampled.tuples), Some(selected(len))));
1248 }
1249 if let Some((child, predicate)) = ranked_filter(child, *strategy) {
1254 let (inner, index_fn) = self.index_node(child, prefix)?;
1255 let predicate = CompiledPredicate::new(predicate);
1256 let mut survivors = Vec::new();
1257 let mut tuple = RuntimeTuple::new();
1258 for p in 0..inner.len() {
1259 tuple.clear();
1260 inner.append_at(p, &mut tuple);
1261 if predicate.keeps(&tuple, self.scope)? {
1262 survivors.push(p);
1263 }
1264 }
1265 let selection = surviving_selection(
1266 *strategy,
1267 index_fn.as_ref(),
1268 inner.len(),
1269 *truncation,
1270 *seed,
1271 &survivors,
1272 )?;
1273 let len = selection.len();
1274 return Ok((
1275 Indexed::Select {
1276 child: Box::new(inner),
1277 selection,
1278 },
1279 Some(selected(len)),
1280 ));
1281 }
1282 let (inner, index_fn) = self.index_node(child, prefix)?;
1283 let selection = order_selection(
1284 *strategy,
1285 index_fn.as_ref(),
1286 inner.len(),
1287 *truncation,
1288 *seed,
1289 )?;
1290 let len = selection.len();
1291 Ok((
1292 Indexed::Select {
1293 child: Box::new(inner),
1294 selection,
1295 },
1296 order_output(*strategy, *truncation, index_fn, len),
1297 ))
1298 }
1299 }
1300 }
1301
1302 fn index_clause(
1303 &mut self,
1304 name: &str,
1305 source: &Source,
1306 prefix: &[(String, Value)],
1307 ) -> Result<(Indexed, Option<IndexFn>), RuntimeError> {
1308 let values = match source {
1309 Source::IntRange { lo, hi, step } => {
1312 let step = (*step).max(1);
1313 let len = if hi <= lo {
1314 0
1315 } else {
1316 ((i128::from(*hi) - i128::from(*lo)) as u128).div_ceil(step as u128) as u64
1317 };
1318 self.record_yield(source, len as usize, Vec::new());
1319 ClauseValues::Range { lo: *lo, step, len }
1320 }
1321 _ => {
1322 let evaluated = self.evaluate_source(name, source, prefix)?;
1323 let index_fn = evaluated.index_fn;
1324 return Ok((
1325 Indexed::Clause {
1326 name: name.to_string(),
1327 values: ClauseValues::List(evaluated.values),
1328 },
1329 Some(index_fn),
1330 ));
1331 }
1332 };
1333 let len = values.len();
1334 Ok((
1335 Indexed::Clause {
1336 name: name.to_string(),
1337 values,
1338 },
1339 Some(IndexFn::Lattice {
1340 axis_sizes: vec![len],
1341 }),
1342 ))
1343 }
1344
1345 fn index_cartesian(
1351 &mut self,
1352 children: &[Comprehension],
1353 prefix: &[(String, Value)],
1354 ) -> Result<(Indexed, Option<IndexFn>), RuntimeError> {
1355 if children.is_empty() || references_an_earlier_axis(children) {
1356 let node = self.evaluate_cartesian(children, prefix)?;
1357 return Ok((Indexed::Tuples(node.tuples), node.index_fn));
1358 }
1359 let base = self.mult;
1360 let mut parts = Vec::with_capacity(children.len());
1361 let mut index_fns = Vec::with_capacity(children.len());
1362 let mut lens = Vec::with_capacity(children.len());
1363 let mut len: u64 = 1;
1364 for child in children {
1365 let evaluated = self.index_node(child, prefix);
1366 let (part, index_fn) = match evaluated {
1367 Ok(done) => done,
1368 Err(e) => {
1369 self.mult = base;
1370 return Err(e);
1371 }
1372 };
1373 let part_len = part.len();
1374 parts.push(part);
1375 index_fns.push(index_fn);
1376 lens.push(part_len);
1377 len = match len.checked_mul(part_len) {
1378 Some(n) => n,
1379 None => {
1380 self.mult = base;
1381 return Err(RuntimeError::UnsupportedShape(format!(
1382 "cartesian of {lens:?} tuples exceeds 2^64"
1383 )));
1384 }
1385 };
1386 if part_len == 0 {
1387 break;
1388 }
1389 self.mult = self
1390 .mult
1391 .saturating_mul(usize::try_from(part_len).unwrap_or(usize::MAX));
1392 }
1393 self.mult = base;
1394 let index_fn = combine_cartesian_index_fn(&index_fns);
1395 if len == 0 {
1396 return Ok((Indexed::Tuples(Vec::new()), index_fn));
1397 }
1398 Ok((
1399 Indexed::Product {
1400 children: parts,
1401 lens,
1402 len,
1403 },
1404 index_fn,
1405 ))
1406 }
1407
1408 fn index_zip(
1409 &mut self,
1410 children: &[Comprehension],
1411 mode: crate::iteration::comprehension::strategy::ZipMode,
1412 prefix: &[(String, Value)],
1413 ) -> Result<(Indexed, Option<IndexFn>), RuntimeError> {
1414 use crate::iteration::comprehension::strategy::ZipMode;
1415 if children.is_empty() {
1416 let node = self.evaluate_zip(children, mode, prefix)?;
1417 return Ok((Indexed::Tuples(node.tuples), node.index_fn));
1418 }
1419 let mut parts = Vec::with_capacity(children.len());
1420 for child in children {
1421 parts.push(self.index_node(child, prefix)?.0);
1422 }
1423 let lengths: Vec<u64> = parts.iter().map(Indexed::len).collect();
1424 let len = match mode {
1425 ZipMode::Strict => {
1426 let first = lengths[0];
1427 if lengths.iter().any(|&n| n != first) {
1428 return Err(RuntimeError::ZipLengthMismatch { lengths });
1429 }
1430 first
1431 }
1432 ZipMode::Truncate => lengths.iter().copied().min().unwrap_or(0),
1433 ZipMode::Cycle => cycle_length(&lengths),
1434 };
1435 Ok(match mode {
1436 ZipMode::Strict | ZipMode::Truncate => (
1437 Indexed::Lockstep {
1438 children: parts,
1439 len,
1440 },
1441 Some(IndexFn::Lockstep { length: len }),
1442 ),
1443 ZipMode::Cycle => (
1444 Indexed::Cycle {
1445 children: parts,
1446 len,
1447 },
1448 Some(IndexFn::Modular {
1449 axis_sizes: lengths,
1450 }),
1451 ),
1452 })
1453 }
1454
1455 fn index_union(
1456 &mut self,
1457 children: &[Comprehension],
1458 prefix: &[(String, Value)],
1459 ) -> Result<(Indexed, Option<IndexFn>), RuntimeError> {
1460 let mut parts = Vec::with_capacity(children.len());
1461 let mut segment_sizes = Vec::with_capacity(children.len());
1462 let mut all_segments_addressable = true;
1463 for child in children {
1464 let (part, index_fn) = self.index_node(child, prefix)?;
1465 all_segments_addressable &= index_fn.is_some();
1466 segment_sizes.push(part.len());
1467 parts.push(part);
1468 }
1469 let len = segment_sizes
1470 .iter()
1471 .try_fold(0u64, |acc, n| acc.checked_add(*n))
1472 .ok_or_else(|| {
1473 RuntimeError::UnsupportedShape(format!(
1474 "union of {segment_sizes:?} tuples exceeds 2^64"
1475 ))
1476 })?;
1477 let index_fn = all_segments_addressable.then_some(IndexFn::Concatenation { segment_sizes });
1478 Ok((
1479 Indexed::Concat {
1480 children: parts,
1481 len,
1482 },
1483 index_fn,
1484 ))
1485 }
1486}
1487
1488fn references_an_earlier_axis(children: &[Comprehension]) -> bool {
1493 let mut bound: std::collections::BTreeSet<String> = std::collections::BTreeSet::new();
1494 for child in children {
1495 if child.source_names_read().iter().any(|n| bound.contains(n)) {
1496 return true;
1497 }
1498 collect_clause_names(child, &mut bound);
1499 }
1500 false
1501}
1502
1503fn collect_clause_names(c: &Comprehension, out: &mut std::collections::BTreeSet<String>) {
1505 match c {
1506 Comprehension::Clause { name, .. } => {
1507 out.insert(name.clone());
1508 }
1509 Comprehension::Cartesian { children }
1510 | Comprehension::Zip { children, .. }
1511 | Comprehension::Union { children } => {
1512 for child in children {
1513 collect_clause_names(child, out);
1514 }
1515 }
1516 Comprehension::Filter { child, .. } | Comprehension::Order { child, .. } => {
1517 collect_clause_names(child, out);
1518 }
1519 }
1520}
1521
1522fn order_selection(
1530 strategy: StrategyName,
1531 index_fn: Option<&IndexFn>,
1532 cardinality: u64,
1533 truncation: Option<u64>,
1534 seed: Option<u64>,
1535) -> Result<Selection, RuntimeError> {
1536 let dispatch = crate::iteration::comprehension::strategies::for_name(strategy);
1537 if !dispatch.accepts_input(index_fn) {
1538 return Err(RuntimeError::StrategyRejectsInput {
1539 strategy,
1540 index_fn: index_fn.cloned(),
1541 });
1542 }
1543 let fallback;
1544 let index_fn = match index_fn {
1545 Some(idx) => idx,
1546 None => {
1547 fallback = IndexFn::Lattice {
1548 axis_sizes: vec![cardinality],
1549 };
1550 &fallback
1551 }
1552 };
1553 Ok(dispatch.select(index_fn, cardinality, truncation, seed))
1554}
1555
1556fn surviving_selection(
1561 strategy: StrategyName,
1562 index_fn: Option<&IndexFn>,
1563 cardinality: u64,
1564 truncation: Option<u64>,
1565 seed: Option<u64>,
1566 survivors: &[u64],
1567) -> Result<Selection, RuntimeError> {
1568 let dispatch = crate::iteration::comprehension::strategies::for_name(strategy);
1569 let Some(index_fn) = index_fn.filter(|idx| dispatch.accepts_input(Some(idx))) else {
1570 return Err(RuntimeError::StrategyRejectsInput {
1571 strategy,
1572 index_fn: index_fn.cloned(),
1573 });
1574 };
1575 Ok(dispatch.select_surviving(index_fn, cardinality, truncation, seed, survivors))
1576}
1577
1578const SAMPLE_ROUNDS: u32 = 6;
1581
1582const UNIT_SCALE: f64 = (1u64 << 53) as f64;
1585
1586enum SampleAxis {
1591 Discrete(Vec<RuntimeTuple>),
1592 Continuous {
1593 name: String,
1594 interval: Interval,
1595 measure: AxisMeasure,
1596 },
1597}
1598
1599#[derive(Default)]
1603struct SampleSpace {
1604 axes: Vec<SampleAxis>,
1605 predicates: Vec<String>,
1606}
1607
1608impl SampleSpace {
1609 fn realize(&self, mi: &[u64], strategy: StrategyName) -> RuntimeTuple {
1617 let extrema = matches!(strategy, StrategyName::Extrema);
1618 let discrete_count = self
1619 .axes
1620 .iter()
1621 .filter(|a| matches!(a, SampleAxis::Discrete(_)))
1622 .count();
1623 let (mut d, mut c) = (0, if extrema { 0 } else { discrete_count });
1624 let mut out = RuntimeTuple::new();
1625 for axis in &self.axes {
1626 match axis {
1627 SampleAxis::Discrete(tuples) => {
1628 let pos = mi.get(d).copied().unwrap_or(0) as usize;
1629 d += 1;
1630 if extrema {
1631 c += 1;
1632 }
1633 if let Some(t) = tuples.get(pos) {
1634 out.extend(t.iter().cloned());
1635 }
1636 }
1637 SampleAxis::Continuous {
1638 name,
1639 interval,
1640 measure,
1641 } => {
1642 let code = mi.get(c).copied().unwrap_or(0);
1643 c += 1;
1644 if extrema {
1645 d += 1;
1646 }
1647 let x = if extrema {
1648 measure.endpoint(interval, code == 1)
1649 } else {
1650 measure.map_unit(code as f64 / UNIT_SCALE, interval)
1651 };
1652 out.push((name.clone(), Value::F64(x)));
1653 }
1654 }
1655 }
1656 out
1657 }
1658}
1659
1660fn draw_sample(
1665 index_fn: &IndexFn,
1666 strategy: StrategyName,
1667 count: Option<u64>,
1668 seed: Option<u64>,
1669) -> Result<Vec<Vec<u64>>, RuntimeError> {
1670 use crate::iteration::comprehension::strategies::{
1671 extrema::extrema_multi_indices, halton::try_halton_multi_indices,
1672 lhs::try_lhs_multi_indices, shuffle::try_shuffle_multi_indices,
1673 sobol::try_sobol_multi_indices,
1674 };
1675 if matches!(strategy, StrategyName::Extrema) {
1676 return Ok(extrema_multi_indices(index_fn, count));
1677 }
1678 let Some(n) = count else {
1679 return Err(RuntimeError::OrderEval {
1680 strategy,
1681 message: "a continuous source has no finite tuple set; give the order a count, \
1682 as in `order halton/16`"
1683 .into(),
1684 });
1685 };
1686 let drawn = match strategy {
1690 StrategyName::Halton => try_halton_multi_indices(index_fn, Some(n)),
1691 StrategyName::Sobol => try_sobol_multi_indices(index_fn, Some(n)),
1692 StrategyName::Lhs => try_lhs_multi_indices(index_fn, Some(n), seed),
1693 StrategyName::Shuffle => try_shuffle_multi_indices(index_fn, Some(n), seed),
1694 other => {
1695 return Err(RuntimeError::OrderEval {
1696 strategy: other,
1697 message: "a continuous source needs a sampling strategy: halton, sobol, lhs, \
1698 shuffle, or extrema"
1699 .into(),
1700 });
1701 }
1702 };
1703 drawn.map_err(|message| RuntimeError::OrderEval { strategy, message })
1704}
1705
1706pub(crate) fn has_continuous_axis(c: &Comprehension) -> bool {
1711 match c {
1712 Comprehension::Clause { source, .. } => matches!(
1713 source,
1714 Source::ContinuousInterval { .. } | Source::Distribution { .. }
1715 ),
1716 Comprehension::Cartesian { children } => children.iter().any(has_continuous_axis),
1717 Comprehension::Filter { child, .. } => has_continuous_axis(child),
1718 Comprehension::Zip { .. } | Comprehension::Union { .. } | Comprehension::Order { .. } => {
1719 false
1720 }
1721 }
1722}
1723
1724fn combine_cartesian_index_fn(children: &[Option<IndexFn>]) -> Option<IndexFn> {
1725 let mut axis_sizes = Vec::new();
1726 for opt in children {
1727 match opt {
1728 Some(IndexFn::Lattice { axis_sizes: a }) => axis_sizes.extend(a.iter().copied()),
1729 Some(IndexFn::Lockstep { length }) => axis_sizes.push(*length),
1730 _ => return None,
1733 }
1734 }
1735 Some(IndexFn::Lattice { axis_sizes })
1736}
1737
1738#[cfg(test)]
1739mod tests {
1740 use super::*;
1741 use crate::iteration::comprehension::source::LiteralValue;
1742
1743 fn empty_kernel() -> Arc<PolydatKernel> {
1744 Arc::new(crate::dsl::compile_polydat_interpreter("\n").unwrap())
1745 }
1746
1747 fn canonical_with_k() -> Arc<PolydatKernel> {
1752 Arc::new(crate::dsl::compile_polydat_interpreter("extern k: u64\n").unwrap())
1753 }
1754
1755 fn clause(name: &str, source: Source) -> Comprehension {
1756 Comprehension::Clause {
1757 name: name.into(),
1758 source,
1759 }
1760 }
1761
1762 fn empty_literal() -> Source {
1763 Source::Literal { values: Vec::new() }
1764 }
1765
1766 #[test]
1768 fn every_leaf_reports_what_it_yielded() {
1769 let comp = Comprehension::Cartesian {
1770 children: vec![
1771 clause(
1772 "a",
1773 Source::IntRange {
1774 lo: 0,
1775 hi: 3,
1776 step: 1,
1777 },
1778 ),
1779 clause(
1780 "b",
1781 Source::Literal {
1782 values: vec![LiteralValue::Int(7), LiteralValue::Int(8)],
1783 },
1784 ),
1785 ],
1786 };
1787 let scope = empty_kernel();
1788
1789 let out = evaluate_for_iteration_reported(&comp, &*scope).unwrap();
1790 assert_eq!(out.tuples.len(), 6, "3 x 2");
1791 assert_eq!(out.clauses.len(), 2, "one entry per leaf, in tree order");
1792 assert_eq!(out.clauses[0].var, "a");
1793 assert_eq!(out.clauses[0].values, 3);
1794 assert_eq!(out.clauses[1].var, "b");
1795 assert_eq!(out.clauses[1].evaluations, 3);
1798 assert_eq!(out.clauses[1].values, 6);
1799 }
1800
1801 #[test]
1804 fn an_empty_clause_is_reached_and_yields_nothing() {
1805 let comp = Comprehension::Cartesian {
1806 children: vec![
1807 clause(
1808 "a",
1809 Source::IntRange {
1810 lo: 0,
1811 hi: 2,
1812 step: 1,
1813 },
1814 ),
1815 clause("b", empty_literal()),
1816 ],
1817 };
1818 let scope = empty_kernel();
1819
1820 let out = evaluate_for_iteration_reported(&comp, &*scope).unwrap();
1821 assert!(out.tuples.is_empty(), "an empty clause empties the product");
1822 let culprits: Vec<&str> = out
1823 .clauses
1824 .iter()
1825 .filter(|c| c.evaluations > 0 && c.values == 0)
1826 .map(|c| c.var.as_str())
1827 .collect();
1828 assert_eq!(culprits, ["b"], "only the empty clause is named");
1829 }
1830
1831 #[test]
1834 fn a_clause_behind_an_empty_one_is_never_reached() {
1835 let comp = Comprehension::Cartesian {
1836 children: vec![
1837 clause("outer", empty_literal()),
1838 clause(
1839 "inner",
1840 Source::IntRange {
1841 lo: 0,
1842 hi: 9,
1843 step: 1,
1844 },
1845 ),
1846 ],
1847 };
1848 let scope = empty_kernel();
1849
1850 let out = evaluate_for_iteration_reported(&comp, &*scope).unwrap();
1851 assert!(out.tuples.is_empty());
1852 let by = |v: &str| {
1853 out.clauses
1854 .iter()
1855 .find(|c| c.var == v)
1856 .expect("every leaf is present whether reached or not")
1857 };
1858 assert_eq!(by("outer").evaluations, 1);
1859 assert_eq!(by("outer").values, 0);
1860 assert_eq!(
1861 by("inner").evaluations,
1862 0,
1863 "never reached: the cause is `outer`, not this"
1864 );
1865 assert_eq!(by("inner").values, 0);
1866 }
1867
1868 #[test]
1871 fn clauses_sharing_a_name_across_a_union_are_counted_apart() {
1872 let comp = Comprehension::Union {
1873 children: vec![
1874 clause(
1875 "k",
1876 Source::Literal {
1877 values: vec![LiteralValue::Int(1)],
1878 },
1879 ),
1880 clause("k", empty_literal()),
1881 ],
1882 };
1883 let scope = empty_kernel();
1884
1885 let out = evaluate_for_iteration_reported(&comp, &*scope).unwrap();
1886 assert_eq!(out.clauses.len(), 2, "two leaves, one name");
1887 assert_eq!(out.clauses[0].values, 1);
1888 assert_eq!(out.clauses[1].values, 0);
1889 assert_eq!(out.clauses[1].evaluations, 1, "reached, and empty");
1890 }
1891
1892 #[test]
1895 fn the_plain_entry_point_agrees_with_the_reported_one() {
1896 let comp = Comprehension::Cartesian {
1897 children: vec![
1898 clause(
1899 "a",
1900 Source::IntRange {
1901 lo: 1,
1902 hi: 4,
1903 step: 1,
1904 },
1905 ),
1906 clause(
1907 "b",
1908 Source::Literal {
1909 values: vec![LiteralValue::Int(5)],
1910 },
1911 ),
1912 ],
1913 };
1914 let scope = empty_kernel();
1915
1916 let plain = evaluate_for_iteration(&comp, &*scope).unwrap();
1917 let reported = evaluate_for_iteration_reported(&comp, &*scope).unwrap();
1918 assert_eq!(plain, reported.tuples);
1919 }
1920
1921 #[test]
1922 fn int_range_yields_values() {
1923 let comp = Comprehension::Clause {
1924 name: "k".into(),
1925 source: Source::IntRange {
1926 lo: 1,
1927 hi: 5,
1928 step: 1,
1929 },
1930 };
1931 let canonical = empty_kernel();
1932
1933 let tuples = evaluate_for_iteration(&comp, &*canonical).unwrap();
1934 assert_eq!(tuples.len(), 4);
1935 assert_eq!(tuples[0][0].1, Value::U64(1));
1936 assert_eq!(tuples[3][0].1, Value::U64(4));
1937 }
1938
1939 #[test]
1940 fn literal_list_yields_values() {
1941 let comp = Comprehension::Clause {
1942 name: "x".into(),
1943 source: Source::Literal {
1944 values: vec![LiteralValue::Int(10), LiteralValue::Int(20)],
1945 },
1946 };
1947 let canonical = empty_kernel();
1948
1949 let tuples = evaluate_for_iteration(&comp, &*canonical).unwrap();
1950 assert_eq!(tuples.len(), 2);
1951 }
1952
1953 #[test]
1954 fn cartesian_produces_product() {
1955 let comp = Comprehension::cartesian(vec![
1956 Comprehension::Clause {
1957 name: "x".into(),
1958 source: Source::IntRange {
1959 lo: 1,
1960 hi: 3,
1961 step: 1,
1962 },
1963 },
1964 Comprehension::Clause {
1965 name: "y".into(),
1966 source: Source::IntRange {
1967 lo: 10,
1968 hi: 30,
1969 step: 10,
1970 },
1971 },
1972 ]);
1973 let canonical = empty_kernel();
1974
1975 let tuples = evaluate_for_iteration(&comp, &*canonical).unwrap();
1976 assert_eq!(tuples.len(), 4);
1978 }
1979
1980 #[test]
1981 fn union_produces_concatenation() {
1982 let comp = Comprehension::union(vec![
1983 Comprehension::Clause {
1984 name: "k".into(),
1985 source: Source::Literal {
1986 values: vec![LiteralValue::Int(1)],
1987 },
1988 },
1989 Comprehension::Clause {
1990 name: "k".into(),
1991 source: Source::Literal {
1992 values: vec![LiteralValue::Int(10), LiteralValue::Int(20)],
1993 },
1994 },
1995 ]);
1996 let canonical = empty_kernel();
1997
1998 let tuples = evaluate_for_iteration(&comp, &*canonical).unwrap();
1999 assert_eq!(tuples.len(), 3);
2000 }
2001
2002 #[test]
2003 fn filter_drops_non_matching() {
2004 let comp = Comprehension::filter(
2005 Comprehension::Clause {
2006 name: "k".into(),
2007 source: Source::IntRange {
2008 lo: 1,
2009 hi: 6,
2010 step: 1,
2011 },
2012 },
2013 "{k} > 3",
2014 );
2015 let canonical = canonical_with_k();
2016
2017 let tuples = evaluate_for_iteration(&comp, &*canonical).unwrap();
2018 assert_eq!(tuples.len(), 2);
2020 }
2021
2022 #[test]
2023 fn order_lex_truncate() {
2024 let comp = Comprehension::order(
2025 Comprehension::Clause {
2026 name: "k".into(),
2027 source: Source::IntRange {
2028 lo: 1,
2029 hi: 100,
2030 step: 1,
2031 },
2032 },
2033 StrategyName::Lex,
2034 Some(5),
2035 );
2036 let canonical = empty_kernel();
2037
2038 let tuples = evaluate_for_iteration(&comp, &*canonical).unwrap();
2039 assert_eq!(tuples.len(), 5);
2040 }
2041
2042 #[test]
2046 fn a_multi_name_head_is_one_lattice_axis() {
2047 use crate::iteration::comprehension::strategy::ZipMode;
2048 let lit = |name: &str, vs: &[i64]| Comprehension::Clause {
2049 name: name.into(),
2050 source: Source::Literal {
2051 values: vs.iter().map(|v| LiteralValue::Int(*v)).collect(),
2052 },
2053 };
2054 let comp = Comprehension::order(
2055 Comprehension::cartesian(vec![
2056 Comprehension::zip(vec![lit("a", &[1, 2]), lit("b", &[3, 4])], ZipMode::Strict),
2057 lit("c", &[5, 6, 7]),
2058 ]),
2059 StrategyName::Halton,
2060 Some(6),
2061 );
2062 let tuples = evaluate_for_iteration(&comp, &*empty_kernel()).unwrap();
2063 assert_eq!(
2064 tuples.len(),
2065 6,
2066 "every tuple of the 2 x 3 product: {tuples:?}"
2067 );
2068 }
2069
2070 #[test]
2076 fn extrema_over_cartesian_uses_indexed_form() {
2077 let comp = Comprehension::order(
2078 Comprehension::cartesian(vec![
2079 Comprehension::Clause {
2080 name: "k".into(),
2081 source: Source::Literal {
2082 values: vec![
2083 LiteralValue::Int(1),
2084 LiteralValue::Int(2),
2085 LiteralValue::Int(3),
2086 ],
2087 },
2088 },
2089 Comprehension::Clause {
2090 name: "limit".into(),
2091 source: Source::Literal {
2092 values: vec![
2093 LiteralValue::Int(10),
2094 LiteralValue::Int(20),
2095 LiteralValue::Int(30),
2096 ],
2097 },
2098 },
2099 ]),
2100 StrategyName::Extrema,
2101 Some(1),
2105 );
2106 let canonical = empty_kernel();
2107
2108 let tuples = evaluate_for_iteration(&comp, &*canonical).unwrap();
2109 assert_eq!(tuples.len(), 4);
2111 for t in &tuples {
2113 assert_eq!(t.len(), 2);
2114 let k = match &t[0].1 {
2115 Value::U64(n) => *n,
2116 other => panic!("expected u64 k, got {other:?}"),
2117 };
2118 let lim = match &t[1].1 {
2119 Value::U64(n) => *n,
2120 other => panic!("expected u64 limit, got {other:?}"),
2121 };
2122 assert!(k == 1 || k == 3, "expected extreme k, got {k}");
2123 assert!(lim == 10 || lim == 30, "expected extreme limit, got {lim}");
2124 }
2125 }
2126}