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#[cfg(test)]
269mod tests {
270    use super::*;
271
272    #[test]
273    fn test_normalize_simd() {
274        let mut state = vec![
275            Complex64::new(1.0, 0.0),
276            Complex64::new(0.0, 1.0),
277            Complex64::new(1.0, 0.0),
278            Complex64::new(0.0, -1.0),
279        ];
280
281        normalize_simd(&mut state).unwrap();
282
283        let norm_sqr: f64 = state.iter().map(|c| c.norm_sqr()).sum();
284        assert!((norm_sqr - 1.0).abs() < 1e-10);
285    }
286
287    #[test]
288    fn test_inner_product() {
289        let state1 = vec![Complex64::new(1.0, 0.0), Complex64::new(0.0, 0.0)];
290        let state2 = vec![Complex64::new(0.0, 0.0), Complex64::new(1.0, 0.0)];
291
292        let result = inner_product(&state1, &state2).unwrap();
293        assert_eq!(result, Complex64::new(0.0, 0.0));
294    }
295
296    #[test]
297    fn test_expectation_z() {
298        // |0⟩ state
299        let state0 = vec![Complex64::new(1.0, 0.0), Complex64::new(0.0, 0.0)];
300        let exp0 = expectation_z_simd(&state0, 0, 1);
301        assert!((exp0 - 1.0).abs() < 1e-10);
302
303        // |1⟩ state
304        let state1 = vec![Complex64::new(0.0, 0.0), Complex64::new(1.0, 0.0)];
305        let exp1 = expectation_z_simd(&state1, 0, 1);
306        assert!((exp1 + 1.0).abs() < 1e-10);
307
308        // |+⟩ state
309        let sqrt2_inv = 1.0 / std::f64::consts::SQRT_2;
310        let state_plus = vec![
311            Complex64::new(sqrt2_inv, 0.0),
312            Complex64::new(sqrt2_inv, 0.0),
313        ];
314        let exp_plus = expectation_z_simd(&state_plus, 0, 1);
315        assert!(exp_plus.abs() < 1e-10);
316    }
317
318    #[test]
319    fn test_hadamard_simd() {
320        // Test Hadamard gate on |0⟩ state to create |+⟩
321        let mut state = vec![Complex64::new(1.0, 0.0), Complex64::new(0.0, 0.0)];
322        hadamard_simd(&mut state, 0, 1);
323
324        let sqrt2_inv = 1.0 / 2.0_f64.sqrt();
325        assert!((state[0].re - sqrt2_inv).abs() < 1e-10);
326        assert!((state[1].re - sqrt2_inv).abs() < 1e-10);
327        assert!(state[0].im.abs() < 1e-10);
328        assert!(state[1].im.abs() < 1e-10);
329
330        // Apply Hadamard again to get back to |0⟩
331        hadamard_simd(&mut state, 0, 1);
332        assert!((state[0].re - 1.0).abs() < 1e-10);
333        assert!(state[1].re.abs() < 1e-10);
334    }
335
336    #[test]
337    fn test_phase_simd() {
338        let mut state = vec![Complex64::new(0.5, 0.5), Complex64::new(0.5, -0.5)];
339
340        let theta = std::f64::consts::PI / 4.0;
341        apply_phase_simd(&mut state, theta);
342
343        // Check that magnitudes are preserved
344        let norm_before = 0.5_f64.powi(2) + 0.5_f64.powi(2);
345        let norm_after = state[0].norm_sqr();
346        assert!((norm_before - norm_after).abs() < 1e-10);
347    }
348}