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, SparseElement};
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 + SparseElement + 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            + SparseElement
232            + NumAssign
233            + Send
234            + Sync
235            + Copy
236            + std::fmt::Debug
237            + scirs2_core::numeric::FromPrimitive,
238    {
239        let start_time = std::time::Instant::now();
240
241        let mut indptr = Vec::new();
242        let mut indices = Vec::new();
243        let mut data = Vec::new();
244
245        // Decompress each block
246        for block in compressed_matrix.get_blocks_row_major() {
247            // Check cache first
248            let decompressed_data =
249                if let Some(cached_data) = self.get_cached_block(&block.blockid)? {
250                    cached_data
251                } else {
252                    // Decompress and cache
253                    let decompressed =
254                        self.decompress_block(block, compressed_matrix.compression_algorithm)?;
255                    self.cache_block(&block.blockid, &decompressed)?;
256                    decompressed
257                };
258
259            // Parse decompressed data based on block type
260            match block.block_type {
261                BlockType::IndPtr => {
262                    indptr.extend(self.parse_indptr_data(&decompressed_data)?);
263                }
264                BlockType::Indices => {
265                    indices.extend(self.parse_indices_data(&decompressed_data)?);
266                }
267                BlockType::Data => {
268                    data.extend(self.parse_data_values::<T>(&decompressed_data)?);
269                }
270                BlockType::Combined => {
271                    let (block_indptr, block_indices, block_data) =
272                        self.parse_combined_data::<T>(&decompressed_data)?;
273                    indptr.extend(block_indptr);
274                    indices.extend(block_indices);
275                    data.extend(block_data);
276                }
277                BlockType::Metadata => {
278                    // Handle metadata blocks if needed
279                }
280            }
281        }
282
283        let decompression_time = start_time.elapsed().as_secs_f64();
284
285        // Update statistics
286        if let Ok(mut stats) = self.compression_stats.lock() {
287            stats.decompression_time += decompression_time;
288        }
289
290        // Record access pattern
291        self.record_matrix_access(compressed_matrix.matrixid, AccessType::Read);
292
293        Ok((indptr, indices, data))
294    }
295
296    /// Get memory usage statistics
297    pub fn get_memory_stats(&self) -> MemoryStats {
298        let current_usage = self.memory_usage.load(Ordering::Relaxed);
299        let usage_ratio = current_usage as f64 / self.config.memory_budget as f64;
300
301        let compression_stats = self.compression_stats.lock().unwrap().clone();
302        let cache_stats = self.get_cache_stats();
303
304        let mut memory_stats = MemoryStats::new(self.config.memory_budget, self.config.out_of_core);
305        memory_stats.update_memory_usage(current_usage);
306        memory_stats.compression_stats = compression_stats;
307        memory_stats.cache_hits = cache_stats.hits;
308        memory_stats.cache_misses = cache_stats.misses;
309        memory_stats.cache_hit_ratio = cache_stats.hit_ratio;
310        memory_stats
311    }
312
313    /// Get compression statistics
314    pub fn get_stats(&self) -> CompressionStats {
315        self.compression_stats.lock().unwrap().clone()
316    }
317
318    /// Optimize for sequential access patterns
319    pub fn optimize_for_sequential_access(&mut self) {
320        // Note: AccessTracker doesn't have set_access_pattern_hint method
321        // This optimization is handled internally by the tracker
322    }
323
324    /// Optimize for random access patterns
325    pub fn optimize_for_random_access(&mut self) {
326        // Note: AccessTracker doesn't have set_access_pattern_hint method
327        // This optimization is handled internally by the tracker
328    }
329
330    /// Clear cache and reset statistics
331    pub fn reset(&mut self) -> SparseResult<()> {
332        // Clear cache
333        if let Ok(mut cache) = self.block_cache.lock() {
334            cache.clear();
335        }
336
337        // Reset statistics
338        if let Ok(mut stats) = self.compression_stats.lock() {
339            *stats = CompressionStats::new();
340        }
341
342        // Reset access tracker
343        if let Ok(mut tracker) = self.access_tracker.lock() {
344            tracker.cleanup_old_patterns(0); // Remove all patterns
345        }
346
347        // Reset memory usage
348        self.memory_usage.store(0, Ordering::Relaxed);
349
350        // Cleanup out-of-core files
351        if let Some(ref mut manager) = self.out_of_core_manager {
352            manager.cleanup()?;
353        }
354
355        Ok(())
356    }
357
358    // Private helper methods
359
360    fn determine_compression_strategy(
361        &self,
362        matrix_id: u64,
363        rows: usize,
364        indptr: &[usize],
365        indices: &[usize],
366    ) -> SparseResult<CompressionStrategy> {
367        // Analyze matrix characteristics
368        let nnz = indices.len();
369        let density = if rows > 0 && !indices.is_empty() {
370            let max_col = *indices.iter().max().unwrap_or(&0);
371            nnz as f64 / (rows as f64 * (max_col + 1) as f64)
372        } else {
373            0.0
374        };
375
376        // Analyze sparsity patterns
377        let pattern_analysis = self.analyze_sparsity_patterns(indptr, indices);
378
379        // Check access patterns if available
380        let access_info = self.get_access_pattern_info(matrix_id);
381
382        // Select compression algorithm based on analysis
383        let algorithm = if self.config.adaptive_compression {
384            self.select_adaptive_algorithm(density, &pattern_analysis, &access_info)
385        } else {
386            self.config.compression_algorithm
387        };
388
389        // Determine block size
390        let block_size = self.determine_optimal_block_size(rows, nnz, density);
391
392        Ok(CompressionStrategy {
393            algorithm,
394            block_size,
395            hierarchical: self.config.hierarchical_compression,
396            predicted_ratio: self.predict_compression_ratio(algorithm, density, &pattern_analysis),
397        })
398    }
399
400    fn apply_compression_strategy<T>(
401        &mut self,
402        strategy: &CompressionStrategy,
403        matrix_id: u64,
404        rows: usize,
405        indptr: &[usize],
406        indices: &[usize],
407        data: &[T],
408    ) -> SparseResult<Vec<CompressedBlock>>
409    where
410        T: Float + SparseElement + NumAssign + Send + Sync + Copy + std::fmt::Debug,
411    {
412        // Serialize matrix components
413        let indptr_data = self.serialize_indptr(indptr)?;
414        let indices_data = self.serialize_indices(indices)?;
415        let data_data = self.serialize_data(data)?;
416
417        let mut blocks = Vec::new();
418
419        // Compress indptr
420        let indptr_block_id = BlockId::new(matrix_id, 0, 0);
421        let indptr_result = self.compression_engine.compress(
422            &indptr_data,
423            strategy.algorithm,
424            &indptr_block_id,
425            BlockType::IndPtr,
426        )?;
427        blocks.push(CompressedBlock::new(
428            indptr_block_id,
429            BlockType::IndPtr,
430            indptr_result.compressed_data,
431            indptr_data.len(),
432            (indptr_result.compression_ratio.clamp(1.0, 10.0) as u8).max(1),
433        ));
434
435        // Compress indices
436        let indices_block_id = BlockId::new(matrix_id, 0, 1);
437        let indices_result = self.compression_engine.compress(
438            &indices_data,
439            strategy.algorithm,
440            &indices_block_id,
441            BlockType::Indices,
442        )?;
443        blocks.push(CompressedBlock::new(
444            indices_block_id,
445            BlockType::Indices,
446            indices_result.compressed_data,
447            indices_data.len(),
448            (indices_result.compression_ratio.clamp(1.0, 10.0) as u8).max(1),
449        ));
450
451        // Compress data
452        let data_block_id = BlockId::new(matrix_id, 0, 2);
453        let data_result = self.compression_engine.compress(
454            &data_data,
455            strategy.algorithm,
456            &data_block_id,
457            BlockType::Data,
458        )?;
459        blocks.push(CompressedBlock::new(
460            data_block_id,
461            BlockType::Data,
462            data_result.compressed_data,
463            data_data.len(),
464            (data_result.compression_ratio.clamp(1.0, 10.0) as u8).max(1),
465        ));
466
467        Ok(blocks)
468    }
469
470    fn analyze_sparsity_patterns(
471        &self,
472        indptr: &[usize],
473        indices: &[usize],
474    ) -> SparsityPatternAnalysis {
475        let mut analysis = SparsityPatternAnalysis::default();
476
477        if indptr.len() <= 1 {
478            return analysis;
479        }
480
481        let rows = indptr.len() - 1;
482
483        // Analyze row distribution
484        let mut row_nnz = Vec::new();
485        for row in 0..rows {
486            row_nnz.push(indptr[row + 1] - indptr[row]);
487        }
488
489        analysis.avg_nnz_per_row = row_nnz.iter().sum::<usize>() as f64 / rows as f64;
490        analysis.max_nnz_per_row = *row_nnz.iter().max().unwrap_or(&0);
491        analysis.min_nnz_per_row = *row_nnz.iter().min().unwrap_or(&0);
492
493        // Analyze column patterns
494        analysis.sequential_patterns = self.count_sequential_patterns(indices);
495        analysis.clustering_factor = self.calculate_clustering_factor(indptr, indices);
496        analysis.bandwidth = self.calculate_bandwidth(indptr, indices);
497
498        analysis
499    }
500
501    fn count_sequential_patterns(&self, indices: &[usize]) -> usize {
502        let mut sequential_count = 0;
503        let mut current_sequence = 0;
504
505        for window in indices.windows(2) {
506            if window[1] == window[0] + 1 {
507                current_sequence += 1;
508            } else {
509                if current_sequence >= 3 {
510                    sequential_count += current_sequence;
511                }
512                current_sequence = 0;
513            }
514        }
515
516        if current_sequence >= 3 {
517            sequential_count += current_sequence;
518        }
519
520        sequential_count
521    }
522
523    fn calculate_clustering_factor(&self, indptr: &[usize], indices: &[usize]) -> f64 {
524        if indptr.len() <= 1 {
525            return 0.0;
526        }
527
528        let mut total_distance = 0.0;
529        let mut total_pairs = 0;
530
531        let rows = indptr.len() - 1;
532        for row in 0..rows {
533            let start = indptr[row];
534            let end = indptr[row + 1];
535
536            if end > start + 1 {
537                for i in start..(end - 1) {
538                    total_distance += (indices[i + 1] - indices[i]) as f64;
539                    total_pairs += 1;
540                }
541            }
542        }
543
544        if total_pairs > 0 {
545            total_distance / total_pairs as f64
546        } else {
547            0.0
548        }
549    }
550
551    fn calculate_bandwidth(&self, indptr: &[usize], indices: &[usize]) -> usize {
552        if indptr.len() <= 1 {
553            return 0;
554        }
555
556        let mut max_bandwidth = 0;
557        let rows = indptr.len() - 1;
558
559        for row in 0..rows {
560            let start = indptr[row];
561            let end = indptr[row + 1];
562
563            if end > start {
564                let min_col = indices[start];
565                let max_col = indices[end - 1];
566                let bandwidth = max_col.saturating_sub(min_col);
567                max_bandwidth = max_bandwidth.max(bandwidth);
568            }
569        }
570
571        max_bandwidth
572    }
573
574    fn get_access_pattern_info(&self, _matrix_id: u64) -> AccessPatternInfo {
575        let access_tracker = self.access_tracker.lock().unwrap();
576        // AccessTracker doesn't have get_matrix_access_info, so we'll use available methods
577        let stats = access_tracker.get_statistics();
578        AccessPatternInfo {
579            total_accesses: stats.total_access_events,
580            avg_temporal_locality: 0.5, // Default value
581            avg_spatial_locality: 0.5,  // Default value
582            pattern_count: stats.total_tracked_blocks,
583        }
584    }
585
586    fn select_adaptive_algorithm(
587        &self,
588        density: f64,
589        pattern_analysis: &SparsityPatternAnalysis,
590        access_info: &AccessPatternInfo,
591    ) -> CompressionAlgorithm {
592        // Decision tree for algorithm selection
593        if density > 0.1 {
594            // Dense matrices benefit from general compression
595            CompressionAlgorithm::LZ77
596        } else if pattern_analysis.sequential_patterns
597            > pattern_analysis.avg_nnz_per_row as usize * 10
598        {
599            // High sequential patterns favor RLE
600            CompressionAlgorithm::RLE
601        } else if pattern_analysis.clustering_factor < 2.0 {
602            // Low clustering suggests delta encoding
603            CompressionAlgorithm::Delta
604        } else if access_info.avg_temporal_locality > 0.8 {
605            // High temporal locality suggests sparse-optimized
606            CompressionAlgorithm::SparseOptimized
607        } else {
608            // Default to Huffman for general case
609            CompressionAlgorithm::Huffman
610        }
611    }
612
613    fn determine_optimal_block_size(&self, rows: usize, nnz: usize, density: f64) -> usize {
614        let base_block_size = self.config.block_size;
615
616        // Adjust block size based on matrix characteristics
617        let size_factor = if rows > 1_000_000 {
618            2.0 // Larger blocks for large matrices
619        } else if rows < 10_000 {
620            0.5 // Smaller blocks for small matrices
621        } else {
622            1.0
623        };
624
625        let density_factor = if density > 0.1 {
626            1.5 // Larger blocks for denser matrices
627        } else {
628            1.0
629        };
630
631        let nnz_factor = if nnz > 10_000_000 {
632            1.5 // Larger blocks for many non-zeros
633        } else {
634            1.0
635        };
636
637        let optimal_size =
638            (base_block_size as f64 * size_factor * density_factor * nnz_factor) as usize;
639        optimal_size.clamp(4096, 16 * 1024 * 1024) // 4KB to 16MB range
640    }
641
642    fn predict_compression_ratio(
643        &self,
644        algorithm: CompressionAlgorithm,
645        density: f64,
646        pattern_analysis: &SparsityPatternAnalysis,
647    ) -> f64 {
648        let base_ratio = algorithm.expected_compression_ratio();
649
650        // Adjust based on matrix characteristics
651        let adjustment = if pattern_analysis.bandwidth > 100000 {
652            0.8 // Lower compression for high bandwidth
653        } else if pattern_analysis.bandwidth < 100 {
654            1.2 // Higher compression for low bandwidth
655        } else {
656            1.0
657        };
658
659        base_ratio * adjustment
660    }
661
662    fn handle_out_of_core_storage(&mut self, blocks: &[CompressedBlock]) -> SparseResult<()> {
663        if let Some(ref mut manager) = self.out_of_core_manager {
664            for block in blocks {
665                if block.compressed_data.len() > self.config.block_size {
666                    // Store large blocks out-of-core
667                    manager.write_block_to_disk(block)?;
668                }
669            }
670        }
671        Ok(())
672    }
673
674    fn get_cached_block(&self, block_id: &BlockId) -> SparseResult<Option<Vec<u8>>> {
675        if let Ok(mut cache) = self.block_cache.lock() {
676            Ok(cache
677                .get(block_id)
678                .map(|cached_block| cached_block.data.clone()))
679        } else {
680            Ok(None)
681        }
682    }
683
684    fn cache_block(&self, block_id: &BlockId, data: &[u8]) -> SparseResult<()> {
685        use super::cache::CachedBlock;
686        if let Ok(mut cache) = self.block_cache.lock() {
687            let cached_block = CachedBlock::new(data.to_vec(), false, 1);
688            cache.insert(block_id.clone(), cached_block);
689        }
690        Ok(())
691    }
692
693    fn record_matrix_access(&self, matrix_id: u64, access_type: AccessType) {
694        if let Ok(mut tracker) = self.access_tracker.lock() {
695            let block_id = BlockId::new(matrix_id, 0, 0); // Default block position
696            tracker.record_access(block_id, access_type, 1024); // Default size
697        }
698    }
699
700    fn decompress_block(
701        &mut self,
702        block: &CompressedBlock,
703        algorithm: CompressionAlgorithm,
704    ) -> SparseResult<Vec<u8>> {
705        self.compression_engine
706            .decompress(&block.compressed_data, algorithm, block.original_size)
707    }
708
709    fn create_uncompressed_matrix<T>(
710        &self,
711        matrix_id: u64,
712        rows: usize,
713        indptr: &[usize],
714        indices: &[usize],
715        data: &[T],
716    ) -> SparseResult<CompressedMatrix<T>>
717    where
718        T: Float + SparseElement + NumAssign + Send + Sync + Copy + std::fmt::Debug,
719    {
720        let mut matrix = CompressedMatrix::new(
721            matrix_id,
722            rows,
723            if !indptr.is_empty() {
724                *indices.iter().max().unwrap_or(&0) + 1
725            } else {
726                0
727            },
728            CompressionAlgorithm::None,
729            self.config.block_size,
730        );
731
732        // Create uncompressed blocks
733        let indptr_data = self.serialize_indptr(indptr)?;
734        let indices_data = self.serialize_indices(indices)?;
735        let data_data = self.serialize_data(data)?;
736
737        matrix.add_block(CompressedBlock::new(
738            BlockId::new(matrix_id, 0, 0),
739            BlockType::IndPtr,
740            indptr_data.clone(),
741            indptr_data.len(),
742            0,
743        ));
744
745        matrix.add_block(CompressedBlock::new(
746            BlockId::new(matrix_id, 0, 1),
747            BlockType::Indices,
748            indices_data.clone(),
749            indices_data.len(),
750            0,
751        ));
752
753        matrix.add_block(CompressedBlock::new(
754            BlockId::new(matrix_id, 0, 2),
755            BlockType::Data,
756            data_data.clone(),
757            data_data.len(),
758            0,
759        ));
760
761        Ok(matrix)
762    }
763
764    fn update_compression_stats(
765        &self,
766        original_size: usize,
767        blocks: &[CompressedBlock],
768        compression_time: f64,
769    ) {
770        if let Ok(mut stats) = self.compression_stats.lock() {
771            let compressed_size = blocks
772                .iter()
773                .map(|b| b.compressed_data.len())
774                .sum::<usize>();
775
776            stats.update_compression(original_size, compressed_size, compression_time);
777        }
778    }
779
780    fn get_cache_stats(&self) -> CacheStats {
781        if let Ok(cache) = self.block_cache.lock() {
782            let stats = cache.get_stats();
783            CacheStats {
784                hits: stats.hit_count,
785                misses: stats.miss_count,
786                hit_ratio: if stats.hit_count + stats.miss_count > 0 {
787                    stats.hit_count as f64 / (stats.hit_count + stats.miss_count) as f64
788                } else {
789                    0.0
790                },
791            }
792        } else {
793            CacheStats {
794                hits: 0,
795                misses: 0,
796                hit_ratio: 0.0,
797            }
798        }
799    }
800
801    // Serialization methods
802    fn serialize_indptr(&self, indptr: &[usize]) -> SparseResult<Vec<u8>> {
803        let mut serialized = Vec::new();
804        serialized.extend_from_slice(&indptr.len().to_le_bytes());
805        for &value in indptr {
806            serialized.extend_from_slice(&value.to_le_bytes());
807        }
808        Ok(serialized)
809    }
810
811    fn serialize_indices(&self, indices: &[usize]) -> SparseResult<Vec<u8>> {
812        let mut serialized = Vec::new();
813        serialized.extend_from_slice(&indices.len().to_le_bytes());
814        for &value in indices {
815            serialized.extend_from_slice(&value.to_le_bytes());
816        }
817        Ok(serialized)
818    }
819
820    fn serialize_data<T>(&self, data: &[T]) -> SparseResult<Vec<u8>>
821    where
822        T: Float + SparseElement + NumAssign + Send + Sync + Copy,
823    {
824        let mut serialized = Vec::new();
825        serialized.extend_from_slice(&data.len().to_le_bytes());
826        for &value in data {
827            let bytes = value.to_f64().unwrap_or(0.0).to_le_bytes();
828            serialized.extend_from_slice(&bytes);
829        }
830        Ok(serialized)
831    }
832
833    // Parsing methods
834    fn parse_indptr_data(&self, data: &[u8]) -> SparseResult<Vec<usize>> {
835        if data.len() < 8 {
836            return Ok(Vec::new());
837        }
838
839        let length = usize::from_le_bytes([
840            data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7],
841        ]);
842        let mut indptr = Vec::with_capacity(length);
843
844        let mut offset = 8;
845        for _ in 0..length {
846            if offset + 8 <= data.len() {
847                let value = usize::from_le_bytes([
848                    data[offset],
849                    data[offset + 1],
850                    data[offset + 2],
851                    data[offset + 3],
852                    data[offset + 4],
853                    data[offset + 5],
854                    data[offset + 6],
855                    data[offset + 7],
856                ]);
857                indptr.push(value);
858                offset += 8;
859            }
860        }
861
862        Ok(indptr)
863    }
864
865    fn parse_indices_data(&self, data: &[u8]) -> SparseResult<Vec<usize>> {
866        self.parse_indptr_data(data) // Same format
867    }
868
869    fn parse_data_values<T>(&self, data: &[u8]) -> SparseResult<Vec<T>>
870    where
871        T: Float
872            + SparseElement
873            + NumAssign
874            + Send
875            + Sync
876            + Copy
877            + scirs2_core::numeric::FromPrimitive,
878    {
879        if data.len() < 8 {
880            return Ok(Vec::new());
881        }
882
883        let length = usize::from_le_bytes([
884            data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7],
885        ]);
886        let mut values = Vec::with_capacity(length);
887
888        let mut offset = 8;
889        for _ in 0..length {
890            if offset + 8 <= data.len() {
891                let value_f64 = f64::from_le_bytes([
892                    data[offset],
893                    data[offset + 1],
894                    data[offset + 2],
895                    data[offset + 3],
896                    data[offset + 4],
897                    data[offset + 5],
898                    data[offset + 6],
899                    data[offset + 7],
900                ]);
901                if let Some(value) = T::from_f64(value_f64) {
902                    values.push(value);
903                }
904                offset += 8;
905            }
906        }
907
908        Ok(values)
909    }
910
911    fn parse_combined_data<T>(&self, _data: &[u8]) -> SparseResult<(Vec<usize>, Vec<usize>, Vec<T>)>
912    where
913        T: Float + SparseElement + NumAssign + Send + Sync + Copy,
914    {
915        // Placeholder for combined data parsing
916        Ok((Vec::new(), Vec::new(), Vec::new()))
917    }
918}
919
920#[cfg(test)]
921mod tests {
922    use super::*;
923    use tempfile::TempDir;
924
925    #[test]
926    fn test_compressor_creation() {
927        let config = AdaptiveCompressionConfig::default();
928        let compressor = AdaptiveMemoryCompressor::new(config);
929        assert!(compressor.is_ok());
930    }
931
932    #[test]
933    fn test_matrix_compression_roundtrip() {
934        let config = AdaptiveCompressionConfig {
935            compression_algorithm: CompressionAlgorithm::None,
936            out_of_core: false,
937            memory_mapping: false,
938            ..Default::default()
939        };
940        let mut compressor = AdaptiveMemoryCompressor::new(config).unwrap();
941
942        let indptr = vec![0, 2, 3];
943        let indices = vec![0, 1, 1];
944        let data = vec![1.0, 2.0, 3.0];
945
946        let compressed = compressor
947            .compress_matrix(1, 2, &indptr, &indices, &data)
948            .unwrap();
949
950        let (decompressed_indptr, decompressed_indices, decompressed_data) =
951            compressor.decompress_matrix(&compressed).unwrap();
952
953        assert_eq!(decompressed_indptr, indptr);
954        assert_eq!(decompressed_indices, indices);
955        assert_eq!(decompressed_data.len(), data.len());
956    }
957
958    #[test]
959    fn test_memory_stats() {
960        let config = AdaptiveCompressionConfig::default();
961        let compressor = AdaptiveMemoryCompressor::new(config).unwrap();
962        let stats = compressor.get_memory_stats();
963
964        assert_eq!(stats.current_memory_usage, 0);
965        assert!(stats.memory_usage_ratio >= 0.0);
966    }
967
968    #[test]
969    fn test_access_pattern_optimization() {
970        let config = AdaptiveCompressionConfig::default();
971        let mut compressor = AdaptiveMemoryCompressor::new(config).unwrap();
972
973        // Test sequential optimization
974        compressor.optimize_for_sequential_access();
975
976        // Test random optimization
977        compressor.optimize_for_random_access();
978
979        // Should not panic (implicit in test succeeding)
980    }
981
982    #[test]
983    fn test_compressor_reset() {
984        let temp_dir = TempDir::new().unwrap();
985        let config = AdaptiveCompressionConfig {
986            temp_directory: temp_dir.path().to_str().unwrap().to_string(),
987            ..Default::default()
988        };
989        let mut compressor = AdaptiveMemoryCompressor::new(config).unwrap();
990
991        // Add some data
992        let indptr = vec![0, 1];
993        let indices = vec![0];
994        let data = vec![1.0];
995
996        let _ = compressor.compress_matrix(1, 1, &indptr, &indices, &data);
997
998        // Reset should work
999        let result = compressor.reset();
1000        assert!(result.is_ok());
1001
1002        let stats = compressor.get_memory_stats();
1003        assert_eq!(stats.current_memory_usage, 0);
1004    }
1005}