quantrs2_circuit/
circuit_cache.rs

1//! Circuit caching for repeated execution
2//!
3//! This module provides sophisticated caching mechanisms for quantum circuits to avoid
4//! redundant compilation, optimization, and execution overhead when circuits are reused
5//! across multiple invocations.
6
7use crate::builder::Circuit;
8use crate::simulator_interface::{CompiledCircuit, ExecutionResult};
9use crate::transpiler::TranspilationResult;
10use quantrs2_core::{
11    error::{QuantRS2Error, QuantRS2Result},
12    gate::GateOp,
13};
14use serde::{Deserialize, Serialize};
15use std::collections::{HashMap, VecDeque};
16use std::hash::{Hash, Hasher};
17use std::sync::{Arc, Mutex, RwLock};
18use std::time::{Duration, Instant, SystemTime};
19
20/// Cache entry for compiled circuits
21#[derive(Debug, Clone)]
22pub struct CacheEntry<T> {
23    /// Cached value
24    pub value: T,
25    /// Creation timestamp
26    pub created_at: SystemTime,
27    /// Last access timestamp
28    pub last_accessed: SystemTime,
29    /// Access count
30    pub access_count: usize,
31    /// Cache key hash
32    pub key_hash: u64,
33    /// Size estimate in bytes
34    pub size_bytes: usize,
35}
36
37/// Cache eviction policy
38#[derive(Debug, Clone, PartialEq, Eq)]
39pub enum EvictionPolicy {
40    /// Least Recently Used
41    LRU,
42    /// Least Frequently Used
43    LFU,
44    /// Time To Live
45    TTL(Duration),
46    /// First In First Out
47    FIFO,
48    /// Random eviction
49    Random,
50    /// Size-based eviction
51    SizeBased,
52}
53
54/// Cache configuration
55#[derive(Debug, Clone)]
56pub struct CacheConfig {
57    /// Maximum number of entries
58    pub max_entries: usize,
59    /// Maximum memory usage in bytes
60    pub max_memory_bytes: usize,
61    /// Eviction policy
62    pub eviction_policy: EvictionPolicy,
63    /// Enable compression for stored circuits
64    pub enable_compression: bool,
65    /// Cache hit statistics collection
66    pub collect_stats: bool,
67    /// Persistence to disk
68    pub persist_to_disk: bool,
69    /// Disk cache directory
70    pub cache_directory: Option<String>,
71}
72
73impl Default for CacheConfig {
74    fn default() -> Self {
75        Self {
76            max_entries: 1000,
77            max_memory_bytes: 100 * 1024 * 1024, // 100 MB
78            eviction_policy: EvictionPolicy::LRU,
79            enable_compression: true,
80            collect_stats: true,
81            persist_to_disk: false,
82            cache_directory: None,
83        }
84    }
85}
86
87/// Cache statistics
88#[derive(Debug, Clone, Default)]
89pub struct CacheStats {
90    /// Total cache hits
91    pub hits: usize,
92    /// Total cache misses
93    pub misses: usize,
94    /// Total evictions
95    pub evictions: usize,
96    /// Current memory usage
97    pub memory_usage_bytes: usize,
98    /// Average access time
99    pub avg_access_time: Duration,
100    /// Cache efficiency ratio
101    pub hit_ratio: f64,
102}
103
104/// Circuit signature for cache key generation
105#[derive(Debug, Clone, Hash, PartialEq, Eq)]
106pub struct CircuitSignature {
107    /// Number of qubits
108    pub num_qubits: usize,
109    /// Gate sequence hash
110    pub gate_sequence_hash: u64,
111    /// Parameter hash (for parametrized gates)
112    pub parameter_hash: u64,
113    /// Compiler options hash
114    pub options_hash: u64,
115}
116
117/// Generic cache implementation with configurable policies
118pub struct CircuitCache<T: Clone> {
119    /// Cache entries
120    entries: Arc<RwLock<HashMap<u64, CacheEntry<T>>>>,
121    /// Access order for LRU
122    access_order: Arc<Mutex<VecDeque<u64>>>,
123    /// Cache configuration
124    config: CacheConfig,
125    /// Cache statistics
126    stats: Arc<Mutex<CacheStats>>,
127    /// Background cleanup thread handle
128    cleanup_handle: Option<std::thread::JoinHandle<()>>,
129}
130
131impl<T: Clone + Send + Sync + 'static> CircuitCache<T> {
132    /// Create a new circuit cache
133    #[must_use]
134    pub fn new(config: CacheConfig) -> Self {
135        Self {
136            entries: Arc::new(RwLock::new(HashMap::new())),
137            access_order: Arc::new(Mutex::new(VecDeque::new())),
138            config,
139            stats: Arc::new(Mutex::new(CacheStats::default())),
140            cleanup_handle: None,
141        }
142    }
143
144    /// Create cache with default configuration
145    #[must_use]
146    pub fn with_default_config() -> Self {
147        Self::new(CacheConfig::default())
148    }
149
150    /// Get value from cache
151    #[must_use]
152    pub fn get(&self, key: &CircuitSignature) -> Option<T> {
153        let start_time = Instant::now();
154        let key_hash = self.hash_signature(key);
155
156        let result = {
157            let entries = self
158                .entries
159                .read()
160                .expect("Cache entries lock poisoned - another thread panicked");
161            entries.get(&key_hash).map(|entry| entry.value.clone())
162        };
163
164        if let Some(value) = &result {
165            self.update_access(key_hash);
166            if self.config.collect_stats {
167                self.update_stats(true, start_time.elapsed());
168            }
169        } else if self.config.collect_stats {
170            self.update_stats(false, start_time.elapsed());
171        }
172
173        result
174    }
175
176    /// Put value into cache
177    pub fn put(&self, key: CircuitSignature, value: T) -> QuantRS2Result<()> {
178        let key_hash = self.hash_signature(&key);
179        let size_estimate = self.estimate_size(&value);
180
181        // Check if we need to evict entries
182        self.ensure_capacity(size_estimate)?;
183
184        let entry = CacheEntry {
185            value,
186            created_at: SystemTime::now(),
187            last_accessed: SystemTime::now(),
188            access_count: 1,
189            key_hash,
190            size_bytes: size_estimate,
191        };
192
193        {
194            let mut entries = self
195                .entries
196                .write()
197                .expect("Cache entries lock poisoned - another thread panicked");
198            entries.insert(key_hash, entry);
199        }
200
201        self.update_access(key_hash);
202        self.update_memory_usage(size_estimate as isize);
203
204        Ok(())
205    }
206
207    /// Remove entry from cache
208    #[must_use]
209    pub fn remove(&self, key: &CircuitSignature) -> Option<T> {
210        let key_hash = self.hash_signature(key);
211
212        let removed = {
213            let mut entries = self
214                .entries
215                .write()
216                .expect("Cache entries lock poisoned - another thread panicked");
217            entries.remove(&key_hash)
218        };
219
220        if let Some(entry) = &removed {
221            self.remove_from_access_order(key_hash);
222            self.update_memory_usage(-(entry.size_bytes as isize));
223        }
224
225        removed.map(|entry| entry.value)
226    }
227
228    /// Clear all cache entries
229    pub fn clear(&self) {
230        {
231            let mut entries = self
232                .entries
233                .write()
234                .expect("Cache entries lock poisoned - another thread panicked");
235            entries.clear();
236        }
237
238        {
239            let mut access_order = self
240                .access_order
241                .lock()
242                .expect("Access order lock poisoned - another thread panicked");
243            access_order.clear();
244        }
245
246        if let Ok(mut stats) = self.stats.lock() {
247            stats.memory_usage_bytes = 0;
248        }
249    }
250
251    /// Get cache statistics
252    #[must_use]
253    pub fn get_stats(&self) -> CacheStats {
254        self.stats
255            .lock()
256            .expect("Cache stats lock poisoned - another thread panicked")
257            .clone()
258    }
259
260    /// Get current cache size
261    #[must_use]
262    pub fn size(&self) -> usize {
263        self.entries
264            .read()
265            .expect("Cache entries lock poisoned - another thread panicked")
266            .len()
267    }
268
269    /// Check if cache contains key
270    #[must_use]
271    pub fn contains_key(&self, key: &CircuitSignature) -> bool {
272        let key_hash = self.hash_signature(key);
273        self.entries
274            .read()
275            .expect("Cache entries lock poisoned - another thread panicked")
276            .contains_key(&key_hash)
277    }
278
279    /// Start background cleanup process
280    pub fn start_cleanup(&mut self, interval: Duration) {
281        let entries = Arc::clone(&self.entries);
282        let config = self.config.clone();
283        let stats = Arc::clone(&self.stats);
284
285        let handle = std::thread::spawn(move || {
286            loop {
287                std::thread::sleep(interval);
288
289                if let Ok(mut entries_guard) = entries.write() {
290                    let now = SystemTime::now();
291                    let mut to_remove = Vec::new();
292
293                    // TTL-based cleanup
294                    if let EvictionPolicy::TTL(ttl) = &config.eviction_policy {
295                        for (key, entry) in entries_guard.iter() {
296                            if let Ok(elapsed) = now.duration_since(entry.created_at) {
297                                if elapsed > *ttl {
298                                    to_remove.push(*key);
299                                }
300                            }
301                        }
302
303                        for key in to_remove {
304                            if let Some(entry) = entries_guard.remove(&key) {
305                                if let Ok(mut stats_guard) = stats.lock() {
306                                    stats_guard.evictions += 1;
307                                    stats_guard.memory_usage_bytes = stats_guard
308                                        .memory_usage_bytes
309                                        .saturating_sub(entry.size_bytes);
310                                }
311                            }
312                        }
313                    }
314                }
315            }
316        });
317
318        self.cleanup_handle = Some(handle);
319    }
320
321    /// Hash circuit signature
322    fn hash_signature(&self, signature: &CircuitSignature) -> u64 {
323        use std::collections::hash_map::DefaultHasher;
324        let mut hasher = DefaultHasher::new();
325        signature.hash(&mut hasher);
326        hasher.finish()
327    }
328
329    /// Update access information
330    fn update_access(&self, key_hash: u64) {
331        // Update last accessed time
332        if let Ok(mut entries) = self.entries.write() {
333            if let Some(entry) = entries.get_mut(&key_hash) {
334                entry.last_accessed = SystemTime::now();
335                entry.access_count += 1;
336            }
337        }
338
339        // Update access order for LRU
340        if matches!(self.config.eviction_policy, EvictionPolicy::LRU) {
341            let mut access_order = self
342                .access_order
343                .lock()
344                .expect("Access order lock poisoned - another thread panicked");
345
346            // Remove from current position
347            access_order.retain(|&x| x != key_hash);
348
349            // Add to front
350            access_order.push_front(key_hash);
351        }
352    }
353
354    /// Remove from access order tracking
355    fn remove_from_access_order(&self, key_hash: u64) {
356        let mut access_order = self
357            .access_order
358            .lock()
359            .expect("Access order lock poisoned - another thread panicked");
360        access_order.retain(|&x| x != key_hash);
361    }
362
363    /// Update cache statistics
364    fn update_stats(&self, hit: bool, access_time: Duration) {
365        if let Ok(mut stats) = self.stats.lock() {
366            if hit {
367                stats.hits += 1;
368            } else {
369                stats.misses += 1;
370            }
371
372            let total_accesses = stats.hits + stats.misses;
373            stats.hit_ratio = stats.hits as f64 / total_accesses as f64;
374
375            // Update average access time (simple moving average)
376            let current_avg_nanos = stats.avg_access_time.as_nanos() as f64;
377            let new_avg_nanos = current_avg_nanos
378                .mul_add((total_accesses - 1) as f64, access_time.as_nanos() as f64)
379                / total_accesses as f64;
380            stats.avg_access_time = Duration::from_nanos(new_avg_nanos as u64);
381        }
382    }
383
384    /// Update memory usage statistics
385    fn update_memory_usage(&self, delta: isize) {
386        if let Ok(mut stats) = self.stats.lock() {
387            if delta > 0 {
388                stats.memory_usage_bytes = stats.memory_usage_bytes.saturating_add(delta as usize);
389            } else {
390                stats.memory_usage_bytes =
391                    stats.memory_usage_bytes.saturating_sub((-delta) as usize);
392            }
393        }
394    }
395
396    /// Ensure cache has capacity for new entry
397    fn ensure_capacity(&self, new_entry_size: usize) -> QuantRS2Result<()> {
398        let current_stats = self.get_stats();
399        let would_exceed_memory =
400            current_stats.memory_usage_bytes + new_entry_size > self.config.max_memory_bytes;
401        let would_exceed_count = self.size() >= self.config.max_entries;
402
403        if would_exceed_memory || would_exceed_count {
404            self.evict_entries(new_entry_size)?;
405        }
406
407        Ok(())
408    }
409
410    /// Evict entries based on policy
411    fn evict_entries(&self, needed_space: usize) -> QuantRS2Result<()> {
412        match &self.config.eviction_policy {
413            EvictionPolicy::LRU => self.evict_lru(needed_space),
414            EvictionPolicy::LFU => self.evict_lfu(needed_space),
415            EvictionPolicy::FIFO => self.evict_fifo(needed_space),
416            EvictionPolicy::Random => self.evict_random(needed_space),
417            EvictionPolicy::SizeBased => self.evict_size_based(needed_space),
418            EvictionPolicy::TTL(_) => Ok(()), // TTL is handled by background cleanup
419        }
420    }
421
422    /// Evict using LRU policy
423    fn evict_lru(&self, needed_space: usize) -> QuantRS2Result<()> {
424        let mut freed_space = 0;
425        let mut evicted_count = 0;
426
427        while freed_space < needed_space && self.size() > 0 {
428            let key_to_evict = {
429                let access_order = self
430                    .access_order
431                    .lock()
432                    .expect("Access order lock poisoned - another thread panicked");
433                access_order.back().copied()
434            };
435
436            if let Some(key) = key_to_evict {
437                if let Ok(mut entries) = self.entries.write() {
438                    if let Some(entry) = entries.remove(&key) {
439                        freed_space += entry.size_bytes;
440                        evicted_count += 1;
441                        self.remove_from_access_order(key);
442                    }
443                }
444            } else {
445                break;
446            }
447        }
448
449        if let Ok(mut stats) = self.stats.lock() {
450            stats.evictions += evicted_count;
451            stats.memory_usage_bytes = stats.memory_usage_bytes.saturating_sub(freed_space);
452        }
453
454        Ok(())
455    }
456
457    /// Evict using LFU policy
458    fn evict_lfu(&self, needed_space: usize) -> QuantRS2Result<()> {
459        let mut freed_space = 0;
460        let mut evicted_count = 0;
461
462        while freed_space < needed_space && self.size() > 0 {
463            let key_to_evict = {
464                let entries = self
465                    .entries
466                    .read()
467                    .expect("Cache entries lock poisoned - another thread panicked");
468                entries
469                    .iter()
470                    .min_by_key(|(_, entry)| entry.access_count)
471                    .map(|(key, _)| *key)
472            };
473
474            if let Some(key) = key_to_evict {
475                if let Ok(mut entries) = self.entries.write() {
476                    if let Some(entry) = entries.remove(&key) {
477                        freed_space += entry.size_bytes;
478                        evicted_count += 1;
479                        self.remove_from_access_order(key);
480                    }
481                }
482            } else {
483                break;
484            }
485        }
486
487        if let Ok(mut stats) = self.stats.lock() {
488            stats.evictions += evicted_count;
489            stats.memory_usage_bytes = stats.memory_usage_bytes.saturating_sub(freed_space);
490        }
491
492        Ok(())
493    }
494
495    /// Evict using FIFO policy
496    fn evict_fifo(&self, needed_space: usize) -> QuantRS2Result<()> {
497        let mut freed_space = 0;
498        let mut evicted_count = 0;
499
500        while freed_space < needed_space && self.size() > 0 {
501            let key_to_evict = {
502                let entries = self
503                    .entries
504                    .read()
505                    .expect("Cache entries lock poisoned - another thread panicked");
506                entries
507                    .iter()
508                    .min_by_key(|(_, entry)| entry.created_at)
509                    .map(|(key, _)| *key)
510            };
511
512            if let Some(key) = key_to_evict {
513                if let Ok(mut entries) = self.entries.write() {
514                    if let Some(entry) = entries.remove(&key) {
515                        freed_space += entry.size_bytes;
516                        evicted_count += 1;
517                        self.remove_from_access_order(key);
518                    }
519                }
520            } else {
521                break;
522            }
523        }
524
525        if let Ok(mut stats) = self.stats.lock() {
526            stats.evictions += evicted_count;
527            stats.memory_usage_bytes = stats.memory_usage_bytes.saturating_sub(freed_space);
528        }
529
530        Ok(())
531    }
532
533    /// Evict random entries
534    fn evict_random(&self, needed_space: usize) -> QuantRS2Result<()> {
535        use scirs2_core::random::prelude::*;
536        let mut rng = thread_rng();
537        let mut freed_space = 0;
538        let mut evicted_count = 0;
539
540        while freed_space < needed_space && self.size() > 0 {
541            let key_to_evict = {
542                let entries = self
543                    .entries
544                    .read()
545                    .expect("Cache entries lock poisoned - another thread panicked");
546                let keys: Vec<u64> = entries.keys().copied().collect();
547                if keys.is_empty() {
548                    None
549                } else {
550                    let idx = rng.gen_range(0..keys.len());
551                    Some(keys[idx])
552                }
553            };
554
555            if let Some(key) = key_to_evict {
556                if let Ok(mut entries) = self.entries.write() {
557                    if let Some(entry) = entries.remove(&key) {
558                        freed_space += entry.size_bytes;
559                        evicted_count += 1;
560                        self.remove_from_access_order(key);
561                    }
562                }
563            } else {
564                break;
565            }
566        }
567
568        if let Ok(mut stats) = self.stats.lock() {
569            stats.evictions += evicted_count;
570            stats.memory_usage_bytes = stats.memory_usage_bytes.saturating_sub(freed_space);
571        }
572
573        Ok(())
574    }
575
576    /// Evict largest entries first
577    fn evict_size_based(&self, needed_space: usize) -> QuantRS2Result<()> {
578        let mut freed_space = 0;
579        let mut evicted_count = 0;
580
581        while freed_space < needed_space && self.size() > 0 {
582            let key_to_evict = {
583                let entries = self
584                    .entries
585                    .read()
586                    .expect("Cache entries lock poisoned - another thread panicked");
587                entries
588                    .iter()
589                    .max_by_key(|(_, entry)| entry.size_bytes)
590                    .map(|(key, _)| *key)
591            };
592
593            if let Some(key) = key_to_evict {
594                if let Ok(mut entries) = self.entries.write() {
595                    if let Some(entry) = entries.remove(&key) {
596                        freed_space += entry.size_bytes;
597                        evicted_count += 1;
598                        self.remove_from_access_order(key);
599                    }
600                }
601            } else {
602                break;
603            }
604        }
605
606        if let Ok(mut stats) = self.stats.lock() {
607            stats.evictions += evicted_count;
608            stats.memory_usage_bytes = stats.memory_usage_bytes.saturating_sub(freed_space);
609        }
610
611        Ok(())
612    }
613
614    /// Estimate size of cached value
615    const fn estimate_size(&self, _value: &T) -> usize {
616        // This is a simplified size estimation
617        // In practice, this would use serialization or memory introspection
618        std::mem::size_of::<T>() + 1024 // Base overhead estimate
619    }
620}
621
622/// Specialized cache for compiled circuits
623pub type CompiledCircuitCache = CircuitCache<CompiledCircuit>;
624
625/// Specialized cache for execution results
626pub type ExecutionResultCache = CircuitCache<ExecutionResult>;
627
628/// Specialized cache for transpilation results
629pub type TranspilationCache<const N: usize> = CircuitCache<TranspilationResult<N>>;
630
631/// Circuit signature generator for different circuit types
632pub struct SignatureGenerator;
633
634impl SignatureGenerator {
635    /// Generate signature for a circuit
636    #[must_use]
637    pub fn generate_circuit_signature<const N: usize>(
638        circuit: &Circuit<N>,
639        options_hash: u64,
640    ) -> CircuitSignature {
641        use std::collections::hash_map::DefaultHasher;
642
643        let mut gate_hasher = DefaultHasher::new();
644        let mut param_hasher = DefaultHasher::new();
645
646        // Hash gate sequence
647        for gate in circuit.gates() {
648            gate.name().hash(&mut gate_hasher);
649            for qubit in gate.qubits() {
650                qubit.id().hash(&mut gate_hasher);
651            }
652
653            // Hash parameters if any (simplified)
654            // In practice, this would extract actual gate parameters
655            0u64.hash(&mut param_hasher);
656        }
657
658        CircuitSignature {
659            num_qubits: N,
660            gate_sequence_hash: gate_hasher.finish(),
661            parameter_hash: param_hasher.finish(),
662            options_hash,
663        }
664    }
665
666    /// Generate signature with compilation options
667    #[must_use]
668    pub fn generate_with_compilation_options<const N: usize>(
669        circuit: &Circuit<N>,
670        backend: &str,
671        optimization_level: u32,
672    ) -> CircuitSignature {
673        use std::collections::hash_map::DefaultHasher;
674
675        let mut options_hasher = DefaultHasher::new();
676        backend.hash(&mut options_hasher);
677        optimization_level.hash(&mut options_hasher);
678
679        Self::generate_circuit_signature(circuit, options_hasher.finish())
680    }
681
682    /// Generate signature with transpilation options
683    #[must_use]
684    pub fn generate_with_transpilation_options<const N: usize>(
685        circuit: &Circuit<N>,
686        device: &str,
687        strategy: &str,
688    ) -> CircuitSignature {
689        use std::collections::hash_map::DefaultHasher;
690
691        let mut options_hasher = DefaultHasher::new();
692        device.hash(&mut options_hasher);
693        strategy.hash(&mut options_hasher);
694
695        Self::generate_circuit_signature(circuit, options_hasher.finish())
696    }
697}
698
699/// High-level cache manager that coordinates different cache types
700pub struct CacheManager {
701    /// Cache for compiled circuits
702    pub compiled_cache: CompiledCircuitCache,
703    /// Cache for execution results
704    pub execution_cache: ExecutionResultCache,
705    /// Global cache configuration
706    config: CacheConfig,
707}
708
709impl CacheManager {
710    /// Create a new cache manager
711    #[must_use]
712    pub fn new(config: CacheConfig) -> Self {
713        Self {
714            compiled_cache: CompiledCircuitCache::new(config.clone()),
715            execution_cache: ExecutionResultCache::new(config.clone()),
716            config,
717        }
718    }
719
720    /// Create with default configuration
721    #[must_use]
722    pub fn with_default_config() -> Self {
723        Self::new(CacheConfig::default())
724    }
725
726    /// Get aggregated cache statistics
727    #[must_use]
728    pub fn get_aggregated_stats(&self) -> HashMap<String, CacheStats> {
729        let mut stats = HashMap::new();
730        stats.insert("compiled".to_string(), self.compiled_cache.get_stats());
731        stats.insert("execution".to_string(), self.execution_cache.get_stats());
732        stats
733    }
734
735    /// Clear all caches
736    pub fn clear_all(&self) {
737        self.compiled_cache.clear();
738        self.execution_cache.clear();
739    }
740
741    /// Start background cleanup for all caches
742    pub fn start_background_cleanup(&mut self, interval: Duration) {
743        self.compiled_cache.start_cleanup(interval);
744        self.execution_cache.start_cleanup(interval);
745    }
746}
747
748#[cfg(test)]
749mod tests {
750    use super::*;
751    use quantrs2_core::gate::single::Hadamard;
752    use quantrs2_core::qubit::QubitId;
753
754    #[test]
755    fn test_cache_creation() {
756        let cache: CircuitCache<String> = CircuitCache::with_default_config();
757        assert_eq!(cache.size(), 0);
758    }
759
760    #[test]
761    fn test_cache_put_get() {
762        let cache: CircuitCache<String> = CircuitCache::with_default_config();
763        let signature = CircuitSignature {
764            num_qubits: 2,
765            gate_sequence_hash: 12345,
766            parameter_hash: 67890,
767            options_hash: 11111,
768        };
769
770        cache
771            .put(signature.clone(), "test_value".to_string())
772            .expect("Failed to put value into cache");
773        let result = cache.get(&signature);
774        assert_eq!(result, Some("test_value".to_string()));
775    }
776
777    #[test]
778    fn test_cache_miss() {
779        let cache: CircuitCache<String> = CircuitCache::with_default_config();
780        let signature = CircuitSignature {
781            num_qubits: 2,
782            gate_sequence_hash: 12345,
783            parameter_hash: 67890,
784            options_hash: 11111,
785        };
786
787        let result = cache.get(&signature);
788        assert_eq!(result, None);
789    }
790
791    #[test]
792    fn test_signature_generation() {
793        let mut circuit = Circuit::<2>::new();
794        circuit
795            .add_gate(Hadamard { target: QubitId(0) })
796            .expect("Failed to add Hadamard gate");
797
798        let signature = SignatureGenerator::generate_circuit_signature(&circuit, 0);
799        assert_eq!(signature.num_qubits, 2);
800        assert_ne!(signature.gate_sequence_hash, 0);
801    }
802
803    #[test]
804    fn test_cache_stats() {
805        let cache: CircuitCache<String> = CircuitCache::with_default_config();
806        let signature = CircuitSignature {
807            num_qubits: 2,
808            gate_sequence_hash: 12345,
809            parameter_hash: 67890,
810            options_hash: 11111,
811        };
812
813        // Miss
814        let _ = cache.get(&signature);
815
816        // Put and hit
817        cache
818            .put(signature.clone(), "test".to_string())
819            .expect("Failed to put value into cache");
820        let _ = cache.get(&signature);
821
822        let stats = cache.get_stats();
823        assert_eq!(stats.hits, 1);
824        assert_eq!(stats.misses, 1);
825        assert_eq!(stats.hit_ratio, 0.5);
826    }
827
828    #[test]
829    fn test_cache_manager() {
830        let manager = CacheManager::with_default_config();
831        let stats = manager.get_aggregated_stats();
832        assert!(stats.contains_key("compiled"));
833        assert!(stats.contains_key("execution"));
834    }
835}