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 ndarray::Array2;
12use num_complex::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        let outcome = rand::random::<f64>() < prob_one;
292
293        Ok((outcome, if outcome { prob_one } else { 1.0 - prob_one }))
294    }
295
296    fn expectation_value(
297        &self,
298        state: &dyn GpuBuffer,
299        observable: &Array2<Complex64>,
300        qubits: &[QubitId],
301        n_qubits: usize,
302    ) -> QuantRS2Result<f64> {
303        let cpu_buffer = state
304            .as_any()
305            .downcast_ref::<CpuBuffer>()
306            .ok_or_else(|| QuantRS2Error::InvalidInput("Expected CpuBuffer".to_string()))?;
307
308        let data = cpu_buffer.data();
309
310        // For now, implement expectation value for single-qubit observables
311        if qubits.len() != 1 || observable.dim() != (2, 2) {
312            return Err(QuantRS2Error::UnsupportedOperation(
313                "Only single-qubit observables supported currently".to_string(),
314            ));
315        }
316
317        let qubit_idx = qubits[0].0 as usize;
318        let stride = 1 << qubit_idx;
319        let pairs = 1 << (n_qubits - 1);
320
321        let mut expectation = Complex64::new(0.0, 0.0);
322
323        for i in 0..pairs {
324            let i0 = ((i >> qubit_idx) << (qubit_idx + 1)) | (i & ((1 << qubit_idx) - 1));
325            let i1 = i0 | stride;
326
327            let a = data[i0];
328            let b = data[i1];
329
330            expectation += a.conj() * (observable[(0, 0)] * a + observable[(0, 1)] * b);
331            expectation += b.conj() * (observable[(1, 0)] * a + observable[(1, 1)] * b);
332        }
333
334        if expectation.im.abs() > 1e-10 {
335            return Err(QuantRS2Error::InvalidInput(
336                "Observable expectation value is not real".to_string(),
337            ));
338        }
339
340        Ok(expectation.re)
341    }
342}
343
344/// CPU backend implementation
345pub struct CpuBackend {
346    kernel: CpuKernel,
347}
348
349impl CpuBackend {
350    /// Create a new CPU backend
351    pub fn new() -> Self {
352        Self { kernel: CpuKernel }
353    }
354}
355
356impl Default for CpuBackend {
357    fn default() -> Self {
358        Self::new()
359    }
360}
361
362impl GpuBackend for CpuBackend {
363    fn is_available() -> bool {
364        true // CPU is always available
365    }
366
367    fn name(&self) -> &str {
368        "CPU"
369    }
370
371    fn device_info(&self) -> String {
372        format!("CPU backend with {} threads", rayon::current_num_threads())
373    }
374
375    fn allocate_state_vector(&self, n_qubits: usize) -> QuantRS2Result<Box<dyn GpuBuffer>> {
376        let size = 1 << n_qubits;
377        Ok(Box::new(CpuBuffer::new(size)))
378    }
379
380    fn allocate_density_matrix(&self, n_qubits: usize) -> QuantRS2Result<Box<dyn GpuBuffer>> {
381        let size = 1 << (2 * n_qubits);
382        Ok(Box::new(CpuBuffer::new(size)))
383    }
384
385    fn kernel(&self) -> &dyn GpuKernel {
386        &self.kernel
387    }
388}
389
390#[cfg(test)]
391mod tests {
392    use super::*;
393
394    #[test]
395    fn test_cpu_buffer() {
396        let mut buffer = CpuBuffer::new(4);
397        let data = vec![
398            Complex64::new(1.0, 0.0),
399            Complex64::new(0.0, 1.0),
400            Complex64::new(-1.0, 0.0),
401            Complex64::new(0.0, -1.0),
402        ];
403
404        buffer.upload(&data).unwrap();
405
406        let mut downloaded = vec![Complex64::new(0.0, 0.0); 4];
407        buffer.download(&mut downloaded).unwrap();
408
409        assert_eq!(data, downloaded);
410    }
411
412    #[test]
413    fn test_cpu_backend() {
414        let backend = CpuBackend::new();
415        assert!(CpuBackend::is_available());
416        assert_eq!(backend.name(), "CPU");
417
418        // Test state vector allocation
419        let buffer = backend.allocate_state_vector(3).unwrap();
420        assert_eq!(buffer.size(), 8 * std::mem::size_of::<Complex64>());
421    }
422}