use std::fmt;
use num_complex::Complex64;
use crate::pauli_string::PauliString;
use crate::pauli_sum::PauliSum;
use crate::phase::Phase;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum StabilizerError {
GeneratorCount {
expected: usize,
found: usize,
},
QubitOutOfRange {
generator: usize,
num_qubits: usize,
},
NotCommuting {
first: usize,
second: usize,
},
Dependent {
generator: usize,
},
}
impl fmt::Display for StabilizerError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::GeneratorCount { expected, found } => write!(
f,
"a stabilizer state on {expected} qubits needs exactly {expected} generators, \
got {found}",
),
Self::QubitOutOfRange { generator, num_qubits } => {
write!(f, "generator {generator} acts on a qubit at or beyond index {num_qubits}",)
}
Self::NotCommuting { first, second } => write!(
f,
"generators {first} and {second} anticommute; stabilizer generators must \
pairwise commute",
),
Self::Dependent { generator } => write!(
f,
"generator {generator} is a product of the others (or the generators imply \
-I ∈ S); stabilizer generators must be independent over GF(2)",
),
}
}
}
impl std::error::Error for StabilizerError {}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
struct Column {
word: usize,
mask: u64,
is_z: bool,
}
impl Column {
fn is_set<const W: usize>(self, p: &PauliString<W>) -> bool {
let half = if self.is_z { &p.z } else { &p.x };
half[self.word] & self.mask != 0
}
}
fn columns(num_qubits: usize) -> Vec<Column> {
let mut columns = Vec::with_capacity(2 * num_qubits);
for is_z in [false, true] {
for q in 0..num_qubits {
columns.push(Column { word: q / 64, mask: 1u64 << (q % 64), is_z });
}
}
columns
}
#[derive(Clone, Copy, Debug)]
struct Row<const W: usize> {
key: PauliString<W>,
negative: bool,
pivot: Column,
}
#[derive(Clone, Debug)]
pub struct StabilizerState<const W: usize> {
num_qubits: usize,
rows: Vec<Row<W>>,
}
impl<const W: usize> StabilizerState<W> {
pub fn from_generators(
num_qubits: usize,
generators: &[(PauliString<W>, bool)],
) -> Result<Self, StabilizerError> {
assert!(
num_qubits <= 64 * W,
"StabilizerState::from_generators: num_qubits {num_qubits} exceeds the {W}-word width",
);
if generators.len() != num_qubits {
return Err(StabilizerError::GeneratorCount {
expected: num_qubits,
found: generators.len(),
});
}
for (i, (key, _)) in generators.iter().enumerate() {
if !key.is_within(num_qubits) {
return Err(StabilizerError::QubitOutOfRange { generator: i, num_qubits });
}
}
for i in 0..generators.len() {
for j in (i + 1)..generators.len() {
if !generators[i].0.commutes_with(&generators[j].0) {
return Err(StabilizerError::NotCommuting { first: i, second: j });
}
}
}
let mut work: Vec<(PauliString<W>, bool, usize)> = generators
.iter()
.enumerate()
.map(|(i, (key, negative))| (*key, *negative, i))
.collect();
let mut rows: Vec<Row<W>> = Vec::with_capacity(num_qubits);
for column in columns(num_qubits) {
let rank = rows.len();
let Some(p) = (rank..work.len()).find(|&r| column.is_set(&work[r].0)) else {
continue;
};
work.swap(rank, p);
let (key, negative, _) = work[rank];
for (r, row) in work.iter_mut().enumerate() {
if r == rank || !column.is_set(&row.0) {
continue;
}
let phase = row.0.mul_assign(&key);
assert_eq!(
phase.exponent() & 1,
0,
"a product of commuting Hermitian Paulis came out imaginary"
);
row.1 ^= negative ^ (phase == Phase::MINUS_ONE);
}
rows.push(Row { key, negative, pivot: column });
if rows.len() == num_qubits {
break;
}
}
if rows.len() < num_qubits {
return Err(StabilizerError::Dependent { generator: work[rows.len()].2 });
}
Ok(Self { num_qubits, rows })
}
pub fn num_qubits(&self) -> usize {
self.num_qubits
}
pub(crate) fn sign_of(&self, key: &PauliString<W>) -> Option<bool> {
let mut reduced = *key;
let mut phase = Phase::ONE;
let mut negative = false;
for row in &self.rows {
if row.pivot.is_set(&reduced) {
phase += reduced.mul_assign(&row.key);
negative ^= row.negative;
}
}
if reduced != PauliString::<W>::identity() {
return None;
}
debug_assert_eq!(
phase.exponent() & 1,
0,
"stabilizer membership produced an imaginary phase i^{}",
phase.exponent(),
);
Some(negative ^ (phase == Phase::MINUS_ONE))
}
}
impl<const W: usize> PauliSum<W> {
pub fn expectation_stabilizer(&self, state: &StabilizerState<W>) -> Complex64 {
assert_eq!(
self.num_qubits(),
state.num_qubits(),
"PauliSum::expectation_stabilizer: num_qubits mismatch ({} vs {})",
self.num_qubits(),
state.num_qubits(),
);
self.sum_over_terms(|x, z, coeff| match state.sign_of(&PauliString::<W> { x: *x, z: *z }) {
None => Complex64::new(0.0, 0.0),
Some(false) => coeff,
Some(true) => -coeff,
})
}
}
#[cfg(test)]
mod tests;