quantrs2_core/gpu/
mod.rs

1//! GPU acceleration backend for quantum operations
2//!
3//! This module provides an abstraction layer for GPU-accelerated quantum
4//! computations, supporting multiple backends (CUDA, Metal, Vulkan, etc.)
5
6use crate::{
7    error::{QuantRS2Error, QuantRS2Result},
8    gate::GateOp,
9    qubit::QubitId,
10};
11use ndarray::{Array1, Array2};
12use num_complex::Complex64;
13use std::sync::Arc;
14
15pub mod cpu_backend;
16#[cfg(feature = "cuda")]
17pub mod cuda_backend;
18#[cfg(feature = "metal")]
19pub mod metal_backend;
20#[cfg(feature = "vulkan")]
21pub mod vulkan_backend;
22
23/// GPU memory buffer abstraction
24pub trait GpuBuffer: Send + Sync {
25    /// Get the size of the buffer in bytes
26    fn size(&self) -> usize;
27
28    /// Copy data from host to device
29    fn upload(&mut self, data: &[Complex64]) -> QuantRS2Result<()>;
30
31    /// Copy data from device to host
32    fn download(&self, data: &mut [Complex64]) -> QuantRS2Result<()>;
33
34    /// Synchronize GPU operations
35    fn sync(&self) -> QuantRS2Result<()>;
36
37    /// Enable downcasting to concrete types
38    fn as_any(&self) -> &dyn std::any::Any;
39
40    /// Enable mutable downcasting to concrete types
41    fn as_any_mut(&mut self) -> &mut dyn std::any::Any;
42}
43
44/// GPU kernel for quantum operations
45pub trait GpuKernel: Send + Sync {
46    /// Apply a single-qubit gate
47    fn apply_single_qubit_gate(
48        &self,
49        state: &mut dyn GpuBuffer,
50        gate_matrix: &[Complex64; 4],
51        qubit: QubitId,
52        n_qubits: usize,
53    ) -> QuantRS2Result<()>;
54
55    /// Apply a two-qubit gate
56    fn apply_two_qubit_gate(
57        &self,
58        state: &mut dyn GpuBuffer,
59        gate_matrix: &[Complex64; 16],
60        control: QubitId,
61        target: QubitId,
62        n_qubits: usize,
63    ) -> QuantRS2Result<()>;
64
65    /// Apply a multi-qubit gate
66    fn apply_multi_qubit_gate(
67        &self,
68        state: &mut dyn GpuBuffer,
69        gate_matrix: &Array2<Complex64>,
70        qubits: &[QubitId],
71        n_qubits: usize,
72    ) -> QuantRS2Result<()>;
73
74    /// Measure a qubit
75    fn measure_qubit(
76        &self,
77        state: &dyn GpuBuffer,
78        qubit: QubitId,
79        n_qubits: usize,
80    ) -> QuantRS2Result<(bool, f64)>;
81
82    /// Calculate expectation value
83    fn expectation_value(
84        &self,
85        state: &dyn GpuBuffer,
86        observable: &Array2<Complex64>,
87        qubits: &[QubitId],
88        n_qubits: usize,
89    ) -> QuantRS2Result<f64>;
90}
91
92/// GPU backend trait for quantum computations
93pub trait GpuBackend: Send + Sync {
94    /// Check if this backend is available on the current system
95    fn is_available() -> bool
96    where
97        Self: Sized;
98
99    /// Get the name of this backend
100    fn name(&self) -> &str;
101
102    /// Get device information
103    fn device_info(&self) -> String;
104
105    /// Allocate a GPU buffer for a state vector
106    fn allocate_state_vector(&self, n_qubits: usize) -> QuantRS2Result<Box<dyn GpuBuffer>>;
107
108    /// Allocate a GPU buffer for a density matrix
109    fn allocate_density_matrix(&self, n_qubits: usize) -> QuantRS2Result<Box<dyn GpuBuffer>>;
110
111    /// Get the kernel implementation
112    fn kernel(&self) -> &dyn GpuKernel;
113
114    /// Apply a quantum gate
115    fn apply_gate(
116        &self,
117        state: &mut dyn GpuBuffer,
118        gate: &dyn GateOp,
119        qubits: &[QubitId],
120        n_qubits: usize,
121    ) -> QuantRS2Result<()> {
122        match qubits.len() {
123            1 => {
124                let matrix = gate.matrix()?;
125                let gate_array: [Complex64; 4] = [matrix[0], matrix[1], matrix[2], matrix[3]];
126                self.kernel()
127                    .apply_single_qubit_gate(state, &gate_array, qubits[0], n_qubits)
128            }
129            2 => {
130                let matrix = gate.matrix()?;
131                let mut gate_array = [Complex64::new(0.0, 0.0); 16];
132                for (i, &val) in matrix.iter().enumerate() {
133                    gate_array[i] = val;
134                }
135                self.kernel().apply_two_qubit_gate(
136                    state,
137                    &gate_array,
138                    qubits[0],
139                    qubits[1],
140                    n_qubits,
141                )
142            }
143            _ => {
144                let matrix_vec = gate.matrix()?;
145                let size = (1 << qubits.len(), 1 << qubits.len());
146                let matrix = Array2::from_shape_vec(size, matrix_vec)?;
147                self.kernel()
148                    .apply_multi_qubit_gate(state, &matrix, qubits, n_qubits)
149            }
150        }
151    }
152
153    /// Measure a qubit and collapse the state
154    fn measure(
155        &self,
156        state: &mut dyn GpuBuffer,
157        qubit: QubitId,
158        n_qubits: usize,
159    ) -> QuantRS2Result<bool> {
160        let (outcome, _prob) = self.kernel().measure_qubit(state, qubit, n_qubits)?;
161        Ok(outcome)
162    }
163
164    /// Get measurement probability without collapsing
165    fn get_probability(
166        &self,
167        state: &dyn GpuBuffer,
168        qubit: QubitId,
169        n_qubits: usize,
170    ) -> QuantRS2Result<f64> {
171        let (_outcome, prob) = self.kernel().measure_qubit(state, qubit, n_qubits)?;
172        Ok(prob)
173    }
174}
175
176/// GPU-accelerated state vector
177pub struct GpuStateVector {
178    /// The GPU backend
179    backend: Arc<dyn GpuBackend>,
180    /// The GPU buffer holding the state
181    buffer: Box<dyn GpuBuffer>,
182    /// Number of qubits
183    n_qubits: usize,
184}
185
186impl GpuStateVector {
187    /// Create a new GPU state vector
188    pub fn new(backend: Arc<dyn GpuBackend>, n_qubits: usize) -> QuantRS2Result<Self> {
189        let buffer = backend.allocate_state_vector(n_qubits)?;
190        Ok(Self {
191            backend,
192            buffer,
193            n_qubits,
194        })
195    }
196
197    /// Initialize to |00...0⟩ state
198    pub fn initialize_zero_state(&mut self) -> QuantRS2Result<()> {
199        let size = 1 << self.n_qubits;
200        let mut data = vec![Complex64::new(0.0, 0.0); size];
201        data[0] = Complex64::new(1.0, 0.0);
202        self.buffer.upload(&data)
203    }
204
205    /// Apply a gate
206    pub fn apply_gate(&mut self, gate: &dyn GateOp, qubits: &[QubitId]) -> QuantRS2Result<()> {
207        self.backend
208            .apply_gate(self.buffer.as_mut(), gate, qubits, self.n_qubits)
209    }
210
211    /// Measure a qubit
212    pub fn measure(&mut self, qubit: QubitId) -> QuantRS2Result<bool> {
213        self.backend
214            .measure(self.buffer.as_mut(), qubit, self.n_qubits)
215    }
216
217    /// Get the state vector as a host array
218    pub fn to_array(&self) -> QuantRS2Result<Array1<Complex64>> {
219        let size = 1 << self.n_qubits;
220        let mut data = vec![Complex64::new(0.0, 0.0); size];
221        self.buffer.download(&mut data)?;
222        Ok(Array1::from_vec(data))
223    }
224
225    /// Get measurement probabilities for all basis states
226    pub fn get_probabilities(&self) -> QuantRS2Result<Vec<f64>> {
227        let state = self.to_array()?;
228        Ok(state.iter().map(|c| c.norm_sqr()).collect())
229    }
230}
231
232/// GPU backend factory
233pub struct GpuBackendFactory;
234
235impl GpuBackendFactory {
236    /// Create the best available GPU backend
237    pub fn create_best_available() -> QuantRS2Result<Arc<dyn GpuBackend>> {
238        // Try backends in order of preference
239        #[cfg(feature = "cuda")]
240        if cuda_backend::CudaBackend::is_available() {
241            return Ok(Arc::new(cuda_backend::CudaBackend::new()?));
242        }
243
244        #[cfg(feature = "metal")]
245        if metal_backend::MetalBackend::is_available() {
246            return Ok(Arc::new(metal_backend::MetalBackend::new()?));
247        }
248
249        #[cfg(feature = "vulkan")]
250        if vulkan_backend::VulkanBackend::is_available() {
251            return Ok(Arc::new(vulkan_backend::VulkanBackend::new()?));
252        }
253
254        // Fallback to CPU backend
255        Ok(Arc::new(cpu_backend::CpuBackend::new()))
256    }
257
258    /// Create a specific backend
259    pub fn create_backend(backend_type: &str) -> QuantRS2Result<Arc<dyn GpuBackend>> {
260        match backend_type.to_lowercase().as_str() {
261            #[cfg(feature = "cuda")]
262            "cuda" => Ok(Arc::new(cuda_backend::CudaBackend::new()?)),
263
264            #[cfg(feature = "metal")]
265            "metal" => Ok(Arc::new(metal_backend::MetalBackend::new()?)),
266
267            #[cfg(feature = "vulkan")]
268            "vulkan" => Ok(Arc::new(vulkan_backend::VulkanBackend::new()?)),
269
270            "cpu" => Ok(Arc::new(cpu_backend::CpuBackend::new())),
271
272            _ => Err(QuantRS2Error::InvalidInput(format!(
273                "Unknown backend type: {}",
274                backend_type
275            ))),
276        }
277    }
278
279    /// List available backends
280    pub fn available_backends() -> Vec<&'static str> {
281        let mut backends = vec!["cpu"];
282
283        #[cfg(feature = "cuda")]
284        if cuda_backend::CudaBackend::is_available() {
285            backends.push("cuda");
286        }
287
288        #[cfg(feature = "metal")]
289        if metal_backend::MetalBackend::is_available() {
290            backends.push("metal");
291        }
292
293        #[cfg(feature = "vulkan")]
294        if vulkan_backend::VulkanBackend::is_available() {
295            backends.push("vulkan");
296        }
297
298        backends
299    }
300}
301
302/// Configuration for GPU operations
303#[derive(Debug, Clone)]
304pub struct GpuConfig {
305    /// Preferred backend (None for auto-selection)
306    pub backend: Option<String>,
307    /// Maximum GPU memory to use (in bytes)
308    pub max_memory: Option<usize>,
309    /// Number of GPU threads/work items
310    pub num_threads: Option<usize>,
311    /// Enable profiling
312    pub enable_profiling: bool,
313}
314
315impl Default for GpuConfig {
316    fn default() -> Self {
317        Self {
318            backend: None,
319            max_memory: None,
320            num_threads: None,
321            enable_profiling: false,
322        }
323    }
324}
325
326#[cfg(test)]
327mod tests {
328    use super::*;
329    use crate::gate::single::Hadamard;
330
331    #[test]
332    fn test_gpu_backend_factory() {
333        let backends = GpuBackendFactory::available_backends();
334        assert!(backends.contains(&"cpu"));
335
336        // Should always be able to create CPU backend
337        let backend = GpuBackendFactory::create_backend("cpu").unwrap();
338        assert_eq!(backend.name(), "CPU");
339    }
340
341    #[test]
342    fn test_gpu_state_vector() {
343        let backend = GpuBackendFactory::create_best_available().unwrap();
344        let mut state = GpuStateVector::new(backend, 2).unwrap();
345
346        // Initialize to |00⟩
347        state.initialize_zero_state().unwrap();
348
349        // Apply Hadamard to first qubit
350        let h_gate = Hadamard { target: QubitId(0) };
351        state.apply_gate(&h_gate, &[QubitId(0)]).unwrap();
352
353        // Get probabilities
354        let probs = state.get_probabilities().unwrap();
355        assert_eq!(probs.len(), 4);
356
357        // Should be in equal superposition on first qubit
358        // With our bit ordering (LSB), |00⟩ and |01⟩ should have probability 0.5 each
359        assert!((probs[0] - 0.5).abs() < 1e-10); // |00⟩
360        assert!((probs[1] - 0.5).abs() < 1e-10); // |01⟩
361        assert!((probs[2] - 0.0).abs() < 1e-10); // |10⟩
362        assert!((probs[3] - 0.0).abs() < 1e-10); // |11⟩
363    }
364}