1#[cfg(feature = "jit")]
2use std::marker::PhantomData;
3use std::{mem::size_of, sync::Arc};
4
5use laddu_compile::CachePlan;
6use laddu_data::{
7 data::{CacheStorage, Dataset, EventBatch},
8 io::ReadPlan,
9};
10use laddu_expr::{ExprId, ValueKind};
11use nalgebra::{DMatrix, DVector};
12use num::complex::Complex64;
13
14use super::layout::{FlatRows, matrix_at_optional};
15use super::{CpuPlan, DynamicLu, PreparedDatasetStats, RuntimeError, RuntimeResult, Value};
16use crate::MemoryLease;
17
18#[cfg(feature = "jit")]
20#[repr(C)]
21#[derive(Copy, Clone)]
22pub(crate) struct CacheDescriptor {
23 pub(crate) values: *const u8,
24 pub(crate) width: usize,
25}
26
27#[cfg(feature = "jit")]
28pub(crate) struct JitDescriptorSet<'a> {
29 pub(crate) values: Vec<CacheDescriptor>,
30 pub(crate) solve_rows: Vec<CacheDescriptor>,
31 pub(crate) _cache: PhantomData<&'a CpuBatchCache>,
32}
33
34#[derive(Clone, Debug)]
36pub struct CpuBatchCache {
37 pub(super) len: usize,
38 pub(super) weights: Vec<f64>,
39 pub(super) sum_weights: f64,
40 pub(super) nodes: Vec<ExprId>,
41 pub(crate) slots: Vec<CachedSlot>,
42 pub(super) factor_nodes: Vec<ExprId>,
43 pub(super) factor_slots: Vec<CachedFactorSlot>,
44 pub(super) solve_row_keys: Vec<(ExprId, usize, usize)>,
45 pub(crate) solve_row_slots: Vec<CachedSolveRowSlot>,
46}
47
48impl CpuBatchCache {
49 pub(super) fn new(
50 cache_plan: &CachePlan,
51 factor_matrices: &[(ExprId, usize)],
52 solve_row_keys: &[(ExprId, usize, usize)],
53 len: usize,
54 ) -> RuntimeResult<Self> {
55 let slots = cache_plan
56 .entries()
57 .iter()
58 .map(|entry| CachedSlot::new(entry.value_kind(), len))
59 .collect::<RuntimeResult<Vec<_>>>()?;
60 let solve_row_slots = solve_row_keys
61 .iter()
62 .map(|(_, _, dimension)| CachedSolveRowSlot::new(*dimension, len))
63 .collect::<RuntimeResult<Vec<_>>>()?;
64 Ok(Self {
65 len,
66 weights: vec![1.0; len],
67 sum_weights: len as f64,
68 nodes: cache_plan
69 .entries()
70 .iter()
71 .map(|entry| entry.node())
72 .collect(),
73 slots,
74 factor_nodes: factor_matrices.iter().map(|(node, _)| *node).collect(),
75 factor_slots: factor_matrices
76 .iter()
77 .map(|(_, dimension)| CachedFactorSlot::new(*dimension))
78 .collect(),
79 solve_row_keys: solve_row_keys.to_vec(),
80 solve_row_slots,
81 })
82 }
83
84 pub fn len(&self) -> usize {
86 self.len
87 }
88
89 pub fn is_empty(&self) -> bool {
91 self.len == 0
92 }
93
94 pub fn weights(&self) -> &[f64] {
96 &self.weights
97 }
98
99 pub fn sum_weights(&self) -> f64 {
101 self.sum_weights
102 }
103
104 #[cfg(feature = "jit")]
110 #[cfg(feature = "jit")]
111 pub(crate) fn jit_descriptors(&self) -> JitDescriptorSet<'_> {
112 JitDescriptorSet {
113 values: self
114 .slots
115 .iter()
116 .map(|slot| CacheDescriptor {
117 values: slot.values_ptr(),
118 width: slot.width(),
119 })
120 .collect(),
121 solve_rows: self
122 .solve_row_slots
123 .iter()
124 .map(|slot| CacheDescriptor {
125 values: slot.values.as_ptr().cast(),
126 width: slot.dimension,
127 })
128 .collect(),
129 _cache: PhantomData,
130 }
131 }
132
133 pub fn resident_bytes(&self) -> usize {
135 self.weights.capacity() * size_of::<f64>()
136 + self.nodes.capacity() * size_of::<ExprId>()
137 + self
138 .slots
139 .iter()
140 .map(CachedSlot::resident_bytes)
141 .sum::<usize>()
142 + self.factor_nodes.capacity() * size_of::<ExprId>()
143 + self
144 .factor_slots
145 .iter()
146 .map(CachedFactorSlot::resident_bytes)
147 .sum::<usize>()
148 + self.solve_row_keys.capacity() * size_of::<(ExprId, usize, usize)>()
149 + self
150 .solve_row_slots
151 .iter()
152 .map(CachedSolveRowSlot::resident_bytes)
153 .sum::<usize>()
154 }
155
156 pub(super) fn set_weights(&mut self, weights: Vec<f64>) {
157 self.sum_weights = weights.iter().sum();
158 self.weights = weights;
159 }
160
161 pub(super) fn push(&mut self, slot: usize, value: Value) -> RuntimeResult<()> {
162 let len = self.slots.len();
163 self.slots
164 .get_mut(slot)
165 .ok_or(RuntimeError::InvalidCache {
166 expected: len,
167 actual: slot + 1,
168 })?
169 .push(value)
170 }
171
172 pub(super) fn value(&self, slot: usize, row: usize) -> RuntimeResult<Value> {
173 if row >= self.len {
174 return Err(RuntimeError::InvalidShape {
175 index: row,
176 message: format!("cache row {row} out of bounds for len {}", self.len),
177 });
178 }
179 self.slots
180 .get(slot)
181 .ok_or(RuntimeError::InvalidCache {
182 expected: self.slots.len(),
183 actual: slot + 1,
184 })?
185 .value(row)
186 }
187
188 pub(super) fn scalar(&self, slot: usize, row: usize) -> RuntimeResult<Complex64> {
189 if row >= self.len {
190 return Err(RuntimeError::InvalidShape {
191 index: row,
192 message: format!("cache row {row} out of bounds for len {}", self.len),
193 });
194 }
195 self.slots
196 .get(slot)
197 .ok_or(RuntimeError::InvalidCache {
198 expected: self.slots.len(),
199 actual: slot + 1,
200 })?
201 .scalar(row)
202 }
203
204 pub(super) fn real_range(
205 &self,
206 slot: usize,
207 start: usize,
208 end: usize,
209 ) -> RuntimeResult<&[f64]> {
210 if start > end || end > self.len {
211 return Err(RuntimeError::InvalidShape {
212 index: start,
213 message: format!(
214 "cache range {start}..{end} out of bounds for len {}",
215 self.len
216 ),
217 });
218 }
219 self.slots
220 .get(slot)
221 .ok_or(RuntimeError::InvalidCache {
222 expected: self.slots.len(),
223 actual: slot + 1,
224 })?
225 .real_range(start, end)
226 }
227
228 pub(super) fn complex_range(
229 &self,
230 slot: usize,
231 start: usize,
232 end: usize,
233 ) -> RuntimeResult<&[Complex64]> {
234 if start > end || end > self.len {
235 return Err(RuntimeError::InvalidShape {
236 index: start,
237 message: format!(
238 "cache range {start}..{end} out of bounds for len {}",
239 self.len
240 ),
241 });
242 }
243 self.slots
244 .get(slot)
245 .ok_or(RuntimeError::InvalidCache {
246 expected: self.slots.len(),
247 actual: slot + 1,
248 })?
249 .complex_range(start, end)
250 }
251
252 pub(super) fn push_factor(&mut self, slot: usize, factor: DynamicLu) -> RuntimeResult<()> {
253 let len = self.factor_slots.len();
254 self.factor_slots
255 .get_mut(slot)
256 .ok_or(RuntimeError::InvalidCache {
257 expected: len,
258 actual: slot + 1,
259 })?
260 .push(factor)
261 }
262
263 pub(super) fn factor(&self, slot: usize, row: usize) -> RuntimeResult<&DynamicLu> {
264 self.factor_slots
265 .get(slot)
266 .ok_or(RuntimeError::InvalidCache {
267 expected: self.factor_slots.len(),
268 actual: slot + 1,
269 })?
270 .factor(row)
271 }
272
273 pub(super) fn push_solve_row(
274 &mut self,
275 slot: usize,
276 values: impl IntoIterator<Item = Complex64>,
277 ) -> RuntimeResult<()> {
278 let len = self.solve_row_slots.len();
279 self.solve_row_slots
280 .get_mut(slot)
281 .ok_or(RuntimeError::InvalidCache {
282 expected: len,
283 actual: slot + 1,
284 })?
285 .push(values)
286 }
287
288 pub(super) fn solve_row(&self, slot: usize, row: usize) -> RuntimeResult<&[Complex64]> {
289 self.solve_row_slots
290 .get(slot)
291 .ok_or(RuntimeError::InvalidCache {
292 expected: self.solve_row_slots.len(),
293 actual: slot + 1,
294 })?
295 .row(row)
296 }
297}
298
299impl CpuPlan {
300 pub fn cache_event_batch(&self, batch: &EventBatch) -> RuntimeResult<CpuBatchCache> {
311 let event_columns = self.event_columns(batch.schema())?;
312 let mut cache = CpuBatchCache::new(
313 &self.cache_plan,
314 &self.factor_matrices,
315 &self.solve_row_keys,
316 batch.len(),
317 )?;
318 for row in 0..batch.len() {
319 let values = self.evaluate_cache_values_for_row(batch, row, &event_columns)?;
320 for (slot, entry) in self.cache_plan.entries().iter().enumerate() {
321 let value = values[entry.node().index()]
322 .as_ref()
323 .expect("cacheable node should have been evaluated")
324 .clone();
325 cache.push(slot, value)?;
326 }
327 for plan in &self.solve_row_matrices {
328 let (rows, cols, values) = matrix_at_optional(&values, plan.matrix().index())?;
329 if rows != plan.dimension() || cols != plan.dimension() {
330 return Err(RuntimeError::InvalidShape {
331 index: plan.matrix().index(),
332 message: format!(
333 "specialized solve expected a {}x{} matrix, got {rows}x{cols}",
334 plan.dimension(),
335 plan.dimension()
336 ),
337 });
338 }
339 let transpose_factor = DMatrix::from_row_slice(rows, cols, values).transpose().lu();
340 for (slot, index) in plan.rows() {
341 let mut basis = DVector::zeros(plan.dimension());
342 basis[*index] = Complex64::ONE;
343 let inverse_row = transpose_factor
344 .solve(&basis)
345 .ok_or(RuntimeError::SingularMatrix(plan.matrix().index()))?;
346 cache.push_solve_row(*slot, inverse_row.iter().copied())?;
347 }
348 }
349 for (slot, (matrix, _)) in self.factor_matrices.iter().enumerate() {
350 let (rows, cols, values) = matrix_at_optional(&values, matrix.index())?;
351 cache.push_factor(slot, DMatrix::from_row_slice(rows, cols, values).lu())?;
352 }
353 }
354 cache.set_weights((0..batch.len()).map(|row| batch.weights_at(row)).collect());
355 Ok(cache)
356 }
357}
358
359#[derive(Clone, Debug)]
361pub struct CpuCachedBatch {
362 pub(super) cache: CpuBatchCache,
363}
364
365impl CpuCachedBatch {
366 pub(crate) fn from_cache(cache: CpuBatchCache) -> Self {
367 Self { cache }
368 }
369
370 pub fn cache(&self) -> &CpuBatchCache {
372 &self.cache
373 }
374
375 pub fn len(&self) -> usize {
377 self.cache.len()
378 }
379
380 pub fn is_empty(&self) -> bool {
382 self.cache.is_empty()
383 }
384
385 pub fn weights(&self) -> &[f64] {
387 self.cache.weights()
388 }
389
390 pub fn sum_weights(&self) -> f64 {
392 self.cache.sum_weights()
393 }
394
395 pub fn resident_bytes(&self) -> usize {
397 self.cache.resident_bytes()
398 }
399}
400
401#[derive(Clone, Debug, Default)]
403pub struct CpuCachedDataset {
404 pub(super) batches: Vec<CpuCachedBatch>,
405 pub(super) sum_weights: f64,
406}
407
408impl PreparedDatasetStats {
409 pub(crate) fn new(
410 local_events: usize,
411 global_events: usize,
412 local_batches: usize,
413 sum_weights: f64,
414 resident_bytes: usize,
415 storage: CacheStorage,
416 ) -> Self {
417 Self {
418 local_events,
419 global_events,
420 local_batches,
421 sum_weights,
422 resident_bytes,
423 storage,
424 }
425 }
426
427 pub fn local_events(&self) -> usize {
429 self.local_events
430 }
431
432 pub fn global_events(&self) -> usize {
434 self.global_events
435 }
436
437 pub fn local_batches(&self) -> usize {
439 self.local_batches
440 }
441
442 pub fn sum_weights(&self) -> f64 {
444 self.sum_weights
445 }
446
447 pub fn resident_bytes(&self) -> usize {
449 self.resident_bytes
450 }
451
452 pub fn storage(&self) -> CacheStorage {
454 self.storage
455 }
456}
457
458#[derive(Clone)]
459pub enum CpuPreparedDataset {
464 Resident {
466 dataset: Arc<CpuCachedDataset>,
468 stats: PreparedDatasetStats,
470 memory_lease: MemoryLease,
472 },
473 Streaming {
475 dataset: Dataset,
477 read_plan: ReadPlan,
479 stats: PreparedDatasetStats,
481 transient_bytes: u64,
483 },
484}
485
486impl std::fmt::Debug for CpuPreparedDataset {
487 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
488 formatter
489 .debug_struct("CpuPreparedDataset")
490 .field("stats", self.stats())
491 .finish_non_exhaustive()
492 }
493}
494
495impl CpuPreparedDataset {
496 pub fn stats(&self) -> &PreparedDatasetStats {
498 match self {
499 Self::Resident { stats, .. } | Self::Streaming { stats, .. } => stats,
500 }
501 }
502}
503
504impl CpuCachedDataset {
505 pub(crate) fn from_parts(batches: Vec<CpuCachedBatch>, sum_weights: f64) -> Self {
506 Self {
507 batches,
508 sum_weights,
509 }
510 }
511
512 pub fn batches(&self) -> &[CpuCachedBatch] {
514 &self.batches
515 }
516
517 pub fn len(&self) -> usize {
519 self.batches.iter().map(CpuCachedBatch::len).sum()
520 }
521
522 pub fn is_empty(&self) -> bool {
524 self.batches.iter().all(CpuCachedBatch::is_empty)
525 }
526
527 pub fn sum_weights(&self) -> f64 {
529 self.sum_weights
530 }
531
532 pub fn resident_bytes(&self) -> usize {
534 self.batches
535 .iter()
536 .map(CpuCachedBatch::resident_bytes)
537 .sum()
538 }
539}
540
541#[derive(Clone, Debug)]
542pub(super) struct CachedFactorSlot {
543 dimension: usize,
544 factors: Vec<DynamicLu>,
545}
546
547#[derive(Clone, Debug)]
548pub(crate) struct CachedSolveRowSlot {
549 #[cfg_attr(not(feature = "jit"), allow(dead_code))]
550 pub(crate) dimension: usize,
551 pub(crate) values: FlatRows<Complex64>,
552}
553
554impl CachedSolveRowSlot {
555 fn new(dimension: usize, events: usize) -> RuntimeResult<Self> {
556 Ok(Self {
557 dimension,
558 values: FlatRows::try_with_capacity(dimension, events)?,
559 })
560 }
561
562 fn push(&mut self, values: impl IntoIterator<Item = Complex64>) -> RuntimeResult<()> {
563 self.values.push_row(values)
564 }
565
566 fn row(&self, row: usize) -> RuntimeResult<&[Complex64]> {
567 self.values.row(row)
568 }
569
570 fn resident_bytes(&self) -> usize {
571 self.values.capacity() * size_of::<Complex64>()
572 }
573}
574
575impl CachedFactorSlot {
576 fn new(dimension: usize) -> Self {
577 Self {
578 dimension,
579 factors: Vec::new(),
580 }
581 }
582
583 fn push(&mut self, factor: DynamicLu) -> RuntimeResult<()> {
584 self.factors.push(factor);
585 Ok(())
586 }
587
588 fn factor(&self, row: usize) -> RuntimeResult<&DynamicLu> {
589 self.factors
590 .get(row)
591 .ok_or_else(|| RuntimeError::InvalidShape {
592 index: row,
593 message: format!(
594 "factor row {row} out of bounds for len {}",
595 self.factors.len()
596 ),
597 })
598 }
599
600 fn resident_bytes(&self) -> usize {
601 self.factors.capacity()
602 * (self.dimension * self.dimension * size_of::<Complex64>()
603 + self.dimension * size_of::<usize>())
604 }
605}
606
607#[derive(Clone, Debug, PartialEq)]
608pub(crate) enum CachedSlot {
609 Real(Vec<f64>),
610 Complex(Vec<Complex64>),
611 Vector {
612 len: usize,
613 values: FlatRows<Complex64>,
614 },
615 Matrix {
616 rows: usize,
617 cols: usize,
618 values: FlatRows<Complex64>,
619 },
620}
621
622impl CachedSlot {
623 #[cfg(feature = "jit")]
624 pub(crate) fn values_ptr(&self) -> *const u8 {
625 match self {
626 Self::Real(values) => values.as_ptr().cast(),
627 Self::Complex(values) => values.as_ptr().cast(),
628 Self::Vector { values, .. } | Self::Matrix { values, .. } => values.as_ptr().cast(),
629 }
630 }
631
632 #[cfg(feature = "jit")]
633 pub(crate) fn width(&self) -> usize {
634 match self {
635 Self::Real(_) | Self::Complex(_) => 1,
636 Self::Vector { values, .. } | Self::Matrix { values, .. } => values.width(),
637 }
638 }
639
640 fn new(kind: ValueKind, events: usize) -> RuntimeResult<Self> {
641 Ok(match kind {
642 ValueKind::Real => Self::Real(Vec::with_capacity(events)),
643 ValueKind::Complex => Self::Complex(Vec::with_capacity(events)),
644 ValueKind::Vector { len } => Self::Vector {
645 len,
646 values: FlatRows::try_with_capacity(len, events)?,
647 },
648 ValueKind::Matrix { rows, cols } => Self::Matrix {
649 rows,
650 cols,
651 values: FlatRows::try_with_capacity(
652 rows.checked_mul(cols)
653 .ok_or_else(|| RuntimeError::InvalidShape {
654 index: rows,
655 message: format!("matrix width overflowed for {rows}x{cols}"),
656 })?,
657 events,
658 )?,
659 },
660 })
661 }
662
663 pub(crate) fn resident_bytes(&self) -> usize {
664 match self {
665 Self::Real(values) => values.capacity() * size_of::<f64>(),
666 Self::Complex(values) => values.capacity() * size_of::<Complex64>(),
667 Self::Vector { values, .. } | Self::Matrix { values, .. } => {
668 values.capacity() * size_of::<Complex64>()
669 }
670 }
671 }
672
673 fn push(&mut self, value: Value) -> RuntimeResult<()> {
674 match (self, value) {
675 (Self::Real(values), Value::Scalar(value)) => {
676 values.push(value.re);
677 Ok(())
678 }
679 (Self::Complex(values), Value::Scalar(value)) => {
680 values.push(value);
681 Ok(())
682 }
683 (Self::Vector { len, values }, Value::Vector(value)) if *len == value.len() => {
684 values.push_row(value)
685 }
686 (
687 Self::Matrix { rows, cols, values },
688 Value::Matrix {
689 rows: value_rows,
690 cols: value_cols,
691 values: value,
692 },
693 ) if *rows == value_rows && *cols == value_cols => values.push_row(value),
694 (_, value) => Err(RuntimeError::InvalidShape {
695 index: 0,
696 message: format!("cached value kind did not match slot: {}", value.kind()),
697 }),
698 }
699 }
700
701 pub(super) fn value(&self, row: usize) -> RuntimeResult<Value> {
702 match self {
703 Self::Real(values) => values
704 .get(row)
705 .copied()
706 .map(Complex64::from)
707 .map(Value::Scalar)
708 .ok_or_else(|| RuntimeError::InvalidShape {
709 index: row,
710 message: format!("cache row {row} out of bounds"),
711 }),
712 Self::Complex(values) => values.get(row).copied().map(Value::Scalar).ok_or_else(|| {
713 RuntimeError::InvalidShape {
714 index: row,
715 message: format!("cache row {row} out of bounds"),
716 }
717 }),
718 Self::Vector { values, .. } => {
719 values.row(row).map(|value| Value::Vector(value.to_vec()))
720 }
721 Self::Matrix { rows, cols, values } => values.row(row).map(|value| Value::Matrix {
722 rows: *rows,
723 cols: *cols,
724 values: value.to_vec(),
725 }),
726 }
727 }
728
729 fn scalar(&self, row: usize) -> RuntimeResult<Complex64> {
730 match self {
731 Self::Real(values) => values
732 .get(row)
733 .copied()
734 .map(Complex64::from)
735 .ok_or_else(|| RuntimeError::InvalidShape {
736 index: row,
737 message: format!("cache row {row} out of bounds"),
738 }),
739 Self::Complex(values) => {
740 values
741 .get(row)
742 .copied()
743 .ok_or_else(|| RuntimeError::InvalidShape {
744 index: row,
745 message: format!("cache row {row} out of bounds"),
746 })
747 }
748 Self::Vector { .. } | Self::Matrix { .. } => Err(RuntimeError::TypeMismatch {
749 index: row,
750 expected: "scalar",
751 actual: match self {
752 Self::Vector { .. } => "vector",
753 Self::Matrix { .. } => "matrix",
754 Self::Real(_) | Self::Complex(_) => unreachable!(),
755 },
756 }),
757 }
758 }
759
760 fn real_range(&self, start: usize, end: usize) -> RuntimeResult<&[f64]> {
761 match self {
762 Self::Real(values) => {
763 values
764 .get(start..end)
765 .ok_or_else(|| RuntimeError::InvalidShape {
766 index: start,
767 message: format!("cache range {start}..{end} out of bounds"),
768 })
769 }
770 Self::Complex(_) | Self::Vector { .. } | Self::Matrix { .. } => {
771 Err(RuntimeError::TypeMismatch {
772 index: start,
773 expected: "real scalar",
774 actual: match self {
775 Self::Complex(_) => "complex scalar",
776 Self::Vector { .. } => "vector",
777 Self::Matrix { .. } => "matrix",
778 Self::Real(_) => unreachable!(),
779 },
780 })
781 }
782 }
783 }
784
785 fn complex_range(&self, start: usize, end: usize) -> RuntimeResult<&[Complex64]> {
786 match self {
787 Self::Complex(values) => {
788 values
789 .get(start..end)
790 .ok_or_else(|| RuntimeError::InvalidShape {
791 index: start,
792 message: format!("cache range {start}..{end} out of bounds"),
793 })
794 }
795 Self::Real(_) | Self::Vector { .. } | Self::Matrix { .. } => {
796 Err(RuntimeError::TypeMismatch {
797 index: start,
798 expected: "complex scalar",
799 actual: match self {
800 Self::Real(_) => "real scalar",
801 Self::Vector { .. } => "vector",
802 Self::Matrix { .. } => "matrix",
803 Self::Complex(_) => unreachable!(),
804 },
805 })
806 }
807 }
808 }
809}