Skip to main content

cqlib_core/qis/state/
stabilizer.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//! Stabilizer state simulator using the Aaronson-Gottesman tableau algorithm.
17//!
18//! Implements efficient simulation of Clifford circuits using the symplectic tableau
19//! representation from:
20//!
21//! > Aaronson & Gottesman, "Improved Simulation of Stabilizer Circuits",
22//! > Phys. Rev. A 70, 052328 (2004).
23//!
24//! Non-Clifford gates (T, TDG, RX, RZ, U, …) are rejected immediately with
25//! [`QisError::NonCliffordGate`] — use [`crate::qis::Statevector`] for universal circuits.
26//!
27//! # Supported Gates
28//!
29//! | Gate | Symbol | Kind |
30//! |------|--------|------|
31//! | Hadamard | H | single-qubit |
32//! | Phase | S | single-qubit |
33//! | Phase-dagger | S† / SDG | single-qubit |
34//! | Pauli-X | X | single-qubit |
35//! | Pauli-Y | Y | single-qubit |
36//! | Pauli-Z | Z | single-qubit |
37//! | √X | X2P / SX | single-qubit |
38//! | √X† | X2M / SXdg | single-qubit |
39//! | √Y | Y2P / SY | single-qubit |
40//! | √Y† | Y2M / SYdg | single-qubit |
41//! | Controlled-NOT | CX / CNOT | two-qubit |
42//! | Controlled-Y | CY | two-qubit |
43//! | Controlled-Z | CZ | two-qubit |
44//! | SWAP | SWAP | two-qubit |
45//!
46//! # Performance
47//!
48//! | Operation | Complexity | Notes |
49//! |-----------|-----------|-------|
50//! | Single-qubit gate | O(n) | one pass over 2n rows |
51//! | Two-qubit gate | O(n) | one pass over 2n rows |
52//! | Measurement (random) | O(n²/w) | SIMD-accelerated rowsum, w = 64 |
53//! | Measurement (deterministic) | O(n²/w) | scratch-row accumulation via rowsum |
54//! | Memory | O(n²/w) | ~50 MB for n = 10 000 |
55//!
56//! Multi-shot sampling uses Rayon for parallel execution; each shot gets an
57//! independent clone of the state.
58//!
59//! # Memory Layout
60//!
61//! The tableau uses a single flat `Box<[u64]>` for cache efficiency:
62//! - `2n + 1` rows total: rows `0..n` = destabilizers, rows `n..2n` = stabilizers,
63//!   row `2n` = pre-allocated scratch for deterministic measurement.
64//! - Each row: `row_len` u64 words for the X-block, then `row_len` words for the Z-block:
65//!   `[x₀..xₙ | z₀..zₙ]` packed into `row_len` words each.
66//! - `row_len` is padded to a multiple of 8 (512 bits) for AVX-512 alignment.
67//! - Phases stored separately in `phases: Box<[u8]>` as `0` (= +1), `1` (= +i), `2` (= −1), `3` (= −i).
68//!
69//! # Bit Addressing
70//!
71//! Qubit `q` maps to word `q / 64` and bit `q % 64` within both the X and Z blocks
72//! of every row.
73//!
74//! # Examples
75//!
76//! **Bell state preparation and measurement:**
77//! ```rust
78//! use cqlib_core::qis::StabilizerState;
79//!
80//! let mut s = StabilizerState::new(2);
81//! s.apply_h(0).unwrap();
82//! s.apply_cx(0, 1).unwrap(); // |Φ⁺⟩ = (|00⟩ + |11⟩)/√2
83//!
84//! // Outcomes are always correlated
85//! let shots = s.sample_shots(500);
86//! assert!(shots.iter().all(|v| v.is_one(0) == v.is_one(1)));
87//! ```
88//!
89//! **GHZ state — 1000 qubits:**
90//! ```rust
91//! use cqlib_core::qis::StabilizerState;
92//!
93//! let n = 1000;
94//! let mut s = StabilizerState::new(n);
95//! s.apply_h(0).unwrap();
96//! for q in 0..n - 1 {
97//!     s.apply_cx(q, q + 1).unwrap();
98//! }
99//! // All qubits measure identically
100//! let mut copy = s.clone();
101//! let result = copy.measure_all();
102//! let first = result.is_one(0);
103//! assert!((0..n).all(|q| result.is_one(q) == first));
104//! ```
105//!
106//! **Deterministic measurement on |0⟩:**
107//! ```rust
108//! use cqlib_core::qis::StabilizerState;
109//!
110//! let mut s = StabilizerState::new(3);
111//! // |000⟩ state — all measurements deterministically 0
112//! assert_eq!(s.measure(0).unwrap(), false);
113//! assert_eq!(s.measure(1).unwrap(), false);
114//! assert_eq!(s.measure(2).unwrap(), false);
115//! ```
116//!
117//! **From a Clifford circuit:**
118//! ```rust
119//! use cqlib_core::circuit::{Circuit, Qubit};
120//! use cqlib_core::qis::StabilizerState;
121//!
122//! let mut c = Circuit::new(2);
123//! c.h(Qubit::new(0)).unwrap();
124//! c.cx(Qubit::new(0), Qubit::new(1)).unwrap();
125//! let stab = StabilizerState::from_circuit(&c).unwrap();
126//! let stabilizers = stab.get_stabilizers();
127//! // Bell state is stabilized by +XX and +ZZ
128//! assert_eq!(stabilizers.len(), 2);
129//! ```
130//!
131//! **Circuit measurement results:**
132//! ```rust
133//! use cqlib_core::circuit::{Circuit, ClassicalType, Qubit};
134//! use cqlib_core::qis::{RuntimeValue, StabilizerState};
135//!
136//! let mut c = Circuit::new(3);
137//! c.x(Qubit::new(0)).unwrap();
138//! c.x(Qubit::new(2)).unwrap();
139//!
140//! // The returned measurement carries both the IR value and measured qubit order.
141//! let bit = c.measure(Qubit::new(0)).unwrap();
142//!
143//! // `measure_bits_into` also writes a mutable variable copy for later classical use.
144//! let latest = c.var(ClassicalType::bit_vec(3).unwrap());
145//! let bits = c
146//!     .measure_bits_into([Qubit::new(0), Qubit::new(1), Qubit::new(2)], latest)
147//!     .unwrap();
148//!
149//! let execution = StabilizerState::run_circuit(&c).unwrap();
150//! assert_eq!(execution.classical.value(bit.value()), Some(&RuntimeValue::Bit(true)));
151//! assert_eq!(
152//!     execution.classical.value(bits.value()).and_then(RuntimeValue::to_bitstring).as_deref(),
153//!     Some("101")
154//! );
155//! assert_eq!(execution.classical.var(latest), execution.classical.value(bits.value()));
156//! ```
157//!
158//! **Non-Clifford gate returns an error:**
159//! ```rust
160//! use cqlib_core::circuit::{Circuit, Qubit};
161//! use cqlib_core::qis::{StabilizerState, QisError};
162//!
163//! let mut c = Circuit::new(1);
164//! c.t(Qubit::new(0)).unwrap(); // T gate is not Clifford
165//! let result = StabilizerState::from_circuit(&c);
166//! assert!(matches!(result, Err(QisError::NonCliffordGate(_))));
167//! ```
168
169use super::aligned_buffer::AlignedBuffer;
170use crate::circuit::circuit_impl::Circuit;
171use crate::circuit::circuit_param::CircuitParam;
172use crate::circuit::error::CircuitError;
173use crate::circuit::gate::directive::Directive;
174use crate::circuit::gate::{ClassicalDataOp, Instruction, StandardGate};
175use crate::circuit::operation::Operation;
176use crate::circuit::{Measurement, Qubit};
177use crate::device::{ExecutionResult, Outcome};
178use crate::qis::error::QisError;
179use crate::qis::pauli::{Pauli, PauliString, Phase};
180use crate::qis::state::{ClassicalState, RuntimeValue};
181use rand::rngs::SmallRng;
182use rand::{Rng, SeedableRng};
183use rayon::prelude::*;
184use smallvec::SmallVec;
185use std::collections::HashMap;
186
187/// Result returned by [`StabilizerState::run_circuit`].
188///
189/// Contains both the final quantum state and runtime classical data produced
190/// while executing measurement and store operations.
191#[derive(Debug)]
192pub struct CircuitExecutionResult {
193    /// Final stabilizer state after all circuit operations.
194    pub state: StabilizerState,
195    /// Runtime classical values and variables indexed by circuit-local handles.
196    pub classical: ClassicalState,
197}
198
199#[derive(Debug, Clone, Copy, PartialEq, Eq)]
200enum CircuitClassicalMode {
201    Ignore,
202    Execute,
203}
204
205/// Stabilizer state simulator based on the Aaronson-Gottesman symplectic tableau.
206///
207/// Represents an n-qubit stabilizer state using 2n generator rows, each composed
208/// of an X-block and Z-block of packed `u64` words, plus a phase per row.
209#[derive(Debug)]
210pub struct StabilizerState {
211    /// Circuit qubit identifiers mapped to storage positions.
212    qubit_map: super::QubitMap,
213    /// Number of qubits.
214    pub num_qubits: usize,
215    /// Number of `u64` words per block (X-block or Z-block) per row.
216    /// Padded to a multiple of 8 for 512-bit SIMD alignment.
217    row_len: usize,
218    /// Flat tableau storage: `(2n+1)` rows × `2 * row_len` words, 64-byte aligned.
219    /// Row `i` occupies `tableau[i * 2 * row_len .. (i+1) * 2 * row_len]`.
220    /// Within a row: first `row_len` words = X-block, next `row_len` words = Z-block.
221    /// Row `2n` is the pre-allocated scratch row for deterministic measurement.
222    tableau: AlignedBuffer<u64>,
223    /// Phase for each of the `2n+1` rows: `0`=+1, `1`=+i, `2`=−1, `3`=−i.
224    phases: Box<[u8]>,
225    /// RNG for random measurement outcomes.
226    rng: SmallRng,
227}
228
229// `xor_rows_dispatch` XORs `count` u64 words from `src_base` into `dst_base`
230// within `tableau`, dispatching to the best available SIMD tier at runtime.
231// `count` is always a multiple of 16 (= 2 × row_len, row_len ≥ 8), so no tail
232// handling is required for any SIMD width up to 512 bits.
233//
234// SAFETY contract shared by all variants:
235//   • `dst_base + count ≤ tableau.len()` and `src_base + count ≤ tableau.len()`
236//   • `[dst_base, dst_base+count)` and `[src_base, src_base+count)` do not overlap
237//     (guaranteed by the caller via distinct row indices h ≠ i).
238
239/// AVX2 path: 256-bit XOR, processing 4 u64 words per iteration.
240///
241/// Requires 32-byte alignment. Our `AlignedBuffer` provides 64-byte alignment,
242/// so aligned loads (`_mm256_load_si256`) are safe for every row start.
243#[cfg(target_arch = "x86_64")]
244#[target_feature(enable = "avx2")]
245unsafe fn xor_rows_avx2(tableau: &mut [u64], dst_base: usize, src_base: usize, count: usize) {
246    use std::arch::x86_64::{__m256i, _mm256_load_si256, _mm256_store_si256, _mm256_xor_si256};
247    // SAFETY: avx2 feature enabled; AlignedBuffer guarantees 64-byte alignment
248    // (≥ required 32-byte AVX2 alignment); caller guarantees bounds + non-overlap.
249    unsafe {
250        let ptr = tableau.as_mut_ptr();
251        let dst = ptr.add(dst_base) as *mut __m256i;
252        let src = ptr.add(src_base) as *const __m256i;
253        let chunks = count / 4; // 4 u64 per __m256i (256 bits)
254        for i in 0..chunks {
255            let a = _mm256_load_si256(dst.add(i));
256            let b = _mm256_load_si256(src.add(i));
257            _mm256_store_si256(dst.add(i), _mm256_xor_si256(a, b));
258        }
259    }
260}
261
262/// SSE2 path: 128-bit XOR, processing 2 u64 words per iteration.
263///
264/// Requires 16-byte alignment. Our `AlignedBuffer` provides 64-byte alignment.
265#[cfg(target_arch = "x86_64")]
266#[target_feature(enable = "sse2")]
267unsafe fn xor_rows_sse2(tableau: &mut [u64], dst_base: usize, src_base: usize, count: usize) {
268    use std::arch::x86_64::{__m128i, _mm_load_si128, _mm_store_si128, _mm_xor_si128};
269    // SAFETY: sse2 feature enabled; AlignedBuffer guarantees 64-byte alignment
270    // (≥ required 16-byte SSE2 alignment); caller guarantees bounds + non-overlap.
271    unsafe {
272        let ptr = tableau.as_mut_ptr();
273        let dst = ptr.add(dst_base) as *mut __m128i;
274        let src = ptr.add(src_base) as *const __m128i;
275        let chunks = count / 2; // 2 u64 per __m128i (128 bits)
276        for i in 0..chunks {
277            let a = _mm_load_si128(dst.add(i));
278            let b = _mm_load_si128(src.add(i));
279            _mm_store_si128(dst.add(i), _mm_xor_si128(a, b));
280        }
281    }
282}
283
284/// NEON path: 128-bit XOR, processing 2 u64 words per iteration.
285/// NEON is mandatory on aarch64; no runtime feature detection required.
286#[cfg(target_arch = "aarch64")]
287#[target_feature(enable = "neon")]
288unsafe fn xor_rows_neon(tableau: &mut [u64], dst_base: usize, src_base: usize, count: usize) {
289    use std::arch::aarch64::{veorq_u64, vld1q_u64, vst1q_u64};
290    // SAFETY: NEON is mandatory on aarch64; caller guarantees bounds and non-overlap.
291    unsafe {
292        let ptr = tableau.as_mut_ptr();
293        let dst = ptr.add(dst_base);
294        let src = ptr.add(src_base) as *const u64;
295        let chunks = count / 2; // 2 u64 per uint64x2_t (128 bits)
296        for i in 0..chunks {
297            let a = vld1q_u64(dst.add(i * 2) as *const u64);
298            let b = vld1q_u64(src.add(i * 2));
299            vst1q_u64(dst.add(i * 2), veorq_u64(a, b));
300        }
301    }
302}
303
304/// Scalar fallback: word loop that the compiler auto-vectorizes at -O2.
305/// Used on unsupported architectures or when SIMD features are unavailable.
306#[inline]
307fn xor_rows_scalar(tableau: &mut [u64], dst_base: usize, src_base: usize, count: usize) {
308    // SAFETY: use raw pointer to read src while dst is mutably borrowed from the
309    // same slice.  Non-overlap is guaranteed by the caller.
310    let ptr = tableau.as_mut_ptr();
311    for w in 0..count {
312        // SAFETY: dst_base+w < tableau.len() and src_base+w < tableau.len().
313        unsafe { *ptr.add(dst_base + w) ^= *ptr.add(src_base + w) };
314    }
315}
316
317/// Runtime-dispatched XOR of `count` u64 words from `src_base` into `dst_base`.
318///
319/// # Safety
320/// * `dst_base + count ≤ tableau.len()` and `src_base + count ≤ tableau.len()`
321/// * `[dst_base, dst_base+count)` and `[src_base, src_base+count)` must not overlap.
322#[inline]
323#[allow(unreachable_code)] // scalar tail unreachable on aarch64 (always takes NEON path)
324unsafe fn xor_rows_dispatch(tableau: &mut [u64], dst_base: usize, src_base: usize, count: usize) {
325    #[cfg(target_arch = "x86_64")]
326    {
327        if is_x86_feature_detected!("avx2") {
328            // SAFETY: avx2 confirmed; bounds + non-overlap guaranteed by caller.
329            return unsafe { xor_rows_avx2(tableau, dst_base, src_base, count) };
330        }
331        if is_x86_feature_detected!("sse2") {
332            // SAFETY: sse2 confirmed; bounds + non-overlap guaranteed by caller.
333            return unsafe { xor_rows_sse2(tableau, dst_base, src_base, count) };
334        }
335    }
336    #[cfg(target_arch = "aarch64")]
337    {
338        // SAFETY: NEON mandatory on aarch64; bounds + non-overlap guaranteed by caller.
339        return unsafe { xor_rows_neon(tableau, dst_base, src_base, count) };
340    }
341    xor_rows_scalar(tableau, dst_base, src_base, count);
342}
343
344impl StabilizerState {
345    /// Creates a new stabilizer state initialized to |0...0⟩.
346    ///
347    /// In the Aaronson-Gottesman convention the initial generators are:
348    /// - Destabilizer `i` (row `i`):   X on qubit `i`, identity elsewhere, phase +1.
349    /// - Stabilizer  `i` (row `n+i`):  Z on qubit `i`, identity elsewhere, phase +1.
350    ///
351    /// # Arguments
352    /// * `n` — number of qubits
353    pub fn new(n: usize) -> Self {
354        assert!(n > 0, "StabilizerState requires at least 1 qubit");
355
356        // Pad row_len to the next multiple of 8 u64 words (= 512 bits).
357        let words_needed = n.div_ceil(64);
358        let row_len = ((words_needed + 7) & !7).max(8);
359
360        // Allocate 2n+1 rows: rows 0..n = destabilizers, n..2n = stabilizers,
361        // row 2n = persistent scratch (used by deterministic measurement).
362        let total_rows = 2 * n + 1;
363        let total_words = total_rows * 2 * row_len;
364        let mut tableau = AlignedBuffer::new_zeroed(total_words);
365        let phases = vec![0u8; total_rows].into_boxed_slice();
366
367        // Initialise destabilizers: row i has X bit set at column i.
368        for i in 0..n {
369            let word = i / 64;
370            let bit = i % 64;
371            tableau[Self::x_offset(i, i, row_len, word)] |= 1u64 << bit;
372        }
373
374        // Initialise stabilizers: row n+i has Z bit set at column i.
375        for i in 0..n {
376            let word = i / 64;
377            let bit = i % 64;
378            tableau[Self::z_offset(n + i, i, row_len, word)] |= 1u64 << bit;
379        }
380
381        StabilizerState {
382            num_qubits: n,
383            qubit_map: None,
384            row_len,
385            tableau,
386            phases,
387            rng: SmallRng::from_os_rng(),
388        }
389    }
390
391    /// Copies tableau and phase data from `source` into `self` without allocating.
392    ///
393    /// The two states must have the same number of qubits (and therefore the same
394    /// `row_len`). The RNG is **not** copied — the caller is expected to manage it
395    /// separately (e.g., re-seeding for each shot).
396    ///
397    /// This is used by [`sample_shots`](Self::sample_shots) to reuse a single
398    /// pre-allocated working buffer per Rayon worker thread.
399    fn reset_from(&mut self, source: &StabilizerState) {
400        debug_assert_eq!(self.num_qubits, source.num_qubits);
401        self.qubit_map.clone_from(&source.qubit_map);
402        debug_assert_eq!(self.row_len, source.row_len);
403        self.tableau
404            .as_mut_slice()
405            .copy_from_slice(source.tableau.as_slice());
406        self.phases.copy_from_slice(&source.phases);
407    }
408
409    /// Index of the persistent scratch row in the tableau (= `2n`).
410    #[inline(always)]
411    fn scratch_row(&self) -> usize {
412        2 * self.num_qubits
413    }
414
415    /// Zeroes the scratch row's bit-columns and phase, ready for a new accumulation.
416    fn clear_scratch(&mut self) {
417        let s = self.scratch_row();
418        let base = Self::row_base(s, self.row_len);
419        for w in 0..2 * self.row_len {
420            self.tableau[base + w] = 0;
421        }
422        self.phases[s] = 0;
423    }
424
425    /// Base byte offset (in u64 words) of row `row` in the tableau.
426    #[inline(always)]
427    fn row_base(row: usize, row_len: usize) -> usize {
428        row * 2 * row_len
429    }
430
431    /// Flat index into `tableau` for the `word`-th u64 in the X-block of `row`,
432    /// where `col_qubit` determines which X-block column we're accessing.
433    /// (The `word` parameter is `col_qubit / 64`.)
434    #[inline(always)]
435    fn x_offset(row: usize, _col_qubit: usize, row_len: usize, word: usize) -> usize {
436        Self::row_base(row, row_len) + word
437    }
438
439    /// Flat index into `tableau` for the `word`-th u64 in the Z-block of `row`.
440    #[inline(always)]
441    fn z_offset(row: usize, _col_qubit: usize, row_len: usize, word: usize) -> usize {
442        Self::row_base(row, row_len) + row_len + word
443    }
444
445    /// Returns the X-bit of `row` at qubit column `q`.
446    #[inline(always)]
447    pub(crate) fn x_bit(&self, row: usize, q: usize) -> bool {
448        let base = Self::row_base(row, self.row_len);
449        let word = q / 64;
450        let bit = q % 64;
451        (self.tableau[base + word] >> bit) & 1 == 1
452    }
453
454    /// Returns the Z-bit of `row` at qubit column `q`.
455    #[inline(always)]
456    pub(crate) fn z_bit(&self, row: usize, q: usize) -> bool {
457        let base = Self::row_base(row, self.row_len);
458        let word = q / 64;
459        let bit = q % 64;
460        (self.tableau[base + self.row_len + word] >> bit) & 1 == 1
461    }
462
463    /// Sets or clears the X-bit of `row` at qubit column `q`.
464    #[allow(dead_code)]
465    #[inline(always)]
466    pub(crate) fn set_x_bit(&mut self, row: usize, q: usize, val: bool) {
467        let base = Self::row_base(row, self.row_len);
468        let word = q / 64;
469        let bit = q % 64;
470        if val {
471            self.tableau[base + word] |= 1u64 << bit;
472        } else {
473            self.tableau[base + word] &= !(1u64 << bit);
474        }
475    }
476
477    /// Sets or clears the Z-bit of `row` at qubit column `q`.
478    #[inline(always)]
479    pub(crate) fn set_z_bit(&mut self, row: usize, q: usize, val: bool) {
480        let base = Self::row_base(row, self.row_len);
481        let word = q / 64;
482        let bit = q % 64;
483        if val {
484            self.tableau[base + self.row_len + word] |= 1u64 << bit;
485        } else {
486            self.tableau[base + self.row_len + word] &= !(1u64 << bit);
487        }
488    }
489
490    /// Returns the encoded phase of row `row`.
491    #[inline(always)]
492    pub(crate) fn phase(&self, row: usize) -> u8 {
493        self.phases[row]
494    }
495
496    /// Sets the phase for `row` to `val` (0–3 for +1, +i, −1, −i respectively).
497    #[inline(always)]
498    pub(crate) fn set_phase(&mut self, row: usize, val: u8) {
499        self.phases[row] = val & 3;
500    }
501
502    pub(crate) fn validate_qubit(&self, q: usize) -> Result<(), QisError> {
503        if self.num_qubits == 0 {
504            return Err(QisError::InvalidParameterValue(
505                "no valid qubit indices in a zero-qubit system".to_string(),
506            ));
507        }
508        if q >= self.num_qubits {
509            return Err(QisError::IndexOutOfBounds {
510                index: q,
511                max: self.num_qubits.saturating_sub(1),
512            });
513        }
514        Ok(())
515    }
516
517    pub(crate) fn validate_two_qubits(&self, q0: usize, q1: usize) -> Result<(), QisError> {
518        self.validate_qubit(q0)?;
519        self.validate_qubit(q1)?;
520        if q0 == q1 {
521            return Err(QisError::InvalidParameterValue(
522                "Two-qubit gate requires distinct qubit indices".to_string(),
523            ));
524        }
525        Ok(())
526    }
527
528    /// Applies the CNOT (CX) gate with `control` as control and `target` as target.
529    ///
530    /// Aaronson-Gottesman rule (Eq. 11) for each row `i`:
531    /// - `phase[i] ^= 2 * x[i][c] * z[i][t] * (x[i][t] ^ z[i][c] ^ 1)`
532    /// - `x[i][t] ^= x[i][c]`
533    /// - `z[i][c] ^= z[i][t]`
534    pub fn apply_cx(&mut self, control: usize, target: usize) -> Result<(), QisError> {
535        self.validate_two_qubits(control, target)?;
536        let c_word = control / 64;
537        let c_mask = 1u64 << (control % 64);
538        let t_word = target / 64;
539        let t_mask = 1u64 << (target % 64);
540        let rl = self.row_len;
541        for row in 0..(2 * self.num_qubits) {
542            let base = row * 2 * rl;
543            let xc = (self.tableau[base + c_word] & c_mask) != 0;
544            let xt = (self.tableau[base + t_word] & t_mask) != 0;
545            let zc = (self.tableau[base + rl + c_word] & c_mask) != 0;
546            let zt = (self.tableau[base + rl + t_word] & t_mask) != 0;
547            if xc && zt && (xt ^ zc ^ true) {
548                self.phases[row] ^= 2;
549            }
550            if xc {
551                self.tableau[base + t_word] ^= t_mask;
552            }
553            if zt {
554                self.tableau[base + rl + c_word] ^= c_mask;
555            }
556        }
557        Ok(())
558    }
559
560    /// Applies the SWAP gate between qubits `q0` and `q1`.
561    ///
562    /// Native rule: for each row `i`, swap (x[i][q0], z[i][q0]) with (x[i][q1], z[i][q1]).
563    /// No phase change — SWAP maps Paulis without sign.
564    pub fn apply_swap(&mut self, q0: usize, q1: usize) -> Result<(), QisError> {
565        self.validate_two_qubits(q0, q1)?;
566        let w0 = q0 / 64;
567        let m0 = 1u64 << (q0 % 64);
568        let w1 = q1 / 64;
569        let m1 = 1u64 << (q1 % 64);
570        let rl = self.row_len;
571        for row in 0..(2 * self.num_qubits) {
572            let base = row * 2 * rl;
573            // Read bits
574            let x0 = (self.tableau[base + w0] & m0) != 0;
575            let z0 = (self.tableau[base + rl + w0] & m0) != 0;
576            let x1 = (self.tableau[base + w1] & m1) != 0;
577            let z1 = (self.tableau[base + rl + w1] & m1) != 0;
578            // Write swapped bits
579            if x1 {
580                self.tableau[base + w0] |= m0;
581            } else {
582                self.tableau[base + w0] &= !m0;
583            }
584            if z1 {
585                self.tableau[base + rl + w0] |= m0;
586            } else {
587                self.tableau[base + rl + w0] &= !m0;
588            }
589            if x0 {
590                self.tableau[base + w1] |= m1;
591            } else {
592                self.tableau[base + w1] &= !m1;
593            }
594            if z0 {
595                self.tableau[base + rl + w1] |= m1;
596            } else {
597                self.tableau[base + rl + w1] &= !m1;
598            }
599        }
600        Ok(())
601    }
602
603    /// Applies the CZ gate (controlled-Z) between `q0` and `q1`.
604    ///
605    /// Implemented as (I⊗H)·CNOT·(I⊗H) inlined per-row — no heap allocations.
606    pub fn apply_cz(&mut self, q0: usize, q1: usize) -> Result<(), QisError> {
607        self.validate_two_qubits(q0, q1)?;
608        let w0 = q0 / 64;
609        let m0 = 1u64 << (q0 % 64);
610        let w1 = q1 / 64;
611        let m1 = 1u64 << (q1 % 64);
612        let rl = self.row_len;
613        for row in 0..(2 * self.num_qubits) {
614            let base = row * 2 * rl;
615            // H on q1: swap x1 ↔ z1, phase if both set
616            let x1 = (self.tableau[base + w1] & m1) != 0;
617            let z1 = (self.tableau[base + rl + w1] & m1) != 0;
618            if x1 && z1 {
619                self.phases[row] ^= 2;
620            }
621            if z1 {
622                self.tableau[base + w1] |= m1;
623            } else {
624                self.tableau[base + w1] &= !m1;
625            }
626            if x1 {
627                self.tableau[base + rl + w1] |= m1;
628            } else {
629                self.tableau[base + rl + w1] &= !m1;
630            }
631
632            // CNOT(q0, q1)
633            let xc = (self.tableau[base + w0] & m0) != 0;
634            let xt = (self.tableau[base + w1] & m1) != 0;
635            let zc = (self.tableau[base + rl + w0] & m0) != 0;
636            let zt = (self.tableau[base + rl + w1] & m1) != 0;
637            if xc && zt && (xt ^ zc ^ true) {
638                self.phases[row] ^= 2;
639            }
640            if xc {
641                self.tableau[base + w1] ^= m1;
642            }
643            if zt {
644                self.tableau[base + rl + w0] ^= m0;
645            }
646
647            // H on q1 again
648            let x1 = (self.tableau[base + w1] & m1) != 0;
649            let z1 = (self.tableau[base + rl + w1] & m1) != 0;
650            if x1 && z1 {
651                self.phases[row] ^= 2;
652            }
653            if z1 {
654                self.tableau[base + w1] |= m1;
655            } else {
656                self.tableau[base + w1] &= !m1;
657            }
658            if x1 {
659                self.tableau[base + rl + w1] |= m1;
660            } else {
661                self.tableau[base + rl + w1] &= !m1;
662            }
663        }
664        Ok(())
665    }
666
667    /// Applies the CY gate (controlled-Y) between `control` and `target`.
668    ///
669    /// Implemented as (I⊗S)·CX·(I⊗S†) inlined per-row, i.e. in time order:
670    /// S†(target) → CX(control, target) → S(target).
671    ///
672    /// Derivation: Y = SXS†, so CY = (I⊗S)·CX·(I⊗S†).
673    pub fn apply_cy(&mut self, control: usize, target: usize) -> Result<(), QisError> {
674        self.validate_two_qubits(control, target)?;
675        let cw = control / 64;
676        let cm = 1u64 << (control % 64);
677        let tw = target / 64;
678        let tm = 1u64 << (target % 64);
679        let rl = self.row_len;
680        for row in 0..(2 * self.num_qubits) {
681            let base = row * 2 * rl;
682            // S† on target: phase flip when x=1 and z=0 (i.e. X → -Y), then z ^= x
683            let x = (self.tableau[base + tw] & tm) != 0;
684            let z = (self.tableau[base + rl + tw] & tm) != 0;
685            if x && !z {
686                self.phases[row] ^= 2;
687            }
688            if x {
689                self.tableau[base + rl + tw] ^= tm;
690            }
691
692            // CNOT(control, target)
693            let xc = (self.tableau[base + cw] & cm) != 0;
694            let xt = (self.tableau[base + tw] & tm) != 0;
695            let zc = (self.tableau[base + rl + cw] & cm) != 0;
696            let zt = (self.tableau[base + rl + tw] & tm) != 0;
697            if xc && zt && (xt ^ zc ^ true) {
698                self.phases[row] ^= 2;
699            }
700            if xc {
701                self.tableau[base + tw] ^= tm;
702            }
703            if zt {
704                self.tableau[base + rl + cw] ^= cm;
705            }
706
707            // S on target: phase flip when x=1 and z=1 (i.e. Y → -X), then z ^= x
708            let x = (self.tableau[base + tw] & tm) != 0;
709            let z = (self.tableau[base + rl + tw] & tm) != 0;
710            if x && z {
711                self.phases[row] ^= 2;
712            }
713            if x {
714                self.tableau[base + rl + tw] ^= tm;
715            }
716        }
717        Ok(())
718    }
719
720    /// Returns the number of qubits.
721    pub fn num_qubits(&self) -> usize {
722        self.num_qubits
723    }
724
725    /// Applies the Hadamard gate to qubit `qubit`.
726    ///
727    /// Aaronson-Gottesman update rule for each row `i` (0..2n):
728    /// - `phase[i] ^= 2 * x[i][qubit] & z[i][qubit]`  (adds 2 mod 4 when Y, i.e. sign flip)
729    /// - swap `x[i][qubit]` and `z[i][qubit]`
730    pub fn apply_h(&mut self, qubit: usize) -> Result<(), QisError> {
731        self.validate_qubit(qubit)?;
732        let word = qubit / 64;
733        let mask = 1u64 << (qubit % 64);
734        let rl = self.row_len;
735        for row in 0..(2 * self.num_qubits) {
736            let base = row * 2 * rl;
737            let x = (self.tableau[base + word] & mask) != 0;
738            let z = (self.tableau[base + rl + word] & mask) != 0;
739            if x && z {
740                self.phases[row] ^= 2;
741            }
742            // Swap x and z bits.
743            if x {
744                self.tableau[base + rl + word] |= mask;
745            } else {
746                self.tableau[base + rl + word] &= !mask;
747            }
748            if z {
749                self.tableau[base + word] |= mask;
750            } else {
751                self.tableau[base + word] &= !mask;
752            }
753        }
754        Ok(())
755    }
756
757    /// Applies the S gate (phase gate, √Z) to qubit `qubit`.
758    ///
759    /// Aaronson-Gottesman rule for each row `i`:
760    /// - `phase[i] ^= 2 * x[i][qubit] * z[i][qubit]`
761    /// - `z[i][qubit] ^= x[i][qubit]`
762    ///
763    /// Effect: X→Y, Y→-X, Z→Z.
764    pub fn apply_s(&mut self, qubit: usize) -> Result<(), QisError> {
765        self.validate_qubit(qubit)?;
766        let word = qubit / 64;
767        let mask = 1u64 << (qubit % 64);
768        let rl = self.row_len;
769        for row in 0..(2 * self.num_qubits) {
770            let base = row * 2 * rl;
771            let x = (self.tableau[base + word] & mask) != 0;
772            let z = (self.tableau[base + rl + word] & mask) != 0;
773            if x && z {
774                self.phases[row] ^= 2;
775            }
776            if x {
777                // z ^= x (x=1): flip z bit
778                self.tableau[base + rl + word] ^= mask;
779            }
780        }
781        Ok(())
782    }
783
784    /// Applies the S† gate (S-dagger, inverse phase gate) to qubit `qubit`.
785    ///
786    /// Native rule for each row `i` (derived from S†: X→-Y, Y→X, Z→Z):
787    /// - `phase[i] ^= 2 * x[i][qubit] * (1 - z[i][qubit])`  (flip sign when X but not yet Y)
788    /// - `z[i][qubit] ^= x[i][qubit]`
789    pub fn apply_sdg(&mut self, qubit: usize) -> Result<(), QisError> {
790        self.validate_qubit(qubit)?;
791        let word = qubit / 64;
792        let mask = 1u64 << (qubit % 64);
793        let rl = self.row_len;
794        for row in 0..(2 * self.num_qubits) {
795            let base = row * 2 * rl;
796            let x = (self.tableau[base + word] & mask) != 0;
797            let z = (self.tableau[base + rl + word] & mask) != 0;
798            if x && !z {
799                self.phases[row] ^= 2;
800            }
801            if x {
802                self.tableau[base + rl + word] ^= mask;
803            }
804        }
805        Ok(())
806    }
807
808    /// Applies the Pauli-X (bit-flip) gate to qubit `qubit`.
809    ///
810    /// Effect: X→X, Y→-Y, Z→-Z.
811    /// Rule per row `i`: `phase[i] ^= 2 * z[i][qubit]`. No bit changes.
812    pub fn apply_x(&mut self, qubit: usize) -> Result<(), QisError> {
813        self.validate_qubit(qubit)?;
814        let word = qubit / 64;
815        let mask = 1u64 << (qubit % 64);
816        let rl = self.row_len;
817        for row in 0..(2 * self.num_qubits) {
818            let base = row * 2 * rl;
819            if (self.tableau[base + rl + word] & mask) != 0 {
820                self.phases[row] ^= 2;
821            }
822        }
823        Ok(())
824    }
825
826    /// Applies the Pauli-Z (phase-flip) gate to qubit `qubit`.
827    ///
828    /// Effect: X→-X, Y→-Y, Z→Z.
829    /// Rule per row `i`: `phase[i] ^= 2 * x[i][qubit]`. No bit changes.
830    pub fn apply_z(&mut self, qubit: usize) -> Result<(), QisError> {
831        self.validate_qubit(qubit)?;
832        let word = qubit / 64;
833        let mask = 1u64 << (qubit % 64);
834        let rl = self.row_len;
835        for row in 0..(2 * self.num_qubits) {
836            let base = row * 2 * rl;
837            if (self.tableau[base + word] & mask) != 0 {
838                self.phases[row] ^= 2;
839            }
840        }
841        Ok(())
842    }
843
844    /// Applies the Pauli-Y gate to qubit `qubit`.
845    ///
846    /// Effect: X→-X, Y→Y, Z→-Z.
847    /// Rule per row `i`: `phase[i] ^= 2 * (x[i][qubit] XOR z[i][qubit])`. No bit changes.
848    pub fn apply_y(&mut self, qubit: usize) -> Result<(), QisError> {
849        self.validate_qubit(qubit)?;
850        let word = qubit / 64;
851        let mask = 1u64 << (qubit % 64);
852        let rl = self.row_len;
853        for row in 0..(2 * self.num_qubits) {
854            let base = row * 2 * rl;
855            let x = (self.tableau[base + word] & mask) != 0;
856            let z = (self.tableau[base + rl + word] & mask) != 0;
857            if x ^ z {
858                self.phases[row] ^= 2;
859            }
860        }
861        Ok(())
862    }
863
864    /// Applies the √X gate (X2P, SX, Rx(π/2)) to qubit `qubit`.
865    ///
866    /// Conjugation: X→X, Y→Z, Z→−Y.
867    /// Rule per row `i`:
868    /// - `phase[i] ^= 2 * (!x[i][qubit] & z[i][qubit])` (flip for Z→−Y)
869    /// - `x[i][qubit] ^= z[i][qubit]`
870    pub fn apply_x2p(&mut self, qubit: usize) -> Result<(), QisError> {
871        self.validate_qubit(qubit)?;
872        let word = qubit / 64;
873        let mask = 1u64 << (qubit % 64);
874        let rl = self.row_len;
875        for row in 0..(2 * self.num_qubits) {
876            let base = row * 2 * rl;
877            let x = (self.tableau[base + word] & mask) != 0;
878            let z = (self.tableau[base + rl + word] & mask) != 0;
879            if !x && z {
880                self.phases[row] ^= 2;
881            }
882            // x ^= z
883            if z {
884                self.tableau[base + word] ^= mask;
885            }
886        }
887        Ok(())
888    }
889
890    /// Applies the √X† gate (X2M, SXdg, Rx(−π/2)) to qubit `qubit`.
891    ///
892    /// Conjugation: X→X, Y→−Z, Z→Y.
893    /// Rule per row `i`:
894    /// - `phase[i] ^= 2 * (x[i][qubit] & z[i][qubit])` (flip for Y→−Z)
895    /// - `x[i][qubit] ^= z[i][qubit]`
896    pub fn apply_x2m(&mut self, qubit: usize) -> Result<(), QisError> {
897        self.validate_qubit(qubit)?;
898        let word = qubit / 64;
899        let mask = 1u64 << (qubit % 64);
900        let rl = self.row_len;
901        for row in 0..(2 * self.num_qubits) {
902            let base = row * 2 * rl;
903            let x = (self.tableau[base + word] & mask) != 0;
904            let z = (self.tableau[base + rl + word] & mask) != 0;
905            if x && z {
906                self.phases[row] ^= 2;
907            }
908            if z {
909                self.tableau[base + word] ^= mask;
910            }
911        }
912        Ok(())
913    }
914
915    /// Applies the √Y gate (Y2P, SY, Ry(π/2)) to qubit `qubit`.
916    ///
917    /// Conjugation U P U†: X→−Z, Y→Y, Z→X.
918    /// Rule per row `i`:
919    /// - `phase[i] ^= 2 * (x[i][qubit] & !z[i][qubit])` (flip for X→−Z)
920    /// - swap `x[i][qubit]` ↔ `z[i][qubit]`
921    pub fn apply_y2p(&mut self, qubit: usize) -> Result<(), QisError> {
922        self.validate_qubit(qubit)?;
923        let word = qubit / 64;
924        let mask = 1u64 << (qubit % 64);
925        let rl = self.row_len;
926        for row in 0..(2 * self.num_qubits) {
927            let base = row * 2 * rl;
928            let x = (self.tableau[base + word] & mask) != 0;
929            let z = (self.tableau[base + rl + word] & mask) != 0;
930            if x && !z {
931                self.phases[row] ^= 2;
932            }
933            // swap x ↔ z
934            if x {
935                self.tableau[base + rl + word] |= mask;
936            } else {
937                self.tableau[base + rl + word] &= !mask;
938            }
939            if z {
940                self.tableau[base + word] |= mask;
941            } else {
942                self.tableau[base + word] &= !mask;
943            }
944        }
945        Ok(())
946    }
947
948    /// Applies the √Y† gate (Y2M, SYdg, Ry(−π/2)) to qubit `qubit`.
949    ///
950    /// Conjugation U P U†: X→Z, Y→Y, Z→−X.
951    /// Rule per row `i`:
952    /// - `phase[i] ^= 2 * (!x[i][qubit] & z[i][qubit])` (flip for Z→−X)
953    /// - swap `x[i][qubit]` ↔ `z[i][qubit]`
954    pub fn apply_y2m(&mut self, qubit: usize) -> Result<(), QisError> {
955        self.validate_qubit(qubit)?;
956        let word = qubit / 64;
957        let mask = 1u64 << (qubit % 64);
958        let rl = self.row_len;
959        for row in 0..(2 * self.num_qubits) {
960            let base = row * 2 * rl;
961            let x = (self.tableau[base + word] & mask) != 0;
962            let z = (self.tableau[base + rl + word] & mask) != 0;
963            if !x && z {
964                self.phases[row] ^= 2;
965            }
966            // swap x ↔ z
967            if x {
968                self.tableau[base + rl + word] |= mask;
969            } else {
970                self.tableau[base + rl + word] &= !mask;
971            }
972            if z {
973                self.tableau[base + word] |= mask;
974            } else {
975                self.tableau[base + word] &= !mask;
976            }
977        }
978        Ok(())
979    }
980
981    /// Phase contribution from multiplying Pauli `P_h` (from h-row) by `P_i` (from i-row).
982    ///
983    /// Returns an integer exponent `e` such that `P_h * P_i = i^e * P_result`.
984    /// Kept for reference/testing; the hot path uses `g_phase_word` instead.
985    #[allow(dead_code)]
986    #[inline(always)]
987    pub(crate) fn g_phase(x1: bool, z1: bool, x2: bool, z2: bool) -> i32 {
988        match (x1, z1) {
989            (false, false) => 0,
990            (true, true) => z2 as i32 - x2 as i32,
991            (true, false) => z2 as i32 * (2 * x2 as i32 - 1),
992            (false, true) => x2 as i32 * (1 - 2 * z2 as i32),
993        }
994    }
995
996    /// Accumulates the g_phase contribution of a single 64-bit word pair using
997    /// word-level popcount — **2 `count_ones()` calls** per 64-bit word.
998    ///
999    /// Algebraic derivation:
1000    /// - `anti_comm = (xh & zi) XOR (xi & zh)` — positions where the two Paulis
1001    ///   anti-commute (anti-commuting ↔ g ≠ 0).
1002    /// - `pos = (xh & zi & (zh XOR xi)) | (!xh & zh & xi & !zi)` — positions
1003    ///   where g = +1 (the "forward cyclic" Pauli pairs X→Y, Y→Z, Z→X).
1004    /// - `sum = 2 · popcount(pos) − popcount(anti_comm)`
1005    ///   (since popcount(neg) = popcount(anti_comm) − popcount(pos),
1006    ///   and popcount(pos) − popcount(neg) = 2·popcount(pos) − popcount(anti_comm)).
1007    ///
1008    /// Verified against the scalar `g_phase` for all 9 single-qubit Pauli pairs.
1009    #[inline(always)]
1010    pub(crate) fn g_phase_word(xh: u64, zh: u64, xi: u64, zi: u64) -> i32 {
1011        // Positions where the Paulis anti-commute (g ≠ 0).
1012        let anti_comm = (xh & zi) ^ (xi & zh);
1013        // Positions where g = +1 (cyclic order X→Y, Y→Z, Z→X).
1014        let pos = (xh & zi & (zh ^ xi)) | (!xh & zh & xi & !zi);
1015        2 * pos.count_ones() as i32 - anti_comm.count_ones() as i32
1016    }
1017
1018    /// Multiplies row `h` by row `i` in-place: `row_h := row_h * row_i`.
1019    ///
1020    /// Phase is computed via `g_phase_word` (word-level popcount, O(n/64) vs O(n)).
1021    /// Bit XOR is dispatched to the best available SIMD instruction set at runtime.
1022    fn rowsum(&mut self, h: usize, i: usize) {
1023        debug_assert_ne!(h, i, "rowsum requires distinct rows");
1024
1025        let h_base = Self::row_base(h, self.row_len);
1026        let i_base = Self::row_base(i, self.row_len);
1027        let rl = self.row_len;
1028
1029        let mut sum = self.phases[h] as i32 + self.phases[i] as i32;
1030        // Only the active words (0..n_words) contain qubit data; the padding words
1031        // in the range n_words..row_len are always zero and contribute 0 to the sum.
1032        let n_words = self.num_qubits.div_ceil(64);
1033        for w in 0..n_words {
1034            let xh = self.tableau[h_base + w];
1035            let zh = self.tableau[h_base + rl + w];
1036            let xi = self.tableau[i_base + w];
1037            let zi = self.tableau[i_base + rl + w];
1038            sum += Self::g_phase_word(xh, zh, xi, zi);
1039        }
1040        // New phase is sum mod 4, normalised to {0,1,2,3}.
1041        self.phases[h] = (((sum % 4) + 4) % 4) as u8;
1042
1043        // SAFETY: h ≠ i guarantees [h_base, h_base+2*rl) and [i_base, i_base+2*rl)
1044        // are non-overlapping regions within the same 64-byte aligned allocation.
1045        unsafe {
1046            xor_rows_dispatch(self.tableau.as_mut_slice(), h_base, i_base, 2 * rl);
1047        }
1048    }
1049
1050    /// Measures qubit `qubit` in the Z basis, collapsing the state.
1051    ///
1052    /// Returns `false` for outcome `0` (+1 eigenvalue) and `true` for outcome `1` (-1 eigenvalue).
1053    ///
1054    /// # Algorithm (Aaronson-Gottesman §IV)
1055    ///
1056    /// **Random case** — some stabilizer generator has X or Y on qubit `qubit`:
1057    /// 1. Find any stabilizer row `p` (index `n..2n`) with `x[p][qubit] = 1`.
1058    /// 2. XOR row `p` into every other row that also has `x[.][qubit] = 1`.
1059    /// 3. Move row `p` to its paired destabilizer row `p - n`.
1060    /// 4. Replace stabilizer row `p` with `±Z_qubit`, where the sign is random.
1061    ///
1062    /// **Deterministic case** — all stabilizers commute with `Z_qubit`:
1063    /// 1. Compute the product of destabilizer rows `i` (for which the paired
1064    ///    stabilizer `n+i` has `x[n+i][qubit] = 1`) into a scratch accumulator.
1065    /// 2. The measurement outcome is the phase of the scratch row.
1066    pub fn measure(&mut self, qubit: usize) -> Result<bool, QisError> {
1067        self.validate_qubit(qubit)?;
1068
1069        // Find first stabilizer row with x[.][qubit] = 1.
1070        let maybe_p = (self.num_qubits..2 * self.num_qubits).find(|&row| self.x_bit(row, qubit));
1071
1072        match maybe_p {
1073            Some(p) => {
1074                // Make p the unique row with x[.][qubit] = 1 by XOR-ing p into all others.
1075                for i in 0..2 * self.num_qubits {
1076                    if i != p && self.x_bit(i, qubit) {
1077                        self.rowsum(i, p);
1078                    }
1079                }
1080
1081                // Copy stabilizer p → destabilizer p-n.
1082                let dest = p - self.num_qubits;
1083                let p_base = Self::row_base(p, self.row_len);
1084                let d_base = Self::row_base(dest, self.row_len);
1085                // SAFETY: dest ≠ p (since dest < n ≤ p < 2n).
1086                unsafe {
1087                    let src = self.tableau.as_slice()[p_base..p_base + 2 * self.row_len].as_ptr();
1088                    let dst =
1089                        self.tableau.as_mut_slice()[d_base..d_base + 2 * self.row_len].as_mut_ptr();
1090                    std::ptr::copy_nonoverlapping(src, dst, 2 * self.row_len);
1091                }
1092                self.phases[dest] = self.phases[p];
1093
1094                // Reset stabilizer p to Z_qubit with random phase.
1095                let p_base = Self::row_base(p, self.row_len);
1096                for w in 0..2 * self.row_len {
1097                    self.tableau[p_base + w] = 0;
1098                }
1099                self.set_z_bit(p, qubit, true);
1100                let b: bool = self.rng.random();
1101                self.set_phase(p, if b { 2 } else { 0 });
1102                Ok(b)
1103            }
1104            None => {
1105                // Accumulate the product of paired stabilizer rows into the
1106                // pre-allocated scratch row (row 2n) using the SIMD-accelerated
1107                // rowsum, giving O(n²/w) complexity instead of O(n²) scalar work.
1108                let scratch = self.scratch_row();
1109                self.clear_scratch();
1110
1111                for i in 0..self.num_qubits {
1112                    // Include destabilizer i only if it has an X-component at qubit `qubit`.
1113                    // Then rowsum the paired stabilizer n+i into scratch.
1114                    if self.x_bit(i, qubit) {
1115                        self.rowsum(scratch, self.num_qubits + i);
1116                    }
1117                }
1118
1119                // Phase 0 = +1 eigenvalue (outcome 0); phase 2 = -1 eigenvalue (outcome 1).
1120                Ok(self.phases[scratch] == 2)
1121            }
1122        }
1123    }
1124
1125    /// Measures all qubits in order, returning a bit-packed [`Outcome`].
1126    ///
1127    /// Qubit `q`'s result is stored at bit `q` inside the `Outcome`.
1128    /// Use [`Outcome::is_one(q)`](crate::device::Outcome::is_one) to read it.
1129    pub fn measure_all(&mut self) -> Outcome {
1130        let num_chunks = self.num_qubits.div_ceil(64);
1131        let mut chunks = SmallVec::from_elem(0u64, num_chunks);
1132        for qubit in 0..self.num_qubits {
1133            if self.measure(qubit).unwrap() {
1134                chunks[qubit / 64] |= 1u64 << (qubit % 64);
1135            }
1136        }
1137        Outcome::new(chunks)
1138    }
1139
1140    /// Resets `qubit` to the |0⟩ state.
1141    ///
1142    /// Implemented by measuring in the Z basis and applying X if the outcome is |1⟩.
1143    /// This correctly handles both deterministic and random measurement cases.
1144    ///
1145    /// # Example
1146    ///
1147    /// ```rust
1148    /// use cqlib_core::qis::StabilizerState;
1149    ///
1150    /// let mut s = StabilizerState::new(2);
1151    /// s.apply_x(0).unwrap();  // |0⟩ → |1⟩
1152    /// s.reset(0).unwrap();    // back to |0⟩
1153    /// assert_eq!(s.probabilities().unwrap()[0], 1.0); // P(|00⟩) = 1
1154    /// ```
1155    pub fn reset(&mut self, qubit: usize) -> Result<(), QisError> {
1156        self.validate_qubit(qubit)?;
1157        if self.measure(qubit)? {
1158            self.apply_x(qubit)?;
1159        }
1160        Ok(())
1161    }
1162
1163    /// Performs a forced (post-selected) Z-basis measurement on `qubit`.
1164    ///
1165    /// Like `measure`, but the outcome is forced to `outcome` rather than random.
1166    /// Returns `true` if the forced outcome has non-zero probability (measurement is
1167    /// consistent with the state), `false` if probability is zero (outcome is impossible).
1168    ///
1169    /// Used internally by `probability_of`.
1170    fn force_measure(&mut self, qubit: usize, outcome: bool) -> bool {
1171        // Find first stabilizer row with x[.][qubit] = 1.
1172        let maybe_p = (self.num_qubits..2 * self.num_qubits).find(|&row| self.x_bit(row, qubit));
1173
1174        match maybe_p {
1175            Some(p) => {
1176                // Random measurement: force to `outcome`.
1177                for i in 0..2 * self.num_qubits {
1178                    if i != p && self.x_bit(i, qubit) {
1179                        self.rowsum(i, p);
1180                    }
1181                }
1182                let dest = p - self.num_qubits;
1183                let p_base = Self::row_base(p, self.row_len);
1184                let d_base = Self::row_base(dest, self.row_len);
1185                // SAFETY: dest < n ≤ p.
1186                unsafe {
1187                    let src = self.tableau.as_slice()[p_base..p_base + 2 * self.row_len].as_ptr();
1188                    let dst =
1189                        self.tableau.as_mut_slice()[d_base..d_base + 2 * self.row_len].as_mut_ptr();
1190                    std::ptr::copy_nonoverlapping(src, dst, 2 * self.row_len);
1191                }
1192                self.phases[dest] = self.phases[p];
1193                let p_base = Self::row_base(p, self.row_len);
1194                for w in 0..2 * self.row_len {
1195                    self.tableau[p_base + w] = 0;
1196                }
1197                self.set_z_bit(p, qubit, true);
1198                self.set_phase(p, if outcome { 2 } else { 0 });
1199                true // random case: forced outcome is always possible
1200            }
1201            None => {
1202                // Deterministic case: check whether outcome matches.
1203                let scratch = self.scratch_row();
1204                self.clear_scratch();
1205                for i in 0..self.num_qubits {
1206                    if self.x_bit(i, qubit) {
1207                        self.rowsum(scratch, self.num_qubits + i);
1208                    }
1209                }
1210                let actual = self.phases[scratch] == 2;
1211                actual == outcome // false → this outcome is impossible
1212            }
1213        }
1214    }
1215
1216    /// Returns the probability of measuring the given computational basis state.
1217    ///
1218    /// Performs O(n²) work per qubit, O(n³) total, on a cloned (non-destructive) copy.
1219    ///
1220    /// # Returns
1221    /// - `Ok(0.0)` if the bitstring is inconsistent with the stabilizer state.
1222    /// - `Ok(1.0 / 2.0^k)` where `k` is the number of qubits with random outcomes,
1223    ///   if the bitstring is in the support.
1224    /// - `Err(QubitMismatch)` if `bits.len() != n`.
1225    ///
1226    /// # Example
1227    /// ```rust
1228    /// use cqlib_core::qis::StabilizerState;
1229    ///
1230    /// let mut s = StabilizerState::new(2);
1231    /// s.apply_h(0).unwrap();
1232    /// s.apply_cx(0, 1).unwrap(); // Bell state |Φ⁺⟩
1233    ///
1234    /// // Only |00⟩ and |11⟩ are possible, each with probability 0.5
1235    /// assert!((s.probability_of(&[false, false]).unwrap() - 0.5).abs() < 1e-10);
1236    /// assert!((s.probability_of(&[true,  true ]).unwrap() - 0.5).abs() < 1e-10);
1237    /// assert_eq!(s.probability_of(&[false, true]).unwrap(), 0.0);
1238    /// assert_eq!(s.probability_of(&[true,  false]).unwrap(), 0.0);
1239    /// ```
1240    pub fn probability_of(&self, bits: &[bool]) -> Result<f64, QisError> {
1241        if bits.len() != self.num_qubits {
1242            return Err(QisError::QubitMismatch {
1243                expected: self.num_qubits,
1244                actual: bits.len(),
1245            });
1246        }
1247        let mut s = self.clone();
1248        let mut prob = 1.0_f64;
1249        for (qubit, &desired) in bits.iter().enumerate() {
1250            // Check if measurement is random (some stabilizer has X on this qubit).
1251            let is_random = (s.num_qubits..2 * s.num_qubits).any(|row| s.x_bit(row, qubit));
1252            if is_random {
1253                prob *= 0.5;
1254                s.force_measure(qubit, desired);
1255            } else {
1256                // Deterministic: force_measure returns false if outcome is impossible.
1257                if !s.force_measure(qubit, desired) {
1258                    return Ok(0.0);
1259                }
1260            }
1261        }
1262        Ok(prob)
1263    }
1264
1265    /// Returns the full probability distribution over all computational basis states.
1266    ///
1267    /// Returns a `Vec<f64>` of length `2^n` where entry `i` is the probability of
1268    /// measuring the state whose binary representation is `i` (qubit 0 = LSB).
1269    ///
1270    /// # Limitations
1271    /// Only feasible for small systems. Returns `Err(InvalidParameterValue)` if `n > 20`
1272    /// (which would require a 8 MB vector and O(2^20 × n³) computation).
1273    ///
1274    /// # Example
1275    /// ```rust
1276    /// use cqlib_core::qis::StabilizerState;
1277    ///
1278    /// let mut s = StabilizerState::new(2);
1279    /// s.apply_h(0).unwrap();
1280    /// s.apply_cx(0, 1).unwrap(); // Bell state
1281    ///
1282    /// let probs = s.probabilities().unwrap();
1283    /// assert_eq!(probs.len(), 4);
1284    /// assert!((probs[0b00] - 0.5).abs() < 1e-10); // |00⟩
1285    /// assert_eq!(probs[0b01], 0.0);                // |10⟩ (qubit-0 is LSB)
1286    /// assert_eq!(probs[0b10], 0.0);                // |01⟩
1287    /// assert!((probs[0b11] - 0.5).abs() < 1e-10); // |11⟩
1288    /// ```
1289    pub fn probabilities(&self) -> Result<Vec<f64>, QisError> {
1290        const MAX_QUBITS: usize = 20;
1291        if self.num_qubits > MAX_QUBITS {
1292            return Err(QisError::InvalidParameterValue(format!(
1293                "probabilities() is only supported for n ≤ {MAX_QUBITS} qubits \
1294                 (requested n = {}); use sample_shots() for large systems",
1295                self.num_qubits
1296            )));
1297        }
1298        let size = 1usize << self.num_qubits;
1299        let mut probs = vec![0.0_f64; size];
1300        for (i, prob) in probs.iter_mut().enumerate() {
1301            let bits: Vec<bool> = (0..self.num_qubits).map(|q| (i >> q) & 1 == 1).collect();
1302            *prob = self.probability_of(&bits)?;
1303        }
1304        Ok(probs)
1305    }
1306
1307    /// Runs `shots` independent measurements in parallel using Rayon.
1308    ///
1309    /// Returns a [`Vec<Outcome>`] of bit-packed measurement results, one per shot.
1310    /// Each Rayon worker thread reuses a single pre-allocated working copy of the
1311    /// state (via [`reset_from`](Self::reset_from)), avoiding per-shot heap allocation.
1312    ///
1313    /// Seeds for each shot are derived sequentially from the primary RNG (ensuring
1314    /// reproducibility), then each shot runs on an independently-seeded clone in
1315    /// parallel. This produces the correct marginal distribution while being
1316    /// deterministic: the same initial state always yields the same joint sample set.
1317    ///
1318    /// # Example
1319    /// ```rust
1320    /// use cqlib_core::qis::StabilizerState;
1321    /// let mut s = StabilizerState::new(2);
1322    /// s.apply_h(0).unwrap();
1323    /// s.apply_cx(0, 1).unwrap(); // Bell state
1324    /// let results = s.sample_shots(1000);
1325    /// assert_eq!(results.len(), 1000);
1326    /// for shot in &results { assert_eq!(shot.is_one(0), shot.is_one(1)); }
1327    /// ```
1328    pub fn sample_shots(&self, shots: usize) -> Vec<Outcome> {
1329        self.sample_shots_with_seed(shots, None)
1330    }
1331
1332    /// Samples using per-shot random streams independent of Rayon scheduling.
1333    /// With no seed, preserves the state's existing random stream.
1334    pub fn sample_shots_with_seed(&self, shots: usize, seed: Option<u64>) -> Vec<Outcome> {
1335        // Derive independent seeds sequentially so the overall sequence is
1336        // deterministic and reproducible from the same initial RNG state.
1337        let seeds: Vec<u64> = {
1338            let mut rng = seed
1339                .map(SmallRng::seed_from_u64)
1340                .unwrap_or_else(|| self.rng.clone());
1341            (0..shots).map(|_| rng.random()).collect()
1342        };
1343        seeds
1344            .into_par_iter()
1345            .map_with(self.clone(), |work, seed| {
1346                work.reset_from(self);
1347                work.rng = SmallRng::seed_from_u64(seed);
1348                work.measure_all()
1349            })
1350            .collect()
1351    }
1352
1353    /// Seeds the internal RNG for direct circuit sampling.
1354    pub(crate) fn seed_rng(&mut self, seed: u64) {
1355        self.rng = SmallRng::seed_from_u64(seed);
1356    }
1357
1358    /// Samples the state using a circuit [`Measurement`] as the output contract.
1359    ///
1360    /// Unlike [`apply_circuit`](Self::apply_circuit), this method does not read
1361    /// or execute the circuit IR. The [`Measurement`] already carries the qubits
1362    /// and their result bit order:
1363    /// - `measurement.qubits()[i]` becomes bit `i` in each [`Outcome`].
1364    /// - [`Outcome::to_bitstring`] displays the most-significant result bit first,
1365    ///   so string order is the reverse of `measurement.qubits()`.
1366    ///
1367    /// # Example
1368    /// ```rust
1369    /// use cqlib_core::circuit::{Circuit, Qubit};
1370    /// use cqlib_core::qis::StabilizerState;
1371    ///
1372    /// let mut c = Circuit::new(2);
1373    /// c.h(Qubit::new(0)).unwrap();
1374    /// c.cx(Qubit::new(0), Qubit::new(1)).unwrap();
1375    /// let out = c.measure_bits([Qubit::new(1), Qubit::new(0)]).unwrap();
1376    ///
1377    /// // `from_circuit` ignores terminal measurements and keeps the Bell state.
1378    /// let state = StabilizerState::from_circuit(&c).unwrap();
1379    /// let result = state.sample(&out, 1000).unwrap();
1380    ///
1381    /// assert_eq!(result.shots(), 1000);
1382    /// assert!(result
1383    ///     .counts()
1384    ///     .keys()
1385    ///     .all(|bits| bits.to_bitstring(out.width()) == "00"
1386    ///         || bits.to_bitstring(out.width()) == "11"));
1387    /// ```
1388    pub fn sample(
1389        &self,
1390        measurement: &Measurement,
1391        shots: usize,
1392    ) -> Result<ExecutionResult, QisError> {
1393        self.sample_with_seed(measurement, shots, None)
1394    }
1395
1396    /// Projects seeded full-state samples onto the requested measurement.
1397    pub fn sample_with_seed(
1398        &self,
1399        measurement: &Measurement,
1400        shots: usize,
1401        seed: Option<u64>,
1402    ) -> Result<ExecutionResult, QisError> {
1403        let projection = super::resolve_measurement(measurement, &self.qubit_map, self.num_qubits)?;
1404
1405        let mut counts = HashMap::new();
1406        for full in self.sample_shots_with_seed(shots, seed) {
1407            *counts.entry(projection.project(&full)).or_insert(0usize) += 1;
1408        }
1409
1410        Ok(ExecutionResult::from_counts(
1411            "stabilizer-sample".to_string(),
1412            measurement.qubits().to_vec(),
1413            shots,
1414            measurement.width(),
1415            Some("stabilizer".to_string()),
1416            counts,
1417        ))
1418    }
1419
1420    /// Returns the probability distribution selected by a circuit [`Measurement`].
1421    ///
1422    /// This is a marginal distribution over `measurement.qubits()`, not the full
1423    /// `2^n` state distribution. The returned [`Outcome`] keys use the same bit
1424    /// order as [`sample`](Self::sample).
1425    ///
1426    /// v1 intentionally reuses [`probabilities`](Self::probabilities), so it has
1427    /// the same `n <= 20` limit. Use [`sample`](Self::sample) for larger states.
1428    pub fn probs(&self, measurement: &Measurement) -> Result<HashMap<Outcome, f64>, QisError> {
1429        let projection = super::resolve_measurement(measurement, &self.qubit_map, self.num_qubits)?;
1430
1431        let mut marginal = HashMap::new();
1432        for (basis, prob) in self.probabilities()?.into_iter().enumerate() {
1433            if prob == 0.0 {
1434                continue;
1435            }
1436            *marginal
1437                .entry(projection.project_basis(basis))
1438                .or_insert(0.0) += prob;
1439        }
1440        Ok(marginal)
1441    }
1442
1443    /// Constructs a `StabilizerState` by simulating a Clifford circuit.
1444    ///
1445    /// This state-level entry point only performs quantum state evolution.
1446    /// Terminal measurement and store operations produced by `Circuit::measure*` are
1447    /// treated as output declarations and ignored here: they do not collapse
1448    /// the state and do not populate runtime classical data.
1449    ///
1450    /// Use [`sample`](Self::sample) or [`probs`](Self::probs) with the returned
1451    /// [`Measurement`] to query measurement distributions from the final state.
1452    /// Use [`run_circuit`](Self::run_circuit) when you need execution
1453    /// semantics where measurements collapse the state and write classical data.
1454    ///
1455    /// # Supported instructions
1456    /// - Gates: `I, H, X, Y, Z, S, SDG, X2P, X2M, Y2P, Y2M, CX, CY, CZ, SWAP`
1457    /// - Classical data: `MeasureBit`, `MeasureBits`, `Store` are ignored
1458    /// - Directives: `Reset` (returns qubit to |0⟩), `Barrier`/`Delay` (no-op)
1459    ///
1460    /// [`run_circuit`]: StabilizerState::run_circuit
1461    ///
1462    /// # Example
1463    /// ```rust
1464    /// use cqlib_core::circuit::{Circuit, Qubit};
1465    /// use cqlib_core::qis::StabilizerState;
1466    ///
1467    /// let mut c = Circuit::new(2);
1468    /// c.h(Qubit::new(0)).unwrap();
1469    /// c.cx(Qubit::new(0), Qubit::new(1)).unwrap();
1470    /// let out = c
1471    ///     .measure_bits([Qubit::new(1), Qubit::new(0)])
1472    ///     .unwrap();
1473    ///
1474    /// // The measurement above is an output declaration for state sampling.
1475    /// // It does not collapse the Bell state during `from_circuit`.
1476    /// let stab = StabilizerState::from_circuit(&c).unwrap();
1477    /// let probs = stab.probs(&out).unwrap();
1478    /// assert!((probs.values().sum::<f64>() - 1.0).abs() < 1e-10);
1479    /// ```
1480    pub fn from_circuit(circuit: &Circuit) -> Result<Self, QisError> {
1481        let mut state = StabilizerState::new(circuit.num_qubits());
1482        state.apply_circuit(circuit)?;
1483        Ok(state)
1484    }
1485
1486    /// Applies a Clifford circuit to this stabilizer state in-place.
1487    ///
1488    /// Once bound to circuit qubit IDs, subsequent circuits must have the same
1489    /// IDs in the same order. The mapping is committed only on success; gates
1490    /// completed before an error are not rolled back.
1491    ///
1492    /// As in `from_circuit`, terminal measurements are ignored. A measured qubit
1493    /// used by a later gate or Reset is rejected before the state is changed.
1494    /// Independent gates, repeated measurements, Barrier, Delay, and Store are allowed.
1495    ///
1496    /// This state-level entry point ignores terminal measurement declarations,
1497    /// matching [`from_circuit`](Self::from_circuit). Use
1498    /// [`run_circuit`](Self::run_circuit) when runtime classical measurement
1499    /// values are required.
1500    pub fn apply_circuit(&mut self, input_circuit: &Circuit) -> Result<(), QisError> {
1501        if input_circuit.num_qubits() != self.num_qubits {
1502            return Err(QisError::InvalidStateDimension(input_circuit.num_qubits()));
1503        }
1504        if input_circuit
1505            .operations()
1506            .iter()
1507            .any(|op| matches!(op.instruction, Instruction::ClassicalControl(_)))
1508        {
1509            return Err(QisError::UnsupportedOperation(
1510                "classical control flow is not supported in stabilizer simulation".to_string(),
1511            ));
1512        }
1513
1514        let circuit = input_circuit.decompose()?;
1515        super::validate_terminal_measurements(&circuit)?;
1516        let qubit_map = super::prepare_qubit_map(&circuit.qubits(), &self.qubit_map)?;
1517
1518        let parameter_values: Vec<Option<f64>> = circuit
1519            .parameters()
1520            .iter()
1521            .map(|p| p.evaluate(&None).ok())
1522            .collect();
1523
1524        let mut classical = ClassicalState::for_circuit(&circuit);
1525        StabilizerState::execute_operations(
1526            self,
1527            circuit.operations(),
1528            &qubit_map,
1529            &parameter_values,
1530            &mut classical,
1531            CircuitClassicalMode::Ignore,
1532        )?;
1533        self.qubit_map = Some(qubit_map);
1534        Ok(())
1535    }
1536
1537    /// Executes a Clifford circuit and returns both the final state and runtime
1538    /// classical data.
1539    ///
1540    /// Returns a [`CircuitExecutionResult`] containing:
1541    /// - `state`: the final [`StabilizerState`] after all operations
1542    /// - `classical`: measurement results and mutable variables indexed by
1543    ///   [`crate::circuit::ClassicalValue`] and [`crate::circuit::ClassicalVar`]
1544    ///
1545    /// # Supported instructions
1546    /// Same quantum gates as [`from_circuit`]. Unlike `from_circuit`, this
1547    /// method executes `MeasureBit`, `MeasureBits`, and `Store`: measurements
1548    /// collapse the state immediately and write runtime classical data.
1549    /// Control-flow gates are not supported.
1550    ///
1551    /// [`from_circuit`]: StabilizerState::from_circuit
1552    ///
1553    /// # Example
1554    /// ```rust
1555    /// use cqlib_core::circuit::{Circuit, ClassicalExpr, ClassicalType, Qubit};
1556    /// use cqlib_core::qis::{RuntimeValue, StabilizerState};
1557    ///
1558    /// let mut c = Circuit::new(1);
1559    /// c.x(Qubit::new(0)).unwrap();
1560    ///
1561    /// // `measure` creates an immutable runtime value.
1562    /// let measured = c.measure(Qubit::new(0)).unwrap();
1563    ///
1564    /// // `store` can copy or transform measured values into mutable variables.
1565    /// let flag = c.var(ClassicalType::Bool);
1566    /// c.store(flag, ClassicalExpr::bit_to_bool(measured.expr()).unwrap()).unwrap();
1567    ///
1568    /// let result = StabilizerState::run_circuit(&c).unwrap();
1569    /// assert_eq!(result.classical.value(measured.value()), Some(&RuntimeValue::Bit(true)));
1570    /// assert_eq!(result.classical.var(flag), Some(&RuntimeValue::Bool(true)));
1571    /// ```
1572    pub fn run_circuit(circuit: &Circuit) -> Result<CircuitExecutionResult, QisError> {
1573        StabilizerState::run_circuit_with_mode(circuit, CircuitClassicalMode::Execute)
1574    }
1575
1576    fn run_circuit_with_mode(
1577        input_circuit: &Circuit,
1578        classical_mode: CircuitClassicalMode,
1579    ) -> Result<CircuitExecutionResult, QisError> {
1580        if input_circuit
1581            .operations()
1582            .iter()
1583            .any(|op| matches!(op.instruction, Instruction::ClassicalControl(_)))
1584        {
1585            return Err(QisError::UnsupportedOperation(
1586                "classical control flow is not supported in stabilizer simulation".to_string(),
1587            ));
1588        }
1589
1590        let circuit = input_circuit.decompose()?;
1591        let mut state = StabilizerState::new(circuit.num_qubits());
1592
1593        let qubits = circuit.qubits();
1594        let qubit_map: std::collections::HashMap<_, _> = qubits
1595            .iter()
1596            .enumerate()
1597            .map(|(idx, q)| (*q, idx))
1598            .collect();
1599        let qubit_map = std::sync::Arc::new(qubit_map);
1600        state.qubit_map = Some(qubit_map.clone());
1601
1602        let parameter_values: Vec<Option<f64>> = circuit
1603            .parameters()
1604            .iter()
1605            .map(|p| p.evaluate(&None).ok())
1606            .collect();
1607
1608        let mut classical = ClassicalState::for_circuit(&circuit);
1609
1610        StabilizerState::execute_operations(
1611            &mut state,
1612            circuit.operations(),
1613            &qubit_map,
1614            &parameter_values,
1615            &mut classical,
1616            classical_mode,
1617        )?;
1618        classical.rebind_to_circuit(input_circuit)?;
1619
1620        Ok(CircuitExecutionResult { state, classical })
1621    }
1622
1623    /// Recursive operation executor used by [`apply_circuit`].
1624    ///
1625    /// Processes a slice of operations, updating the quantum state and runtime
1626    /// classical state in place.
1627    ///
1628    /// [`apply_circuit`]: StabilizerState::apply_circuit
1629    fn execute_operations(
1630        state: &mut StabilizerState,
1631        ops: &[Operation],
1632        qubit_map: &std::collections::HashMap<Qubit, usize>,
1633        parameter_values: &[Option<f64>],
1634        classical: &mut ClassicalState,
1635        classical_mode: CircuitClassicalMode,
1636    ) -> Result<(), QisError> {
1637        for op in ops {
1638            let qubit_indices: Vec<usize> =
1639                op.qubits
1640                    .iter()
1641                    .map(|q| {
1642                        qubit_map.get(q).copied().ok_or_else(|| {
1643                            QisError::CircuitError(CircuitError::QubitNotFound(q.id()))
1644                        })
1645                    })
1646                    .collect::<Result<_, _>>()?;
1647
1648            let params: Vec<f64> = op
1649                .params
1650                .iter()
1651                .map(|p| match p {
1652                    CircuitParam::Fixed(v) => Ok(*v),
1653                    CircuitParam::Index(idx) => parameter_values
1654                        .get(*idx as usize)
1655                        .copied()
1656                        .flatten()
1657                        .ok_or(QisError::CircuitError(CircuitError::SymbolicParameterError)),
1658                })
1659                .collect::<Result<_, _>>()?;
1660
1661            match &op.instruction {
1662                Instruction::Standard(gate) => {
1663                    state.apply_clifford_gate(*gate, &qubit_indices, &params)?;
1664                }
1665                Instruction::McGate(mc) if mc.num_ctrl_qubits() == 1 => match mc.base_gate() {
1666                    StandardGate::X => state.apply_cx(qubit_indices[0], qubit_indices[1])?,
1667                    StandardGate::Y => state.apply_cy(qubit_indices[0], qubit_indices[1])?,
1668                    StandardGate::Z => state.apply_cz(qubit_indices[0], qubit_indices[1])?,
1669                    g => {
1670                        return Err(QisError::UnsupportedOperation(format!(
1671                            "Controlled-{g} is not supported in stabilizer simulation"
1672                        )));
1673                    }
1674                },
1675                Instruction::McGate(mc) => {
1676                    return Err(QisError::UnsupportedOperation(format!(
1677                        "{}-controlled gates are not supported in stabilizer simulation",
1678                        mc.num_ctrl_qubits()
1679                    )));
1680                }
1681                Instruction::Directive(directive) => match directive {
1682                    Directive::Barrier => {} // no-op
1683                    Directive::Measure => {
1684                        return Err(QisError::UnsupportedOperation(
1685                            "legacy Measure directive is not supported by stabilizer circuit execution; use Circuit::measure".to_string(),
1686                        ));
1687                    }
1688                    Directive::Reset => {
1689                        let q = qubit_indices[0];
1690                        state.reset(q)?;
1691                    }
1692                },
1693                Instruction::Delay => {} // no-op
1694                Instruction::UnitaryGate(_) => {
1695                    return Err(QisError::UnsupportedOperation(
1696                        "Arbitrary unitary gates are not Clifford".to_string(),
1697                    ));
1698                }
1699                Instruction::CircuitGate(_) => {
1700                    return Err(QisError::CircuitError(CircuitError::InvalidOperation(
1701                        "CircuitGate should have been decomposed".to_string(),
1702                    )));
1703                }
1704                Instruction::ClassicalControl(_) => {
1705                    return Err(QisError::UnsupportedOperation(
1706                        "classical control flow is not supported in stabilizer simulation"
1707                            .to_string(),
1708                    ));
1709                }
1710                Instruction::ClassicalData(data) => {
1711                    if classical_mode == CircuitClassicalMode::Ignore {
1712                        continue;
1713                    }
1714                    match data {
1715                        ClassicalDataOp::MeasureBit { result } => {
1716                            let bit = state.measure(qubit_indices[0])?;
1717                            classical.set_value(*result, RuntimeValue::Bit(bit))?;
1718                        }
1719                        ClassicalDataOp::MeasureBits { result } => {
1720                            let bits = qubit_indices
1721                                .iter()
1722                                .copied()
1723                                .map(|q| state.measure(q))
1724                                .collect::<Result<Vec<_>, _>>()?;
1725                            classical.set_value(*result, RuntimeValue::bit_vec_from_lsb(&bits))?;
1726                        }
1727                        ClassicalDataOp::Store { target, value } => {
1728                            classical.store(*target, value)?;
1729                        }
1730                    }
1731                }
1732            }
1733        }
1734        Ok(())
1735    }
1736
1737    /// Applies a standard gate to the stabilizer tableau.
1738    ///
1739    /// Returns [`QisError::NonCliffordGate`] for gates outside the Clifford set.
1740    pub fn apply_standard_gate(
1741        &mut self,
1742        gate: StandardGate,
1743        qubits: &[usize],
1744        params: &[f64],
1745    ) -> Result<(), QisError> {
1746        if qubits.len() != gate.num_qubits() {
1747            return Err(QisError::InvalidParameterValue(format!(
1748                "Gate {:?} requires {} qubits, got {}",
1749                gate,
1750                gate.num_qubits(),
1751                qubits.len()
1752            )));
1753        }
1754        if params.len() != gate.num_params() {
1755            return Err(QisError::InvalidParameterValue(format!(
1756                "Gate {:?} requires {} parameters, got {}",
1757                gate,
1758                gate.num_params(),
1759                params.len()
1760            )));
1761        }
1762        self.apply_clifford_gate(gate, qubits, params)
1763    }
1764
1765    /// Dispatches a [`StandardGate`] to the appropriate Clifford tableau method.
1766    /// Returns `Err` for any non-Clifford gate.
1767    fn apply_clifford_gate(
1768        &mut self,
1769        gate: StandardGate,
1770        qubits: &[usize],
1771        _params: &[f64],
1772    ) -> Result<(), QisError> {
1773        match gate {
1774            StandardGate::I => {}
1775            StandardGate::H => self.apply_h(qubits[0])?,
1776            StandardGate::X => self.apply_x(qubits[0])?,
1777            StandardGate::Y => self.apply_y(qubits[0])?,
1778            StandardGate::Z => self.apply_z(qubits[0])?,
1779            StandardGate::S => self.apply_s(qubits[0])?,
1780            StandardGate::SDG => self.apply_sdg(qubits[0])?,
1781            StandardGate::X2P => self.apply_x2p(qubits[0])?,
1782            StandardGate::X2M => self.apply_x2m(qubits[0])?,
1783            StandardGate::Y2P => self.apply_y2p(qubits[0])?,
1784            StandardGate::Y2M => self.apply_y2m(qubits[0])?,
1785            StandardGate::CX => self.apply_cx(qubits[0], qubits[1])?,
1786            StandardGate::CY => self.apply_cy(qubits[0], qubits[1])?,
1787            StandardGate::CZ => self.apply_cz(qubits[0], qubits[1])?,
1788            StandardGate::SWAP => self.apply_swap(qubits[0], qubits[1])?,
1789            g => {
1790                return Err(QisError::NonCliffordGate(format!(
1791                    "Gate '{g}' is not a Clifford gate — use Statevector for universal simulation"
1792                )));
1793            }
1794        }
1795        Ok(())
1796    }
1797
1798    /// There are `n` stabilizer generators (rows `n..2n`).
1799    pub fn get_stabilizers(&self) -> Vec<PauliString> {
1800        (0..self.num_qubits)
1801            .map(|i| self.row_to_pauli_string(self.num_qubits + i))
1802            .collect()
1803    }
1804
1805    /// Returns the destabilizer generators as [`PauliString`]s.
1806    pub fn get_destabilizers(&self) -> Vec<PauliString> {
1807        (0..self.num_qubits)
1808            .map(|i| self.row_to_pauli_string(i))
1809            .collect()
1810    }
1811
1812    /// Converts tableau row `row` to a [`PauliString`].
1813    fn row_to_pauli_string(&self, row: usize) -> PauliString {
1814        let mut ps = PauliString::new(self.num_qubits);
1815        ps.phase = Phase::from(self.phase(row));
1816        for q in 0..self.num_qubits {
1817            let x = self.x_bit(row, q);
1818            let z = self.z_bit(row, q);
1819            let pauli = match (x, z) {
1820                (false, false) => Pauli::I,
1821                (true, false) => Pauli::X,
1822                (true, true) => Pauli::Y,
1823                (false, true) => Pauli::Z,
1824            };
1825            ps.set_pauli(q, pauli);
1826        }
1827        ps
1828    }
1829
1830    /// Returns the expectation value ⟨ψ|P|ψ⟩ for a Pauli string `pauli`.
1831    ///
1832    /// Uses GF(2) Gaussian elimination over the symplectic representation of the
1833    /// stabilizer generators to determine whether `pauli` is in the stabilizer
1834    /// group (expectation +1), the negative stabilizer group (expectation -1),
1835    /// or neither (expectation 0).
1836    ///
1837    /// This correctly handles products of generators, not just individual generators.
1838    pub fn pauli_expectation(&self, pauli: &PauliString) -> Result<i32, QisError> {
1839        if pauli.num_qubits != self.num_qubits {
1840            return Err(QisError::QubitMismatch {
1841                expected: self.num_qubits,
1842                actual: pauli.num_qubits,
1843            });
1844        }
1845
1846        let n = self.num_qubits;
1847        let n_words = n.div_ceil(64);
1848        let rl = self.row_len;
1849
1850        // combo_words: one bit per generator (row), tracking which generators are
1851        // XOR'd together to match the query. n generators → n bits → n_words words.
1852        let combo_words = n_words;
1853
1854        // Build working matrix: each entry is [x-block (n_words) | z-block (n_words) | combo (combo_words)].
1855        // Row i corresponds to stabilizer generator i (tableau row n+i).
1856        let row_stride = 2 * n_words + combo_words;
1857        let mut mat: Vec<u64> = vec![0u64; n * row_stride];
1858        let mut mat_phase: Vec<i32> = vec![0i32; n];
1859
1860        for i in 0..n {
1861            let src = n + i; // stabilizer row index
1862            let src_base = Self::row_base(src, rl);
1863            let dst = &mut mat[i * row_stride..(i + 1) * row_stride];
1864            // Copy x-block
1865            dst[..n_words].copy_from_slice(&self.tableau[src_base..src_base + n_words]);
1866            // Copy z-block
1867            dst[n_words..2 * n_words]
1868                .copy_from_slice(&self.tableau[src_base + rl..src_base + rl + n_words]);
1869            // Set combo bit i → 1
1870            dst[2 * n_words + i / 64] |= 1u64 << (i % 64);
1871            // Phase as i32 in {0,1,2,3}
1872            mat_phase[i] = self.phases[src] as i32;
1873        }
1874
1875        // Build the query symplectic vector from the PauliString.
1876        let mut qx = vec![0u64; n_words];
1877        let mut qz = vec![0u64; n_words];
1878        for (q, p) in pauli.iter().enumerate() {
1879            match p {
1880                Pauli::X | Pauli::Y => qx[q / 64] |= 1u64 << (q % 64),
1881                _ => {}
1882            }
1883            match p {
1884                Pauli::Z | Pauli::Y => qz[q / 64] |= 1u64 << (q % 64),
1885                _ => {}
1886            }
1887        }
1888        let mut q_combo = vec![0u64; combo_words];
1889        let mut q_phase: i32 = pauli.phase as i32; // 0=+1,1=+i,2=-1,3=-i
1890
1891        // Reduced row echelon form (RREF) over GF(2), iterating over 2n columns:
1892        // columns 0..n → x-block (qubit q = col, word offset 0)
1893        // columns n..2n → z-block (qubit q = col-n, word offset n_words)
1894        let mut pivot_row = 0usize;
1895        for col in 0..(2 * n) {
1896            let (word_off, bit_idx) = if col < n {
1897                (0usize, col % 64)
1898            } else {
1899                (n_words, (col - n) % 64)
1900            };
1901            let word_col = if col < n { col / 64 } else { (col - n) / 64 };
1902            let word_abs = word_off + word_col;
1903            let mask = 1u64 << bit_idx;
1904
1905            // Find a pivot in rows pivot_row..n
1906            let found = (pivot_row..n).find(|&r| (mat[r * row_stride + word_abs] & mask) != 0);
1907            let p = match found {
1908                Some(p) => p,
1909                None => continue, // no pivot in this column → skip
1910            };
1911
1912            // Swap pivot row to position pivot_row
1913            if p != pivot_row {
1914                let (lo, hi) = if p < pivot_row {
1915                    (p, pivot_row)
1916                } else {
1917                    (pivot_row, p)
1918                };
1919                let (left, right) = mat.split_at_mut(hi * row_stride);
1920                left[lo * row_stride..(lo + 1) * row_stride]
1921                    .swap_with_slice(&mut right[..row_stride]);
1922                mat_phase.swap(p, pivot_row);
1923            }
1924
1925            // Eliminate all other rows (full RREF, not just lower triangular)
1926            for r in 0..n {
1927                if r == pivot_row {
1928                    continue;
1929                }
1930                if (mat[r * row_stride + word_abs] & mask) == 0 {
1931                    continue;
1932                }
1933                // XOR row pivot_row into row r
1934                // phase update: same formula as rowsum but non-mutating for pivot_row
1935                let mut sum = mat_phase[r] + mat_phase[pivot_row];
1936                for w in 0..n_words {
1937                    let xh = mat[r * row_stride + w];
1938                    let zh = mat[r * row_stride + n_words + w];
1939                    let xi = mat[pivot_row * row_stride + w];
1940                    let zi = mat[pivot_row * row_stride + n_words + w];
1941                    sum += Self::g_phase_word(xh, zh, xi, zi);
1942                }
1943                mat_phase[r] = ((sum % 4) + 4) % 4;
1944                for w in 0..row_stride {
1945                    mat[r * row_stride + w] ^= mat[pivot_row * row_stride + w];
1946                }
1947            }
1948
1949            // Eliminate the query vector at this column
1950            let q_word = if col < n { &qx } else { &qz };
1951            if (q_word[word_col] & mask) != 0 {
1952                // XOR pivot_row into query
1953                let mut sum = q_phase + mat_phase[pivot_row];
1954                for w in 0..n_words {
1955                    let xh = qx[w];
1956                    let zh = qz[w];
1957                    let xi = mat[pivot_row * row_stride + w];
1958                    let zi = mat[pivot_row * row_stride + n_words + w];
1959                    sum += Self::g_phase_word(xh, zh, xi, zi);
1960                }
1961                q_phase = ((sum % 4) + 4) % 4;
1962                for w in 0..n_words {
1963                    qx[w] ^= mat[pivot_row * row_stride + w];
1964                    qz[w] ^= mat[pivot_row * row_stride + n_words + w];
1965                }
1966                for w in 0..combo_words {
1967                    q_combo[w] ^= mat[pivot_row * row_stride + 2 * n_words + w];
1968                }
1969            }
1970
1971            pivot_row += 1;
1972        }
1973
1974        // If any x or z bit remains in the query, the Pauli is not in the stabilizer group.
1975        let in_group = qx.iter().chain(qz.iter()).all(|&w| w == 0);
1976        if !in_group {
1977            return Ok(0);
1978        }
1979
1980        // q_phase tracks the phase of the running product:
1981        //   Q_orig · G_combo  (where G_combo is the generators eliminated so far)
1982        // After elimination q_phase = phase(Q_orig · G_combo) = phase(λI), so
1983        // Q_orig = λ · G_combo^{-1} and ⟨Q_orig⟩ = i^{q_phase}:
1984        //   q_phase = 0 (+1) → Q_orig is the stabilizer  → expectation +1
1985        //   q_phase = 2 (-1) → Q_orig is −(stabilizer)   → expectation −1
1986        //   q_phase = 1 or 3 → imaginary, not a Hermitian stabilizer element
1987        Ok(match q_phase {
1988            0 => 1,
1989            2 => -1,
1990            _ => 0,
1991        })
1992    }
1993
1994    /// Exports the stabilizer tableau in Stim-compatible text format.
1995    pub fn to_stim_format(&self) -> String {
1996        let mut out = String::new();
1997        for i in 0..self.num_qubits {
1998            let stab = self.row_to_pauli_string(self.num_qubits + i);
1999            out.push_str(&stab.to_string());
2000            out.push('\n');
2001        }
2002        out
2003    }
2004}
2005
2006impl Clone for StabilizerState {
2007    /// O(n²) deep copy — clones the flat tableau, phases, and RNG state.
2008    ///
2009    /// The RNG is cloned (not re-seeded) so that parallel samples produced from
2010    /// independent clones diverge deterministically from the same seed, which is
2011    /// required for reproducible multi-shot sampling with Rayon.
2012    fn clone(&self) -> Self {
2013        StabilizerState {
2014            num_qubits: self.num_qubits,
2015            qubit_map: self.qubit_map.clone(),
2016            row_len: self.row_len,
2017            tableau: self.tableau.clone(),
2018            phases: self.phases.clone(),
2019            rng: self.rng.clone(),
2020        }
2021    }
2022}
2023
2024#[cfg(test)]
2025#[path = "./stabilizer_test.rs"]
2026mod stabilizer_test;