Skip to main content

qualia_core_db/specialized_libs/statistical_computing/
compression.rs

1use super::*;
2
3/// Data compression engine
4pub struct DataCompressionEngine {
5    compression_algorithms: Vec<CompressionAlgorithm>,
6    compression_statistics: CompressionStatistics,
7}
8
9/// Compression algorithms
10#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
11pub enum CompressionAlgorithm {
12    Gzip,
13    LZ4,
14    ZSTD,
15    Snappy,
16    Custom(String),
17}
18
19/// Compression statistics
20///
21/// Tracks cumulative metrics across all compression/decompression operations
22/// performed by a [`DataCompressionEngine`]. The `compression_ratio` field
23/// records the ratio of the *most recent* compression operation, while
24/// [`CompressionStatistics::compression_ratio`] computes the *overall* ratio
25/// from the cumulative byte totals.
26#[derive(Debug, Clone)]
27pub struct CompressionStatistics {
28    /// Total original (uncompressed) bytes processed across all compress ops.
29    pub original_size: u64,
30    /// Total compressed bytes produced across all compress ops.
31    pub compressed_size: u64,
32    /// Ratio of the most recent compression operation (compressed / original).
33    pub compression_ratio: f64,
34    /// Total time spent compressing, in nanoseconds.
35    pub compression_time: u64,
36    /// Total time spent decompressing, in nanoseconds.
37    pub decompression_time: u64,
38    /// Number of compression operations performed.
39    pub compression_count: u64,
40    /// Number of decompression operations performed.
41    pub decompression_count: u64,
42}
43
44impl DataCompressionEngine {
45    pub fn new() -> Self {
46        Self {
47            compression_algorithms: vec![CompressionAlgorithm::LZ4, CompressionAlgorithm::ZSTD],
48            compression_statistics: CompressionStatistics::new(),
49        }
50    }
51
52    pub fn initialize(&mut self) -> Result<(), StatisticalError> {
53        Ok(())
54    }
55
56    /// Compress `data` using a simple run-length encoding and record the
57    /// operation's statistics (original size, compressed size, ratio, time).
58    pub fn compress(&mut self, data: &[u8]) -> Result<Vec<u8>, StatisticalError> {
59        let start = Instant::now();
60        let compressed = rle_compress(data);
61        let elapsed = start.elapsed().as_nanos() as u64;
62        self.compression_statistics.record_compression(
63            data.len() as u64,
64            compressed.len() as u64,
65            elapsed,
66        );
67        Ok(compressed)
68    }
69
70    /// Decompress data previously produced by [`compress`](Self::compress) and
71    /// record the decompression statistics.
72    pub fn decompress(&mut self, data: &[u8]) -> Result<Vec<u8>, StatisticalError> {
73        let start = Instant::now();
74        let decompressed = rle_decompress(data)?;
75        let elapsed = start.elapsed().as_nanos() as u64;
76        self.compression_statistics.record_decompression(elapsed);
77        Ok(decompressed)
78    }
79
80    /// Returns a reference to the cumulative compression statistics.
81    pub fn get_statistics(&self) -> &CompressionStatistics {
82        &self.compression_statistics
83    }
84
85    /// Resets all accumulated compression statistics to zero.
86    pub fn reset_statistics(&mut self) {
87        self.compression_statistics = CompressionStatistics::new();
88    }
89
90    /// Returns the list of compression algorithms available to this engine.
91    pub fn compression_algorithms(&self) -> &[CompressionAlgorithm] {
92        &self.compression_algorithms
93    }
94
95    /// Register an additional compression algorithm.
96    pub fn add_compression_algorithm(&mut self, algorithm: CompressionAlgorithm) {
97        if !self.compression_algorithms.contains(&algorithm) {
98            self.compression_algorithms.push(algorithm);
99        }
100    }
101
102    /// Returns `true` when the given algorithm is registered.
103    pub fn supports_algorithm(&self, algorithm: &CompressionAlgorithm) -> bool {
104        self.compression_algorithms.contains(algorithm)
105    }
106}
107
108impl CompressionStatistics {
109    /// Create a fresh, zeroed statistics record.
110    pub fn new() -> Self {
111        Self {
112            original_size: 0,
113            compressed_size: 0,
114            compression_ratio: 0.0,
115            compression_time: 0,
116            decompression_time: 0,
117            compression_count: 0,
118            decompression_count: 0,
119        }
120    }
121
122    /// Record a single compression operation.
123    pub fn record_compression(&mut self, original: u64, compressed: u64, elapsed_ns: u64) {
124        self.original_size += original;
125        self.compressed_size += compressed;
126        self.compression_time += elapsed_ns;
127        self.compression_count += 1;
128        self.compression_ratio = if original == 0 {
129            0.0
130        } else {
131            compressed as f64 / original as f64
132        };
133    }
134
135    /// Record a single decompression operation.
136    pub fn record_decompression(&mut self, elapsed_ns: u64) {
137        self.decompression_time += elapsed_ns;
138        self.decompression_count += 1;
139    }
140
141    /// Overall compression ratio across all operations
142    /// (`compressed_size / original_size`). Returns `0.0` when no data has been
143    /// compressed yet.
144    pub fn compression_ratio(&self) -> f64 {
145        if self.original_size == 0 {
146            0.0
147        } else {
148            self.compressed_size as f64 / self.original_size as f64
149        }
150    }
151
152    /// Human-readable summary of the accumulated statistics.
153    pub fn summary(&self) -> String {
154        format!(
155            "CompressionStatistics: {} compress op(s), {} decompress op(s), \
156             original={} bytes, compressed={} bytes, overall ratio={:.4}, \
157             last-op ratio={:.4}, compress_time={} ns, decompress_time={} ns",
158            self.compression_count,
159            self.decompression_count,
160            self.original_size,
161            self.compressed_size,
162            self.compression_ratio(),
163            self.compression_ratio,
164            self.compression_time,
165            self.decompression_time,
166        )
167    }
168}
169
170/// Simple run-length encoding over bytes. Each run is emitted as
171/// `(count: u8, byte: u8)`; runs longer than 255 are split. Incompressible
172/// data expands by ~2x, but repetitive data (the common statistical-dataset
173/// case for constant columns) compresses well.
174fn rle_compress(data: &[u8]) -> Vec<u8> {
175    let mut out = Vec::with_capacity(data.len());
176    let mut i = 0;
177    while i < data.len() {
178        let byte = data[i];
179        let mut count: usize = 1;
180        while i + count < data.len() && data[i + count] == byte && count < 255 {
181            count += 1;
182        }
183        out.push(count as u8);
184        out.push(byte);
185        i += count;
186    }
187    out
188}
189
190/// Inverse of [`rle_compress`].
191fn rle_decompress(data: &[u8]) -> Result<Vec<u8>, StatisticalError> {
192    if data.len() % 2 != 0 {
193        return Err(StatisticalError::InvalidData(
194            "Corrupted RLE stream (odd length)".to_string(),
195        ));
196    }
197    let mut out = Vec::with_capacity(data.len());
198    let mut i = 0;
199    while i < data.len() {
200        let count = data[i] as usize;
201        let byte = data[i + 1];
202        out.resize(out.len() + count, byte);
203        i += 2;
204    }
205    Ok(out)
206}