Skip to main content

cqlib_core/qis/state/
statevector.rs

1// This code is part of Cqlib.
2// Modified to support reproducible state sampling.
3//
4// (C) Copyright China Telecom Quantum Group 2026
5//
6// This code is licensed under the Apache License, Version 2.0. You may
7// obtain a copy of this license in the LICENSE.txt file in the root directory
8// of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
9//
10// Any modifications or derivative works of this code must retain this
11// copyright notice, and modified files need to carry a notice indicating
12// that they have been altered from the originals.
13//
14// Modified to validate terminal measurements and correct density matrix reset.
15
16//! Statevector quantum simulation.
17//!
18//! This module provides a high-performance statevector simulator for quantum circuits.
19//! It supports:
20//! - Single-qubit and multi-qubit gate operations
21//! - Controlled and multi-controlled gates
22//! - Parameterized rotations (RX, RY, RZ, etc.)
23//! - Native gates for superconducting qubits (fSim, XY)
24//! - Parallel execution for large statevectors using Rayon
25//!
26//! # Performance Features
27//! - Automatic parallelization threshold (14+ qubits)
28//! - Specialized kernels for common gates (avoiding generic matrix multiplication)
29//! - Memory-efficient in-place operations
30//! - Contiguous memory layout compatible with C/NumPy
31//!
32//! # Example
33//! ```rust
34//! use cqlib_core::qis::Statevector;
35//!
36//! // Create Bell state |Φ+⟩ = (|00⟩ + |11⟩)/√2
37//! let mut sv = Statevector::new(2);
38//! sv.apply_h(0);
39//! sv.apply_cx(0, 1);
40//!
41//! let probs = sv.probabilities();
42//! assert!((probs[0] - 0.5).abs() < 1e-10);
43//! assert!((probs[3] - 0.5).abs() < 1e-10);
44//! ```
45
46use super::aligned_buffer::AlignedBuffer;
47use crate::circuit::Measurement;
48use crate::circuit::circuit_impl::Circuit;
49use crate::circuit::circuit_param::CircuitParam;
50use crate::circuit::error::CircuitError;
51use crate::circuit::gate::StandardGate;
52use crate::circuit::gate::directive::Directive;
53use crate::circuit::gate::instruction::Instruction;
54use crate::device::{ExecutionResult, Outcome};
55use crate::qis::error::QisError;
56use crate::qis::observable::Observable;
57use crate::qis::pauli::{PauliString, Phase};
58use num_complex::Complex64;
59use rand::{Rng, SeedableRng, rngs::SmallRng};
60use rayon::prelude::*;
61use smallvec::SmallVec;
62use std::collections::HashMap;
63use std::f64::consts::FRAC_1_SQRT_2;
64
65// Statevector gates reduce to: for each (α, β) pair separated by `dist`,
66//     new_α = u00·α + u01·β
67//     new_β = u10·α + u11·β
68// where u_ij are complex scalars fixed for the entire gate.
69//
70// LLVM auto-vectorizes *real* multiplications well (H gate, Z gate), but
71// struggles with complex×complex: `(ac−bd, ad+bc)` requires a cross-term
72// subtraction that breaks the sequential dependency pattern. The AVX2 path
73// below uses `_mm256_addsub_pd` to express this in 5 SIMD ops per 2 complex
74// numbers, delivering ~4× throughput vs. scalar on this hot loop.
75//
76// Memory layout (interleaved): [re₀, im₀, re₁, im₁, re₂, im₂, …]
77//
78// The trick (Goto–van de Geijn complex GEMV):
79//   Given scalar u = (u_re, u_im), vector of 2 complexes a = [a0, a1]:
80//     a_perm = permute(a, 0101b)     → [a0.im, a0.re, a1.im, a1.re]  (swap re/im)
81//     term1  = a  * broadcast(u_re)  → [a0.re·u_re, a0.im·u_re, …]
82//     term2  = a_perm * broadcast(u_im) → [a0.im·u_im, a0.re·u_im, …]
83//     result = addsub(term1, term2)  → even indices subtract, odd add:
84//              [a0.re·u_re − a0.im·u_im,   a0.im·u_re + a0.re·u_im, …]
85//              = [re(u·a0), im(u·a0), re(u·a1), im(u·a1)] ✓
86
87/// Computes `u·a` for two consecutive Complex64 values using AVX2.
88///
89/// # Safety
90/// Caller must ensure:
91/// - `avx2` + `fma` features are enabled at runtime.
92/// - `a_ptr` points to at least 4 aligned `f64` words (= 2 Complex64, = 32 bytes).
93#[cfg(target_arch = "x86_64")]
94#[target_feature(enable = "avx2,fma")]
95#[inline]
96unsafe fn cmul2_avx2(
97    a_ptr: *const f64,
98    u_re: std::arch::x86_64::__m256d,
99    u_im: std::arch::x86_64::__m256d,
100) -> std::arch::x86_64::__m256d {
101    use std::arch::x86_64::*;
102    // SAFETY: caller guarantees alignment and valid pointer.
103    unsafe {
104        let a = _mm256_load_pd(a_ptr); // [a0.re, a0.im, a1.re, a1.im]
105        let a_perm = _mm256_permute_pd(a, 0b0101); // [a0.im, a0.re, a1.im, a1.re]
106        let term1 = _mm256_mul_pd(a, u_re); // [a.re·u_re, a.im·u_re, …]
107        // addsub: even=subtract, odd=add → [re(u·a), im(u·a), …]
108        _mm256_addsub_pd(term1, _mm256_mul_pd(a_perm, u_im))
109    }
110}
111
112/// AVX2 kernel for a single-qubit gate on one chunk `[lower | upper]`.
113///
114/// Processes pairs (α, β) where lower[i] = α, upper[i] = β, applying:
115///   new_α = u00·α + u01·β
116///   new_β = u10·α + u11·β
117///
118/// Requires `dist` ≥ 2 (handled by the caller which falls back to scalar).
119///
120/// # Safety
121/// - `avx2` + `fma` features enabled.
122/// - `lower.as_ptr()` and `upper.as_ptr()` are 32-byte aligned (guaranteed by
123///   our `AlignedBuffer<Complex64>` and the chunk layout: `dist` is always a
124///   power of 2 ≥ 2, so the upper pointer is also 32-byte aligned).
125/// - `lower.len() == upper.len()` and both are multiples of 2.
126#[cfg(target_arch = "x86_64")]
127#[target_feature(enable = "avx2,fma")]
128unsafe fn sqg_kernel_avx2(
129    lower: &mut [Complex64],
130    upper: &mut [Complex64],
131    u00: Complex64,
132    u01: Complex64,
133    u10: Complex64,
134    u11: Complex64,
135) {
136    use std::arch::x86_64::*;
137    // SAFETY: avx2+fma enabled; caller guarantees alignment and length.
138    unsafe {
139        // Broadcast scalar components.
140        let v00_re = _mm256_set1_pd(u00.re);
141        let v00_im = _mm256_set1_pd(u00.im);
142        let v01_re = _mm256_set1_pd(u01.re);
143        let v01_im = _mm256_set1_pd(u01.im);
144        let v10_re = _mm256_set1_pd(u10.re);
145        let v10_im = _mm256_set1_pd(u10.im);
146        let v11_re = _mm256_set1_pd(u11.re);
147        let v11_im = _mm256_set1_pd(u11.im);
148
149        let n = lower.len(); // number of Complex64 elements
150        let mut i = 0;
151
152        // Main loop: 2 Complex64 per iteration (= 4 f64 = 1 × __m256d).
153        while i + 2 <= n {
154            let a_ptr = lower.as_ptr().add(i) as *const f64;
155            let b_ptr = upper.as_ptr().add(i) as *const f64;
156
157            // u00·α + u01·β
158            let ua = cmul2_avx2(a_ptr, v00_re, v00_im);
159            let ub = cmul2_avx2(b_ptr, v01_re, v01_im);
160            let new_alpha = _mm256_add_pd(ua, ub);
161
162            // u10·α + u11·β
163            let wa = cmul2_avx2(a_ptr, v10_re, v10_im);
164            let wb = cmul2_avx2(b_ptr, v11_re, v11_im);
165            let new_beta = _mm256_add_pd(wa, wb);
166
167            _mm256_store_pd(lower.as_mut_ptr().add(i) as *mut f64, new_alpha);
168            _mm256_store_pd(upper.as_mut_ptr().add(i) as *mut f64, new_beta);
169
170            i += 2;
171        }
172
173        // Scalar tail (handles dist == 1, i.e., qubit 0).
174        while i < n {
175            let a = lower[i];
176            let b = upper[i];
177            lower[i] = u00 * a + u01 * b;
178            upper[i] = u10 * a + u11 * b;
179            i += 1;
180        }
181    }
182}
183
184/// Scalar fallback for one chunk of a single-qubit gate.
185#[inline(always)]
186fn sqg_kernel_scalar(
187    lower: &mut [Complex64],
188    upper: &mut [Complex64],
189    u00: Complex64,
190    u01: Complex64,
191    u10: Complex64,
192    u11: Complex64,
193) {
194    lower
195        .iter_mut()
196        .zip(upper.iter_mut())
197        .for_each(|(alpha, beta)| {
198            let a = *alpha;
199            let b = *beta;
200            *alpha = u00 * a + u01 * b;
201            *beta = u10 * a + u11 * b;
202        });
203}
204
205/// Runtime-dispatched single-qubit gate kernel.
206///
207/// Dispatches to AVX2 (x86_64) or scalar. On AArch64, LLVM auto-vectorizes
208/// the scalar path using NEON, which is sufficient for interleaved Complex64.
209///
210/// # Safety
211/// `lower.len() == upper.len()` must hold; `lower.as_ptr()` must be 32-byte
212/// aligned (guaranteed by `AlignedBuffer` + power-of-2 `dist` offset).
213#[inline]
214#[allow(unreachable_code)] // scalar path unreachable when AVX2 always taken
215fn sqg_kernel(
216    lower: &mut [Complex64],
217    upper: &mut [Complex64],
218    u00: Complex64,
219    u01: Complex64,
220    u10: Complex64,
221    u11: Complex64,
222) {
223    #[cfg(target_arch = "x86_64")]
224    if is_x86_feature_detected!("avx2") && is_x86_feature_detected!("fma") {
225        // SAFETY: avx2+fma confirmed; AlignedBuffer guarantees 64-byte base
226        // alignment; dist is always a power of 2, so upper ptr is also aligned.
227        return unsafe { sqg_kernel_avx2(lower, upper, u00, u01, u10, u11) };
228    }
229    sqg_kernel_scalar(lower, upper, u00, u01, u10, u11);
230}
231
232/// Threshold for parallel execution (in number of qubits).
233///
234/// For small systems, the overhead of thread pool scheduling exceeds
235/// the performance gains from parallelization. Systems with fewer than
236/// 2^14 = 16384 amplitudes use serial execution.
237const PARALLEL_THRESHOLD: usize = 14;
238
239/// Conditionally executes serial or parallel iteration based on qubit count.
240///
241/// Switches between Rayon parallel iteration and standard serial iteration
242/// to avoid thread pool overhead for small quantum states.
243///
244/// # Arguments
245/// * `$num_qubits` - Number of qubits (determines execution mode)
246/// * `$data` - Mutable slice to process
247/// * `$chunk_size` - Size of each chunk for processing
248/// * `$body` - Closure to apply to each chunk
249///
250/// # Example
251/// ```rust,ignore
252/// with_maybe_par!(num_qubits, data, chunk_size, |chunk| {
253///     for elem in chunk { *elem *= 2.0; }
254/// });
255/// ```
256macro_rules! with_maybe_par {
257    ($num_qubits:expr, $data:expr, $chunk_size:expr, $body:expr) => {{
258        use rayon::prelude::*;
259        use std::iter::Iterator;
260
261        if $num_qubits < PARALLEL_THRESHOLD {
262            $data.chunks_exact_mut($chunk_size).for_each($body);
263        } else {
264            $data.par_chunks_exact_mut($chunk_size).for_each($body);
265        }
266    }};
267}
268
269/// Quantum statevector representing a pure quantum state.
270///
271/// A statevector describes the quantum state of an N-qubit system as a vector
272/// of 2^N complex amplitudes. The state |ψ⟩ = Σᵢ αᵢ|i⟩ is stored with αᵢ
273/// as the amplitude for basis state |i⟩ (i in binary representation).
274///
275/// # Memory Layout
276/// The `data` vector uses contiguous memory layout (compatible with C/NumPy),
277/// where the amplitude at index `i` corresponds to basis state |i⟩ with
278/// qubit indices mapping to bits from least significant (qubit 0) to most.
279///
280/// # Memory Layout
281///
282/// The `data` buffer uses a 64-byte aligned allocation so that SIMD kernels
283/// (AVX2, SSE2) can safely use aligned load/store instructions.
284///
285/// # Example
286/// ```rust
287/// use cqlib_core::qis::Statevector;
288///
289/// // Create a 2-qubit state in |00⟩
290/// let sv = Statevector::new(2);
291/// assert_eq!(sv.num_qubits, 2);
292/// assert_eq!(sv.data().len(), 4); // 2^2 amplitudes
293///
294/// // |00⟩ state: amplitude 1.0 at index 0, 0.0 elsewhere
295/// assert_eq!(sv.data()[0], num_complex::Complex64::new(1.0, 0.0));
296/// ```
297#[derive(Debug, Clone)]
298pub struct Statevector {
299    /// Complex amplitudes for each basis state. Length is 2^N where N = num_qubits.
300    /// 64-byte aligned for SIMD compatibility (see [`AlignedBuffer`]).
301    data: AlignedBuffer<Complex64>,
302    /// Circuit qubit identifiers mapped to storage positions.
303    qubit_map: super::QubitMap,
304    /// Number of qubits in the system.
305    pub num_qubits: usize,
306}
307
308impl Statevector {
309    /// Returns the complex amplitudes as a shared slice.
310    ///
311    /// Length is `2^num_qubits`. The amplitude at index `i` is the coefficient of basis
312    /// state |i⟩ in the computational basis.
313    ///
314    /// # Example
315    /// ```rust
316    /// use cqlib_core::qis::Statevector;
317    ///
318    /// let sv = Statevector::new(2); // |00⟩
319    /// assert_eq!(sv.data().len(), 4);
320    /// assert_eq!(sv.data()[0], num_complex::Complex64::new(1.0, 0.0));
321    /// ```
322    pub fn data(&self) -> &[Complex64] {
323        &self.data
324    }
325
326    /// Returns the complex amplitudes as a mutable slice.
327    ///
328    /// Mutating the slice can break normalization; callers are responsible for
329    /// preserving a valid statevector.
330    pub fn data_mut(&mut self) -> &mut [Complex64] {
331        &mut self.data
332    }
333
334    /// Creates a new statevector initialized to the |0...0⟩ state.
335    ///
336    /// The statevector represents the quantum state as a vector of 2^N complex amplitudes,
337    /// where N is the number of qubits. All amplitudes are initialized to zero except
338    /// the first element (|0...0⟩) which is set to 1.0.
339    ///
340    /// # Arguments
341    /// * `num_qubits` - Number of qubits in the system
342    ///
343    /// # Returns
344    /// A new `Statevector` instance in the ground state
345    ///
346    /// # Example
347    /// ```rust
348    /// use cqlib_core::qis::Statevector;
349    ///
350    /// let sv = Statevector::new(2); // |00⟩ state
351    /// assert_eq!(sv.num_qubits, 2);
352    /// assert_eq!(sv.data().len(), 4); // 2^2 amplitudes
353    /// ```
354    ///
355    /// # Panics
356    ///
357    /// Panics if the dimension is unrepresentable or allocation fails.
358    /// Use [`Self::try_new`] to receive an error instead.
359    pub fn new(num_qubits: usize) -> Self {
360        Self::try_new(num_qubits).expect("cannot allocate statevector")
361    }
362
363    /// Creates the ground state, returning an error for an unrepresentable
364    /// dimension or a failed allocation.
365    ///
366    /// # Errors
367    ///
368    /// Returns [`QisError::InvalidParameterValue`] for an unrepresentable
369    /// dimension, or [`QisError::UnsupportedOperation`] if allocation fails.
370    pub fn try_new(num_qubits: usize) -> Result<Self, QisError> {
371        let size = super::checked_state_len(num_qubits, false)?;
372        // 64-byte aligned allocation — zero-initialised (all amplitudes = 0+0i).
373        let mut data = AlignedBuffer::<Complex64>::try_new_zeroed(size).ok_or_else(|| {
374            QisError::UnsupportedOperation("statevector allocation failed".into())
375        })?;
376        data[0] = Complex64::new(1.0, 0.0); // |0...0⟩
377        Ok(Statevector {
378            data,
379            num_qubits,
380            qubit_map: None,
381        })
382    }
383
384    /// Creates a statevector from initial amplitudes with normalization check.
385    ///
386    /// # Arguments
387    /// * `num_qubits` - Number of qubits in the system
388    /// * `initial_state` - Vector of 2^N complex amplitudes
389    ///
390    /// # Errors
391    /// Returns an error if:
392    /// - `initial_state` length doesn't match 2^num_qubits
393    /// - State is not normalized (sum of probabilities ≠ 1.0 within a dimension-scaled tolerance)
394    ///
395    /// # Example
396    /// ```rust
397    /// use cqlib_core::qis::Statevector;
398    /// use num_complex::Complex64;
399    ///
400    /// // Create |+0⟩ = (|00⟩ + |10⟩)/√2
401    /// let amps = vec![
402    ///     Complex64::new(std::f64::consts::FRAC_1_SQRT_2, 0.0), // |00⟩
403    ///     Complex64::new(0.0, 0.0),                             // |01⟩
404    ///     Complex64::new(std::f64::consts::FRAC_1_SQRT_2, 0.0), // |10⟩
405    ///     Complex64::new(0.0, 0.0),                             // |11⟩
406    /// ];
407    /// let sv = Statevector::from_state(2, amps);
408    /// ```
409    pub fn from_state(num_qubits: usize, initial_state: Vec<Complex64>) -> Result<Self, QisError> {
410        let size = super::checked_state_len(num_qubits, false)?;
411        if initial_state.len() != size {
412            return Err(QisError::InvalidStateDimension(initial_state.len()));
413        }
414        // Verify normalization. The tolerance scales with vector length because
415        // summing many amplitudes accumulates floating-point roundoff.
416        let norm: f64 = initial_state.iter().map(|c| c.norm_sqr()).sum();
417        let tolerance = 1e-10 * (size.max(1) as f64);
418        if !norm.is_finite() || (norm - 1.0).abs() >= tolerance {
419            return Err(QisError::NotNormalized);
420        }
421
422        // Copy caller-provided Vec into 64-byte aligned buffer.
423        let mut data = AlignedBuffer::<Complex64>::try_new_zeroed(size).ok_or_else(|| {
424            QisError::UnsupportedOperation("statevector allocation failed".into())
425        })?;
426        data.as_mut_slice().copy_from_slice(&initial_state);
427        Ok(Statevector {
428            data,
429            num_qubits,
430            qubit_map: None,
431        })
432    }
433
434    /// Constructs a statevector by simulating a quantum circuit.
435    ///
436    /// Executes the circuit gates sequentially to evolve the initial |0...0⟩ state.
437    /// Complex gates are decomposed before execution.
438    ///
439    /// # Arguments
440    /// * `circuit` - The quantum circuit to simulate
441    ///
442    /// # Returns
443    /// * `Ok(Statevector)` - The resulting state after circuit execution
444    /// * `Err(QisError)` - If the circuit contains unsupported operations
445    ///   (control flow gates, symbolic parameters) or decomposition fails
446    ///
447    /// # Supported Instructions
448    /// - Standard single and multi-qubit gates
449    /// - Controlled gates (CX, CY, CZ, CRX, CRY, CRZ)
450    /// - Multi-controlled gates (CCX/Toffoli)
451    /// - Unitary gates with matrix representation
452    /// - Barriers and delays (ignored)
453    ///
454    /// # Example
455    /// ```rust
456    /// use cqlib_core::circuit::{Circuit, Qubit};
457    /// use cqlib_core::qis::Statevector;
458    ///
459    /// // Create Bell state: |Φ+⟩ = (|00⟩ + |11⟩)/√2
460    /// let mut circuit = Circuit::new(2);
461    /// circuit.h(Qubit::new(0)).unwrap();
462    /// circuit.cx(Qubit::new(0), Qubit::new(1)).unwrap();
463    ///
464    /// let sv = Statevector::from_circuit(&circuit).unwrap();
465    /// ```
466    pub fn from_circuit(circuit: &Circuit) -> Result<Self, QisError> {
467        let mut sv = Statevector::try_new(circuit.num_qubits())?;
468        sv.apply_circuit(circuit)?;
469        Ok(sv)
470    }
471
472    /// Applies a quantum circuit to this statevector in-place.
473    ///
474    /// Once bound to circuit qubit IDs, subsequent circuits must have the same
475    /// IDs in the same order. The mapping is committed only on success; gates
476    /// completed before an error are not rolled back.
477    ///
478    /// As in `from_circuit`, terminal measurements are ignored. A measured qubit
479    /// used by a later gate or Reset is rejected before the state is changed.
480    /// Independent gates, repeated measurements, Barrier, Delay, and Store are allowed.
481    ///
482    /// The circuit is first decomposed into basic gates via [`Circuit::decompose`],
483    /// then each operation is applied sequentially to the current state.
484    ///
485    /// # Arguments
486    /// * `circuit` - The circuit to apply. Must have the same number of qubits
487    ///   as this statevector.
488    ///
489    /// # Returns
490    /// * `Ok(())` - If all operations were applied successfully
491    /// * `Err(QisError)` - If the circuit cannot be simulated
492    ///
493    /// # Errors
494    /// - [`QisError::InvalidStateDimension`] if `circuit.num_qubits() != self.num_qubits`
495    /// - [`QisError::UnsupportedOperation`] if the circuit contains classical control flow
496    /// - [`QisError::CircuitError`] if a gate lacks a matrix representation, contains
497    ///   unresolved symbolic parameters, or a referenced qubit is not found
498    ///
499    /// # Supported Instructions
500    /// - Standard single and multi-qubit gates
501    /// - Controlled gates (CX, CY, CZ, CRX, CRY, CRZ)
502    /// - Multi-controlled gates (CCX / Toffoli)
503    /// - Unitary gates with an explicit matrix representation
504    /// - `Reset`
505    /// - Barriers and delays (ignored)
506    /// - Measurement output declarations from `Circuit::measure*` (ignored)
507    pub fn apply_circuit(&mut self, circuit: &Circuit) -> Result<(), QisError> {
508        if circuit.num_qubits() != self.num_qubits {
509            return Err(QisError::InvalidStateDimension(circuit.num_qubits()));
510        }
511        if circuit
512            .operations()
513            .iter()
514            .any(|op| matches!(op.instruction, Instruction::ClassicalControl(_)))
515        {
516            return Err(QisError::UnsupportedOperation(
517                "classical control flow is not supported in statevector simulation".to_string(),
518            ));
519        }
520        // Decompose circuit to basic gates
521        let circuit = circuit.decompose()?;
522        super::validate_terminal_measurements(&circuit)?;
523        let sv = self;
524
525        let qubit_map = super::prepare_qubit_map(&circuit.qubits(), &sv.qubit_map)?;
526
527        // Precompute all parameter values
528        let parameter_values: Vec<Option<f64>> = circuit
529            .parameters()
530            .iter()
531            .map(|p| p.evaluate(&None).ok())
532            .collect();
533
534        for op in circuit.operations() {
535            // Resolve parameters using precomputed values
536            let params: Vec<f64> = op
537                .params
538                .iter()
539                .map(|p| match p {
540                    CircuitParam::Fixed(v) => Ok(*v),
541                    CircuitParam::Index(idx) => parameter_values
542                        .get(*idx as usize)
543                        .copied()
544                        .flatten()
545                        .ok_or(QisError::CircuitError(CircuitError::SymbolicParameterError)),
546                })
547                .collect::<Result<Vec<_>, QisError>>()?;
548
549            // Get storage positions
550            let qubit_indices: Result<Vec<usize>, QisError> =
551                op.qubits
552                    .iter()
553                    .map(|q| {
554                        qubit_map.get(q).copied().ok_or_else(|| {
555                            QisError::CircuitError(CircuitError::QubitNotFound(q.id()))
556                        })
557                    })
558                    .collect();
559            let qubit_indices = qubit_indices?;
560
561            match &op.instruction {
562                Instruction::Standard(gate) => {
563                    sv.apply_standard_gate(*gate, &qubit_indices, &params)?;
564                }
565                Instruction::McGate(mc_gate) => {
566                    let num_controls = mc_gate.num_ctrl_qubits();
567                    let base_gate = mc_gate.base_gate();
568
569                    if num_controls == 1 {
570                        // Single-controlled gate
571                        let control = qubit_indices[0];
572                        let target = qubit_indices[1];
573                        match base_gate {
574                            StandardGate::X => sv.apply_cx(control, target)?,
575                            StandardGate::Y => sv.apply_cy(control, target)?,
576                            StandardGate::Z => sv.apply_cz(control, target)?,
577                            StandardGate::RX => {
578                                let theta = params[0];
579                                sv.apply_crx(control, target, theta)?;
580                            }
581                            StandardGate::RY => {
582                                let theta = params[0];
583                                sv.apply_cry(control, target, theta)?;
584                            }
585                            StandardGate::RZ => {
586                                let theta = params[0];
587                                sv.apply_crz(control, target, theta)?;
588                            }
589                            _ => {
590                                let matrix = mc_gate.matrix(&params).map_err(|_| {
591                                    QisError::CircuitError(CircuitError::NoMatrixRepresentation)
592                                })?;
593                                sv.apply_unitary_gate(&qubit_indices, &matrix)?;
594                            }
595                        }
596                    } else if num_controls == 2 && *base_gate == StandardGate::X {
597                        // Toffoli gate
598                        let c0 = qubit_indices[0];
599                        let c1 = qubit_indices[1];
600                        let target = qubit_indices[2];
601                        sv.apply_ccx(c0, c1, target)?;
602                    } else {
603                        let matrix = mc_gate.matrix(&params).map_err(|_| {
604                            QisError::CircuitError(CircuitError::NoMatrixRepresentation)
605                        })?;
606                        sv.apply_unitary_gate(&qubit_indices, &matrix)?;
607                    }
608                }
609                Instruction::UnitaryGate(u_gate) => {
610                    if let Some(matrix) = u_gate.matrix() {
611                        sv.apply_unitary_gate(&qubit_indices, matrix)?;
612                    } else {
613                        return Err(QisError::CircuitError(CircuitError::NoMatrixRepresentation));
614                    }
615                }
616                Instruction::CircuitGate(_) => {
617                    return Err(QisError::CircuitError(CircuitError::InvalidOperation(
618                        "CircuitGate should have been decomposed".to_string(),
619                    )));
620                }
621                Instruction::Directive(directive) => match directive {
622                    Directive::Barrier => {}
623                    Directive::Measure => {
624                        return Err(QisError::UnsupportedOperation(
625                            "legacy Measure directive is not supported by statevector circuit evolution; use Circuit::measure as an output declaration".to_string(),
626                        ));
627                    }
628                    Directive::Reset => {
629                        let qubit = qubit_indices[0];
630                        sv.reset(qubit)?;
631                    }
632                },
633                Instruction::Delay => {
634                    // Ignore delays
635                    continue;
636                }
637                Instruction::ClassicalControl(_) => {
638                    return Err(QisError::UnsupportedOperation(
639                        "classical control flow is not supported in statevector simulation"
640                            .to_string(),
641                    ));
642                }
643                Instruction::ClassicalData(_) => {}
644            }
645        }
646
647        sv.qubit_map = Some(qubit_map);
648        Ok(())
649    }
650
651    /// Applies a standard gate to the statevector.
652    ///
653    /// Internal dispatcher that routes to specialized implementations
654    /// based on gate type.
655    ///
656    /// # Arguments
657    /// * `gate` - The standard gate to apply
658    /// * `qubits` - Target qubit indices
659    /// * `params` - Gate parameters (for parameterized gates)
660    pub fn apply_standard_gate(
661        &mut self,
662        gate: StandardGate,
663        qubits: &[usize],
664        params: &[f64],
665    ) -> Result<(), QisError> {
666        if qubits.len() != gate.num_qubits() {
667            return Err(QisError::InvalidParameterValue(format!(
668                "Gate {:?} requires {} qubits, got {}",
669                gate,
670                gate.num_qubits(),
671                qubits.len()
672            )));
673        }
674        if params.len() != gate.num_params() {
675            return Err(QisError::InvalidParameterValue(format!(
676                "Gate {:?} requires {} parameters, got {}",
677                gate,
678                gate.num_params(),
679                params.len()
680            )));
681        }
682        match gate {
683            // Single-qubit gates
684            StandardGate::I => { /* no-op */ }
685            StandardGate::X => self.apply_x(qubits[0])?,
686            StandardGate::Y => self.apply_y(qubits[0])?,
687            StandardGate::Z => self.apply_z(qubits[0])?,
688            StandardGate::H => self.apply_h(qubits[0])?,
689            StandardGate::S => self.apply_s(qubits[0])?,
690            StandardGate::SDG => self.apply_sdg(qubits[0])?,
691            StandardGate::T => self.apply_t(qubits[0])?,
692            StandardGate::TDG => self.apply_tdg(qubits[0])?,
693            StandardGate::RX => self.apply_rx(qubits[0], params[0])?,
694            StandardGate::RY => self.apply_ry(qubits[0], params[0])?,
695            StandardGate::RZ => self.apply_rz(qubits[0], params[0])?,
696            StandardGate::Phase => self.apply_phase(qubits[0], params[0])?,
697            StandardGate::X2P => self.apply_x2p(qubits[0])?,
698            StandardGate::X2M => self.apply_x2m(qubits[0])?,
699            StandardGate::Y2P => self.apply_y2p(qubits[0])?,
700            StandardGate::Y2M => self.apply_y2m(qubits[0])?,
701            StandardGate::RXY => self.apply_rxy(qubits[0], params[0], params[1])?,
702            StandardGate::XY => self.apply_xy(qubits[0], params[0])?,
703            StandardGate::XY2P => self.apply_xy2p(qubits[0], params[0])?,
704            StandardGate::XY2M => self.apply_xy2m(qubits[0], params[0])?,
705            StandardGate::U => {
706                self.apply_u(qubits[0], params[0], params[1], params[2])?;
707            }
708            StandardGate::GPhase => self.apply_gphase(params[0])?,
709
710            // Two-qubit gates
711            StandardGate::CX => self.apply_cx(qubits[0], qubits[1])?,
712            StandardGate::CY => self.apply_cy(qubits[0], qubits[1])?,
713            StandardGate::CZ => self.apply_cz(qubits[0], qubits[1])?,
714            StandardGate::SWAP => self.apply_swap(qubits[0], qubits[1])?,
715            StandardGate::RXX => self.apply_rxx(qubits[0], qubits[1], params[0])?,
716            StandardGate::RYY => self.apply_ryy(qubits[0], qubits[1], params[0])?,
717            StandardGate::RZZ => self.apply_rzz(qubits[0], qubits[1], params[0])?,
718            StandardGate::RZX => self.apply_rzx(qubits[0], qubits[1], params[0])?,
719
720            // Controlled rotation gates
721            StandardGate::CRX => self.apply_crx(qubits[0], qubits[1], params[0])?,
722            StandardGate::CRY => self.apply_cry(qubits[0], qubits[1], params[0])?,
723            StandardGate::CRZ => self.apply_crz(qubits[0], qubits[1], params[0])?,
724
725            // Three-qubit gates
726            StandardGate::CCX => self.apply_ccx(qubits[0], qubits[1], qubits[2])?,
727
728            // Simulator gates
729            StandardGate::FSIM => self.apply_fsim(qubits[0], qubits[1], params[0], params[1])?,
730        }
731        Ok(())
732    }
733
734    /// Computes the measurement probability distribution over all basis states.
735    ///
736    /// Returns P(|i⟩) = |αᵢ|² for each basis state |i⟩.
737    /// Uses parallel iteration for large statevectors.
738    ///
739    /// # Returns
740    /// Vector of probabilities summing to 1.0 (within floating-point precision).
741    ///
742    /// # Example
743    /// ```rust
744    /// use cqlib_core::qis::Statevector;
745    ///
746    /// let mut sv = Statevector::new(1);
747    /// sv.apply_h(0);
748    /// let probs = sv.probabilities();
749    /// // |+⟩ state: P(|0⟩) = P(|1⟩) = 0.5
750    /// assert!((probs[0] - 0.5).abs() < 1e-10);
751    /// assert!((probs[1] - 0.5).abs() < 1e-10);
752    /// ```
753    pub fn probabilities(&self) -> Vec<f64> {
754        if self.num_qubits < PARALLEL_THRESHOLD {
755            self.data.iter().map(|c| c.norm_sqr()).collect()
756        } else {
757            self.data.par_iter().map(|c| c.norm_sqr()).collect()
758        }
759    }
760
761    /// Validates that a qubit index is within bounds.
762    fn validate_qubit(&self, qubit: usize) -> Result<(), QisError> {
763        if self.num_qubits == 0 {
764            return Err(QisError::InvalidParameterValue(
765                "no valid qubit indices in a zero-qubit system".to_string(),
766            ));
767        }
768        if qubit >= self.num_qubits {
769            return Err(QisError::IndexOutOfBounds {
770                index: qubit,
771                max: self.num_qubits.saturating_sub(1),
772            });
773        }
774        Ok(())
775    }
776
777    /// Validates that two qubit indices are within bounds and distinct.
778    fn validate_two_qubits(&self, q0: usize, q1: usize) -> Result<(), QisError> {
779        self.validate_qubit(q0)?;
780        self.validate_qubit(q1)?;
781        if q0 == q1 {
782            return Err(QisError::InvalidParameterValue(
783                "Qubit indices must be distinct".to_string(),
784            ));
785        }
786        Ok(())
787    }
788
789    /// Validates that all qubit indices are within bounds and distinct.
790    fn validate_qubits(&self, qubits: &[usize]) -> Result<(), QisError> {
791        for (i, &q) in qubits.iter().enumerate() {
792            self.validate_qubit(q)?;
793            for &other in &qubits[..i] {
794                if q == other {
795                    return Err(QisError::InvalidParameterValue(format!(
796                        "Duplicate qubit index {} in gate operation",
797                        q
798                    )));
799                }
800            }
801        }
802        Ok(())
803    }
804
805    /// Applies an arbitrary single-qubit gate.
806    ///
807    /// General-purpose method for applying any 2x2 unitary matrix.
808    /// For standard gates, prefer specialized methods (apply_h, apply_x, etc.)
809    /// which have optimized implementations.
810    ///
811    /// # Arguments
812    /// * `qubit` - Target qubit index
813    /// * `matrix` - 2x2 unitary matrix [[u00, u01], [u10, u11]]
814    ///
815    /// # Errors
816    /// Returns [`QisError::IndexOutOfBounds`] if `qubit` is out of bounds.
817    ///
818    /// # Example
819    /// ```rust,ignore
820    /// use cqlib_core::qis::Statevector;
821    /// use num_complex::Complex64;
822    ///
823    /// let mut sv = Statevector::new(1);
824    /// // Apply a custom rotation
825    /// let matrix = [
826    ///     [Complex64::new(0.0, 0.0), Complex64::new(1.0, 0.0)],
827    ///     [Complex64::new(1.0, 0.0), Complex64::new(0.0, 0.0)],
828    /// ];
829    /// sv.apply_single_qubit_gate(0, matrix); // Equivalent to X gate
830    /// ```
831    pub fn apply_single_qubit_gate(
832        &mut self,
833        qubit: usize,
834        matrix: [[Complex64; 2]; 2],
835    ) -> Result<(), QisError> {
836        self.validate_qubit(qubit)?;
837        let dist = 1 << qubit;
838
839        // Preload matrix elements to registers, reducing memory access in loop
840        let u00 = matrix[0][0];
841        let u01 = matrix[0][1];
842        let u10 = matrix[1][0];
843        let u11 = matrix[1][1];
844
845        // Dispatch: AVX2 SIMD on x86_64 (2 Complex64/cycle), scalar elsewhere.
846        // The SIMD path uses `_mm256_addsub_pd` to compute (ac−bd, ad+bc) for
847        // two complex pairs per 256-bit register, bypassing LLVM's inability
848        // to auto-vectorize interleaved re/im complex multiplication.
849        let kernel = |chunk: &mut [Complex64]| {
850            let (lower, upper) = chunk.split_at_mut(dist);
851            sqg_kernel(lower, upper, u00, u01, u10, u11);
852        };
853
854        with_maybe_par!(self.num_qubits, self.data, 2 * dist, kernel);
855        Ok(())
856    }
857
858    /// Apply a general two-qubit gate.
859    ///
860    /// # Arguments
861    /// * `q0` - First qubit index (logical)
862    /// * `q1` - Second qubit index (logical)
863    /// * `matrix` - 4x4 unitary matrix in the standard computational basis
864    ///   ordered as |00⟩, |01⟩, |10⟩, |11⟩ where the bits are (q0, q1).
865    ///   That is, matrix[i][j] is the amplitude of |i⟩ when input is |j⟩,
866    ///   with i = q0*2 + q1 (binary: q0 is MSB, q1 is LSB).
867    ///
868    /// # Example - CNOT with q0 as control and q1 as target:
869    /// The CNOT gate flips the target when control is |1⟩:
870    /// |00⟩ -> |00⟩, |01⟩ -> |01⟩, |10⟩ -> |11⟩, |11⟩ -> |10⟩
871    ///
872    /// This corresponds to the matrix (rows/cols ordered as |00⟩, |01⟩, |10⟩, |11⟩):
873    /// ```text
874    /// [[1, 0, 0, 0],
875    ///  [0, 1, 0, 0],
876    ///  [0, 0, 0, 1],
877    ///  [0, 0, 1, 0]]
878    /// ```
879    pub fn apply_two_qubit_gate(
880        &mut self,
881        q0: usize,
882        q1: usize,
883        matrix: [[Complex64; 4]; 4],
884    ) -> Result<(), QisError> {
885        self.validate_two_qubits(q0, q1)?;
886
887        // 1. Identify physical high/low bit positions in memory
888        // q_max determines the outer parallel chunk size
889        // q_min determines the inner serial stride
890        let (q_min, q_max) = if q0 < q1 { (q0, q1) } else { (q1, q0) };
891        let dist_max = 1 << q_max;
892        let dist_min = 1 << q_min;
893
894        // 2. Preload matrix elements to registers, avoiding array access in hot loop
895        // Naming: m_row_col corresponds to logical state |q0 q1>
896        let m00 = matrix[0][0];
897        let m01 = matrix[0][1];
898        let m02 = matrix[0][2];
899        let m03 = matrix[0][3];
900        let m10 = matrix[1][0];
901        let m11 = matrix[1][1];
902        let m12 = matrix[1][2];
903        let m13 = matrix[1][3];
904        let m20 = matrix[2][0];
905        let m21 = matrix[2][1];
906        let m22 = matrix[2][2];
907        let m23 = matrix[2][3];
908        let m30 = matrix[3][0];
909        let m31 = matrix[3][1];
910        let m32 = matrix[3][2];
911        let m33 = matrix[3][3];
912
913        // 3. Choose serial or parallel based on qubit count
914        // Split statevector into chunks by max_qubit
915        // Each chunk contains 2 * dist_max elements
916        // Layout: [ ...dist_max (q_max=0)... | ...dist_max (q_max=1)... ]
917        let kernel = |chunk: &mut [Complex64]| {
918            // Split chunk into q_max=0 and q_max=1 parts
919            let (part0, part1) = chunk.split_at_mut(dist_max);
920
921            // 4. Inner serial traversal
922            // Further split by q_min within each part
923            // Use zip to traverse both parts simultaneously for optimal bounds check elision
924            part0
925                .chunks_exact_mut(2 * dist_min)
926                .zip(part1.chunks_exact_mut(2 * dist_min))
927                .for_each(|(sub_chunk0, sub_chunk1)| {
928                    // sub_chunk0: q_max=0 region
929                    // sub_chunk1: q_max=1 region
930
931                    // Further split by q_min
932                    let (v00_slice, v01_slice) = sub_chunk0.split_at_mut(dist_min);
933                    let (v10_slice, v11_slice) = sub_chunk1.split_at_mut(dist_min);
934
935                    // Now we have four slices corresponding to physical bit states:
936                    // v00_slice: q_max=0, q_min=0
937                    // v01_slice: q_max=0, q_min=1
938                    // v10_slice: q_max=1, q_min=0
939                    // v11_slice: q_max=1, q_min=1
940
941                    // 5. Core computation loop
942                    // Use iterator zip to avoid index calculations, enabling SIMD
943                    for (((c00, c01), c10), c11) in v00_slice
944                        .iter_mut()
945                        .zip(v01_slice.iter_mut())
946                        .zip(v10_slice.iter_mut())
947                        .zip(v11_slice.iter_mut())
948                    {
949                        let a = *c00; // Phys: 00
950                        let b = *c01; // Phys: 01
951                        let c = *c10; // Phys: 10
952                        let d = *c11; // Phys: 11
953
954                        // 6. Logical mapping: determine input vector positions based on q0 vs q1
955                        // logical_amp_00 corresponds to |q0=0, q1=0>
956                        // logical_amp_01 corresponds to |q0=0, q1=1> ...
957                        let (in0, in1, in2, in3) = if q0 < q1 {
958                            // q0=min, q1=max
959                            // Phys 00 (min=0, max=0) -> Log 00
960                            // Phys 01 (min=1, max=0) -> Log 10 (q0=1, q1=0)
961                            // Phys 10 (min=0, max=1) -> Log 01 (q0=0, q1=1)
962                            // Phys 11 (min=1, max=1) -> Log 11
963                            (a, c, b, d)
964                        } else {
965                            // q0=max, q1=min
966                            // Phys 00 (min=0, max=0) -> Log 00
967                            // Phys 01 (min=1, max=0) -> Log 01 (q0=0, q1=1)
968                            // Phys 10 (min=0, max=1) -> Log 10 (q0=1, q1=0)
969                            // Phys 11 (min=1, max=1) -> Log 11
970                            (a, b, c, d)
971                        };
972
973                        // Matrix multiplication
974                        let out0 = m00 * in0 + m01 * in1 + m02 * in2 + m03 * in3;
975                        let out1 = m10 * in0 + m11 * in1 + m12 * in2 + m13 * in3;
976                        let out2 = m20 * in0 + m21 * in1 + m22 * in2 + m23 * in3;
977                        let out3 = m30 * in0 + m31 * in1 + m32 * in2 + m33 * in3;
978
979                        // Write results (inverse mapping)
980                        if q0 < q1 {
981                            *c00 = out0;
982                            *c10 = out1; // Map Log 01 to Phys 10 (Log 01: q0=0, q1=1 => min=0, max=1 => Phys 10)
983                            *c01 = out2; // Map Log 10 to Phys 01 (q0=1, q1=0 => min=1, max=0 => Phys 01)
984                            *c11 = out3;
985                        } else {
986                            *c00 = out0;
987                            *c01 = out1;
988                            *c10 = out2;
989                            *c11 = out3;
990                        }
991                    }
992                });
993        };
994        with_maybe_par!(self.num_qubits, self.data, 2 * dist_max, kernel);
995        Ok(())
996    }
997
998    /// Applies an arbitrary n-qubit unitary gate.
999    ///
1000    /// General-purpose method for applying any 2^n × 2^n unitary matrix.
1001    /// Uses block-iteration with bit-manipulation for efficient index calculation.
1002    /// Supports parallel execution for large statevectors.
1003    ///
1004    /// For standard gates, prefer specialized methods which are more optimized.
1005    ///
1006    /// # Arguments
1007    /// * `qubits` - Slice of qubit indices the gate acts on (logical order)
1008    /// * `matrix` - The unitary matrix as a 2^n × 2^n ndarray
1009    ///
1010    /// # Errors
1011    /// Returns an error if:
1012    /// - Qubit indices are out of bounds
1013    /// - Qubit indices contain duplicates
1014    /// - Matrix dimensions don't match 2^n × 2^n
1015    ///
1016    /// # Example
1017    /// ```rust,ignore
1018    /// use cqlib_core::qis::Statevector;
1019    /// use ndarray::Array2;
1020    /// use num_complex::Complex64;
1021    ///
1022    /// let mut sv = Statevector::new(2);
1023    /// // Create SWAP gate matrix
1024    /// let swap = Array2::from_shape_vec(
1025    ///     (4, 4),
1026    ///     vec![
1027    ///         Complex64::new(1.0, 0.0), Complex64::new(0.0, 0.0),
1028    ///         Complex64::new(0.0, 0.0), Complex64::new(0.0, 0.0),
1029    ///         Complex64::new(0.0, 0.0), Complex64::new(0.0, 0.0),
1030    ///         Complex64::new(1.0, 0.0), Complex64::new(0.0, 0.0),
1031    ///         Complex64::new(0.0, 0.0), Complex64::new(1.0, 0.0),
1032    ///         Complex64::new(0.0, 0.0), Complex64::new(0.0, 0.0),
1033    ///         Complex64::new(0.0, 0.0), Complex64::new(0.0, 0.0),
1034    ///         Complex64::new(0.0, 0.0), Complex64::new(1.0, 0.0),
1035    ///     ]
1036    /// ).unwrap();
1037    /// sv.apply_unitary_gate(&[0, 1], &swap);
1038    /// ```
1039    pub fn apply_unitary_gate(
1040        &mut self,
1041        qubits: &[usize],
1042        matrix: &ndarray::Array2<Complex64>,
1043    ) -> Result<(), QisError> {
1044        let num_target_qubits = qubits.len();
1045        let gate_dim = 1 << num_target_qubits;
1046
1047        // Validate inputs
1048        if num_target_qubits == 0 || num_target_qubits > self.num_qubits {
1049            return Err(QisError::InvalidParameterValue(format!(
1050                "Invalid number of target qubits: {} (must be 1 to {})",
1051                num_target_qubits, self.num_qubits
1052            )));
1053        }
1054
1055        for (i, &q) in qubits.iter().enumerate() {
1056            self.validate_qubit(q)?;
1057            for &other in &qubits[..i] {
1058                if q == other {
1059                    return Err(QisError::InvalidParameterValue(format!(
1060                        "Duplicate qubit index {} in apply_unitary",
1061                        q
1062                    )));
1063                }
1064            }
1065        }
1066
1067        if matrix.shape() != [gate_dim, gate_dim] {
1068            return Err(QisError::InvalidParameterValue(format!(
1069                "Matrix dimensions {}x{} don't match expected {}x{} for {} qubits",
1070                matrix.nrows(),
1071                matrix.ncols(),
1072                gate_dim,
1073                gate_dim,
1074                num_target_qubits
1075            )));
1076        }
1077
1078        // Precompute gate offset mapping: gate_index -> physical_offset
1079        let mut gate_offsets: Vec<usize> = vec![0; gate_dim];
1080        #[allow(clippy::needless_range_loop)]
1081        for gate_idx in 0..gate_dim {
1082            let mut offset = 0;
1083            for (i, &qubit) in qubits.iter().enumerate() {
1084                // Big-endian mapping: qubits[0] corresponds to the MSB of the gate index,
1085                // qubits[last] corresponds to the LSB. This aligns with standard tensor
1086                // product order where the first qubit in the list is the most significant.
1087                let bit_pos = num_target_qubits - 1 - i;
1088                if (gate_idx >> bit_pos) & 1 == 1 {
1089                    offset |= 1 << qubit;
1090                }
1091            }
1092            gate_offsets[gate_idx] = offset;
1093        }
1094
1095        // Sort qubits for efficient index calculation
1096        let mut sorted_qubits: Vec<usize> = qubits.to_vec();
1097        sorted_qubits.sort_unstable();
1098
1099        // Calculate chunk size based on max qubit
1100        let max_qubit = *sorted_qubits.last().unwrap();
1101        let chunk_size = 1 << (max_qubit + 1);
1102
1103        // Create matrix view for cache efficiency
1104        let matrix_rows: Vec<Vec<Complex64>> = (0..gate_dim)
1105            .map(|r| (0..gate_dim).map(|c| matrix[[r, c]]).collect())
1106            .collect();
1107
1108        // Process chunks
1109        let kernel = |chunk: &mut [Complex64]| {
1110            let mut input_buf = vec![Complex64::default(); gate_dim];
1111            let mut output_buf = vec![Complex64::default(); gate_dim];
1112
1113            // Number of blocks in this chunk
1114            let num_blocks = chunk.len() >> num_target_qubits;
1115
1116            for block_idx in 0..num_blocks {
1117                // Expand compressed index to physical address
1118                let mut base = block_idx;
1119                for &q in &sorted_qubits {
1120                    let mask = (1 << q) - 1;
1121                    let left = (base & !mask) << 1;
1122                    let right = base & mask;
1123                    base = left | right;
1124                }
1125
1126                // Load input amplitudes
1127                for (gate_idx, &offset) in gate_offsets.iter().enumerate() {
1128                    input_buf[gate_idx] = chunk[base + offset];
1129                }
1130
1131                // Apply matrix multiplication
1132                for row in 0..gate_dim {
1133                    let mut sum = Complex64::default();
1134                    for (matrix_value, input_value) in matrix_rows[row].iter().zip(input_buf.iter())
1135                    {
1136                        sum += matrix_value * input_value;
1137                    }
1138                    output_buf[row] = sum;
1139                }
1140
1141                // Store output amplitudes
1142                for (gate_idx, &offset) in gate_offsets.iter().enumerate() {
1143                    chunk[base + offset] = output_buf[gate_idx];
1144                }
1145            }
1146        };
1147
1148        with_maybe_par!(self.num_qubits, self.data, chunk_size, kernel);
1149        Ok(())
1150    }
1151
1152    /// Applies a native Pauli-string rotation in place.
1153    ///
1154    /// The operation is
1155    ///
1156    /// `exp(-i * theta / 2 * P)`
1157    ///
1158    /// where `P` is a Hermitian [`PauliString`]. This fused kernel avoids
1159    /// decomposing a Pauli exponential into basis changes, a CNOT ladder, and
1160    /// an RZ gate. It is intended for high-frequency variational workloads.
1161    ///
1162    /// # Errors
1163    ///
1164    /// Returns an error if the Pauli string has a different number of qubits or
1165    /// carries an imaginary global phase (and is therefore not Hermitian).
1166    pub fn apply_pauli_rotation(
1167        &mut self,
1168        pauli: &PauliString,
1169        theta: f64,
1170    ) -> Result<(), QisError> {
1171        if pauli.num_qubits != self.num_qubits {
1172            return Err(QisError::QubitMismatch {
1173                expected: self.num_qubits,
1174                actual: pauli.num_qubits,
1175            });
1176        }
1177        if matches!(pauli.phase, Phase::I | Phase::MinusI) {
1178            return Err(QisError::NotHermitian);
1179        }
1180        if theta.abs() < 1e-15 {
1181            return Ok(());
1182        }
1183
1184        let x_mask = pauli.x_mask();
1185        let z_mask = pauli.z_mask();
1186        let base_factor = pauli.phase.to_complex() * pauli.y_phase();
1187        let half = theta * 0.5;
1188        let cosine = half.cos();
1189        let minus_i_sine = Complex64::new(0.0, -half.sin());
1190
1191        #[inline(always)]
1192        fn source_phase(index: usize, z_mask: usize, base: Complex64) -> Complex64 {
1193            if (index & z_mask).count_ones() & 1 == 0 {
1194                base
1195            } else {
1196                -base
1197            }
1198        }
1199
1200        if x_mask == 0 {
1201            if self.num_qubits < PARALLEL_THRESHOLD {
1202                self.data
1203                    .iter_mut()
1204                    .enumerate()
1205                    .for_each(|(index, amplitude)| {
1206                        let phase = source_phase(index, z_mask, base_factor);
1207                        *amplitude *= Complex64::new(cosine, 0.0) + minus_i_sine * phase;
1208                    });
1209            } else {
1210                self.data
1211                    .par_iter_mut()
1212                    .enumerate()
1213                    .for_each(|(index, amplitude)| {
1214                        let phase = source_phase(index, z_mask, base_factor);
1215                        *amplitude *= Complex64::new(cosine, 0.0) + minus_i_sine * phase;
1216                    });
1217            }
1218            return Ok(());
1219        }
1220
1221        // Split on the highest flipped bit. Every Pauli pair then contains one
1222        // amplitude in the lower half and one in the upper half of a chunk, so
1223        // disjoint pairs can be updated safely without an auxiliary statevector.
1224        let highest_bit = usize::BITS as usize - 1 - x_mask.leading_zeros() as usize;
1225        let dist = 1usize << highest_bit;
1226        let chunk_size = 2 * dist;
1227        let low_flip_mask = x_mask ^ dist;
1228
1229        let apply_chunk = |chunk_index: usize, chunk: &mut [Complex64]| {
1230            let base_index = chunk_index * chunk_size;
1231            for lower_index in 0..dist {
1232                let upper_index = dist + (lower_index ^ low_flip_mask);
1233                let source_lower = base_index + lower_index;
1234                let source_upper = base_index + upper_index;
1235                let lower_phase = source_phase(source_lower, z_mask, base_factor);
1236                let upper_phase = source_phase(source_upper, z_mask, base_factor);
1237                let lower = chunk[lower_index];
1238                let upper = chunk[upper_index];
1239                chunk[lower_index] = cosine * lower + minus_i_sine * upper_phase * upper;
1240                chunk[upper_index] = cosine * upper + minus_i_sine * lower_phase * lower;
1241            }
1242        };
1243
1244        if self.num_qubits < PARALLEL_THRESHOLD {
1245            self.data
1246                .chunks_mut(chunk_size)
1247                .enumerate()
1248                .for_each(|(chunk_index, chunk)| apply_chunk(chunk_index, chunk));
1249        } else {
1250            self.data
1251                .par_chunks_mut(chunk_size)
1252                .enumerate()
1253                .for_each(|(chunk_index, chunk)| apply_chunk(chunk_index, chunk));
1254        }
1255        Ok(())
1256    }
1257
1258    /// Apply Pauli-X gate: bit flip
1259    /// Matrix: [[0, 1], [1, 0]]
1260    /// Cost: 0 muls, 0 adds, just memory swap.
1261    pub fn apply_x(&mut self, qubit: usize) -> Result<(), QisError> {
1262        self.validate_qubit(qubit)?;
1263        let dist = 1 << qubit;
1264        let kernel = |chunk: &mut [Complex64]| {
1265            let (lower, upper) = chunk.split_at_mut(dist);
1266            lower.iter_mut().zip(upper.iter_mut()).for_each(|(a, b)| {
1267                std::mem::swap(a, b);
1268            });
1269        };
1270        with_maybe_par!(self.num_qubits, self.data, 2 * dist, kernel);
1271        Ok(())
1272    }
1273
1274    /// Apply Pauli-Y gate
1275    /// Matrix: [[0, -i], [i, 0]]
1276    /// Cost: Manual component swapping (faster than complex mul).
1277    pub fn apply_y(&mut self, qubit: usize) -> Result<(), QisError> {
1278        self.validate_qubit(qubit)?;
1279        let dist = 1 << qubit;
1280        let kernel = |chunk: &mut [Complex64]| {
1281            let (lower, upper) = chunk.split_at_mut(dist);
1282            lower
1283                .iter_mut()
1284                .zip(upper.iter_mut())
1285                .for_each(|(alpha, beta)| {
1286                    let a = *alpha;
1287                    let b = *beta;
1288                    *alpha = Complex64::new(b.im, -b.re);
1289                    *beta = Complex64::new(-a.im, a.re);
1290                });
1291        };
1292        with_maybe_par!(self.num_qubits, self.data, 2 * dist, kernel);
1293        Ok(())
1294    }
1295
1296    /// Apply Pauli-Z gate: phase flip
1297    /// Matrix: [[1, 0], [0, -1]]
1298    /// Cost: Only processes half the array. 1 negation.
1299    pub fn apply_z(&mut self, qubit: usize) -> Result<(), QisError> {
1300        self.validate_qubit(qubit)?;
1301        let dist = 1 << qubit;
1302        let kernel = |chunk: &mut [Complex64]| {
1303            let (_lower, upper) = chunk.split_at_mut(dist);
1304            upper.iter_mut().for_each(|val| {
1305                *val = -*val;
1306            });
1307        };
1308        with_maybe_par!(self.num_qubits, self.data, 2 * dist, kernel);
1309        Ok(())
1310    }
1311
1312    /// Apply Hadamard gate
1313    /// Matrix: 1/sqrt(2) * [[1, 1], [1, -1]]
1314    /// Creates superposition: H|0⟩ = (|0⟩ + |1⟩)/√2, H|1⟩ = (|0⟩ - |1⟩)/√2
1315    pub fn apply_h(&mut self, qubit: usize) -> Result<(), QisError> {
1316        self.validate_qubit(qubit)?;
1317        let dist = 1 << qubit;
1318        let k = FRAC_1_SQRT_2;
1319
1320        let kernel = |chunk: &mut [Complex64]| {
1321            let (lower, upper) = chunk.split_at_mut(dist);
1322            lower
1323                .iter_mut()
1324                .zip(upper.iter_mut())
1325                .for_each(|(alpha, beta)| {
1326                    let a = *alpha;
1327                    let b = *beta;
1328                    *alpha = k * (a + b);
1329                    *beta = k * (a - b);
1330                });
1331        };
1332        with_maybe_par!(self.num_qubits, self.data, 2 * dist, kernel);
1333        Ok(())
1334    }
1335
1336    /// Apply U gate (generic single-qubit rotation)
1337    /// Matrix: [[cos(θ/2), -e^(iλ)sin(θ/2)], [e^(iφ)sin(θ/2), e^(i(φ+λ))cos(θ/2)]]
1338    ///
1339    /// This is the most general single-qubit unitary (up to global phase).
1340    /// Parameters:
1341    ///   theta: rotation angle
1342    ///   phi: phase of the rotation axis
1343    ///   lambda: second phase parameter
1344    pub fn apply_u(
1345        &mut self,
1346        qubit: usize,
1347        theta: f64,
1348        phi: f64,
1349        lambda: f64,
1350    ) -> Result<(), QisError> {
1351        self.validate_qubit(qubit)?;
1352
1353        // Fast path: if theta is 0, U simplifies to a Z-rotation-like operation
1354        if theta.abs() < 1e-15 {
1355            // U(0, φ, λ) = e^(i(φ+λ)/2) * [[1, 0], [0, e^(i(λ+φ))]]
1356            // This is essentially a phase gate plus global phase
1357            return self.apply_phase(qubit, lambda + phi);
1358        }
1359
1360        let dist = 1 << qubit;
1361
1362        let half_theta = theta * 0.5;
1363        let c = half_theta.cos();
1364        let s = half_theta.sin();
1365
1366        // Compute phase factors
1367        let phase_phi = Complex64::from_polar(1.0, phi); // e^(iφ)
1368        let phase_lambda = Complex64::from_polar(1.0, lambda); // e^(iλ)
1369        let phase_phi_lambda = Complex64::from_polar(1.0, phi + lambda); // e^(i(φ+λ))
1370
1371        // Matrix elements:
1372        // u00 = cos(θ/2)
1373        // u01 = -e^(iλ) * sin(θ/2)
1374        // u10 = e^(iφ) * sin(θ/2)
1375        // u11 = e^(i(φ+λ)) * cos(θ/2)
1376        let u00 = Complex64::new(c, 0.0);
1377        let u01 = -phase_lambda * s;
1378        let u10 = phase_phi * s;
1379        let u11 = phase_phi_lambda * c;
1380
1381        let kernel = |chunk: &mut [Complex64]| {
1382            let (lower, upper) = chunk.split_at_mut(dist);
1383            lower
1384                .iter_mut()
1385                .zip(upper.iter_mut())
1386                .for_each(|(alpha, beta)| {
1387                    let a = *alpha;
1388                    let b = *beta;
1389                    *alpha = u00 * a + u01 * b;
1390                    *beta = u10 * a + u11 * b;
1391                });
1392        };
1393        with_maybe_par!(self.num_qubits, self.data, 2 * dist, kernel);
1394        Ok(())
1395    }
1396
1397    /// Apply S gate (Phase gate, Z^1/2)
1398    /// Matrix: [[1, 0], [0, i]]
1399    pub fn apply_s(&mut self, qubit: usize) -> Result<(), QisError> {
1400        self.validate_qubit(qubit)?;
1401        let dist = 1 << qubit;
1402        let kernel = |chunk: &mut [Complex64]| {
1403            let (_lower, upper) = chunk.split_at_mut(dist);
1404            upper.iter_mut().for_each(|val| {
1405                *val = Complex64::new(-val.im, val.re);
1406            });
1407        };
1408        with_maybe_par!(self.num_qubits, self.data, 2 * dist, kernel);
1409        Ok(())
1410    }
1411
1412    /// Apply S† (S-dagger) gate
1413    /// Matrix: [[1, 0], [0, -i]]
1414    pub fn apply_sdg(&mut self, qubit: usize) -> Result<(), QisError> {
1415        self.validate_qubit(qubit)?;
1416        let dist = 1 << qubit;
1417        let kernel = |chunk: &mut [Complex64]| {
1418            let (_lower, upper) = chunk.split_at_mut(dist);
1419            upper.iter_mut().for_each(|val| {
1420                *val = Complex64::new(val.im, -val.re);
1421            });
1422        };
1423        with_maybe_par!(self.num_qubits, self.data, 2 * dist, kernel);
1424        Ok(())
1425    }
1426
1427    /// Apply T gate (Z^1/4)
1428    /// Matrix: [[1, 0], [0, e^(i*pi/4)]]
1429    pub fn apply_t(&mut self, qubit: usize) -> Result<(), QisError> {
1430        self.validate_qubit(qubit)?;
1431        let dist = 1 << qubit;
1432        let phase = Complex64::new(FRAC_1_SQRT_2, FRAC_1_SQRT_2);
1433        let kernel = |chunk: &mut [Complex64]| {
1434            let (_lower, upper) = chunk.split_at_mut(dist);
1435            upper.iter_mut().for_each(|val| {
1436                *val *= phase;
1437            });
1438        };
1439        with_maybe_par!(self.num_qubits, self.data, 2 * dist, kernel);
1440        Ok(())
1441    }
1442
1443    /// Apply T† (T-dagger) gate
1444    /// Matrix: [[1, 0], [0, e^(-i*pi/4)]]
1445    pub fn apply_tdg(&mut self, qubit: usize) -> Result<(), QisError> {
1446        self.validate_qubit(qubit)?;
1447        let dist = 1 << qubit;
1448        let phase = Complex64::new(FRAC_1_SQRT_2, -FRAC_1_SQRT_2);
1449        let kernel = |chunk: &mut [Complex64]| {
1450            let (_lower, upper) = chunk.split_at_mut(dist);
1451            upper.iter_mut().for_each(|val| {
1452                *val *= phase;
1453            });
1454        };
1455        with_maybe_par!(self.num_qubits, self.data, 2 * dist, kernel);
1456        Ok(())
1457    }
1458
1459    /// Apply P (Phase) gate
1460    /// Matrix: [[1, 0], [0, e^(i*theta)]]
1461    pub fn apply_phase(&mut self, qubit: usize, theta: f64) -> Result<(), QisError> {
1462        self.validate_qubit(qubit)?;
1463        if theta.abs() < 1e-15 {
1464            return Ok(());
1465        }
1466
1467        let dist = 1 << qubit;
1468        let phase = Complex64::from_polar(1.0, theta);
1469        let kernel = |chunk: &mut [Complex64]| {
1470            let (_lower, upper) = chunk.split_at_mut(dist);
1471            upper.iter_mut().for_each(|val| {
1472                *val *= phase;
1473            });
1474        };
1475        with_maybe_par!(self.num_qubits, self.data, 2 * dist, kernel);
1476        Ok(())
1477    }
1478
1479    /// Apply Rx(theta) gate
1480    /// Matrix: [[cos(t/2), -i*sin(t/2)], [-i*sin(t/2), cos(t/2)]]
1481    pub fn apply_rx(&mut self, qubit: usize, theta: f64) -> Result<(), QisError> {
1482        self.validate_qubit(qubit)?;
1483        if theta.abs() < 1e-15 {
1484            return Ok(());
1485        }
1486
1487        let dist = 1 << qubit;
1488        let half_theta = theta * 0.5;
1489        let c = half_theta.cos();
1490        let s = half_theta.sin();
1491
1492        let kernel = |chunk: &mut [Complex64]| {
1493            let (lower, upper) = chunk.split_at_mut(dist);
1494            lower
1495                .iter_mut()
1496                .zip(upper.iter_mut())
1497                .for_each(|(alpha, beta)| {
1498                    let a = *alpha;
1499                    let b = *beta;
1500                    let bs_im = b.im * s;
1501                    let bs_re = b.re * s;
1502                    let as_im = a.im * s;
1503                    let as_re = a.re * s;
1504
1505                    *alpha = Complex64::new(
1506                        a.re * c + bs_im, // Real part
1507                        a.im * c - bs_re, // Imag part
1508                    );
1509
1510                    *beta = Complex64::new(
1511                        b.re * c + as_im, // Real part
1512                        b.im * c - as_re, // Imag part
1513                    );
1514                });
1515        };
1516        with_maybe_par!(self.num_qubits, self.data, 2 * dist, kernel);
1517        Ok(())
1518    }
1519
1520    /// Apply Ry(theta) gate
1521    /// Matrix: [[cos(t/2), -sin(t/2)], [sin(t/2), cos(t/2)]]
1522    pub fn apply_ry(&mut self, qubit: usize, theta: f64) -> Result<(), QisError> {
1523        self.validate_qubit(qubit)?;
1524        if theta.abs() < 1e-15 {
1525            return Ok(());
1526        }
1527
1528        let dist = 1 << qubit;
1529
1530        let half_theta = theta * 0.5;
1531        let c = half_theta.cos();
1532        let s = half_theta.sin();
1533
1534        let kernel = |chunk: &mut [Complex64]| {
1535            let (lower, upper) = chunk.split_at_mut(dist);
1536            lower
1537                .iter_mut()
1538                .zip(upper.iter_mut())
1539                .for_each(|(alpha, beta)| {
1540                    let a = *alpha;
1541                    let b = *beta;
1542                    *alpha = a * c - b * s;
1543                    *beta = a * s + b * c;
1544                });
1545        };
1546        with_maybe_par!(self.num_qubits, self.data, 2 * dist, kernel);
1547        Ok(())
1548    }
1549
1550    /// Apply Rz(theta) gate
1551    /// Matrix: [[e^(-i*theta/2), 0], [0, e^(i*theta/2)]]
1552    pub fn apply_rz(&mut self, qubit: usize, theta: f64) -> Result<(), QisError> {
1553        self.validate_qubit(qubit)?;
1554        if theta.abs() < 1e-15 {
1555            return Ok(());
1556        }
1557
1558        let dist = 1 << qubit;
1559
1560        // Precompute phase factors
1561        let half_theta = theta * 0.5;
1562        let phase_0 = Complex64::from_polar(1.0, -half_theta); // e^(-i*t/2)
1563        let phase_1 = Complex64::from_polar(1.0, half_theta); // e^(i*t/2)
1564
1565        let kernel = |chunk: &mut [Complex64]| {
1566            let (lower, upper) = chunk.split_at_mut(dist);
1567            lower.iter_mut().zip(upper.iter_mut()).for_each(|(a, b)| {
1568                *a *= phase_0;
1569                *b *= phase_1;
1570            });
1571        };
1572        with_maybe_par!(self.num_qubits, self.data, 2 * dist, kernel);
1573        Ok(())
1574    }
1575
1576    /// Applies X2P gate: Rx(π/2), also known as √X.
1577    ///
1578    /// Matrix: 1/√2 * [[1, -i], [-i, 1]]
1579    ///
1580    /// # Optimization
1581    /// Uses constant factors only, no trigonometric functions.
1582    pub fn apply_x2p(&mut self, qubit: usize) -> Result<(), QisError> {
1583        self.validate_qubit(qubit)?;
1584        let dist = 1 << qubit;
1585        let k = FRAC_1_SQRT_2;
1586        let kernel = |chunk: &mut [Complex64]| {
1587            let (lower, upper) = chunk.split_at_mut(dist);
1588            lower
1589                .iter_mut()
1590                .zip(upper.iter_mut())
1591                .for_each(|(alpha, beta)| {
1592                    let a = *alpha;
1593                    let b = *beta;
1594                    *alpha = Complex64::new(k * (a.re + b.im), k * (a.im - b.re));
1595                    *beta = Complex64::new(k * (b.re + a.im), k * (b.im - a.re));
1596                });
1597        };
1598        with_maybe_par!(self.num_qubits, self.data, 2 * dist, kernel);
1599        Ok(())
1600    }
1601
1602    /// Applies X2M gate: Rx(-π/2), inverse of √X.
1603    ///
1604    /// Matrix: 1/√2 * [[1, i], [i, 1]]
1605    pub fn apply_x2m(&mut self, qubit: usize) -> Result<(), QisError> {
1606        self.validate_qubit(qubit)?;
1607        let dist = 1 << qubit;
1608        let k = FRAC_1_SQRT_2;
1609        let kernel = |chunk: &mut [Complex64]| {
1610            let (lower, upper) = chunk.split_at_mut(dist);
1611            lower
1612                .iter_mut()
1613                .zip(upper.iter_mut())
1614                .for_each(|(alpha, beta)| {
1615                    let a = *alpha;
1616                    let b = *beta;
1617                    *alpha = Complex64::new(k * (a.re - b.im), k * (a.im + b.re));
1618                    *beta = Complex64::new(k * (b.re - a.im), k * (b.im + a.re));
1619                });
1620        };
1621        with_maybe_par!(self.num_qubits, self.data, 2 * dist, kernel);
1622        Ok(())
1623    }
1624
1625    /// Applies Y2P gate: Ry(π/2).
1626    ///
1627    /// Matrix: 1/√2 * [[1, -1], [1, 1]]
1628    pub fn apply_y2p(&mut self, qubit: usize) -> Result<(), QisError> {
1629        self.validate_qubit(qubit)?;
1630        let dist = 1 << qubit;
1631        let k = FRAC_1_SQRT_2;
1632        let kernel = |chunk: &mut [Complex64]| {
1633            let (lower, upper) = chunk.split_at_mut(dist);
1634            lower
1635                .iter_mut()
1636                .zip(upper.iter_mut())
1637                .for_each(|(alpha, beta)| {
1638                    let a = *alpha;
1639                    let b = *beta;
1640                    *alpha = k * (a - b);
1641                    *beta = k * (a + b);
1642                });
1643        };
1644        with_maybe_par!(self.num_qubits, self.data, 2 * dist, kernel);
1645        Ok(())
1646    }
1647
1648    /// Applies Y2M gate: Ry(-π/2).
1649    ///
1650    /// Matrix: 1/√2 * [[1, 1], [-1, 1]]
1651    pub fn apply_y2m(&mut self, qubit: usize) -> Result<(), QisError> {
1652        self.validate_qubit(qubit)?;
1653        let dist = 1 << qubit;
1654        let k = FRAC_1_SQRT_2;
1655        let kernel = |chunk: &mut [Complex64]| {
1656            let (lower, upper) = chunk.split_at_mut(dist);
1657            lower
1658                .iter_mut()
1659                .zip(upper.iter_mut())
1660                .for_each(|(alpha, beta)| {
1661                    let a = *alpha;
1662                    let b = *beta;
1663                    *alpha = k * (a + b);
1664                    *beta = k * (b - a);
1665                });
1666        };
1667        with_maybe_par!(self.num_qubits, self.data, 2 * dist, kernel);
1668        Ok(())
1669    }
1670
1671    /// Applies XY2P gate: Rz(θ - π/2) Ry(π/2) Rz(π/2 - θ).
1672    ///
1673    /// Matrix: 1/√2 * [[1, -i·e^(-iθ)], [-i·e^(iθ), 1]]
1674    ///
1675    /// # Arguments
1676    /// * `qubit` - Target qubit index
1677    /// * `theta` - Rotation angle
1678    pub fn apply_xy2p(&mut self, qubit: usize, theta: f64) -> Result<(), QisError> {
1679        self.validate_qubit(qubit)?;
1680        let dist = 1 << qubit;
1681        let k = FRAC_1_SQRT_2;
1682
1683        // Precompute coefficients, fuse k to reduce multiplications in loop
1684        // e^(i*theta)
1685        let phase = Complex64::from_polar(1.0, theta);
1686        let phase_conj = phase.conj(); // e^(-i*theta)
1687        let neg_i = Complex64::new(0.0, -1.0);
1688
1689        // off_diag_01 = k * (-i * e^(-i*theta))
1690        let coef_01 = neg_i * phase_conj * k;
1691
1692        // off_diag_10 = k * (-i * e^(i*theta))
1693        let coef_10 = neg_i * phase * k;
1694
1695        // Explicitly convert to Complex64 for multiplication with complex numbers
1696        let k_complex = Complex64::new(k, 0.0);
1697        let kernel = |chunk: &mut [Complex64]| {
1698            let (lower, upper) = chunk.split_at_mut(dist);
1699            lower
1700                .iter_mut()
1701                .zip(upper.iter_mut())
1702                .for_each(|(alpha, beta)| {
1703                    let a = *alpha;
1704                    let b = *beta;
1705                    *alpha = k_complex * a + coef_01 * b;
1706                    *beta = coef_10 * a + k_complex * b;
1707                });
1708        };
1709        with_maybe_par!(self.num_qubits, self.data, 2 * dist, kernel);
1710        Ok(())
1711    }
1712
1713    /// Applies XY2M gate: Rz(-θ + π/2) Ry(-π/2) Rz(-π/2 + θ).
1714    ///
1715    /// Matrix: 1/√2 * [[1, i·e^(-iθ)], [i·e^(iθ), 1]]
1716    ///
1717    /// # Arguments
1718    /// * `qubit` - Target qubit index
1719    /// * `theta` - Rotation angle
1720    pub fn apply_xy2m(&mut self, qubit: usize, theta: f64) -> Result<(), QisError> {
1721        self.validate_qubit(qubit)?;
1722        let dist = 1 << qubit;
1723        let k = FRAC_1_SQRT_2;
1724        let phase = Complex64::from_polar(1.0, theta);
1725        let phase_conj = phase.conj();
1726        let pos_i = Complex64::new(0.0, 1.0);
1727        let coef_01 = pos_i * phase_conj * k;
1728        let coef_10 = pos_i * phase * k;
1729        let k_complex = Complex64::new(k, 0.0);
1730        let kernel = |chunk: &mut [Complex64]| {
1731            let (lower, upper) = chunk.split_at_mut(dist);
1732            lower
1733                .iter_mut()
1734                .zip(upper.iter_mut())
1735                .for_each(|(alpha, beta)| {
1736                    let a = *alpha;
1737                    let b = *beta;
1738                    *alpha = k_complex * a + coef_01 * b;
1739                    *beta = coef_10 * a + k_complex * b;
1740                });
1741        };
1742        with_maybe_par!(self.num_qubits, self.data, 2 * dist, kernel);
1743        Ok(())
1744    }
1745
1746    /// Applies XY gate.
1747    ///
1748    /// Matrix: [[0, -i·e^(-iθ)], [-i·e^(iθ), 0]]
1749    ///
1750    /// This is a single-qubit gate distinct from XY2P/XY2M.
1751    ///
1752    /// # Arguments
1753    /// * `qubit` - Target qubit index
1754    /// * `theta` - Rotation angle
1755    pub fn apply_xy(&mut self, qubit: usize, theta: f64) -> Result<(), QisError> {
1756        self.validate_qubit(qubit)?;
1757
1758        let dist = 1 << qubit;
1759        let phase = Complex64::from_polar(1.0, theta);
1760        let phase_conj = phase.conj();
1761        let neg_i = Complex64::new(0.0, -1.0);
1762
1763        // u01 = -i * e^(-i*theta)
1764        let u01 = neg_i * phase_conj;
1765        // u10 = -i * e^(i*theta)
1766        let u10 = neg_i * phase;
1767
1768        let kernel = |chunk: &mut [Complex64]| {
1769            let (lower, upper) = chunk.split_at_mut(dist);
1770            lower
1771                .iter_mut()
1772                .zip(upper.iter_mut())
1773                .for_each(|(alpha, beta)| {
1774                    let a = *alpha;
1775                    let b = *beta;
1776                    *alpha = u01 * b;
1777                    *beta = u10 * a;
1778                });
1779        };
1780        with_maybe_par!(self.num_qubits, self.data, 2 * dist, kernel);
1781        Ok(())
1782    }
1783
1784    /// Apply general rotation R(theta, phi).
1785    ///
1786    /// Rotates around axis cos(phi)X + sin(phi)Y by angle theta.
1787    /// Matrix: [[cos(t/2), -i*e^(-i*phi)*sin(t/2)], [-i*e^(i*phi)*sin(t/2), cos(t/2)]]
1788    ///
1789    /// # Arguments
1790    /// * `qubit` - Target qubit index
1791    /// * `theta` - Rotation angle
1792    /// * `phi` - Rotation axis angle in XY plane
1793    pub fn apply_rxy(&mut self, qubit: usize, theta: f64, phi: f64) -> Result<(), QisError> {
1794        self.validate_qubit(qubit)?;
1795
1796        // Fast path: treat as Identity if theta is negligible
1797        if theta.abs() < 1e-15 {
1798            return Ok(());
1799        }
1800
1801        // Precompute all coefficients
1802        let dist = 1 << qubit;
1803        let half_theta = theta * 0.5;
1804        let c = half_theta.cos();
1805        let s = half_theta.sin();
1806
1807        // Precompute phi trig functions
1808        let (sin_phi, cos_phi) = phi.sin_cos();
1809
1810        // Construct off-diagonal coefficients
1811        // u01 = -i * (cos_phi - i*sin_phi) * s
1812        //     = (-i*cos_phi - sin_phi) * s
1813        //     = s * (-sin_phi - i*cos_phi)
1814        let u01 = Complex64::new(-s * sin_phi, -s * cos_phi);
1815
1816        // u10 = -i * (cos_phi + i*sin_phi) * s
1817        //     = (-i*cos_phi + sin_phi) * s
1818        //     = s * (sin_phi - i*cos_phi)
1819        let u10 = Complex64::new(s * sin_phi, -s * cos_phi);
1820
1821        // Apply (serial or parallel based on qubit count)
1822        let kernel = |chunk: &mut [Complex64]| {
1823            let (lower, upper) = chunk.split_at_mut(dist);
1824            lower
1825                .iter_mut()
1826                .zip(upper.iter_mut())
1827                .for_each(|(alpha, beta)| {
1828                    let a = *alpha;
1829                    let b = *beta;
1830
1831                    // Core computation:
1832                    // alpha' = c * a + u01 * b
1833                    // beta'  = u10 * a + c * b
1834
1835                    // num_complex supports Complex * f64, faster than Complex(c, 0)
1836                    *alpha = a * c + b * u01;
1837                    *beta = a * u10 + b * c;
1838                });
1839        };
1840        with_maybe_par!(self.num_qubits, self.data, 2 * dist, kernel);
1841        Ok(())
1842    }
1843
1844    /// Applies SWAP gate to exchange two qubits.
1845    ///
1846    /// Matrix: [[1,0,0,0], [0,0,1,0], [0,1,0,0], [0,0,0,1]]
1847    ///
1848    /// # Arguments
1849    /// * `q0` - First qubit index
1850    /// * `q1` - Second qubit index
1851    ///
1852    /// # Errors
1853    /// Returns [`QisError::IndexOutOfBounds`] if either qubit is out of bounds,
1854    /// or [`QisError::QubitMismatch`] if `q0 == q1`.
1855    ///
1856    /// # Optimization
1857    /// Uses memory swap without floating-point operations.
1858    pub fn apply_swap(&mut self, q0: usize, q1: usize) -> Result<(), QisError> {
1859        self.validate_two_qubits(q0, q1)?;
1860
1861        // 1. Determine physical memory strides
1862        let (q_min, q_max) = if q0 < q1 { (q0, q1) } else { (q1, q0) };
1863        let dist_max = 1 << q_max;
1864        let dist_min = 1 << q_min;
1865
1866        // 2. Choose serial or parallel based on qubit count
1867        let kernel = |chunk: &mut [Complex64]| {
1868            let (part0, part1) = chunk.split_at_mut(dist_max);
1869
1870            part0
1871                .chunks_exact_mut(2 * dist_min)
1872                .zip(part1.chunks_exact_mut(2 * dist_min))
1873                .for_each(|(sub_chunk0, sub_chunk1)| {
1874                    let (_, v01_slice) = sub_chunk0.split_at_mut(dist_min);
1875                    let (v10_slice, _) = sub_chunk1.split_at_mut(dist_min);
1876
1877                    // v01_slice: physical (max=0, min=1) -> logical |01> or |10>
1878                    // v10_slice: physical (max=1, min=0) -> logical |10> or |01>
1879                    // SWAP always exchanges these two memory blocks
1880                    v01_slice
1881                        .iter_mut()
1882                        .zip(v10_slice.iter_mut())
1883                        .for_each(|(a, b)| {
1884                            // Direct memory swap, no floating-point overhead
1885                            std::mem::swap(a, b);
1886                        });
1887                });
1888        };
1889        with_maybe_par!(self.num_qubits, self.data, 2 * dist_max, kernel);
1890        Ok(())
1891    }
1892
1893    /// Applies CNOT (CX) gate: controlled-X operation.
1894    ///
1895    /// Flips the target qubit when the control qubit is |1⟩.
1896    /// Matrix: [[1,0,0,0], [0,1,0,0], [0,0,0,1], [0,0,1,0]]
1897    ///
1898    /// # Arguments
1899    /// * `control` - Control qubit index
1900    /// * `target` - Target qubit index
1901    ///
1902    /// # Errors
1903    /// Returns [`QisError::IndexOutOfBounds`] if either qubit is out of bounds,
1904    /// or [`QisError::QubitMismatch`] if `control == target`.
1905    pub fn apply_cx(&mut self, control: usize, target: usize) -> Result<(), QisError> {
1906        self.validate_two_qubits(control, target)?;
1907
1908        let (q_min, q_max) = if control < target {
1909            (control, target)
1910        } else {
1911            (target, control)
1912        };
1913        let dist_max = 1 << q_max;
1914        let dist_min = 1 << q_min;
1915
1916        let kernel = |chunk: &mut [Complex64]| {
1917            let (part0, part1) = chunk.split_at_mut(dist_max);
1918
1919            part0
1920                .chunks_exact_mut(2 * dist_min)
1921                .zip(part1.chunks_exact_mut(2 * dist_min))
1922                .for_each(|(sub_chunk0, sub_chunk1)| {
1923                    // Get references to four blocks; which to operate on depends on control/target
1924                    let (_v00, v01) = sub_chunk0.split_at_mut(dist_min);
1925                    let (v10, v11) = sub_chunk1.split_at_mut(dist_min);
1926
1927                    if control < target {
1928                        // Case A: Control is low (q_min), Target is high (q_max)
1929                        // Control=1 physical positions:
1930                        //   1. max=0, min=1 (v01) -> Control=1, Target=0
1931                        //   2. max=1, min=1 (v11) -> Control=1, Target=1
1932                        // Action: swap v01 and v11 (flip Target)
1933                        v01.iter_mut()
1934                            .zip(v11.iter_mut())
1935                            .for_each(|(a, b)| std::mem::swap(a, b));
1936                    } else {
1937                        // Case B: Control is high (q_max), Target is low (q_min)
1938                        // Control=1 physical positions:
1939                        //   1. max=1, min=0 (v10) -> Control=1, Target=0
1940                        //   2. max=1, min=1 (v11) -> Control=1, Target=1
1941                        // Action: swap v10 and v11 (flip Target)
1942                        v10.iter_mut()
1943                            .zip(v11.iter_mut())
1944                            .for_each(|(a, b)| std::mem::swap(a, b));
1945                    }
1946                });
1947        };
1948        with_maybe_par!(self.num_qubits, self.data, 2 * dist_max, kernel);
1949        Ok(())
1950    }
1951
1952    /// Applies controlled-Y (CY) gate.
1953    ///
1954    /// Applies Y gate to target when control qubit is |1⟩.
1955    /// Y matrix: [[0, -i], [i, 0]]
1956    ///
1957    /// # Arguments
1958    /// * `control` - Control qubit index
1959    /// * `target` - Target qubit index
1960    ///
1961    /// # Errors
1962    /// Returns [`QisError::IndexOutOfBounds`] if either qubit is out of bounds,
1963    /// or [`QisError::QubitMismatch`] if `control == target`.
1964    pub fn apply_cy(&mut self, control: usize, target: usize) -> Result<(), QisError> {
1965        self.validate_two_qubits(control, target)?;
1966
1967        // 1. Determine physical memory strides
1968        let (q_min, q_max) = if control < target {
1969            (control, target)
1970        } else {
1971            (target, control)
1972        };
1973        let dist_max = 1 << q_max;
1974        let dist_min = 1 << q_min;
1975
1976        // 2. Choose serial or parallel based on qubit count
1977        let kernel = |chunk: &mut [Complex64]| {
1978            let (part0, part1) = chunk.split_at_mut(dist_max);
1979
1980            part0
1981                .chunks_exact_mut(2 * dist_min)
1982                .zip(part1.chunks_exact_mut(2 * dist_min))
1983                .for_each(|(sub_chunk0, sub_chunk1)| {
1984                    // Locate memory blocks where control=1
1985                    // block_t0: amplitude where Control=1, Target=0
1986                    // block_t1: amplitude where Control=1, Target=1
1987
1988                    let (block_t0, block_t1) = if control < target {
1989                        // Case A: Control is low (min), Target is high (max)
1990                        // part0: q_max=0 (Target=0), part1: q_max=1 (Target=1)
1991                        // Need Control=1 states (q_min=1)
1992                        // |10⟩ (q_min=1, q_max=0) and |11⟩ (q_min=1, q_max=1)
1993                        // Corresponds to sub_chunk0[dist_min..] and sub_chunk1[dist_min..]
1994                        let (_, c1_t0) = sub_chunk0.split_at_mut(dist_min);
1995                        let (_, c1_t1) = sub_chunk1.split_at_mut(dist_min);
1996                        (c1_t0, c1_t1)
1997                    } else {
1998                        // Case B: Control is high (max), Target is low (min)
1999                        // part0 is Control=0 (skip), part1 is Control=1
2000                        // Within part1, lower half is Target=0, upper half is Target=1
2001                        let (t0, t1) = sub_chunk1.split_at_mut(dist_min);
2002                        (t0, t1)
2003                    };
2004
2005                    // 3. Apply Y gate transformation
2006                    block_t0
2007                        .iter_mut()
2008                        .zip(block_t1.iter_mut())
2009                        .for_each(|(alpha, beta)| {
2010                            let a = *alpha; // Target=0
2011                            let b = *beta; // Target=1
2012
2013                            // Y matrix: [[0, -i], [i, 0]]
2014                            // new_a = -i * b
2015                            // new_b =  i * a
2016
2017                            // -i * (x + it) = y - ix
2018                            *alpha = Complex64::new(b.im, -b.re);
2019
2020                            // i * (x + it) = -y + ix
2021                            *beta = Complex64::new(-a.im, a.re);
2022                        });
2023                });
2024        };
2025        with_maybe_par!(self.num_qubits, self.data, 2 * dist_max, kernel);
2026        Ok(())
2027    }
2028
2029    /// Applies controlled-Z (CZ) gate.
2030    ///
2031    /// Applies phase flip (-1) to |11⟩ component.
2032    /// Leaves |00⟩, |01⟩, |10⟩ unchanged.
2033    ///
2034    /// # Arguments
2035    /// * `q0` - First qubit index (acts as control)
2036    /// * `q1` - Second qubit index (acts as control)
2037    ///
2038    /// # Errors
2039    /// Returns [`QisError::IndexOutOfBounds`] if either qubit is out of bounds,
2040    /// or [`QisError::QubitMismatch`] if `q0 == q1`.
2041    pub fn apply_cz(&mut self, q0: usize, q1: usize) -> Result<(), QisError> {
2042        self.validate_two_qubits(q0, q1)?;
2043
2044        let (q_min, q_max) = if q0 < q1 { (q0, q1) } else { (q1, q0) };
2045        let dist_max = 1 << q_max;
2046        let dist_min = 1 << q_min;
2047
2048        let kernel = |chunk: &mut [Complex64]| {
2049            let (_, part1) = chunk.split_at_mut(dist_max); // Only care about max=1 part
2050
2051            part1.chunks_exact_mut(2 * dist_min).for_each(|sub_chunk1| {
2052                let (_, v11) = sub_chunk1.split_at_mut(dist_min); // Only care about min=1 part
2053
2054                // v11 now corresponds to max=1, min=1, i.e., |11⟩ state
2055                // Apply sign flip only to this part
2056                for val in v11.iter_mut() {
2057                    *val = -*val;
2058                }
2059            });
2060        };
2061        with_maybe_par!(self.num_qubits, self.data, 2 * dist_max, kernel);
2062        Ok(())
2063    }
2064
2065    /// Apply Toffoli gate (CCX - Controlled-Controlled-X)
2066    /// Two control qubits and one target qubit.
2067    /// Flips the target if and only if both controls are |1⟩.
2068    ///
2069    /// # Arguments
2070    /// * `c0` - First control qubit index
2071    /// * `c1` - Second control qubit index
2072    /// * `target` - Target qubit index
2073    pub fn apply_ccx(&mut self, c0: usize, c1: usize, target: usize) -> Result<(), QisError> {
2074        self.validate_qubits(&[c0, c1, target])?;
2075
2076        // 1. Sort: determine physical min, mid, max positions and their roles
2077        // kind: 0 = Control, 1 = Target
2078        let mut qubits = [(c0, 0), (c1, 0), (target, 1)];
2079        qubits.sort_by_key(|(q, _)| *q);
2080
2081        let (q0, k0) = qubits[0]; // Min position
2082        let (q1, k1) = qubits[1]; // Mid position
2083        let (q2, k2) = qubits[2]; // Max position
2084
2085        let d0 = 1 << q0;
2086        let d1 = 1 << q1;
2087        let d2 = 1 << q2;
2088
2089        match (k0, k1, k2) {
2090            // Case A: Target is at max position -> (Control, Control, Target)
2091            (0, 0, 1) => {
2092                let kernel = |chunk: &mut [Complex64]| {
2093                    let (part_t0, part_t1) = chunk.split_at_mut(d2);
2094
2095                    // Iterate over Mid layer
2096                    part_t0
2097                        .chunks_exact_mut(2 * d1)
2098                        .zip(part_t1.chunks_exact_mut(2 * d1))
2099                        .for_each(|(sub_t0, sub_t1)| {
2100                            // Take Mid=1 part (control is active)
2101                            let (_, high_m_t0) = sub_t0.split_at_mut(d1);
2102                            let (_, high_m_t1) = sub_t1.split_at_mut(d1);
2103
2104                            // Iterate over Min layer (using chunks to handle non-contiguous gaps)
2105                            high_m_t0
2106                                .chunks_exact_mut(2 * d0)
2107                                .zip(high_m_t1.chunks_exact_mut(2 * d0))
2108                                .for_each(|(leaf_t0, leaf_t1)| {
2109                                    // Take Min=1 part (control is active)
2110                                    let (_, valid_t0) = leaf_t0.split_at_mut(d0);
2111                                    let (_, valid_t1) = leaf_t1.split_at_mut(d0);
2112
2113                                    // Now Min=1, Mid=1, execute Target flip
2114                                    valid_t0
2115                                        .iter_mut()
2116                                        .zip(valid_t1.iter_mut())
2117                                        .for_each(|(a, b)| std::mem::swap(a, b));
2118                                });
2119                        });
2120                };
2121                with_maybe_par!(self.num_qubits, self.data, 2 * d2, kernel);
2122            }
2123
2124            // Case B: Target is at mid position -> (Control, Target, Control)
2125            // Min=Control0, Mid=Target, Max=Control1
2126            // Need to swap amplitudes where Control0=1, Control1=1, Target=0 with Target=1
2127            //
2128            // Memory layout note:
2129            // We use zip to pair elements from low_t0 (Target=0) and high_t1 (Target=1).
2130            // Although low_t0 and high_t1 are not contiguous in memory, they are "symmetric"
2131            // in logical structure:
2132            // - low_t0 contains Min=0 and Min=1 parts
2133            // - high_t1 also contains Min=0 and Min=1 parts
2134            // Since both slices come from the same d1 split, their internal structures match.
2135            // Therefore, using zip on low_t0.chunks_exact_mut(2*d0) and
2136            // high_t1.chunks_exact_mut(2*d0) correctly pairs elements at the same Min index.
2137            (0, 1, 0) => {
2138                let kernel = |chunk: &mut [Complex64]| {
2139                    // Max (Control1) is a control, take Max=1 part
2140                    let (_, part_c1) = chunk.split_at_mut(d2);
2141
2142                    part_c1.chunks_exact_mut(2 * d1).for_each(|sub| {
2143                        // Mid is Target, split into Target=0 and Target=1
2144                        let (low_t0, high_t1) = sub.split_at_mut(d1);
2145
2146                        // Iterate over Min (Control0) layer
2147                        // Use memory structure symmetry to zip pair Target=0 and Target=1 blocks
2148                        low_t0
2149                            .chunks_exact_mut(2 * d0)
2150                            .zip(high_t1.chunks_exact_mut(2 * d0))
2151                            .for_each(|(leaf_t0, leaf_t1)| {
2152                                // Min is Control0, take Min=1 part (both controls are 1)
2153                                let (_, valid_t0) = leaf_t0.split_at_mut(d0);
2154                                let (_, valid_t1) = leaf_t1.split_at_mut(d0);
2155
2156                                // Execute SWAP: exchange Target=0 and Target=1 amplitudes
2157                                valid_t0
2158                                    .iter_mut()
2159                                    .zip(valid_t1.iter_mut())
2160                                    .for_each(|(a, b)| std::mem::swap(a, b));
2161                            });
2162                    });
2163                };
2164                with_maybe_par!(self.num_qubits, self.data, 2 * d2, kernel);
2165            }
2166
2167            // Case C: Target is at min position -> (Target, Control, Control)
2168            (1, 0, 0) => {
2169                let kernel = |chunk: &mut [Complex64]| {
2170                    let (_, part_max1) = chunk.split_at_mut(d2); // Max=1
2171
2172                    part_max1.chunks_exact_mut(2 * d1).for_each(|sub| {
2173                        let (_, part_mid1) = sub.split_at_mut(d1); // Mid=1
2174
2175                        part_mid1.chunks_exact_mut(2 * d0).for_each(|leaf| {
2176                            let (target0, target1) = leaf.split_at_mut(d0); // Min=0 vs Min=1
2177
2178                            // No further split needed since Min is Target
2179                            target0
2180                                .iter_mut()
2181                                .zip(target1.iter_mut())
2182                                .for_each(|(a, b)| std::mem::swap(a, b));
2183                        });
2184                    });
2185                };
2186                with_maybe_par!(self.num_qubits, self.data, 2 * d2, kernel);
2187            }
2188
2189            _ => unreachable!("Should cover all permutations"),
2190        }
2191        Ok(())
2192    }
2193
2194    /// Applies RXX gate (Ising XX interaction).
2195    ///
2196    /// Matrix: exp(-i·θ/2 · X⊗X) = cos(θ/2)I - i·sin(θ/2)·X⊗X
2197    ///
2198    /// # Arguments
2199    /// * `q0` - First qubit index
2200    /// * `q1` - Second qubit index
2201    /// * `theta` - Interaction angle
2202    pub fn apply_rxx(&mut self, q0: usize, q1: usize, theta: f64) -> Result<(), QisError> {
2203        self.validate_two_qubits(q0, q1)?;
2204        if theta.abs() < 1e-15 {
2205            return Ok(());
2206        }
2207
2208        let (q_min, q_max) = if q0 < q1 { (q0, q1) } else { (q1, q0) };
2209        let d_max = 1 << q_max;
2210        let d_min = 1 << q_min;
2211
2212        let half_theta = theta * 0.5;
2213        let c = half_theta.cos();
2214        let s = half_theta.sin();
2215
2216        let kernel = |chunk: &mut [Complex64]| {
2217            let (part0, part1) = chunk.split_at_mut(d_max);
2218            part0
2219                .chunks_exact_mut(2 * d_min)
2220                .zip(part1.chunks_exact_mut(2 * d_min))
2221                .for_each(|(sub0, sub1)| {
2222                    let (v00, v01) = sub0.split_at_mut(d_min);
2223                    let (v10, v11) = sub1.split_at_mut(d_min);
2224                    for (((a, b), c_amp), d) in v00
2225                        .iter_mut()
2226                        .zip(v01.iter_mut())
2227                        .zip(v10.iter_mut())
2228                        .zip(v11.iter_mut())
2229                    {
2230                        let a_in = *a;
2231                        let b_in = *b;
2232                        let c_in = *c_amp;
2233                        let d_in = *d;
2234                        *a = c * a_in + Complex64::new(0.0, -s) * d_in;
2235                        *b = c * b_in + Complex64::new(0.0, -s) * c_in;
2236                        *c_amp = Complex64::new(0.0, -s) * b_in + c * c_in;
2237                        *d = Complex64::new(0.0, -s) * a_in + c * d_in;
2238                    }
2239                });
2240        };
2241        with_maybe_par!(self.num_qubits, self.data, 2 * d_max, kernel);
2242        Ok(())
2243    }
2244
2245    /// Applies RYY gate (Ising YY interaction).
2246    ///
2247    /// Matrix: exp(-i·θ/2 · Y⊗Y) = cos(θ/2)I - i·sin(θ/2)·Y⊗Y
2248    ///
2249    /// # Arguments
2250    /// * `q0` - First qubit index
2251    /// * `q1` - Second qubit index
2252    /// * `theta` - Interaction angle
2253    pub fn apply_ryy(&mut self, q0: usize, q1: usize, theta: f64) -> Result<(), QisError> {
2254        self.validate_two_qubits(q0, q1)?;
2255        if theta.abs() < 1e-15 {
2256            return Ok(());
2257        }
2258
2259        let (q_min, q_max) = if q0 < q1 { (q0, q1) } else { (q1, q0) };
2260        let d_max = 1 << q_max;
2261        let d_min = 1 << q_min;
2262
2263        let half_theta = theta * 0.5;
2264        let c = half_theta.cos();
2265        let s = half_theta.sin();
2266
2267        let kernel = |chunk: &mut [Complex64]| {
2268            let (part0, part1) = chunk.split_at_mut(d_max);
2269            part0
2270                .chunks_exact_mut(2 * d_min)
2271                .zip(part1.chunks_exact_mut(2 * d_min))
2272                .for_each(|(sub0, sub1)| {
2273                    let (v00, v01) = sub0.split_at_mut(d_min);
2274                    let (v10, v11) = sub1.split_at_mut(d_min);
2275                    for (((a, b), c_amp), d) in v00
2276                        .iter_mut()
2277                        .zip(v01.iter_mut())
2278                        .zip(v10.iter_mut())
2279                        .zip(v11.iter_mut())
2280                    {
2281                        let a_in = *a;
2282                        let b_in = *b;
2283                        let c_in = *c_amp;
2284                        let d_in = *d;
2285                        *a = c * a_in + Complex64::new(0.0, s) * d_in;
2286                        *b = c * b_in + Complex64::new(0.0, -s) * c_in;
2287                        *c_amp = Complex64::new(0.0, -s) * b_in + c * c_in;
2288                        *d = Complex64::new(0.0, s) * a_in + c * d_in;
2289                    }
2290                });
2291        };
2292        with_maybe_par!(self.num_qubits, self.data, 2 * d_max, kernel);
2293        Ok(())
2294    }
2295
2296    /// Applies RZZ gate (Ising ZZ interaction).
2297    ///
2298    /// Matrix: exp(-i·θ/2 · Z⊗Z) - applies phase based on qubit parity
2299    ///
2300    /// # Arguments
2301    /// * `q0` - First qubit index
2302    /// * `q1` - Second qubit index
2303    /// * `theta` - Interaction angle
2304    pub fn apply_rzz(&mut self, q0: usize, q1: usize, theta: f64) -> Result<(), QisError> {
2305        self.validate_two_qubits(q0, q1)?;
2306        if theta.abs() < 1e-15 {
2307            return Ok(());
2308        }
2309
2310        let (q_min, q_max) = if q0 < q1 { (q0, q1) } else { (q1, q0) };
2311        let d_max = 1 << q_max;
2312        let d_min = 1 << q_min;
2313
2314        let half_theta = theta * 0.5;
2315        let phase_even = Complex64::from_polar(1.0, -half_theta);
2316        let phase_odd = Complex64::from_polar(1.0, half_theta);
2317
2318        let kernel = |chunk: &mut [Complex64]| {
2319            let (part0, part1) = chunk.split_at_mut(d_max);
2320            part0
2321                .chunks_exact_mut(2 * d_min)
2322                .zip(part1.chunks_exact_mut(2 * d_min))
2323                .for_each(|(sub0, sub1)| {
2324                    let (v00, v01) = sub0.split_at_mut(d_min);
2325                    let (v10, v11) = sub1.split_at_mut(d_min);
2326                    v00.iter_mut().for_each(|a| *a *= phase_even);
2327                    v11.iter_mut().for_each(|d| *d *= phase_even);
2328                    v01.iter_mut().for_each(|b| *b *= phase_odd);
2329                    v10.iter_mut().for_each(|c| *c *= phase_odd);
2330                });
2331        };
2332        with_maybe_par!(self.num_qubits, self.data, 2 * d_max, kernel);
2333        Ok(())
2334    }
2335
2336    /// Applies RZX gate (Ising ZX interaction).
2337    ///
2338    /// Matrix: exp(-i·θ/2 · Z⊗X)
2339    ///
2340    /// # Arguments
2341    /// * `q0` - First qubit index (Z rotation)
2342    /// * `q1` - Second qubit index (X rotation)
2343    /// * `theta` - Interaction angle
2344    pub fn apply_rzx(&mut self, q0: usize, q1: usize, theta: f64) -> Result<(), QisError> {
2345        self.validate_two_qubits(q0, q1)?;
2346        if theta.abs() < 1e-15 {
2347            return Ok(());
2348        }
2349
2350        let (q_min, q_max) = if q0 < q1 { (q0, q1) } else { (q1, q0) };
2351        let d_max = 1 << q_max;
2352        let d_min = 1 << q_min;
2353
2354        let half_theta = theta * 0.5;
2355        let c = half_theta.cos();
2356        let s = half_theta.sin();
2357
2358        let kernel = |chunk: &mut [Complex64]| {
2359            let (part0, part1) = chunk.split_at_mut(d_max);
2360            part0
2361                .chunks_exact_mut(2 * d_min)
2362                .zip(part1.chunks_exact_mut(2 * d_min))
2363                .for_each(|(sub0, sub1)| {
2364                    let (v00, v01) = sub0.split_at_mut(d_min);
2365                    let (v10, v11) = sub1.split_at_mut(d_min);
2366                    for (((a, b), c_amp), d) in v00
2367                        .iter_mut()
2368                        .zip(v01.iter_mut())
2369                        .zip(v10.iter_mut())
2370                        .zip(v11.iter_mut())
2371                    {
2372                        let a_in = *a;
2373                        let b_in = *b;
2374                        let c_in = *c_amp;
2375                        let d_in = *d;
2376                        if q0 < q1 {
2377                            *a = c * a_in + Complex64::new(0.0, -s) * c_in;
2378                            *b = c * b_in + Complex64::new(0.0, s) * d_in;
2379                            *c_amp = Complex64::new(0.0, -s) * a_in + c * c_in;
2380                            *d = Complex64::new(0.0, s) * b_in + c * d_in;
2381                        } else {
2382                            *a = c * a_in + Complex64::new(0.0, -s) * b_in;
2383                            *b = Complex64::new(0.0, -s) * a_in + c * b_in;
2384                            *c_amp = c * c_in + Complex64::new(0.0, s) * d_in;
2385                            *d = Complex64::new(0.0, s) * c_in + c * d_in;
2386                        }
2387                    }
2388                });
2389        };
2390        with_maybe_par!(self.num_qubits, self.data, 2 * d_max, kernel);
2391        Ok(())
2392    }
2393
2394    /// Applies controlled-RX gate.
2395    ///
2396    /// Applies RX(θ) to target when control is |1⟩.
2397    ///
2398    /// # Arguments
2399    /// * `control` - Control qubit index
2400    /// * `target` - Target qubit index
2401    /// * `theta` - Rotation angle
2402    pub fn apply_crx(&mut self, control: usize, target: usize, theta: f64) -> Result<(), QisError> {
2403        self.validate_two_qubits(control, target)?;
2404        if theta.abs() < 1e-15 {
2405            return Ok(());
2406        }
2407
2408        let (q_min, q_max) = if control < target {
2409            (control, target)
2410        } else {
2411            (target, control)
2412        };
2413        let d_max = 1 << q_max;
2414        let d_min = 1 << q_min;
2415
2416        let half_theta = theta * 0.5;
2417        let c = half_theta.cos();
2418        let s = half_theta.sin();
2419
2420        // CRX matrix on target (when control=1): [[c, -i*s], [-i*s, c]]
2421
2422        let kernel = |chunk: &mut [Complex64]| {
2423            let (part0, part1) = chunk.split_at_mut(d_max);
2424
2425            if control < target {
2426                // Control is q_min, target is q_max
2427                // Need to pair |Target=0, Control=1⟩ (part0, sub upper) with |Target=1, Control=1⟩ (part1, sub upper)
2428                part0
2429                    .chunks_exact_mut(2 * d_min)
2430                    .zip(part1.chunks_exact_mut(2 * d_min))
2431                    .for_each(|(sub0, sub1)| {
2432                        // sub0: target=0, sub1: target=1
2433                        // In each sub, lower half is control=0, upper half is control=1
2434                        let (_, target0_c1) = sub0.split_at_mut(d_min);
2435                        let (_, target1_c1) = sub1.split_at_mut(d_min);
2436
2437                        // Apply RX rotation to target when control=1
2438                        for (a, b) in target0_c1.iter_mut().zip(target1_c1.iter_mut()) {
2439                            let a_in = *a;
2440                            let b_in = *b;
2441                            // RX: [[c, -i*s], [-i*s, c]]
2442                            // -i*s*(x+iy) = s*y - i*s*x
2443                            *a = Complex64::new(
2444                                c * a_in.re + s * b_in.im,
2445                                c * a_in.im - s * b_in.re,
2446                            );
2447                            *b = Complex64::new(
2448                                s * a_in.im + c * b_in.re,
2449                                -s * a_in.re + c * b_in.im,
2450                            );
2451                        }
2452                    });
2453            } else {
2454                // Control is q_max, target is q_min
2455                // Process pairs where control=1
2456                part0
2457                    .chunks_exact_mut(2 * d_min)
2458                    .zip(part1.chunks_exact_mut(2 * d_min))
2459                    .for_each(|(_, sub1)| {
2460                        // sub0: control=0, sub1: control=1
2461                        // In sub1, split by target
2462                        let (target0_ctl1, target1_ctl1) = sub1.split_at_mut(d_min);
2463
2464                        // Apply RX rotation to target when control=1
2465                        for (a, b) in target0_ctl1.iter_mut().zip(target1_ctl1.iter_mut()) {
2466                            let a_in = *a;
2467                            let b_in = *b;
2468                            // RX: [[c, -i*s], [-i*s, c]]
2469                            *a = Complex64::new(
2470                                c * a_in.re + s * b_in.im,
2471                                c * a_in.im - s * b_in.re,
2472                            );
2473                            *b = Complex64::new(
2474                                s * a_in.im + c * b_in.re,
2475                                -s * a_in.re + c * b_in.im,
2476                            );
2477                        }
2478                    });
2479            }
2480        };
2481        with_maybe_par!(self.num_qubits, self.data, 2 * d_max, kernel);
2482        Ok(())
2483    }
2484
2485    /// Applies controlled-RY gate.
2486    ///
2487    /// Applies RY(θ) to target when control is |1⟩.
2488    ///
2489    /// # Arguments
2490    /// * `control` - Control qubit index
2491    /// * `target` - Target qubit index
2492    /// * `theta` - Rotation angle
2493    pub fn apply_cry(&mut self, control: usize, target: usize, theta: f64) -> Result<(), QisError> {
2494        self.validate_two_qubits(control, target)?;
2495        if theta.abs() < 1e-15 {
2496            return Ok(());
2497        }
2498
2499        let (q_min, q_max) = if control < target {
2500            (control, target)
2501        } else {
2502            (target, control)
2503        };
2504        let d_max = 1 << q_max;
2505        let d_min = 1 << q_min;
2506
2507        let half_theta = theta * 0.5;
2508        let c = half_theta.cos();
2509        let s = half_theta.sin();
2510
2511        let kernel = |chunk: &mut [Complex64]| {
2512            let (part0, part1) = chunk.split_at_mut(d_max);
2513
2514            if control < target {
2515                // Control is q_min, target is q_max
2516                // Need to pair |Target=0, Control=1> (part0, sub upper) with |Target=1, Control=1> (part1, sub upper)
2517                part0
2518                    .chunks_exact_mut(2 * d_min)
2519                    .zip(part1.chunks_exact_mut(2 * d_min))
2520                    .for_each(|(sub0, sub1)| {
2521                        // sub0: target=0, sub1: target=1
2522                        // In each sub, lower half is control=0, upper half is control=1
2523                        let (_, target0_c1) = sub0.split_at_mut(d_min);
2524                        let (_, target1_c1) = sub1.split_at_mut(d_min);
2525
2526                        // Apply RY rotation to target when control=1
2527                        for (a, b) in target0_c1.iter_mut().zip(target1_c1.iter_mut()) {
2528                            let a_in = *a;
2529                            let b_in = *b;
2530                            *a = c * a_in - s * b_in;
2531                            *b = s * a_in + c * b_in;
2532                        }
2533                    });
2534            } else {
2535                // Control is q_max, target is q_min
2536                // Process pairs where control=1
2537                part0
2538                    .chunks_exact_mut(2 * d_min)
2539                    .zip(part1.chunks_exact_mut(2 * d_min))
2540                    .for_each(|(_, sub1)| {
2541                        // sub0: control=0, sub1: control=1
2542                        // In sub1, split by target
2543                        let (target0_ctl1, target1_ctl1) = sub1.split_at_mut(d_min);
2544
2545                        // Apply RY rotation to target when control=1
2546                        for (a, b) in target0_ctl1.iter_mut().zip(target1_ctl1.iter_mut()) {
2547                            let a_in = *a;
2548                            let b_in = *b;
2549                            *a = c * a_in - s * b_in;
2550                            *b = s * a_in + c * b_in;
2551                        }
2552                    });
2553            }
2554        };
2555        with_maybe_par!(self.num_qubits, self.data, 2 * d_max, kernel);
2556        Ok(())
2557    }
2558
2559    /// Applies controlled-RZ gate.
2560    ///
2561    /// Applies RZ(θ) to target when control is |1⟩.
2562    ///
2563    /// # Arguments
2564    /// * `control` - Control qubit index
2565    /// * `target` - Target qubit index
2566    /// * `theta` - Rotation angle
2567    pub fn apply_crz(&mut self, control: usize, target: usize, theta: f64) -> Result<(), QisError> {
2568        self.validate_two_qubits(control, target)?;
2569        if theta.abs() < 1e-15 {
2570            return Ok(());
2571        }
2572
2573        let (q_min, q_max) = if control < target {
2574            (control, target)
2575        } else {
2576            (target, control)
2577        };
2578        let d_max = 1 << q_max;
2579        let d_min = 1 << q_min;
2580
2581        let half_theta = theta * 0.5;
2582        let phase_0 = Complex64::from_polar(1.0, -half_theta);
2583        let phase_1 = Complex64::from_polar(1.0, half_theta);
2584
2585        let kernel = |chunk: &mut [Complex64]| {
2586            let (part0, part1) = chunk.split_at_mut(d_max);
2587
2588            if control < target {
2589                // Control is q_min, target is q_max
2590                // Need to pair |Target=0, Control=1> (part0, sub upper) with |Target=1, Control=1> (part1, sub upper)
2591                part0
2592                    .chunks_exact_mut(2 * d_min)
2593                    .zip(part1.chunks_exact_mut(2 * d_min))
2594                    .for_each(|(sub0, sub1)| {
2595                        // sub0: target=0, sub1: target=1
2596                        // In each sub, lower half is control=0, upper half is control=1
2597                        let (_, target0_c1) = sub0.split_at_mut(d_min);
2598                        let (_, target1_c1) = sub1.split_at_mut(d_min);
2599
2600                        // Apply RZ rotation to target when control=1
2601                        for (a, b) in target0_c1.iter_mut().zip(target1_c1.iter_mut()) {
2602                            *a *= phase_0;
2603                            *b *= phase_1;
2604                        }
2605                    });
2606            } else {
2607                // Control is q_max, target is q_min
2608                // Process pairs where control=1
2609                part0
2610                    .chunks_exact_mut(2 * d_min)
2611                    .zip(part1.chunks_exact_mut(2 * d_min))
2612                    .for_each(|(_, sub1)| {
2613                        // sub0: control=0, sub1: control=1
2614                        // In sub1, split by target
2615                        let (target0_ctl1, target1_ctl1) = sub1.split_at_mut(d_min);
2616
2617                        // When control=1, apply RZ to target
2618                        for (a, b) in target0_ctl1.iter_mut().zip(target1_ctl1.iter_mut()) {
2619                            *a *= phase_0;
2620                            *b *= phase_1;
2621                        }
2622                    });
2623            }
2624        };
2625        with_maybe_par!(self.num_qubits, self.data, 2 * d_max, kernel);
2626        Ok(())
2627    }
2628
2629    /// Applies Fermionic Simulation (fSim) gate.
2630    ///
2631    /// Native gate for superconducting qubits. Combines iSWAP and controlled-phase.
2632    ///
2633    /// Matrix: [[1, 0, 0, 0], [0, c, -i·s, 0], [0, -i·s, c, 0], [0, 0, 0, e^(-iφ)]]
2634    /// where c = cos(θ), s = sin(θ)
2635    ///
2636    /// # Arguments
2637    /// * `q0` - First qubit index
2638    /// * `q1` - Second qubit index
2639    /// * `theta` - iSWAP angle
2640    /// * `phi` - Controlled-phase angle
2641    pub fn apply_fsim(
2642        &mut self,
2643        q0: usize,
2644        q1: usize,
2645        theta: f64,
2646        phi: f64,
2647    ) -> Result<(), QisError> {
2648        self.validate_two_qubits(q0, q1)?;
2649
2650        let (q_min, q_max) = if q0 < q1 { (q0, q1) } else { (q1, q0) };
2651        let d_max = 1 << q_max;
2652        let d_min = 1 << q_min;
2653
2654        let c = theta.cos();
2655        let s = theta.sin();
2656        let phase_11 = Complex64::from_polar(1.0, -phi);
2657
2658        let kernel = |chunk: &mut [Complex64]| {
2659            let (part0, part1) = chunk.split_at_mut(d_max);
2660            part0
2661                .chunks_exact_mut(2 * d_min)
2662                .zip(part1.chunks_exact_mut(2 * d_min))
2663                .for_each(|(sub0, sub1)| {
2664                    let (_, v01) = sub0.split_at_mut(d_min);
2665                    let (v10, v11) = sub1.split_at_mut(d_min);
2666
2667                    // |11⟩ gets phase
2668                    v11.iter_mut().for_each(|d| *d *= phase_11);
2669
2670                    // |01⟩ and |10⟩ get mixed
2671                    for (b, c_amp) in v01.iter_mut().zip(v10.iter_mut()) {
2672                        let b_in = *b;
2673                        let c_in = *c_amp;
2674                        *b = c * b_in + Complex64::new(0.0, -s) * c_in;
2675                        *c_amp = Complex64::new(0.0, -s) * b_in + c * c_in;
2676                    }
2677                });
2678        };
2679        with_maybe_par!(self.num_qubits, self.data, 2 * d_max, kernel);
2680        Ok(())
2681    }
2682
2683    /// Applies global phase.
2684    ///
2685    /// Multiplies the entire statevector by e^(iφ).
2686    /// Physically undetectable but mathematically relevant for circuit composition.
2687    ///
2688    /// # Arguments
2689    /// * `phi` - Phase angle
2690    pub fn apply_gphase(&mut self, phi: f64) -> Result<(), QisError> {
2691        if phi.abs() < 1e-15 {
2692            return Ok(());
2693        }
2694        let phase = Complex64::from_polar(1.0, phi);
2695        if self.num_qubits < PARALLEL_THRESHOLD {
2696            self.data.iter_mut().for_each(|a| *a *= phase);
2697        } else {
2698            self.data.par_iter_mut().for_each(|a| *a *= phase);
2699        }
2700        Ok(())
2701    }
2702
2703    /// Computes the expectation value of a Hamiltonian observable.
2704    ///
2705    /// Calculates ⟨ψ|H|ψ⟩ for the current statevector |ψ⟩ and a given Hamiltonian H.
2706    /// The Hamiltonian is represented as a sum of Pauli strings with coefficients.
2707    ///
2708    /// # Arguments
2709    /// * `h` - The Hamiltonian observable.
2710    ///
2711    /// # Returns
2712    /// The expectation value as a real number (f64), or a [`QisError`] if the
2713    /// qubit counts do not match.
2714    ///
2715    /// # Errors
2716    /// Returns [`QisError::CircuitError`] if the observable acts on a different
2717    /// number of qubits than the statevector.
2718    ///
2719    /// # Example
2720    /// ```rust
2721    /// use cqlib_core::qis::{Statevector, Hamiltonian};
2722    /// use cqlib_core::qis::pauli::{Pauli, PauliString};
2723    /// use num_complex::Complex64;
2724    ///
2725    /// let mut sv = Statevector::new(1);
2726    /// sv.apply_x(0);
2727    ///
2728    /// // Create Hamiltonian H = Z (Pauli-Z observable)
2729    /// let mut ps = PauliString::new(1);
2730    /// ps.set_pauli(0, Pauli::Z);
2731    /// let h = Hamiltonian::from_pauli(ps);
2732    ///
2733    /// let exp = sv.expectation(&h).unwrap();
2734    /// // For |1⟩ state, ⟨Z⟩ = -1
2735    /// ```
2736    pub fn expectation(&self, h: &dyn Observable) -> Result<f64, QisError> {
2737        h.expectation_statevector(self)
2738    }
2739
2740    /// Measures qubit `qubit` in the Z basis, collapsing the statevector.
2741    ///
2742    /// Returns `false` for outcome |0⟩ and `true` for outcome |1⟩.
2743    /// After measurement the statevector is normalised in the post-measurement subspace.
2744    ///
2745    /// This is a destructive operation — clone the statevector first if you need
2746    /// to preserve the pre-measurement state.
2747    pub fn measure(&mut self, qubit: usize) -> Result<bool, QisError> {
2748        let mut rng = rand::rng();
2749        self.measure_with_rng(qubit, &mut rng)
2750    }
2751
2752    /// Measures qubit `qubit` using a caller-provided random number generator.
2753    ///
2754    /// This is useful for reproducible simulations. For one-off non-deterministic
2755    /// measurements, use [`measure`](Self::measure).
2756    pub fn measure_with_rng(&mut self, qubit: usize, rng: &mut impl Rng) -> Result<bool, QisError> {
2757        self.validate_qubit(qubit)?;
2758        let n = self.num_qubits;
2759        let size = 1 << n;
2760        let mask = 1 << qubit;
2761
2762        // Compute probability of outcome |1⟩.
2763        let p1: f64 = (0..size)
2764            .filter(|i| i & mask != 0)
2765            .map(|i| self.data[i].norm_sqr())
2766            .sum();
2767
2768        let outcome = rng.random::<f64>() < p1;
2769
2770        // Collapse and renormalise.
2771        let norm = if outcome {
2772            p1.sqrt()
2773        } else {
2774            (1.0 - p1).sqrt()
2775        };
2776        for i in 0..size {
2777            let keep = if outcome {
2778                (i & mask) != 0
2779            } else {
2780                (i & mask) == 0
2781            };
2782            if keep {
2783                self.data[i] /= norm;
2784            } else {
2785                self.data[i] = Complex64::new(0.0, 0.0);
2786            }
2787        }
2788        Ok(outcome)
2789    }
2790
2791    /// Resets `qubit` to the |0⟩ state.
2792    ///
2793    /// Reset is modeled as a Z-basis measurement followed by an X correction
2794    /// when the measured outcome is |1⟩. This is destructive, matching circuit
2795    /// reset semantics.
2796    pub fn reset(&mut self, qubit: usize) -> Result<(), QisError> {
2797        if self.measure(qubit)? {
2798            self.apply_x(qubit)?;
2799        }
2800        Ok(())
2801    }
2802
2803    /// Copies amplitude data from `source` into `self` without allocating.
2804    ///
2805    /// Both statevectors must have the same `num_qubits`. Used by
2806    /// [`sample_shots`](Self::sample_shots) to reuse each thread's working copy.
2807    fn reset_from(&mut self, source: &Statevector) {
2808        debug_assert_eq!(self.num_qubits, source.num_qubits);
2809        self.qubit_map.clone_from(&source.qubit_map);
2810        self.data
2811            .as_mut_slice()
2812            .copy_from_slice(source.data.as_slice());
2813    }
2814
2815    /// Measures all qubits sequentially, returning a bit-packed [`Outcome`].
2816    ///
2817    /// Equivalent to calling `measure(q)` for each qubit `q` in order `0..num_qubits`.
2818    /// The statevector is fully collapsed after this call.
2819    /// Use [`Outcome::is_one(q)`](crate::device::Outcome::is_one) to read qubit `q`'s result.
2820    pub fn measure_all(&mut self) -> Outcome {
2821        self.measure_all_with_rng(&mut rand::rng())
2822    }
2823
2824    /// Measures all qubits in storage order using the supplied random stream.
2825    pub fn measure_all_with_rng(&mut self, rng: &mut impl Rng) -> Outcome {
2826        let num_chunks = self.num_qubits.div_ceil(64);
2827        let mut chunks = SmallVec::from_elem(0u64, num_chunks);
2828        for q in 0..self.num_qubits {
2829            if self.measure_with_rng(q, rng).unwrap() {
2830                chunks[q / 64] |= 1u64 << (q % 64);
2831            }
2832        }
2833        Outcome::new(chunks)
2834    }
2835
2836    /// Samples `shots` independent measurement outcomes in parallel.
2837    ///
2838    /// Each Rayon worker thread reuses a single pre-allocated clone of the
2839    /// statevector (via [`reset_from`](Self::reset_from)), avoiding per-shot
2840    /// heap allocation. Returns a [`Vec<Outcome>`] of bit-packed results.
2841    ///
2842    /// # Example
2843    /// ```rust
2844    /// use cqlib_core::qis::Statevector;
2845    ///
2846    /// let mut sv = Statevector::new(2);
2847    /// sv.apply_h(0).unwrap();
2848    /// sv.apply_cx(0, 1).unwrap(); // |Φ⁺⟩ Bell state
2849    ///
2850    /// let shots = sv.sample_shots(200);
2851    /// // All outcomes must be |00⟩ or |11⟩
2852    /// assert!(shots.iter().all(|v| v.is_one(0) == v.is_one(1)));
2853    /// ```
2854    pub fn sample_shots(&self, shots: usize) -> Vec<Outcome> {
2855        self.sample_shots_with_seed(shots, None)
2856    }
2857
2858    /// Samples without changing this state. An explicit seed assigns a random
2859    /// stream to each shot before parallel execution, independent of scheduling.
2860    pub fn sample_shots_with_seed(&self, shots: usize, seed: Option<u64>) -> Vec<Outcome> {
2861        if let Some(seed) = seed {
2862            let mut rng = SmallRng::seed_from_u64(seed);
2863            let seeds: Vec<u64> = (0..shots).map(|_| rng.random()).collect();
2864            return seeds
2865                .into_par_iter()
2866                .map_with(self.clone(), |work, seed| {
2867                    work.reset_from(self);
2868                    work.measure_all_with_rng(&mut SmallRng::seed_from_u64(seed))
2869                })
2870                .collect();
2871        }
2872        (0..shots)
2873            .into_par_iter()
2874            .map_with(self.clone(), |work, _| {
2875                work.reset_from(self);
2876                work.measure_all()
2877            })
2878            .collect()
2879    }
2880
2881    /// Samples the state using a circuit [`Measurement`] as the output contract.
2882    ///
2883    /// `measurement.qubits()[i]` becomes bit `i` in each [`Outcome`]. The returned
2884    /// [`ExecutionResult`] reuses the device result shape, with counts projected
2885    /// to only the qubits named by the measurement.
2886    pub fn sample(
2887        &self,
2888        measurement: &Measurement,
2889        shots: usize,
2890    ) -> Result<ExecutionResult, QisError> {
2891        self.sample_with_seed(measurement, shots, None)
2892    }
2893
2894    /// Projects seeded full-state samples onto the requested measurement.
2895    pub fn sample_with_seed(
2896        &self,
2897        measurement: &Measurement,
2898        shots: usize,
2899        seed: Option<u64>,
2900    ) -> Result<ExecutionResult, QisError> {
2901        let projection = super::resolve_measurement(measurement, &self.qubit_map, self.num_qubits)?;
2902
2903        let mut counts = HashMap::new();
2904        for full in self.sample_shots_with_seed(shots, seed) {
2905            *counts.entry(projection.project(&full)).or_insert(0usize) += 1;
2906        }
2907
2908        Ok(ExecutionResult::from_counts(
2909            "statevector-sample".to_string(),
2910            measurement.qubits().to_vec(),
2911            shots,
2912            measurement.width(),
2913            Some("statevector".to_string()),
2914            counts,
2915        ))
2916    }
2917
2918    /// Returns the marginal probability distribution selected by a circuit [`Measurement`].
2919    ///
2920    /// This method does not execute circuit IR. It treats the [`Measurement`] as
2921    /// a self-contained output contract and aggregates the full statevector
2922    /// computational-basis probabilities over unmeasured qubits.
2923    pub fn probs(&self, measurement: &Measurement) -> Result<HashMap<Outcome, f64>, QisError> {
2924        let projection = super::resolve_measurement(measurement, &self.qubit_map, self.num_qubits)?;
2925
2926        let mut marginal = HashMap::new();
2927        for (basis, prob) in self.probabilities().into_iter().enumerate() {
2928            if prob == 0.0 {
2929                continue;
2930            }
2931            *marginal
2932                .entry(projection.project_basis(basis))
2933                .or_insert(0.0) += prob;
2934        }
2935        Ok(marginal)
2936    }
2937}
2938
2939#[cfg(test)]
2940#[path = "./statevector_test.rs"]
2941mod statevector_test;