1#![allow(clippy::cast_precision_loss)]
12
13use alloc::vec::Vec;
14use core::marker::PhantomData;
15
16use crate::{
17 chernoff::{ChernoffFunction, Growth},
18 error::SemiflowError,
19 float::SemiflowFloat,
20 scratch::ScratchPool,
21 state::State,
22};
23
24#[derive(Clone)]
26struct GaussianComponent<F: SemiflowFloat, const D: usize> {
27 mean: [F; D],
28 variance: F,
30 weight: F,
31}
32
33#[derive(Clone)]
42pub struct MeasureState<F: SemiflowFloat, const D: usize> {
43 diracs: Vec<([F; D], F)>,
44 gaussians: Vec<GaussianComponent<F, D>>,
45}
46
47impl<F: SemiflowFloat, const D: usize> MeasureState<F, D> {
48 #[must_use]
50 pub fn dirac(position: [F; D], weight: F) -> Self {
51 Self {
52 diracs: alloc::vec![(position, weight)],
53 gaussians: Vec::new(),
54 }
55 }
56
57 pub fn gaussian(mean: [F; D], variance: F, weight: F) -> Result<Self, SemiflowError> {
62 if !variance.is_finite() || variance <= F::zero() {
63 return Err(SemiflowError::DomainViolation {
64 what: "MeasureState::gaussian: variance must be finite and > 0",
65 value: variance.to_f64().unwrap_or(f64::NAN),
66 });
67 }
68 if !weight.is_finite() {
69 return Err(SemiflowError::DomainViolation {
70 what: "MeasureState::gaussian: weight must be finite",
71 value: weight.to_f64().unwrap_or(f64::NAN),
72 });
73 }
74 Ok(Self {
75 diracs: Vec::new(),
76 gaussians: alloc::vec![GaussianComponent {
77 mean,
78 variance,
79 weight
80 }],
81 })
82 }
83
84 #[must_use]
86 pub fn from_particles(particles: &[([F; D], F)]) -> Self {
87 Self {
88 diracs: particles.to_vec(),
89 gaussians: Vec::new(),
90 }
91 }
92
93 #[must_use]
95 pub fn total_variation(&self) -> F {
96 let d = self.diracs.iter().fold(F::zero(), |a, (_, w)| a + w.abs());
97 let g = self
98 .gaussians
99 .iter()
100 .fold(F::zero(), |a, g| a + g.weight.abs());
101 d + g
102 }
103
104 #[must_use]
106 pub fn second_moment(&self) -> F {
107 let d = self.diracs.iter().fold(F::zero(), |acc, (pos, w)| {
108 acc + pos.iter().fold(F::zero(), |s, &xi| s + xi * xi) * *w
109 });
110 let g = self.gaussians.iter().fold(F::zero(), |acc, g| {
111 let norm2 = g.mean.iter().fold(F::zero(), |s, &xi| s + xi * xi);
112 let d_f = F::from(D as f64).unwrap_or(F::one());
113 acc + (norm2 + d_f * g.variance) * g.weight
114 });
115 d + g
116 }
117
118 #[must_use]
121 pub fn pair<G: Fn(&[F; D]) -> F>(&self, f: G) -> F {
122 let d = self
123 .diracs
124 .iter()
125 .fold(F::zero(), |a, (p, w)| a + f(p) * *w);
126 let g = self
127 .gaussians
128 .iter()
129 .fold(F::zero(), |a, g| a + f(&g.mean) * g.weight);
130 d + g
131 }
132
133 #[must_use]
135 pub fn n_diracs(&self) -> usize {
136 self.diracs.len()
137 }
138
139 #[must_use]
146 #[allow(clippy::needless_range_loop)]
149 pub fn first_moment(&self) -> [F; D] {
150 let mass = self.total_variation();
151 if mass == F::zero() {
152 return [F::zero(); D];
153 }
154 let mut result = [F::zero(); D];
155 for (pos, w) in &self.diracs {
156 for d in 0..D {
157 result[d] += pos[d] * *w;
158 }
159 }
160 for g in &self.gaussians {
161 for d in 0..D {
162 result[d] += g.mean[d] * g.weight;
163 }
164 }
165 for d in 0..D {
166 result[d] /= mass;
167 }
168 result
169 }
170
171 #[must_use]
176 pub fn variance(&self) -> F {
177 let mass = self.total_variation();
178 if mass == F::zero() {
179 return F::zero();
180 }
181 let e_x2 = self.second_moment() / mass;
182 let mu = self.first_moment();
183 let mu_sq = mu.iter().fold(F::zero(), |acc, &xi| acc + xi * xi);
184 e_x2 - mu_sq
185 }
186
187 #[must_use]
191 #[allow(clippy::needless_range_loop)]
194 pub fn variance_per_axis(&self) -> [F; D] {
195 let mass = self.total_variation();
196 if mass == F::zero() {
197 return [F::zero(); D];
198 }
199 let mut e_x2_per = [F::zero(); D];
201 for (pos, w) in &self.diracs {
202 for d in 0..D {
203 e_x2_per[d] += pos[d] * pos[d] * *w;
204 }
205 }
206 for g in &self.gaussians {
207 for d in 0..D {
208 e_x2_per[d] += (g.mean[d] * g.mean[d] + g.variance) * g.weight;
209 }
210 }
211 let mu = self.first_moment();
212 let mut result = [F::zero(); D];
213 for d in 0..D {
214 result[d] = e_x2_per[d] / mass - mu[d] * mu[d];
215 }
216 result
217 }
218
219 #[must_use]
223 pub fn to_flat_buffers_d1(&self) -> (Vec<F>, Vec<F>)
224 where
225 [F; 1]: Sized,
226 F: Copy,
227 {
228 let pos: Vec<F> = self.diracs.iter().map(|(p, _)| p[0]).collect();
229 let wts: Vec<F> = self.diracs.iter().map(|(_, w)| *w).collect();
230 (pos, wts)
231 }
232
233 fn zero_in_place(&mut self) {
234 self.diracs.clear();
235 self.gaussians.clear();
236 }
237
238 #[must_use]
240 pub(crate) fn as_diracs_slice(&self) -> &[([F; D], F)] {
241 &self.diracs
242 }
243
244 pub(crate) fn push_dirac_raw(&mut self, pos: [F; D], weight: F) {
245 self.diracs.push((pos, weight));
246 }
247
248 pub(crate) fn clear_diracs_with_capacity(&mut self, cap: usize) {
250 self.diracs.clear();
251 self.diracs.reserve(cap);
252 }
253}
254
255impl<F: SemiflowFloat, const D: usize> State<F> for MeasureState<F, D> {
261 fn len(&self) -> usize {
262 self.diracs.len() + self.gaussians.len()
263 }
264
265 fn axpy_into(&mut self, alpha: F, src: &Self) {
266 for (pos, w) in &src.diracs {
267 self.diracs.push((*pos, alpha * *w));
268 }
269 for g in &src.gaussians {
270 self.gaussians.push(GaussianComponent {
271 mean: g.mean,
272 variance: g.variance,
273 weight: alpha * g.weight,
274 });
275 }
276 }
277
278 fn copy_from(&mut self, src: &Self) {
279 self.diracs.clone_from(&src.diracs);
280 self.gaussians.clone_from(&src.gaussians);
281 }
282
283 fn zero_into(&mut self) {
284 self.zero_in_place();
285 }
286
287 fn norm_sup(&self) -> F {
289 self.total_variation()
290 }
291
292 fn scale_into(&mut self, k: F) {
293 for (_, w) in &mut self.diracs {
294 *w *= k;
295 }
296 for g in &mut self.gaussians {
297 g.weight *= k;
298 }
299 }
300}
301
302#[allow(clippy::many_single_char_names)]
305fn lemma_a1_push<F: SemiflowFloat>(
306 tau: F,
307 a: F,
308 b: F,
309 c: F,
310 src: &MeasureState<F, 1>,
311 dst: &mut MeasureState<F, 1>,
312) -> Result<(), SemiflowError> {
313 if !tau.is_finite() || tau < F::zero() {
314 return Err(SemiflowError::DomainViolation {
315 what: "adjoint_fp: tau must be finite and >= 0",
316 value: tau.to_f64().unwrap_or(f64::NAN),
317 });
318 }
319 let two = F::from(2.0).unwrap_or(F::one() + F::one());
320 let four = two + two;
321 let quarter = F::one() / four;
322 let half = F::one() / two;
323 let h = two * (a * tau).sqrt(); let k = two * b * tau; let tc = tau * c;
326
327 dst.zero_in_place();
328 for (pos, w) in &src.diracs {
329 let x = pos[0];
330 dst.diracs.push(([x + h], quarter * *w));
331 dst.diracs.push(([x - h], quarter * *w));
332 dst.diracs.push(([x + k], half * *w));
333 dst.diracs.push(([x], tc * *w));
334 }
335 for g in &src.gaussians {
337 dst.gaussians.push(GaussianComponent {
338 mean: [g.mean[0] + k],
339 variance: g.variance + two * a * tau,
340 weight: (F::one() + tc) * g.weight,
341 });
342 }
343 Ok(())
344}
345
346pub trait Adjointable<F: SemiflowFloat, const D: usize>: ChernoffFunction<F> {
349 #[allow(clippy::too_many_arguments)]
357 fn apply_adjoint_into(
358 &self,
359 tau: F,
360 a: F,
361 b: F,
362 c: F,
363 src: &MeasureState<F, D>,
364 dst: &mut MeasureState<F, D>,
365 ) -> Result<(), SemiflowError>;
366}
367
368impl<C, F> Adjointable<F, 1> for C
370where
371 C: ChernoffFunction<F>,
372 F: SemiflowFloat,
373{
374 fn apply_adjoint_into(
375 &self,
376 tau: F,
377 a: F,
378 b: F,
379 c: F,
380 src: &MeasureState<F, 1>,
381 dst: &mut MeasureState<F, 1>,
382 ) -> Result<(), SemiflowError> {
383 lemma_a1_push(tau, a, b, c, src, dst)
384 }
385}
386
387#[derive(Clone)]
399pub struct AdjointFokkerPlanckChernoff<C, F: SemiflowFloat, const D: usize>
400where
401 C: Adjointable<F, D>,
402{
403 forward: C,
404 a: F, b: F, c: F, _phantom: PhantomData<F>,
408}
409
410impl<C, F, const D: usize> AdjointFokkerPlanckChernoff<C, F, D>
411where
412 C: Adjointable<F, D>,
413 F: SemiflowFloat,
414{
415 #[must_use]
421 pub fn new(forward: C, a: F, b: F, c: F) -> Self {
422 Self {
423 forward,
424 a,
425 b,
426 c,
427 _phantom: PhantomData,
428 }
429 }
430
431 #[must_use]
433 pub fn forward(&self) -> &C {
434 &self.forward
435 }
436
437 #[must_use]
439 pub fn a(&self) -> F {
440 self.a
441 }
442
443 #[must_use]
445 pub fn b(&self) -> F {
446 self.b
447 }
448
449 #[must_use]
451 pub fn c(&self) -> F {
452 self.c
453 }
454}
455
456impl<C, F, const D: usize> ChernoffFunction<F> for AdjointFokkerPlanckChernoff<C, F, D>
457where
458 C: Adjointable<F, D>,
459 F: SemiflowFloat,
460{
461 type S = MeasureState<F, D>;
462
463 fn apply_into(
464 &self,
465 tau: F,
466 src: &MeasureState<F, D>,
467 dst: &mut MeasureState<F, D>,
468 _scratch: &mut ScratchPool<F>,
469 ) -> Result<(), SemiflowError> {
470 Adjointable::apply_adjoint_into(&self.forward, tau, self.a, self.b, self.c, src, dst)
471 }
472
473 fn order(&self) -> u32 {
475 self.forward.order()
476 }
477
478 fn growth(&self) -> Growth<F> {
480 self.forward.growth()
481 }
482}
483
484#[cfg(test)]
485include!(concat!(
486 env!("CARGO_MANIFEST_DIR"),
487 "/src/adjoint_fp_tests.rs"
488));