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 scirs2_core::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!("{result:x}"))
309    }
310
311    /// Get from memory cache
312    fn get_from_memory(&self, gate_id: &str) -> QuantRS2Result<Option<CompiledGate>> {
313        let mut cache = self
314            .memory_cache
315            .write()
316            .map_err(|_| QuantRS2Error::RuntimeError("Memory cache lock poisoned".to_string()))?;
317
318        if let Some(compiled) = cache.gates.get(gate_id).cloned() {
319            // Update LRU
320            cache.lru_queue.retain(|id| id != gate_id);
321            cache.lru_queue.push_front(gate_id.to_string());
322
323            // Update last accessed time
324            let mut updated_compiled = compiled;
325            updated_compiled.metadata.last_accessed = current_timestamp();
326            cache
327                .gates
328                .insert(gate_id.to_string(), updated_compiled.clone());
329
330            Ok(Some(updated_compiled))
331        } else {
332            Ok(None)
333        }
334    }
335
336    /// Add to memory cache
337    fn add_to_memory(&self, compiled: CompiledGate) -> QuantRS2Result<()> {
338        let mut cache = self
339            .memory_cache
340            .write()
341            .map_err(|_| QuantRS2Error::RuntimeError("Memory cache lock poisoned".to_string()))?;
342        let gate_size = self.estimate_size(&compiled);
343
344        // Evict entries if needed
345        while cache.gates.len() >= self.config.max_memory_entries
346            || cache.current_size + gate_size > self.config.max_size_bytes
347        {
348            if let Some(evict_id) = cache.lru_queue.pop_back() {
349                if let Some(evicted) = cache.gates.remove(&evict_id) {
350                    cache.current_size -= self.estimate_size(&evicted);
351                }
352            } else {
353                break;
354            }
355        }
356
357        // Add new entry
358        cache
359            .gates
360            .insert(compiled.gate_id.clone(), compiled.clone());
361        cache.lru_queue.push_front(compiled.gate_id);
362        cache.current_size += gate_size;
363
364        // Update statistics
365        if let Ok(mut stats) = self.statistics.write() {
366            stats.num_entries = cache.gates.len();
367            stats.total_size_bytes = cache.current_size;
368        }
369
370        Ok(())
371    }
372
373    /// Get from persistent storage
374    fn get_from_disk(&self, gate_id: &str) -> QuantRS2Result<Option<CompiledGate>> {
375        let file_path = self.cache_file_path(gate_id);
376
377        if !file_path.exists() {
378            return Ok(None);
379        }
380
381        // Check expiration
382        let metadata = fs::metadata(&file_path)?;
383        let modified = metadata.modified()?;
384        let age = SystemTime::now()
385            .duration_since(modified)
386            .unwrap_or_default();
387
388        if age > self.config.expiration_time {
389            // Expired - remove file
390            fs::remove_file(&file_path)?;
391            return Ok(None);
392        }
393
394        // Read and deserialize
395        let file = File::open(&file_path)?;
396        let reader = BufReader::new(file);
397
398        // bincode v2: use serde helper API with an explicit config
399        let compiled: CompiledGate =
400            bincode::serde::decode_from_reader(reader, bincode::config::standard())?;
401
402        Ok(Some(compiled))
403    }
404
405    /// Write to persistent storage
406    fn write_to_disk(&self, compiled: &CompiledGate) -> QuantRS2Result<()> {
407        let file_path = self.cache_file_path(&compiled.gate_id);
408
409        // Ensure parent directory exists
410        if let Some(parent) = file_path.parent() {
411            fs::create_dir_all(parent)?;
412        }
413
414        let file = File::create(&file_path)?;
415        let mut writer = BufWriter::new(file);
416        bincode::serde::encode_into_std_write(compiled, &mut writer, bincode::config::standard())?;
417
418        Ok(())
419    }
420
421    /// Queue gate for asynchronous write
422    fn queue_for_write(&self, compiled: CompiledGate) -> QuantRS2Result<()> {
423        let mut queue = self
424            .write_queue
425            .write()
426            .map_err(|_| QuantRS2Error::RuntimeError("Write queue lock poisoned".to_string()))?;
427        queue.push_back(compiled);
428        Ok(())
429    }
430
431    /// Start background writer thread
432    fn start_background_writer(
433        cache_dir: PathBuf,
434        write_queue: Arc<RwLock<VecDeque<CompiledGate>>>,
435    ) -> std::thread::JoinHandle<()> {
436        std::thread::spawn(move || loop {
437            std::thread::sleep(Duration::from_millis(100));
438
439            let gates_to_write: Vec<CompiledGate> = {
440                match write_queue.write() {
441                    Ok(mut queue) => queue.drain(..).collect(),
442                    Err(_) => continue, // Skip this iteration if lock is poisoned
443                }
444            };
445
446            for compiled in gates_to_write {
447                let filename = format!(
448                    "{}.cache",
449                    &compiled.gate_id[..16.min(compiled.gate_id.len())]
450                );
451                let file_path = cache_dir.join(filename);
452
453                if let Err(e) = Self::write_gate_to_file(&file_path, &compiled, 3) {
454                    eprintln!("Failed to write gate to cache: {e}");
455                }
456            }
457        })
458    }
459
460    /// Write a single gate to file (static method for thread)
461    fn write_gate_to_file(
462        file_path: &Path,
463        compiled: &CompiledGate,
464        _compression_level: i32,
465    ) -> QuantRS2Result<()> {
466        if let Some(parent) = file_path.parent() {
467            fs::create_dir_all(parent)?;
468        }
469
470        let file = File::create(file_path)?;
471        let mut writer = BufWriter::new(file);
472        bincode::serde::encode_into_std_write(compiled, &mut writer, bincode::config::standard())?;
473
474        Ok(())
475    }
476
477    /// Get cache file path for a gate
478    fn cache_file_path(&self, gate_id: &str) -> PathBuf {
479        // Use first 16 chars of hash for filename
480        let filename = format!("{}.cache", &gate_id[..16.min(gate_id.len())]);
481        self.config.cache_dir.join(filename)
482    }
483
484    /// Estimate size of compiled gate
485    fn estimate_size(&self, compiled: &CompiledGate) -> usize {
486        std::mem::size_of::<CompiledGate>() +
487        compiled.matrix.len() * std::mem::size_of::<Complex64>() +
488        compiled.gate_id.len() +
489        // Rough estimate for nested structures
490        1024
491    }
492
493    /// Record cache hit
494    fn record_hit(&self, gate_id: &str) -> QuantRS2Result<()> {
495        let mut stats = self
496            .statistics
497            .write()
498            .map_err(|_| QuantRS2Error::RuntimeError("Statistics lock poisoned".to_string()))?;
499        stats.total_hits += 1;
500
501        // Estimate time saved (average compilation time)
502        if let Ok(cache) = self.memory_cache.read() {
503            if let Some(compiled) = cache.gates.get(gate_id) {
504                stats.time_saved_us += compiled.metadata.compilation_time_us;
505            }
506        }
507
508        Ok(())
509    }
510
511    /// Record cache miss
512    fn record_miss(&self) -> QuantRS2Result<()> {
513        let mut stats = self
514            .statistics
515            .write()
516            .map_err(|_| QuantRS2Error::RuntimeError("Statistics lock poisoned".to_string()))?;
517        stats.total_misses += 1;
518        Ok(())
519    }
520
521    /// Clear the cache
522    pub fn clear(&self) -> QuantRS2Result<()> {
523        // Clear memory cache
524        let mut cache = self
525            .memory_cache
526            .write()
527            .map_err(|_| QuantRS2Error::RuntimeError("Memory cache lock poisoned".to_string()))?;
528        cache.gates.clear();
529        cache.lru_queue.clear();
530        cache.current_size = 0;
531
532        // Clear disk cache if enabled
533        if self.config.enable_persistence && self.config.cache_dir.exists() {
534            for entry in fs::read_dir(&self.config.cache_dir)? {
535                let entry = entry?;
536                if entry.path().extension().and_then(|s| s.to_str()) == Some("cache") {
537                    fs::remove_file(entry.path())?;
538                }
539            }
540        }
541
542        // Reset statistics
543        let mut stats = self
544            .statistics
545            .write()
546            .map_err(|_| QuantRS2Error::RuntimeError("Statistics lock poisoned".to_string()))?;
547        *stats = CacheStatistics {
548            total_hits: 0,
549            total_misses: 0,
550            time_saved_us: 0,
551            num_entries: 0,
552            total_size_bytes: 0,
553            created_at: current_timestamp(),
554        };
555
556        Ok(())
557    }
558
559    /// Get cache statistics
560    pub fn statistics(&self) -> CacheStatistics {
561        self.statistics
562            .read()
563            .map(|s| s.clone())
564            .unwrap_or_else(|_| CacheStatistics {
565                total_hits: 0,
566                total_misses: 0,
567                time_saved_us: 0,
568                num_entries: 0,
569                total_size_bytes: 0,
570                created_at: current_timestamp(),
571            })
572    }
573
574    /// Optimize cache by removing expired entries
575    pub fn optimize(&self) -> QuantRS2Result<()> {
576        if !self.config.enable_persistence {
577            return Ok(());
578        }
579
580        let mut removed_count = 0;
581
582        for entry in fs::read_dir(&self.config.cache_dir)? {
583            let entry = entry?;
584            let path = entry.path();
585
586            if path.extension().and_then(|s| s.to_str()) == Some("cache") {
587                let metadata = fs::metadata(&path)?;
588                let modified = metadata.modified()?;
589                let age = SystemTime::now()
590                    .duration_since(modified)
591                    .unwrap_or_default();
592
593                if age > self.config.expiration_time {
594                    fs::remove_file(&path)?;
595                    removed_count += 1;
596                }
597            }
598        }
599
600        println!("Cache optimization: removed {removed_count} expired entries");
601        Ok(())
602    }
603
604    /// Export cache statistics to file
605    pub fn export_statistics(&self, path: &Path) -> QuantRS2Result<()> {
606        let stats = self.statistics();
607        let json = serde_json::to_string_pretty(&stats)?;
608        fs::write(path, json)?;
609        Ok(())
610    }
611
612    /// Precompile and cache common gates
613    pub fn precompile_common_gates(&self) -> QuantRS2Result<()> {
614        use crate::gate::{multi::*, single::*};
615
616        // Single-qubit gates
617        let single_qubit_gates: Vec<Box<dyn GateOp>> = vec![
618            Box::new(Hadamard {
619                target: crate::qubit::QubitId(0),
620            }),
621            Box::new(PauliX {
622                target: crate::qubit::QubitId(0),
623            }),
624            Box::new(PauliY {
625                target: crate::qubit::QubitId(0),
626            }),
627            Box::new(PauliZ {
628                target: crate::qubit::QubitId(0),
629            }),
630            Box::new(Phase {
631                target: crate::qubit::QubitId(0),
632            }),
633            Box::new(RotationZ {
634                target: crate::qubit::QubitId(0),
635                theta: std::f64::consts::PI / 4.0,
636            }),
637        ];
638
639        for gate in single_qubit_gates {
640            let _ = self.get_or_compile(gate.as_ref(), |g| compile_single_qubit_gate(g))?;
641        }
642
643        // Two-qubit gates
644        let two_qubit_gates: Vec<Box<dyn GateOp>> = vec![
645            Box::new(CNOT {
646                control: crate::qubit::QubitId(0),
647                target: crate::qubit::QubitId(1),
648            }),
649            Box::new(CZ {
650                control: crate::qubit::QubitId(0),
651                target: crate::qubit::QubitId(1),
652            }),
653            Box::new(SWAP {
654                qubit1: crate::qubit::QubitId(0),
655                qubit2: crate::qubit::QubitId(1),
656            }),
657        ];
658
659        for gate in two_qubit_gates {
660            let _ = self.get_or_compile(gate.as_ref(), |g| compile_two_qubit_gate(g))?;
661        }
662
663        Ok(())
664    }
665}
666
667/// Get current timestamp in seconds since UNIX epoch
668fn current_timestamp() -> u64 {
669    SystemTime::now()
670        .duration_since(UNIX_EPOCH)
671        .unwrap_or_default()
672        .as_secs()
673}
674
675/// Default gate compilation functions
676fn compile_single_qubit_gate(gate: &dyn GateOp) -> QuantRS2Result<CompiledGate> {
677    let matrix = gate.matrix()?;
678    let gate_id = String::new(); // Will be set by cache
679
680    // Check if gate is diagonal
681    let is_diagonal = matrix[1].norm() < 1e-10 && matrix[2].norm() < 1e-10;
682    let diagonal = if is_diagonal {
683        Some(vec![matrix[0], matrix[3]])
684    } else {
685        None
686    };
687
688    // Create SIMD layout
689    let simd_layout = if false {
690        // Simplified - disable SIMD check
691        Some(SimdLayout {
692            layout_type: "avx2".to_string(),
693            data: matrix.clone(),
694            stride: 2,
695            alignment: 32,
696        })
697    } else {
698        None
699    };
700
701    Ok(CompiledGate {
702        gate_id,
703        matrix,
704        num_qubits: 1,
705        optimizations: GateOptimizations {
706            diagonal,
707            decomposition: None,
708            simd_layout,
709            gpu_kernel_id: None,
710            tensor_network: None,
711        },
712        metadata: CompilationMetadata {
713            compiled_at: current_timestamp(),
714            compilation_time_us: 0, // Will be set by cache
715            compiler_version: env!("CARGO_PKG_VERSION").to_string(),
716            target_hardware: "generic".to_string(),
717            optimization_level: 2,
718            cache_hits: 0,
719            last_accessed: current_timestamp(),
720        },
721    })
722}
723
724fn compile_two_qubit_gate(gate: &dyn GateOp) -> QuantRS2Result<CompiledGate> {
725    let matrix = gate.matrix()?;
726    let gate_id = String::new(); // Will be set by cache
727
728    // Check for decomposition opportunities
729    let decomposition = if gate.name() == "CNOT" {
730        Some(GateDecomposition {
731            gates: vec!["H".to_string(), "CZ".to_string(), "H".to_string()],
732            parameters: vec![vec![], vec![], vec![]],
733            targets: vec![vec![1], vec![0, 1], vec![1]],
734            gate_count: 3,
735            error: 1e-15,
736        })
737    } else {
738        None
739    };
740
741    Ok(CompiledGate {
742        gate_id,
743        matrix,
744        num_qubits: 2,
745        optimizations: GateOptimizations {
746            diagonal: None,
747            decomposition,
748            simd_layout: None,
749            gpu_kernel_id: Some(format!("{}_kernel", gate.name().to_lowercase())),
750            tensor_network: None,
751        },
752        metadata: CompilationMetadata {
753            compiled_at: current_timestamp(),
754            compilation_time_us: 0,
755            compiler_version: env!("CARGO_PKG_VERSION").to_string(),
756            target_hardware: "generic".to_string(),
757            optimization_level: 2,
758            cache_hits: 0,
759            last_accessed: current_timestamp(),
760        },
761    })
762}
763
764/// Global compilation cache instance
765static GLOBAL_CACHE: OnceLock<Arc<CompilationCache>> = OnceLock::new();
766
767/// Initialize the global compilation cache
768pub fn initialize_compilation_cache(config: CacheConfig) -> QuantRS2Result<()> {
769    let cache = CompilationCache::new(config)?;
770
771    GLOBAL_CACHE.set(Arc::new(cache)).map_err(|_| {
772        QuantRS2Error::RuntimeError("Compilation cache already initialized".to_string())
773    })?;
774
775    Ok(())
776}
777
778/// Get the global compilation cache
779pub fn get_compilation_cache() -> QuantRS2Result<Arc<CompilationCache>> {
780    GLOBAL_CACHE
781        .get()
782        .map(Arc::clone)
783        .ok_or_else(|| QuantRS2Error::RuntimeError("Compilation cache not initialized".to_string()))
784}
785
786#[cfg(test)]
787mod tests {
788    use super::*;
789    use crate::gate::single::{Hadamard, PauliX};
790    use crate::qubit::QubitId;
791    use std::fs;
792    // use tempfile::TempDir;
793
794    #[test]
795    fn test_cache_creation() {
796        let temp_dir = std::env::temp_dir().join("quantrs_test_cache");
797        let config = CacheConfig {
798            cache_dir: temp_dir,
799            enable_persistence: false, // Disable persistence for tests
800            ..Default::default()
801        };
802
803        let cache = CompilationCache::new(config).expect("Failed to create cache");
804        let stats = cache.statistics();
805
806        assert_eq!(stats.total_hits, 0);
807        assert_eq!(stats.total_misses, 0);
808        assert_eq!(stats.num_entries, 0);
809    }
810
811    #[test]
812    fn test_gate_compilation_and_caching() {
813        let temp_dir = std::env::temp_dir().join(format!(
814            "quantrs_test_caching_{}_{}",
815            std::process::id(),
816            std::time::SystemTime::now()
817                .duration_since(std::time::UNIX_EPOCH)
818                .unwrap_or_default()
819                .as_nanos()
820        ));
821        let config = CacheConfig {
822            cache_dir: temp_dir,
823            enable_persistence: false, // Disable persistence to avoid interference
824            async_writes: false,
825            ..Default::default()
826        };
827
828        let cache = CompilationCache::new(config).expect("Failed to create cache");
829        // Clear any existing cache state
830        cache.clear().expect("Failed to clear cache");
831        let gate = Hadamard { target: QubitId(0) };
832
833        // First access - should compile
834        let compiled1 = cache
835            .get_or_compile(&gate, compile_single_qubit_gate)
836            .expect("Failed to compile gate");
837        let stats1 = cache.statistics();
838        assert_eq!(stats1.total_misses, 1);
839        assert_eq!(stats1.total_hits, 0);
840
841        // Second access - should hit cache
842        let compiled2 = cache
843            .get_or_compile(&gate, compile_single_qubit_gate)
844            .expect("Failed to get cached gate");
845        let stats2 = cache.statistics();
846        assert_eq!(stats2.total_misses, 1);
847        assert_eq!(stats2.total_hits, 1);
848
849        // Verify same gate
850        assert_eq!(compiled1.gate_id, compiled2.gate_id);
851        assert_eq!(compiled1.matrix, compiled2.matrix);
852    }
853
854    #[test]
855    fn test_cache_eviction() {
856        let temp_dir = std::env::temp_dir().join(format!("quantrs_test_{}", std::process::id()));
857        let config = CacheConfig {
858            cache_dir: temp_dir,
859            max_memory_entries: 2,
860            enable_persistence: false,
861            ..Default::default()
862        };
863
864        let cache = CompilationCache::new(config).expect("Failed to create cache");
865
866        // Add three gates to trigger eviction
867        for i in 0..3 {
868            let gate = PauliX { target: QubitId(i) };
869            let _ = cache
870                .get_or_compile(&gate, compile_single_qubit_gate)
871                .expect("Failed to compile gate");
872        }
873
874        let stats = cache.statistics();
875        assert_eq!(stats.num_entries, 2); // One should have been evicted
876    }
877
878    #[test]
879    fn test_persistent_cache() {
880        let temp_dir = std::env::temp_dir().join(format!("quantrs_test_{}", std::process::id()));
881        let config = CacheConfig {
882            cache_dir: temp_dir,
883            enable_persistence: true,
884            async_writes: false,
885            ..Default::default()
886        };
887
888        let gate = Hadamard { target: QubitId(0) };
889        let gate_id;
890
891        // Create cache and compile gate
892        {
893            let cache = CompilationCache::new(config.clone()).expect("Failed to create cache");
894            let compiled = cache
895                .get_or_compile(&gate, compile_single_qubit_gate)
896                .expect("Failed to compile gate");
897            gate_id = compiled.gate_id.clone();
898        }
899
900        // Create new cache instance and verify persistence
901        {
902            let cache = CompilationCache::new(config).expect("Failed to create cache");
903            let compiled = cache
904                .get_or_compile(&gate, compile_single_qubit_gate)
905                .expect("Failed to get cached gate");
906
907            assert_eq!(compiled.gate_id, gate_id);
908
909            let stats = cache.statistics();
910            assert_eq!(stats.total_hits, 1); // Should hit persistent cache
911            assert_eq!(stats.total_misses, 0);
912        }
913    }
914
915    #[test]
916    fn test_cache_optimization() {
917        let temp_dir = std::env::temp_dir().join(format!("quantrs_test_{}", std::process::id()));
918        let config = CacheConfig {
919            cache_dir: temp_dir,
920            enable_persistence: true,
921            expiration_time: Duration::from_secs(0), // Immediate expiration
922            async_writes: false,
923            ..Default::default()
924        };
925
926        let cache = CompilationCache::new(config).expect("Failed to create cache");
927        let gate = Hadamard { target: QubitId(0) };
928
929        // Compile and cache gate
930        let _ = cache
931            .get_or_compile(&gate, compile_single_qubit_gate)
932            .expect("Failed to compile gate");
933
934        // Wait a bit and optimize
935        std::thread::sleep(Duration::from_millis(100));
936        cache.optimize().expect("Failed to optimize cache");
937
938        // Try to access again - should miss due to expiration
939        cache.clear().expect("Failed to clear cache"); // Clear memory cache
940        let _ = cache
941            .get_or_compile(&gate, compile_single_qubit_gate)
942            .expect("Failed to recompile gate");
943
944        let stats = cache.statistics();
945        assert_eq!(stats.total_misses, 1); // Should have missed
946    }
947
948    #[test]
949    fn test_precompile_common_gates() {
950        let temp_dir = std::env::temp_dir().join(format!("quantrs_test_{}", std::process::id()));
951        let config = CacheConfig {
952            cache_dir: temp_dir,
953            enable_persistence: false, // Disable persistence to avoid interference
954            async_writes: false,
955            ..Default::default()
956        };
957
958        let cache = CompilationCache::new(config).expect("Failed to create cache");
959        // Clear any existing cache state
960        cache.clear().expect("Failed to clear cache");
961        cache
962            .precompile_common_gates()
963            .expect("Failed to precompile gates");
964
965        let stats = cache.statistics();
966        assert!(stats.num_entries > 0);
967        println!("Precompiled {} gates", stats.num_entries);
968    }
969
970    #[test]
971    fn test_statistics_export() {
972        let temp_dir = std::env::temp_dir().join(format!(
973            "quantrs_test_stats_{}_{}",
974            std::process::id(),
975            std::time::SystemTime::now()
976                .duration_since(std::time::UNIX_EPOCH)
977                .unwrap_or_default()
978                .as_nanos()
979        ));
980        let config = CacheConfig {
981            cache_dir: temp_dir.clone(),
982            enable_persistence: false, // Disable persistence to avoid interference
983            ..Default::default()
984        };
985
986        let cache = CompilationCache::new(config).expect("Failed to create cache");
987        // Clear any existing cache state
988        cache.clear().expect("Failed to clear cache");
989
990        // Generate some statistics
991        let gate = Hadamard { target: QubitId(0) };
992        let _ = cache
993            .get_or_compile(&gate, compile_single_qubit_gate)
994            .expect("Failed to compile gate");
995        let _ = cache
996            .get_or_compile(&gate, compile_single_qubit_gate)
997            .expect("Failed to get cached gate");
998
999        // Export statistics
1000        std::fs::create_dir_all(&temp_dir).expect("Failed to create temp dir");
1001        let stats_path = temp_dir.join("stats.json");
1002        cache
1003            .export_statistics(&stats_path)
1004            .expect("Failed to export statistics");
1005
1006        // Verify file exists and contains valid JSON
1007        assert!(stats_path.exists());
1008        let contents = fs::read_to_string(&stats_path).expect("Failed to read stats file");
1009        let parsed: CacheStatistics =
1010            serde_json::from_str(&contents).expect("Failed to parse JSON");
1011
1012        assert_eq!(parsed.total_hits, 1);
1013        assert_eq!(parsed.total_misses, 1);
1014    }
1015}