use serde::{Deserialize, Serialize};
use super::ast::Comprehension;
use super::cardinality::{CardinalityClass, Hybrid, Interval, ProductMeasure};
use super::source::Source;
use super::strategy::{StrategyName, ZipMode};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Metadata {
pub cardinality: CardinalityClass,
pub index_addressable: Option<IndexFn>,
pub natural_order: NaturalOrder,
pub materialization: Materialization,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum IndexFn {
Lattice { axis_sizes: Vec<u64> },
Lockstep { length: u64 },
Modular { axis_sizes: Vec<u64> },
Concatenation { segment_sizes: Vec<u64> },
Continuous {
intervals: Vec<Interval>,
measure: ProductMeasure,
},
Hybrid {
discrete_axes: Vec<u64>,
continuous_axes: Vec<Interval>,
measure: ProductMeasure,
},
}
impl IndexFn {
pub fn has_continuous_axis(&self) -> bool {
matches!(self, IndexFn::Continuous { .. } | IndexFn::Hybrid { .. })
}
pub fn is_multi_axis_lattice(&self) -> bool {
matches!(self, IndexFn::Lattice { axis_sizes } if axis_sizes.len() >= 2)
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum NaturalOrder {
Lex,
Lockstep,
Sequential,
Strategy(StrategyName),
PendingSampling,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum Materialization {
Streaming,
BoundedBarrier { working_set_size: u64 },
UnboundedBarrier,
}
impl Comprehension {
pub fn metadata(&self) -> Metadata {
match self {
Comprehension::Clause { source, .. } => clause_metadata(source),
Comprehension::Cartesian { children } => cartesian_metadata(children),
Comprehension::Zip { children, mode } => zip_metadata(children, *mode),
Comprehension::Union { children } => union_metadata(children),
Comprehension::Filter { child, .. } => filter_metadata(child),
Comprehension::Order { child, strategy, truncation } => {
order_metadata(child, *strategy, *truncation)
}
}
}
}
fn clause_metadata(source: &Source) -> Metadata {
let cardinality = source.cardinality();
let (index_addressable, natural_order) = match &cardinality {
CardinalityClass::Bounded(n) => (
Some(IndexFn::Lattice { axis_sizes: vec![*n] }),
NaturalOrder::Lex,
),
CardinalityClass::Continuous { intervals, measure } => (
Some(IndexFn::Continuous {
intervals: intervals.clone(),
measure: measure.clone(),
}),
NaturalOrder::PendingSampling,
),
_ => (None, NaturalOrder::Lex),
};
Metadata {
cardinality,
index_addressable,
natural_order,
materialization: Materialization::Streaming,
}
}
fn cartesian_metadata(children: &[Comprehension]) -> Metadata {
let dependent = detect_dependent_sources(children);
let child_meta: Vec<Metadata> = children.iter().map(|c| c.metadata()).collect();
let cardinality = combine_cartesian_cardinality(&child_meta);
let index_addressable = if dependent {
None
} else {
combine_cartesian_index_fn(&child_meta)
};
let natural_order = if matches!(cardinality, CardinalityClass::Continuous { .. } | CardinalityClass::Hybrid(_)) {
NaturalOrder::PendingSampling
} else {
NaturalOrder::Lex
};
Metadata {
cardinality,
index_addressable,
natural_order,
materialization: Materialization::Streaming,
}
}
fn zip_metadata(children: &[Comprehension], mode: ZipMode) -> Metadata {
let child_meta: Vec<Metadata> = children.iter().map(|c| c.metadata()).collect();
let cardinality = combine_zip_cardinality(&child_meta, mode);
let index_addressable = combine_zip_index_fn(&child_meta, mode);
let materialization = match mode {
ZipMode::Strict | ZipMode::Truncate => Materialization::Streaming,
ZipMode::Cycle => {
let cards: Vec<u64> = child_meta
.iter()
.filter_map(|m| match &m.cardinality {
CardinalityClass::Bounded(n) | CardinalityClass::BoundedAtMost(n) => Some(*n),
_ => None,
})
.collect();
if cards.is_empty() {
Materialization::Streaming
} else {
let max = cards.iter().copied().max().unwrap_or(0);
let sum_non_longest: u64 = cards.iter().filter(|n| **n != max).sum();
Materialization::BoundedBarrier {
working_set_size: sum_non_longest,
}
}
}
};
Metadata {
cardinality,
index_addressable,
natural_order: NaturalOrder::Lockstep,
materialization,
}
}
fn union_metadata(children: &[Comprehension]) -> Metadata {
let child_meta: Vec<Metadata> = children.iter().map(|c| c.metadata()).collect();
let cardinality = combine_union_cardinality(&child_meta);
let index_addressable = combine_union_index_fn(&child_meta);
Metadata {
cardinality,
index_addressable,
natural_order: NaturalOrder::Sequential,
materialization: Materialization::Streaming,
}
}
fn filter_metadata(child: &Comprehension) -> Metadata {
let child_meta = child.metadata();
let cardinality = match &child_meta.cardinality {
CardinalityClass::Bounded(n) | CardinalityClass::BoundedAtMost(n) => {
CardinalityClass::BoundedAtMost(*n)
}
CardinalityClass::Unbounded => CardinalityClass::Unbounded,
CardinalityClass::Continuous { intervals, measure }
| CardinalityClass::ContinuousAtMost {
intervals,
measure_at_most: measure,
} => CardinalityClass::ContinuousAtMost {
intervals: intervals.clone(),
measure_at_most: measure.clone(),
},
CardinalityClass::Hybrid(h) => CardinalityClass::Hybrid(h.clone()),
};
Metadata {
cardinality,
index_addressable: None, natural_order: child_meta.natural_order,
materialization: child_meta.materialization,
}
}
fn order_metadata(
child: &Comprehension,
strategy: StrategyName,
truncation: Option<u64>,
) -> Metadata {
let child_meta = child.metadata();
let cardinality = match (&child_meta.cardinality, truncation) {
(CardinalityClass::Continuous { .. }, Some(n))
| (CardinalityClass::ContinuousAtMost { .. }, Some(n))
| (CardinalityClass::Hybrid(_), Some(n))
if !matches!(strategy, StrategyName::Lex) =>
{
CardinalityClass::Bounded(n)
}
(CardinalityClass::Bounded(child_n), Some(n)) => {
CardinalityClass::Bounded((*child_n).min(n))
}
(CardinalityClass::BoundedAtMost(child_n), Some(n)) => {
CardinalityClass::BoundedAtMost((*child_n).min(n))
}
(_, Some(n)) => CardinalityClass::Bounded(n), (c, None) => c.clone(),
};
let (index_addressable, natural_order, materialization) = match strategy {
StrategyName::Lex => (
child_meta.index_addressable, NaturalOrder::Lex,
child_meta.materialization, ),
non_lex => {
let working_set_size = strategy_working_set(
non_lex,
&child_meta.index_addressable,
truncation,
);
(
None,
NaturalOrder::Strategy(non_lex),
Materialization::BoundedBarrier { working_set_size },
)
}
};
Metadata {
cardinality,
index_addressable,
natural_order,
materialization,
}
}
fn combine_cartesian_cardinality(children: &[Metadata]) -> CardinalityClass {
let mut has_continuous = false;
let mut has_discrete = false;
let mut has_unbounded = false;
let mut product: u64 = 1;
let mut overflow = false;
let mut discrete_axes: Vec<u64> = Vec::new();
let mut continuous_intervals: Vec<Interval> = Vec::new();
let mut continuous_measures: Vec<ProductMeasure> = Vec::new();
for m in children {
match &m.cardinality {
CardinalityClass::Bounded(n) => {
has_discrete = true;
discrete_axes.push(*n);
product = product.checked_mul(*n).unwrap_or_else(|| {
overflow = true;
u64::MAX
});
}
CardinalityClass::BoundedAtMost(n) => {
has_discrete = true;
discrete_axes.push(*n); product = product.checked_mul(*n).unwrap_or_else(|| {
overflow = true;
u64::MAX
});
}
CardinalityClass::Unbounded => {
has_unbounded = true;
has_discrete = true;
discrete_axes.push(0);
}
CardinalityClass::Continuous { intervals, measure }
| CardinalityClass::ContinuousAtMost { intervals, measure_at_most: measure } => {
has_continuous = true;
continuous_intervals.extend(intervals.iter().cloned());
continuous_measures.push(measure.clone());
}
CardinalityClass::Hybrid(h) => {
has_continuous = true;
has_discrete = true;
discrete_axes.extend(h.discrete_axes.iter().copied());
continuous_intervals.extend(h.continuous_axes.iter().cloned());
continuous_measures.push(h.measure.clone());
}
}
}
let _ = overflow;
if has_continuous && has_discrete {
CardinalityClass::Hybrid(Hybrid {
discrete_axes,
continuous_axes: continuous_intervals,
measure: simplify_measures(continuous_measures),
})
} else if has_continuous {
CardinalityClass::Continuous {
intervals: continuous_intervals,
measure: simplify_measures(continuous_measures),
}
} else if has_unbounded {
CardinalityClass::Unbounded
} else {
CardinalityClass::Bounded(product)
}
}
fn combine_cartesian_index_fn(children: &[Metadata]) -> Option<IndexFn> {
let all_addressable = children.iter().all(|m| m.index_addressable.is_some());
if !all_addressable {
return None;
}
let mut all_discrete = true;
let mut all_continuous = true;
let mut discrete_axes: Vec<u64> = Vec::new();
let mut continuous_intervals: Vec<Interval> = Vec::new();
let mut continuous_measures: Vec<ProductMeasure> = Vec::new();
for m in children {
match m.index_addressable.as_ref().unwrap() {
IndexFn::Lattice { axis_sizes } => {
all_continuous = false;
discrete_axes.extend(axis_sizes.iter().copied());
}
IndexFn::Continuous { intervals, measure } => {
all_discrete = false;
continuous_intervals.extend(intervals.iter().cloned());
continuous_measures.push(measure.clone());
}
IndexFn::Hybrid {
discrete_axes: d,
continuous_axes: c,
measure,
} => {
all_discrete = false;
all_continuous = false;
discrete_axes.extend(d.iter().copied());
continuous_intervals.extend(c.iter().cloned());
continuous_measures.push(measure.clone());
}
IndexFn::Lockstep { .. } | IndexFn::Modular { .. } | IndexFn::Concatenation { .. } => {
return None;
}
}
}
if all_discrete {
Some(IndexFn::Lattice { axis_sizes: discrete_axes })
} else if all_continuous {
Some(IndexFn::Continuous {
intervals: continuous_intervals,
measure: simplify_measures(continuous_measures),
})
} else {
Some(IndexFn::Hybrid {
discrete_axes,
continuous_axes: continuous_intervals,
measure: simplify_measures(continuous_measures),
})
}
}
fn combine_zip_cardinality(children: &[Metadata], mode: ZipMode) -> CardinalityClass {
let counts: Vec<Option<u64>> = children
.iter()
.map(|m| match &m.cardinality {
CardinalityClass::Bounded(n) | CardinalityClass::BoundedAtMost(n) => Some(*n),
CardinalityClass::Unbounded => None,
_ => None,
})
.collect();
match mode {
ZipMode::Strict => {
counts
.iter()
.find_map(|c| *c)
.map(CardinalityClass::Bounded)
.unwrap_or(CardinalityClass::Unbounded)
}
ZipMode::Truncate => {
let bounded: Vec<u64> = counts.iter().filter_map(|c| *c).collect();
if bounded.is_empty() {
CardinalityClass::Unbounded
} else {
CardinalityClass::Bounded(*bounded.iter().min().unwrap())
}
}
ZipMode::Cycle => {
let bounded: Vec<u64> = counts.iter().filter_map(|c| *c).collect();
if counts.iter().any(Option::is_none) {
CardinalityClass::Unbounded
} else if let Some(max) = bounded.iter().max() {
CardinalityClass::Bounded(*max)
} else {
CardinalityClass::Bounded(0)
}
}
}
}
fn combine_zip_index_fn(children: &[Metadata], mode: ZipMode) -> Option<IndexFn> {
let all_addressable = children.iter().all(|m| m.index_addressable.is_some());
if !all_addressable {
return None;
}
let counts: Vec<u64> = children
.iter()
.filter_map(|m| match &m.cardinality {
CardinalityClass::Bounded(n) | CardinalityClass::BoundedAtMost(n) => Some(*n),
_ => None,
})
.collect();
if counts.len() != children.len() {
return None;
}
match mode {
ZipMode::Strict | ZipMode::Truncate => {
let length = match mode {
ZipMode::Strict => counts[0],
ZipMode::Truncate => *counts.iter().min().unwrap(),
ZipMode::Cycle => unreachable!(),
};
Some(IndexFn::Lockstep { length })
}
ZipMode::Cycle => Some(IndexFn::Modular { axis_sizes: counts }),
}
}
fn combine_union_cardinality(children: &[Metadata]) -> CardinalityClass {
let mut sum: u64 = 0;
let mut any_unbounded = false;
let mut any_atmost = false;
for m in children {
match &m.cardinality {
CardinalityClass::Bounded(n) => {
sum = sum.saturating_add(*n);
}
CardinalityClass::BoundedAtMost(n) => {
sum = sum.saturating_add(*n);
any_atmost = true;
}
CardinalityClass::Unbounded => {
any_unbounded = true;
}
_ => any_unbounded = true,
}
}
if any_unbounded {
CardinalityClass::Unbounded
} else if any_atmost {
CardinalityClass::BoundedAtMost(sum)
} else {
CardinalityClass::Bounded(sum)
}
}
fn combine_union_index_fn(children: &[Metadata]) -> Option<IndexFn> {
let all_addressable = children.iter().all(|m| m.index_addressable.is_some());
if !all_addressable {
return None;
}
let segment_sizes: Vec<u64> = children
.iter()
.filter_map(|m| match &m.cardinality {
CardinalityClass::Bounded(n) | CardinalityClass::BoundedAtMost(n) => Some(*n),
_ => None,
})
.collect();
if segment_sizes.len() != children.len() {
return None;
}
Some(IndexFn::Concatenation { segment_sizes })
}
fn simplify_measures(measures: Vec<ProductMeasure>) -> ProductMeasure {
match measures.len() {
0 => ProductMeasure::Uniform,
1 => measures.into_iter().next().unwrap(),
_ => ProductMeasure::Product(measures),
}
}
fn strategy_working_set(
strategy: StrategyName,
input: &Option<IndexFn>,
truncation: Option<u64>,
) -> u64 {
match (strategy, input, truncation) {
(StrategyName::Halton, Some(_), Some(n))
| (StrategyName::Sobol, Some(_), Some(n))
| (StrategyName::Shuffle, Some(_), Some(n))
| (StrategyName::ReverseLex, Some(_), Some(n)) => n,
(StrategyName::Lhs, Some(idx), Some(n)) => {
let dim = lattice_dim(idx).max(1);
n.saturating_mul(dim as u64)
}
(StrategyName::Extrema, Some(idx), Some(_k)) => index_fn_cardinality(idx),
(StrategyName::Shells, Some(_), Some(n))
| (StrategyName::Diagonal, Some(_), Some(n))
| (StrategyName::Antidiagonal, Some(_), Some(n)) => n,
(_, Some(idx), None) => index_fn_cardinality(idx),
(_, None, Some(n)) => n,
(_, None, None) => 0,
(StrategyName::Lex, Some(_), Some(n)) => n,
}
}
fn lattice_dim(idx: &IndexFn) -> usize {
match idx {
IndexFn::Lattice { axis_sizes } => axis_sizes.len(),
IndexFn::Continuous { intervals, .. } => intervals.len(),
IndexFn::Hybrid {
discrete_axes,
continuous_axes,
..
} => discrete_axes.len() + continuous_axes.len(),
IndexFn::Lockstep { .. } | IndexFn::Modular { .. } => 1,
IndexFn::Concatenation { segment_sizes } => segment_sizes.len(),
}
}
fn index_fn_cardinality(idx: &IndexFn) -> u64 {
match idx {
IndexFn::Lattice { axis_sizes } => {
axis_sizes.iter().copied().fold(1u64, |a, b| a.saturating_mul(b))
}
IndexFn::Lockstep { length } => *length,
IndexFn::Modular { axis_sizes } => {
axis_sizes.iter().copied().max().unwrap_or(0)
}
IndexFn::Concatenation { segment_sizes } => {
segment_sizes.iter().copied().fold(0u64, |a, b| a.saturating_add(b))
}
IndexFn::Continuous { .. } | IndexFn::Hybrid { .. } => 0,
}
}
fn detect_dependent_sources(children: &[Comprehension]) -> bool {
let mut prior_names: Vec<String> = Vec::new();
for child in children {
for name in collect_source_name_references(child) {
if prior_names.contains(&name) {
return true;
}
}
for n in child.coordinate_names() {
if !prior_names.contains(&n) {
prior_names.push(n);
}
}
}
false
}
fn collect_source_name_references(c: &Comprehension) -> Vec<String> {
let mut out = Vec::new();
walk_source_refs(c, &mut out);
out
}
fn walk_source_refs(c: &Comprehension, out: &mut Vec<String>) {
match c {
Comprehension::Clause { source, .. } => {
extract_source_refs(source, out);
}
Comprehension::Cartesian { children } | Comprehension::Zip { children, .. } | Comprehension::Union { children } => {
for c in children {
walk_source_refs(c, out);
}
}
Comprehension::Filter { child, .. } | Comprehension::Order { child, .. } => {
walk_source_refs(child, out);
}
}
}
fn extract_source_refs(source: &Source, out: &mut Vec<String>) {
let s = match source {
Source::Generator { expr, .. } => expr.as_str(),
Source::WorkloadParamList { name, .. } => name.as_str(),
_ => return,
};
let bytes = s.as_bytes();
let mut i = 0;
while i < bytes.len() {
if bytes[i] == b'{'
&& let Some(close) = s[i + 1..].find('}') {
let name = s[i + 1..i + 1 + close].trim();
if !name.is_empty()
&& name.chars().all(|c| c.is_alphanumeric() || c == '_')
&& !out.contains(&name.to_string())
{
out.push(name.to_string());
}
i += close + 2;
continue;
}
i += 1;
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::iteration::comprehension::source::{LiteralValue, Source};
fn clause(name: &str, vs: &[i64]) -> Comprehension {
Comprehension::clause(
name,
Source::Literal {
values: vs.iter().map(|n| LiteralValue::Int(*n)).collect(),
},
)
}
fn continuous_clause(name: &str) -> Comprehension {
Comprehension::clause(
name,
Source::ContinuousInterval {
interval: Interval::closed(0.0, 1.0),
measure: ProductMeasure::Uniform,
},
)
}
#[test]
fn clause_metadata_for_bounded_source() {
let m = clause("k", &[1, 2, 3]).metadata();
assert_eq!(m.cardinality, CardinalityClass::Bounded(3));
assert_eq!(
m.index_addressable,
Some(IndexFn::Lattice { axis_sizes: vec![3] })
);
assert_eq!(m.natural_order, NaturalOrder::Lex);
assert_eq!(m.materialization, Materialization::Streaming);
}
#[test]
fn clause_metadata_for_continuous_source() {
let m = continuous_clause("alpha").metadata();
assert!(matches!(m.cardinality, CardinalityClass::Continuous { .. }));
assert!(matches!(m.index_addressable, Some(IndexFn::Continuous { .. })));
assert_eq!(m.natural_order, NaturalOrder::PendingSampling);
assert_eq!(m.materialization, Materialization::Streaming);
}
#[test]
fn cartesian_metadata_combines_lattice_axes() {
let c = Comprehension::cartesian(vec![clause("k", &[1, 2]), clause("limit", &[10, 20, 30])]);
let m = c.metadata();
assert_eq!(m.cardinality, CardinalityClass::Bounded(6));
assert_eq!(
m.index_addressable,
Some(IndexFn::Lattice { axis_sizes: vec![2, 3] })
);
assert_eq!(m.natural_order, NaturalOrder::Lex);
}
#[test]
fn cartesian_metadata_for_hybrid() {
let c = Comprehension::cartesian(vec![clause("k", &[1, 2, 3, 4]), continuous_clause("theta")]);
let m = c.metadata();
match m.cardinality {
CardinalityClass::Hybrid(h) => {
assert_eq!(h.discrete_axes, vec![4]);
assert_eq!(h.continuous_axes.len(), 1);
}
other => panic!("expected Hybrid, got {other:?}"),
}
assert!(matches!(m.index_addressable, Some(IndexFn::Hybrid { .. })));
assert_eq!(m.natural_order, NaturalOrder::PendingSampling);
}
#[test]
fn dependent_cartesian_produces_none_addressable() {
let dependent = Comprehension::cartesian(vec![
clause("k", &[1, 2, 3]),
Comprehension::clause(
"replicas",
Source::Generator {
expr: "range(0, 2 * {k})".into(),
cardinality_hint: Some(6),
},
),
]);
let m = dependent.metadata();
assert!(m.index_addressable.is_none());
}
#[test]
fn zip_strict_produces_lockstep_index_fn() {
let c = Comprehension::zip(
vec![clause("x", &[1, 2, 3]), clause("y", &[10, 20, 30])],
ZipMode::Strict,
);
let m = c.metadata();
assert_eq!(m.index_addressable, Some(IndexFn::Lockstep { length: 3 }));
assert_eq!(m.natural_order, NaturalOrder::Lockstep);
assert_eq!(m.materialization, Materialization::Streaming);
}
#[test]
fn zip_cycle_produces_modular_index_fn_and_barrier() {
let c = Comprehension::zip(
vec![clause("k", &[1, 2, 3, 4, 5]), clause("color", &[1, 2, 3])],
ZipMode::Cycle,
);
let m = c.metadata();
match m.index_addressable {
Some(IndexFn::Modular { axis_sizes }) => {
assert_eq!(axis_sizes, vec![5, 3]);
}
other => panic!("expected Modular, got {other:?}"),
}
assert_eq!(
m.materialization,
Materialization::BoundedBarrier { working_set_size: 3 }
);
}
#[test]
fn union_produces_concatenation_index_fn() {
let a = Comprehension::cartesian(vec![clause("k", &[1, 2]), clause("limit", &[10])]);
let b = Comprehension::cartesian(vec![clause("k", &[3, 4]), clause("limit", &[20])]);
let u = Comprehension::union(vec![a, b]);
let m = u.metadata();
assert_eq!(m.cardinality, CardinalityClass::Bounded(4));
assert_eq!(
m.index_addressable,
Some(IndexFn::Concatenation { segment_sizes: vec![2, 2] })
);
assert_eq!(m.natural_order, NaturalOrder::Sequential);
}
#[test]
fn filter_destroys_addressability() {
let inner = Comprehension::cartesian(vec![clause("k", &[1, 2]), clause("limit", &[10, 20])]);
let filtered = Comprehension::filter(inner, "{k} > 0");
let m = filtered.metadata();
assert_eq!(m.cardinality, CardinalityClass::BoundedAtMost(4));
assert_eq!(m.index_addressable, None);
}
#[test]
fn lex_order_inherits_addressability() {
let inner = Comprehension::cartesian(vec![clause("k", &[1, 2]), clause("limit", &[10, 20])]);
let ordered = Comprehension::order(inner, StrategyName::Lex, Some(2));
let m = ordered.metadata();
assert_eq!(m.cardinality, CardinalityClass::Bounded(2));
assert!(matches!(m.index_addressable, Some(IndexFn::Lattice { .. })));
assert_eq!(m.natural_order, NaturalOrder::Lex);
}
#[test]
fn non_lex_order_drops_ast_level_addressability() {
let inner = Comprehension::cartesian(vec![clause("k", &[1, 2]), clause("limit", &[10, 20])]);
let ordered = Comprehension::order(inner, StrategyName::Halton, Some(2));
let m = ordered.metadata();
assert!(m.index_addressable.is_none());
match m.natural_order {
NaturalOrder::Strategy(StrategyName::Halton) => {}
other => panic!("expected Strategy(Halton), got {other:?}"),
}
assert_eq!(
m.materialization,
Materialization::BoundedBarrier { working_set_size: 2 }
);
}
#[test]
fn continuous_sampling_yields_bounded_cardinality() {
let inner = Comprehension::cartesian(vec![continuous_clause("alpha"), continuous_clause("beta")]);
let ordered = Comprehension::order(inner, StrategyName::Halton, Some(100));
let m = ordered.metadata();
assert_eq!(m.cardinality, CardinalityClass::Bounded(100));
assert_eq!(
m.materialization,
Materialization::BoundedBarrier { working_set_size: 100 }
);
}
#[test]
fn metadata_propagation_is_idempotent() {
let c = Comprehension::order(
Comprehension::filter(
Comprehension::cartesian(vec![clause("k", &[1, 2, 3]), clause("limit", &[10, 20])]),
"{k} * {limit} > 5",
),
StrategyName::Halton,
Some(5),
);
let m1 = c.metadata();
let m2 = c.metadata();
assert_eq!(m1, m2);
}
#[test]
fn has_continuous_axis_classifier() {
let lat = IndexFn::Lattice { axis_sizes: vec![3, 4] };
assert!(!lat.has_continuous_axis());
let cont = IndexFn::Continuous {
intervals: vec![Interval::closed(0.0, 1.0)],
measure: ProductMeasure::Uniform,
};
assert!(cont.has_continuous_axis());
}
#[test]
fn multi_axis_lattice_classifier() {
assert!(IndexFn::Lattice { axis_sizes: vec![3, 4] }.is_multi_axis_lattice());
assert!(!IndexFn::Lattice { axis_sizes: vec![3] }.is_multi_axis_lattice());
assert!(!IndexFn::Continuous {
intervals: vec![Interval::closed(0.0, 1.0), Interval::closed(0.0, 1.0)],
measure: ProductMeasure::Uniform,
}
.is_multi_axis_lattice());
}
}