//! Compressed streaming breathing buffer (ruvector-temporal-tensor). //! //! [`CompressedBreathingBuffer`] stores per-frame subcarrier amplitude arrays //! using a tiered quantization scheme: //! //! - Hot tier (recent ~10 frames): 8-bit //! - Warm tier: 5–7-bit //! - Cold tier: 3-bit //! //! For 56 subcarriers × 60 s × 100 Hz: 13.4 MB raw → 3.4–6.7 MB compressed. use ruvector_temporal_tensor::segment as tt_segment; use ruvector_temporal_tensor::{TemporalTensorCompressor, TierPolicy}; /// Streaming compressed breathing buffer. /// /// Hot frames (recent ~10) at 8-bit, warm at 5–7-bit, cold at 3-bit. /// For 56 subcarriers × 60 s × 100 Hz: 13.4 MB raw → 3.4–6.7 MB compressed. pub struct CompressedBreathingBuffer { compressor: TemporalTensorCompressor, segments: Vec>, frame_count: u32, /// Number of subcarriers per frame (typically 56). pub n_subcarriers: usize, } impl CompressedBreathingBuffer { /// Create a new buffer. /// /// # Arguments /// /// - `n_subcarriers`: number of subcarriers per frame; typically 56. /// - `zone_id`: disaster zone identifier used as the tensor ID. pub fn new(n_subcarriers: usize, zone_id: u32) -> Self { Self { compressor: TemporalTensorCompressor::new( TierPolicy::default(), n_subcarriers as u32, zone_id, ), segments: Vec::new(), frame_count: 0, n_subcarriers, } } /// Push one time-frame of amplitude values. /// /// The frame is compressed and appended to the internal segment store. /// Non-empty segments are retained; empty outputs (compressor buffering) /// are silently skipped. pub fn push_frame(&mut self, amplitudes: &[f32]) { let ts = self.frame_count; self.compressor.set_access(ts, ts); let mut seg = Vec::new(); self.compressor.push_frame(amplitudes, ts, &mut seg); if !seg.is_empty() { self.segments.push(seg); } self.frame_count += 1; } /// Number of frames pushed so far. pub fn frame_count(&self) -> u32 { self.frame_count } /// Decode all compressed frames to a flat `f32` vec. /// /// Concatenates decoded segments in order. The resulting length may be /// less than `frame_count * n_subcarriers` if the compressor has not yet /// flushed all frames (tiered flushing may batch frames). pub fn to_vec(&self) -> Vec { let mut out = Vec::new(); for seg in &self.segments { tt_segment::decode(seg, &mut out); } out } } #[cfg(test)] mod tests { use super::*; #[test] fn breathing_buffer_frame_count() { let n_subcarriers = 56; let mut buf = CompressedBreathingBuffer::new(n_subcarriers, 1); for i in 0..20 { let amplitudes: Vec = (0..n_subcarriers).map(|s| (i * n_subcarriers + s) as f32 * 0.01).collect(); buf.push_frame(&litudes); } assert_eq!(buf.frame_count(), 20, "frame_count must equal the number of pushed frames"); } #[test] fn breathing_buffer_to_vec_runs() { let n_subcarriers = 56; let mut buf = CompressedBreathingBuffer::new(n_subcarriers, 2); for i in 0..10 { let amplitudes: Vec = (0..n_subcarriers).map(|s| (i + s) as f32 * 0.1).collect(); buf.push_frame(&litudes); } // to_vec() must not panic; output length is determined by compressor flushing. let _decoded = buf.to_vec(); } }