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