Skip to main content

quantrs2_core/optimizations/
gate_cache.rs

1//! Quantum Gate Matrix Caching using SciRS2 Beta.3 Features
2//!
3//! This module demonstrates how to leverage scirs2-core beta.3 caching capabilities
4//! to optimize quantum gate matrix computations.
5
6use 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/// Hash key for gate matrix caching
22#[derive(Debug, Clone, PartialEq, Eq, Hash)]
23pub struct GateKey {
24    pub gate_type: String,
25    pub parameters: Vec<u64>, // Hash of parameters for caching
26    pub num_qubits: usize,
27}
28
29impl GateKey {
30    pub fn new(gate_type: &str, parameters: &[f64], num_qubits: usize) -> Self {
31        // Convert parameters to hashable representation
32        let param_hashes: Vec<u64> = parameters
33            .iter()
34            .map(|&p| {
35                let mut hasher = std::collections::hash_map::DefaultHasher::new();
36                // Use bits representation for consistent hashing
37                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/// Cached quantum gate matrix
51#[derive(Debug, Clone)]
52pub struct CachedGateMatrix {
53    pub matrix: Vec<Complex64>,
54    pub size: usize,
55    pub computation_time_us: u64,
56}
57
58/// High-performance quantum gate cache using SciRS2 beta.3 features
59pub struct QuantumGateCache {
60    /// Gate matrix cache with TTL
61    matrix_cache: Arc<Mutex<TTLSizedCache<GateKey, CachedGateMatrix>>>,
62    /// Buffer pool for matrix computations
63    buffer_pool: Arc<BufferPool<Complex64>>,
64    /// Performance metrics
65    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    /// Create a new optimized quantum gate cache
78    pub fn new() -> Self {
79        let cache_config = CacheConfig {
80            default_size: 2048, // Cache up to 2048 gate matrices
81            default_ttl: 7200,  // 2 hours TTL for gate matrices
82            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    /// Get or compute a gate matrix with caching and profiling
98    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        // Check cache first
107        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        // Cache miss - compute with profiling
117        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                // Create cached entry with timing info
129                let cached_matrix = CachedGateMatrix {
130                    matrix: matrix.clone(),
131                    size: matrix.len(),
132                    computation_time_us: 0, // Would be filled by Timer in real implementation
133                };
134
135                // Store in cache
136                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    /// Get cache performance statistics
147    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    /// Pre-warm cache with common gate matrices
166    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, &param_set, qubits);
189
190                // Compute and cache the real gate matrix by constructing the
191                // corresponding gate from `crate::gate::functions` and calling
192                // its `matrix()` implementation.
193                let _ =
194                    self.get_or_compute_matrix(key, || compute_gate_matrix(gate_name, &param_set))?;
195            }
196        }
197
198        Ok(())
199    }
200
201    /// Clear cache and reset statistics
202    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
218/// Compute the real matrix for a named gate using `crate::gate::functions`.
219///
220/// `gate_name` follows the convention used by [`QuantumGateCache::prewarm_common_gates`]
221/// (e.g. `"pauli_x"`, `"hadamard"`, `"rx"`). Parameterized gates read their angle from
222/// `params[0]`. Qubit identities (`QubitId(0)` / `QubitId(1)`) do not affect the returned
223/// matrix, so fixed placeholders are used. Unknown gate names produce an honest error
224/// rather than a fabricated identity matrix.
225fn 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/// Cache performance statistics
273#[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
282/// Global quantum gate cache instance
283static GLOBAL_GATE_CACHE: OnceLock<QuantumGateCache> = OnceLock::new();
284
285/// Get the global quantum gate cache
286pub fn global_gate_cache() -> &'static QuantumGateCache {
287    GLOBAL_GATE_CACHE.get_or_init(QuantumGateCache::new)
288}
289
290/// Convenience macro for caching gate matrix computations
291#[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        // First call should be a cache miss
311        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        // Second call should be a cache hit
316        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        // Cache should start empty
350        let initial_stats = cache.get_performance_stats();
351        assert_eq!(initial_stats.cache_misses, 0);
352
353        // Pre-warm should populate cache
354        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); // Should have computed some matrices
360
361        // Now test a common gate - should be a cache hit
362        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        // Fetch the cached Hadamard matrix; this must be a cache hit so the
383        // closure must NOT execute.
384        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        // Real Hadamard: 1/sqrt(2) * [[1, 1], [1, -1]] (NOT the identity that the
390        // old fabricated implementation produced).
391        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        // Guard against the old identity-matrix fabrication: off-diagonals were 0.
398        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        // Also verify a parameterized gate (RX(pi)) was cached with its real matrix.
402        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        // RX(pi) = [[0, -i], [-i, 0]]
407        assert!(rx[0].norm() < tol);
408        assert!((rx[1].im + 1.0).abs() < tol);
409    }
410}