1use laddu_compile::{ReductionPlan, ReductionTransform};
2#[cfg(test)]
3use laddu_data::data::accurate::AccurateComplex64;
4use laddu_data::data::accurate::AccurateF64;
5use laddu_data::{LadduDataResult, data::EventBatch};
6use laddu_expr::parameters::ParamValues;
7use laddu_memory::MemoryLease;
8use num::complex::Complex64;
9use rayon::prelude::*;
10
11use crate::execution::Execution;
12#[cfg(feature = "jit")]
13use crate::jit::{JitGradientKernel, JitScalarKernel};
14
15use super::{
16 CpuCachedBatch, CpuCachedDataset, CpuPlan, CpuPreparedDataset, F32KernelInput, Precision,
17 ReductionEvaluation, RuntimeError, RuntimeResult, SCALAR_BLOCK_SIZE, ScalarEventWorkspace,
18 ValueGradient,
19};
20
21struct RealGradientAccumulator {
22 value: AccurateF64,
23 gradient: Vec<AccurateF64>,
24}
25
26struct PreparedBatchStream<'a> {
32 source: PreparedBatchSource<'a>,
33 plan: &'a CpuPlan,
34}
35
36enum PreparedBatchSource<'a> {
37 Resident(std::slice::Iter<'a, CpuCachedBatch>),
38 Streaming {
39 batches: Box<dyn Iterator<Item = LadduDataResult<EventBatch>> + Send + 'a>,
40 _memory: MemoryLease,
41 },
42}
43
44enum PreparedBatch<'a> {
45 Borrowed(&'a CpuCachedBatch),
46 Owned(CpuCachedBatch),
47}
48
49type PreparedManyEvaluation = (Vec<Vec<Complex64>>, Option<Vec<f64>>);
50
51impl<'a> PreparedBatch<'a> {
52 fn cached(&self) -> &CpuCachedBatch {
53 match self {
54 Self::Borrowed(batch) => batch,
55 Self::Owned(batch) => batch,
56 }
57 }
58}
59
60impl<'a> PreparedBatchStream<'a> {
61 fn prepare(
62 plan: &'a CpuPlan,
63 execution: &Execution,
64 dataset: &'a CpuPreparedDataset,
65 ) -> RuntimeResult<Self> {
66 let source = match dataset {
67 CpuPreparedDataset::Resident { dataset, .. } => {
68 PreparedBatchSource::Resident(dataset.batches().iter())
69 }
70 CpuPreparedDataset::Streaming {
71 dataset,
72 read_plan,
73 transient_bytes,
74 ..
75 } => {
76 let memory = execution
77 .host_memory()
78 .reserve(*transient_bytes)
79 .map_err(RuntimeError::from)?;
80 let batches = dataset
81 .stream_with_plan(*read_plan)
82 .map_err(|error| RuntimeError::Data(error.to_string()))?;
83 PreparedBatchSource::Streaming {
84 batches,
85 _memory: memory,
86 }
87 }
88 };
89 Ok(Self { source, plan })
90 }
91
92 fn next(&mut self) -> RuntimeResult<Option<PreparedBatch<'a>>> {
93 match &mut self.source {
94 PreparedBatchSource::Resident(batches) => {
95 Ok(batches.next().map(PreparedBatch::Borrowed))
96 }
97 PreparedBatchSource::Streaming { batches, .. } => batches
98 .next()
99 .transpose()
100 .map_err(|error| RuntimeError::Data(error.to_string()))?
101 .map(|batch| {
102 self.plan
103 .cache_event_batch(&batch)
104 .map(|cache| PreparedBatch::Owned(CpuCachedBatch { cache }))
105 })
106 .transpose(),
107 }
108 }
109}
110
111struct ValueReducer {
112 total: AccurateF64,
113}
114
115impl ValueReducer {
116 fn new() -> Self {
117 Self {
118 total: AccurateF64::zero(),
119 }
120 }
121
122 fn consume(
123 &mut self,
124 plan: &CpuPlan,
125 execution: &Execution,
126 params: &ParamValues,
127 batch: &CpuCachedBatch,
128 reduction: ReductionPlan,
129 ) -> RuntimeResult<()> {
130 let value = if execution.is_parallel() && batch.len().div_ceil(SCALAR_BLOCK_SIZE) >= 2 {
131 execution.install(|| {
132 plan.par_try_weighted_sum_batch(params, batch, |value| {
133 plan.apply_reduction(reduction, value)
134 })
135 })?
136 } else {
137 plan.try_weighted_sum_batch(params, batch, |value| {
138 plan.apply_reduction(reduction, value)
139 })?
140 };
141 self.total.push(value);
142 Ok(())
143 }
144
145 fn finish(self) -> f64 {
146 self.total.finish()
147 }
148}
149
150struct GradientReducer {
151 total: RealGradientAccumulator,
152}
153
154impl GradientReducer {
155 fn new(parameter_count: usize) -> Self {
156 Self {
157 total: RealGradientAccumulator::zero(parameter_count),
158 }
159 }
160
161 fn consume<F>(
162 &mut self,
163 plan: &CpuPlan,
164 execution: &Execution,
165 params: &ParamValues,
166 batch: &CpuCachedBatch,
167 transform: &F,
168 ) -> RuntimeResult<()>
169 where
170 F: Fn(Complex64) -> RuntimeResult<(f64, f64)> + Send + Sync,
171 {
172 let mut transform = transform;
173 let (value, gradient) =
174 if execution.is_parallel() && batch.len().div_ceil(SCALAR_BLOCK_SIZE) >= 2 {
175 execution.install(|| {
176 plan.par_try_weighted_real_sum_with_gradient_batch(params, batch, &transform)
177 })?
178 } else {
179 plan.try_weighted_real_sum_with_gradient_batch(params, batch, &mut transform)?
180 };
181 self.total.value.push(value);
182 for (sum, partial) in self.total.gradient.iter_mut().zip(gradient) {
183 sum.push(partial);
184 }
185 Ok(())
186 }
187
188 fn finish(self) -> (f64, Vec<f64>) {
189 self.total.finish()
190 }
191}
192
193impl RealGradientAccumulator {
194 fn zero(parameter_count: usize) -> Self {
195 Self {
196 value: AccurateF64::zero(),
197 gradient: (0..parameter_count).map(|_| AccurateF64::zero()).collect(),
198 }
199 }
200
201 fn push(&mut self, weight: f64, value: f64, derivative: f64, model_gradient: &[Complex64]) {
202 self.value.push(weight * value);
203 for (sum, model_derivative) in self.gradient.iter_mut().zip(model_gradient) {
204 sum.push(weight * derivative * model_derivative.re);
205 }
206 }
207
208 fn push_f32(&mut self, weight: f64, value: f64, derivative: f64, model_gradient: &[f32]) {
209 self.value.push(weight * value);
210 for (sum, model_derivative) in self.gradient.iter_mut().zip(model_gradient) {
211 sum.push(weight * derivative * f64::from(*model_derivative));
212 }
213 }
214
215 fn merge(&mut self, other: Self) {
216 self.value.merge(other.value);
217 for (target, source) in self.gradient.iter_mut().zip(other.gradient) {
218 target.merge(source);
219 }
220 }
221
222 fn finish(self) -> (f64, Vec<f64>) {
223 (
224 self.value.finish(),
225 self.gradient.into_iter().map(AccurateF64::finish).collect(),
226 )
227 }
228}
229
230impl CpuPlan {
231 pub(crate) fn visit_prepared_dataset_many<F>(
232 &self,
233 execution: &Execution,
234 parameter_sets: &[(&ParamValues, &str)],
235 dataset: &CpuPreparedDataset,
236 reduction: Option<ReductionPlan>,
237 pool_installed: bool,
238 mut consume: F,
239 ) -> RuntimeResult<Vec<f64>>
240 where
241 F: FnMut(usize, usize, &[Complex64]) -> RuntimeResult<()>,
242 {
243 let local = (|| {
244 let mut stream = PreparedBatchStream::prepare(self, execution, dataset)?;
245 let mut offset = 0;
246 let mut sums = reduction.map(|_| {
247 parameter_sets
248 .iter()
249 .map(|_| AccurateF64::zero())
250 .collect::<Vec<_>>()
251 });
252 while let Some(batch) = stream.next()? {
253 let batch = batch.cached();
254 for (index, &(parameters, context)) in parameter_sets.iter().enumerate() {
255 let values = self
256 .evaluate_prepared_batch(execution, parameters, batch, pool_installed)
257 .map_err(|error| {
258 RuntimeError::Parameter(format!("{context} evaluation failed: {error}"))
259 })?;
260 if let (Some(reduction), Some(sums)) = (reduction, sums.as_mut()) {
261 for (weight, value) in batch.weights().iter().zip(&values) {
262 let reduced = reduction.apply(*value).map_err(|error| {
263 RuntimeError::Parameter(format!(
264 "{context} reduction failed: {error}"
265 ))
266 })?;
267 sums[index].push(*weight * reduced.value());
268 }
269 }
270 consume(offset, index, &values)?;
271 }
272 offset += batch.weights().len();
273 }
274 Ok(sums
275 .unwrap_or_default()
276 .into_iter()
277 .map(AccurateF64::finish)
278 .collect::<Vec<_>>())
279 })();
280 if !execution.all_succeeded(local.is_ok()) {
281 return local.and(Err(RuntimeError::DistributedPeerFailure));
282 }
283 Ok(local?
284 .into_iter()
285 .map(|sum| execution.sum_f64(sum))
286 .collect())
287 }
288
289 fn evaluate_prepared_batch(
290 &self,
291 execution: &Execution,
292 params: &ParamValues,
293 batch: &CpuCachedBatch,
294 pool_installed: bool,
295 ) -> RuntimeResult<Vec<Complex64>> {
296 let block_count = batch.len().div_ceil(SCALAR_BLOCK_SIZE);
297 if !execution.is_parallel() || block_count < 2 {
298 return self.evaluate_cache(params, batch.cache());
299 }
300 self.check_batch_cache(batch.cache())?;
301 let invariant = self.scalar_invariant_values(params)?;
302 #[cfg(feature = "jit")]
303 let jit_cache = self
304 .scalar_jit_kernel()
305 .map(|_| JitScalarKernel::prepare_cache(batch.cache()));
306 let evaluate = || {
307 (0..block_count)
308 .into_par_iter()
309 .map(|block| {
310 let start = block * SCALAR_BLOCK_SIZE;
311 let end = (start + SCALAR_BLOCK_SIZE).min(batch.len());
312 let mut workspace = ScalarEventWorkspace::default();
313 let mut output = Vec::with_capacity(end - start);
314 self.evaluate_cache_block_prepared(
315 params,
316 batch.cache(),
317 start,
318 end,
319 invariant.as_ref(),
320 &mut workspace,
321 &mut output,
322 #[cfg(feature = "jit")]
323 jit_cache.as_ref(),
324 )?;
325 Ok(output)
326 })
327 .collect::<RuntimeResult<Vec<_>>>()
328 };
329 let blocks = if pool_installed {
330 evaluate()?
331 } else {
332 execution.install(evaluate)?
333 };
334 Ok(blocks.into_iter().flatten().collect())
335 }
336
337 pub(crate) fn evaluate_prepared_dataset_many(
338 &self,
339 execution: &Execution,
340 params: &[ParamValues],
341 dataset: &CpuPreparedDataset,
342 ) -> RuntimeResult<Vec<Vec<Complex64>>> {
343 self.evaluate_prepared_dataset_many_local(execution, params, dataset, None)
344 .map(|(values, _)| values)
345 }
346
347 pub(crate) fn evaluate_prepared_dataset_many_with_reduction(
348 &self,
349 execution: &Execution,
350 params: &[ParamValues],
351 dataset: &CpuPreparedDataset,
352 reduction: ReductionPlan,
353 ) -> RuntimeResult<(Vec<Vec<Complex64>>, Vec<f64>)> {
354 let local =
355 self.evaluate_prepared_dataset_many_local(execution, params, dataset, Some(reduction));
356 if !execution.all_succeeded(local.is_ok()) {
357 return local.and(Err(RuntimeError::DistributedPeerFailure));
358 }
359 let (output, sums) = local?;
360 Ok((
361 output,
362 sums.unwrap_or_default()
363 .into_iter()
364 .map(|sum| execution.sum_f64(sum))
365 .collect(),
366 ))
367 }
368
369 fn evaluate_prepared_dataset_many_local(
370 &self,
371 execution: &Execution,
372 params: &[ParamValues],
373 dataset: &CpuPreparedDataset,
374 reduction: Option<ReductionPlan>,
375 ) -> RuntimeResult<PreparedManyEvaluation> {
376 let mut stream = PreparedBatchStream::prepare(self, execution, dataset)?;
377 let mut output = params
378 .iter()
379 .map(|_| Vec::with_capacity(dataset.stats().local_events()))
380 .collect::<Vec<_>>();
381 let mut sums = reduction.map(|_| {
382 params
383 .iter()
384 .map(|_| AccurateF64::zero())
385 .collect::<Vec<_>>()
386 });
387 while let Some(batch) = stream.next()? {
388 let batch = batch.cached();
389 for (index, (parameters, values)) in params.iter().zip(&mut output).enumerate() {
390 let batch_values = self.evaluate_cache(parameters, batch.cache())?;
391 if let (Some(reduction), Some(sums)) = (reduction, sums.as_mut()) {
392 for (weight, value) in batch.weights().iter().zip(&batch_values) {
393 sums[index].push(*weight * reduction.apply(*value)?.value());
394 }
395 }
396 values.extend(batch_values);
397 }
398 }
399 Ok((
400 output,
401 sums.map(|sums| sums.into_iter().map(AccurateF64::finish).collect()),
402 ))
403 }
404
405 pub fn reduce(
412 &self,
413 execution: &Execution,
414 params: &ParamValues,
415 dataset: &CpuPreparedDataset,
416 reduction: ReductionPlan,
417 ) -> RuntimeResult<f64> {
418 let local = (|| {
419 let mut stream = PreparedBatchStream::prepare(self, execution, dataset)?;
420 let mut reducer = ValueReducer::new();
421 while let Some(batch) = stream.next()? {
422 reducer.consume(self, execution, params, batch.cached(), reduction)?;
423 }
424 Ok(reducer.finish())
425 })();
426 if !execution.all_succeeded(local.is_ok()) {
427 return local.and(Err(RuntimeError::DistributedPeerFailure));
428 }
429 Ok(execution.sum_f64(local?))
430 }
431
432 pub fn reduce_with_gradient(
440 &self,
441 execution: &Execution,
442 params: &ParamValues,
443 dataset: &CpuPreparedDataset,
444 reduction: ReductionPlan,
445 ) -> RuntimeResult<ReductionEvaluation> {
446 let local = (|| {
447 let mut stream = PreparedBatchStream::prepare(self, execution, dataset)?;
448 let mut reducer = GradientReducer::new(self.free_parameter_count());
449 let transform = |value| {
450 reduction
451 .apply(value)
452 .map(|output| output.into_parts())
453 .map_err(RuntimeError::from)
454 };
455 while let Some(batch) = stream.next()? {
456 reducer.consume(self, execution, params, batch.cached(), &transform)?;
457 }
458 Ok(reducer.finish())
459 })();
460 if !execution.all_succeeded(local.is_ok()) {
461 return local.and(Err(RuntimeError::DistributedPeerFailure));
462 }
463 let (value, gradient) = local?;
464 let value = execution.sum_f64(value);
465 let gradient = execution.sum_slice(&gradient);
466 Ok(ReductionEvaluation { value, gradient })
467 }
468
469 pub fn evaluate_cached_dataset(
476 &self,
477 params: &ParamValues,
478 dataset: &CpuCachedDataset,
479 ) -> RuntimeResult<Vec<Complex64>> {
480 let total_len = dataset.batches.iter().map(CpuCachedBatch::len).sum();
481 let mut out = Vec::with_capacity(total_len);
482 let invariant = self.scalar_invariant_values(params)?;
483 let mut workspace = ScalarEventWorkspace::default();
484 for batch in &dataset.batches {
485 self.check_batch_cache(batch.cache())?;
486 for row in 0..batch.len() {
487 out.push(self.evaluate_cache_row_prepared(
488 params,
489 batch.cache(),
490 row,
491 invariant.as_ref(),
492 &mut workspace,
493 )?);
494 }
495 }
496 Ok(out)
497 }
498
499 pub fn evaluate_cached_dataset_with_gradient(
506 &self,
507 params: &ParamValues,
508 dataset: &CpuCachedDataset,
509 ) -> RuntimeResult<Vec<ValueGradient>> {
510 let total_len = dataset.batches.iter().map(CpuCachedBatch::len).sum();
511 let mut out = Vec::with_capacity(total_len);
512 for batch in &dataset.batches {
513 out.extend(self.evaluate_cache_with_gradient(params, batch.cache())?);
514 }
515 Ok(out)
516 }
517
518 fn try_weighted_sum_batch<E, F>(
519 &self,
520 params: &ParamValues,
521 batch: &CpuCachedBatch,
522 mut f: F,
523 ) -> Result<f64, E>
524 where
525 E: From<RuntimeError>,
526 F: FnMut(Complex64) -> Result<f64, E>,
527 {
528 self.check_batch_cache(batch.cache())?;
529 let invariant = self.scalar_invariant_values(params)?;
530 let mut workspace = ScalarEventWorkspace::default();
531 let mut sum = AccurateF64::zero();
532 for row in 0..batch.len() {
533 let value = self.evaluate_cache_row_prepared(
534 params,
535 batch.cache(),
536 row,
537 invariant.as_ref(),
538 &mut workspace,
539 )?;
540 sum.push(batch.weights()[row] * f(value)?);
541 }
542 Ok(sum.finish())
543 }
544
545 fn par_try_weighted_sum_batch<E, F>(
546 &self,
547 params: &ParamValues,
548 batch: &CpuCachedBatch,
549 f: F,
550 ) -> Result<f64, E>
551 where
552 E: From<RuntimeError> + Send,
553 F: Fn(Complex64) -> Result<f64, E> + Send + Sync,
554 {
555 self.check_batch_cache(batch.cache())?;
556 let invariant = self.scalar_invariant_values(params)?;
557 #[cfg(feature = "jit")]
558 let jit_cache = self
559 .scalar_jit_kernel()
560 .map(|_| JitScalarKernel::prepare_cache(batch.cache()));
561 let n_blocks = batch.len().div_ceil(SCALAR_BLOCK_SIZE);
562 let total = (0..n_blocks)
563 .into_par_iter()
564 .try_fold(
565 || {
566 (
567 AccurateF64::zero(),
568 ScalarEventWorkspace::default(),
569 Vec::new(),
570 )
571 },
572 |(mut acc, mut workspace, mut output), block| {
573 let start = block * SCALAR_BLOCK_SIZE;
574 let end = (start + SCALAR_BLOCK_SIZE).min(batch.len());
575 self.evaluate_cache_block_prepared(
576 params,
577 batch.cache(),
578 start,
579 end,
580 invariant.as_ref(),
581 &mut workspace,
582 &mut output,
583 #[cfg(feature = "jit")]
584 jit_cache.as_ref(),
585 )?;
586 for (lane, value) in output.iter().copied().enumerate() {
587 acc.push(batch.weights()[start + lane] * f(value)?);
588 }
589 Ok::<_, E>((acc, workspace, output))
590 },
591 )
592 .try_reduce(
593 || {
594 (
595 AccurateF64::zero(),
596 ScalarEventWorkspace::default(),
597 Vec::new(),
598 )
599 },
600 |(mut lhs, workspace, output), (rhs, _, _)| {
601 lhs.merge(rhs);
602 Ok::<_, E>((lhs, workspace, output))
603 },
604 )?;
605 Ok(total.0.finish())
606 }
607
608 fn try_weighted_real_sum_with_gradient_batch<E, F>(
609 &self,
610 params: &ParamValues,
611 batch: &CpuCachedBatch,
612 transform: &mut F,
613 ) -> Result<(f64, Vec<f64>), E>
614 where
615 E: From<RuntimeError>,
616 F: FnMut(Complex64) -> Result<(f64, f64), E>,
617 {
618 self.check_batch_cache(batch.cache())?;
619 #[cfg(feature = "jit")]
620 if let (Some(value_kernel), Some(gradient_kernel)) =
621 (self.scalar_jit_kernel(), self.gradient_jit_kernel())
622 {
623 return self.try_weighted_real_sum_with_jit_gradient_batch(
624 params,
625 batch,
626 transform,
627 value_kernel,
628 gradient_kernel,
629 );
630 }
631 if self.precision != Precision::F32
632 && let Some(interpreter) = self.gradient_interpreter()
633 && let Some(mut state) = interpreter.prepare_real_blocks(params)?
634 {
635 let output_count = state.output_count();
636 let mut total = RealGradientAccumulator::zero(self.free_parameter_count());
637 for block in 0..batch.len().div_ceil(SCALAR_BLOCK_SIZE) {
638 let start = block * SCALAR_BLOCK_SIZE;
639 let end = (start + SCALAR_BLOCK_SIZE).min(batch.len());
640 let outputs = state.evaluate(batch.cache(), start, end)?;
641 for (lane, row) in outputs.chunks_exact(output_count).enumerate() {
642 let (value, derivative) = transform(row[0])?;
643 total.push(batch.weights()[start + lane], value, derivative, &row[1..]);
644 }
645 }
646 return Ok(total.finish());
647 }
648 if self.precision == Precision::F32
649 && let Some(ir) = self.f32_gradient_fallback_real.as_ref()
650 {
651 let mut total = RealGradientAccumulator::zero(self.free_parameter_count());
652 let mut gradient = Vec::new();
653 for row in 0..batch.len() {
654 let (value, model_gradient) = self.evaluate_f32_gradient_component_prepared(
655 ir,
656 params,
657 F32KernelInput::Cache(Some((batch.cache(), row))),
658 &mut gradient,
659 )?;
660 let (value, derivative) = transform(value)?;
661 total.push_f32(batch.weights()[row], value, derivative, model_gradient);
662 }
663 return Ok(total.finish());
664 }
665 let mut total = RealGradientAccumulator::zero(self.free_parameter_count());
666 for row in 0..batch.len() {
667 let evaluation =
668 self.evaluate_cache_row_with_gradient_unchecked(params, batch.cache(), row)?;
669 let (value, derivative) = transform(evaluation.value())?;
670 total.push(
671 batch.weights()[row],
672 value,
673 derivative,
674 evaluation.gradient(),
675 );
676 }
677 Ok(total.finish())
678 }
679
680 fn par_try_weighted_real_sum_with_gradient_batch<E, F>(
681 &self,
682 params: &ParamValues,
683 batch: &CpuCachedBatch,
684 transform: &F,
685 ) -> Result<(f64, Vec<f64>), E>
686 where
687 E: From<RuntimeError> + Send,
688 F: Fn(Complex64) -> Result<(f64, f64), E> + Send + Sync,
689 {
690 #[cfg(feature = "jit")]
691 if let (Some(value_kernel), Some(gradient_kernel)) =
692 (self.scalar_jit_kernel(), self.gradient_jit_kernel())
693 {
694 return self.par_try_weighted_real_sum_with_jit_gradient_batch(
695 params,
696 batch,
697 transform,
698 value_kernel,
699 gradient_kernel,
700 );
701 }
702 if self.precision != Precision::F32
703 && let Some(interpreter) = self.gradient_interpreter()
704 && let Some(state) = interpreter.prepare_real_blocks(params)?
705 {
706 let output_count = state.output_count();
707 let partial = (0..batch.len().div_ceil(SCALAR_BLOCK_SIZE))
708 .into_par_iter()
709 .try_fold(
710 || {
711 (
712 RealGradientAccumulator::zero(self.free_parameter_count()),
713 state.clone(),
714 )
715 },
716 |(mut accumulator, mut state), block| {
717 let start = block * SCALAR_BLOCK_SIZE;
718 let end = (start + SCALAR_BLOCK_SIZE).min(batch.len());
719 let outputs = state.evaluate(batch.cache(), start, end)?;
720 for (lane, row) in outputs.chunks_exact(output_count).enumerate() {
721 let (value, derivative) = transform(row[0])?;
722 accumulator.push(
723 batch.weights()[start + lane],
724 value,
725 derivative,
726 &row[1..],
727 );
728 }
729 Ok::<_, E>((accumulator, state))
730 },
731 )
732 .try_reduce(
733 || {
734 (
735 RealGradientAccumulator::zero(self.free_parameter_count()),
736 state.clone(),
737 )
738 },
739 |(mut lhs, state), (rhs, _)| {
740 lhs.merge(rhs);
741 Ok::<_, E>((lhs, state))
742 },
743 )?;
744 return Ok(partial.0.finish());
745 }
746 if self.precision == Precision::F32
747 && let Some(ir) = self.f32_gradient_fallback_real.as_ref()
748 {
749 let partial = (0..batch.len())
750 .into_par_iter()
751 .try_fold(
752 || {
753 (
754 RealGradientAccumulator::zero(self.free_parameter_count()),
755 Vec::new(),
756 )
757 },
758 |(mut accumulator, mut gradient), row| {
759 let (value, model_gradient) = self
760 .evaluate_f32_gradient_component_prepared(
761 ir,
762 params,
763 F32KernelInput::Cache(Some((batch.cache(), row))),
764 &mut gradient,
765 )?;
766 let (value, derivative) = transform(value)?;
767 accumulator.push_f32(
768 batch.weights()[row],
769 value,
770 derivative,
771 model_gradient,
772 );
773 Ok::<_, E>((accumulator, gradient))
774 },
775 )
776 .try_reduce(
777 || {
778 (
779 RealGradientAccumulator::zero(self.free_parameter_count()),
780 Vec::new(),
781 )
782 },
783 |(mut lhs, gradient), (rhs, _)| {
784 lhs.merge(rhs);
785 Ok::<_, E>((lhs, gradient))
786 },
787 )?;
788 return Ok(partial.0.finish());
789 }
790 let partial = (0..batch.len())
791 .into_par_iter()
792 .try_fold(
793 || RealGradientAccumulator::zero(self.free_parameter_count()),
794 |mut accumulator, row| {
795 let evaluation = self.evaluate_cache_row_with_gradient_unchecked(
796 params,
797 batch.cache(),
798 row,
799 )?;
800 let (value, derivative) = transform(evaluation.value())?;
801 accumulator.push(
802 batch.weights()[row],
803 value,
804 derivative,
805 evaluation.gradient(),
806 );
807 Ok::<_, E>(accumulator)
808 },
809 )
810 .try_reduce(
811 || RealGradientAccumulator::zero(self.free_parameter_count()),
812 |mut lhs, rhs| {
813 lhs.merge(rhs);
814 Ok::<_, E>(lhs)
815 },
816 )?;
817 Ok(partial.finish())
818 }
819
820 #[cfg(feature = "jit")]
821 fn try_weighted_real_sum_with_jit_gradient_batch<E, F>(
822 &self,
823 params: &ParamValues,
824 batch: &CpuCachedBatch,
825 transform: &mut F,
826 value_kernel: &JitScalarKernel,
827 gradient_kernel: &JitGradientKernel,
828 ) -> Result<(f64, Vec<f64>), E>
829 where
830 E: From<RuntimeError>,
831 F: FnMut(Complex64) -> Result<(f64, f64), E>,
832 {
833 let cache = JitScalarKernel::prepare_cache(batch.cache());
834 let mut total = RealGradientAccumulator::zero(self.free_parameter_count());
835 let mut values = Vec::new();
836 let mut tangents = Vec::new();
837 let mut derivatives = Vec::new();
838 for block in 0..batch.len().div_ceil(SCALAR_BLOCK_SIZE) {
839 let start = block * SCALAR_BLOCK_SIZE;
840 let end = (start + SCALAR_BLOCK_SIZE).min(batch.len());
841 value_kernel.evaluate_prepared(params, &cache, start, end, &mut values)?;
842 derivatives.clear();
843 derivatives.reserve(values.len());
844 for (lane, value) in values.iter().copied().enumerate() {
845 let (value, derivative) = transform(value)?;
846 total.value.push(batch.weights()[start + lane] * value);
847 derivatives.push(batch.weights()[start + lane] * derivative);
848 }
849 gradient_kernel.evaluate_prepared(params, &cache, start, end, 0, &mut tangents)?;
850 for (lane, factor) in derivatives.iter().enumerate() {
851 for free_index in 0..self.free_parameter_count() {
852 total.gradient[free_index]
853 .push(factor * tangents[lane * self.free_parameter_count() + free_index]);
854 }
855 }
856 }
857 Ok(total.finish())
858 }
859
860 #[cfg(feature = "jit")]
861 fn par_try_weighted_real_sum_with_jit_gradient_batch<E, F>(
862 &self,
863 params: &ParamValues,
864 batch: &CpuCachedBatch,
865 transform: &F,
866 value_kernel: &JitScalarKernel,
867 gradient_kernel: &JitGradientKernel,
868 ) -> Result<(f64, Vec<f64>), E>
869 where
870 E: From<RuntimeError> + Send,
871 F: Fn(Complex64) -> Result<(f64, f64), E> + Send + Sync,
872 {
873 let cache = JitScalarKernel::prepare_cache(batch.cache());
874 let partial = (0..batch.len().div_ceil(SCALAR_BLOCK_SIZE))
875 .into_par_iter()
876 .try_fold(
877 || {
878 (
879 RealGradientAccumulator::zero(self.free_parameter_count()),
880 Vec::new(),
881 Vec::new(),
882 Vec::new(),
883 )
884 },
885 |(mut accumulator, mut values, mut tangents, mut derivatives), block| {
886 let start = block * SCALAR_BLOCK_SIZE;
887 let end = (start + SCALAR_BLOCK_SIZE).min(batch.len());
888 value_kernel.evaluate_prepared(params, &cache, start, end, &mut values)?;
889 derivatives.clear();
890 for (lane, value) in values.iter().copied().enumerate() {
891 let (value, derivative) = transform(value)?;
892 let weight = batch.weights()[start + lane];
893 accumulator.value.push(weight * value);
894 derivatives.push(weight * derivative);
895 }
896 gradient_kernel.evaluate_prepared(
897 params,
898 &cache,
899 start,
900 end,
901 0,
902 &mut tangents,
903 )?;
904 for (lane, factor) in derivatives.iter().enumerate() {
905 for free_index in 0..self.free_parameter_count() {
906 accumulator.gradient[free_index].push(
907 factor * tangents[lane * self.free_parameter_count() + free_index],
908 );
909 }
910 }
911 Ok::<_, E>((accumulator, values, tangents, derivatives))
912 },
913 )
914 .try_reduce(
915 || {
916 (
917 RealGradientAccumulator::zero(self.free_parameter_count()),
918 Vec::new(),
919 Vec::new(),
920 Vec::new(),
921 )
922 },
923 |(mut lhs, values, tangents, derivatives), (rhs, _, _, _)| {
924 lhs.merge(rhs);
925 Ok::<_, E>((lhs, values, tangents, derivatives))
926 },
927 )?;
928 Ok(partial.0.finish())
929 }
930
931 #[cfg(test)]
932 fn try_weighted_sum_cached<E, F>(
933 &self,
934 params: &ParamValues,
935 dataset: &CpuCachedDataset,
936 mut f: F,
937 ) -> Result<f64, E>
938 where
939 E: From<RuntimeError>,
940 F: FnMut(Complex64) -> Result<f64, E>,
941 {
942 let mut sum = AccurateF64::zero();
943 for batch in dataset.batches() {
944 sum.push(self.try_weighted_sum_batch(params, batch, &mut f)?);
945 }
946 Ok(sum.finish())
947 }
948
949 #[cfg(test)]
950 pub(in crate::cpu) fn weighted_sum_cached<F>(
951 &self,
952 params: &ParamValues,
953 dataset: &CpuCachedDataset,
954 mut f: F,
955 ) -> RuntimeResult<f64>
956 where
957 F: FnMut(Complex64) -> f64,
958 {
959 self.try_weighted_sum_cached(params, dataset, |value| Ok(f(value)))
960 }
961
962 #[cfg(test)]
963 pub(in crate::cpu) fn try_weighted_real_sum_with_gradient_cached<E, F>(
964 &self,
965 params: &ParamValues,
966 dataset: &CpuCachedDataset,
967 mut transform: F,
968 ) -> Result<(f64, Vec<f64>), E>
969 where
970 E: From<RuntimeError>,
971 F: FnMut(Complex64) -> Result<(f64, f64), E>,
972 {
973 let mut total = RealGradientAccumulator::zero(self.free_parameter_count());
974 for batch in dataset.batches() {
975 let (value, gradient) =
976 self.try_weighted_real_sum_with_gradient_batch(params, batch, &mut transform)?;
977 total.value.push(value);
978 for (sum, partial) in total.gradient.iter_mut().zip(gradient) {
979 sum.push(partial);
980 }
981 }
982 Ok(total.finish())
983 }
984
985 #[cfg(test)]
986 fn try_weighted_complex_sum_cached<E, F>(
987 &self,
988 params: &ParamValues,
989 dataset: &CpuCachedDataset,
990 mut f: F,
991 ) -> Result<Complex64, E>
992 where
993 E: From<RuntimeError>,
994 F: FnMut(Complex64) -> Result<Complex64, E>,
995 {
996 let mut sum = Complex64::default();
997 let invariant = self.scalar_invariant_values(params)?;
998 let mut workspace = ScalarEventWorkspace::default();
999 for batch in dataset.batches() {
1000 self.check_batch_cache(batch.cache())?;
1001 for row in 0..batch.len() {
1002 let value = self.evaluate_cache_row_prepared(
1003 params,
1004 batch.cache(),
1005 row,
1006 invariant.as_ref(),
1007 &mut workspace,
1008 )?;
1009 sum += f(value)? * batch.weights()[row];
1010 }
1011 }
1012 Ok(sum)
1013 }
1014
1015 #[cfg(test)]
1016 pub(in crate::cpu) fn weighted_complex_sum_cached<F>(
1017 &self,
1018 params: &ParamValues,
1019 dataset: &CpuCachedDataset,
1020 mut f: F,
1021 ) -> RuntimeResult<Complex64>
1022 where
1023 F: FnMut(Complex64) -> Complex64,
1024 {
1025 self.try_weighted_complex_sum_cached(params, dataset, |value| Ok(f(value)))
1026 }
1027
1028 fn apply_reduction(&self, reduction: ReductionPlan, value: Complex64) -> RuntimeResult<f64> {
1029 if self.precision != Precision::F32 {
1030 return reduction
1031 .apply(value)
1032 .map(|output| output.value())
1033 .map_err(RuntimeError::from);
1034 }
1035 let real = value.re as f32;
1036 match reduction.transform() {
1037 ReductionTransform::Real => Ok(real as f64),
1038 ReductionTransform::PositiveReal if real > 0.0 => Ok(real as f64),
1039 ReductionTransform::LogPositiveReal if real > 0.0 => Ok(real.ln() as f64),
1040 ReductionTransform::PositiveReal | ReductionTransform::LogPositiveReal => reduction
1041 .apply(Complex64::from(real as f64))
1042 .map(|output| output.value())
1043 .map_err(RuntimeError::from),
1044 }
1045 }
1046
1047 #[cfg(test)]
1048 pub(crate) fn par_try_weighted_sum_cached<E, F>(
1049 &self,
1050 params: &ParamValues,
1051 dataset: &CpuCachedDataset,
1052 f: F,
1053 ) -> Result<f64, E>
1054 where
1055 E: From<RuntimeError> + Send,
1056 F: Fn(Complex64) -> Result<f64, E> + Send + Sync,
1057 {
1058 let mut total = AccurateF64::zero();
1059 for batch in dataset.batches() {
1060 total.push(self.par_try_weighted_sum_batch(params, batch, &f)?);
1061 }
1062 Ok(total.finish())
1063 }
1064
1065 #[cfg(test)]
1066 pub(crate) fn par_weighted_sum_cached<F>(
1067 &self,
1068 params: &ParamValues,
1069 dataset: &CpuCachedDataset,
1070 f: F,
1071 ) -> RuntimeResult<f64>
1072 where
1073 F: Fn(Complex64) -> f64 + Send + Sync,
1074 {
1075 self.par_try_weighted_sum_cached(params, dataset, |value| Ok(f(value)))
1076 }
1077
1078 #[cfg(test)]
1079 pub(in crate::cpu) fn par_try_weighted_real_sum_with_gradient_cached<E, F>(
1080 &self,
1081 params: &ParamValues,
1082 dataset: &CpuCachedDataset,
1083 transform: F,
1084 ) -> Result<(f64, Vec<f64>), E>
1085 where
1086 E: From<RuntimeError> + Send,
1087 F: Fn(Complex64) -> Result<(f64, f64), E> + Send + Sync,
1088 {
1089 let mut total = RealGradientAccumulator::zero(self.free_parameter_count());
1090 for batch in dataset.batches() {
1091 let (value, gradient) =
1092 self.par_try_weighted_real_sum_with_gradient_batch(params, batch, &transform)?;
1093 total.value.push(value);
1094 for (sum, partial) in total.gradient.iter_mut().zip(gradient) {
1095 sum.push(partial);
1096 }
1097 }
1098 Ok(total.finish())
1099 }
1100
1101 #[cfg(test)]
1102 pub(crate) fn par_try_weighted_complex_sum_cached<E, F>(
1103 &self,
1104 params: &ParamValues,
1105 dataset: &CpuCachedDataset,
1106 f: F,
1107 ) -> Result<Complex64, E>
1108 where
1109 E: From<RuntimeError> + Send,
1110 F: Fn(Complex64) -> Result<Complex64, E> + Send + Sync,
1111 {
1112 let mut total = AccurateComplex64::zero();
1113 let invariant = self.scalar_invariant_values(params)?;
1114 for batch in dataset.batches() {
1115 self.check_batch_cache(batch.cache())?;
1116 #[cfg(feature = "jit")]
1117 let jit_cache = self
1118 .scalar_jit_kernel()
1119 .map(|_| JitScalarKernel::prepare_cache(batch.cache()));
1120 let n_blocks = batch.len().div_ceil(SCALAR_BLOCK_SIZE);
1121 let partial = (0..n_blocks)
1122 .into_par_iter()
1123 .try_fold(
1124 || {
1125 (
1126 AccurateComplex64::zero(),
1127 ScalarEventWorkspace::default(),
1128 Vec::new(),
1129 )
1130 },
1131 |(mut acc, mut workspace, mut output), block| {
1132 let start = block * SCALAR_BLOCK_SIZE;
1133 let end = (start + SCALAR_BLOCK_SIZE).min(batch.len());
1134 self.evaluate_cache_block_prepared(
1135 params,
1136 batch.cache(),
1137 start,
1138 end,
1139 invariant.as_ref(),
1140 &mut workspace,
1141 &mut output,
1142 #[cfg(feature = "jit")]
1143 jit_cache.as_ref(),
1144 )?;
1145 for (lane, value) in output.iter().copied().enumerate() {
1146 acc.push(f(value)? * batch.weights()[start + lane]);
1147 }
1148 Ok::<_, E>((acc, workspace, output))
1149 },
1150 )
1151 .try_reduce(
1152 || {
1153 (
1154 AccurateComplex64::zero(),
1155 ScalarEventWorkspace::default(),
1156 Vec::new(),
1157 )
1158 },
1159 |(mut lhs, workspace, output), (rhs, _, _)| {
1160 lhs.merge(rhs);
1161 Ok::<_, E>((lhs, workspace, output))
1162 },
1163 )?;
1164 total.merge(partial.0);
1165 }
1166 Ok(total.finish())
1167 }
1168
1169 #[cfg(test)]
1170 pub(crate) fn par_weighted_complex_sum_cached<F>(
1171 &self,
1172 params: &ParamValues,
1173 dataset: &CpuCachedDataset,
1174 f: F,
1175 ) -> RuntimeResult<Complex64>
1176 where
1177 F: Fn(Complex64) -> Complex64 + Send + Sync,
1178 {
1179 self.par_try_weighted_complex_sum_cached(params, dataset, |value| Ok(f(value)))
1180 }
1181}