use scirs2_core::ndarray::{Array1, Array2};
use scirs2_core::Complex64;
use serde::{Deserialize, Serialize};
use std::collections::{HashMap, VecDeque};
use std::hash::{Hash, Hasher};
use std::sync::{Arc, Mutex, RwLock};
use std::time::{Duration, Instant};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct OperationKey {
pub operation_type: String,
pub parameters: Vec<f64>,
pub qubits: Vec<usize>,
pub metadata: HashMap<String, String>,
}
impl Hash for OperationKey {
fn hash<H: Hasher>(&self, state: &mut H) {
self.operation_type.hash(state);
for param in &self.parameters {
let rounded = (param * 1e12).round() as i64;
rounded.hash(state);
}
self.qubits.hash(state);
for (k, v) in &self.metadata {
k.hash(state);
v.hash(state);
}
}
}
impl Eq for OperationKey {}
#[derive(Debug, Clone)]
pub struct CachedOperation {
pub data: CachedData,
pub created_at: Instant,
pub access_count: u64,
pub last_accessed: Instant,
pub size_bytes: usize,
}
#[derive(Debug, Clone)]
pub enum CachedData {
SingleQubitMatrix(Array2<Complex64>),
TwoQubitMatrix(Array2<Complex64>),
Matrix(Array2<Complex64>),
StateVector(Array1<Complex64>),
ExpectationValue(Complex64),
Probabilities(Vec<f64>),
Custom(Vec<u8>),
}
impl CachedData {
#[must_use]
pub fn estimate_size(&self) -> usize {
match self {
Self::SingleQubitMatrix(_) => 4 * std::mem::size_of::<Complex64>(),
Self::TwoQubitMatrix(_) => 16 * std::mem::size_of::<Complex64>(),
Self::Matrix(m) => m.len() * std::mem::size_of::<Complex64>(),
Self::StateVector(v) => v.len() * std::mem::size_of::<Complex64>(),
Self::ExpectationValue(_) => std::mem::size_of::<Complex64>(),
Self::Probabilities(p) => p.len() * std::mem::size_of::<f64>(),
Self::Custom(data) => data.len(),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EvictionPolicy {
LRU,
LFU,
TTL,
FIFO,
Hybrid,
}
#[derive(Debug, Clone)]
pub struct CacheConfig {
pub max_entries: usize,
pub max_memory_bytes: usize,
pub eviction_policy: EvictionPolicy,
pub ttl: Duration,
pub enable_stats: bool,
pub cleanup_interval: Duration,
}
impl Default for CacheConfig {
fn default() -> Self {
Self {
max_entries: 10_000,
max_memory_bytes: 256 * 1024 * 1024, eviction_policy: EvictionPolicy::LRU,
ttl: Duration::from_secs(3600), enable_stats: true,
cleanup_interval: Duration::from_secs(60), }
}
}
#[derive(Debug, Clone, Default)]
pub struct CacheStats {
pub total_requests: u64,
pub hits: u64,
pub misses: u64,
pub evictions: u64,
pub current_entries: usize,
pub current_memory_bytes: usize,
pub peak_memory_bytes: usize,
pub average_access_time_ns: f64,
pub efficiency_by_type: HashMap<String, f64>,
}
impl CacheStats {
#[must_use]
pub fn hit_ratio(&self) -> f64 {
if self.total_requests == 0 {
0.0
} else {
self.hits as f64 / self.total_requests as f64
}
}
pub fn record_access(&mut self, operation_type: &str, hit: bool, access_time_ns: u64) {
self.total_requests += 1;
if hit {
self.hits += 1;
} else {
self.misses += 1;
}
let total_time = self
.average_access_time_ns
.mul_add((self.total_requests - 1) as f64, access_time_ns as f64);
self.average_access_time_ns = total_time / self.total_requests as f64;
let type_stats = self
.efficiency_by_type
.entry(operation_type.to_string())
.or_insert(0.0);
if hit {
*type_stats = f64::midpoint(*type_stats, 1.0);
} else {
*type_stats /= 2.0;
}
}
}
#[derive(Debug)]
pub struct QuantumOperationCache {
cache: RwLock<HashMap<OperationKey, CachedOperation>>,
access_order: Mutex<VecDeque<OperationKey>>,
config: CacheConfig,
stats: Arc<Mutex<CacheStats>>,
last_cleanup: Mutex<Instant>,
}
impl QuantumOperationCache {
#[must_use]
pub fn new(config: CacheConfig) -> Self {
Self {
cache: RwLock::new(HashMap::new()),
access_order: Mutex::new(VecDeque::new()),
config,
stats: Arc::new(Mutex::new(CacheStats::default())),
last_cleanup: Mutex::new(Instant::now()),
}
}
pub fn get(&self, key: &OperationKey) -> Option<CachedData> {
let start_time = Instant::now();
let result = {
let entry_data = {
let cache = self
.cache
.read()
.expect("QuantumOperationCache: cache lock poisoned");
cache.get(key).map(|entry| entry.data.clone())
};
if entry_data.is_some() {
self.update_access_stats(key);
}
entry_data
};
let access_time = start_time.elapsed().as_nanos() as u64;
if self.config.enable_stats {
if let Ok(mut stats) = self.stats.lock() {
stats.record_access(&key.operation_type, result.is_some(), access_time);
}
}
result
}
pub fn put(&self, key: OperationKey, data: CachedData) {
let size = data.estimate_size();
let entry = CachedOperation {
data,
created_at: Instant::now(),
access_count: 0,
last_accessed: Instant::now(),
size_bytes: size,
};
self.maybe_evict(&key, size);
{
let mut cache = self
.cache
.write()
.expect("QuantumOperationCache: cache lock poisoned");
cache.insert(key.clone(), entry);
}
{
let mut access_order = self
.access_order
.lock()
.expect("QuantumOperationCache: access_order lock poisoned");
access_order.push_back(key);
}
if self.config.enable_stats {
if let Ok(mut stats) = self.stats.lock() {
stats.current_entries = self
.cache
.read()
.expect("QuantumOperationCache: cache lock poisoned")
.len();
stats.current_memory_bytes += size;
if stats.current_memory_bytes > stats.peak_memory_bytes {
stats.peak_memory_bytes = stats.current_memory_bytes;
}
}
}
self.maybe_cleanup();
}
fn update_access_stats(&self, key: &OperationKey) {
let mut cache = self
.cache
.write()
.expect("QuantumOperationCache: cache lock poisoned");
if let Some(entry) = cache.get_mut(key) {
entry.access_count += 1;
entry.last_accessed = Instant::now();
}
if self.config.eviction_policy == EvictionPolicy::LRU {
let mut access_order = self
.access_order
.lock()
.expect("QuantumOperationCache: access_order lock poisoned");
if let Some(pos) = access_order.iter().position(|k| k == key) {
access_order.remove(pos);
}
access_order.push_back(key.clone());
}
}
fn maybe_evict(&self, _new_key: &OperationKey, new_size: usize) {
let (current_entries, current_memory) = {
let cache = self
.cache
.read()
.expect("QuantumOperationCache: cache lock poisoned");
(cache.len(), self.get_current_memory_usage())
};
let needs_eviction = current_entries >= self.config.max_entries
|| current_memory + new_size > self.config.max_memory_bytes;
if needs_eviction {
self.evict_entries();
}
}
fn evict_entries(&self) {
match self.config.eviction_policy {
EvictionPolicy::LRU => self.evict_lru(),
EvictionPolicy::LFU => self.evict_lfu(),
EvictionPolicy::TTL => self.evict_expired(),
EvictionPolicy::FIFO => self.evict_fifo(),
EvictionPolicy::Hybrid => self.evict_hybrid(),
}
}
fn evict_lru(&self) {
let keys_to_evict: Vec<OperationKey> = {
let mut access_order = self
.access_order
.lock()
.expect("QuantumOperationCache: access_order lock poisoned");
let mut keys = Vec::new();
let max_evictions = (self.config.max_entries / 4).max(1);
for _ in 0..max_evictions {
if let Some(key) = access_order.pop_front() {
keys.push(key);
} else {
break;
}
}
keys
};
self.remove_entries(keys_to_evict);
}
fn evict_lfu(&self) {
let keys_to_evict: Vec<OperationKey> = {
let cache = self
.cache
.read()
.expect("QuantumOperationCache: cache lock poisoned");
let mut entries: Vec<_> = cache.iter().collect();
entries.sort_by_key(|(_, entry)| entry.access_count);
let max_evictions = (cache.len() / 4).max(1);
entries
.into_iter()
.take(max_evictions)
.map(|(key, _)| key.clone())
.collect()
};
self.remove_entries(keys_to_evict);
}
fn evict_expired(&self) {
let now = Instant::now();
let keys_to_evict: Vec<OperationKey> = {
let cache = self
.cache
.read()
.expect("QuantumOperationCache: cache lock poisoned");
cache
.iter()
.filter(|(_, entry)| now.duration_since(entry.created_at) > self.config.ttl)
.map(|(key, _)| key.clone())
.collect()
};
self.remove_entries(keys_to_evict);
}
fn evict_fifo(&self) {
let keys_to_evict: Vec<OperationKey> = {
let cache = self
.cache
.read()
.expect("QuantumOperationCache: cache lock poisoned");
let mut entries: Vec<_> = cache.iter().collect();
entries.sort_by_key(|(_, entry)| entry.created_at);
let max_evictions = (cache.len() / 4).max(1);
entries
.into_iter()
.take(max_evictions)
.map(|(key, _)| key.clone())
.collect()
};
self.remove_entries(keys_to_evict);
}
fn evict_hybrid(&self) {
let keys_to_evict: Vec<OperationKey> = {
let cache = self
.cache
.read()
.expect("QuantumOperationCache: cache lock poisoned");
let mut entries: Vec<_> = cache.iter().collect();
entries.sort_by_key(|(_, entry)| {
let recency_score = entry.last_accessed.elapsed().as_secs();
let frequency_score = 1000 / (entry.access_count + 1); recency_score + frequency_score
});
let max_evictions = (cache.len() / 4).max(1);
entries
.into_iter()
.take(max_evictions)
.map(|(key, _)| key.clone())
.collect()
};
self.remove_entries(keys_to_evict);
}
fn remove_entries(&self, keys: Vec<OperationKey>) {
let mut total_freed_memory = 0usize;
{
let mut cache = self
.cache
.write()
.expect("QuantumOperationCache: cache lock poisoned");
for key in &keys {
if let Some(entry) = cache.remove(key) {
total_freed_memory += entry.size_bytes;
}
}
}
{
let mut access_order = self
.access_order
.lock()
.expect("QuantumOperationCache: access_order lock poisoned");
access_order.retain(|key| !keys.contains(key));
}
if self.config.enable_stats {
if let Ok(mut stats) = self.stats.lock() {
stats.evictions += keys.len() as u64;
stats.current_entries = self
.cache
.read()
.expect("QuantumOperationCache: cache lock poisoned")
.len();
stats.current_memory_bytes = stats
.current_memory_bytes
.saturating_sub(total_freed_memory);
}
}
}
fn get_current_memory_usage(&self) -> usize {
let cache = self
.cache
.read()
.expect("QuantumOperationCache: cache lock poisoned");
cache.values().map(|entry| entry.size_bytes).sum()
}
fn maybe_cleanup(&self) {
if let Ok(mut last_cleanup) = self.last_cleanup.try_lock() {
if last_cleanup.elapsed() > self.config.cleanup_interval {
self.evict_expired();
*last_cleanup = Instant::now();
}
}
}
pub fn clear(&self) {
let mut cache = self
.cache
.write()
.expect("QuantumOperationCache: cache lock poisoned");
cache.clear();
let mut access_order = self
.access_order
.lock()
.expect("QuantumOperationCache: access_order lock poisoned");
access_order.clear();
if self.config.enable_stats {
if let Ok(mut stats) = self.stats.lock() {
stats.current_entries = 0;
stats.current_memory_bytes = 0;
}
}
}
pub fn get_stats(&self) -> CacheStats {
self.stats
.lock()
.expect("QuantumOperationCache: stats lock poisoned")
.clone()
}
pub const fn get_config(&self) -> &CacheConfig {
&self.config
}
}
pub struct GateMatrixCache {
cache: QuantumOperationCache,
}
impl Default for GateMatrixCache {
fn default() -> Self {
Self::new()
}
}
impl GateMatrixCache {
#[must_use]
pub fn new() -> Self {
let config = CacheConfig {
max_entries: 5000,
max_memory_bytes: 64 * 1024 * 1024, eviction_policy: EvictionPolicy::LRU,
ttl: Duration::from_secs(1800), enable_stats: true,
cleanup_interval: Duration::from_secs(120), };
Self {
cache: QuantumOperationCache::new(config),
}
}
pub fn get_single_qubit_gate(
&self,
gate_name: &str,
parameters: &[f64],
) -> Option<Array2<Complex64>> {
let key = OperationKey {
operation_type: format!("single_qubit_{gate_name}"),
parameters: parameters.to_vec(),
qubits: vec![],
metadata: HashMap::new(),
};
match self.cache.get(&key) {
Some(CachedData::SingleQubitMatrix(matrix)) => Some(matrix),
Some(CachedData::Matrix(matrix)) if matrix.shape() == [2, 2] => Some(matrix),
_ => None,
}
}
pub fn put_single_qubit_gate(
&self,
gate_name: &str,
parameters: &[f64],
matrix: Array2<Complex64>,
) {
let key = OperationKey {
operation_type: format!("single_qubit_{gate_name}"),
parameters: parameters.to_vec(),
qubits: vec![],
metadata: HashMap::new(),
};
self.cache.put(key, CachedData::SingleQubitMatrix(matrix));
}
pub fn get_two_qubit_gate(
&self,
gate_name: &str,
parameters: &[f64],
) -> Option<Array2<Complex64>> {
let key = OperationKey {
operation_type: format!("two_qubit_{gate_name}"),
parameters: parameters.to_vec(),
qubits: vec![],
metadata: HashMap::new(),
};
match self.cache.get(&key) {
Some(CachedData::TwoQubitMatrix(matrix)) => Some(matrix),
Some(CachedData::Matrix(matrix)) if matrix.shape() == [4, 4] => Some(matrix),
_ => None,
}
}
pub fn put_two_qubit_gate(
&self,
gate_name: &str,
parameters: &[f64],
matrix: Array2<Complex64>,
) {
let key = OperationKey {
operation_type: format!("two_qubit_{gate_name}"),
parameters: parameters.to_vec(),
qubits: vec![],
metadata: HashMap::new(),
};
self.cache.put(key, CachedData::TwoQubitMatrix(matrix));
}
pub fn get_stats(&self) -> CacheStats {
self.cache.get_stats()
}
}
#[cfg(test)]
#[allow(clippy::field_reassign_with_default)]
mod tests {
use super::*;
use scirs2_core::ndarray::array;
#[test]
fn test_operation_cache() {
let config = CacheConfig::default();
let cache = QuantumOperationCache::new(config);
let key = OperationKey {
operation_type: "test_op".to_string(),
parameters: vec![0.5, 1.0],
qubits: vec![0, 1],
metadata: HashMap::new(),
};
let matrix = array![
[Complex64::new(1.0, 0.0), Complex64::new(0.0, 0.0)],
[Complex64::new(0.0, 0.0), Complex64::new(1.0, 0.0)]
];
assert!(cache.get(&key).is_none());
cache.put(key.clone(), CachedData::Matrix(matrix.clone()));
match cache.get(&key) {
Some(CachedData::Matrix(cached_matrix)) => {
assert_eq!(cached_matrix, matrix);
}
_ => panic!("Expected cached matrix"),
}
let stats = cache.get_stats();
assert_eq!(stats.total_requests, 2);
assert_eq!(stats.hits, 1);
assert_eq!(stats.misses, 1);
}
#[test]
fn test_gate_matrix_cache() {
let cache = GateMatrixCache::new();
let pauli_x = array![
[Complex64::new(0.0, 0.0), Complex64::new(1.0, 0.0)],
[Complex64::new(1.0, 0.0), Complex64::new(0.0, 0.0)]
];
assert!(cache.get_single_qubit_gate("X", &[]).is_none());
cache.put_single_qubit_gate("X", &[], pauli_x.clone());
let cached_matrix = cache
.get_single_qubit_gate("X", &[])
.expect("cached matrix should exist after put");
assert_eq!(cached_matrix, pauli_x);
}
#[test]
fn test_eviction_policies() {
let mut config = CacheConfig::default();
config.max_entries = 2;
let cache = QuantumOperationCache::new(config);
for i in 0..3 {
let key = OperationKey {
operation_type: format!("test_{i}"),
parameters: vec![i as f64],
qubits: vec![i],
metadata: HashMap::new(),
};
cache.put(
key,
CachedData::ExpectationValue(Complex64::new(i as f64, 0.0)),
);
}
let stats = cache.get_stats();
assert_eq!(stats.current_entries, 2);
assert!(stats.evictions > 0);
}
}