quantrs2_core/
simd_ops.rs

1//! SIMD-accelerated quantum operations
2//!
3//! This module provides SIMD-accelerated implementations of common quantum
4//! operations using SciRS2's unified SIMD operations.
5
6use crate::error::QuantRS2Result;
7use scirs2_core::Complex64;
8// use scirs2_core::simd_ops::SimdUnifiedOps;
9use crate::simd_ops_stubs::{SimdComplex64, SimdF64};
10use scirs2_core::ndarray::{ArrayView1, ArrayViewMut1};
11
12// All SIMD operations now use SciRS2's unified trait
13
14/// Apply a phase rotation to a quantum state vector using SIMD when available
15///
16/// This function applies the phase rotation e^(i*theta) to each amplitude.
17pub fn apply_phase_simd(amplitudes: &mut [Complex64], theta: f64) {
18    let cos_theta = theta.cos();
19    let sin_theta = theta.sin();
20
21    // Extract real and imaginary parts for SIMD operations
22    let len = amplitudes.len();
23    let mut real_parts = Vec::with_capacity(len);
24    let mut imag_parts = Vec::with_capacity(len);
25
26    for amp in amplitudes.iter() {
27        real_parts.push(amp.re);
28        imag_parts.push(amp.im);
29    }
30
31    // Apply phase rotation using SIMD
32    // (a + bi) * (cos + i*sin) = (a*cos - b*sin) + i*(a*sin + b*cos)
33    let mut new_real = vec![0.0; len];
34    let mut new_imag = vec![0.0; len];
35
36    // Compute real part: a*cos - b*sin
37    let real_view = ArrayView1::from(&real_parts);
38    let imag_view = ArrayView1::from(&imag_parts);
39    let mut new_real_view = ArrayViewMut1::from(&mut new_real);
40    let mut new_imag_view = ArrayViewMut1::from(&mut new_imag);
41
42    // Use SciRS2 SIMD operations
43    let real_cos = <f64 as SimdF64>::simd_scalar_mul(&real_view, cos_theta);
44    let imag_sin = <f64 as SimdF64>::simd_scalar_mul(&imag_view, sin_theta);
45    let new_real_arr = <f64 as SimdF64>::simd_sub_arrays(&real_cos.view(), &imag_sin.view());
46
47    // Compute imaginary part: a*sin + b*cos
48    let real_sin = <f64 as SimdF64>::simd_scalar_mul(&real_view, sin_theta);
49    let imag_cos = <f64 as SimdF64>::simd_scalar_mul(&imag_view, cos_theta);
50    let new_imag_arr = <f64 as SimdF64>::simd_add_arrays(&real_sin.view(), &imag_cos.view());
51
52    // Copy results
53    new_real_view.assign(&new_real_arr);
54    new_imag_view.assign(&new_imag_arr);
55
56    // Reconstruct complex numbers
57    for (i, amp) in amplitudes.iter_mut().enumerate() {
58        *amp = Complex64::new(new_real[i], new_imag[i]);
59    }
60}
61
62/// Compute the inner product of two quantum state vectors
63///
64/// This computes ⟨ψ|φ⟩ = Σ conj(ψ[i]) * φ[i]
65pub fn inner_product(state1: &[Complex64], state2: &[Complex64]) -> QuantRS2Result<Complex64> {
66    if state1.len() != state2.len() {
67        return Err(crate::error::QuantRS2Error::InvalidInput(
68            "State vectors must have the same length".to_string(),
69        ));
70    }
71
72    let len = state1.len();
73
74    // Extract real and imaginary parts
75    let mut state1_real = Vec::with_capacity(len);
76    let mut state1_imag = Vec::with_capacity(len);
77    let mut state2_real = Vec::with_capacity(len);
78    let mut state2_imag = Vec::with_capacity(len);
79
80    for (a, b) in state1.iter().zip(state2.iter()) {
81        state1_real.push(a.re);
82        state1_imag.push(a.im);
83        state2_real.push(b.re);
84        state2_imag.push(b.im);
85    }
86
87    // Compute inner product using SIMD
88    // ⟨ψ|φ⟩ = Σ (a_r - i*a_i) * (b_r + i*b_i)
89    //       = Σ (a_r*b_r + a_i*b_i) + i*(a_r*b_i - a_i*b_r)
90
91    let state1_real_view = ArrayView1::from(&state1_real);
92    let state1_imag_view = ArrayView1::from(&state1_imag);
93    let state2_real_view = ArrayView1::from(&state2_real);
94    let state2_imag_view = ArrayView1::from(&state2_imag);
95
96    // Compute real part: a_r*b_r + a_i*b_i
97    let rr_product = <f64 as SimdF64>::simd_mul_arrays(&state1_real_view, &state2_real_view);
98    let ii_product = <f64 as SimdF64>::simd_mul_arrays(&state1_imag_view, &state2_imag_view);
99    let real_sum = <f64 as SimdF64>::simd_add_arrays(&rr_product.view(), &ii_product.view());
100    let real_part = <f64 as SimdF64>::simd_sum_array(&real_sum.view());
101
102    // Compute imaginary part: a_r*b_i - a_i*b_r
103    let ri_product = <f64 as SimdF64>::simd_mul_arrays(&state1_real_view, &state2_imag_view);
104    let ir_product = <f64 as SimdF64>::simd_mul_arrays(&state1_imag_view, &state2_real_view);
105    let imag_diff = <f64 as SimdF64>::simd_sub_arrays(&ri_product.view(), &ir_product.view());
106    let imag_part = <f64 as SimdF64>::simd_sum_array(&imag_diff.view());
107
108    Ok(Complex64::new(real_part, -imag_part)) // Negative because of conjugate
109}
110
111/// Normalize a quantum state vector in-place
112///
113/// This ensures that the sum of squared magnitudes equals 1.
114pub fn normalize_simd(amplitudes: &mut [Complex64]) -> QuantRS2Result<()> {
115    let len = amplitudes.len();
116
117    // Extract real and imaginary parts
118    let mut real_parts = Vec::with_capacity(len);
119    let mut imag_parts = Vec::with_capacity(len);
120
121    for amp in amplitudes.iter() {
122        real_parts.push(amp.re);
123        imag_parts.push(amp.im);
124    }
125
126    // Compute norm squared using SIMD: Σ(re² + im²)
127    let real_view = ArrayView1::from(&real_parts);
128    let imag_view = ArrayView1::from(&imag_parts);
129
130    let real_squared = <f64 as SimdF64>::simd_mul_arrays(&real_view, &real_view);
131    let imag_squared = <f64 as SimdF64>::simd_mul_arrays(&imag_view, &imag_view);
132    let sum_squared = <f64 as SimdF64>::simd_add_arrays(&real_squared.view(), &imag_squared.view());
133
134    let norm_sqr = <f64 as SimdF64>::simd_sum_array(&sum_squared.view());
135
136    if norm_sqr == 0.0 {
137        return Err(crate::error::QuantRS2Error::InvalidInput(
138            "Cannot normalize zero vector".to_string(),
139        ));
140    }
141
142    let norm = norm_sqr.sqrt();
143    let norm_inv = 1.0 / norm;
144
145    // Normalize using SIMD
146    let mut normalized_real = vec![0.0; len];
147    let mut normalized_imag = vec![0.0; len];
148    let mut normalized_real_view = ArrayViewMut1::from(&mut normalized_real);
149    let mut normalized_imag_view = ArrayViewMut1::from(&mut normalized_imag);
150
151    let normalized_real_arr = <f64 as SimdF64>::simd_scalar_mul(&real_view, norm_inv);
152    let normalized_imag_arr = <f64 as SimdF64>::simd_scalar_mul(&imag_view, norm_inv);
153    normalized_real_view.assign(&normalized_real_arr);
154    normalized_imag_view.assign(&normalized_imag_arr);
155
156    // Reconstruct normalized complex numbers
157    for (i, amp) in amplitudes.iter_mut().enumerate() {
158        *amp = Complex64::new(normalized_real[i], normalized_imag[i]);
159    }
160
161    Ok(())
162}
163
164/// Compute expectation value of a Pauli Z operator
165///
166/// This computes ⟨ψ|Z|ψ⟩ where Z is the Pauli Z operator on the given qubit.
167pub fn expectation_z_simd(amplitudes: &[Complex64], qubit: usize, _num_qubits: usize) -> f64 {
168    let qubit_mask = 1 << qubit;
169    let len = amplitudes.len();
170
171    // Compute norm squared and signs
172    let mut norm_sqrs = Vec::with_capacity(len);
173    let mut signs = Vec::with_capacity(len);
174
175    for (i, amp) in amplitudes.iter().enumerate() {
176        norm_sqrs.push(amp.re * amp.re + amp.im * amp.im);
177        signs.push(if (i & qubit_mask) == 0 { 1.0 } else { -1.0 });
178    }
179
180    // Compute expectation using SIMD
181    let norm_view = ArrayView1::from(&norm_sqrs);
182    let sign_view = ArrayView1::from(&signs);
183
184    let weighted_arr = <f64 as SimdF64>::simd_mul_arrays(&norm_view, &sign_view);
185    <f64 as SimdF64>::simd_sum_array(&weighted_arr.view())
186}
187
188/// Apply a Hadamard gate using SIMD operations
189///
190/// This applies H = (1/√2) * [[1, 1], [1, -1]] to the specified qubit.
191pub fn hadamard_simd(amplitudes: &mut [Complex64], qubit: usize, _num_qubits: usize) {
192    let qubit_mask = 1 << qubit;
193    let sqrt2_inv = 1.0 / 2.0_f64.sqrt();
194
195    // Collect pairs of indices that need to be processed
196    let mut idx0_list = Vec::new();
197    let mut idx1_list = Vec::new();
198
199    for i in 0..(amplitudes.len() / 2) {
200        let idx0 = (i & !(qubit_mask >> 1)) | ((i & (qubit_mask >> 1)) << 1);
201        let idx1 = idx0 | qubit_mask;
202
203        if idx1 < amplitudes.len() {
204            idx0_list.push(idx0);
205            idx1_list.push(idx1);
206        }
207    }
208
209    let pair_count = idx0_list.len();
210    if pair_count == 0 {
211        return;
212    }
213
214    // Extract amplitude pairs
215    let mut a0_real = Vec::with_capacity(pair_count);
216    let mut a0_imag = Vec::with_capacity(pair_count);
217    let mut a1_real = Vec::with_capacity(pair_count);
218    let mut a1_imag = Vec::with_capacity(pair_count);
219
220    for i in 0..pair_count {
221        let a0 = amplitudes[idx0_list[i]];
222        let a1 = amplitudes[idx1_list[i]];
223        a0_real.push(a0.re);
224        a0_imag.push(a0.im);
225        a1_real.push(a1.re);
226        a1_imag.push(a1.im);
227    }
228
229    // Apply Hadamard using SIMD
230    let a0_real_view = ArrayView1::from(&a0_real);
231    let a0_imag_view = ArrayView1::from(&a0_imag);
232    let a1_real_view = ArrayView1::from(&a1_real);
233    let a1_imag_view = ArrayView1::from(&a1_imag);
234
235    let mut new_a0_real = vec![0.0; pair_count];
236    let mut new_a0_imag = vec![0.0; pair_count];
237    let mut new_a1_real = vec![0.0; pair_count];
238    let mut new_a1_imag = vec![0.0; pair_count];
239
240    let mut new_a0_real_view = ArrayViewMut1::from(&mut new_a0_real);
241    let mut new_a0_imag_view = ArrayViewMut1::from(&mut new_a0_imag);
242    let mut new_a1_real_view = ArrayViewMut1::from(&mut new_a1_real);
243    let mut new_a1_imag_view = ArrayViewMut1::from(&mut new_a1_imag);
244
245    // Compute (a0 + a1) * sqrt2_inv
246    let real_sum = <f64 as SimdF64>::simd_add_arrays(&a0_real_view, &a1_real_view);
247    let new_a0_real_arr = <f64 as SimdF64>::simd_scalar_mul(&real_sum.view(), sqrt2_inv);
248    let imag_sum = <f64 as SimdF64>::simd_add_arrays(&a0_imag_view, &a1_imag_view);
249    let new_a0_imag_arr = <f64 as SimdF64>::simd_scalar_mul(&imag_sum.view(), sqrt2_inv);
250
251    // Compute (a0 - a1) * sqrt2_inv
252    let real_diff = <f64 as SimdF64>::simd_sub_arrays(&a0_real_view, &a1_real_view);
253    let new_a1_real_arr = <f64 as SimdF64>::simd_scalar_mul(&real_diff.view(), sqrt2_inv);
254    let imag_diff = <f64 as SimdF64>::simd_sub_arrays(&a0_imag_view, &a1_imag_view);
255    let new_a1_imag_arr = <f64 as SimdF64>::simd_scalar_mul(&imag_diff.view(), sqrt2_inv);
256
257    new_a0_real_view.assign(&new_a0_real_arr);
258    new_a0_imag_view.assign(&new_a0_imag_arr);
259    new_a1_real_view.assign(&new_a1_real_arr);
260    new_a1_imag_view.assign(&new_a1_imag_arr);
261
262    // Write back results
263    for i in 0..pair_count {
264        amplitudes[idx0_list[i]] = Complex64::new(new_a0_real[i], new_a0_imag[i]);
265        amplitudes[idx1_list[i]] = Complex64::new(new_a1_real[i], new_a1_imag[i]);
266    }
267}
268
269/// Apply a controlled phase rotation
270///
271/// This applies a phase rotation to amplitudes where the control qubit is |1⟩.
272pub fn controlled_phase_simd(
273    amplitudes: &mut [Complex64],
274    control_qubit: usize,
275    target_qubit: usize,
276    theta: f64,
277) -> QuantRS2Result<()> {
278    let num_qubits = (amplitudes.len() as f64).log2() as usize;
279
280    if control_qubit >= num_qubits || target_qubit >= num_qubits {
281        return Err(crate::error::QuantRS2Error::InvalidInput(
282            "Qubit index out of range".to_string(),
283        ));
284    }
285
286    let cos_theta = theta.cos();
287    let sin_theta = theta.sin();
288    let control_mask = 1 << control_qubit;
289    let target_mask = 1 << target_qubit;
290
291    // Collect indices where phase needs to be applied
292    let mut indices = Vec::new();
293    for idx in 0..amplitudes.len() {
294        if (idx & control_mask) != 0 && (idx & target_mask) != 0 {
295            indices.push(idx);
296        }
297    }
298
299    if indices.is_empty() {
300        return Ok(());
301    }
302
303    // Extract amplitudes to be rotated
304    let count = indices.len();
305    let mut real_parts = Vec::with_capacity(count);
306    let mut imag_parts = Vec::with_capacity(count);
307
308    for &idx in &indices {
309        let amp = amplitudes[idx];
310        real_parts.push(amp.re);
311        imag_parts.push(amp.im);
312    }
313
314    // Apply phase rotation using SIMD
315    let real_view = ArrayView1::from(&real_parts);
316    let imag_view = ArrayView1::from(&imag_parts);
317
318    let mut new_real = vec![0.0; count];
319    let mut new_imag = vec![0.0; count];
320    let mut new_real_view = ArrayViewMut1::from(&mut new_real);
321    let mut new_imag_view = ArrayViewMut1::from(&mut new_imag);
322
323    // Compute real part: a*cos - b*sin
324    let real_cos = <f64 as SimdF64>::simd_scalar_mul(&real_view, cos_theta);
325    let imag_sin = <f64 as SimdF64>::simd_scalar_mul(&imag_view, sin_theta);
326    let new_real_arr = <f64 as SimdF64>::simd_sub_arrays(&real_cos.view(), &imag_sin.view());
327
328    // Compute imaginary part: a*sin + b*cos
329    let real_sin = <f64 as SimdF64>::simd_scalar_mul(&real_view, sin_theta);
330    let imag_cos = <f64 as SimdF64>::simd_scalar_mul(&imag_view, cos_theta);
331    let new_imag_arr = <f64 as SimdF64>::simd_add_arrays(&real_sin.view(), &imag_cos.view());
332
333    new_real_view.assign(&new_real_arr);
334    new_imag_view.assign(&new_imag_arr);
335
336    // Write back results
337    for (i, &idx) in indices.iter().enumerate() {
338        amplitudes[idx] = Complex64::new(new_real[i], new_imag[i]);
339    }
340
341    Ok(())
342}
343
344#[cfg(test)]
345mod tests {
346    use super::*;
347
348    #[test]
349    fn test_normalize_simd() {
350        let mut state = vec![
351            Complex64::new(1.0, 0.0),
352            Complex64::new(0.0, 1.0),
353            Complex64::new(1.0, 0.0),
354            Complex64::new(0.0, -1.0),
355        ];
356
357        normalize_simd(&mut state).unwrap();
358
359        let norm_sqr: f64 = state.iter().map(|c| c.norm_sqr()).sum();
360        assert!((norm_sqr - 1.0).abs() < 1e-10);
361    }
362
363    #[test]
364    fn test_inner_product() {
365        let state1 = vec![Complex64::new(1.0, 0.0), Complex64::new(0.0, 0.0)];
366        let state2 = vec![Complex64::new(0.0, 0.0), Complex64::new(1.0, 0.0)];
367
368        let result = inner_product(&state1, &state2).unwrap();
369        assert_eq!(result, Complex64::new(0.0, 0.0));
370    }
371
372    #[test]
373    fn test_expectation_z() {
374        // |0⟩ state
375        let state0 = vec![Complex64::new(1.0, 0.0), Complex64::new(0.0, 0.0)];
376        let exp0 = expectation_z_simd(&state0, 0, 1);
377        assert!((exp0 - 1.0).abs() < 1e-10);
378
379        // |1⟩ state
380        let state1 = vec![Complex64::new(0.0, 0.0), Complex64::new(1.0, 0.0)];
381        let exp1 = expectation_z_simd(&state1, 0, 1);
382        assert!((exp1 + 1.0).abs() < 1e-10);
383
384        // |+⟩ state
385        let sqrt2_inv = 1.0 / std::f64::consts::SQRT_2;
386        let state_plus = vec![
387            Complex64::new(sqrt2_inv, 0.0),
388            Complex64::new(sqrt2_inv, 0.0),
389        ];
390        let exp_plus = expectation_z_simd(&state_plus, 0, 1);
391        assert!(exp_plus.abs() < 1e-10);
392    }
393
394    #[test]
395    fn test_hadamard_simd() {
396        // Test Hadamard gate on |0⟩ state to create |+⟩
397        let mut state = vec![Complex64::new(1.0, 0.0), Complex64::new(0.0, 0.0)];
398        hadamard_simd(&mut state, 0, 1);
399
400        let sqrt2_inv = 1.0 / 2.0_f64.sqrt();
401        assert!((state[0].re - sqrt2_inv).abs() < 1e-10);
402        assert!((state[1].re - sqrt2_inv).abs() < 1e-10);
403        assert!(state[0].im.abs() < 1e-10);
404        assert!(state[1].im.abs() < 1e-10);
405
406        // Apply Hadamard again to get back to |0⟩
407        hadamard_simd(&mut state, 0, 1);
408        assert!((state[0].re - 1.0).abs() < 1e-10);
409        assert!(state[1].re.abs() < 1e-10);
410    }
411
412    #[test]
413    fn test_phase_simd() {
414        let mut state = vec![Complex64::new(0.5, 0.5), Complex64::new(0.5, -0.5)];
415
416        let theta = std::f64::consts::PI / 4.0;
417        apply_phase_simd(&mut state, theta);
418
419        // Check that magnitudes are preserved
420        let norm_before = 0.5_f64.powi(2) + 0.5_f64.powi(2);
421        let norm_after = state[0].norm_sqr();
422        assert!((norm_before - norm_after).abs() < 1e-10);
423    }
424}