1use crate::error::{QuantRS2Error, QuantRS2Result};
7use crate::gate::functions::multi::{CNOT, CZ};
8use crate::gate::functions::single::{
9 Hadamard, PauliX, PauliY, PauliZ, Phase, RotationX, RotationY, RotationZ,
10};
11use crate::gate::GateOp;
12use crate::qubit::QubitId;
13use scirs2_core::cache::{CacheConfig, TTLSizedCache};
14use scirs2_core::memory::{global_buffer_pool, BufferPool};
15use scirs2_core::profiling::{Profiler, Timer};
16use scirs2_core::Complex64;
17use std::collections::HashMap;
18use std::hash::{Hash, Hasher};
19use std::sync::{Arc, Mutex, OnceLock};
20
21#[derive(Debug, Clone, PartialEq, Eq, Hash)]
23pub struct GateKey {
24 pub gate_type: String,
25 pub parameters: Vec<u64>, pub num_qubits: usize,
27}
28
29impl GateKey {
30 pub fn new(gate_type: &str, parameters: &[f64], num_qubits: usize) -> Self {
31 let param_hashes: Vec<u64> = parameters
33 .iter()
34 .map(|&p| {
35 let mut hasher = std::collections::hash_map::DefaultHasher::new();
36 p.to_bits().hash(&mut hasher);
38 hasher.finish()
39 })
40 .collect();
41
42 Self {
43 gate_type: gate_type.to_string(),
44 parameters: param_hashes,
45 num_qubits,
46 }
47 }
48}
49
50#[derive(Debug, Clone)]
52pub struct CachedGateMatrix {
53 pub matrix: Vec<Complex64>,
54 pub size: usize,
55 pub computation_time_us: u64,
56}
57
58pub struct QuantumGateCache {
60 matrix_cache: Arc<Mutex<TTLSizedCache<GateKey, CachedGateMatrix>>>,
62 buffer_pool: Arc<BufferPool<Complex64>>,
64 cache_hits: Arc<Mutex<u64>>,
66 cache_misses: Arc<Mutex<u64>>,
67 total_computation_time: Arc<Mutex<u64>>,
68}
69
70impl Default for QuantumGateCache {
71 fn default() -> Self {
72 Self::new()
73 }
74}
75
76impl QuantumGateCache {
77 pub fn new() -> Self {
79 let cache_config = CacheConfig {
80 default_size: 2048, default_ttl: 7200, enable_caching: true,
83 };
84
85 Self {
86 matrix_cache: Arc::new(Mutex::new(TTLSizedCache::new(
87 cache_config.default_size,
88 cache_config.default_ttl,
89 ))),
90 buffer_pool: Arc::new(BufferPool::new()),
91 cache_hits: Arc::new(Mutex::new(0)),
92 cache_misses: Arc::new(Mutex::new(0)),
93 total_computation_time: Arc::new(Mutex::new(0)),
94 }
95 }
96
97 pub fn get_or_compute_matrix<F>(
99 &self,
100 key: GateKey,
101 compute_fn: F,
102 ) -> QuantRS2Result<Vec<Complex64>>
103 where
104 F: FnOnce() -> QuantRS2Result<Vec<Complex64>>,
105 {
106 if let Ok(mut cache) = self.matrix_cache.lock() {
108 if let Some(cached) = cache.get(&key) {
109 if let Ok(mut hits) = self.cache_hits.lock() {
110 *hits += 1;
111 }
112 return Ok(cached.matrix);
113 }
114 }
115
116 if let Ok(mut misses) = self.cache_misses.lock() {
118 *misses += 1;
119 }
120
121 let computation_result = Timer::time_function(
122 &format!("gate_matrix_computation_{}", key.gate_type),
123 compute_fn,
124 );
125
126 match computation_result {
127 Ok(matrix) => {
128 let cached_matrix = CachedGateMatrix {
130 matrix: matrix.clone(),
131 size: matrix.len(),
132 computation_time_us: 0, };
134
135 if let Ok(mut cache) = self.matrix_cache.lock() {
137 cache.insert(key, cached_matrix);
138 }
139
140 Ok(matrix)
141 }
142 Err(e) => Err(e),
143 }
144 }
145
146 pub fn get_performance_stats(&self) -> QuantumGateCacheStats {
148 let hits = self.cache_hits.lock().map(|g| *g).unwrap_or(0);
149 let misses = self.cache_misses.lock().map(|g| *g).unwrap_or(0);
150 let total_time = self.total_computation_time.lock().map(|g| *g).unwrap_or(0);
151
152 QuantumGateCacheStats {
153 cache_hits: hits,
154 cache_misses: misses,
155 hit_ratio: if hits + misses > 0 {
156 hits as f64 / (hits + misses) as f64
157 } else {
158 0.0
159 },
160 total_computation_time_us: total_time,
161 average_computation_time_us: total_time.checked_div(misses).unwrap_or(0),
162 }
163 }
164
165 pub fn prewarm_common_gates(&self) -> QuantRS2Result<()> {
167 use std::f64::consts::PI;
168
169 let common_gates = vec![
170 ("pauli_x", vec![], 1),
171 ("pauli_y", vec![], 1),
172 ("pauli_z", vec![], 1),
173 ("hadamard", vec![], 1),
174 ("phase", vec![PI / 2.0], 1),
175 ("rx", vec![PI / 4.0, PI / 2.0, PI], 1),
176 ("ry", vec![PI / 4.0, PI / 2.0, PI], 1),
177 ("rz", vec![PI / 4.0, PI / 2.0, PI], 1),
178 ("cnot", vec![], 2),
179 ("cz", vec![], 2),
180 ];
181
182 for (gate_name, params, qubits) in common_gates {
183 for param_set in if params.is_empty() {
184 vec![vec![]]
185 } else {
186 params.into_iter().map(|p| vec![p]).collect()
187 } {
188 let key = GateKey::new(gate_name, ¶m_set, qubits);
189
190 let _ =
194 self.get_or_compute_matrix(key, || compute_gate_matrix(gate_name, ¶m_set))?;
195 }
196 }
197
198 Ok(())
199 }
200
201 pub fn clear_cache(&self) {
203 if let Ok(mut cache) = self.matrix_cache.lock() {
204 cache.clear();
205 }
206 if let Ok(mut hits) = self.cache_hits.lock() {
207 *hits = 0;
208 }
209 if let Ok(mut misses) = self.cache_misses.lock() {
210 *misses = 0;
211 }
212 if let Ok(mut time) = self.total_computation_time.lock() {
213 *time = 0;
214 }
215 }
216}
217
218fn compute_gate_matrix(gate_name: &str, params: &[f64]) -> QuantRS2Result<Vec<Complex64>> {
226 let q0 = QubitId(0);
227 let q1 = QubitId(1);
228
229 let angle = |gate: &str| -> QuantRS2Result<f64> {
230 params.first().copied().ok_or_else(|| {
231 QuantRS2Error::InvalidInput(format!("gate '{gate}' requires a rotation parameter"))
232 })
233 };
234
235 match gate_name {
236 "pauli_x" => PauliX { target: q0 }.matrix(),
237 "pauli_y" => PauliY { target: q0 }.matrix(),
238 "pauli_z" => PauliZ { target: q0 }.matrix(),
239 "hadamard" => Hadamard { target: q0 }.matrix(),
240 "phase" => Phase { target: q0 }.matrix(),
241 "rx" => RotationX {
242 target: q0,
243 theta: angle("rx")?,
244 }
245 .matrix(),
246 "ry" => RotationY {
247 target: q0,
248 theta: angle("ry")?,
249 }
250 .matrix(),
251 "rz" => RotationZ {
252 target: q0,
253 theta: angle("rz")?,
254 }
255 .matrix(),
256 "cnot" => CNOT {
257 control: q0,
258 target: q1,
259 }
260 .matrix(),
261 "cz" => CZ {
262 control: q0,
263 target: q1,
264 }
265 .matrix(),
266 other => Err(QuantRS2Error::UnsupportedOperation(format!(
267 "unknown gate name '{other}' for matrix prewarming"
268 ))),
269 }
270}
271
272#[derive(Debug, Clone)]
274pub struct QuantumGateCacheStats {
275 pub cache_hits: u64,
276 pub cache_misses: u64,
277 pub hit_ratio: f64,
278 pub total_computation_time_us: u64,
279 pub average_computation_time_us: u64,
280}
281
282static GLOBAL_GATE_CACHE: OnceLock<QuantumGateCache> = OnceLock::new();
284
285pub fn global_gate_cache() -> &'static QuantumGateCache {
287 GLOBAL_GATE_CACHE.get_or_init(QuantumGateCache::new)
288}
289
290#[macro_export]
292macro_rules! cached_gate_matrix {
293 ($gate_type:expr, $params:expr, $qubits:expr, $compute:expr) => {{
294 let key = $crate::optimizations::gate_cache::GateKey::new($gate_type, $params, $qubits);
295 $crate::optimizations::gate_cache::global_gate_cache()
296 .get_or_compute_matrix(key, || $compute)
297 }};
298}
299
300#[cfg(test)]
301mod tests {
302 use super::*;
303
304 #[test]
305 fn test_gate_cache_basic_functionality() {
306 let cache = QuantumGateCache::new();
307
308 let key = GateKey::new("test_gate", &[1.0], 1);
309
310 let matrix1 = cache
312 .get_or_compute_matrix(key.clone(), || Ok(vec![Complex64::new(1.0, 0.0); 4]))
313 .expect("matrix computation should succeed");
314
315 let matrix2 = cache
317 .get_or_compute_matrix(key, || {
318 panic!("Should not be called due to cache hit");
319 })
320 .expect("cache hit should succeed");
321
322 assert_eq!(matrix1, matrix2);
323
324 let stats = cache.get_performance_stats();
325 assert_eq!(stats.cache_hits, 1);
326 assert_eq!(stats.cache_misses, 1);
327 assert_eq!(stats.hit_ratio, 0.5);
328 }
329
330 #[test]
331 fn test_gate_key_hashing() {
332 let key1 = GateKey::new("rx", &[std::f64::consts::PI], 1);
333 let key2 = GateKey::new("rx", &[std::f64::consts::PI], 1);
334 let key3 = GateKey::new("rx", &[std::f64::consts::PI / 2.0], 1);
335
336 assert_eq!(key1, key2);
337 assert_ne!(key1, key3);
338
339 let mut set = std::collections::HashSet::new();
340 set.insert(key1);
341 assert!(set.contains(&key2));
342 assert!(!set.contains(&key3));
343 }
344
345 #[test]
346 fn test_cache_prewarming() {
347 let cache = QuantumGateCache::new();
348
349 let initial_stats = cache.get_performance_stats();
351 assert_eq!(initial_stats.cache_misses, 0);
352
353 cache
355 .prewarm_common_gates()
356 .expect("prewarming common gates should succeed");
357
358 let stats = cache.get_performance_stats();
359 assert!(stats.cache_misses > 0); let key = GateKey::new("hadamard", &[], 1);
363 let _matrix = cache
364 .get_or_compute_matrix(key, || {
365 panic!("Should be a cache hit");
366 })
367 .expect("cache hit for hadamard gate should succeed");
368
369 let final_stats = cache.get_performance_stats();
370 assert!(final_stats.cache_hits > 0);
371 }
372
373 #[test]
374 fn test_prewarmed_hadamard_is_real_not_identity() {
375 use std::f64::consts::FRAC_1_SQRT_2;
376
377 let cache = QuantumGateCache::new();
378 cache
379 .prewarm_common_gates()
380 .expect("prewarming common gates should succeed");
381
382 let key = GateKey::new("hadamard", &[], 1);
385 let matrix = cache
386 .get_or_compute_matrix(key, || panic!("Hadamard should already be cached"))
387 .expect("cache hit for hadamard gate should succeed");
388
389 assert_eq!(matrix.len(), 4);
392 let tol = 1e-12;
393 assert!((matrix[0].re - FRAC_1_SQRT_2).abs() < tol);
394 assert!((matrix[1].re - FRAC_1_SQRT_2).abs() < tol);
395 assert!((matrix[2].re - FRAC_1_SQRT_2).abs() < tol);
396 assert!((matrix[3].re + FRAC_1_SQRT_2).abs() < tol);
397 assert!(matrix[1].norm() > 0.5, "off-diagonal must be non-zero");
399 assert!(matrix[2].norm() > 0.5, "off-diagonal must be non-zero");
400
401 let rx_key = GateKey::new("rx", &[std::f64::consts::PI], 1);
403 let rx = cache
404 .get_or_compute_matrix(rx_key, || panic!("RX(pi) should already be cached"))
405 .expect("cache hit for RX gate should succeed");
406 assert!(rx[0].norm() < tol);
408 assert!((rx[1].im + 1.0).abs() < tol);
409 }
410}