use super::aligned_buffer::AlignedBuffer;
use crate::circuit::circuit_impl::Circuit;
use crate::circuit::circuit_param::CircuitParam;
use crate::circuit::error::CircuitError;
use crate::circuit::gate::directive::Directive;
use crate::circuit::gate::{ClassicalDataOp, Instruction, StandardGate};
use crate::circuit::operation::Operation;
use crate::circuit::{Measurement, Qubit};
use crate::device::{ExecutionResult, Outcome};
use crate::qis::error::QisError;
use crate::qis::pauli::{Pauli, PauliString, Phase};
use crate::qis::state::{ClassicalState, RuntimeValue};
use rand::rngs::SmallRng;
use rand::{Rng, SeedableRng};
use rayon::prelude::*;
use smallvec::SmallVec;
use std::collections::HashMap;
#[derive(Debug)]
pub struct CircuitExecutionResult {
pub state: StabilizerState,
pub classical: ClassicalState,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum CircuitClassicalMode {
Ignore,
Execute,
}
#[derive(Debug)]
pub struct StabilizerState {
qubit_map: super::QubitMap,
pub num_qubits: usize,
row_len: usize,
tableau: AlignedBuffer<u64>,
phases: Box<[u8]>,
rng: SmallRng,
}
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "avx2")]
unsafe fn xor_rows_avx2(tableau: &mut [u64], dst_base: usize, src_base: usize, count: usize) {
use std::arch::x86_64::{__m256i, _mm256_load_si256, _mm256_store_si256, _mm256_xor_si256};
unsafe {
let ptr = tableau.as_mut_ptr();
let dst = ptr.add(dst_base) as *mut __m256i;
let src = ptr.add(src_base) as *const __m256i;
let chunks = count / 4; for i in 0..chunks {
let a = _mm256_load_si256(dst.add(i));
let b = _mm256_load_si256(src.add(i));
_mm256_store_si256(dst.add(i), _mm256_xor_si256(a, b));
}
}
}
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "sse2")]
unsafe fn xor_rows_sse2(tableau: &mut [u64], dst_base: usize, src_base: usize, count: usize) {
use std::arch::x86_64::{__m128i, _mm_load_si128, _mm_store_si128, _mm_xor_si128};
unsafe {
let ptr = tableau.as_mut_ptr();
let dst = ptr.add(dst_base) as *mut __m128i;
let src = ptr.add(src_base) as *const __m128i;
let chunks = count / 2; for i in 0..chunks {
let a = _mm_load_si128(dst.add(i));
let b = _mm_load_si128(src.add(i));
_mm_store_si128(dst.add(i), _mm_xor_si128(a, b));
}
}
}
#[cfg(target_arch = "aarch64")]
#[target_feature(enable = "neon")]
unsafe fn xor_rows_neon(tableau: &mut [u64], dst_base: usize, src_base: usize, count: usize) {
use std::arch::aarch64::{veorq_u64, vld1q_u64, vst1q_u64};
unsafe {
let ptr = tableau.as_mut_ptr();
let dst = ptr.add(dst_base);
let src = ptr.add(src_base) as *const u64;
let chunks = count / 2; for i in 0..chunks {
let a = vld1q_u64(dst.add(i * 2) as *const u64);
let b = vld1q_u64(src.add(i * 2));
vst1q_u64(dst.add(i * 2), veorq_u64(a, b));
}
}
}
#[inline]
fn xor_rows_scalar(tableau: &mut [u64], dst_base: usize, src_base: usize, count: usize) {
let ptr = tableau.as_mut_ptr();
for w in 0..count {
unsafe { *ptr.add(dst_base + w) ^= *ptr.add(src_base + w) };
}
}
#[inline]
#[allow(unreachable_code)] unsafe fn xor_rows_dispatch(tableau: &mut [u64], dst_base: usize, src_base: usize, count: usize) {
#[cfg(target_arch = "x86_64")]
{
if is_x86_feature_detected!("avx2") {
return unsafe { xor_rows_avx2(tableau, dst_base, src_base, count) };
}
if is_x86_feature_detected!("sse2") {
return unsafe { xor_rows_sse2(tableau, dst_base, src_base, count) };
}
}
#[cfg(target_arch = "aarch64")]
{
return unsafe { xor_rows_neon(tableau, dst_base, src_base, count) };
}
xor_rows_scalar(tableau, dst_base, src_base, count);
}
impl StabilizerState {
pub fn new(n: usize) -> Self {
assert!(n > 0, "StabilizerState requires at least 1 qubit");
let words_needed = n.div_ceil(64);
let row_len = ((words_needed + 7) & !7).max(8);
let total_rows = 2 * n + 1;
let total_words = total_rows * 2 * row_len;
let mut tableau = AlignedBuffer::new_zeroed(total_words);
let phases = vec![0u8; total_rows].into_boxed_slice();
for i in 0..n {
let word = i / 64;
let bit = i % 64;
tableau[Self::x_offset(i, i, row_len, word)] |= 1u64 << bit;
}
for i in 0..n {
let word = i / 64;
let bit = i % 64;
tableau[Self::z_offset(n + i, i, row_len, word)] |= 1u64 << bit;
}
StabilizerState {
num_qubits: n,
qubit_map: None,
row_len,
tableau,
phases,
rng: SmallRng::from_os_rng(),
}
}
fn reset_from(&mut self, source: &StabilizerState) {
debug_assert_eq!(self.num_qubits, source.num_qubits);
self.qubit_map.clone_from(&source.qubit_map);
debug_assert_eq!(self.row_len, source.row_len);
self.tableau
.as_mut_slice()
.copy_from_slice(source.tableau.as_slice());
self.phases.copy_from_slice(&source.phases);
}
#[inline(always)]
fn scratch_row(&self) -> usize {
2 * self.num_qubits
}
fn clear_scratch(&mut self) {
let s = self.scratch_row();
let base = Self::row_base(s, self.row_len);
for w in 0..2 * self.row_len {
self.tableau[base + w] = 0;
}
self.phases[s] = 0;
}
#[inline(always)]
fn row_base(row: usize, row_len: usize) -> usize {
row * 2 * row_len
}
#[inline(always)]
fn x_offset(row: usize, _col_qubit: usize, row_len: usize, word: usize) -> usize {
Self::row_base(row, row_len) + word
}
#[inline(always)]
fn z_offset(row: usize, _col_qubit: usize, row_len: usize, word: usize) -> usize {
Self::row_base(row, row_len) + row_len + word
}
#[inline(always)]
pub(crate) fn x_bit(&self, row: usize, q: usize) -> bool {
let base = Self::row_base(row, self.row_len);
let word = q / 64;
let bit = q % 64;
(self.tableau[base + word] >> bit) & 1 == 1
}
#[inline(always)]
pub(crate) fn z_bit(&self, row: usize, q: usize) -> bool {
let base = Self::row_base(row, self.row_len);
let word = q / 64;
let bit = q % 64;
(self.tableau[base + self.row_len + word] >> bit) & 1 == 1
}
#[allow(dead_code)]
#[inline(always)]
pub(crate) fn set_x_bit(&mut self, row: usize, q: usize, val: bool) {
let base = Self::row_base(row, self.row_len);
let word = q / 64;
let bit = q % 64;
if val {
self.tableau[base + word] |= 1u64 << bit;
} else {
self.tableau[base + word] &= !(1u64 << bit);
}
}
#[inline(always)]
pub(crate) fn set_z_bit(&mut self, row: usize, q: usize, val: bool) {
let base = Self::row_base(row, self.row_len);
let word = q / 64;
let bit = q % 64;
if val {
self.tableau[base + self.row_len + word] |= 1u64 << bit;
} else {
self.tableau[base + self.row_len + word] &= !(1u64 << bit);
}
}
#[inline(always)]
pub(crate) fn phase(&self, row: usize) -> u8 {
self.phases[row]
}
#[inline(always)]
pub(crate) fn set_phase(&mut self, row: usize, val: u8) {
self.phases[row] = val & 3;
}
pub(crate) fn validate_qubit(&self, q: usize) -> Result<(), QisError> {
if self.num_qubits == 0 {
return Err(QisError::InvalidParameterValue(
"no valid qubit indices in a zero-qubit system".to_string(),
));
}
if q >= self.num_qubits {
return Err(QisError::IndexOutOfBounds {
index: q,
max: self.num_qubits.saturating_sub(1),
});
}
Ok(())
}
pub(crate) fn validate_two_qubits(&self, q0: usize, q1: usize) -> Result<(), QisError> {
self.validate_qubit(q0)?;
self.validate_qubit(q1)?;
if q0 == q1 {
return Err(QisError::InvalidParameterValue(
"Two-qubit gate requires distinct qubit indices".to_string(),
));
}
Ok(())
}
pub fn apply_cx(&mut self, control: usize, target: usize) -> Result<(), QisError> {
self.validate_two_qubits(control, target)?;
let c_word = control / 64;
let c_mask = 1u64 << (control % 64);
let t_word = target / 64;
let t_mask = 1u64 << (target % 64);
let rl = self.row_len;
for row in 0..(2 * self.num_qubits) {
let base = row * 2 * rl;
let xc = (self.tableau[base + c_word] & c_mask) != 0;
let xt = (self.tableau[base + t_word] & t_mask) != 0;
let zc = (self.tableau[base + rl + c_word] & c_mask) != 0;
let zt = (self.tableau[base + rl + t_word] & t_mask) != 0;
if xc && zt && (xt ^ zc ^ true) {
self.phases[row] ^= 2;
}
if xc {
self.tableau[base + t_word] ^= t_mask;
}
if zt {
self.tableau[base + rl + c_word] ^= c_mask;
}
}
Ok(())
}
pub fn apply_swap(&mut self, q0: usize, q1: usize) -> Result<(), QisError> {
self.validate_two_qubits(q0, q1)?;
let w0 = q0 / 64;
let m0 = 1u64 << (q0 % 64);
let w1 = q1 / 64;
let m1 = 1u64 << (q1 % 64);
let rl = self.row_len;
for row in 0..(2 * self.num_qubits) {
let base = row * 2 * rl;
let x0 = (self.tableau[base + w0] & m0) != 0;
let z0 = (self.tableau[base + rl + w0] & m0) != 0;
let x1 = (self.tableau[base + w1] & m1) != 0;
let z1 = (self.tableau[base + rl + w1] & m1) != 0;
if x1 {
self.tableau[base + w0] |= m0;
} else {
self.tableau[base + w0] &= !m0;
}
if z1 {
self.tableau[base + rl + w0] |= m0;
} else {
self.tableau[base + rl + w0] &= !m0;
}
if x0 {
self.tableau[base + w1] |= m1;
} else {
self.tableau[base + w1] &= !m1;
}
if z0 {
self.tableau[base + rl + w1] |= m1;
} else {
self.tableau[base + rl + w1] &= !m1;
}
}
Ok(())
}
pub fn apply_cz(&mut self, q0: usize, q1: usize) -> Result<(), QisError> {
self.validate_two_qubits(q0, q1)?;
let w0 = q0 / 64;
let m0 = 1u64 << (q0 % 64);
let w1 = q1 / 64;
let m1 = 1u64 << (q1 % 64);
let rl = self.row_len;
for row in 0..(2 * self.num_qubits) {
let base = row * 2 * rl;
let x1 = (self.tableau[base + w1] & m1) != 0;
let z1 = (self.tableau[base + rl + w1] & m1) != 0;
if x1 && z1 {
self.phases[row] ^= 2;
}
if z1 {
self.tableau[base + w1] |= m1;
} else {
self.tableau[base + w1] &= !m1;
}
if x1 {
self.tableau[base + rl + w1] |= m1;
} else {
self.tableau[base + rl + w1] &= !m1;
}
let xc = (self.tableau[base + w0] & m0) != 0;
let xt = (self.tableau[base + w1] & m1) != 0;
let zc = (self.tableau[base + rl + w0] & m0) != 0;
let zt = (self.tableau[base + rl + w1] & m1) != 0;
if xc && zt && (xt ^ zc ^ true) {
self.phases[row] ^= 2;
}
if xc {
self.tableau[base + w1] ^= m1;
}
if zt {
self.tableau[base + rl + w0] ^= m0;
}
let x1 = (self.tableau[base + w1] & m1) != 0;
let z1 = (self.tableau[base + rl + w1] & m1) != 0;
if x1 && z1 {
self.phases[row] ^= 2;
}
if z1 {
self.tableau[base + w1] |= m1;
} else {
self.tableau[base + w1] &= !m1;
}
if x1 {
self.tableau[base + rl + w1] |= m1;
} else {
self.tableau[base + rl + w1] &= !m1;
}
}
Ok(())
}
pub fn apply_cy(&mut self, control: usize, target: usize) -> Result<(), QisError> {
self.validate_two_qubits(control, target)?;
let cw = control / 64;
let cm = 1u64 << (control % 64);
let tw = target / 64;
let tm = 1u64 << (target % 64);
let rl = self.row_len;
for row in 0..(2 * self.num_qubits) {
let base = row * 2 * rl;
let x = (self.tableau[base + tw] & tm) != 0;
let z = (self.tableau[base + rl + tw] & tm) != 0;
if x && !z {
self.phases[row] ^= 2;
}
if x {
self.tableau[base + rl + tw] ^= tm;
}
let xc = (self.tableau[base + cw] & cm) != 0;
let xt = (self.tableau[base + tw] & tm) != 0;
let zc = (self.tableau[base + rl + cw] & cm) != 0;
let zt = (self.tableau[base + rl + tw] & tm) != 0;
if xc && zt && (xt ^ zc ^ true) {
self.phases[row] ^= 2;
}
if xc {
self.tableau[base + tw] ^= tm;
}
if zt {
self.tableau[base + rl + cw] ^= cm;
}
let x = (self.tableau[base + tw] & tm) != 0;
let z = (self.tableau[base + rl + tw] & tm) != 0;
if x && z {
self.phases[row] ^= 2;
}
if x {
self.tableau[base + rl + tw] ^= tm;
}
}
Ok(())
}
pub fn num_qubits(&self) -> usize {
self.num_qubits
}
pub fn apply_h(&mut self, qubit: usize) -> Result<(), QisError> {
self.validate_qubit(qubit)?;
let word = qubit / 64;
let mask = 1u64 << (qubit % 64);
let rl = self.row_len;
for row in 0..(2 * self.num_qubits) {
let base = row * 2 * rl;
let x = (self.tableau[base + word] & mask) != 0;
let z = (self.tableau[base + rl + word] & mask) != 0;
if x && z {
self.phases[row] ^= 2;
}
if x {
self.tableau[base + rl + word] |= mask;
} else {
self.tableau[base + rl + word] &= !mask;
}
if z {
self.tableau[base + word] |= mask;
} else {
self.tableau[base + word] &= !mask;
}
}
Ok(())
}
pub fn apply_s(&mut self, qubit: usize) -> Result<(), QisError> {
self.validate_qubit(qubit)?;
let word = qubit / 64;
let mask = 1u64 << (qubit % 64);
let rl = self.row_len;
for row in 0..(2 * self.num_qubits) {
let base = row * 2 * rl;
let x = (self.tableau[base + word] & mask) != 0;
let z = (self.tableau[base + rl + word] & mask) != 0;
if x && z {
self.phases[row] ^= 2;
}
if x {
self.tableau[base + rl + word] ^= mask;
}
}
Ok(())
}
pub fn apply_sdg(&mut self, qubit: usize) -> Result<(), QisError> {
self.validate_qubit(qubit)?;
let word = qubit / 64;
let mask = 1u64 << (qubit % 64);
let rl = self.row_len;
for row in 0..(2 * self.num_qubits) {
let base = row * 2 * rl;
let x = (self.tableau[base + word] & mask) != 0;
let z = (self.tableau[base + rl + word] & mask) != 0;
if x && !z {
self.phases[row] ^= 2;
}
if x {
self.tableau[base + rl + word] ^= mask;
}
}
Ok(())
}
pub fn apply_x(&mut self, qubit: usize) -> Result<(), QisError> {
self.validate_qubit(qubit)?;
let word = qubit / 64;
let mask = 1u64 << (qubit % 64);
let rl = self.row_len;
for row in 0..(2 * self.num_qubits) {
let base = row * 2 * rl;
if (self.tableau[base + rl + word] & mask) != 0 {
self.phases[row] ^= 2;
}
}
Ok(())
}
pub fn apply_z(&mut self, qubit: usize) -> Result<(), QisError> {
self.validate_qubit(qubit)?;
let word = qubit / 64;
let mask = 1u64 << (qubit % 64);
let rl = self.row_len;
for row in 0..(2 * self.num_qubits) {
let base = row * 2 * rl;
if (self.tableau[base + word] & mask) != 0 {
self.phases[row] ^= 2;
}
}
Ok(())
}
pub fn apply_y(&mut self, qubit: usize) -> Result<(), QisError> {
self.validate_qubit(qubit)?;
let word = qubit / 64;
let mask = 1u64 << (qubit % 64);
let rl = self.row_len;
for row in 0..(2 * self.num_qubits) {
let base = row * 2 * rl;
let x = (self.tableau[base + word] & mask) != 0;
let z = (self.tableau[base + rl + word] & mask) != 0;
if x ^ z {
self.phases[row] ^= 2;
}
}
Ok(())
}
pub fn apply_x2p(&mut self, qubit: usize) -> Result<(), QisError> {
self.validate_qubit(qubit)?;
let word = qubit / 64;
let mask = 1u64 << (qubit % 64);
let rl = self.row_len;
for row in 0..(2 * self.num_qubits) {
let base = row * 2 * rl;
let x = (self.tableau[base + word] & mask) != 0;
let z = (self.tableau[base + rl + word] & mask) != 0;
if !x && z {
self.phases[row] ^= 2;
}
if z {
self.tableau[base + word] ^= mask;
}
}
Ok(())
}
pub fn apply_x2m(&mut self, qubit: usize) -> Result<(), QisError> {
self.validate_qubit(qubit)?;
let word = qubit / 64;
let mask = 1u64 << (qubit % 64);
let rl = self.row_len;
for row in 0..(2 * self.num_qubits) {
let base = row * 2 * rl;
let x = (self.tableau[base + word] & mask) != 0;
let z = (self.tableau[base + rl + word] & mask) != 0;
if x && z {
self.phases[row] ^= 2;
}
if z {
self.tableau[base + word] ^= mask;
}
}
Ok(())
}
pub fn apply_y2p(&mut self, qubit: usize) -> Result<(), QisError> {
self.validate_qubit(qubit)?;
let word = qubit / 64;
let mask = 1u64 << (qubit % 64);
let rl = self.row_len;
for row in 0..(2 * self.num_qubits) {
let base = row * 2 * rl;
let x = (self.tableau[base + word] & mask) != 0;
let z = (self.tableau[base + rl + word] & mask) != 0;
if x && !z {
self.phases[row] ^= 2;
}
if x {
self.tableau[base + rl + word] |= mask;
} else {
self.tableau[base + rl + word] &= !mask;
}
if z {
self.tableau[base + word] |= mask;
} else {
self.tableau[base + word] &= !mask;
}
}
Ok(())
}
pub fn apply_y2m(&mut self, qubit: usize) -> Result<(), QisError> {
self.validate_qubit(qubit)?;
let word = qubit / 64;
let mask = 1u64 << (qubit % 64);
let rl = self.row_len;
for row in 0..(2 * self.num_qubits) {
let base = row * 2 * rl;
let x = (self.tableau[base + word] & mask) != 0;
let z = (self.tableau[base + rl + word] & mask) != 0;
if !x && z {
self.phases[row] ^= 2;
}
if x {
self.tableau[base + rl + word] |= mask;
} else {
self.tableau[base + rl + word] &= !mask;
}
if z {
self.tableau[base + word] |= mask;
} else {
self.tableau[base + word] &= !mask;
}
}
Ok(())
}
#[allow(dead_code)]
#[inline(always)]
pub(crate) fn g_phase(x1: bool, z1: bool, x2: bool, z2: bool) -> i32 {
match (x1, z1) {
(false, false) => 0,
(true, true) => z2 as i32 - x2 as i32,
(true, false) => z2 as i32 * (2 * x2 as i32 - 1),
(false, true) => x2 as i32 * (1 - 2 * z2 as i32),
}
}
#[inline(always)]
pub(crate) fn g_phase_word(xh: u64, zh: u64, xi: u64, zi: u64) -> i32 {
let anti_comm = (xh & zi) ^ (xi & zh);
let pos = (xh & zi & (zh ^ xi)) | (!xh & zh & xi & !zi);
2 * pos.count_ones() as i32 - anti_comm.count_ones() as i32
}
fn rowsum(&mut self, h: usize, i: usize) {
debug_assert_ne!(h, i, "rowsum requires distinct rows");
let h_base = Self::row_base(h, self.row_len);
let i_base = Self::row_base(i, self.row_len);
let rl = self.row_len;
let mut sum = self.phases[h] as i32 + self.phases[i] as i32;
let n_words = self.num_qubits.div_ceil(64);
for w in 0..n_words {
let xh = self.tableau[h_base + w];
let zh = self.tableau[h_base + rl + w];
let xi = self.tableau[i_base + w];
let zi = self.tableau[i_base + rl + w];
sum += Self::g_phase_word(xh, zh, xi, zi);
}
self.phases[h] = (((sum % 4) + 4) % 4) as u8;
unsafe {
xor_rows_dispatch(self.tableau.as_mut_slice(), h_base, i_base, 2 * rl);
}
}
pub fn measure(&mut self, qubit: usize) -> Result<bool, QisError> {
self.validate_qubit(qubit)?;
let maybe_p = (self.num_qubits..2 * self.num_qubits).find(|&row| self.x_bit(row, qubit));
match maybe_p {
Some(p) => {
for i in 0..2 * self.num_qubits {
if i != p && self.x_bit(i, qubit) {
self.rowsum(i, p);
}
}
let dest = p - self.num_qubits;
let p_base = Self::row_base(p, self.row_len);
let d_base = Self::row_base(dest, self.row_len);
unsafe {
let src = self.tableau.as_slice()[p_base..p_base + 2 * self.row_len].as_ptr();
let dst =
self.tableau.as_mut_slice()[d_base..d_base + 2 * self.row_len].as_mut_ptr();
std::ptr::copy_nonoverlapping(src, dst, 2 * self.row_len);
}
self.phases[dest] = self.phases[p];
let p_base = Self::row_base(p, self.row_len);
for w in 0..2 * self.row_len {
self.tableau[p_base + w] = 0;
}
self.set_z_bit(p, qubit, true);
let b: bool = self.rng.random();
self.set_phase(p, if b { 2 } else { 0 });
Ok(b)
}
None => {
let scratch = self.scratch_row();
self.clear_scratch();
for i in 0..self.num_qubits {
if self.x_bit(i, qubit) {
self.rowsum(scratch, self.num_qubits + i);
}
}
Ok(self.phases[scratch] == 2)
}
}
}
pub fn measure_all(&mut self) -> Outcome {
let num_chunks = self.num_qubits.div_ceil(64);
let mut chunks = SmallVec::from_elem(0u64, num_chunks);
for qubit in 0..self.num_qubits {
if self.measure(qubit).unwrap() {
chunks[qubit / 64] |= 1u64 << (qubit % 64);
}
}
Outcome::new(chunks)
}
pub fn reset(&mut self, qubit: usize) -> Result<(), QisError> {
self.validate_qubit(qubit)?;
if self.measure(qubit)? {
self.apply_x(qubit)?;
}
Ok(())
}
fn force_measure(&mut self, qubit: usize, outcome: bool) -> bool {
let maybe_p = (self.num_qubits..2 * self.num_qubits).find(|&row| self.x_bit(row, qubit));
match maybe_p {
Some(p) => {
for i in 0..2 * self.num_qubits {
if i != p && self.x_bit(i, qubit) {
self.rowsum(i, p);
}
}
let dest = p - self.num_qubits;
let p_base = Self::row_base(p, self.row_len);
let d_base = Self::row_base(dest, self.row_len);
unsafe {
let src = self.tableau.as_slice()[p_base..p_base + 2 * self.row_len].as_ptr();
let dst =
self.tableau.as_mut_slice()[d_base..d_base + 2 * self.row_len].as_mut_ptr();
std::ptr::copy_nonoverlapping(src, dst, 2 * self.row_len);
}
self.phases[dest] = self.phases[p];
let p_base = Self::row_base(p, self.row_len);
for w in 0..2 * self.row_len {
self.tableau[p_base + w] = 0;
}
self.set_z_bit(p, qubit, true);
self.set_phase(p, if outcome { 2 } else { 0 });
true }
None => {
let scratch = self.scratch_row();
self.clear_scratch();
for i in 0..self.num_qubits {
if self.x_bit(i, qubit) {
self.rowsum(scratch, self.num_qubits + i);
}
}
let actual = self.phases[scratch] == 2;
actual == outcome }
}
}
pub fn probability_of(&self, bits: &[bool]) -> Result<f64, QisError> {
if bits.len() != self.num_qubits {
return Err(QisError::QubitMismatch {
expected: self.num_qubits,
actual: bits.len(),
});
}
let mut s = self.clone();
let mut prob = 1.0_f64;
for (qubit, &desired) in bits.iter().enumerate() {
let is_random = (s.num_qubits..2 * s.num_qubits).any(|row| s.x_bit(row, qubit));
if is_random {
prob *= 0.5;
s.force_measure(qubit, desired);
} else {
if !s.force_measure(qubit, desired) {
return Ok(0.0);
}
}
}
Ok(prob)
}
pub fn probabilities(&self) -> Result<Vec<f64>, QisError> {
const MAX_QUBITS: usize = 20;
if self.num_qubits > MAX_QUBITS {
return Err(QisError::InvalidParameterValue(format!(
"probabilities() is only supported for n ≤ {MAX_QUBITS} qubits \
(requested n = {}); use sample_shots() for large systems",
self.num_qubits
)));
}
let size = 1usize << self.num_qubits;
let mut probs = vec![0.0_f64; size];
for (i, prob) in probs.iter_mut().enumerate() {
let bits: Vec<bool> = (0..self.num_qubits).map(|q| (i >> q) & 1 == 1).collect();
*prob = self.probability_of(&bits)?;
}
Ok(probs)
}
pub fn sample_shots(&self, shots: usize) -> Vec<Outcome> {
self.sample_shots_with_seed(shots, None)
}
pub fn sample_shots_with_seed(&self, shots: usize, seed: Option<u64>) -> Vec<Outcome> {
let seeds: Vec<u64> = {
let mut rng = seed
.map(SmallRng::seed_from_u64)
.unwrap_or_else(|| self.rng.clone());
(0..shots).map(|_| rng.random()).collect()
};
seeds
.into_par_iter()
.map_with(self.clone(), |work, seed| {
work.reset_from(self);
work.rng = SmallRng::seed_from_u64(seed);
work.measure_all()
})
.collect()
}
pub(crate) fn seed_rng(&mut self, seed: u64) {
self.rng = SmallRng::seed_from_u64(seed);
}
pub fn sample(
&self,
measurement: &Measurement,
shots: usize,
) -> Result<ExecutionResult, QisError> {
self.sample_with_seed(measurement, shots, None)
}
pub fn sample_with_seed(
&self,
measurement: &Measurement,
shots: usize,
seed: Option<u64>,
) -> Result<ExecutionResult, QisError> {
let projection = super::resolve_measurement(measurement, &self.qubit_map, self.num_qubits)?;
let mut counts = HashMap::new();
for full in self.sample_shots_with_seed(shots, seed) {
*counts.entry(projection.project(&full)).or_insert(0usize) += 1;
}
Ok(ExecutionResult::from_counts(
"stabilizer-sample".to_string(),
measurement.qubits().to_vec(),
shots,
measurement.width(),
Some("stabilizer".to_string()),
counts,
))
}
pub fn probs(&self, measurement: &Measurement) -> Result<HashMap<Outcome, f64>, QisError> {
let projection = super::resolve_measurement(measurement, &self.qubit_map, self.num_qubits)?;
let mut marginal = HashMap::new();
for (basis, prob) in self.probabilities()?.into_iter().enumerate() {
if prob == 0.0 {
continue;
}
*marginal
.entry(projection.project_basis(basis))
.or_insert(0.0) += prob;
}
Ok(marginal)
}
pub fn from_circuit(circuit: &Circuit) -> Result<Self, QisError> {
let mut state = StabilizerState::new(circuit.num_qubits());
state.apply_circuit(circuit)?;
Ok(state)
}
pub fn apply_circuit(&mut self, input_circuit: &Circuit) -> Result<(), QisError> {
if input_circuit.num_qubits() != self.num_qubits {
return Err(QisError::InvalidStateDimension(input_circuit.num_qubits()));
}
if input_circuit
.operations()
.iter()
.any(|op| matches!(op.instruction, Instruction::ClassicalControl(_)))
{
return Err(QisError::UnsupportedOperation(
"classical control flow is not supported in stabilizer simulation".to_string(),
));
}
let circuit = input_circuit.decompose()?;
super::validate_terminal_measurements(&circuit)?;
let qubit_map = super::prepare_qubit_map(&circuit.qubits(), &self.qubit_map)?;
let parameter_values: Vec<Option<f64>> = circuit
.parameters()
.iter()
.map(|p| p.evaluate(&None).ok())
.collect();
let mut classical = ClassicalState::for_circuit(&circuit);
StabilizerState::execute_operations(
self,
circuit.operations(),
&qubit_map,
¶meter_values,
&mut classical,
CircuitClassicalMode::Ignore,
)?;
self.qubit_map = Some(qubit_map);
Ok(())
}
pub fn run_circuit(circuit: &Circuit) -> Result<CircuitExecutionResult, QisError> {
StabilizerState::run_circuit_with_mode(circuit, CircuitClassicalMode::Execute)
}
fn run_circuit_with_mode(
input_circuit: &Circuit,
classical_mode: CircuitClassicalMode,
) -> Result<CircuitExecutionResult, QisError> {
if input_circuit
.operations()
.iter()
.any(|op| matches!(op.instruction, Instruction::ClassicalControl(_)))
{
return Err(QisError::UnsupportedOperation(
"classical control flow is not supported in stabilizer simulation".to_string(),
));
}
let circuit = input_circuit.decompose()?;
let mut state = StabilizerState::new(circuit.num_qubits());
let qubits = circuit.qubits();
let qubit_map: std::collections::HashMap<_, _> = qubits
.iter()
.enumerate()
.map(|(idx, q)| (*q, idx))
.collect();
let qubit_map = std::sync::Arc::new(qubit_map);
state.qubit_map = Some(qubit_map.clone());
let parameter_values: Vec<Option<f64>> = circuit
.parameters()
.iter()
.map(|p| p.evaluate(&None).ok())
.collect();
let mut classical = ClassicalState::for_circuit(&circuit);
StabilizerState::execute_operations(
&mut state,
circuit.operations(),
&qubit_map,
¶meter_values,
&mut classical,
classical_mode,
)?;
classical.rebind_to_circuit(input_circuit)?;
Ok(CircuitExecutionResult { state, classical })
}
fn execute_operations(
state: &mut StabilizerState,
ops: &[Operation],
qubit_map: &std::collections::HashMap<Qubit, usize>,
parameter_values: &[Option<f64>],
classical: &mut ClassicalState,
classical_mode: CircuitClassicalMode,
) -> Result<(), QisError> {
for op in ops {
let qubit_indices: Vec<usize> =
op.qubits
.iter()
.map(|q| {
qubit_map.get(q).copied().ok_or_else(|| {
QisError::CircuitError(CircuitError::QubitNotFound(q.id()))
})
})
.collect::<Result<_, _>>()?;
let params: Vec<f64> = op
.params
.iter()
.map(|p| match p {
CircuitParam::Fixed(v) => Ok(*v),
CircuitParam::Index(idx) => parameter_values
.get(*idx as usize)
.copied()
.flatten()
.ok_or(QisError::CircuitError(CircuitError::SymbolicParameterError)),
})
.collect::<Result<_, _>>()?;
match &op.instruction {
Instruction::Standard(gate) => {
state.apply_clifford_gate(*gate, &qubit_indices, ¶ms)?;
}
Instruction::McGate(mc) if mc.num_ctrl_qubits() == 1 => match mc.base_gate() {
StandardGate::X => state.apply_cx(qubit_indices[0], qubit_indices[1])?,
StandardGate::Y => state.apply_cy(qubit_indices[0], qubit_indices[1])?,
StandardGate::Z => state.apply_cz(qubit_indices[0], qubit_indices[1])?,
g => {
return Err(QisError::UnsupportedOperation(format!(
"Controlled-{g} is not supported in stabilizer simulation"
)));
}
},
Instruction::McGate(mc) => {
return Err(QisError::UnsupportedOperation(format!(
"{}-controlled gates are not supported in stabilizer simulation",
mc.num_ctrl_qubits()
)));
}
Instruction::Directive(directive) => match directive {
Directive::Barrier => {} Directive::Measure => {
return Err(QisError::UnsupportedOperation(
"legacy Measure directive is not supported by stabilizer circuit execution; use Circuit::measure".to_string(),
));
}
Directive::Reset => {
let q = qubit_indices[0];
state.reset(q)?;
}
},
Instruction::Delay => {} Instruction::UnitaryGate(_) => {
return Err(QisError::UnsupportedOperation(
"Arbitrary unitary gates are not Clifford".to_string(),
));
}
Instruction::CircuitGate(_) => {
return Err(QisError::CircuitError(CircuitError::InvalidOperation(
"CircuitGate should have been decomposed".to_string(),
)));
}
Instruction::ClassicalControl(_) => {
return Err(QisError::UnsupportedOperation(
"classical control flow is not supported in stabilizer simulation"
.to_string(),
));
}
Instruction::ClassicalData(data) => {
if classical_mode == CircuitClassicalMode::Ignore {
continue;
}
match data {
ClassicalDataOp::MeasureBit { result } => {
let bit = state.measure(qubit_indices[0])?;
classical.set_value(*result, RuntimeValue::Bit(bit))?;
}
ClassicalDataOp::MeasureBits { result } => {
let bits = qubit_indices
.iter()
.copied()
.map(|q| state.measure(q))
.collect::<Result<Vec<_>, _>>()?;
classical.set_value(*result, RuntimeValue::bit_vec_from_lsb(&bits))?;
}
ClassicalDataOp::Store { target, value } => {
classical.store(*target, value)?;
}
}
}
}
}
Ok(())
}
pub fn apply_standard_gate(
&mut self,
gate: StandardGate,
qubits: &[usize],
params: &[f64],
) -> Result<(), QisError> {
if qubits.len() != gate.num_qubits() {
return Err(QisError::InvalidParameterValue(format!(
"Gate {:?} requires {} qubits, got {}",
gate,
gate.num_qubits(),
qubits.len()
)));
}
if params.len() != gate.num_params() {
return Err(QisError::InvalidParameterValue(format!(
"Gate {:?} requires {} parameters, got {}",
gate,
gate.num_params(),
params.len()
)));
}
self.apply_clifford_gate(gate, qubits, params)
}
fn apply_clifford_gate(
&mut self,
gate: StandardGate,
qubits: &[usize],
_params: &[f64],
) -> Result<(), QisError> {
match gate {
StandardGate::I => {}
StandardGate::H => self.apply_h(qubits[0])?,
StandardGate::X => self.apply_x(qubits[0])?,
StandardGate::Y => self.apply_y(qubits[0])?,
StandardGate::Z => self.apply_z(qubits[0])?,
StandardGate::S => self.apply_s(qubits[0])?,
StandardGate::SDG => self.apply_sdg(qubits[0])?,
StandardGate::X2P => self.apply_x2p(qubits[0])?,
StandardGate::X2M => self.apply_x2m(qubits[0])?,
StandardGate::Y2P => self.apply_y2p(qubits[0])?,
StandardGate::Y2M => self.apply_y2m(qubits[0])?,
StandardGate::CX => self.apply_cx(qubits[0], qubits[1])?,
StandardGate::CY => self.apply_cy(qubits[0], qubits[1])?,
StandardGate::CZ => self.apply_cz(qubits[0], qubits[1])?,
StandardGate::SWAP => self.apply_swap(qubits[0], qubits[1])?,
g => {
return Err(QisError::NonCliffordGate(format!(
"Gate '{g}' is not a Clifford gate — use Statevector for universal simulation"
)));
}
}
Ok(())
}
pub fn get_stabilizers(&self) -> Vec<PauliString> {
(0..self.num_qubits)
.map(|i| self.row_to_pauli_string(self.num_qubits + i))
.collect()
}
pub fn get_destabilizers(&self) -> Vec<PauliString> {
(0..self.num_qubits)
.map(|i| self.row_to_pauli_string(i))
.collect()
}
fn row_to_pauli_string(&self, row: usize) -> PauliString {
let mut ps = PauliString::new(self.num_qubits);
ps.phase = Phase::from(self.phase(row));
for q in 0..self.num_qubits {
let x = self.x_bit(row, q);
let z = self.z_bit(row, q);
let pauli = match (x, z) {
(false, false) => Pauli::I,
(true, false) => Pauli::X,
(true, true) => Pauli::Y,
(false, true) => Pauli::Z,
};
ps.set_pauli(q, pauli);
}
ps
}
pub fn pauli_expectation(&self, pauli: &PauliString) -> Result<i32, QisError> {
if pauli.num_qubits != self.num_qubits {
return Err(QisError::QubitMismatch {
expected: self.num_qubits,
actual: pauli.num_qubits,
});
}
let n = self.num_qubits;
let n_words = n.div_ceil(64);
let rl = self.row_len;
let combo_words = n_words;
let row_stride = 2 * n_words + combo_words;
let mut mat: Vec<u64> = vec![0u64; n * row_stride];
let mut mat_phase: Vec<i32> = vec![0i32; n];
for i in 0..n {
let src = n + i; let src_base = Self::row_base(src, rl);
let dst = &mut mat[i * row_stride..(i + 1) * row_stride];
dst[..n_words].copy_from_slice(&self.tableau[src_base..src_base + n_words]);
dst[n_words..2 * n_words]
.copy_from_slice(&self.tableau[src_base + rl..src_base + rl + n_words]);
dst[2 * n_words + i / 64] |= 1u64 << (i % 64);
mat_phase[i] = self.phases[src] as i32;
}
let mut qx = vec![0u64; n_words];
let mut qz = vec![0u64; n_words];
for (q, p) in pauli.iter().enumerate() {
match p {
Pauli::X | Pauli::Y => qx[q / 64] |= 1u64 << (q % 64),
_ => {}
}
match p {
Pauli::Z | Pauli::Y => qz[q / 64] |= 1u64 << (q % 64),
_ => {}
}
}
let mut q_combo = vec![0u64; combo_words];
let mut q_phase: i32 = pauli.phase as i32;
let mut pivot_row = 0usize;
for col in 0..(2 * n) {
let (word_off, bit_idx) = if col < n {
(0usize, col % 64)
} else {
(n_words, (col - n) % 64)
};
let word_col = if col < n { col / 64 } else { (col - n) / 64 };
let word_abs = word_off + word_col;
let mask = 1u64 << bit_idx;
let found = (pivot_row..n).find(|&r| (mat[r * row_stride + word_abs] & mask) != 0);
let p = match found {
Some(p) => p,
None => continue, };
if p != pivot_row {
let (lo, hi) = if p < pivot_row {
(p, pivot_row)
} else {
(pivot_row, p)
};
let (left, right) = mat.split_at_mut(hi * row_stride);
left[lo * row_stride..(lo + 1) * row_stride]
.swap_with_slice(&mut right[..row_stride]);
mat_phase.swap(p, pivot_row);
}
for r in 0..n {
if r == pivot_row {
continue;
}
if (mat[r * row_stride + word_abs] & mask) == 0 {
continue;
}
let mut sum = mat_phase[r] + mat_phase[pivot_row];
for w in 0..n_words {
let xh = mat[r * row_stride + w];
let zh = mat[r * row_stride + n_words + w];
let xi = mat[pivot_row * row_stride + w];
let zi = mat[pivot_row * row_stride + n_words + w];
sum += Self::g_phase_word(xh, zh, xi, zi);
}
mat_phase[r] = ((sum % 4) + 4) % 4;
for w in 0..row_stride {
mat[r * row_stride + w] ^= mat[pivot_row * row_stride + w];
}
}
let q_word = if col < n { &qx } else { &qz };
if (q_word[word_col] & mask) != 0 {
let mut sum = q_phase + mat_phase[pivot_row];
for w in 0..n_words {
let xh = qx[w];
let zh = qz[w];
let xi = mat[pivot_row * row_stride + w];
let zi = mat[pivot_row * row_stride + n_words + w];
sum += Self::g_phase_word(xh, zh, xi, zi);
}
q_phase = ((sum % 4) + 4) % 4;
for w in 0..n_words {
qx[w] ^= mat[pivot_row * row_stride + w];
qz[w] ^= mat[pivot_row * row_stride + n_words + w];
}
for w in 0..combo_words {
q_combo[w] ^= mat[pivot_row * row_stride + 2 * n_words + w];
}
}
pivot_row += 1;
}
let in_group = qx.iter().chain(qz.iter()).all(|&w| w == 0);
if !in_group {
return Ok(0);
}
Ok(match q_phase {
0 => 1,
2 => -1,
_ => 0,
})
}
pub fn to_stim_format(&self) -> String {
let mut out = String::new();
for i in 0..self.num_qubits {
let stab = self.row_to_pauli_string(self.num_qubits + i);
out.push_str(&stab.to_string());
out.push('\n');
}
out
}
}
impl Clone for StabilizerState {
fn clone(&self) -> Self {
StabilizerState {
num_qubits: self.num_qubits,
qubit_map: self.qubit_map.clone(),
row_len: self.row_len,
tableau: self.tableau.clone(),
phases: self.phases.clone(),
rng: self.rng.clone(),
}
}
}
#[cfg(test)]
#[path = "./stabilizer_test.rs"]
mod stabilizer_test;