quantrs2_core/
compilation_cache.rs

1//! Gate compilation caching with persistent storage
2//!
3//! This module provides a high-performance caching system for compiled quantum gates,
4//! with support for persistent storage to disk, automatic cache management, and
5//! concurrent access patterns.
6
7use crate::{
8    error::{QuantRS2Error, QuantRS2Result},
9    gate::GateOp,
10};
11use num_complex::Complex64;
12use serde::{Deserialize, Serialize};
13use std::{
14    collections::{HashMap, VecDeque},
15    fs::{self, File},
16    io::{BufReader, BufWriter},
17    path::{Path, PathBuf},
18    sync::{Arc, OnceLock, RwLock},
19    time::{Duration, SystemTime, UNIX_EPOCH},
20};
21// use sha2::{Sha256, Digest}; // Disabled for simplified implementation
22use std::collections::hash_map::DefaultHasher;
23use std::hash::{Hash, Hasher};
24
25/// Compiled gate representation
26#[derive(Debug, Clone, Serialize, Deserialize)]
27pub struct CompiledGate {
28    /// Unique identifier for the gate
29    pub gate_id: String,
30    /// Gate matrix elements (row-major order)
31    pub matrix: Vec<Complex64>,
32    /// Number of qubits the gate acts on
33    pub num_qubits: usize,
34    /// Optimized representations
35    pub optimizations: GateOptimizations,
36    /// Metadata about the compilation
37    pub metadata: CompilationMetadata,
38}
39
40/// Optimized representations of a gate
41#[derive(Debug, Clone, Serialize, Deserialize)]
42pub struct GateOptimizations {
43    /// Diagonal representation if applicable
44    pub diagonal: Option<Vec<Complex64>>,
45    /// Decomposition into simpler gates
46    pub decomposition: Option<GateDecomposition>,
47    /// SIMD-optimized matrix layout
48    pub simd_layout: Option<SimdLayout>,
49    /// GPU kernel identifier
50    pub gpu_kernel_id: Option<String>,
51    /// Tensor network representation
52    pub tensor_network: Option<TensorNetworkRep>,
53}
54
55/// Gate decomposition into simpler gates
56#[derive(Debug, Clone, Serialize, Deserialize)]
57pub struct GateDecomposition {
58    /// Sequence of gate identifiers
59    pub gates: Vec<String>,
60    /// Parameters for parametric gates
61    pub parameters: Vec<Vec<f64>>,
62    /// Target qubits for each gate
63    pub targets: Vec<Vec<usize>>,
64    /// Total gate count
65    pub gate_count: usize,
66    /// Decomposition error
67    pub error: f64,
68}
69
70/// SIMD-optimized memory layout
71#[derive(Debug, Clone, Serialize, Deserialize)]
72pub struct SimdLayout {
73    /// Layout type (e.g., "avx2", "avx512", "neon")
74    pub layout_type: String,
75    /// Reordered matrix data for SIMD access
76    pub data: Vec<Complex64>,
77    /// Stride information
78    pub stride: usize,
79    /// Alignment requirement
80    pub alignment: usize,
81}
82
83/// Tensor network representation
84#[derive(Debug, Clone, Serialize, Deserialize)]
85pub struct TensorNetworkRep {
86    /// Tensor indices
87    pub tensors: Vec<TensorNode>,
88    /// Contraction order
89    pub contraction_order: Vec<(usize, usize)>,
90    /// Bond dimensions
91    pub bond_dims: Vec<usize>,
92}
93
94#[derive(Debug, Clone, Serialize, Deserialize)]
95pub struct TensorNode {
96    pub id: usize,
97    pub shape: Vec<usize>,
98    pub data: Vec<Complex64>,
99}
100
101/// Metadata about gate compilation
102#[derive(Debug, Clone, Serialize, Deserialize)]
103pub struct CompilationMetadata {
104    /// Timestamp of compilation
105    pub compiled_at: u64,
106    /// Compilation time in microseconds
107    pub compilation_time_us: u64,
108    /// Compiler version
109    pub compiler_version: String,
110    /// Hardware target
111    pub target_hardware: String,
112    /// Optimization level
113    pub optimization_level: u32,
114    /// Cache hits for this gate
115    pub cache_hits: u64,
116    /// Last access time
117    pub last_accessed: u64,
118}
119
120/// Cache statistics
121#[derive(Debug, Clone, Serialize, Deserialize)]
122pub struct CacheStatistics {
123    /// Total number of cache hits
124    pub total_hits: u64,
125    /// Total number of cache misses
126    pub total_misses: u64,
127    /// Total compilation time saved (microseconds)
128    pub time_saved_us: u64,
129    /// Number of entries in cache
130    pub num_entries: usize,
131    /// Total cache size in bytes
132    pub total_size_bytes: usize,
133    /// Cache creation time
134    pub created_at: u64,
135}
136
137/// Configuration for the compilation cache
138#[derive(Debug, Clone, Serialize, Deserialize)]
139pub struct CacheConfig {
140    /// Maximum number of entries in memory
141    pub max_memory_entries: usize,
142    /// Maximum total size in bytes
143    pub max_size_bytes: usize,
144    /// Cache directory path
145    pub cache_dir: PathBuf,
146    /// Enable persistent storage
147    pub enable_persistence: bool,
148    /// Cache expiration time
149    pub expiration_time: Duration,
150    /// Compression level (0-9, 0 = no compression)
151    pub compression_level: u32,
152    /// Enable async writes
153    pub async_writes: bool,
154}
155
156impl Default for CacheConfig {
157    fn default() -> Self {
158        Self {
159            max_memory_entries: 10000,
160            max_size_bytes: 1024 * 1024 * 1024, // 1GB
161            cache_dir: PathBuf::from(".quantrs_cache"),
162            enable_persistence: true,
163            expiration_time: Duration::from_secs(30 * 24 * 60 * 60), // 30 days
164            compression_level: 3,
165            async_writes: true,
166        }
167    }
168}
169
170/// Gate compilation cache with persistent storage
171pub struct CompilationCache {
172    /// In-memory cache
173    memory_cache: Arc<RwLock<MemoryCache>>,
174    /// Configuration
175    config: CacheConfig,
176    /// Cache statistics
177    statistics: Arc<RwLock<CacheStatistics>>,
178    /// Background writer handle
179    writer_handle: Option<std::thread::JoinHandle<()>>,
180    /// Write queue for async persistence
181    write_queue: Arc<RwLock<VecDeque<CompiledGate>>>,
182}
183
184/// In-memory cache structure
185struct MemoryCache {
186    /// Gate storage by ID
187    gates: HashMap<String, CompiledGate>,
188    /// LRU queue for eviction
189    lru_queue: VecDeque<String>,
190    /// Current total size
191    current_size: usize,
192}
193
194impl CompilationCache {
195    /// Create a new compilation cache
196    pub fn new(config: CacheConfig) -> QuantRS2Result<Self> {
197        // Create cache directory if needed
198        if config.enable_persistence {
199            fs::create_dir_all(&config.cache_dir)?;
200        }
201
202        let memory_cache = Arc::new(RwLock::new(MemoryCache {
203            gates: HashMap::new(),
204            lru_queue: VecDeque::new(),
205            current_size: 0,
206        }));
207
208        let statistics = Arc::new(RwLock::new(CacheStatistics {
209            total_hits: 0,
210            total_misses: 0,
211            time_saved_us: 0,
212            num_entries: 0,
213            total_size_bytes: 0,
214            created_at: current_timestamp(),
215        }));
216
217        let write_queue = Arc::new(RwLock::new(VecDeque::new()));
218
219        // Start background writer if async writes are enabled
220        let writer_handle = if config.async_writes && config.enable_persistence {
221            Some(Self::start_background_writer(
222                config.cache_dir.clone(),
223                Arc::clone(&write_queue),
224            ))
225        } else {
226            None
227        };
228
229        Ok(Self {
230            memory_cache,
231            config,
232            statistics,
233            writer_handle,
234            write_queue,
235        })
236    }
237
238    /// Get or compile a gate
239    pub fn get_or_compile<F>(
240        &self,
241        gate: &dyn GateOp,
242        compile_fn: F,
243    ) -> QuantRS2Result<CompiledGate>
244    where
245        F: FnOnce(&dyn GateOp) -> QuantRS2Result<CompiledGate>,
246    {
247        let gate_id = self.compute_gate_id(gate)?;
248
249        // Try memory cache first
250        if let Some(compiled) = self.get_from_memory(&gate_id)? {
251            self.record_hit(&gate_id)?;
252            return Ok(compiled);
253        }
254
255        // Try persistent cache
256        if self.config.enable_persistence {
257            if let Some(compiled) = self.get_from_disk(&gate_id)? {
258                self.add_to_memory(compiled.clone())?;
259                self.record_hit(&gate_id)?;
260                return Ok(compiled);
261            }
262        }
263
264        // Cache miss - compile the gate
265        self.record_miss()?;
266        let start_time = std::time::Instant::now();
267
268        let mut compiled = compile_fn(gate)?;
269
270        let compilation_time = start_time.elapsed().as_micros() as u64;
271        compiled.metadata.compilation_time_us = compilation_time;
272        compiled.gate_id = gate_id;
273
274        // Add to cache
275        self.add_to_memory(compiled.clone())?;
276
277        if self.config.enable_persistence {
278            if self.config.async_writes {
279                self.queue_for_write(compiled.clone())?;
280            } else {
281                self.write_to_disk(&compiled)?;
282            }
283        }
284
285        Ok(compiled)
286    }
287
288    /// Compute unique gate identifier
289    fn compute_gate_id(&self, gate: &dyn GateOp) -> QuantRS2Result<String> {
290        let mut hasher = DefaultHasher::new();
291
292        // Hash gate name
293        gate.name().hash(&mut hasher);
294
295        // Hash gate matrix
296        let matrix = gate.matrix()?;
297        for elem in &matrix {
298            elem.re.to_bits().hash(&mut hasher);
299            elem.im.to_bits().hash(&mut hasher);
300        }
301
302        // Hash target qubits
303        for qubit in gate.qubits() {
304            qubit.0.hash(&mut hasher);
305        }
306
307        let result = hasher.finish();
308        Ok(format!("{:x}", result))
309    }
310
311    /// Get from memory cache
312    fn get_from_memory(&self, gate_id: &str) -> QuantRS2Result<Option<CompiledGate>> {
313        let mut cache = self.memory_cache.write().unwrap();
314
315        if let Some(compiled) = cache.gates.get(gate_id).cloned() {
316            // Update LRU
317            cache.lru_queue.retain(|id| id != gate_id);
318            cache.lru_queue.push_front(gate_id.to_string());
319
320            // Update last accessed time
321            let mut updated_compiled = compiled.clone();
322            updated_compiled.metadata.last_accessed = current_timestamp();
323            cache
324                .gates
325                .insert(gate_id.to_string(), updated_compiled.clone());
326
327            Ok(Some(updated_compiled))
328        } else {
329            Ok(None)
330        }
331    }
332
333    /// Add to memory cache
334    fn add_to_memory(&self, compiled: CompiledGate) -> QuantRS2Result<()> {
335        let mut cache = self.memory_cache.write().unwrap();
336        let gate_size = self.estimate_size(&compiled);
337
338        // Evict entries if needed
339        while cache.gates.len() >= self.config.max_memory_entries
340            || cache.current_size + gate_size > self.config.max_size_bytes
341        {
342            if let Some(evict_id) = cache.lru_queue.pop_back() {
343                if let Some(evicted) = cache.gates.remove(&evict_id) {
344                    cache.current_size -= self.estimate_size(&evicted);
345                }
346            } else {
347                break;
348            }
349        }
350
351        // Add new entry
352        cache
353            .gates
354            .insert(compiled.gate_id.clone(), compiled.clone());
355        cache.lru_queue.push_front(compiled.gate_id.clone());
356        cache.current_size += gate_size;
357
358        // Update statistics
359        let mut stats = self.statistics.write().unwrap();
360        stats.num_entries = cache.gates.len();
361        stats.total_size_bytes = cache.current_size;
362
363        Ok(())
364    }
365
366    /// Get from persistent storage
367    fn get_from_disk(&self, gate_id: &str) -> QuantRS2Result<Option<CompiledGate>> {
368        let file_path = self.cache_file_path(gate_id);
369
370        if !file_path.exists() {
371            return Ok(None);
372        }
373
374        // Check expiration
375        let metadata = fs::metadata(&file_path)?;
376        let modified = metadata.modified()?;
377        let age = SystemTime::now()
378            .duration_since(modified)
379            .unwrap_or_default();
380
381        if age > self.config.expiration_time {
382            // Expired - remove file
383            fs::remove_file(&file_path)?;
384            return Ok(None);
385        }
386
387        // Read and deserialize
388        let file = File::open(&file_path)?;
389        let reader = BufReader::new(file);
390
391        let compiled = bincode::deserialize_from(reader)?;
392
393        Ok(Some(compiled))
394    }
395
396    /// Write to persistent storage
397    fn write_to_disk(&self, compiled: &CompiledGate) -> QuantRS2Result<()> {
398        let file_path = self.cache_file_path(&compiled.gate_id);
399
400        // Ensure parent directory exists
401        if let Some(parent) = file_path.parent() {
402            fs::create_dir_all(parent)?;
403        }
404
405        let file = File::create(&file_path)?;
406        let writer = BufWriter::new(file);
407
408        bincode::serialize_into(writer, compiled)?;
409
410        Ok(())
411    }
412
413    /// Queue gate for asynchronous write
414    fn queue_for_write(&self, compiled: CompiledGate) -> QuantRS2Result<()> {
415        let mut queue = self.write_queue.write().unwrap();
416        queue.push_back(compiled);
417        Ok(())
418    }
419
420    /// Start background writer thread
421    fn start_background_writer(
422        cache_dir: PathBuf,
423        write_queue: Arc<RwLock<VecDeque<CompiledGate>>>,
424    ) -> std::thread::JoinHandle<()> {
425        std::thread::spawn(move || loop {
426            std::thread::sleep(Duration::from_millis(100));
427
428            let gates_to_write: Vec<CompiledGate> = {
429                let mut queue = write_queue.write().unwrap();
430                queue.drain(..).collect()
431            };
432
433            for compiled in gates_to_write {
434                let filename = format!(
435                    "{}.cache",
436                    &compiled.gate_id[..16.min(compiled.gate_id.len())]
437                );
438                let file_path = cache_dir.join(filename);
439
440                if let Err(e) = Self::write_gate_to_file(&file_path, &compiled, 3) {
441                    eprintln!("Failed to write gate to cache: {}", e);
442                }
443            }
444        })
445    }
446
447    /// Write a single gate to file (static method for thread)
448    fn write_gate_to_file(
449        file_path: &Path,
450        compiled: &CompiledGate,
451        _compression_level: i32,
452    ) -> QuantRS2Result<()> {
453        if let Some(parent) = file_path.parent() {
454            fs::create_dir_all(parent)?;
455        }
456
457        let file = File::create(file_path)?;
458        let writer = BufWriter::new(file);
459
460        bincode::serialize_into(writer, compiled)?;
461
462        Ok(())
463    }
464
465    /// Get cache file path for a gate
466    fn cache_file_path(&self, gate_id: &str) -> PathBuf {
467        // Use first 16 chars of hash for filename
468        let filename = format!("{}.cache", &gate_id[..16.min(gate_id.len())]);
469        self.config.cache_dir.join(filename)
470    }
471
472    /// Estimate size of compiled gate
473    fn estimate_size(&self, compiled: &CompiledGate) -> usize {
474        std::mem::size_of::<CompiledGate>() +
475        compiled.matrix.len() * std::mem::size_of::<Complex64>() +
476        compiled.gate_id.len() +
477        // Rough estimate for nested structures
478        1024
479    }
480
481    /// Record cache hit
482    fn record_hit(&self, gate_id: &str) -> QuantRS2Result<()> {
483        let mut stats = self.statistics.write().unwrap();
484        stats.total_hits += 1;
485
486        // Estimate time saved (average compilation time)
487        if let Ok(cache) = self.memory_cache.read() {
488            if let Some(compiled) = cache.gates.get(gate_id) {
489                stats.time_saved_us += compiled.metadata.compilation_time_us;
490            }
491        }
492
493        Ok(())
494    }
495
496    /// Record cache miss
497    fn record_miss(&self) -> QuantRS2Result<()> {
498        let mut stats = self.statistics.write().unwrap();
499        stats.total_misses += 1;
500        Ok(())
501    }
502
503    /// Clear the cache
504    pub fn clear(&self) -> QuantRS2Result<()> {
505        // Clear memory cache
506        let mut cache = self.memory_cache.write().unwrap();
507        cache.gates.clear();
508        cache.lru_queue.clear();
509        cache.current_size = 0;
510
511        // Clear disk cache if enabled
512        if self.config.enable_persistence && self.config.cache_dir.exists() {
513            for entry in fs::read_dir(&self.config.cache_dir)? {
514                let entry = entry?;
515                if entry.path().extension().and_then(|s| s.to_str()) == Some("cache") {
516                    fs::remove_file(entry.path())?;
517                }
518            }
519        }
520
521        // Reset statistics
522        let mut stats = self.statistics.write().unwrap();
523        *stats = CacheStatistics {
524            total_hits: 0,
525            total_misses: 0,
526            time_saved_us: 0,
527            num_entries: 0,
528            total_size_bytes: 0,
529            created_at: current_timestamp(),
530        };
531
532        Ok(())
533    }
534
535    /// Get cache statistics
536    pub fn statistics(&self) -> CacheStatistics {
537        self.statistics.read().unwrap().clone()
538    }
539
540    /// Optimize cache by removing expired entries
541    pub fn optimize(&self) -> QuantRS2Result<()> {
542        if !self.config.enable_persistence {
543            return Ok(());
544        }
545
546        let mut removed_count = 0;
547
548        for entry in fs::read_dir(&self.config.cache_dir)? {
549            let entry = entry?;
550            let path = entry.path();
551
552            if path.extension().and_then(|s| s.to_str()) == Some("cache") {
553                let metadata = fs::metadata(&path)?;
554                let modified = metadata.modified()?;
555                let age = SystemTime::now()
556                    .duration_since(modified)
557                    .unwrap_or_default();
558
559                if age > self.config.expiration_time {
560                    fs::remove_file(&path)?;
561                    removed_count += 1;
562                }
563            }
564        }
565
566        println!(
567            "Cache optimization: removed {} expired entries",
568            removed_count
569        );
570        Ok(())
571    }
572
573    /// Export cache statistics to file
574    pub fn export_statistics(&self, path: &Path) -> QuantRS2Result<()> {
575        let stats = self.statistics();
576        let json = serde_json::to_string_pretty(&stats)?;
577        fs::write(path, json)?;
578        Ok(())
579    }
580
581    /// Precompile and cache common gates
582    pub fn precompile_common_gates(&self) -> QuantRS2Result<()> {
583        use crate::gate::{multi::*, single::*};
584
585        // Single-qubit gates
586        let single_qubit_gates: Vec<Box<dyn GateOp>> = vec![
587            Box::new(Hadamard {
588                target: crate::qubit::QubitId(0),
589            }),
590            Box::new(PauliX {
591                target: crate::qubit::QubitId(0),
592            }),
593            Box::new(PauliY {
594                target: crate::qubit::QubitId(0),
595            }),
596            Box::new(PauliZ {
597                target: crate::qubit::QubitId(0),
598            }),
599            Box::new(Phase {
600                target: crate::qubit::QubitId(0),
601            }),
602            Box::new(RotationZ {
603                target: crate::qubit::QubitId(0),
604                theta: std::f64::consts::PI / 4.0,
605            }),
606        ];
607
608        for gate in single_qubit_gates {
609            let _ = self.get_or_compile(gate.as_ref(), |g| compile_single_qubit_gate(g))?;
610        }
611
612        // Two-qubit gates
613        let two_qubit_gates: Vec<Box<dyn GateOp>> = vec![
614            Box::new(CNOT {
615                control: crate::qubit::QubitId(0),
616                target: crate::qubit::QubitId(1),
617            }),
618            Box::new(CZ {
619                control: crate::qubit::QubitId(0),
620                target: crate::qubit::QubitId(1),
621            }),
622            Box::new(SWAP {
623                qubit1: crate::qubit::QubitId(0),
624                qubit2: crate::qubit::QubitId(1),
625            }),
626        ];
627
628        for gate in two_qubit_gates {
629            let _ = self.get_or_compile(gate.as_ref(), |g| compile_two_qubit_gate(g))?;
630        }
631
632        Ok(())
633    }
634}
635
636/// Get current timestamp in seconds since UNIX epoch
637fn current_timestamp() -> u64 {
638    SystemTime::now()
639        .duration_since(UNIX_EPOCH)
640        .unwrap_or_default()
641        .as_secs()
642}
643
644/// Default gate compilation functions
645fn compile_single_qubit_gate(gate: &dyn GateOp) -> QuantRS2Result<CompiledGate> {
646    let matrix = gate.matrix()?;
647    let gate_id = String::new(); // Will be set by cache
648
649    // Check if gate is diagonal
650    let is_diagonal = matrix[1].norm() < 1e-10 && matrix[2].norm() < 1e-10;
651    let diagonal = if is_diagonal {
652        Some(vec![matrix[0], matrix[3]])
653    } else {
654        None
655    };
656
657    // Create SIMD layout
658    let simd_layout = if false {
659        // Simplified - disable SIMD check
660        Some(SimdLayout {
661            layout_type: "avx2".to_string(),
662            data: matrix.clone(),
663            stride: 2,
664            alignment: 32,
665        })
666    } else {
667        None
668    };
669
670    Ok(CompiledGate {
671        gate_id,
672        matrix,
673        num_qubits: 1,
674        optimizations: GateOptimizations {
675            diagonal,
676            decomposition: None,
677            simd_layout,
678            gpu_kernel_id: None,
679            tensor_network: None,
680        },
681        metadata: CompilationMetadata {
682            compiled_at: current_timestamp(),
683            compilation_time_us: 0, // Will be set by cache
684            compiler_version: env!("CARGO_PKG_VERSION").to_string(),
685            target_hardware: "generic".to_string(),
686            optimization_level: 2,
687            cache_hits: 0,
688            last_accessed: current_timestamp(),
689        },
690    })
691}
692
693fn compile_two_qubit_gate(gate: &dyn GateOp) -> QuantRS2Result<CompiledGate> {
694    let matrix = gate.matrix()?;
695    let gate_id = String::new(); // Will be set by cache
696
697    // Check for decomposition opportunities
698    let decomposition = if gate.name() == "CNOT" {
699        Some(GateDecomposition {
700            gates: vec!["H".to_string(), "CZ".to_string(), "H".to_string()],
701            parameters: vec![vec![], vec![], vec![]],
702            targets: vec![vec![1], vec![0, 1], vec![1]],
703            gate_count: 3,
704            error: 1e-15,
705        })
706    } else {
707        None
708    };
709
710    Ok(CompiledGate {
711        gate_id,
712        matrix,
713        num_qubits: 2,
714        optimizations: GateOptimizations {
715            diagonal: None,
716            decomposition,
717            simd_layout: None,
718            gpu_kernel_id: Some(format!("{}_kernel", gate.name().to_lowercase())),
719            tensor_network: None,
720        },
721        metadata: CompilationMetadata {
722            compiled_at: current_timestamp(),
723            compilation_time_us: 0,
724            compiler_version: env!("CARGO_PKG_VERSION").to_string(),
725            target_hardware: "generic".to_string(),
726            optimization_level: 2,
727            cache_hits: 0,
728            last_accessed: current_timestamp(),
729        },
730    })
731}
732
733/// Global compilation cache instance
734static GLOBAL_CACHE: OnceLock<Arc<CompilationCache>> = OnceLock::new();
735
736/// Initialize the global compilation cache
737pub fn initialize_compilation_cache(config: CacheConfig) -> QuantRS2Result<()> {
738    let cache = CompilationCache::new(config)?;
739
740    GLOBAL_CACHE.set(Arc::new(cache)).map_err(|_| {
741        QuantRS2Error::RuntimeError("Compilation cache already initialized".to_string())
742    })?;
743
744    Ok(())
745}
746
747/// Get the global compilation cache
748pub fn get_compilation_cache() -> QuantRS2Result<Arc<CompilationCache>> {
749    GLOBAL_CACHE
750        .get()
751        .map(Arc::clone)
752        .ok_or_else(|| QuantRS2Error::RuntimeError("Compilation cache not initialized".to_string()))
753}
754
755#[cfg(test)]
756mod tests {
757    use super::*;
758    use crate::gate::single::{Hadamard, PauliX};
759    use crate::qubit::QubitId;
760    use std::fs;
761    // use tempfile::TempDir;
762
763    #[test]
764    fn test_cache_creation() {
765        let temp_dir = std::env::temp_dir().join("quantrs_test_cache");
766        let config = CacheConfig {
767            cache_dir: temp_dir,
768            enable_persistence: false, // Disable persistence for tests
769            ..Default::default()
770        };
771
772        let cache = CompilationCache::new(config).unwrap();
773        let stats = cache.statistics();
774
775        assert_eq!(stats.total_hits, 0);
776        assert_eq!(stats.total_misses, 0);
777        assert_eq!(stats.num_entries, 0);
778    }
779
780    #[test]
781    fn test_gate_compilation_and_caching() {
782        let temp_dir = std::env::temp_dir().join(format!(
783            "quantrs_test_caching_{}_{}",
784            std::process::id(),
785            std::time::SystemTime::now()
786                .duration_since(std::time::UNIX_EPOCH)
787                .unwrap()
788                .as_nanos()
789        ));
790        let config = CacheConfig {
791            cache_dir: temp_dir,
792            enable_persistence: false, // Disable persistence to avoid interference
793            async_writes: false,
794            ..Default::default()
795        };
796
797        let cache = CompilationCache::new(config).unwrap();
798        // Clear any existing cache state
799        cache.clear().unwrap();
800        let gate = Hadamard { target: QubitId(0) };
801
802        // First access - should compile
803        let compiled1 = cache
804            .get_or_compile(&gate, compile_single_qubit_gate)
805            .unwrap();
806        let stats1 = cache.statistics();
807        assert_eq!(stats1.total_misses, 1);
808        assert_eq!(stats1.total_hits, 0);
809
810        // Second access - should hit cache
811        let compiled2 = cache
812            .get_or_compile(&gate, compile_single_qubit_gate)
813            .unwrap();
814        let stats2 = cache.statistics();
815        assert_eq!(stats2.total_misses, 1);
816        assert_eq!(stats2.total_hits, 1);
817
818        // Verify same gate
819        assert_eq!(compiled1.gate_id, compiled2.gate_id);
820        assert_eq!(compiled1.matrix, compiled2.matrix);
821    }
822
823    #[test]
824    fn test_cache_eviction() {
825        let temp_dir = std::env::temp_dir().join(format!("quantrs_test_{}", std::process::id()));
826        let config = CacheConfig {
827            cache_dir: temp_dir,
828            max_memory_entries: 2,
829            enable_persistence: false,
830            ..Default::default()
831        };
832
833        let cache = CompilationCache::new(config).unwrap();
834
835        // Add three gates to trigger eviction
836        for i in 0..3 {
837            let gate = PauliX { target: QubitId(i) };
838            let _ = cache
839                .get_or_compile(&gate, compile_single_qubit_gate)
840                .unwrap();
841        }
842
843        let stats = cache.statistics();
844        assert_eq!(stats.num_entries, 2); // One should have been evicted
845    }
846
847    #[test]
848    fn test_persistent_cache() {
849        let temp_dir = std::env::temp_dir().join(format!("quantrs_test_{}", std::process::id()));
850        let config = CacheConfig {
851            cache_dir: temp_dir,
852            enable_persistence: true,
853            async_writes: false,
854            ..Default::default()
855        };
856
857        let gate = Hadamard { target: QubitId(0) };
858        let gate_id;
859
860        // Create cache and compile gate
861        {
862            let cache = CompilationCache::new(config.clone()).unwrap();
863            let compiled = cache
864                .get_or_compile(&gate, compile_single_qubit_gate)
865                .unwrap();
866            gate_id = compiled.gate_id.clone();
867        }
868
869        // Create new cache instance and verify persistence
870        {
871            let cache = CompilationCache::new(config).unwrap();
872            let compiled = cache
873                .get_or_compile(&gate, compile_single_qubit_gate)
874                .unwrap();
875
876            assert_eq!(compiled.gate_id, gate_id);
877
878            let stats = cache.statistics();
879            assert_eq!(stats.total_hits, 1); // Should hit persistent cache
880            assert_eq!(stats.total_misses, 0);
881        }
882    }
883
884    #[test]
885    fn test_cache_optimization() {
886        let temp_dir = std::env::temp_dir().join(format!("quantrs_test_{}", std::process::id()));
887        let config = CacheConfig {
888            cache_dir: temp_dir,
889            enable_persistence: true,
890            expiration_time: Duration::from_secs(0), // Immediate expiration
891            async_writes: false,
892            ..Default::default()
893        };
894
895        let cache = CompilationCache::new(config).unwrap();
896        let gate = Hadamard { target: QubitId(0) };
897
898        // Compile and cache gate
899        let _ = cache
900            .get_or_compile(&gate, compile_single_qubit_gate)
901            .unwrap();
902
903        // Wait a bit and optimize
904        std::thread::sleep(Duration::from_millis(100));
905        cache.optimize().unwrap();
906
907        // Try to access again - should miss due to expiration
908        cache.clear().unwrap(); // Clear memory cache
909        let _ = cache
910            .get_or_compile(&gate, compile_single_qubit_gate)
911            .unwrap();
912
913        let stats = cache.statistics();
914        assert_eq!(stats.total_misses, 1); // Should have missed
915    }
916
917    #[test]
918    fn test_precompile_common_gates() {
919        let temp_dir = std::env::temp_dir().join(format!("quantrs_test_{}", std::process::id()));
920        let config = CacheConfig {
921            cache_dir: temp_dir,
922            enable_persistence: false, // Disable persistence to avoid interference
923            async_writes: false,
924            ..Default::default()
925        };
926
927        let cache = CompilationCache::new(config).unwrap();
928        // Clear any existing cache state
929        cache.clear().unwrap();
930        cache.precompile_common_gates().unwrap();
931
932        let stats = cache.statistics();
933        assert!(stats.num_entries > 0);
934        println!("Precompiled {} gates", stats.num_entries);
935    }
936
937    #[test]
938    fn test_statistics_export() {
939        let temp_dir = std::env::temp_dir().join(format!(
940            "quantrs_test_stats_{}_{}",
941            std::process::id(),
942            std::time::SystemTime::now()
943                .duration_since(std::time::UNIX_EPOCH)
944                .unwrap()
945                .as_nanos()
946        ));
947        let config = CacheConfig {
948            cache_dir: temp_dir.clone(),
949            enable_persistence: false, // Disable persistence to avoid interference
950            ..Default::default()
951        };
952
953        let cache = CompilationCache::new(config).unwrap();
954        // Clear any existing cache state
955        cache.clear().unwrap();
956
957        // Generate some statistics
958        let gate = Hadamard { target: QubitId(0) };
959        let _ = cache
960            .get_or_compile(&gate, compile_single_qubit_gate)
961            .unwrap();
962        let _ = cache
963            .get_or_compile(&gate, compile_single_qubit_gate)
964            .unwrap();
965
966        // Export statistics
967        std::fs::create_dir_all(&temp_dir).unwrap();
968        let stats_path = temp_dir.join("stats.json");
969        cache.export_statistics(&stats_path).unwrap();
970
971        // Verify file exists and contains valid JSON
972        assert!(stats_path.exists());
973        let contents = fs::read_to_string(&stats_path).unwrap();
974        let parsed: CacheStatistics = serde_json::from_str(&contents).unwrap();
975
976        assert_eq!(parsed.total_hits, 1);
977        assert_eq!(parsed.total_misses, 1);
978    }
979}