1use 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#[derive(Debug, Clone)]
22pub struct CacheEntry<T> {
23 pub value: T,
25 pub created_at: SystemTime,
27 pub last_accessed: SystemTime,
29 pub access_count: usize,
31 pub key_hash: u64,
33 pub size_bytes: usize,
35}
36
37#[derive(Debug, Clone, PartialEq, Eq)]
39pub enum EvictionPolicy {
40 LRU,
42 LFU,
44 TTL(Duration),
46 FIFO,
48 Random,
50 SizeBased,
52}
53
54#[derive(Debug, Clone)]
56pub struct CacheConfig {
57 pub max_entries: usize,
59 pub max_memory_bytes: usize,
61 pub eviction_policy: EvictionPolicy,
63 pub enable_compression: bool,
65 pub collect_stats: bool,
67 pub persist_to_disk: bool,
69 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, 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#[derive(Debug, Clone, Default)]
89pub struct CacheStats {
90 pub hits: usize,
92 pub misses: usize,
94 pub evictions: usize,
96 pub memory_usage_bytes: usize,
98 pub avg_access_time: Duration,
100 pub hit_ratio: f64,
102}
103
104#[derive(Debug, Clone, Hash, PartialEq, Eq)]
106pub struct CircuitSignature {
107 pub num_qubits: usize,
109 pub gate_sequence_hash: u64,
111 pub parameter_hash: u64,
113 pub options_hash: u64,
115}
116
117pub struct CircuitCache<T: Clone> {
119 entries: Arc<RwLock<HashMap<u64, CacheEntry<T>>>>,
121 access_order: Arc<Mutex<VecDeque<u64>>>,
123 config: CacheConfig,
125 stats: Arc<Mutex<CacheStats>>,
127 cleanup_handle: Option<std::thread::JoinHandle<()>>,
129}
130
131impl<T: Clone + Send + Sync + 'static> CircuitCache<T> {
132 #[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 #[must_use]
146 pub fn with_default_config() -> Self {
147 Self::new(CacheConfig::default())
148 }
149
150 #[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 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 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 #[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 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 #[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 #[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 #[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 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 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 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 fn update_access(&self, key_hash: u64) {
331 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 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 access_order.retain(|&x| x != key_hash);
348
349 access_order.push_front(key_hash);
351 }
352 }
353
354 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 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 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 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 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 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(()), }
420 }
421
422 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 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 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 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 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 const fn estimate_size(&self, _value: &T) -> usize {
616 std::mem::size_of::<T>() + 1024 }
620}
621
622pub type CompiledCircuitCache = CircuitCache<CompiledCircuit>;
624
625pub type ExecutionResultCache = CircuitCache<ExecutionResult>;
627
628pub type TranspilationCache<const N: usize> = CircuitCache<TranspilationResult<N>>;
630
631pub struct SignatureGenerator;
633
634impl SignatureGenerator {
635 #[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 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 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 #[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 #[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
699pub struct CacheManager {
701 pub compiled_cache: CompiledCircuitCache,
703 pub execution_cache: ExecutionResultCache,
705 config: CacheConfig,
707}
708
709impl CacheManager {
710 #[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 #[must_use]
722 pub fn with_default_config() -> Self {
723 Self::new(CacheConfig::default())
724 }
725
726 #[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 pub fn clear_all(&self) {
737 self.compiled_cache.clear();
738 self.execution_cache.clear();
739 }
740
741 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 let _ = cache.get(&signature);
815
816 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}