molrs_compute/result.rs
1//! Traits carried by every [`Compute`](crate::Compute) output.
2//!
3//! - [`ComputeResult`] — "finalize into a fully-usable value". Multi-frame
4//! accumulations can return a not-yet-normalized intermediate value from
5//! `Compute::compute`; `ComputeResult::finalize` turns it into the
6//! user-facing final form. [`Graph::run`](crate::Graph) calls it once for
7//! every node before inserting into the [`Store`](crate::Store).
8//! - [`DescriptorRow`] — "flatten into an `&[F]` row". Used by downstream
9//! matrix consumers such as PCA and k-means to treat any prior Compute
10//! output as a descriptor row without an extra conversion step.
11
12use molrs::types::F;
13
14/// Marker + finalization hook for Compute outputs.
15///
16/// `finalize` is called **once** by [`Graph::run`](crate::Graph) on the output
17/// of every node before the value is moved into the [`Store`](crate::Store).
18/// The default is a no-op, which suits outputs already in their final form
19/// (cluster assignments, per-frame observables, etc.). Accumulating outputs
20/// (notably RDF's raw pair histogram) override it to perform their
21/// normalization step.
22///
23/// # Invariant
24///
25/// Implementations **must** be idempotent: calling `finalize` twice must
26/// yield the same state as calling it once. Graph calls it exactly once, but
27/// users may call it again on persisted results.
28pub trait ComputeResult {
29 /// Convert any accumulated intermediate state into the final user-facing form.
30 fn finalize(&mut self) {}
31}
32
33/// Flatten a Compute output into a row of floats.
34///
35/// Consumed by downstream multi-frame analyses (PCA, k-means) that treat a
36/// `Vec<T: DescriptorRow>` as a row-major matrix. Implementations must return
37/// a **consistent** row length across calls on a given type — changing
38/// dimension between rows is a programmer error that the caller checks at
39/// matrix assembly time.
40pub trait DescriptorRow {
41 /// The flat row representation.
42 fn as_row(&self) -> &[F];
43}
44
45/// Per-frame sequences automatically finalize by propagating to each element.
46impl<T: ComputeResult + Clone + Send + Sync + 'static> ComputeResult for Vec<T> {
47 fn finalize(&mut self) {
48 for item in self.iter_mut() {
49 item.finalize();
50 }
51 }
52}
53
54#[cfg(test)]
55mod tests {
56 use super::*;
57
58 #[derive(Clone)]
59 struct NoopResult;
60 impl ComputeResult for NoopResult {}
61
62 #[derive(Clone)]
63 struct Counter {
64 raw: f64,
65 norm: f64,
66 finalized: bool,
67 }
68
69 impl ComputeResult for Counter {
70 fn finalize(&mut self) {
71 if !self.finalized {
72 self.norm = self.raw / 10.0;
73 self.finalized = true;
74 }
75 }
76 }
77
78 #[derive(Clone)]
79 struct Row(Vec<F>);
80 impl DescriptorRow for Row {
81 fn as_row(&self) -> &[F] {
82 &self.0
83 }
84 }
85
86 #[test]
87 fn default_finalize_is_noop() {
88 let mut x = NoopResult;
89 x.finalize();
90 x.finalize();
91 }
92
93 #[test]
94 fn finalize_is_idempotent() {
95 let mut c = Counter {
96 raw: 50.0,
97 norm: 0.0,
98 finalized: false,
99 };
100 c.finalize();
101 assert!((c.norm - 5.0).abs() < 1e-12);
102 c.finalize();
103 assert!((c.norm - 5.0).abs() < 1e-12);
104 }
105
106 #[test]
107 fn descriptor_row_as_row() {
108 let r = Row(vec![1.0, 2.0, 3.0]);
109 assert_eq!(r.as_row(), &[1.0, 2.0, 3.0]);
110 }
111}