use super::{Backend, drive};
use crate::circuit::Circuit;
use crate::error::Error;
use crate::gate::{Gate1, Gate2};
use crate::rng::Rng;
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct PauliString {
x: Vec<bool>,
z: Vec<bool>,
neg: bool,
}
impl PauliString {
#[must_use]
pub fn num_qubits(&self) -> usize {
self.x.len()
}
#[must_use]
pub fn is_negative(&self) -> bool {
self.neg
}
#[must_use]
pub fn pauli(&self, q: usize) -> char {
match (self.x[q], self.z[q]) {
(false, false) => 'I',
(true, false) => 'X',
(false, true) => 'Z',
(true, true) => 'Y',
}
}
#[must_use]
pub fn expectation(&self, state: &crate::State) -> f64 {
assert_eq!(
state.num_qubits(),
self.num_qubits(),
"qubit count mismatch"
);
let applied = self.apply_to(state);
state.overlap(&applied).re
}
fn apply_to(&self, state: &crate::State) -> crate::State {
use crate::Complex64;
let n = self.num_qubits();
let dim = 1usize << n;
let src = state.amplitudes();
let mut out = vec![Complex64::ZERO; dim];
let y_count = (0..n).filter(|&q| self.x[q] && self.z[q]).count();
let phase = match y_count % 4 {
0 => Complex64::new(1.0, 0.0),
1 => Complex64::new(0.0, 1.0),
2 => Complex64::new(-1.0, 0.0),
_ => Complex64::new(0.0, -1.0),
};
let sign = if self.neg { -1.0 } else { 1.0 };
let factor = phase * sign;
for (j, &) in src.iter().enumerate() {
let mut target = j;
let mut zsign = 1.0;
for q in 0..n {
if self.x[q] {
target ^= 1 << q;
}
if self.z[q] && (j >> q) & 1 == 1 {
zsign = -zsign;
}
}
out[target] += amp * factor * zsign;
}
crate::State::from_amplitudes(out).expect("dim is a power of two")
}
}
impl std::fmt::Display for PauliString {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", if self.neg { '-' } else { '+' })?;
for q in 0..self.num_qubits() {
write!(f, "{}", self.pauli(q))?;
}
Ok(())
}
}
enum Prim {
H(usize),
S(usize),
Sdg(usize),
X(usize),
Y(usize),
Z(usize),
Cnot(usize, usize),
}
pub struct StabilizerBackend {
n: usize,
x: Vec<Vec<bool>>,
z: Vec<Vec<bool>>,
r: Vec<bool>,
rng: Rng,
error: Option<Error>,
}
#[derive(Clone, Debug)]
pub struct StabilizerExecution {
generators: Vec<PauliString>,
classical: Vec<bool>,
}
impl StabilizerExecution {
#[must_use]
pub fn generators(&self) -> &[PauliString] {
&self.generators
}
#[must_use]
pub fn classical(&self) -> &[bool] {
&self.classical
}
}
impl StabilizerBackend {
pub fn run(circuit: &Circuit) -> crate::Result<StabilizerExecution> {
Self::run_seeded(circuit, 0)
}
pub fn run_seeded(circuit: &Circuit, seed: u64) -> crate::Result<StabilizerExecution> {
circuit.validate()?;
let n = circuit.num_qubits();
let mut backend = Self::identity(n, seed);
let classical = drive(&mut backend, circuit.ops(), circuit.num_classical());
if let Some(err) = backend.error {
return Err(err);
}
Ok(StabilizerExecution {
generators: backend.stabilizer_generators(),
classical,
})
}
fn identity(n: usize, seed: u64) -> Self {
let rows = 2 * n + 1;
let mut x = vec![vec![false; n]; rows];
let mut z = vec![vec![false; n]; rows];
for i in 0..n {
x[i][i] = true; z[n + i][i] = true; }
Self {
n,
x,
z,
r: vec![false; rows],
rng: Rng::seed_from_u64(seed),
error: None,
}
}
fn prim_h(&mut self, q: usize) {
for i in 0..2 * self.n {
self.r[i] ^= self.x[i][q] && self.z[i][q];
let (xi, zi) = (self.x[i][q], self.z[i][q]);
self.x[i][q] = zi;
self.z[i][q] = xi;
}
}
fn prim_s(&mut self, q: usize) {
for i in 0..2 * self.n {
self.r[i] ^= self.x[i][q] && self.z[i][q];
self.z[i][q] ^= self.x[i][q];
}
}
fn prim_cnot(&mut self, a: usize, b: usize) {
for i in 0..2 * self.n {
self.r[i] ^= self.x[i][a] && self.z[i][b] && (self.x[i][b] ^ self.z[i][a] ^ true);
self.x[i][b] ^= self.x[i][a];
self.z[i][a] ^= self.z[i][b];
}
}
fn prim_x(&mut self, q: usize) {
for i in 0..2 * self.n {
self.r[i] ^= self.z[i][q];
}
}
fn prim_z(&mut self, q: usize) {
for i in 0..2 * self.n {
self.r[i] ^= self.x[i][q];
}
}
fn prim_y(&mut self, q: usize) {
for i in 0..2 * self.n {
self.r[i] ^= self.x[i][q] ^ self.z[i][q];
}
}
fn apply_prim(&mut self, p: &Prim) {
match *p {
Prim::H(q) => self.prim_h(q),
Prim::S(q) => self.prim_s(q),
Prim::Sdg(q) => {
self.prim_s(q);
self.prim_z(q);
}
Prim::X(q) => self.prim_x(q),
Prim::Y(q) => self.prim_y(q),
Prim::Z(q) => self.prim_z(q),
Prim::Cnot(a, b) => self.prim_cnot(a, b),
}
}
fn measure_qubit(&mut self, q: usize) -> bool {
let p = (self.n..2 * self.n).find(|&i| self.x[i][q]);
match p {
Some(p) => self.measure_random(q, p),
None => self.measure_determined(q),
}
}
fn measure_random(&mut self, q: usize, p: usize) -> bool {
let n = self.n;
for i in 0..2 * n {
if i != p && self.x[i][q] {
self.rowsum(i, p);
}
}
self.copy_row(p - n, p);
for j in 0..n {
self.x[p][j] = false;
self.z[p][j] = false;
}
let outcome = self.rng.next_u64() & 1 == 1;
self.r[p] = outcome;
self.z[p][q] = true;
outcome
}
fn measure_determined(&mut self, q: usize) -> bool {
let n = self.n;
let scratch = 2 * n;
for j in 0..n {
self.x[scratch][j] = false;
self.z[scratch][j] = false;
}
self.r[scratch] = false;
for i in 0..n {
if self.x[i][q] {
self.rowsum(scratch, n + i);
}
}
self.r[scratch]
}
fn rowsum(&mut self, h: usize, j: usize) {
let n = self.n;
let mut sum: i32 = 2 * i32::from(self.r[h]) + 2 * i32::from(self.r[j]);
for q in 0..n {
sum += Self::g(self.x[j][q], self.z[j][q], self.x[h][q], self.z[h][q]);
}
self.r[h] = sum.rem_euclid(4) == 2;
for q in 0..n {
self.x[h][q] ^= self.x[j][q];
self.z[h][q] ^= self.z[j][q];
}
}
#[allow(clippy::fn_params_excessive_bools)]
fn g(x1: bool, z1: bool, x2: bool, z2: bool) -> i32 {
match (x1, z1) {
(false, false) => 0,
(true, true) => i32::from(z2) - i32::from(x2), (true, false) => i32::from(z2) * (2 * i32::from(x2) - 1), (false, true) => i32::from(x2) * (1 - 2 * i32::from(z2)), }
}
fn copy_row(&mut self, dst: usize, src: usize) {
self.x[dst] = self.x[src].clone();
self.z[dst] = self.z[src].clone();
self.r[dst] = self.r[src];
}
fn stabilizer_generators(&self) -> Vec<PauliString> {
(self.n..2 * self.n)
.map(|i| PauliString {
x: self.x[i].clone(),
z: self.z[i].clone(),
neg: self.r[i],
})
.collect()
}
fn poison(&mut self, gate: &'static str) {
if self.error.is_none() {
self.error = Some(Error::NonClifford { gate });
}
}
}
impl Backend for StabilizerBackend {
fn apply_1q(&mut self, gate: &Gate1, target: usize) {
if self.error.is_some() {
return;
}
match clifford1(gate) {
Some(prims) => {
for p in &prims {
self.apply_prim(&remap1(p, target));
}
}
None => self.poison("non-Clifford 1-qubit gate"),
}
}
fn apply_2q(&mut self, gate: &Gate2, a: usize, b: usize) {
if self.error.is_some() {
return;
}
match clifford2(gate, a, b) {
Some(prims) => {
for p in &prims {
self.apply_prim(p);
}
}
None => self.poison("non-Clifford 2-qubit gate"),
}
}
fn apply_controlled(&mut self, controls: &[usize], gate: &Gate1, target: usize) {
if self.error.is_some() {
return;
}
if controls.len() == 1 {
if let Some(prims) = controlled_clifford(gate, controls[0], target) {
for p in &prims {
self.apply_prim(p);
}
return;
}
}
self.poison("non-Clifford controlled gate");
}
fn measure(&mut self, qubit: usize) -> bool {
if self.error.is_some() {
return false;
}
self.measure_qubit(qubit)
}
}
const EPS: f64 = 1e-9;
fn remap1(p: &Prim, target: usize) -> Prim {
match *p {
Prim::H(_) => Prim::H(target),
Prim::S(_) => Prim::S(target),
Prim::Sdg(_) => Prim::Sdg(target),
Prim::X(_) => Prim::X(target),
Prim::Y(_) => Prim::Y(target),
Prim::Z(_) => Prim::Z(target),
Prim::Cnot(_, _) => unreachable!("no 2-qubit prim in a 1-qubit decomposition"),
}
}
fn clifford1(g: &Gate1) -> Option<Vec<Prim>> {
if matrix_eq_phase(g, &Gate1::id()) {
Some(vec![])
} else if matrix_eq_phase(g, &Gate1::h()) {
Some(vec![Prim::H(0)])
} else if matrix_eq_phase(g, &Gate1::s()) {
Some(vec![Prim::S(0)])
} else if matrix_eq_phase(g, &Gate1::s().adjoint()) {
Some(vec![Prim::Sdg(0)])
} else if matrix_eq_phase(g, &Gate1::x()) {
Some(vec![Prim::X(0)])
} else if matrix_eq_phase(g, &Gate1::y()) {
Some(vec![Prim::Y(0)])
} else if matrix_eq_phase(g, &Gate1::z()) {
Some(vec![Prim::Z(0)])
} else {
None
}
}
fn clifford2(g: &Gate2, a: usize, b: usize) -> Option<Vec<Prim>> {
if matrix2_eq_phase(g, &Gate2::cnot()) {
Some(vec![Prim::Cnot(a, b)])
} else if matrix2_eq_phase(g, &Gate2::cz()) {
Some(vec![Prim::H(b), Prim::Cnot(a, b), Prim::H(b)])
} else if matrix2_eq_phase(g, &Gate2::swap()) {
Some(vec![Prim::Cnot(a, b), Prim::Cnot(b, a), Prim::Cnot(a, b)])
} else {
None
}
}
fn controlled_clifford(g: &Gate1, control: usize, target: usize) -> Option<Vec<Prim>> {
if matrix_eq_phase(g, &Gate1::x()) {
Some(vec![Prim::Cnot(control, target)])
} else if matrix_eq_phase(g, &Gate1::z()) {
Some(vec![
Prim::H(target),
Prim::Cnot(control, target),
Prim::H(target),
])
} else if matrix_eq_phase(g, &Gate1::id()) {
Some(vec![])
} else {
None
}
}
fn matrix_eq_phase(a: &Gate1, b: &Gate1) -> bool {
phase_aligned(&a.m, &b.m)
}
fn matrix2_eq_phase(a: &Gate2, b: &Gate2) -> bool {
phase_aligned(&a.m, &b.m)
}
fn phase_aligned(a: &[crate::Complex64], b: &[crate::Complex64]) -> bool {
debug_assert_eq!(a.len(), b.len());
let pivot = b.iter().position(|z| z.norm() > EPS);
let Some(p) = pivot else {
return a.iter().all(|z| z.norm() <= EPS);
};
if a[p].norm() <= EPS {
return false;
}
let phase = a[p] * b[p].conj() / b[p].norm_sqr();
if (phase.norm() - 1.0).abs() > EPS {
return false;
}
a.iter()
.zip(b.iter())
.all(|(za, zb)| (*za - phase * *zb).norm() <= EPS)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn ghz_has_n_generators() {
let mut c = Circuit::new(4);
c.h(0).cnot(0, 1).cnot(1, 2).cnot(2, 3);
let exec = StabilizerBackend::run(&c).unwrap();
assert_eq!(exec.generators().len(), 4);
}
#[test]
fn t_gate_is_rejected() {
let mut c = Circuit::new(1);
c.t(0);
assert!(matches!(
StabilizerBackend::run(&c),
Err(Error::NonClifford { .. })
));
}
#[test]
fn generic_rotation_is_rejected() {
let mut c = Circuit::new(1);
c.rx(0, 0.3);
assert!(matches!(
StabilizerBackend::run(&c),
Err(Error::NonClifford { .. })
));
}
#[test]
fn rz_half_pi_is_recognized_as_s() {
let mut c = Circuit::new(1);
c.rz(0, std::f64::consts::FRAC_PI_2);
assert!(StabilizerBackend::run(&c).is_ok());
}
#[test]
fn bell_measurement_is_correlated() {
for seed in 0..32 {
let mut c = Circuit::with_classical(2, 2);
c.h(0).cnot(0, 1).measure(0, 0).measure(1, 1);
let exec = StabilizerBackend::run_seeded(&c, seed).unwrap();
assert_eq!(exec.classical()[0], exec.classical()[1]);
}
}
#[test]
fn deterministic_measurement_of_zero() {
let mut c = Circuit::with_classical(1, 1);
c.measure(0, 0);
let exec = StabilizerBackend::run(&c).unwrap();
assert!(!exec.classical()[0]);
}
#[test]
fn large_ghz_is_feasible() {
let n = 200;
let mut c = Circuit::new(n);
c.h(0);
for k in 0..n - 1 {
c.cnot(k, k + 1);
}
let exec = StabilizerBackend::run(&c).unwrap();
assert_eq!(exec.generators().len(), n);
}
}