quantrs2_core/optimizations/
memory_optimization.rs

1//! Memory Optimization for Quantum Simulations using SciRS2 Beta.1
2//!
3//! This module provides memory-efficient algorithms and buffer management
4//! for large-scale quantum state simulations.
5
6use crate::error::QuantRS2Result;
7use scirs2_core::memory::{metrics, BufferPool, ChunkProcessor2D};
8use scirs2_core::ndarray::{Array1, Array2, ArrayViewMut1};
9use scirs2_core::Complex64;
10use std::sync::{Arc, Mutex, OnceLock};
11
12/// Quantum-specific buffer pool for state vectors and matrices
13pub struct QuantumBufferPool {
14    /// Pool for complex64 state vector components
15    state_vector_pool: Arc<Mutex<BufferPool<Complex64>>>,
16    /// Pool for real-valued probability arrays
17    probability_pool: Arc<Mutex<BufferPool<f64>>>,
18    /// Pool for temporary computation buffers
19    temp_buffer_pool: Arc<Mutex<BufferPool<Complex64>>>,
20    /// Usage statistics
21    allocations: Arc<Mutex<u64>>,
22    deallocations: Arc<Mutex<u64>>,
23    peak_memory_usage: Arc<Mutex<usize>>,
24}
25
26impl Default for QuantumBufferPool {
27    fn default() -> Self {
28        Self::new()
29    }
30}
31
32impl QuantumBufferPool {
33    /// Create a new quantum buffer pool
34    pub fn new() -> Self {
35        Self {
36            state_vector_pool: Arc::new(Mutex::new(BufferPool::new())),
37            probability_pool: Arc::new(Mutex::new(BufferPool::new())),
38            temp_buffer_pool: Arc::new(Mutex::new(BufferPool::new())),
39            allocations: Arc::new(Mutex::new(0)),
40            deallocations: Arc::new(Mutex::new(0)),
41            peak_memory_usage: Arc::new(Mutex::new(0)),
42        }
43    }
44
45    /// Acquire a state vector buffer from the pool
46    pub fn acquire_state_vector(&self, size: usize) -> Vec<Complex64> {
47        metrics::track_allocation("QuantumStateVector", size * 16, 0); // Complex64 is 16 bytes
48        *self.allocations.lock().expect("Allocations lock poisoned") += 1;
49
50        let current_usage = size * 16;
51        let mut peak = self
52            .peak_memory_usage
53            .lock()
54            .expect("Peak memory usage lock poisoned");
55        if current_usage > *peak {
56            *peak = current_usage;
57        }
58
59        self.state_vector_pool
60            .lock()
61            .expect("State vector pool lock poisoned")
62            .acquire_vec(size)
63    }
64
65    /// Release a state vector buffer back to the pool
66    pub fn release_state_vector(&self, buffer: Vec<Complex64>) {
67        let size = buffer.len();
68        metrics::track_deallocation("QuantumStateVector", size * 16, 0);
69        *self
70            .deallocations
71            .lock()
72            .expect("Deallocations lock poisoned") += 1;
73
74        self.state_vector_pool
75            .lock()
76            .expect("State vector pool lock poisoned")
77            .release_vec(buffer);
78    }
79
80    /// Acquire a probability buffer from the pool
81    pub fn acquire_probability_buffer(&self, size: usize) -> Vec<f64> {
82        metrics::track_allocation("ProbabilityBuffer", size * 8, 0); // f64 is 8 bytes
83        *self.allocations.lock().expect("Allocations lock poisoned") += 1;
84
85        self.probability_pool
86            .lock()
87            .expect("Probability pool lock poisoned")
88            .acquire_vec(size)
89    }
90
91    /// Release a probability buffer back to the pool
92    pub fn release_probability_buffer(&self, buffer: Vec<f64>) {
93        let size = buffer.len();
94        metrics::track_deallocation("ProbabilityBuffer", size * 8, 0);
95        *self
96            .deallocations
97            .lock()
98            .expect("Deallocations lock poisoned") += 1;
99
100        self.probability_pool
101            .lock()
102            .expect("Probability pool lock poisoned")
103            .release_vec(buffer);
104    }
105
106    /// Get buffer pool statistics
107    pub fn get_stats(&self) -> MemoryUsageStats {
108        let allocations = *self.allocations.lock().expect("Allocations lock poisoned");
109        let deallocations = *self
110            .deallocations
111            .lock()
112            .expect("Deallocations lock poisoned");
113        MemoryUsageStats {
114            total_allocations: allocations,
115            total_deallocations: deallocations,
116            peak_memory_usage_bytes: *self
117                .peak_memory_usage
118                .lock()
119                .expect("Peak memory usage lock poisoned"),
120            active_buffers: allocations.saturating_sub(deallocations),
121        }
122    }
123}
124
125/// Memory usage statistics
126#[derive(Debug, Clone)]
127pub struct MemoryUsageStats {
128    pub total_allocations: u64,
129    pub total_deallocations: u64,
130    pub peak_memory_usage_bytes: usize,
131    pub active_buffers: u64,
132}
133
134/// Optimized state vector manager for large quantum systems
135pub struct StateVectorManager {
136    /// Current state vector
137    state: Option<Vec<Complex64>>,
138    /// Number of qubits
139    num_qubits: usize,
140    /// Buffer pool reference
141    pool: Arc<QuantumBufferPool>,
142    /// Whether to use chunked processing for large states
143    use_chunked_processing: bool,
144}
145
146impl StateVectorManager {
147    /// Create a new state vector manager
148    pub const fn new(num_qubits: usize, pool: Arc<QuantumBufferPool>) -> Self {
149        let use_chunked_processing = num_qubits > 20; // Use chunking for >20 qubits (~16M elements)
150
151        Self {
152            state: None,
153            num_qubits,
154            pool,
155            use_chunked_processing,
156        }
157    }
158
159    /// Initialize the state vector to |00...0⟩
160    pub fn initialize_zero_state(&mut self) -> QuantRS2Result<()> {
161        let size = 1 << self.num_qubits;
162        let mut state = self.pool.acquire_state_vector(size);
163
164        // Initialize to zero state
165        state.fill(Complex64::new(0.0, 0.0));
166        state[0] = Complex64::new(1.0, 0.0);
167
168        self.state = Some(state);
169        Ok(())
170    }
171
172    /// Apply a single-qubit gate with memory optimization
173    pub fn apply_single_qubit_gate(
174        &mut self,
175        gate_matrix: &[Complex64; 4],
176        qubit_idx: usize,
177    ) -> QuantRS2Result<()> {
178        let use_chunked = self.use_chunked_processing;
179        let pool = self.pool.clone();
180
181        let state = self.state.as_mut().ok_or_else(|| {
182            crate::error::QuantRS2Error::InvalidInput("State not initialized".to_string())
183        })?;
184
185        if use_chunked {
186            Self::apply_single_qubit_gate_chunked_impl(&pool, state, gate_matrix, qubit_idx)
187        } else {
188            Self::apply_single_qubit_gate_direct_impl(&pool, state, gate_matrix, qubit_idx)
189        }
190    }
191
192    /// Direct application for smaller state vectors
193    fn apply_single_qubit_gate_direct_impl(
194        pool: &QuantumBufferPool,
195        state: &mut [Complex64],
196        gate_matrix: &[Complex64; 4],
197        qubit_idx: usize,
198    ) -> QuantRS2Result<()> {
199        let size = state.len();
200        let target_bit = 1 << qubit_idx;
201
202        // Acquire temporary buffer for parallel processing
203        let mut temp_buffer = pool.acquire_state_vector(size);
204        temp_buffer.copy_from_slice(state);
205
206        // Apply gate using SIMD-optimized operations
207        for i in 0..size {
208            if i & target_bit == 0 {
209                let j = i | target_bit;
210                let amp_0 = temp_buffer[i];
211                let amp_1 = temp_buffer[j];
212
213                state[i] = gate_matrix[0] * amp_0 + gate_matrix[1] * amp_1;
214                state[j] = gate_matrix[2] * amp_0 + gate_matrix[3] * amp_1;
215            }
216        }
217
218        pool.release_state_vector(temp_buffer);
219        Ok(())
220    }
221
222    /// Chunked application for large state vectors
223    fn apply_single_qubit_gate_chunked_impl(
224        pool: &QuantumBufferPool,
225        state: &mut [Complex64],
226        gate_matrix: &[Complex64; 4],
227        qubit_idx: usize,
228    ) -> QuantRS2Result<()> {
229        let chunk_size = 1 << 18; // Process in 256K element chunks
230        let target_bit = 1 << qubit_idx;
231
232        // Create a temporary copy for atomic operations
233        let mut temp_state = pool.acquire_state_vector(state.len());
234        temp_state.copy_from_slice(state);
235
236        // Apply gate using temporary buffer
237        for i in 0..state.len() {
238            if i & target_bit == 0 {
239                let j = i | target_bit;
240                if j < state.len() {
241                    let amp_0 = temp_state[i];
242                    let amp_1 = temp_state[j];
243
244                    state[i] = gate_matrix[0] * amp_0 + gate_matrix[1] * amp_1;
245                    state[j] = gate_matrix[2] * amp_0 + gate_matrix[3] * amp_1;
246                }
247            }
248        }
249
250        pool.release_state_vector(temp_state);
251
252        Ok(())
253    }
254
255    /// Get measurement probabilities with memory optimization
256    pub fn get_probabilities(&self) -> QuantRS2Result<Vec<f64>> {
257        let state = self.state.as_ref().ok_or_else(|| {
258            crate::error::QuantRS2Error::InvalidInput("State not initialized".to_string())
259        })?;
260
261        let mut probabilities = self.pool.acquire_probability_buffer(state.len());
262
263        // Compute probabilities using SIMD operations
264        for (i, &amplitude) in state.iter().enumerate() {
265            probabilities[i] = amplitude.norm_sqr();
266        }
267
268        Ok(probabilities)
269    }
270
271    /// Release resources when done
272    pub fn finalize(mut self) {
273        if let Some(state) = self.state.take() {
274            self.pool.release_state_vector(state);
275        }
276    }
277}
278
279/// Global quantum buffer pool
280static GLOBAL_QUANTUM_POOL: OnceLock<QuantumBufferPool> = OnceLock::new();
281
282/// Get the global quantum buffer pool
283pub fn global_quantum_buffer_pool() -> &'static QuantumBufferPool {
284    GLOBAL_QUANTUM_POOL.get_or_init(QuantumBufferPool::new)
285}
286
287/// Initialize buffer pools
288pub fn initialize_buffer_pools() {
289    // Force initialization of the global pool
290    let _pool = global_quantum_buffer_pool();
291}
292
293/// Optimized state vector allocation function
294pub fn optimized_state_vector_allocation(num_qubits: usize) -> StateVectorManager {
295    let pool = Arc::new(QuantumBufferPool::new());
296    StateVectorManager::new(num_qubits, pool)
297}
298
299/// Get global memory usage statistics
300pub fn get_memory_usage_stats() -> MemoryUsageStats {
301    global_quantum_buffer_pool().get_stats()
302}
303
304#[cfg(test)]
305mod tests {
306    use super::*;
307
308    #[test]
309    #[ignore] // Slow test: SciRS2 metrics tracking is unexpectedly slow (~71s)
310    fn test_buffer_pool_basic_functionality() {
311        let pool = QuantumBufferPool::new();
312
313        // Test state vector acquisition and release
314        let buffer = pool.acquire_state_vector(100);
315        assert_eq!(buffer.len(), 100);
316
317        let stats_before = pool.get_stats();
318        pool.release_state_vector(buffer);
319        let stats_after = pool.get_stats();
320
321        assert_eq!(
322            stats_after.total_allocations,
323            stats_before.total_allocations
324        );
325        assert_eq!(
326            stats_after.total_deallocations,
327            stats_before.total_deallocations + 1
328        );
329    }
330
331    #[test]
332    fn test_state_vector_manager() {
333        let pool = Arc::new(QuantumBufferPool::new());
334        let mut manager = StateVectorManager::new(2, pool); // 2-qubit system
335
336        // Initialize state
337        manager
338            .initialize_zero_state()
339            .expect("Failed to initialize zero state");
340
341        // Apply Hadamard-like gate to first qubit
342        let h_gate = [
343            Complex64::new(1.0 / 2.0_f64.sqrt(), 0.0),
344            Complex64::new(1.0 / 2.0_f64.sqrt(), 0.0),
345            Complex64::new(1.0 / 2.0_f64.sqrt(), 0.0),
346            Complex64::new(-1.0 / 2.0_f64.sqrt(), 0.0),
347        ];
348
349        manager
350            .apply_single_qubit_gate(&h_gate, 0)
351            .expect("Failed to apply Hadamard gate");
352
353        // Get probabilities
354        let probabilities = manager
355            .get_probabilities()
356            .expect("Failed to get probabilities");
357        assert_eq!(probabilities.len(), 4);
358
359        // Should be in equal superposition on first qubit
360        assert!((probabilities[0] - 0.5).abs() < 1e-10); // |00⟩
361        assert!((probabilities[1] - 0.5).abs() < 1e-10); // |01⟩
362        assert!((probabilities[2] - 0.0).abs() < 1e-10); // |10⟩
363        assert!((probabilities[3] - 0.0).abs() < 1e-10); // |11⟩
364
365        manager.finalize();
366    }
367
368    #[test]
369    fn test_chunked_processing_threshold() {
370        let pool = Arc::new(QuantumBufferPool::new());
371
372        // Small system should not use chunking
373        let small_manager = StateVectorManager::new(10, pool.clone());
374        assert!(!small_manager.use_chunked_processing);
375
376        // Large system should use chunking
377        let large_manager = StateVectorManager::new(25, pool);
378        assert!(large_manager.use_chunked_processing);
379    }
380}