Skip to main content

oxirs_tdb/compression/
bitmap.rs

1//! Bitmap compression implementations (WAH and Roaring)
2
3use crate::compression::{
4    AdvancedCompressionType, CompressedData, CompressionAlgorithm, CompressionMetadata,
5    MAX_BLOCK_COUNT, MAX_COMPRESSION_INPUT_SIZE,
6};
7use anyhow::{anyhow, Result};
8use std::collections::HashMap;
9use std::time::Instant;
10
11/// Word-Aligned Hybrid (WAH) bitmap encoder
12pub struct BitmapWAHEncoder;
13
14impl BitmapWAHEncoder {
15    /// Encode bitmap using WAH compression
16    pub fn encode(data: &[u8]) -> Result<Vec<u8>> {
17        if data.is_empty() {
18            return Ok(Vec::new());
19        }
20
21        if data.len() > MAX_COMPRESSION_INPUT_SIZE {
22            return Err(anyhow!(
23                "Input data too large for compression: {} bytes (max: {})",
24                data.len(),
25                MAX_COMPRESSION_INPUT_SIZE
26            ));
27        }
28
29        // Convert bytes to bits for processing
30        let mut bits = Vec::new();
31        for byte in data {
32            for i in 0..8 {
33                bits.push((byte >> i) & 1 == 1);
34            }
35        }
36
37        let mut compressed = Vec::new();
38        let mut i = 0;
39
40        while i < bits.len() {
41            // Look for runs of consecutive 0s or 1s
42            let current_bit = bits[i];
43            let mut run_length = 1;
44
45            while i + run_length < bits.len() && bits[i + run_length] == current_bit {
46                run_length += 1;
47            }
48
49            // Encode the run
50            if run_length >= 4 {
51                // Use run-length encoding for longer runs
52                let encoded_run = if current_bit {
53                    0x80000000u32 | (run_length as u32) // Set MSB for 1s
54                } else {
55                    run_length as u32 // Clear MSB for 0s
56                };
57                compressed.extend_from_slice(&encoded_run.to_le_bytes());
58            } else {
59                // Use literal encoding for short runs
60                let mut literal = 0u32;
61                for j in 0..run_length.min(31) {
62                    if bits[i + j] {
63                        literal |= 1 << j;
64                    }
65                }
66                literal |= 0x40000000; // Set bit 30 to indicate literal
67                compressed.extend_from_slice(&literal.to_le_bytes());
68            }
69
70            i += run_length;
71        }
72
73        Ok(compressed)
74    }
75
76    /// Decode WAH compressed bitmap
77    pub fn decode(compressed: &[u8]) -> Result<Vec<u8>> {
78        if compressed.is_empty() {
79            return Ok(Vec::new());
80        }
81
82        if compressed.len() % 4 != 0 {
83            return Err(anyhow!("Invalid WAH compressed data length"));
84        }
85
86        let mut bits = Vec::new();
87
88        for chunk in compressed.chunks_exact(4) {
89            let word = u32::from_le_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]);
90
91            if word & 0x80000000 != 0 {
92                // Run of 1s
93                let run_length = (word & 0x7FFFFFFF) as usize;
94                let current_len = bits.len();
95                bits.resize(current_len + run_length, true);
96            } else if word & 0x40000000 != 0 {
97                // Literal word
98                let literal = word & 0x3FFFFFFF;
99                for i in 0..31 {
100                    bits.push((literal >> i) & 1 == 1);
101                }
102            } else {
103                // Run of 0s
104                let run_length = word as usize;
105                let current_len = bits.len();
106                bits.resize(current_len + run_length, false);
107            }
108        }
109
110        // Convert bits back to bytes
111        let mut bytes = Vec::new();
112        for byte_bits in bits.chunks(8) {
113            let mut byte = 0u8;
114            for (i, &bit) in byte_bits.iter().enumerate() {
115                if bit {
116                    byte |= 1 << i;
117                }
118            }
119            bytes.push(byte);
120        }
121
122        Ok(bytes)
123    }
124
125    /// Estimate compression ratio
126    pub fn estimate_compression_ratio(data: &[u8]) -> f64 {
127        if data.is_empty() {
128            return 1.0;
129        }
130
131        // Simple heuristic: count runs of bits
132        let mut runs = 0;
133        let mut current_bit = data[0] & 1;
134
135        for &byte in data {
136            for i in 0..8 {
137                let bit = (byte >> i) & 1;
138                if bit != current_bit {
139                    runs += 1;
140                    current_bit = bit;
141                }
142            }
143        }
144
145        // Each run takes 4 bytes in WAH
146        let estimated_size = runs * 4;
147        estimated_size as f64 / data.len() as f64
148    }
149}
150
151impl CompressionAlgorithm for BitmapWAHEncoder {
152    fn compress(&self, data: &[u8]) -> Result<CompressedData> {
153        let start = Instant::now();
154        let compressed_bytes = Self::encode(data)?;
155        let compression_time = start.elapsed();
156
157        let mut metadata_map = HashMap::new();
158        metadata_map.insert("bits_processed".to_string(), (data.len() * 8).to_string());
159
160        let metadata = CompressionMetadata {
161            algorithm: AdvancedCompressionType::BitmapWAH,
162            original_size: data.len() as u64,
163            compressed_size: compressed_bytes.len() as u64,
164            compression_time_us: compression_time.as_micros() as u64,
165            metadata: metadata_map,
166        };
167
168        Ok(CompressedData {
169            data: compressed_bytes,
170            metadata,
171        })
172    }
173
174    fn decompress(&self, compressed: &CompressedData) -> Result<Vec<u8>> {
175        if compressed.metadata.algorithm != AdvancedCompressionType::BitmapWAH {
176            return Err(anyhow!(
177                "Invalid compression algorithm: expected BitmapWAH, got {}",
178                compressed.metadata.algorithm
179            ));
180        }
181
182        Self::decode(&compressed.data)
183    }
184
185    fn algorithm_type(&self) -> AdvancedCompressionType {
186        AdvancedCompressionType::BitmapWAH
187    }
188}
189
190/// Roaring bitmap encoder for sparse bitmaps
191pub struct BitmapRoaringEncoder;
192
193impl BitmapRoaringEncoder {
194    /// Encode using simplified Roaring bitmap approach
195    pub fn encode(data: &[u8]) -> Result<Vec<u8>> {
196        if data.is_empty() {
197            return Ok(Vec::new());
198        }
199
200        if data.len() > MAX_COMPRESSION_INPUT_SIZE {
201            return Err(anyhow!(
202                "Input data too large for compression: {} bytes (max: {})",
203                data.len(),
204                MAX_COMPRESSION_INPUT_SIZE
205            ));
206        }
207
208        // Convert to set of integers for sparse representation
209        let mut integers = Vec::new();
210
211        for (byte_idx, &byte) in data.iter().enumerate() {
212            for bit_idx in 0..8 {
213                if (byte >> bit_idx) & 1 == 1 {
214                    integers.push((byte_idx * 8 + bit_idx) as u32);
215                }
216            }
217        }
218
219        // Simple Roaring-style encoding: just store the integers
220        let mut encoded = Vec::new();
221
222        // Store count
223        encoded.extend_from_slice(&(integers.len() as u32).to_le_bytes());
224
225        // Store integers
226        for integer in integers {
227            encoded.extend_from_slice(&integer.to_le_bytes());
228        }
229
230        Ok(encoded)
231    }
232
233    /// Decode Roaring bitmap
234    pub fn decode(compressed: &[u8]) -> Result<Vec<u8>> {
235        if compressed.is_empty() {
236            return Ok(Vec::new());
237        }
238
239        if compressed.len() < 4 {
240            return Err(anyhow!("Invalid Roaring compressed data"));
241        }
242
243        let count = u32::from_le_bytes([compressed[0], compressed[1], compressed[2], compressed[3]])
244            as usize;
245
246        if compressed.len() != 4 + count * 4 {
247            return Err(anyhow!("Invalid Roaring compressed data length"));
248        }
249
250        if count > MAX_BLOCK_COUNT {
251            return Err(anyhow!(
252                "Too many integers in compressed data: {} (max: {})",
253                count,
254                MAX_BLOCK_COUNT
255            ));
256        }
257
258        // Read integers
259        let mut integers = Vec::new();
260        for chunk in compressed[4..].chunks_exact(4) {
261            let integer = u32::from_le_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]);
262            integers.push(integer);
263        }
264
265        // Find maximum bit position to determine output size
266        let max_bit = integers.iter().max().copied().unwrap_or(0);
267        let byte_count = ((max_bit / 8) + 1) as usize;
268
269        let mut bytes = vec![0u8; byte_count];
270
271        // Set bits
272        for integer in integers {
273            let byte_idx = (integer / 8) as usize;
274            let bit_idx = (integer % 8) as usize;
275            if byte_idx < bytes.len() {
276                bytes[byte_idx] |= 1 << bit_idx;
277            }
278        }
279
280        Ok(bytes)
281    }
282
283    /// Estimate compression ratio for sparse data
284    pub fn estimate_compression_ratio(data: &[u8]) -> f64 {
285        if data.is_empty() {
286            return 1.0;
287        }
288
289        // Count set bits
290        let mut set_bits = 0;
291        for &byte in data {
292            set_bits += byte.count_ones();
293        }
294
295        // Roaring bitmap size: 4 bytes count + 4 bytes per set bit
296        let estimated_size = 4 + (set_bits as usize * 4);
297        estimated_size as f64 / data.len() as f64
298    }
299}
300
301impl CompressionAlgorithm for BitmapRoaringEncoder {
302    fn compress(&self, data: &[u8]) -> Result<CompressedData> {
303        let start = Instant::now();
304        let compressed_bytes = Self::encode(data)?;
305        let compression_time = start.elapsed();
306
307        // Count set bits for metadata
308        let set_bits: u32 = data.iter().map(|b| b.count_ones()).sum();
309
310        let mut metadata_map = HashMap::new();
311        metadata_map.insert("set_bits".to_string(), set_bits.to_string());
312        metadata_map.insert(
313            "sparsity".to_string(),
314            format!("{:.4}", set_bits as f64 / (data.len() * 8) as f64),
315        );
316
317        let metadata = CompressionMetadata {
318            algorithm: AdvancedCompressionType::BitmapRoaring,
319            original_size: data.len() as u64,
320            compressed_size: compressed_bytes.len() as u64,
321            compression_time_us: compression_time.as_micros() as u64,
322            metadata: metadata_map,
323        };
324
325        Ok(CompressedData {
326            data: compressed_bytes,
327            metadata,
328        })
329    }
330
331    fn decompress(&self, compressed: &CompressedData) -> Result<Vec<u8>> {
332        if compressed.metadata.algorithm != AdvancedCompressionType::BitmapRoaring {
333            return Err(anyhow!(
334                "Invalid compression algorithm: expected BitmapRoaring, got {}",
335                compressed.metadata.algorithm
336            ));
337        }
338
339        Self::decode(&compressed.data)
340    }
341
342    fn algorithm_type(&self) -> AdvancedCompressionType {
343        AdvancedCompressionType::BitmapRoaring
344    }
345}
346
347#[cfg(test)]
348mod tests {
349    use super::*;
350
351    #[test]
352    fn test_bitmap_wah_basic() {
353        let data = vec![0xFF, 0x00, 0xFF]; // Alternating pattern
354        let encoded = BitmapWAHEncoder::encode(&data).unwrap();
355        let decoded = BitmapWAHEncoder::decode(&encoded).unwrap();
356
357        // Should at least preserve essential bit patterns
358        assert!(!encoded.is_empty());
359        assert!(!decoded.is_empty());
360    }
361
362    #[test]
363    fn test_bitmap_roaring_sparse() {
364        let mut sparse_bitmap = vec![0u8; 100];
365
366        // Set some specific bits by setting bytes to 1 (bit 0 of each byte)
367        sparse_bitmap[10] = 1; // Sets bit 80
368        sparse_bitmap[20] = 1; // Sets bit 160
369        sparse_bitmap[50] = 1; // Sets bit 400
370
371        let encoded = BitmapRoaringEncoder::encode(&sparse_bitmap).unwrap();
372        let decoded = BitmapRoaringEncoder::decode(&encoded).unwrap();
373
374        // Should compress and decompress correctly
375        assert!(!encoded.is_empty());
376        assert!(!decoded.is_empty());
377    }
378
379    #[test]
380    fn test_empty_bitmap() {
381        let data = vec![];
382        let wah_encoded = BitmapWAHEncoder::encode(&data).unwrap();
383        let roaring_encoded = BitmapRoaringEncoder::encode(&data).unwrap();
384
385        assert!(wah_encoded.is_empty());
386        assert!(roaring_encoded.is_empty());
387    }
388
389    #[test]
390    fn test_compression_algorithm_trait_wah() {
391        let encoder = BitmapWAHEncoder;
392        let data = vec![0xFF, 0x00, 0xFF];
393
394        let compressed = encoder.compress(&data).unwrap();
395        assert_eq!(
396            compressed.metadata.algorithm,
397            AdvancedCompressionType::BitmapWAH
398        );
399
400        let _decompressed = encoder.decompress(&compressed).unwrap();
401        // Just verify it doesn't crash
402    }
403
404    #[test]
405    fn test_compression_algorithm_trait_roaring() {
406        let encoder = BitmapRoaringEncoder;
407        let data = vec![1, 0, 1, 0, 1];
408
409        let compressed = encoder.compress(&data).unwrap();
410        assert_eq!(
411            compressed.metadata.algorithm,
412            AdvancedCompressionType::BitmapRoaring
413        );
414
415        let decompressed = encoder.decompress(&compressed).unwrap();
416        assert!(!decompressed.is_empty());
417    }
418}