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