1use std::{
2 collections::HashMap,
3 sync::{Arc, OnceLock},
4};
5
6use laddu_autodiff::{AutodiffMode, AutodiffPlan, AutodiffResult};
7use laddu_compile::{CachePlan, CompiledModel, SolveComponentPlan, SolveRowMatrixPlan};
8use laddu_data::data::{CacheStorage, EventBatch};
9use laddu_expr::{
10 ExprGraph, ExprId, P4Component,
11 parameters::{ParamId, ParamLayout, ParamValues},
12};
13#[cfg(test)]
14use laddu_kernel::ir::KernelValueClass;
15use laddu_kernel::ir::{GradientKernelIr, ScalarKernelIr};
16use nalgebra::{Dyn, LU};
17use num::complex::Complex64;
18
19use crate::{JitPolicy, Precision, RuntimeError, RuntimeResult, execution::Execution};
20
21mod gradient_interpreter;
22use gradient_interpreter::GradientInterpreter;
23mod autodiff;
24use autodiff::{DerivativeWorkspace, ReverseDerivativeWorkspace};
25mod cache;
26#[cfg(feature = "jit")]
27pub(crate) use cache::{CacheDescriptor, JitDescriptorSet};
28pub use cache::{CpuBatchCache, CpuCachedBatch, CpuCachedDataset, CpuPreparedDataset};
29mod layout;
30use layout::{Value, matrix_at, matrix_values_row_major, scalar_at, vector_at};
31
32#[cfg(feature = "jit")]
33use crate::jit::{JitGradientKernel, JitScalarKernel};
34
35mod scalar;
36use scalar::{SCALAR_BLOCK_SIZE, ScalarEvaluationPlan, ScalarEventWorkspace, ScalarExecutor};
37mod planning;
38use planning::GradientExecutor;
39mod evaluation;
40use evaluation::F32KernelInput;
41mod prepared;
42mod reduction;
43
44pub trait EventLookup {
46 fn scalar(&self, name: &str) -> Option<f64>;
48
49 fn p4_component(&self, name: &str, component: P4Component) -> Option<f64> {
51 let key = format!("{}.{}", name, component.label());
52 self.scalar(&key)
53 }
54}
55
56impl<F> EventLookup for F
57where
58 F: for<'a> Fn(&'a str) -> Option<f64>,
59{
60 fn scalar(&self, name: &str) -> Option<f64> {
61 self(name)
62 }
63}
64
65impl EventLookup for HashMap<String, f64> {
66 fn scalar(&self, name: &str) -> Option<f64> {
67 self.get(name).copied()
68 }
69}
70
71#[derive(Clone, Debug, Default)]
73pub struct CpuBackend;
74
75#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
77pub enum CpuExecutionMode {
78 #[default]
80 Auto,
81 Interpreter,
83}
84
85#[derive(Clone, Debug)]
87pub struct CpuPlan {
88 pub(super) precision: Precision,
89 pub(super) graph: ExprGraph,
90 pub(super) required_event_scalars: Vec<String>,
91 pub(super) params: ParamLayout,
92 pub(in crate::cpu) parameter_slots: Vec<Option<ParamId>>,
93 pub(super) autodiff: AutodiffPlan,
94 pub(super) cache_plan: CachePlan,
95 pub(super) cache_slots: Vec<Option<usize>>,
96 pub(super) cached_evaluation_nodes: Vec<ExprId>,
97 pub(super) cached_value_slots: Vec<Option<usize>>,
98 pub(super) scalar_kernel: Option<ScalarKernelIr>,
99 pub(in crate::cpu) scalar_executor: Option<ScalarExecutor>,
100 #[cfg_attr(not(feature = "jit"), allow(dead_code))]
101 pub(in crate::cpu) gradient_executor: GradientExecutor,
102 pub(in crate::cpu) f32_gradient_fallback_real: Option<GradientKernelIr>,
105 pub(super) f32_gradient_fallback_imag: Option<GradientKernelIr>,
106 pub(super) cache_materialization_nodes: Vec<ExprId>,
107 pub(super) solve_components: Vec<Option<SolveComponentPlan>>,
108 pub(super) solve_rhs_elements: Vec<Option<Vec<ExprId>>>,
109 pub(in crate::cpu) solve_row_matrices: Vec<SolveRowMatrixPlan>,
110 pub(super) solve_row_keys: Vec<(ExprId, usize, usize)>,
111 pub(super) factor_matrix_slots: Vec<Option<usize>>,
112 pub(super) factor_matrices: Vec<(ExprId, usize)>,
113 pub(super) constant_factor_slots: Vec<Option<usize>>,
114 pub(super) constant_factors: Vec<Arc<OnceLock<DynamicLu>>>,
115}
116
117impl CpuBackend {
118 pub fn prepare_for_execution(
125 &self,
126 model: &CompiledModel,
127 execution: &Execution,
128 ) -> RuntimeResult<CpuPlan> {
129 let mode = match execution.jit_policy() {
130 JitPolicy::Auto | JitPolicy::Enabled => CpuExecutionMode::Auto,
131 JitPolicy::Disabled => CpuExecutionMode::Interpreter,
132 };
133 let plan = self
134 .prepare_with_modes_precision(
135 model,
136 execution.autodiff_mode(),
137 mode,
138 execution.precision(),
139 )
140 .map_err(|error| RuntimeError::Data(error.to_string()))?;
141 if execution.precision() == Precision::F32 && !plan.supports_f32_scalar_execution() {
142 return Err(crate::ExecutionError::UnsupportedCpuF32Model.into());
143 }
144 Ok(plan)
145 }
146
147 pub fn prepare(&self, model: &CompiledModel) -> CpuPlan {
154 self.prepare_with_modes(model, AutodiffMode::Forward, CpuExecutionMode::Auto)
155 .expect("forward autodiff supports every compiled expression node")
156 }
157
158 pub fn prepare_with_execution_mode(
165 &self,
166 model: &CompiledModel,
167 execution_mode: CpuExecutionMode,
168 ) -> CpuPlan {
169 self.prepare_with_modes(model, AutodiffMode::Forward, execution_mode)
170 .expect("forward autodiff supports every compiled expression node")
171 }
172
173 pub fn prepare_with_autodiff_mode(
180 &self,
181 model: &CompiledModel,
182 mode: AutodiffMode,
183 ) -> AutodiffResult<CpuPlan> {
184 self.prepare_with_modes(model, mode, CpuExecutionMode::Auto)
185 }
186
187 pub fn prepare_with_modes(
194 &self,
195 model: &CompiledModel,
196 autodiff_mode: AutodiffMode,
197 execution_mode: CpuExecutionMode,
198 ) -> AutodiffResult<CpuPlan> {
199 self.prepare_with_modes_precision(model, autodiff_mode, execution_mode, Precision::F64)
200 }
201}
202
203#[derive(Clone, Debug, PartialEq)]
205pub struct ValueGradient {
206 value: Complex64,
207 gradient: Vec<Complex64>,
208}
209
210#[derive(Copy, Clone, Debug, PartialEq)]
211pub struct PreparedDatasetStats {
213 pub(super) local_events: usize,
214 pub(super) global_events: usize,
215 pub(super) local_batches: usize,
216 pub(super) sum_weights: f64,
217 pub(super) resident_bytes: usize,
218 pub(super) storage: CacheStorage,
219}
220
221#[derive(Clone, Debug, PartialEq)]
223pub struct ReductionEvaluation {
224 value: f64,
225 gradient: Vec<f64>,
226}
227
228impl ReductionEvaluation {
229 #[cfg(feature = "wgpu")]
230 pub(crate) fn new(value: f64, gradient: Vec<f64>) -> Self {
231 Self { value, gradient }
232 }
233
234 pub fn value(&self) -> f64 {
236 self.value
237 }
238
239 pub fn gradient(&self) -> &[f64] {
241 &self.gradient
242 }
243
244 pub fn into_parts(self) -> (f64, Vec<f64>) {
246 (self.value, self.gradient)
247 }
248}
249
250impl ValueGradient {
251 pub fn value(&self) -> Complex64 {
253 self.value
254 }
255
256 pub fn gradient(&self) -> &[Complex64] {
258 &self.gradient
259 }
260
261 pub fn into_parts(self) -> (Complex64, Vec<Complex64>) {
263 (self.value, self.gradient)
264 }
265}
266
267impl CpuPlan {
268 pub(in crate::cpu) fn scalar_interpreter_plan(&self) -> Option<&ScalarEvaluationPlan> {
269 match (&self.scalar_kernel, &self.scalar_executor) {
270 (Some(_), Some(ScalarExecutor::Interpreter(plan))) => Some(plan),
271 #[cfg(feature = "jit")]
272 (Some(_), Some(ScalarExecutor::Jit(_))) => None,
273 (Some(_), None) | (None, None) => None,
274 (None, Some(_)) => unreachable!("executor requires kernel IR"),
275 }
276 }
277
278 #[cfg(feature = "jit")]
279 pub(super) fn scalar_jit_kernel(&self) -> Option<&JitScalarKernel> {
280 match (&self.scalar_kernel, &self.scalar_executor) {
281 (Some(_), Some(ScalarExecutor::Jit(kernel))) => Some(kernel),
282 (Some(_), Some(ScalarExecutor::Interpreter(_))) | (Some(_), None) | (None, None) => {
283 None
284 }
285 (None, Some(_)) => unreachable!("executor requires kernel IR"),
286 }
287 }
288
289 #[cfg(feature = "jit")]
290 pub(in crate::cpu) fn gradient_jit_kernel(&self) -> Option<&JitGradientKernel> {
291 match &self.gradient_executor {
292 GradientExecutor::Jit(kernel) => Some(kernel),
293 GradientExecutor::Interpreter(_) => None,
294 }
295 }
296
297 pub(in crate::cpu) fn gradient_interpreter(&self) -> Option<&GradientInterpreter> {
298 match &self.gradient_executor {
299 GradientExecutor::Interpreter(interpreter) => interpreter.as_ref(),
300 #[cfg(feature = "jit")]
301 GradientExecutor::Jit(_) => None,
302 }
303 }
304
305 pub fn parameter_count(&self) -> usize {
307 self.params.len()
308 }
309
310 pub fn free_parameter_count(&self) -> usize {
312 self.params.n_free()
313 }
314
315 pub fn cache_plan(&self) -> &CachePlan {
317 &self.cache_plan
318 }
319
320 pub fn required_event_scalars(&self) -> &[String] {
322 &self.required_event_scalars
323 }
324
325 pub fn evaluate(&self, params: &ParamValues) -> RuntimeResult<Complex64> {
332 self.evaluate_inner(params, None)
333 }
334
335 pub fn evaluate_with_event(
342 &self,
343 params: &ParamValues,
344 event: &impl EventLookup,
345 ) -> RuntimeResult<Complex64> {
346 self.evaluate_inner(params, Some(event))
347 }
348
349 pub fn evaluate_with_event_and_gradient(
356 &self,
357 params: &ParamValues,
358 event: &impl EventLookup,
359 ) -> RuntimeResult<ValueGradient> {
360 if self.precision == Precision::F32 {
361 return self.evaluate_f32_gradient(params, F32KernelInput::Event(event));
362 }
363 self.require_f64_gradient()?;
364 let values = self.evaluate_values(params, Some(event))?;
365 self.value_gradient(values, None)
366 }
367
368 pub fn evaluate_cache_row(
375 &self,
376 params: &ParamValues,
377 cache: &CpuBatchCache,
378 row: usize,
379 ) -> RuntimeResult<Complex64> {
380 self.check_batch_cache(cache)?;
381 self.evaluate_cache_row_unchecked(params, cache, row)
382 }
383}
384
385impl CpuPlan {
386 pub fn evaluate_cache_row_with_gradient(
393 &self,
394 params: &ParamValues,
395 cache: &CpuBatchCache,
396 row: usize,
397 ) -> RuntimeResult<ValueGradient> {
398 self.check_batch_cache(cache)?;
399 self.evaluate_cache_row_with_gradient_unchecked(params, cache, row)
400 }
401
402 pub fn evaluate_batch(
408 &self,
409 params: &ParamValues,
410 batch: &EventBatch,
411 ) -> RuntimeResult<Vec<Complex64>> {
412 let cache = self.cache_event_batch(batch)?;
413 self.evaluate_cache(params, &cache)
414 }
415
416 pub(crate) fn evaluate_batch_outputs(
420 &self,
421 params: &ParamValues,
422 batch: &EventBatch,
423 outputs: &[ExprId],
424 ) -> RuntimeResult<Vec<Vec<Complex64>>> {
425 let cache = self.cache_event_batch(batch)?;
426 let root = self.graph.root();
427 let root_elements = match self.graph.node(root) {
428 Some(laddu_expr::ExprNode::Vector { elements }) => elements,
429 _ => {
430 return Err(RuntimeError::InvalidShape {
431 index: root.index(),
432 message: "multi-output evaluation requires a vector root".into(),
433 });
434 }
435 };
436 let positions = outputs
437 .iter()
438 .map(|output| {
439 root_elements
440 .iter()
441 .position(|element| element == output)
442 .ok_or_else(|| RuntimeError::InvalidShape {
443 index: output.index(),
444 message: "query output is not a vector root element".into(),
445 })
446 })
447 .collect::<RuntimeResult<Vec<_>>>()?;
448 let mut values = outputs
449 .iter()
450 .map(|_| Vec::with_capacity(cache.len()))
451 .collect::<Vec<_>>();
452 for row in 0..cache.len() {
453 let evaluated = self.evaluate_values_from_cache(params, &cache, row)?;
454 let row_values = self.cached_vector_at(&evaluated, root)?;
455 for (column, position) in values.iter_mut().zip(&positions) {
456 column.push(row_values[*position]);
457 }
458 }
459 Ok(values)
460 }
461
462 pub fn evaluate_batch_with_gradient(
469 &self,
470 params: &ParamValues,
471 batch: &EventBatch,
472 ) -> RuntimeResult<Vec<ValueGradient>> {
473 let cache = self.cache_event_batch(batch)?;
474 self.evaluate_cache_with_gradient(params, &cache)
475 }
476
477 pub(in crate::cpu) fn value_gradient(
478 &self,
479 values: Vec<Value>,
480 cached_factors: Option<(&CpuBatchCache, usize)>,
481 ) -> RuntimeResult<ValueGradient> {
482 let value = if cached_factors.is_some() {
483 self.cached_scalar_at(&values, self.graph.root())?
484 } else {
485 scalar_at(&values, self.graph.root().index())?
486 };
487 let gradient = match self.autodiff.mode() {
488 AutodiffMode::Auto => unreachable!("autodiff mode is resolved during preparation"),
489 AutodiffMode::Forward => {
490 DerivativeWorkspace::new(self, &values, cached_factors).gradient()?
491 }
492 AutodiffMode::Reverse => {
493 ReverseDerivativeWorkspace::new(self, &values, cached_factors).gradient()?
494 }
495 };
496 Ok(ValueGradient { value, gradient })
497 }
498}
499
500pub(super) type DynamicLu = LU<Complex64, Dyn, Dyn>;
501
502#[cfg(test)]
503#[path = "cpu/tests/mod.rs"]
504mod tests;