Skip to main content

dsfb_tmtr/
kernel.rs

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}