scirs2_sparse/adaptive_memory_compression/
compressor.rs

1//! Adaptive Memory Compressor
2//!
3//! This module contains the main compressor implementation that coordinates
4//! all compression, caching, and out-of-core operations.
5
6use super::access_tracking::{AccessEvent, AccessPattern, AccessTracker, AccessType};
7use super::cache::{BlockCache, BlockId};
8use super::compressed_data::{BlockType, CompressedBlock, CompressedMatrix};
9use super::compression::{CompressionEngine, CompressionResult};
10use super::config::{AdaptiveCompressionConfig, CompressionAlgorithm};
11use super::memory_mapping::{MemoryMappingConfig, MemoryMappingManager};
12use super::out_of_core::OutOfCoreManager;
13use super::stats::AccessPatternType;
14use super::stats::{CompressionMetadata, CompressionStats, MemoryStats};
15use crate::error::{SparseError, SparseResult};
16use scirs2_core::numeric::{Float, NumAssign};
17use std::marker::PhantomData;
18use std::sync::atomic::{AtomicUsize, Ordering};
19use std::sync::{Arc, Mutex};
20
21/// Adaptive memory compression manager
22pub struct AdaptiveMemoryCompressor {
23    config: AdaptiveCompressionConfig,
24    memory_usage: AtomicUsize,
25    compression_stats: Arc<Mutex<CompressionStats>>,
26    block_cache: Arc<Mutex<BlockCache>>,
27    access_tracker: Arc<Mutex<AccessTracker>>,
28    compression_engine: CompressionEngine,
29    hierarchical_levels: Vec<CompressionLevel>,
30    out_of_core_manager: Option<OutOfCoreManager>,
31    memory_mapping_manager: Option<MemoryMappingManager>,
32}
33
34/// Hierarchical compression level configuration
35#[derive(Debug, Clone)]
36struct CompressionLevel {
37    level: u8,
38    compression_ratio: f64,
39    algorithm: CompressionAlgorithm,
40    block_size: usize,
41    access_threshold: usize,
42}
43
44/// Compression strategy selection
45#[derive(Debug)]
46struct CompressionStrategy {
47    algorithm: CompressionAlgorithm,
48    block_size: usize,
49    hierarchical: bool,
50    predicted_ratio: f64,
51}
52
53/// Sparsity pattern analysis results
54#[derive(Debug, Default)]
55struct SparsityPatternAnalysis {
56    avg_nnz_per_row: f64,
57    max_nnz_per_row: usize,
58    min_nnz_per_row: usize,
59    sequential_patterns: usize,
60    clustering_factor: f64,
61    bandwidth: usize,
62}
63
64/// Access pattern information for a matrix
65#[derive(Debug, Default)]
66struct AccessPatternInfo {
67    total_accesses: usize,
68    avg_temporal_locality: f64,
69    avg_spatial_locality: f64,
70    pattern_count: usize,
71}
72
73/// Cache statistics for internal use
74#[derive(Debug)]
75struct CacheStats {
76    hits: usize,
77    misses: usize,
78    hit_ratio: f64,
79}
80
81impl AdaptiveMemoryCompressor {
82    /// Create a new adaptive memory compressor
83    pub fn new(config: AdaptiveCompressionConfig) -> SparseResult<Self> {
84        let block_cache = BlockCache::new(config.cache_size);
85        let access_tracker = AccessTracker::new(); // Default capacity
86        let compression_engine = CompressionEngine::new();
87
88        // Initialize hierarchical compression levels
89        let hierarchical_levels = vec![
90            CompressionLevel {
91                level: 1,
92                compression_ratio: 2.0,
93                algorithm: CompressionAlgorithm::RLE,
94                block_size: config.block_size,
95                access_threshold: 100,
96            },
97            CompressionLevel {
98                level: 2,
99                compression_ratio: 4.0,
100                algorithm: CompressionAlgorithm::Delta,
101                block_size: config.block_size / 2,
102                access_threshold: 50,
103            },
104            CompressionLevel {
105                level: 3,
106                compression_ratio: 8.0,
107                algorithm: CompressionAlgorithm::LZ77,
108                block_size: config.block_size / 4,
109                access_threshold: 10,
110            },
111        ];
112
113        // Initialize out-of-core manager if enabled
114        let out_of_core_manager = if config.out_of_core {
115            Some(OutOfCoreManager::new(&config.temp_directory)?)
116        } else {
117            None
118        };
119
120        // Initialize memory mapping manager if enabled
121        let memory_mapping_manager = if config.memory_mapping {
122            let mapping_config = MemoryMappingConfig {
123                read_only: false,
124                write_through: true,
125                prefetch: true,
126                page_size_hint: 4096,
127            };
128            Some(MemoryMappingManager::new(mapping_config))
129        } else {
130            None
131        };
132
133        Ok(Self {
134            config,
135            memory_usage: AtomicUsize::new(0),
136            compression_stats: Arc::new(Mutex::new(CompressionStats::new())),
137            block_cache: Arc::new(Mutex::new(block_cache)),
138            access_tracker: Arc::new(Mutex::new(access_tracker)),
139            compression_engine,
140            hierarchical_levels,
141            out_of_core_manager,
142            memory_mapping_manager,
143        })
144    }
145
146    /// Compress sparse matrix data adaptively
147    #[allow(clippy::too_many_arguments)]
148    pub fn compress_matrix<T>(
149        &mut self,
150        matrix_id: u64,
151        rows: usize,
152        indptr: &[usize],
153        indices: &[usize],
154        data: &[T],
155    ) -> SparseResult<CompressedMatrix<T>>
156    where
157        T: Float + NumAssign + Send + Sync + Copy + std::fmt::Debug,
158    {
159        let total_size = std::mem::size_of_val(indptr)
160            + std::mem::size_of_val(indices)
161            + std::mem::size_of_val(data);
162
163        // Check if compression is needed
164        let current_usage = self.memory_usage.load(Ordering::Relaxed);
165        let usage_ratio = (current_usage + total_size) as f64 / self.config.memory_budget as f64;
166
167        if usage_ratio < self.config.compression_threshold && !self.config.adaptive_compression {
168            // No compression needed
169            return self.create_uncompressed_matrix(matrix_id, rows, indptr, indices, data);
170        }
171
172        let start_time = std::time::Instant::now();
173
174        // Determine optimal compression strategy
175        let compression_strategy =
176            self.determine_compression_strategy(matrix_id, rows, indptr, indices)?;
177
178        // Apply compression based on strategy
179        let compressed_blocks = self.apply_compression_strategy(
180            &compression_strategy,
181            matrix_id,
182            rows,
183            indptr,
184            indices,
185            data,
186        )?;
187
188        let compression_time = start_time.elapsed().as_secs_f64();
189
190        // Update statistics
191        self.update_compression_stats(total_size, &compressed_blocks, compression_time);
192
193        // Update memory usage
194        let compressed_size = compressed_blocks
195            .iter()
196            .map(|b| b.compressed_data.len())
197            .sum::<usize>();
198        self.memory_usage
199            .fetch_add(compressed_size, Ordering::Relaxed);
200
201        // Handle out-of-core storage if needed
202        self.handle_out_of_core_storage(&compressed_blocks)?;
203
204        let mut compressed_matrix = CompressedMatrix::new(
205            matrix_id,
206            rows,
207            if !indptr.is_empty() {
208                *indices.iter().max().unwrap_or(&0) + 1
209            } else {
210                0
211            },
212            compression_strategy.algorithm,
213            compression_strategy.block_size,
214        );
215
216        // Add the compressed blocks to the matrix
217        for block in compressed_blocks {
218            compressed_matrix.add_block(block);
219        }
220
221        Ok(compressed_matrix)
222    }
223
224    /// Decompress matrix data
225    pub fn decompress_matrix<T>(
226        &mut self,
227        compressed_matrix: &CompressedMatrix<T>,
228    ) -> SparseResult<(Vec<usize>, Vec<usize>, Vec<T>)>
229    where
230        T: Float
231            + NumAssign
232            + Send
233            + Sync
234            + Copy
235            + std::fmt::Debug
236            + scirs2_core::numeric::FromPrimitive,
237    {
238        let start_time = std::time::Instant::now();
239
240        let mut indptr = Vec::new();
241        let mut indices = Vec::new();
242        let mut data = Vec::new();
243
244        // Decompress each block
245        for block in compressed_matrix.get_blocks_row_major() {
246            // Check cache first
247            let decompressed_data =
248                if let Some(cached_data) = self.get_cached_block(&block.blockid)? {
249                    cached_data
250                } else {
251                    // Decompress and cache
252                    let decompressed =
253                        self.decompress_block(block, compressed_matrix.compression_algorithm)?;
254                    self.cache_block(&block.blockid, &decompressed)?;
255                    decompressed
256                };
257
258            // Parse decompressed data based on block type
259            match block.block_type {
260                BlockType::IndPtr => {
261                    indptr.extend(self.parse_indptr_data(&decompressed_data)?);
262                }
263                BlockType::Indices => {
264                    indices.extend(self.parse_indices_data(&decompressed_data)?);
265                }
266                BlockType::Data => {
267                    data.extend(self.parse_data_values::<T>(&decompressed_data)?);
268                }
269                BlockType::Combined => {
270                    let (block_indptr, block_indices, block_data) =
271                        self.parse_combined_data::<T>(&decompressed_data)?;
272                    indptr.extend(block_indptr);
273                    indices.extend(block_indices);
274                    data.extend(block_data);
275                }
276                BlockType::Metadata => {
277                    // Handle metadata blocks if needed
278                }
279            }
280        }
281
282        let decompression_time = start_time.elapsed().as_secs_f64();
283
284        // Update statistics
285        if let Ok(mut stats) = self.compression_stats.lock() {
286            stats.decompression_time += decompression_time;
287        }
288
289        // Record access pattern
290        self.record_matrix_access(compressed_matrix.matrixid, AccessType::Read);
291
292        Ok((indptr, indices, data))
293    }
294
295    /// Get memory usage statistics
296    pub fn get_memory_stats(&self) -> MemoryStats {
297        let current_usage = self.memory_usage.load(Ordering::Relaxed);
298        let usage_ratio = current_usage as f64 / self.config.memory_budget as f64;
299
300        let compression_stats = self.compression_stats.lock().unwrap().clone();
301        let cache_stats = self.get_cache_stats();
302
303        let mut memory_stats = MemoryStats::new(self.config.memory_budget, self.config.out_of_core);
304        memory_stats.update_memory_usage(current_usage);
305        memory_stats.compression_stats = compression_stats;
306        memory_stats.cache_hits = cache_stats.hits;
307        memory_stats.cache_misses = cache_stats.misses;
308        memory_stats.cache_hit_ratio = cache_stats.hit_ratio;
309        memory_stats
310    }
311
312    /// Get compression statistics
313    pub fn get_stats(&self) -> CompressionStats {
314        self.compression_stats.lock().unwrap().clone()
315    }
316
317    /// Optimize for sequential access patterns
318    pub fn optimize_for_sequential_access(&mut self) {
319        // Note: AccessTracker doesn't have set_access_pattern_hint method
320        // This optimization is handled internally by the tracker
321    }
322
323    /// Optimize for random access patterns
324    pub fn optimize_for_random_access(&mut self) {
325        // Note: AccessTracker doesn't have set_access_pattern_hint method
326        // This optimization is handled internally by the tracker
327    }
328
329    /// Clear cache and reset statistics
330    pub fn reset(&mut self) -> SparseResult<()> {
331        // Clear cache
332        if let Ok(mut cache) = self.block_cache.lock() {
333            cache.clear();
334        }
335
336        // Reset statistics
337        if let Ok(mut stats) = self.compression_stats.lock() {
338            *stats = CompressionStats::new();
339        }
340
341        // Reset access tracker
342        if let Ok(mut tracker) = self.access_tracker.lock() {
343            tracker.cleanup_old_patterns(0); // Remove all patterns
344        }
345
346        // Reset memory usage
347        self.memory_usage.store(0, Ordering::Relaxed);
348
349        // Cleanup out-of-core files
350        if let Some(ref mut manager) = self.out_of_core_manager {
351            manager.cleanup()?;
352        }
353
354        Ok(())
355    }
356
357    // Private helper methods
358
359    fn determine_compression_strategy(
360        &self,
361        matrix_id: u64,
362        rows: usize,
363        indptr: &[usize],
364        indices: &[usize],
365    ) -> SparseResult<CompressionStrategy> {
366        // Analyze matrix characteristics
367        let nnz = indices.len();
368        let density = if rows > 0 && !indices.is_empty() {
369            let max_col = *indices.iter().max().unwrap_or(&0);
370            nnz as f64 / (rows as f64 * (max_col + 1) as f64)
371        } else {
372            0.0
373        };
374
375        // Analyze sparsity patterns
376        let pattern_analysis = self.analyze_sparsity_patterns(indptr, indices);
377
378        // Check access patterns if available
379        let access_info = self.get_access_pattern_info(matrix_id);
380
381        // Select compression algorithm based on analysis
382        let algorithm = if self.config.adaptive_compression {
383            self.select_adaptive_algorithm(density, &pattern_analysis, &access_info)
384        } else {
385            self.config.compression_algorithm
386        };
387
388        // Determine block size
389        let block_size = self.determine_optimal_block_size(rows, nnz, density);
390
391        Ok(CompressionStrategy {
392            algorithm,
393            block_size,
394            hierarchical: self.config.hierarchical_compression,
395            predicted_ratio: self.predict_compression_ratio(algorithm, density, &pattern_analysis),
396        })
397    }
398
399    fn apply_compression_strategy<T>(
400        &mut self,
401        strategy: &CompressionStrategy,
402        matrix_id: u64,
403        rows: usize,
404        indptr: &[usize],
405        indices: &[usize],
406        data: &[T],
407    ) -> SparseResult<Vec<CompressedBlock>>
408    where
409        T: Float + NumAssign + Send + Sync + Copy + std::fmt::Debug,
410    {
411        // Serialize matrix components
412        let indptr_data = self.serialize_indptr(indptr)?;
413        let indices_data = self.serialize_indices(indices)?;
414        let data_data = self.serialize_data(data)?;
415
416        let mut blocks = Vec::new();
417
418        // Compress indptr
419        let indptr_block_id = BlockId::new(matrix_id, 0, 0);
420        let indptr_result = self.compression_engine.compress(
421            &indptr_data,
422            strategy.algorithm,
423            &indptr_block_id,
424            BlockType::IndPtr,
425        )?;
426        blocks.push(CompressedBlock::new(
427            indptr_block_id,
428            BlockType::IndPtr,
429            indptr_result.compressed_data,
430            indptr_data.len(),
431            (indptr_result.compression_ratio.clamp(1.0, 10.0) as u8).max(1),
432        ));
433
434        // Compress indices
435        let indices_block_id = BlockId::new(matrix_id, 0, 1);
436        let indices_result = self.compression_engine.compress(
437            &indices_data,
438            strategy.algorithm,
439            &indices_block_id,
440            BlockType::Indices,
441        )?;
442        blocks.push(CompressedBlock::new(
443            indices_block_id,
444            BlockType::Indices,
445            indices_result.compressed_data,
446            indices_data.len(),
447            (indices_result.compression_ratio.clamp(1.0, 10.0) as u8).max(1),
448        ));
449
450        // Compress data
451        let data_block_id = BlockId::new(matrix_id, 0, 2);
452        let data_result = self.compression_engine.compress(
453            &data_data,
454            strategy.algorithm,
455            &data_block_id,
456            BlockType::Data,
457        )?;
458        blocks.push(CompressedBlock::new(
459            data_block_id,
460            BlockType::Data,
461            data_result.compressed_data,
462            data_data.len(),
463            (data_result.compression_ratio.clamp(1.0, 10.0) as u8).max(1),
464        ));
465
466        Ok(blocks)
467    }
468
469    fn analyze_sparsity_patterns(
470        &self,
471        indptr: &[usize],
472        indices: &[usize],
473    ) -> SparsityPatternAnalysis {
474        let mut analysis = SparsityPatternAnalysis::default();
475
476        if indptr.len() <= 1 {
477            return analysis;
478        }
479
480        let rows = indptr.len() - 1;
481
482        // Analyze row distribution
483        let mut row_nnz = Vec::new();
484        for row in 0..rows {
485            row_nnz.push(indptr[row + 1] - indptr[row]);
486        }
487
488        analysis.avg_nnz_per_row = row_nnz.iter().sum::<usize>() as f64 / rows as f64;
489        analysis.max_nnz_per_row = *row_nnz.iter().max().unwrap_or(&0);
490        analysis.min_nnz_per_row = *row_nnz.iter().min().unwrap_or(&0);
491
492        // Analyze column patterns
493        analysis.sequential_patterns = self.count_sequential_patterns(indices);
494        analysis.clustering_factor = self.calculate_clustering_factor(indptr, indices);
495        analysis.bandwidth = self.calculate_bandwidth(indptr, indices);
496
497        analysis
498    }
499
500    fn count_sequential_patterns(&self, indices: &[usize]) -> usize {
501        let mut sequential_count = 0;
502        let mut current_sequence = 0;
503
504        for window in indices.windows(2) {
505            if window[1] == window[0] + 1 {
506                current_sequence += 1;
507            } else {
508                if current_sequence >= 3 {
509                    sequential_count += current_sequence;
510                }
511                current_sequence = 0;
512            }
513        }
514
515        if current_sequence >= 3 {
516            sequential_count += current_sequence;
517        }
518
519        sequential_count
520    }
521
522    fn calculate_clustering_factor(&self, indptr: &[usize], indices: &[usize]) -> f64 {
523        if indptr.len() <= 1 {
524            return 0.0;
525        }
526
527        let mut total_distance = 0.0;
528        let mut total_pairs = 0;
529
530        let rows = indptr.len() - 1;
531        for row in 0..rows {
532            let start = indptr[row];
533            let end = indptr[row + 1];
534
535            if end > start + 1 {
536                for i in start..(end - 1) {
537                    total_distance += (indices[i + 1] - indices[i]) as f64;
538                    total_pairs += 1;
539                }
540            }
541        }
542
543        if total_pairs > 0 {
544            total_distance / total_pairs as f64
545        } else {
546            0.0
547        }
548    }
549
550    fn calculate_bandwidth(&self, indptr: &[usize], indices: &[usize]) -> usize {
551        if indptr.len() <= 1 {
552            return 0;
553        }
554
555        let mut max_bandwidth = 0;
556        let rows = indptr.len() - 1;
557
558        for row in 0..rows {
559            let start = indptr[row];
560            let end = indptr[row + 1];
561
562            if end > start {
563                let min_col = indices[start];
564                let max_col = indices[end - 1];
565                let bandwidth = max_col.saturating_sub(min_col);
566                max_bandwidth = max_bandwidth.max(bandwidth);
567            }
568        }
569
570        max_bandwidth
571    }
572
573    fn get_access_pattern_info(&self, _matrix_id: u64) -> AccessPatternInfo {
574        let access_tracker = self.access_tracker.lock().unwrap();
575        // AccessTracker doesn't have get_matrix_access_info, so we'll use available methods
576        let stats = access_tracker.get_statistics();
577        AccessPatternInfo {
578            total_accesses: stats.total_access_events,
579            avg_temporal_locality: 0.5, // Default value
580            avg_spatial_locality: 0.5,  // Default value
581            pattern_count: stats.total_tracked_blocks,
582        }
583    }
584
585    fn select_adaptive_algorithm(
586        &self,
587        density: f64,
588        pattern_analysis: &SparsityPatternAnalysis,
589        access_info: &AccessPatternInfo,
590    ) -> CompressionAlgorithm {
591        // Decision tree for algorithm selection
592        if density > 0.1 {
593            // Dense matrices benefit from general compression
594            CompressionAlgorithm::LZ77
595        } else if pattern_analysis.sequential_patterns
596            > pattern_analysis.avg_nnz_per_row as usize * 10
597        {
598            // High sequential patterns favor RLE
599            CompressionAlgorithm::RLE
600        } else if pattern_analysis.clustering_factor < 2.0 {
601            // Low clustering suggests delta encoding
602            CompressionAlgorithm::Delta
603        } else if access_info.avg_temporal_locality > 0.8 {
604            // High temporal locality suggests sparse-optimized
605            CompressionAlgorithm::SparseOptimized
606        } else {
607            // Default to Huffman for general case
608            CompressionAlgorithm::Huffman
609        }
610    }
611
612    fn determine_optimal_block_size(&self, rows: usize, nnz: usize, density: f64) -> usize {
613        let base_block_size = self.config.block_size;
614
615        // Adjust block size based on matrix characteristics
616        let size_factor = if rows > 1_000_000 {
617            2.0 // Larger blocks for large matrices
618        } else if rows < 10_000 {
619            0.5 // Smaller blocks for small matrices
620        } else {
621            1.0
622        };
623
624        let density_factor = if density > 0.1 {
625            1.5 // Larger blocks for denser matrices
626        } else {
627            1.0
628        };
629
630        let nnz_factor = if nnz > 10_000_000 {
631            1.5 // Larger blocks for many non-zeros
632        } else {
633            1.0
634        };
635
636        let optimal_size =
637            (base_block_size as f64 * size_factor * density_factor * nnz_factor) as usize;
638        optimal_size.clamp(4096, 16 * 1024 * 1024) // 4KB to 16MB range
639    }
640
641    fn predict_compression_ratio(
642        &self,
643        algorithm: CompressionAlgorithm,
644        density: f64,
645        pattern_analysis: &SparsityPatternAnalysis,
646    ) -> f64 {
647        let base_ratio = algorithm.expected_compression_ratio();
648
649        // Adjust based on matrix characteristics
650        let adjustment = if pattern_analysis.bandwidth > 100000 {
651            0.8 // Lower compression for high bandwidth
652        } else if pattern_analysis.bandwidth < 100 {
653            1.2 // Higher compression for low bandwidth
654        } else {
655            1.0
656        };
657
658        base_ratio * adjustment
659    }
660
661    fn handle_out_of_core_storage(&mut self, blocks: &[CompressedBlock]) -> SparseResult<()> {
662        if let Some(ref mut manager) = self.out_of_core_manager {
663            for block in blocks {
664                if block.compressed_data.len() > self.config.block_size {
665                    // Store large blocks out-of-core
666                    manager.write_block_to_disk(block)?;
667                }
668            }
669        }
670        Ok(())
671    }
672
673    fn get_cached_block(&self, block_id: &BlockId) -> SparseResult<Option<Vec<u8>>> {
674        if let Ok(mut cache) = self.block_cache.lock() {
675            Ok(cache
676                .get(block_id)
677                .map(|cached_block| cached_block.data.clone()))
678        } else {
679            Ok(None)
680        }
681    }
682
683    fn cache_block(&self, block_id: &BlockId, data: &[u8]) -> SparseResult<()> {
684        use super::cache::CachedBlock;
685        if let Ok(mut cache) = self.block_cache.lock() {
686            let cached_block = CachedBlock::new(data.to_vec(), false, 1);
687            cache.insert(block_id.clone(), cached_block);
688        }
689        Ok(())
690    }
691
692    fn record_matrix_access(&self, matrix_id: u64, access_type: AccessType) {
693        if let Ok(mut tracker) = self.access_tracker.lock() {
694            let block_id = BlockId::new(matrix_id, 0, 0); // Default block position
695            tracker.record_access(block_id, access_type, 1024); // Default size
696        }
697    }
698
699    fn decompress_block(
700        &mut self,
701        block: &CompressedBlock,
702        algorithm: CompressionAlgorithm,
703    ) -> SparseResult<Vec<u8>> {
704        self.compression_engine
705            .decompress(&block.compressed_data, algorithm, block.original_size)
706    }
707
708    fn create_uncompressed_matrix<T>(
709        &self,
710        matrix_id: u64,
711        rows: usize,
712        indptr: &[usize],
713        indices: &[usize],
714        data: &[T],
715    ) -> SparseResult<CompressedMatrix<T>>
716    where
717        T: Float + NumAssign + Send + Sync + Copy + std::fmt::Debug,
718    {
719        let mut matrix = CompressedMatrix::new(
720            matrix_id,
721            rows,
722            if !indptr.is_empty() {
723                *indices.iter().max().unwrap_or(&0) + 1
724            } else {
725                0
726            },
727            CompressionAlgorithm::None,
728            self.config.block_size,
729        );
730
731        // Create uncompressed blocks
732        let indptr_data = self.serialize_indptr(indptr)?;
733        let indices_data = self.serialize_indices(indices)?;
734        let data_data = self.serialize_data(data)?;
735
736        matrix.add_block(CompressedBlock::new(
737            BlockId::new(matrix_id, 0, 0),
738            BlockType::IndPtr,
739            indptr_data.clone(),
740            indptr_data.len(),
741            0,
742        ));
743
744        matrix.add_block(CompressedBlock::new(
745            BlockId::new(matrix_id, 0, 1),
746            BlockType::Indices,
747            indices_data.clone(),
748            indices_data.len(),
749            0,
750        ));
751
752        matrix.add_block(CompressedBlock::new(
753            BlockId::new(matrix_id, 0, 2),
754            BlockType::Data,
755            data_data.clone(),
756            data_data.len(),
757            0,
758        ));
759
760        Ok(matrix)
761    }
762
763    fn update_compression_stats(
764        &self,
765        original_size: usize,
766        blocks: &[CompressedBlock],
767        compression_time: f64,
768    ) {
769        if let Ok(mut stats) = self.compression_stats.lock() {
770            let compressed_size = blocks
771                .iter()
772                .map(|b| b.compressed_data.len())
773                .sum::<usize>();
774
775            stats.update_compression(original_size, compressed_size, compression_time);
776        }
777    }
778
779    fn get_cache_stats(&self) -> CacheStats {
780        if let Ok(cache) = self.block_cache.lock() {
781            let stats = cache.get_stats();
782            CacheStats {
783                hits: stats.hit_count,
784                misses: stats.miss_count,
785                hit_ratio: if stats.hit_count + stats.miss_count > 0 {
786                    stats.hit_count as f64 / (stats.hit_count + stats.miss_count) as f64
787                } else {
788                    0.0
789                },
790            }
791        } else {
792            CacheStats {
793                hits: 0,
794                misses: 0,
795                hit_ratio: 0.0,
796            }
797        }
798    }
799
800    // Serialization methods
801    fn serialize_indptr(&self, indptr: &[usize]) -> SparseResult<Vec<u8>> {
802        let mut serialized = Vec::new();
803        serialized.extend_from_slice(&indptr.len().to_le_bytes());
804        for &value in indptr {
805            serialized.extend_from_slice(&value.to_le_bytes());
806        }
807        Ok(serialized)
808    }
809
810    fn serialize_indices(&self, indices: &[usize]) -> SparseResult<Vec<u8>> {
811        let mut serialized = Vec::new();
812        serialized.extend_from_slice(&indices.len().to_le_bytes());
813        for &value in indices {
814            serialized.extend_from_slice(&value.to_le_bytes());
815        }
816        Ok(serialized)
817    }
818
819    fn serialize_data<T>(&self, data: &[T]) -> SparseResult<Vec<u8>>
820    where
821        T: Float + NumAssign + Send + Sync + Copy,
822    {
823        let mut serialized = Vec::new();
824        serialized.extend_from_slice(&data.len().to_le_bytes());
825        for &value in data {
826            let bytes = value.to_f64().unwrap_or(0.0).to_le_bytes();
827            serialized.extend_from_slice(&bytes);
828        }
829        Ok(serialized)
830    }
831
832    // Parsing methods
833    fn parse_indptr_data(&self, data: &[u8]) -> SparseResult<Vec<usize>> {
834        if data.len() < 8 {
835            return Ok(Vec::new());
836        }
837
838        let length = usize::from_le_bytes([
839            data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7],
840        ]);
841        let mut indptr = Vec::with_capacity(length);
842
843        let mut offset = 8;
844        for _ in 0..length {
845            if offset + 8 <= data.len() {
846                let value = usize::from_le_bytes([
847                    data[offset],
848                    data[offset + 1],
849                    data[offset + 2],
850                    data[offset + 3],
851                    data[offset + 4],
852                    data[offset + 5],
853                    data[offset + 6],
854                    data[offset + 7],
855                ]);
856                indptr.push(value);
857                offset += 8;
858            }
859        }
860
861        Ok(indptr)
862    }
863
864    fn parse_indices_data(&self, data: &[u8]) -> SparseResult<Vec<usize>> {
865        self.parse_indptr_data(data) // Same format
866    }
867
868    fn parse_data_values<T>(&self, data: &[u8]) -> SparseResult<Vec<T>>
869    where
870        T: Float + NumAssign + Send + Sync + Copy + scirs2_core::numeric::FromPrimitive,
871    {
872        if data.len() < 8 {
873            return Ok(Vec::new());
874        }
875
876        let length = usize::from_le_bytes([
877            data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7],
878        ]);
879        let mut values = Vec::with_capacity(length);
880
881        let mut offset = 8;
882        for _ in 0..length {
883            if offset + 8 <= data.len() {
884                let value_f64 = f64::from_le_bytes([
885                    data[offset],
886                    data[offset + 1],
887                    data[offset + 2],
888                    data[offset + 3],
889                    data[offset + 4],
890                    data[offset + 5],
891                    data[offset + 6],
892                    data[offset + 7],
893                ]);
894                if let Some(value) = T::from_f64(value_f64) {
895                    values.push(value);
896                }
897                offset += 8;
898            }
899        }
900
901        Ok(values)
902    }
903
904    fn parse_combined_data<T>(&self, _data: &[u8]) -> SparseResult<(Vec<usize>, Vec<usize>, Vec<T>)>
905    where
906        T: Float + NumAssign + Send + Sync + Copy,
907    {
908        // Placeholder for combined data parsing
909        Ok((Vec::new(), Vec::new(), Vec::new()))
910    }
911}
912
913#[cfg(test)]
914mod tests {
915    use super::*;
916    use tempfile::TempDir;
917
918    #[test]
919    fn test_compressor_creation() {
920        let config = AdaptiveCompressionConfig::default();
921        let compressor = AdaptiveMemoryCompressor::new(config);
922        assert!(compressor.is_ok());
923    }
924
925    #[test]
926    fn test_matrix_compression_roundtrip() {
927        let config = AdaptiveCompressionConfig {
928            compression_algorithm: CompressionAlgorithm::None,
929            out_of_core: false,
930            memory_mapping: false,
931            ..Default::default()
932        };
933        let mut compressor = AdaptiveMemoryCompressor::new(config).unwrap();
934
935        let indptr = vec![0, 2, 3];
936        let indices = vec![0, 1, 1];
937        let data = vec![1.0, 2.0, 3.0];
938
939        let compressed = compressor
940            .compress_matrix(1, 2, &indptr, &indices, &data)
941            .unwrap();
942
943        let (decompressed_indptr, decompressed_indices, decompressed_data) =
944            compressor.decompress_matrix(&compressed).unwrap();
945
946        assert_eq!(decompressed_indptr, indptr);
947        assert_eq!(decompressed_indices, indices);
948        assert_eq!(decompressed_data.len(), data.len());
949    }
950
951    #[test]
952    fn test_memory_stats() {
953        let config = AdaptiveCompressionConfig::default();
954        let compressor = AdaptiveMemoryCompressor::new(config).unwrap();
955        let stats = compressor.get_memory_stats();
956
957        assert_eq!(stats.current_memory_usage, 0);
958        assert!(stats.memory_usage_ratio >= 0.0);
959    }
960
961    #[test]
962    fn test_access_pattern_optimization() {
963        let config = AdaptiveCompressionConfig::default();
964        let mut compressor = AdaptiveMemoryCompressor::new(config).unwrap();
965
966        // Test sequential optimization
967        compressor.optimize_for_sequential_access();
968
969        // Test random optimization
970        compressor.optimize_for_random_access();
971
972        // Should not panic (implicit in test succeeding)
973    }
974
975    #[test]
976    fn test_compressor_reset() {
977        let temp_dir = TempDir::new().unwrap();
978        let config = AdaptiveCompressionConfig {
979            temp_directory: temp_dir.path().to_str().unwrap().to_string(),
980            ..Default::default()
981        };
982        let mut compressor = AdaptiveMemoryCompressor::new(config).unwrap();
983
984        // Add some data
985        let indptr = vec![0, 1];
986        let indices = vec![0];
987        let data = vec![1.0];
988
989        let _ = compressor.compress_matrix(1, 1, &indptr, &indices, &data);
990
991        // Reset should work
992        let result = compressor.reset();
993        assert!(result.is_ok());
994
995        let stats = compressor.get_memory_stats();
996        assert_eq!(stats.current_memory_usage, 0);
997    }
998}