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