1use std::sync::Arc;
2
3use laddu_compile::CompiledModel;
4use laddu_data::{
5 LadduDataError, LadduDataResult,
6 data::{Dataset, EventBatch},
7 io::{EventBatchIter, EventSource, ReadPlan, SourceCapabilities, memory::MemorySource},
8 schema::Schema,
9};
10use laddu_expr::{Expr, ExprShape, ValueKind};
11use num::complex::Complex64;
12use serde::{Deserialize, Deserializer, Serialize};
13
14use crate::{Execution, PreparedModel, RuntimeError, RuntimeResult};
15
16#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
18pub enum Comparison {
19 Lt,
21 Le,
23 Gt,
25 Ge,
27 Eq,
29 Ne,
31}
32
33#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
35pub enum IntervalClosure {
36 Open,
38 LeftClosed,
40 RightClosed,
42 #[default]
44 Closed,
45}
46
47#[derive(Clone, Debug)]
49pub enum Predicate {
50 Compare {
52 lhs: Expr,
54 op: Comparison,
56 rhs: Expr,
58 },
59 And(Box<Self>, Box<Self>),
61 Or(Box<Self>, Box<Self>),
63 Not(Box<Self>),
65 Between {
67 value: Expr,
69 lower: Expr,
71 upper: Expr,
73 closure: IntervalClosure,
75 },
76}
77
78impl Predicate {
79 pub fn compare(lhs: impl Into<Expr>, op: Comparison, rhs: impl Into<Expr>) -> Self {
81 Self::Compare {
82 lhs: lhs.into(),
83 op,
84 rhs: rhs.into(),
85 }
86 }
87
88 pub fn lt(lhs: impl Into<Expr>, rhs: impl Into<Expr>) -> Self {
90 Self::compare(lhs, Comparison::Lt, rhs)
91 }
92 pub fn le(lhs: impl Into<Expr>, rhs: impl Into<Expr>) -> Self {
94 Self::compare(lhs, Comparison::Le, rhs)
95 }
96 pub fn gt(lhs: impl Into<Expr>, rhs: impl Into<Expr>) -> Self {
98 Self::compare(lhs, Comparison::Gt, rhs)
99 }
100 pub fn ge(lhs: impl Into<Expr>, rhs: impl Into<Expr>) -> Self {
102 Self::compare(lhs, Comparison::Ge, rhs)
103 }
104 pub fn eq(lhs: impl Into<Expr>, rhs: impl Into<Expr>) -> Self {
106 Self::compare(lhs, Comparison::Eq, rhs)
107 }
108 pub fn ne(lhs: impl Into<Expr>, rhs: impl Into<Expr>) -> Self {
110 Self::compare(lhs, Comparison::Ne, rhs)
111 }
112 pub fn and(self, rhs: Self) -> Self {
114 Self::And(Box::new(self), Box::new(rhs))
115 }
116 pub fn or(self, rhs: Self) -> Self {
118 Self::Or(Box::new(self), Box::new(rhs))
119 }
120 pub fn between(value: impl Into<Expr>, lower: impl Into<Expr>, upper: impl Into<Expr>) -> Self {
122 Self::between_with(value, lower, upper, IntervalClosure::Closed)
123 }
124 pub fn between_with(
126 value: impl Into<Expr>,
127 lower: impl Into<Expr>,
128 upper: impl Into<Expr>,
129 closure: IntervalClosure,
130 ) -> Self {
131 Self::Between {
132 value: value.into(),
133 lower: lower.into(),
134 upper: upper.into(),
135 closure,
136 }
137 }
138}
139
140impl std::ops::Not for Predicate {
141 type Output = Self;
142 fn not(self) -> Self::Output {
143 Self::Not(Box::new(self))
144 }
145}
146
147#[derive(Clone, Debug, PartialEq, Serialize)]
149#[serde(transparent)]
150pub struct BinSpec {
151 edges: Arc<[f64]>,
152}
153
154impl<'de> Deserialize<'de> for BinSpec {
155 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
156 where
157 D: Deserializer<'de>,
158 {
159 let edges = Vec::<f64>::deserialize(deserializer)?;
160 Self::edges(edges).map_err(serde::de::Error::custom)
161 }
162}
163
164impl BinSpec {
165 pub fn uniform(count: usize, min: f64, max: f64) -> RuntimeResult<Self> {
172 if count == 0 || !min.is_finite() || !max.is_finite() || min >= max {
173 return Err(query_error(
174 "uniform bins require a positive count and finite min < max",
175 ));
176 }
177 let width = (max - min) / count as f64;
178 Self::edges((0..=count).map(|i| min + i as f64 * width))
179 }
180
181 pub fn edges(edges: impl IntoIterator<Item = f64>) -> RuntimeResult<Self> {
188 let edges: Vec<_> = edges.into_iter().collect();
189 if edges.len() < 2
190 || edges.iter().any(|x| !x.is_finite())
191 || edges.windows(2).any(|w| w[0] >= w[1])
192 {
193 return Err(query_error(
194 "bin edges must contain at least two finite, strictly increasing values",
195 ));
196 }
197 Ok(Self {
198 edges: edges.into(),
199 })
200 }
201
202 pub fn bin_count(&self) -> usize {
204 self.edges.len() - 1
205 }
206 pub fn edges_slice(&self) -> &[f64] {
208 &self.edges
209 }
210
211 fn index(&self, value: f64) -> Option<usize> {
212 if !value.is_finite() || value < self.edges[0] || value > *self.edges.last()? {
213 return None;
214 }
215 if value == *self.edges.last()? {
216 return Some(self.bin_count() - 1);
217 }
218 let upper = self.edges.partition_point(|edge| *edge <= value);
219 upper
220 .checked_sub(1)
221 .filter(|index| *index < self.bin_count())
222 }
223}
224
225#[derive(Clone)]
227pub struct DatasetBin {
228 index: usize,
229 lower: f64,
230 upper: f64,
231 dataset: Dataset,
232}
233
234impl DatasetBin {
235 pub fn index(&self) -> usize {
237 self.index
238 }
239 pub fn lower(&self) -> f64 {
241 self.lower
242 }
243 pub fn upper(&self) -> f64 {
245 self.upper
246 }
247 pub fn dataset(&self) -> &Dataset {
249 &self.dataset
250 }
251 pub fn into_dataset(self) -> Dataset {
253 self.dataset
254 }
255}
256
257pub trait DatasetExprExt {
259 fn evaluate_expr(&self, expr: &Expr, execution: &Execution) -> RuntimeResult<Vec<Complex64>>;
266 fn evaluate_real(&self, expr: &Expr, execution: &Execution) -> RuntimeResult<Vec<f64>>;
273 fn select(&self, predicate: &Predicate, execution: &Execution) -> RuntimeResult<Dataset>;
280 fn bin_by(
287 &self,
288 expr: &Expr,
289 bins: BinSpec,
290 execution: &Execution,
291 ) -> RuntimeResult<Vec<DatasetBin>>;
292}
293
294impl DatasetExprExt for Dataset {
295 fn evaluate_expr(&self, expr: &Expr, execution: &Execution) -> RuntimeResult<Vec<Complex64>> {
296 let query = QueryExpr::prepare(expr, execution, false)?;
297 let mut output = Vec::new();
298 for batch in self.batches().map_err(data_error)? {
299 output.extend(query.evaluate_batch(&batch.map_err(data_error)?)?);
300 }
301 Ok(output)
302 }
303
304 fn evaluate_real(&self, expr: &Expr, execution: &Execution) -> RuntimeResult<Vec<f64>> {
305 let query = QueryExpr::prepare(expr, execution, true)?;
306 let mut output = Vec::new();
307 for batch in self.batches().map_err(data_error)? {
308 output.extend(
309 query
310 .evaluate_batch(&batch.map_err(data_error)?)?
311 .into_iter()
312 .map(|v| v.re),
313 );
314 }
315 Ok(output)
316 }
317
318 fn select(&self, predicate: &Predicate, execution: &Execution) -> RuntimeResult<Dataset> {
319 let compiled = CompiledPredicate::prepare(predicate, execution)?;
320 Ok(self.with_derived_source(QuerySource {
321 source: self.clone(),
322 filter: QueryFilter::Predicate(Arc::new(compiled)),
323 }))
324 }
325
326 fn bin_by(
327 &self,
328 expr: &Expr,
329 bins: BinSpec,
330 execution: &Execution,
331 ) -> RuntimeResult<Vec<DatasetBin>> {
332 let query = QueryExpr::prepare(expr, execution, true)?;
333 let schema = self.schema().map_err(data_error)?;
334 let mut partitions = vec![Vec::new(); bins.bin_count()];
335 for batch in self.batches().map_err(data_error)? {
336 let batch = batch.map_err(data_error)?;
337 let mut rows = vec![Vec::new(); bins.bin_count()];
338 for (row, value) in query.evaluate_batch(&batch)?.into_iter().enumerate() {
339 if let Some(index) = bins.index(value.re) {
340 rows[index].push(row);
341 }
342 }
343 for (partition, rows) in partitions.iter_mut().zip(rows) {
344 if !rows.is_empty() {
345 partition.push(batch.select(&rows));
346 }
347 }
348 }
349
350 partitions
351 .into_iter()
352 .enumerate()
353 .map(|(index, batches)| {
354 let source = if batches.is_empty() {
355 MemorySource::new(
356 EventBatch::from_events(Arc::clone(&schema), std::iter::empty())
357 .map_err(data_error)?,
358 )
359 } else {
360 MemorySource::from_batches(batches).map_err(data_error)?
361 };
362 Ok(DatasetBin {
363 index,
364 lower: bins.edges[index],
365 upper: bins.edges[index + 1],
366 dataset: self.with_derived_source(source),
367 })
368 })
369 .collect()
370 }
371}
372
373struct QueryExpr {
374 model: PreparedModel,
375 params: laddu_expr::parameters::ParamValues,
376}
377
378impl QueryExpr {
379 fn prepare(expr: &Expr, execution: &Execution, require_real: bool) -> RuntimeResult<Self> {
380 if expr.shape().map_err(|e| query_error(e.to_string()))? != ExprShape::Scalar {
381 return Err(query_error("dataset expressions must be scalar"));
382 }
383 let compiled = CompiledModel::from_expr(expr).map_err(|e| query_error(e.to_string()))?;
384 if compiled.params().n_free() != 0 {
385 return Err(query_error(
386 "dataset expressions cannot contain free parameters",
387 ));
388 }
389 if require_real
390 && compiled
391 .node_facts(compiled.graph().root())
392 .is_some_and(|facts| facts.value_kind == ValueKind::Complex)
393 {
394 return Err(query_error(
395 "this dataset operation requires a real-valued expression",
396 ));
397 }
398 let params = compiled.params().default_values();
399 let model = PreparedModel::prepare(&compiled, execution)?;
400 Ok(Self { model, params })
401 }
402
403 fn evaluate_batch(&self, batch: &EventBatch) -> RuntimeResult<Vec<Complex64>> {
404 self.model.evaluate_batch(&self.params, batch)
405 }
406}
407
408enum CompiledPredicate {
409 Compare {
410 lhs: Box<QueryExpr>,
411 op: Comparison,
412 rhs: Box<QueryExpr>,
413 },
414 And(Box<Self>, Box<Self>),
415 Or(Box<Self>, Box<Self>),
416 Not(Box<Self>),
417 Between {
418 value: Box<QueryExpr>,
419 lower: Box<QueryExpr>,
420 upper: Box<QueryExpr>,
421 closure: IntervalClosure,
422 },
423}
424
425impl CompiledPredicate {
426 fn prepare(predicate: &Predicate, execution: &Execution) -> RuntimeResult<Self> {
427 Ok(match predicate {
428 Predicate::Compare { lhs, op, rhs } => Self::Compare {
429 lhs: Box::new(QueryExpr::prepare(lhs, execution, true)?),
430 op: *op,
431 rhs: Box::new(QueryExpr::prepare(rhs, execution, true)?),
432 },
433 Predicate::And(lhs, rhs) => Self::And(
434 Box::new(Self::prepare(lhs, execution)?),
435 Box::new(Self::prepare(rhs, execution)?),
436 ),
437 Predicate::Or(lhs, rhs) => Self::Or(
438 Box::new(Self::prepare(lhs, execution)?),
439 Box::new(Self::prepare(rhs, execution)?),
440 ),
441 Predicate::Not(inner) => Self::Not(Box::new(Self::prepare(inner, execution)?)),
442 Predicate::Between {
443 value,
444 lower,
445 upper,
446 closure,
447 } => Self::Between {
448 value: Box::new(QueryExpr::prepare(value, execution, true)?),
449 lower: Box::new(QueryExpr::prepare(lower, execution, true)?),
450 upper: Box::new(QueryExpr::prepare(upper, execution, true)?),
451 closure: *closure,
452 },
453 })
454 }
455
456 fn evaluate_batch(&self, batch: &EventBatch) -> RuntimeResult<Vec<bool>> {
457 Ok(match self {
458 Self::Compare { lhs, op, rhs } => lhs
459 .evaluate_batch(batch)?
460 .into_iter()
461 .zip(rhs.evaluate_batch(batch)?)
462 .map(|(lhs, rhs)| compare(lhs.re, *op, rhs.re))
463 .collect(),
464 Self::And(lhs, rhs) => lhs
465 .evaluate_batch(batch)?
466 .into_iter()
467 .zip(rhs.evaluate_batch(batch)?)
468 .map(|(l, r)| l && r)
469 .collect(),
470 Self::Or(lhs, rhs) => lhs
471 .evaluate_batch(batch)?
472 .into_iter()
473 .zip(rhs.evaluate_batch(batch)?)
474 .map(|(l, r)| l || r)
475 .collect(),
476 Self::Not(inner) => inner
477 .evaluate_batch(batch)?
478 .into_iter()
479 .map(|v| !v)
480 .collect(),
481 Self::Between {
482 value,
483 lower,
484 upper,
485 closure,
486 } => value
487 .evaluate_batch(batch)?
488 .into_iter()
489 .zip(lower.evaluate_batch(batch)?)
490 .zip(upper.evaluate_batch(batch)?)
491 .map(|((value, lower), upper)| {
492 let lower_op = match closure {
493 IntervalClosure::Open | IntervalClosure::RightClosed => Comparison::Gt,
494 IntervalClosure::LeftClosed | IntervalClosure::Closed => Comparison::Ge,
495 };
496 let upper_op = match closure {
497 IntervalClosure::Open | IntervalClosure::LeftClosed => Comparison::Lt,
498 IntervalClosure::RightClosed | IntervalClosure::Closed => Comparison::Le,
499 };
500 compare(value.re, lower_op, lower.re) && compare(value.re, upper_op, upper.re)
501 })
502 .collect(),
503 })
504 }
505}
506
507fn compare(lhs: f64, op: Comparison, rhs: f64) -> bool {
508 if lhs.is_nan() || rhs.is_nan() {
509 return false;
510 }
511 match op {
512 Comparison::Lt => lhs < rhs,
513 Comparison::Le => lhs <= rhs,
514 Comparison::Gt => lhs > rhs,
515 Comparison::Ge => lhs >= rhs,
516 Comparison::Eq => lhs == rhs,
517 Comparison::Ne => lhs != rhs,
518 }
519}
520
521#[derive(Clone)]
522struct QuerySource {
523 source: Dataset,
524 filter: QueryFilter,
525}
526
527#[derive(Clone)]
528enum QueryFilter {
529 Predicate(Arc<CompiledPredicate>),
530}
531
532impl EventSource for QuerySource {
533 fn schema(&self) -> LadduDataResult<Arc<Schema>> {
534 self.source.schema()
535 }
536
537 fn capabilities(&self) -> SourceCapabilities {
538 let source = self.source.capabilities();
539 SourceCapabilities {
540 exact_len: false,
541 exact_weighted_total: false,
542 random_access: false,
543 deterministic_partitioning: source.deterministic_partitioning,
544 predicate_pushdown: false,
545 projection_pushdown: false,
546 streaming: true,
547 }
548 }
549
550 fn batches(&self, plan: ReadPlan) -> LadduDataResult<EventBatchIter> {
551 let batches = self.source.batches_with_plan(plan)?;
552 let filter = self.filter.clone();
553 Ok(Box::new(batches.filter_map(move |batch| {
554 let batch = match batch {
555 Ok(batch) => batch,
556 Err(error) => return Some(Err(error)),
557 };
558 let rows = match filter.rows(&batch) {
559 Ok(rows) => rows,
560 Err(error) => return Some(Err(LadduDataError::Source(error.to_string()))),
561 };
562 (!rows.is_empty()).then(|| Ok(batch.select(&rows)))
563 })))
564 }
565}
566
567impl QueryFilter {
568 fn rows(&self, batch: &EventBatch) -> RuntimeResult<Vec<usize>> {
569 match self {
571 Self::Predicate(predicate) => Ok(predicate
572 .evaluate_batch(batch)?
573 .into_iter()
574 .enumerate()
575 .filter_map(|(row, keep)| keep.then_some(row))
576 .collect()),
577 }
578 }
579}
580
581fn query_error(message: impl Into<String>) -> RuntimeError {
582 RuntimeError::InvalidShape {
583 index: 0,
584 message: message.into(),
585 }
586}
587fn data_error(error: impl ToString) -> RuntimeError {
588 RuntimeError::Data(error.to_string())
589}
590
591#[cfg(test)]
592mod tests {
593 use super::*;
594 use laddu_data::{
595 data::OwnedEvent,
596 io::{EventSource, ReadPlan, SourceCapabilities, memory::MemorySource},
597 schema::Schema,
598 };
599 use laddu_expr::{complex, event_scalar};
600 use std::sync::atomic::{AtomicUsize, Ordering};
601
602 #[test]
603 fn bin_spec_roundtrip_preserves_validation() {
604 let bins = BinSpec::edges([-1.0, 0.0, 2.0]).unwrap();
605 let json = serde_json::to_string(&bins).unwrap();
606 assert_eq!(serde_json::from_str::<BinSpec>(&json).unwrap(), bins);
607 assert!(serde_json::from_str::<BinSpec>("[0.0,0.0]").is_err());
608 }
609
610 #[derive(Clone)]
611 struct CountingSource {
612 inner: MemorySource,
613 reads: Arc<AtomicUsize>,
614 }
615
616 impl EventSource for CountingSource {
617 fn schema(&self) -> LadduDataResult<Arc<Schema>> {
618 EventSource::schema(&self.inner)
619 }
620
621 fn capabilities(&self) -> SourceCapabilities {
622 self.inner.capabilities()
623 }
624
625 fn batches(&self, plan: ReadPlan) -> LadduDataResult<EventBatchIter> {
626 self.reads.fetch_add(1, Ordering::Relaxed);
627 self.inner.batches(plan)
628 }
629 }
630
631 fn dataset() -> Dataset {
632 let schema = Arc::new(Schema::new(std::iter::empty::<&str>(), ["x"], true).unwrap());
633 Dataset::from_events(
634 schema,
635 [
636 OwnedEvent::weighted(vec![], vec![-1.0], 0.5),
637 OwnedEvent::weighted(vec![], vec![0.0], 1.0),
638 OwnedEvent::weighted(vec![], vec![1.0], 1.5),
639 OwnedEvent::weighted(vec![], vec![2.0], 2.0),
640 ],
641 )
642 .unwrap()
643 }
644
645 #[test]
646 fn evaluates_selects_and_bins_dataset_expressions() {
647 let dataset = dataset().chunked(1).unwrap();
648 let execution = Execution::default();
649 let x = event_scalar("x");
650 assert_eq!(
651 dataset.evaluate_real(&x, &execution).unwrap(),
652 vec![-1.0, 0.0, 1.0, 2.0]
653 );
654
655 let selected = dataset
656 .select(
657 &Predicate::ge(x.clone(), 0.0).and(Predicate::lt(x.clone(), 2.0)),
658 &execution,
659 )
660 .unwrap();
661 assert_eq!(
662 selected.map_events(|event| event.scalar(0)).unwrap(),
663 vec![0.0, 1.0]
664 );
665 assert_eq!(selected.sum_weights().unwrap(), 2.5);
666
667 let bins = dataset
668 .bin_by(&x, BinSpec::uniform(2, 0.0, 2.0).unwrap(), &execution)
669 .unwrap();
670 assert_eq!(bins.len(), 2);
671 assert_eq!(
672 bins[0]
673 .dataset()
674 .map_events(|event| event.scalar(0))
675 .unwrap(),
676 vec![0.0]
677 );
678 assert_eq!(
679 bins[1]
680 .dataset()
681 .map_events(|event| event.scalar(0))
682 .unwrap(),
683 vec![1.0, 2.0]
684 );
685 }
686
687 #[test]
688 fn traversing_all_bins_reads_the_source_once() {
689 let reads = Arc::new(AtomicUsize::new(0));
690 let source = CountingSource {
691 inner: match dataset().batches().unwrap().next().unwrap() {
692 Ok(batch) => MemorySource::new(batch),
693 Err(error) => panic!("unexpected source error: {error}"),
694 },
695 reads: Arc::clone(&reads),
696 };
697 let dataset = Dataset::new(source).chunked(1).unwrap();
698 let bins = dataset
699 .bin_by(
700 &event_scalar("x"),
701 BinSpec::uniform(4, -1.0, 3.0).unwrap(),
702 &Execution::default(),
703 )
704 .unwrap();
705
706 let values = bins
707 .into_iter()
708 .map(|bin| {
709 bin.into_dataset()
710 .map_events(|event| event.scalar(0))
711 .unwrap()
712 })
713 .collect::<Vec<_>>();
714 assert_eq!(values, [vec![-1.0], vec![0.0], vec![1.0], vec![2.0]]);
715 assert_eq!(reads.load(Ordering::Relaxed), 1);
716 }
717
718 #[test]
719 fn between_predicates_have_explicit_endpoint_semantics() {
720 let dataset = dataset();
721 let execution = Execution::default();
722 let x = event_scalar("x");
723
724 let closed = dataset
725 .select(&Predicate::between(x.clone(), 0.0, 1.0), &execution)
726 .unwrap();
727 assert_eq!(
728 closed.map_events(|event| event.scalar(0)).unwrap(),
729 vec![0.0, 1.0]
730 );
731
732 let open = dataset
733 .select(
734 &Predicate::between_with(x, -1.0, 1.0, IntervalClosure::Open),
735 &execution,
736 )
737 .unwrap();
738 assert_eq!(open.map_events(|event| event.scalar(0)).unwrap(), vec![0.0]);
739 }
740
741 #[test]
742 fn real_queries_reject_complex_and_free_parameter_expressions() {
743 let dataset = dataset();
744 let execution = Execution::default();
745 assert!(
746 dataset
747 .evaluate_real(&complex(1.0, 1.0), &execution)
748 .is_err()
749 );
750 let parameter = Expr::from(laddu_expr::parameters::Parameter::free("p"));
751 assert!(dataset.evaluate_expr(¶meter, &execution).is_err());
752 }
753
754 #[test]
755 fn bin_edges_validate_and_nan_predicates_are_false() {
756 assert!(BinSpec::edges([0.0, 0.0]).is_err());
757 assert!(!compare(f64::NAN, Comparison::Ne, 0.0));
758 }
759
760 #[test]
761 fn selection_is_lazy_and_one_pass_binning_preserves_streaming_policy() {
762 let source = dataset();
763 let batch = source.batches().unwrap().next().unwrap().unwrap();
764 let reads = Arc::new(AtomicUsize::new(0));
765 let dataset = Dataset::new(CountingSource {
766 inner: MemorySource::new(batch),
767 reads: Arc::clone(&reads),
768 })
769 .streaming();
770 let execution = Execution::default();
771 let x = event_scalar("x");
772
773 let selected = dataset
774 .select(&Predicate::ge(x.clone(), 0.0), &execution)
775 .unwrap();
776 let bins = dataset
777 .bin_by(&x, BinSpec::uniform(2, 0.0, 2.0).unwrap(), &execution)
778 .unwrap();
779 assert_eq!(reads.load(Ordering::Relaxed), 1);
780 assert_eq!(
781 selected.cache_storage(),
782 laddu_data::data::CacheStorage::Streaming
783 );
784
785 assert_eq!(
786 selected.map_events(|event| event.scalar(0)).unwrap(),
787 vec![0.0, 1.0, 2.0]
788 );
789 assert_eq!(reads.load(Ordering::Relaxed), 2);
790 assert_eq!(
791 bins[0]
792 .dataset()
793 .map_events(|event| event.scalar(0))
794 .unwrap(),
795 vec![0.0]
796 );
797 assert_eq!(reads.load(Ordering::Relaxed), 2);
798 }
799
800 #[test]
801 fn unknown_cardinality_fastest_discovers_and_retains_small_selection() {
802 let source = dataset();
803 let batch = source.batches().unwrap().next().unwrap().unwrap();
804 let reads = Arc::new(AtomicUsize::new(0));
805 let dataset = Dataset::new(CountingSource {
806 inner: MemorySource::new(batch),
807 reads: Arc::clone(&reads),
808 });
809 let execution = Execution::default();
810 let x = event_scalar("x");
811 let selected = dataset
812 .select(&Predicate::ge(x.clone(), 0.0), &execution)
813 .unwrap();
814 let compiled = CompiledModel::from_expr(&x).unwrap();
815 let params = compiled.params().default_values();
816 let model = PreparedModel::prepare(&compiled, &execution).unwrap();
817 let prepared = model.prepare_dataset(&execution, &selected).unwrap();
818
819 #[cfg(not(feature = "wgpu"))]
820 let crate::PreparedDataset::Cpu(prepared_cpu) = &prepared;
821 #[cfg(feature = "wgpu")]
822 let crate::PreparedDataset::Cpu(prepared_cpu) = &prepared else {
823 panic!("default execution prepares CPU datasets");
824 };
825 assert_eq!(
826 prepared_cpu.stats().storage(),
827 laddu_data::data::CacheStorage::Resident
828 );
829 assert_eq!(prepared_cpu.stats().local_events(), 3);
830 assert_eq!(reads.load(Ordering::Relaxed), 2);
831
832 for _ in 0..2 {
833 assert_eq!(
834 model
835 .reduce(
836 &execution,
837 ¶ms,
838 &prepared,
839 laddu_compile::ReductionPlan::weighted_real(),
840 )
841 .unwrap(),
842 5.5
843 );
844 }
845 assert_eq!(reads.load(Ordering::Relaxed), 2);
846 }
847}