quantrs2_core/gpu/
cpu_backend.rs

1//! CPU backend implementation for GPU abstraction
2//!
3//! This provides a CPU-based fallback implementation of the GPU backend
4//! interface, useful for testing and systems without GPU support.
5
6use super::{GpuBackend, GpuBuffer, GpuKernel};
7use crate::{
8    error::{QuantRS2Error, QuantRS2Result},
9    qubit::QubitId,
10};
11use scirs2_core::ndarray::Array2;
12use scirs2_core::Complex64;
13use std::sync::{Arc, Mutex};
14
15/// CPU-based buffer implementation
16pub struct CpuBuffer {
17    data: Arc<Mutex<Vec<Complex64>>>,
18}
19
20impl CpuBuffer {
21    /// Create a new CPU buffer
22    pub fn new(size: usize) -> Self {
23        Self {
24            data: Arc::new(Mutex::new(vec![Complex64::new(0.0, 0.0); size])),
25        }
26    }
27
28    /// Get a reference to the data
29    pub fn data(&self) -> std::sync::MutexGuard<'_, Vec<Complex64>> {
30        self.data.lock().unwrap()
31    }
32}
33
34impl GpuBuffer for CpuBuffer {
35    fn size(&self) -> usize {
36        self.data.lock().unwrap().len() * std::mem::size_of::<Complex64>()
37    }
38
39    fn upload(&mut self, data: &[Complex64]) -> QuantRS2Result<()> {
40        let mut buffer = self.data.lock().unwrap();
41        if buffer.len() != data.len() {
42            return Err(QuantRS2Error::InvalidInput(format!(
43                "Buffer size mismatch: {} != {}",
44                buffer.len(),
45                data.len()
46            )));
47        }
48        buffer.copy_from_slice(data);
49        Ok(())
50    }
51
52    fn download(&self, data: &mut [Complex64]) -> QuantRS2Result<()> {
53        let buffer = self.data.lock().unwrap();
54        if buffer.len() != data.len() {
55            return Err(QuantRS2Error::InvalidInput(format!(
56                "Buffer size mismatch: {} != {}",
57                buffer.len(),
58                data.len()
59            )));
60        }
61        data.copy_from_slice(&buffer);
62        Ok(())
63    }
64
65    fn sync(&self) -> QuantRS2Result<()> {
66        // No-op for CPU backend
67        Ok(())
68    }
69
70    fn as_any(&self) -> &dyn std::any::Any {
71        self
72    }
73
74    fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
75        self
76    }
77}
78
79/// CPU-based kernel implementation
80pub struct CpuKernel;
81
82impl CpuKernel {
83    /// Apply a gate matrix to specific qubit indices
84    fn apply_gate_to_indices(state: &mut [Complex64], gate: &[Complex64], indices: &[usize]) {
85        let gate_size = indices.len();
86        let mut temp = vec![Complex64::new(0.0, 0.0); gate_size];
87
88        // Read values
89        for (i, &idx) in indices.iter().enumerate() {
90            temp[i] = state[idx];
91        }
92
93        // Apply gate
94        for (i, &idx) in indices.iter().enumerate() {
95            let mut sum = Complex64::new(0.0, 0.0);
96            for j in 0..gate_size {
97                sum += gate[i * gate_size + j] * temp[j];
98            }
99            state[idx] = sum;
100        }
101    }
102}
103
104impl GpuKernel for CpuKernel {
105    fn apply_single_qubit_gate(
106        &self,
107        state: &mut dyn GpuBuffer,
108        gate_matrix: &[Complex64; 4],
109        qubit: QubitId,
110        n_qubits: usize,
111    ) -> QuantRS2Result<()> {
112        let cpu_buffer = state
113            .as_any_mut()
114            .downcast_mut::<CpuBuffer>()
115            .ok_or_else(|| QuantRS2Error::InvalidInput("Expected CpuBuffer".to_string()))?;
116
117        let mut data = cpu_buffer.data();
118        let qubit_idx = qubit.0 as usize;
119        let stride = 1 << qubit_idx;
120        let pairs = 1 << (n_qubits - 1);
121
122        // Apply gate using bit manipulation
123        for i in 0..pairs {
124            let i0 = ((i >> qubit_idx) << (qubit_idx + 1)) | (i & ((1 << qubit_idx) - 1));
125            let i1 = i0 | stride;
126
127            let a = data[i0];
128            let b = data[i1];
129
130            data[i0] = gate_matrix[0] * a + gate_matrix[1] * b;
131            data[i1] = gate_matrix[2] * a + gate_matrix[3] * b;
132        }
133
134        Ok(())
135    }
136
137    fn apply_two_qubit_gate(
138        &self,
139        state: &mut dyn GpuBuffer,
140        gate_matrix: &[Complex64; 16],
141        control: QubitId,
142        target: QubitId,
143        n_qubits: usize,
144    ) -> QuantRS2Result<()> {
145        let cpu_buffer = state
146            .as_any_mut()
147            .downcast_mut::<CpuBuffer>()
148            .ok_or_else(|| QuantRS2Error::InvalidInput("Expected CpuBuffer".to_string()))?;
149
150        let mut data = cpu_buffer.data();
151        let control_idx = control.0 as usize;
152        let target_idx = target.0 as usize;
153
154        // Determine bit positions
155        let (high_idx, low_idx) = if control_idx > target_idx {
156            (control_idx, target_idx)
157        } else {
158            (target_idx, control_idx)
159        };
160
161        let high_stride = 1 << high_idx;
162        let low_stride = 1 << low_idx;
163
164        let state_size = 1 << n_qubits;
165        let block_size = 1 << (high_idx + 1);
166        let num_blocks = state_size / block_size;
167
168        // Apply gate to each block
169        for block in 0..num_blocks {
170            let block_start = block * block_size;
171
172            for i in 0..(block_size / 4) {
173                // Calculate indices for the 4 basis states
174                let base = block_start
175                    + (i & ((1 << low_idx) - 1))
176                    + ((i >> low_idx) << (low_idx + 1))
177                    + ((i >> (high_idx - 1)) << (high_idx + 1));
178
179                let indices = [
180                    base,
181                    base + low_stride,
182                    base + high_stride,
183                    base + low_stride + high_stride,
184                ];
185
186                Self::apply_gate_to_indices(&mut data, gate_matrix, &indices);
187            }
188        }
189
190        Ok(())
191    }
192
193    fn apply_multi_qubit_gate(
194        &self,
195        state: &mut dyn GpuBuffer,
196        gate_matrix: &Array2<Complex64>,
197        qubits: &[QubitId],
198        n_qubits: usize,
199    ) -> QuantRS2Result<()> {
200        let cpu_buffer = state
201            .as_any_mut()
202            .downcast_mut::<CpuBuffer>()
203            .ok_or_else(|| QuantRS2Error::InvalidInput("Expected CpuBuffer".to_string()))?;
204
205        let mut data = cpu_buffer.data();
206        let gate_qubits = qubits.len();
207        let gate_dim = 1 << gate_qubits;
208
209        if gate_matrix.dim() != (gate_dim, gate_dim) {
210            return Err(QuantRS2Error::InvalidInput(format!(
211                "Gate matrix dimension mismatch: {:?} != ({}, {})",
212                gate_matrix.dim(),
213                gate_dim,
214                gate_dim
215            )));
216        }
217
218        // Convert gate matrix to flat array for easier indexing
219        let gate_flat: Vec<Complex64> = gate_matrix.iter().cloned().collect();
220
221        // Calculate indices for all affected basis states
222        let _total_states = 1 << n_qubits;
223        let affected_states = 1 << gate_qubits;
224        let unaffected_qubits = n_qubits - gate_qubits;
225        let iterations = 1 << unaffected_qubits;
226
227        // Sort qubit indices for consistent ordering
228        let mut qubit_indices: Vec<usize> = qubits.iter().map(|q| q.0 as usize).collect();
229        qubit_indices.sort_unstable();
230
231        // Apply gate to each group of affected states
232        for i in 0..iterations {
233            let mut indices = vec![0; affected_states];
234
235            // Calculate base index
236            let mut base = 0;
237            let mut remaining = i;
238            let mut qubit_pos = 0;
239
240            for bit in 0..n_qubits {
241                if qubit_pos < gate_qubits && bit == qubit_indices[qubit_pos] {
242                    qubit_pos += 1;
243                } else {
244                    if remaining & 1 == 1 {
245                        base |= 1 << bit;
246                    }
247                    remaining >>= 1;
248                }
249            }
250
251            // Generate all indices for this gate application
252            for j in 0..affected_states {
253                indices[j] = base;
254                for (k, &qubit_idx) in qubit_indices.iter().enumerate() {
255                    if (j >> k) & 1 == 1 {
256                        indices[j] |= 1 << qubit_idx;
257                    }
258                }
259            }
260
261            Self::apply_gate_to_indices(&mut data, &gate_flat, &indices);
262        }
263
264        Ok(())
265    }
266
267    fn measure_qubit(
268        &self,
269        state: &dyn GpuBuffer,
270        qubit: QubitId,
271        n_qubits: usize,
272    ) -> QuantRS2Result<(bool, f64)> {
273        let cpu_buffer = state
274            .as_any()
275            .downcast_ref::<CpuBuffer>()
276            .ok_or_else(|| QuantRS2Error::InvalidInput("Expected CpuBuffer".to_string()))?;
277
278        let data = cpu_buffer.data();
279        let qubit_idx = qubit.0 as usize;
280        let _stride = 1 << qubit_idx;
281
282        // Calculate probability of measuring |1⟩
283        let mut prob_one = 0.0;
284        for i in 0..(1 << n_qubits) {
285            if (i >> qubit_idx) & 1 == 1 {
286                prob_one += data[i].norm_sqr();
287            }
288        }
289
290        // Simulate measurement
291        use scirs2_core::random::prelude::*;
292        let outcome = thread_rng().gen::<f64>() < prob_one;
293
294        Ok((outcome, if outcome { prob_one } else { 1.0 - prob_one }))
295    }
296
297    fn expectation_value(
298        &self,
299        state: &dyn GpuBuffer,
300        observable: &Array2<Complex64>,
301        qubits: &[QubitId],
302        n_qubits: usize,
303    ) -> QuantRS2Result<f64> {
304        let cpu_buffer = state
305            .as_any()
306            .downcast_ref::<CpuBuffer>()
307            .ok_or_else(|| QuantRS2Error::InvalidInput("Expected CpuBuffer".to_string()))?;
308
309        let data = cpu_buffer.data();
310
311        // For now, implement expectation value for single-qubit observables
312        if qubits.len() != 1 || observable.dim() != (2, 2) {
313            return Err(QuantRS2Error::UnsupportedOperation(
314                "Only single-qubit observables supported currently".to_string(),
315            ));
316        }
317
318        let qubit_idx = qubits[0].0 as usize;
319        let stride = 1 << qubit_idx;
320        let pairs = 1 << (n_qubits - 1);
321
322        let mut expectation = Complex64::new(0.0, 0.0);
323
324        for i in 0..pairs {
325            let i0 = ((i >> qubit_idx) << (qubit_idx + 1)) | (i & ((1 << qubit_idx) - 1));
326            let i1 = i0 | stride;
327
328            let a = data[i0];
329            let b = data[i1];
330
331            expectation += a.conj() * (observable[(0, 0)] * a + observable[(0, 1)] * b);
332            expectation += b.conj() * (observable[(1, 0)] * a + observable[(1, 1)] * b);
333        }
334
335        if expectation.im.abs() > 1e-10 {
336            return Err(QuantRS2Error::InvalidInput(
337                "Observable expectation value is not real".to_string(),
338            ));
339        }
340
341        Ok(expectation.re)
342    }
343}
344
345/// CPU backend implementation
346pub struct CpuBackend {
347    kernel: CpuKernel,
348}
349
350impl CpuBackend {
351    /// Create a new CPU backend
352    pub fn new() -> Self {
353        Self { kernel: CpuKernel }
354    }
355}
356
357impl Default for CpuBackend {
358    fn default() -> Self {
359        Self::new()
360    }
361}
362
363impl GpuBackend for CpuBackend {
364    fn is_available() -> bool {
365        true // CPU is always available
366    }
367
368    fn name(&self) -> &str {
369        "CPU"
370    }
371
372    fn device_info(&self) -> String {
373        // Use scirs2_core::parallel_ops (SciRS2 POLICY compliant)
374        use scirs2_core::parallel_ops::current_num_threads;
375        format!("CPU backend with {} threads", current_num_threads())
376    }
377
378    fn allocate_state_vector(&self, n_qubits: usize) -> QuantRS2Result<Box<dyn GpuBuffer>> {
379        let size = 1 << n_qubits;
380        Ok(Box::new(CpuBuffer::new(size)))
381    }
382
383    fn allocate_density_matrix(&self, n_qubits: usize) -> QuantRS2Result<Box<dyn GpuBuffer>> {
384        let size = 1 << (2 * n_qubits);
385        Ok(Box::new(CpuBuffer::new(size)))
386    }
387
388    fn kernel(&self) -> &dyn GpuKernel {
389        &self.kernel
390    }
391}
392
393#[cfg(test)]
394mod tests {
395    use super::*;
396
397    #[test]
398    fn test_cpu_buffer() {
399        let mut buffer = CpuBuffer::new(4);
400        let data = vec![
401            Complex64::new(1.0, 0.0),
402            Complex64::new(0.0, 1.0),
403            Complex64::new(-1.0, 0.0),
404            Complex64::new(0.0, -1.0),
405        ];
406
407        buffer.upload(&data).unwrap();
408
409        let mut downloaded = vec![Complex64::new(0.0, 0.0); 4];
410        buffer.download(&mut downloaded).unwrap();
411
412        assert_eq!(data, downloaded);
413    }
414
415    #[test]
416    fn test_cpu_backend() {
417        let backend = CpuBackend::new();
418        assert!(CpuBackend::is_available());
419        assert_eq!(backend.name(), "CPU");
420
421        // Test state vector allocation
422        let buffer = backend.allocate_state_vector(3).unwrap();
423        assert_eq!(buffer.size(), 8 * std::mem::size_of::<Complex64>());
424    }
425}