1use scirs2_core::ndarray::{Array1, Array2};
7use scirs2_core::Complex64;
8use serde::{Deserialize, Serialize};
9use std::collections::{HashMap, VecDeque};
10use std::hash::{Hash, Hasher};
11use std::sync::{Arc, Mutex, RwLock};
12use std::time::{Duration, Instant};
13
14#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
16pub struct OperationKey {
17 pub operation_type: String,
19 pub parameters: Vec<f64>,
21 pub qubits: Vec<usize>,
23 pub metadata: HashMap<String, String>,
25}
26
27impl Hash for OperationKey {
28 fn hash<H: Hasher>(&self, state: &mut H) {
29 self.operation_type.hash(state);
30 for param in &self.parameters {
32 let rounded = (param * 1e12).round() as i64;
34 rounded.hash(state);
35 }
36 self.qubits.hash(state);
37 for (k, v) in &self.metadata {
38 k.hash(state);
39 v.hash(state);
40 }
41 }
42}
43
44impl Eq for OperationKey {}
45
46#[derive(Debug, Clone)]
48pub struct CachedOperation {
49 pub data: CachedData,
51 pub created_at: Instant,
53 pub access_count: u64,
55 pub last_accessed: Instant,
57 pub size_bytes: usize,
59}
60
61#[derive(Debug, Clone)]
63pub enum CachedData {
64 SingleQubitMatrix(Array2<Complex64>),
66 TwoQubitMatrix(Array2<Complex64>),
68 Matrix(Array2<Complex64>),
70 StateVector(Array1<Complex64>),
72 ExpectationValue(Complex64),
74 Probabilities(Vec<f64>),
76 Custom(Vec<u8>),
78}
79
80impl CachedData {
81 #[must_use]
83 pub fn estimate_size(&self) -> usize {
84 match self {
85 Self::SingleQubitMatrix(_) => 4 * std::mem::size_of::<Complex64>(),
86 Self::TwoQubitMatrix(_) => 16 * std::mem::size_of::<Complex64>(),
87 Self::Matrix(m) => m.len() * std::mem::size_of::<Complex64>(),
88 Self::StateVector(v) => v.len() * std::mem::size_of::<Complex64>(),
89 Self::ExpectationValue(_) => std::mem::size_of::<Complex64>(),
90 Self::Probabilities(p) => p.len() * std::mem::size_of::<f64>(),
91 Self::Custom(data) => data.len(),
92 }
93 }
94}
95
96#[derive(Debug, Clone, Copy, PartialEq, Eq)]
98pub enum EvictionPolicy {
99 LRU,
101 LFU,
103 TTL,
105 FIFO,
107 Hybrid,
109}
110
111#[derive(Debug, Clone)]
113pub struct CacheConfig {
114 pub max_entries: usize,
116 pub max_memory_bytes: usize,
118 pub eviction_policy: EvictionPolicy,
120 pub ttl: Duration,
122 pub enable_stats: bool,
124 pub cleanup_interval: Duration,
126}
127
128impl Default for CacheConfig {
129 fn default() -> Self {
130 Self {
131 max_entries: 10_000,
132 max_memory_bytes: 256 * 1024 * 1024, eviction_policy: EvictionPolicy::LRU,
134 ttl: Duration::from_secs(3600), enable_stats: true,
136 cleanup_interval: Duration::from_secs(60), }
138 }
139}
140
141#[derive(Debug, Clone, Default)]
143pub struct CacheStats {
144 pub total_requests: u64,
146 pub hits: u64,
148 pub misses: u64,
150 pub evictions: u64,
152 pub current_entries: usize,
154 pub current_memory_bytes: usize,
156 pub peak_memory_bytes: usize,
158 pub average_access_time_ns: f64,
160 pub efficiency_by_type: HashMap<String, f64>,
162}
163
164impl CacheStats {
165 #[must_use]
167 pub fn hit_ratio(&self) -> f64 {
168 if self.total_requests == 0 {
169 0.0
170 } else {
171 self.hits as f64 / self.total_requests as f64
172 }
173 }
174
175 pub fn record_access(&mut self, operation_type: &str, hit: bool, access_time_ns: u64) {
177 self.total_requests += 1;
178 if hit {
179 self.hits += 1;
180 } else {
181 self.misses += 1;
182 }
183
184 let total_time = self
186 .average_access_time_ns
187 .mul_add((self.total_requests - 1) as f64, access_time_ns as f64);
188 self.average_access_time_ns = total_time / self.total_requests as f64;
189
190 let type_stats = self
192 .efficiency_by_type
193 .entry(operation_type.to_string())
194 .or_insert(0.0);
195 if hit {
197 *type_stats = f64::midpoint(*type_stats, 1.0);
198 } else {
199 *type_stats /= 2.0;
200 }
201 }
202}
203
204#[derive(Debug)]
206pub struct QuantumOperationCache {
207 cache: RwLock<HashMap<OperationKey, CachedOperation>>,
209 access_order: Mutex<VecDeque<OperationKey>>,
211 config: CacheConfig,
213 stats: Arc<Mutex<CacheStats>>,
215 last_cleanup: Mutex<Instant>,
217}
218
219impl QuantumOperationCache {
220 #[must_use]
222 pub fn new(config: CacheConfig) -> Self {
223 Self {
224 cache: RwLock::new(HashMap::new()),
225 access_order: Mutex::new(VecDeque::new()),
226 config,
227 stats: Arc::new(Mutex::new(CacheStats::default())),
228 last_cleanup: Mutex::new(Instant::now()),
229 }
230 }
231
232 pub fn get(&self, key: &OperationKey) -> Option<CachedData> {
234 let start_time = Instant::now();
235
236 let result = {
237 let entry_data = {
238 let cache = self
239 .cache
240 .read()
241 .expect("QuantumOperationCache: cache lock poisoned");
242 cache.get(key).map(|entry| entry.data.clone())
243 };
244
245 if entry_data.is_some() {
246 self.update_access_stats(key);
247 }
248
249 entry_data
250 };
251
252 let access_time = start_time.elapsed().as_nanos() as u64;
253
254 if self.config.enable_stats {
256 if let Ok(mut stats) = self.stats.lock() {
257 stats.record_access(&key.operation_type, result.is_some(), access_time);
258 }
259 }
260
261 result
262 }
263
264 pub fn put(&self, key: OperationKey, data: CachedData) {
266 let size = data.estimate_size();
267 let entry = CachedOperation {
268 data,
269 created_at: Instant::now(),
270 access_count: 0,
271 last_accessed: Instant::now(),
272 size_bytes: size,
273 };
274
275 self.maybe_evict(&key, size);
277
278 {
280 let mut cache = self
281 .cache
282 .write()
283 .expect("QuantumOperationCache: cache lock poisoned");
284 cache.insert(key.clone(), entry);
285 }
286
287 {
289 let mut access_order = self
290 .access_order
291 .lock()
292 .expect("QuantumOperationCache: access_order lock poisoned");
293 access_order.push_back(key);
294 }
295
296 if self.config.enable_stats {
298 if let Ok(mut stats) = self.stats.lock() {
299 stats.current_entries = self
300 .cache
301 .read()
302 .expect("QuantumOperationCache: cache lock poisoned")
303 .len();
304 stats.current_memory_bytes += size;
305 if stats.current_memory_bytes > stats.peak_memory_bytes {
306 stats.peak_memory_bytes = stats.current_memory_bytes;
307 }
308 }
309 }
310
311 self.maybe_cleanup();
313 }
314
315 fn update_access_stats(&self, key: &OperationKey) {
317 let mut cache = self
318 .cache
319 .write()
320 .expect("QuantumOperationCache: cache lock poisoned");
321 if let Some(entry) = cache.get_mut(key) {
322 entry.access_count += 1;
323 entry.last_accessed = Instant::now();
324 }
325
326 if self.config.eviction_policy == EvictionPolicy::LRU {
328 let mut access_order = self
329 .access_order
330 .lock()
331 .expect("QuantumOperationCache: access_order lock poisoned");
332 if let Some(pos) = access_order.iter().position(|k| k == key) {
334 access_order.remove(pos);
335 }
336 access_order.push_back(key.clone());
337 }
338 }
339
340 fn maybe_evict(&self, _new_key: &OperationKey, new_size: usize) {
342 let (current_entries, current_memory) = {
343 let cache = self
344 .cache
345 .read()
346 .expect("QuantumOperationCache: cache lock poisoned");
347 (cache.len(), self.get_current_memory_usage())
348 };
349
350 let needs_eviction = current_entries >= self.config.max_entries
351 || current_memory + new_size > self.config.max_memory_bytes;
352
353 if needs_eviction {
354 self.evict_entries();
355 }
356 }
357
358 fn evict_entries(&self) {
360 match self.config.eviction_policy {
361 EvictionPolicy::LRU => self.evict_lru(),
362 EvictionPolicy::LFU => self.evict_lfu(),
363 EvictionPolicy::TTL => self.evict_expired(),
364 EvictionPolicy::FIFO => self.evict_fifo(),
365 EvictionPolicy::Hybrid => self.evict_hybrid(),
366 }
367 }
368
369 fn evict_lru(&self) {
371 let keys_to_evict: Vec<OperationKey> = {
372 let mut access_order = self
373 .access_order
374 .lock()
375 .expect("QuantumOperationCache: access_order lock poisoned");
376 let mut keys = Vec::new();
377
378 let max_evictions = (self.config.max_entries / 4).max(1);
380
381 for _ in 0..max_evictions {
382 if let Some(key) = access_order.pop_front() {
383 keys.push(key);
384 } else {
385 break;
386 }
387 }
388 keys
389 };
390
391 self.remove_entries(keys_to_evict);
392 }
393
394 fn evict_lfu(&self) {
396 let keys_to_evict: Vec<OperationKey> = {
397 let cache = self
398 .cache
399 .read()
400 .expect("QuantumOperationCache: cache lock poisoned");
401 let mut entries: Vec<_> = cache.iter().collect();
402
403 entries.sort_by_key(|(_, entry)| entry.access_count);
405
406 let max_evictions = (cache.len() / 4).max(1);
408 entries
409 .into_iter()
410 .take(max_evictions)
411 .map(|(key, _)| key.clone())
412 .collect()
413 };
414
415 self.remove_entries(keys_to_evict);
416 }
417
418 fn evict_expired(&self) {
420 let now = Instant::now();
421 let keys_to_evict: Vec<OperationKey> = {
422 let cache = self
423 .cache
424 .read()
425 .expect("QuantumOperationCache: cache lock poisoned");
426 cache
427 .iter()
428 .filter(|(_, entry)| now.duration_since(entry.created_at) > self.config.ttl)
429 .map(|(key, _)| key.clone())
430 .collect()
431 };
432
433 self.remove_entries(keys_to_evict);
434 }
435
436 fn evict_fifo(&self) {
438 let keys_to_evict: Vec<OperationKey> = {
440 let cache = self
441 .cache
442 .read()
443 .expect("QuantumOperationCache: cache lock poisoned");
444 let mut entries: Vec<_> = cache.iter().collect();
445
446 entries.sort_by_key(|(_, entry)| entry.created_at);
448
449 let max_evictions = (cache.len() / 4).max(1);
451 entries
452 .into_iter()
453 .take(max_evictions)
454 .map(|(key, _)| key.clone())
455 .collect()
456 };
457
458 self.remove_entries(keys_to_evict);
459 }
460
461 fn evict_hybrid(&self) {
463 let keys_to_evict: Vec<OperationKey> = {
464 let cache = self
465 .cache
466 .read()
467 .expect("QuantumOperationCache: cache lock poisoned");
468 let mut entries: Vec<_> = cache.iter().collect();
469
470 entries.sort_by_key(|(_, entry)| {
472 let recency_score = entry.last_accessed.elapsed().as_secs();
473 let frequency_score = 1000 / (entry.access_count + 1); recency_score + frequency_score
475 });
476
477 let max_evictions = (cache.len() / 4).max(1);
479 entries
480 .into_iter()
481 .take(max_evictions)
482 .map(|(key, _)| key.clone())
483 .collect()
484 };
485
486 self.remove_entries(keys_to_evict);
487 }
488
489 fn remove_entries(&self, keys: Vec<OperationKey>) {
491 let mut total_freed_memory = 0usize;
492
493 {
494 let mut cache = self
495 .cache
496 .write()
497 .expect("QuantumOperationCache: cache lock poisoned");
498 for key in &keys {
499 if let Some(entry) = cache.remove(key) {
500 total_freed_memory += entry.size_bytes;
501 }
502 }
503 }
504
505 {
507 let mut access_order = self
508 .access_order
509 .lock()
510 .expect("QuantumOperationCache: access_order lock poisoned");
511 access_order.retain(|key| !keys.contains(key));
512 }
513
514 if self.config.enable_stats {
516 if let Ok(mut stats) = self.stats.lock() {
517 stats.evictions += keys.len() as u64;
518 stats.current_entries = self
519 .cache
520 .read()
521 .expect("QuantumOperationCache: cache lock poisoned")
522 .len();
523 stats.current_memory_bytes = stats
524 .current_memory_bytes
525 .saturating_sub(total_freed_memory);
526 }
527 }
528 }
529
530 fn get_current_memory_usage(&self) -> usize {
532 let cache = self
533 .cache
534 .read()
535 .expect("QuantumOperationCache: cache lock poisoned");
536 cache.values().map(|entry| entry.size_bytes).sum()
537 }
538
539 fn maybe_cleanup(&self) {
541 if let Ok(mut last_cleanup) = self.last_cleanup.try_lock() {
542 if last_cleanup.elapsed() > self.config.cleanup_interval {
543 self.evict_expired();
544 *last_cleanup = Instant::now();
545 }
546 }
547 }
548
549 pub fn clear(&self) {
551 let mut cache = self
552 .cache
553 .write()
554 .expect("QuantumOperationCache: cache lock poisoned");
555 cache.clear();
556
557 let mut access_order = self
558 .access_order
559 .lock()
560 .expect("QuantumOperationCache: access_order lock poisoned");
561 access_order.clear();
562
563 if self.config.enable_stats {
564 if let Ok(mut stats) = self.stats.lock() {
565 stats.current_entries = 0;
566 stats.current_memory_bytes = 0;
567 }
568 }
569 }
570
571 pub fn get_stats(&self) -> CacheStats {
573 self.stats
574 .lock()
575 .expect("QuantumOperationCache: stats lock poisoned")
576 .clone()
577 }
578
579 pub const fn get_config(&self) -> &CacheConfig {
581 &self.config
582 }
583}
584
585pub struct GateMatrixCache {
587 cache: QuantumOperationCache,
589}
590
591impl Default for GateMatrixCache {
592 fn default() -> Self {
593 Self::new()
594 }
595}
596
597impl GateMatrixCache {
598 #[must_use]
600 pub fn new() -> Self {
601 let config = CacheConfig {
602 max_entries: 5000,
603 max_memory_bytes: 64 * 1024 * 1024, eviction_policy: EvictionPolicy::LRU,
605 ttl: Duration::from_secs(1800), enable_stats: true,
607 cleanup_interval: Duration::from_secs(120), };
609
610 Self {
611 cache: QuantumOperationCache::new(config),
612 }
613 }
614
615 pub fn get_single_qubit_gate(
617 &self,
618 gate_name: &str,
619 parameters: &[f64],
620 ) -> Option<Array2<Complex64>> {
621 let key = OperationKey {
622 operation_type: format!("single_qubit_{gate_name}"),
623 parameters: parameters.to_vec(),
624 qubits: vec![],
625 metadata: HashMap::new(),
626 };
627
628 match self.cache.get(&key) {
629 Some(CachedData::SingleQubitMatrix(matrix)) => Some(matrix),
630 Some(CachedData::Matrix(matrix)) if matrix.shape() == [2, 2] => Some(matrix),
631 _ => None,
632 }
633 }
634
635 pub fn put_single_qubit_gate(
637 &self,
638 gate_name: &str,
639 parameters: &[f64],
640 matrix: Array2<Complex64>,
641 ) {
642 let key = OperationKey {
643 operation_type: format!("single_qubit_{gate_name}"),
644 parameters: parameters.to_vec(),
645 qubits: vec![],
646 metadata: HashMap::new(),
647 };
648
649 self.cache.put(key, CachedData::SingleQubitMatrix(matrix));
650 }
651
652 pub fn get_two_qubit_gate(
654 &self,
655 gate_name: &str,
656 parameters: &[f64],
657 ) -> Option<Array2<Complex64>> {
658 let key = OperationKey {
659 operation_type: format!("two_qubit_{gate_name}"),
660 parameters: parameters.to_vec(),
661 qubits: vec![],
662 metadata: HashMap::new(),
663 };
664
665 match self.cache.get(&key) {
666 Some(CachedData::TwoQubitMatrix(matrix)) => Some(matrix),
667 Some(CachedData::Matrix(matrix)) if matrix.shape() == [4, 4] => Some(matrix),
668 _ => None,
669 }
670 }
671
672 pub fn put_two_qubit_gate(
674 &self,
675 gate_name: &str,
676 parameters: &[f64],
677 matrix: Array2<Complex64>,
678 ) {
679 let key = OperationKey {
680 operation_type: format!("two_qubit_{gate_name}"),
681 parameters: parameters.to_vec(),
682 qubits: vec![],
683 metadata: HashMap::new(),
684 };
685
686 self.cache.put(key, CachedData::TwoQubitMatrix(matrix));
687 }
688
689 pub fn get_stats(&self) -> CacheStats {
691 self.cache.get_stats()
692 }
693}
694
695#[cfg(test)]
696mod tests {
697 use super::*;
698 use scirs2_core::ndarray::array;
699
700 #[test]
701 fn test_operation_cache() {
702 let config = CacheConfig::default();
703 let cache = QuantumOperationCache::new(config);
704
705 let key = OperationKey {
706 operation_type: "test_op".to_string(),
707 parameters: vec![0.5, 1.0],
708 qubits: vec![0, 1],
709 metadata: HashMap::new(),
710 };
711
712 let matrix = array![
713 [Complex64::new(1.0, 0.0), Complex64::new(0.0, 0.0)],
714 [Complex64::new(0.0, 0.0), Complex64::new(1.0, 0.0)]
715 ];
716
717 assert!(cache.get(&key).is_none());
719
720 cache.put(key.clone(), CachedData::Matrix(matrix.clone()));
722
723 match cache.get(&key) {
724 Some(CachedData::Matrix(cached_matrix)) => {
725 assert_eq!(cached_matrix, matrix);
726 }
727 _ => panic!("Expected cached matrix"),
728 }
729
730 let stats = cache.get_stats();
731 assert_eq!(stats.total_requests, 2);
732 assert_eq!(stats.hits, 1);
733 assert_eq!(stats.misses, 1);
734 }
735
736 #[test]
737 fn test_gate_matrix_cache() {
738 let cache = GateMatrixCache::new();
739
740 let pauli_x = array![
741 [Complex64::new(0.0, 0.0), Complex64::new(1.0, 0.0)],
742 [Complex64::new(1.0, 0.0), Complex64::new(0.0, 0.0)]
743 ];
744
745 assert!(cache.get_single_qubit_gate("X", &[]).is_none());
747
748 cache.put_single_qubit_gate("X", &[], pauli_x.clone());
750
751 let cached_matrix = cache
752 .get_single_qubit_gate("X", &[])
753 .expect("cached matrix should exist after put");
754 assert_eq!(cached_matrix, pauli_x);
755 }
756
757 #[test]
758 fn test_eviction_policies() {
759 let mut config = CacheConfig::default();
760 config.max_entries = 2;
761
762 let cache = QuantumOperationCache::new(config);
763
764 for i in 0..3 {
766 let key = OperationKey {
767 operation_type: format!("test_{i}"),
768 parameters: vec![i as f64],
769 qubits: vec![i],
770 metadata: HashMap::new(),
771 };
772 cache.put(
773 key,
774 CachedData::ExpectationValue(Complex64::new(i as f64, 0.0)),
775 );
776 }
777
778 let stats = cache.get_stats();
780 assert_eq!(stats.current_entries, 2);
781 assert!(stats.evictions > 0);
782 }
783}