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