quantrs2_core/
simd_ops.rs

1//! SIMD-accelerated quantum operations
2//!
3//! This module provides SIMD-accelerated implementations of common quantum
4//! operations using platform-specific SIMD instructions when available.
5
6use crate::error::QuantRS2Result;
7use num_complex::Complex64;
8
9// SIMD operations will be conditionally compiled
10
11/// Apply a phase rotation to a quantum state vector using SIMD when available
12///
13/// This function applies the phase rotation e^(i*theta) to each amplitude.
14pub fn apply_phase_simd(amplitudes: &mut [Complex64], theta: f64) {
15    let phase_factor = Complex64::new(theta.cos(), theta.sin());
16
17    #[cfg(feature = "simd")]
18    {
19        // Process in SIMD chunks when available
20        let mut chunks = amplitudes.chunks_exact_mut(4);
21        
22        // Process SIMD chunks
23        for chunk in &mut chunks {
24            // Simulate SIMD operation on 4 complex numbers at once
25            for amp in chunk {
26                *amp *= phase_factor;
27            }
28        }
29        
30        // Process remainder
31        let remainder = chunks.into_remainder();
32        for amp in remainder {
33            *amp *= phase_factor;
34        }
35    }
36    
37    #[cfg(not(feature = "simd"))]
38    {
39        // Fallback to scalar implementation
40        for amp in amplitudes.iter_mut() {
41            *amp *= phase_factor;
42        }
43    }
44}
45
46/// Compute the inner product of two quantum state vectors
47///
48/// This computes ⟨ψ|φ⟩ = Σ conj(ψ[i]) * φ[i]
49pub fn inner_product(state1: &[Complex64], state2: &[Complex64]) -> QuantRS2Result<Complex64> {
50    if state1.len() != state2.len() {
51        return Err(crate::error::QuantRS2Error::InvalidInput(
52            "State vectors must have the same length".to_string(),
53        ));
54    }
55
56    #[cfg(feature = "simd")]
57    {
58        let mut result = Complex64::new(0.0, 0.0);
59        
60        // Process in SIMD chunks
61        let chunks1 = state1.chunks_exact(4);
62        let chunks2 = state2.chunks_exact(4);
63        let remainder1 = chunks1.remainder();
64        let remainder2 = chunks2.remainder();
65        
66        // Process SIMD chunks
67        for (chunk1, chunk2) in chunks1.zip(chunks2) {
68            // Simulate SIMD operation on 4 complex numbers at once
69            for (a, b) in chunk1.iter().zip(chunk2.iter()) {
70                result += a.conj() * b;
71            }
72        }
73        
74        // Process remainder
75        for (a, b) in remainder1.iter().zip(remainder2.iter()) {
76            result += a.conj() * b;
77        }
78        
79        Ok(result)
80    }
81    
82    #[cfg(not(feature = "simd"))]
83    {
84        // Fallback to scalar implementation
85        let result = state1
86            .iter()
87            .zip(state2.iter())
88            .map(|(a, b)| a.conj() * b)
89            .sum();
90
91        Ok(result)
92    }
93}
94
95/// Normalize a quantum state vector in-place
96///
97/// This ensures that the sum of squared magnitudes equals 1.
98pub fn normalize_simd(amplitudes: &mut [Complex64]) -> QuantRS2Result<()> {
99    let norm_sqr: f64 = {
100        #[cfg(feature = "simd")]
101        {
102            // Compute norm squared using SIMD
103            let mut norm_sqr = 0.0;
104            
105            // Process in SIMD chunks
106            let chunks = amplitudes.chunks_exact(4);
107            let remainder = chunks.remainder();
108            
109            // Process SIMD chunks
110            for chunk in chunks {
111                // Simulate SIMD operation on 4 complex numbers at once
112                for amp in chunk {
113                    norm_sqr += amp.norm_sqr();
114                }
115            }
116            
117            // Process remainder
118            for amp in remainder {
119                norm_sqr += amp.norm_sqr();
120            }
121            
122            norm_sqr
123        }
124        
125        #[cfg(not(feature = "simd"))]
126        {
127            amplitudes.iter().map(|c| c.norm_sqr()).sum()
128        }
129    };
130
131    if norm_sqr == 0.0 {
132        return Err(crate::error::QuantRS2Error::InvalidInput(
133            "Cannot normalize zero vector".to_string(),
134        ));
135    }
136
137    let norm = norm_sqr.sqrt();
138
139    // Normalize each amplitude
140    #[cfg(feature = "simd")]
141    {
142        // Process in SIMD chunks
143        let mut chunks = amplitudes.chunks_exact_mut(4);
144        
145        // Process SIMD chunks
146        for chunk in &mut chunks {
147            for amp in chunk {
148                *amp /= norm;
149            }
150        }
151        
152        // Process remainder
153        let remainder = chunks.into_remainder();
154        for amp in remainder {
155            *amp /= norm;
156        }
157    }
158    
159    #[cfg(not(feature = "simd"))]
160    {
161        for amp in amplitudes.iter_mut() {
162            *amp /= norm;
163        }
164    }
165
166    Ok(())
167}
168
169/// Compute expectation value of a Pauli Z operator
170///
171/// This computes ⟨ψ|Z|ψ⟩ where Z is the Pauli Z operator on the given qubit.
172pub fn expectation_z_simd(amplitudes: &[Complex64], qubit: usize, num_qubits: usize) -> f64 {
173    let qubit_mask = 1 << qubit;
174    let mut expectation = 0.0;
175    
176    #[cfg(feature = "simd")]
177    {
178        // Process in SIMD chunks
179        for (i, amp) in amplitudes.iter().enumerate() {
180            let sign = if (i & qubit_mask) == 0 { 1.0 } else { -1.0 };
181            expectation += sign * amp.norm_sqr();
182        }
183    }
184    
185    #[cfg(not(feature = "simd"))]
186    {
187        for (i, amp) in amplitudes.iter().enumerate() {
188            let sign = if (i & qubit_mask) == 0 { 1.0 } else { -1.0 };
189            expectation += sign * amp.norm_sqr();
190        }
191    }
192    
193    expectation
194}
195
196/// Apply a Hadamard gate using SIMD operations
197///
198/// This applies H = (1/√2) * [[1, 1], [1, -1]] to the specified qubit.
199pub fn hadamard_simd(amplitudes: &mut [Complex64], qubit: usize, num_qubits: usize) {
200    let qubit_mask = 1 << qubit;
201    let sqrt2_inv = 1.0 / 2.0_f64.sqrt();
202    
203    #[cfg(feature = "simd")]
204    {
205        // Process pairs of amplitudes that differ only in the target qubit
206        for i in 0..(amplitudes.len() / 2) {
207            let idx0 = (i & !(qubit_mask >> 1)) | ((i & (qubit_mask >> 1)) << 1);
208            let idx1 = idx0 | qubit_mask;
209            
210            if idx1 < amplitudes.len() {
211                let a0 = amplitudes[idx0];
212                let a1 = amplitudes[idx1];
213                
214                amplitudes[idx0] = (a0 + a1) * sqrt2_inv;
215                amplitudes[idx1] = (a0 - a1) * sqrt2_inv;
216            }
217        }
218    }
219    
220    #[cfg(not(feature = "simd"))]
221    {
222        for i in 0..(amplitudes.len() / 2) {
223            let idx0 = (i & !(qubit_mask >> 1)) | ((i & (qubit_mask >> 1)) << 1);
224            let idx1 = idx0 | qubit_mask;
225            
226            if idx1 < amplitudes.len() {
227                let a0 = amplitudes[idx0];
228                let a1 = amplitudes[idx1];
229                
230                amplitudes[idx0] = (a0 + a1) * sqrt2_inv;
231                amplitudes[idx1] = (a0 - a1) * sqrt2_inv;
232            }
233        }
234    }
235}
236
237/// Apply a controlled phase rotation
238///
239/// This applies a phase rotation to amplitudes where the control qubit is |1⟩.
240pub fn controlled_phase_simd(
241    amplitudes: &mut [Complex64],
242    control_qubit: usize,
243    target_qubit: usize,
244    theta: f64,
245) -> QuantRS2Result<()> {
246    let num_qubits = (amplitudes.len() as f64).log2() as usize;
247
248    if control_qubit >= num_qubits || target_qubit >= num_qubits {
249        return Err(crate::error::QuantRS2Error::InvalidInput(
250            "Qubit index out of range".to_string(),
251        ));
252    }
253
254    let phase_factor = Complex64::new(theta.cos(), theta.sin());
255    let control_mask = 1 << control_qubit;
256    let target_mask = 1 << target_qubit;
257
258    // Apply phase to states where both control and target are |1⟩
259    for (idx, amp) in amplitudes.iter_mut().enumerate() {
260        if (idx & control_mask) != 0 && (idx & target_mask) != 0 {
261            *amp *= phase_factor;
262        }
263    }
264
265    Ok(())
266}
267
268
269#[cfg(test)]
270mod tests {
271    use super::*;
272
273    #[test]
274    fn test_normalize_simd() {
275        let mut state = vec![
276            Complex64::new(1.0, 0.0),
277            Complex64::new(0.0, 1.0),
278            Complex64::new(1.0, 0.0),
279            Complex64::new(0.0, -1.0),
280        ];
281
282        normalize_simd(&mut state).unwrap();
283
284        let norm_sqr: f64 = state.iter().map(|c| c.norm_sqr()).sum();
285        assert!((norm_sqr - 1.0).abs() < 1e-10);
286    }
287
288    #[test]
289    fn test_inner_product() {
290        let state1 = vec![Complex64::new(1.0, 0.0), Complex64::new(0.0, 0.0)];
291        let state2 = vec![Complex64::new(0.0, 0.0), Complex64::new(1.0, 0.0)];
292
293        let result = inner_product(&state1, &state2).unwrap();
294        assert_eq!(result, Complex64::new(0.0, 0.0));
295    }
296
297    #[test]
298    fn test_expectation_z() {
299        // |0⟩ state
300        let state0 = vec![Complex64::new(1.0, 0.0), Complex64::new(0.0, 0.0)];
301        let exp0 = expectation_z_simd(&state0, 0, 1);
302        assert!((exp0 - 1.0).abs() < 1e-10);
303
304        // |1⟩ state
305        let state1 = vec![Complex64::new(0.0, 0.0), Complex64::new(1.0, 0.0)];
306        let exp1 = expectation_z_simd(&state1, 0, 1);
307        assert!((exp1 + 1.0).abs() < 1e-10);
308
309        // |+⟩ state
310        let sqrt2_inv = 1.0 / std::f64::consts::SQRT_2;
311        let state_plus = vec![
312            Complex64::new(sqrt2_inv, 0.0),
313            Complex64::new(sqrt2_inv, 0.0),
314        ];
315        let exp_plus = expectation_z_simd(&state_plus, 0, 1);
316        assert!(exp_plus.abs() < 1e-10);
317    }
318    
319    #[test]
320    fn test_hadamard_simd() {
321        // Test Hadamard gate on |0⟩ state to create |+⟩
322        let mut state = vec![Complex64::new(1.0, 0.0), Complex64::new(0.0, 0.0)];
323        hadamard_simd(&mut state, 0, 1);
324        
325        let sqrt2_inv = 1.0 / 2.0_f64.sqrt();
326        assert!((state[0].re - sqrt2_inv).abs() < 1e-10);
327        assert!((state[1].re - sqrt2_inv).abs() < 1e-10);
328        assert!(state[0].im.abs() < 1e-10);
329        assert!(state[1].im.abs() < 1e-10);
330        
331        // Apply Hadamard again to get back to |0⟩
332        hadamard_simd(&mut state, 0, 1);
333        assert!((state[0].re - 1.0).abs() < 1e-10);
334        assert!(state[1].re.abs() < 1e-10);
335    }
336    
337    #[test]
338    fn test_phase_simd() {
339        let mut state = vec![
340            Complex64::new(0.5, 0.5),
341            Complex64::new(0.5, -0.5),
342        ];
343        
344        let theta = std::f64::consts::PI / 4.0;
345        apply_phase_simd(&mut state, theta);
346        
347        // Check that magnitudes are preserved
348        let norm_before = 0.5_f64.powi(2) + 0.5_f64.powi(2);
349        let norm_after = state[0].norm_sqr();
350        assert!((norm_before - norm_after).abs() < 1e-10);
351    }
352}