1use serde::Serialize;
2
3use crate::config::KernelKind;
4use crate::observer::ObserverSeries;
5
6#[derive(Debug, Clone, Copy, Serialize)]
7pub struct KernelEvaluation {
8 pub signal: f64,
9 pub weight: f64,
10 pub compatibility: f64,
11}
12
13pub fn evaluate_kernel(
14 kind: KernelKind,
15 source: &ObserverSeries,
16 target: &ObserverSeries,
17 corrected_time: usize,
18 anchor_time: usize,
19 delta: usize,
20 resonance_threshold: f64,
21) -> KernelEvaluation {
22 let start = corrected_time.saturating_sub(delta);
23 let end = anchor_time.min(corrected_time.saturating_add(delta));
24 let mut weighted_signal = 0.0;
25 let mut weight_sum = 0.0;
26 let mut compatibility_sum = 0.0;
27
28 for tau in start..=end {
29 let distance = tau.abs_diff(corrected_time) as f64;
30 let base_weight = match kind {
31 KernelKind::Uniform => 1.0,
32 KernelKind::Exponential | KernelKind::ResonanceGated => {
33 let scale = delta.max(1) as f64;
34 (-distance / scale).exp()
35 }
36 };
37 let source_slope = slope_at(&source.estimate, tau);
38 let target_slope = slope_at(&target.estimate, corrected_time);
39 let slope_gap = (source_slope - target_slope).abs();
40 let same_direction = source.correction_driver(tau).signum()
41 == target.residual[corrected_time].signum()
42 || target.residual[corrected_time].abs() < 1e-6;
43 let compatibility = if slope_gap <= resonance_threshold && same_direction {
44 1.0
45 } else if slope_gap <= resonance_threshold * 1.8 {
46 0.55
47 } else {
48 0.18
49 };
50 let gated_weight = match kind {
51 KernelKind::ResonanceGated => base_weight * compatibility,
52 _ => base_weight,
53 };
54 weighted_signal += gated_weight * source.correction_driver(tau);
55 weight_sum += gated_weight;
56 compatibility_sum += compatibility;
57 }
58
59 if weight_sum <= f64::EPSILON {
60 return KernelEvaluation {
61 signal: 0.0,
62 weight: 0.0,
63 compatibility: 0.0,
64 };
65 }
66
67 KernelEvaluation {
68 signal: weighted_signal / weight_sum,
69 weight: weight_sum / ((end - start + 1) as f64),
70 compatibility: compatibility_sum / ((end - start + 1) as f64),
71 }
72}
73
74fn slope_at(series: &[f64], index: usize) -> f64 {
75 if index == 0 {
76 0.0
77 } else {
78 series[index] - series[index - 1]
79 }
80}