Skip to main content

fpm_rs/algorithms/
metrics.rs

1//! Typed algorithm-step output and deterministic metric reduction.
2
3use std::collections::BTreeMap;
4
5use serde::{Deserialize, Serialize};
6
7use crate::reconstruction::AlgorithmMetricRecord;
8
9/// Universal, algorithm-neutral information produced by one batch step.
10#[derive(Clone, Debug, Default, Serialize, Deserialize)]
11#[serde(deny_unknown_fields)]
12pub struct StepSummary {
13    /// Sum of per-frame objectives multiplied by frame weights.
14    pub objective_sum: f64,
15    /// Number of frames visited, including zero-weight frames.
16    pub frame_count: usize,
17    /// Sum of non-negative reconstruction weights for visited frames.
18    pub weight_sum: f64,
19    /// Unweighted objective for each visited acquisition-frame index.
20    pub per_frame_objective: BTreeMap<usize, f64>,
21}
22
23impl StepSummary {
24    /// Accumulates one frame's objective and reconstruction weight.
25    pub fn push_frame(&mut self, frame: usize, objective: f64, weight: f64) {
26        self.objective_sum += weight * objective;
27        self.frame_count += 1;
28        self.weight_sum += weight;
29        self.per_frame_objective.insert(frame, objective);
30    }
31
32    /// Returns the weight-normalized objective, or `None` when total weight is zero.
33    pub fn mean_objective(&self) -> Option<f64> {
34        (self.weight_sum > 0.0).then(|| self.objective_sum / self.weight_sum)
35    }
36
37    /// Deterministically combines counts, sums, and per-frame values from another batch.
38    pub fn merge(&mut self, other: Self) {
39        self.objective_sum += other.objective_sum;
40        self.frame_count += other.frame_count;
41        self.weight_sum += other.weight_sum;
42        self.per_frame_objective.extend(other.per_frame_objective);
43    }
44}
45
46/// Algorithm-owned iteration metrics.
47///
48/// Implementations define both deterministic batch merging and conversion to
49/// generic persisted records. This deliberately avoids a central algorithm or
50/// metric enum.
51pub trait AlgorithmIterationMetrics: Default {
52    /// Deterministically accumulates metrics from another batch in schedule order.
53    fn merge(&mut self, other: Self);
54
55    /// Appends stable scalar records for a one-based completed `iteration`.
56    fn append_records(&self, iteration: usize, output: &mut Vec<AlgorithmMetricRecord>);
57}
58
59/// Metrics type for algorithms that have no stable algorithm-specific scalar.
60#[derive(Clone, Copy, Debug, Default)]
61pub struct NoIterationMetrics;
62
63impl AlgorithmIterationMetrics for NoIterationMetrics {
64    fn merge(&mut self, _other: Self) {}
65
66    fn append_records(&self, _iteration: usize, _output: &mut Vec<AlgorithmMetricRecord>) {}
67}
68
69/// Typed output from one algorithm batch.
70#[derive(Clone, Debug)]
71pub struct StepOutput<M> {
72    /// Algorithm-neutral objective and visited-frame summary.
73    pub summary: StepSummary,
74    /// Algorithm-specific metrics accumulated for the same batch.
75    pub metrics: M,
76}
77
78impl<M: Default> From<StepSummary> for StepOutput<M> {
79    fn from(summary: StepSummary) -> Self {
80        Self {
81            summary,
82            metrics: M::default(),
83        }
84    }
85}
86
87impl<M: AlgorithmIterationMetrics> Default for StepOutput<M> {
88    fn default() -> Self {
89        Self {
90            summary: StepSummary::default(),
91            metrics: M::default(),
92        }
93    }
94}
95
96impl<M: AlgorithmIterationMetrics> StepOutput<M> {
97    /// Combines the neutral summary and algorithm-specific metrics from another batch.
98    pub fn merge(&mut self, other: Self) {
99        self.summary.merge(other.summary);
100        self.metrics.merge(other.metrics);
101    }
102}