use crate::circuit::{Basis, Circuit, Instr, Resolved};
use crate::simulator::StabilizerSimulator;
pub struct M2d {
pub num_measurements: usize,
pub num_sweep_bits: usize,
pub num_detectors: usize,
pub num_observables: usize,
detectors: Vec<Vec<usize>>,
observables: Vec<Vec<usize>>,
ref_det: Vec<bool>,
ref_obs: u64,
sweep_det: Vec<Vec<u32>>,
sweep_obs: Vec<u64>,
}
fn reset(sim: &mut StabilizerSimulator, basis: Basis, q: usize) {
match basis {
Basis::Z => {
if sim.measure_z(q) == 1 {
sim.apply_x(q);
}
}
Basis::X => {
if sim.measure_x(q) == 1 {
sim.apply_z(q);
}
}
}
}
fn mix(k: u64) -> u64 {
let mut z = k.wrapping_add(0x9E37_79B9_7F4A_7C15);
z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
z ^ (z >> 31)
}
fn run(res: &Resolved, sweeps: &[bool], seed: u64) -> Vec<bool> {
let mut sim = StabilizerSimulator::with_seed(res.num_qubits.max(1), mix(seed));
let mut rec = Vec::with_capacity(res.num_measurements);
for ins in &res.instrs {
match ins {
Instr::Reset { basis, qubits } => {
for &q in qubits {
reset(&mut sim, *basis, q as usize);
}
}
Instr::H(qubits) => qubits.iter().for_each(|&q| sim.apply_h(q as usize)),
Instr::Cx(pairs) => pairs.iter().for_each(|&(c, t)| sim.apply_cnot(c as usize, t as usize)),
Instr::Cz(pairs) => {
for &(a, b) in pairs {
sim.apply_h(b as usize);
sim.apply_cnot(a as usize, b as usize);
sim.apply_h(b as usize);
}
}
Instr::Pauli { pauli, qubits } => {
for &q in qubits {
match pauli {
1 => sim.apply_x(q as usize),
2 => sim.apply_z(q as usize),
3 => sim.apply_y(q as usize),
_ => {}
}
}
}
Instr::SweepX(pairs) => {
for &(k, q) in pairs {
if sweeps[k as usize] {
sim.apply_x(q as usize);
}
}
}
Instr::Measure { basis, reset: then_reset, qubits, .. } => {
for &q in qubits {
let q = q as usize;
let bit = match basis {
Basis::Z => sim.measure_z(q),
Basis::X => sim.measure_x(q),
};
rec.push(bit == 1);
if *then_reset {
reset(&mut sim, *basis, q);
}
}
}
Instr::S(qubits) => qubits.iter().for_each(|&q| sim.apply_s(q as usize)),
Instr::Pad { values, .. } => rec.extend(values.iter().copied()),
Instr::Feedback { pauli, control, qubit } => {
let on = match control {
crate::circuit::Control::Rec(k) => rec[rec.len() - *k as usize],
crate::circuit::Control::Sweep(k) => sweeps.get(*k as usize).copied().unwrap_or(false),
};
if on {
match pauli {
1 => sim.apply_x(*qubit as usize),
2 => sim.apply_z(*qubit as usize),
_ => sim.apply_y(*qubit as usize),
}
}
}
Instr::Heralded { qubits, .. } => rec.extend(qubits.iter().map(|_| false)),
Instr::PauliError { .. }
| Instr::Depolarize1 { .. }
| Instr::Depolarize2 { .. }
| Instr::PauliChannel1 { .. }
| Instr::PauliChannel2 { .. }
| Instr::Correlated { .. }
| Instr::Detector { .. }
| Instr::Observable { .. }
| Instr::QubitCoords { .. }
| Instr::ShiftCoords(_)
| Instr::Tick
| Instr::Repeat { .. }
| Instr::Gate { .. } => {}
}
}
rec
}
fn parity(recs: &[usize], rec: &[bool]) -> bool {
recs.iter().fold(false, |acc, &m| acc ^ rec[m])
}
fn evaluate(detectors: &[Vec<usize>], observables: &[Vec<usize>], rec: &[bool]) -> (Vec<bool>, u64) {
let dets = detectors.iter().map(|r| parity(r, rec)).collect();
let mut obs = 0u64;
for (k, r) in observables.iter().enumerate() {
if parity(r, rec) {
obs |= 1 << k;
}
}
(dets, obs)
}
const MAX_QUBITS: usize = 1 << 14;
impl M2d {
pub fn new(circuit: &Circuit) -> Result<M2d, String> {
let res = circuit.resolve()?;
if res.num_qubits > MAX_QUBITS {
return Err(format!(
"{} qubits: the reference run keeps a dense tableau, which holds at most {MAX_QUBITS}",
res.num_qubits
));
}
let (detectors, observables) = (res.detectors.clone(), res.observables.clone());
let clear = vec![false; res.num_sweep_bits];
let (ref_det, ref_obs) = evaluate(&detectors, &observables, &run(&res, &clear, 1));
for seed in 2..=8 {
let (det, obs) = evaluate(&detectors, &observables, &run(&res, &clear, seed));
if let Some(d) = (0..ref_det.len()).find(|&d| ref_det[d] != det[d]) {
return Err(format!("detector D{d} is not deterministic"));
}
if ref_obs != obs {
return Err("an observable is not deterministic".into());
}
}
let mut sweep_det = Vec::with_capacity(res.num_sweep_bits);
let mut sweep_obs = Vec::with_capacity(res.num_sweep_bits);
for k in 0..res.num_sweep_bits {
let mut bits = clear.clone();
bits[k] = true;
let (det, obs) = evaluate(&detectors, &observables, &run(&res, &bits, 9 + k as u64));
sweep_det.push((0..det.len()).filter(|&d| det[d] != ref_det[d]).map(|d| d as u32).collect());
sweep_obs.push(obs ^ ref_obs);
}
Ok(M2d {
num_measurements: res.num_measurements,
num_sweep_bits: res.num_sweep_bits,
num_detectors: detectors.len(),
num_observables: observables.len(),
detectors,
observables,
ref_det,
ref_obs,
sweep_det,
sweep_obs,
})
}
pub fn convert(&self, meas: &[bool], sweeps: &[bool]) -> (Vec<bool>, u64) {
assert!(
meas.len() == self.num_measurements && sweeps.len() == self.num_sweep_bits,
"a shot of {} measurements and {} sweep bits, for a circuit of {} and {}",
meas.len(),
sweeps.len(),
self.num_measurements,
self.num_sweep_bits
);
let (mut det, mut obs) = evaluate(&self.detectors, &self.observables, meas);
for (d, &r) in det.iter_mut().zip(&self.ref_det) {
*d ^= r;
}
obs ^= self.ref_obs;
for (k, &set) in sweeps.iter().enumerate() {
if set {
for &d in &self.sweep_det[k] {
det[d as usize] ^= true;
}
obs ^= self.sweep_obs[k];
}
}
(det, obs)
}
pub fn convert_b8(&self, meas: &[u8], sweeps: &[u8], num_shots: usize) -> Result<(Vec<u8>, Vec<u8>), String> {
let (ms, ss) = (self.num_measurements.div_ceil(8), self.num_sweep_bits.div_ceil(8));
if ms.checked_mul(num_shots) != Some(meas.len()) || ss.checked_mul(num_shots) != Some(sweeps.len()) {
return Err(format!(
"{} + {} bytes is not {num_shots} shots of {} measurements and {} sweep bits",
meas.len(),
sweeps.len(),
self.num_measurements,
self.num_sweep_bits
));
}
let unpack = |row: &[u8], n: usize| -> Vec<bool> { (0..n).map(|i| (row[i / 8] >> (i % 8)) & 1 == 1).collect() };
let (mut dets, mut obs) = (Vec::new(), Vec::new());
for s in 0..num_shots {
let m = unpack(&meas[s * ms..(s + 1) * ms], self.num_measurements);
let w = unpack(&sweeps[s * ss..(s + 1) * ss], self.num_sweep_bits);
let (d, o) = self.convert(&m, &w);
crate::shots::pack_row(&d, &mut dets);
let ob: Vec<bool> = (0..self.num_observables).map(|k| (o >> k) & 1 == 1).collect();
crate::shots::pack_row(&ob, &mut obs);
}
Ok((dets, obs))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::circuit::Circuit;
#[test]
fn a_sweep_bit_moves_the_reference() {
let c = Circuit::parse(
"R 0 1\nCX sweep[0] 0\nX 1\nM 0 1\nDETECTOR rec[-2]\nDETECTOR rec[-1]\nOBSERVABLE_INCLUDE(0) rec[-1]\n",
)
.unwrap();
let m = M2d::new(&c).unwrap();
assert_eq!((m.num_measurements, m.num_sweep_bits, m.num_detectors), (2, 1, 2));
assert_eq!(m.convert(&[true, true], &[true]), (vec![false, false], 0));
assert_eq!(m.convert(&[true, true], &[false]), (vec![true, false], 0));
assert_eq!(m.convert(&[false, false], &[false]), (vec![false, true], 1));
}
#[test]
fn noiseless_runs_convert_to_silence() {
use crate::circuit::Basis;
use crate::memory::{generate, CodeKind, NoiseModel};
let text = generate(CodeKind::Rotated, 3, 3, NoiseModel::Sd6 { p: 0.0 }, Basis::Z).unwrap().to_stim();
let reset = "R 0 1 2 3 4 5 6 7 8\n";
let sweep: String = (0..9).map(|k| format!(" sweep[{k}] {k}")).collect();
let with = text.replacen(reset, &format!("{reset}CX{sweep}\n"), 1);
assert_ne!(with, text, "the data qubits' reset line moved");
let c = Circuit::parse(&with).unwrap();
let m = M2d::new(&c).unwrap();
assert_eq!(m.num_sweep_bits, 9);
let res = c.resolve().unwrap();
let mut rng = crate::surface_code::Xorshift::new(5);
let mut fired_before = 0;
for seed in 0..40u64 {
let sweeps: Vec<bool> = (0..9).map(|_| rng.next_u64() & 1 == 1).collect();
let meas = run(&res, &sweeps, 100 + seed);
let (dets, obs) = m.convert(&meas, &sweeps);
assert!(dets.iter().all(|&b| !b), "seed {seed}: {dets:?}");
assert_eq!(obs, 0);
fired_before += usize::from(m.convert(&meas, &[false; 9]).0.iter().any(|&b| b));
}
assert!(fired_before > 30, "{fired_before}");
}
#[test]
fn circuits_too_wide_for_the_tableau_are_refused() {
let err = M2d::new(&Circuit::parse("M 16384").unwrap()).err().unwrap_or_default();
assert!(err.contains("dense tableau"), "{err}");
}
#[test]
fn nondeterministic_detectors_are_refused() {
let c = Circuit::parse("RX 0\nM 0\nDETECTOR rec[-1]\n").unwrap();
assert!(M2d::new(&c).err().unwrap().contains("not deterministic"));
}
#[test]
fn b8_conversion_matches_the_bool_path() {
let c = Circuit::parse("R 0 1\nCX sweep[0] 0\nM 0 1\nDETECTOR rec[-2]\nDETECTOR rec[-1] rec[-2]\n").unwrap();
let m = M2d::new(&c).unwrap();
let (d, o) = m.convert_b8(&[0b01, 0b11], &[1, 0], 2).unwrap();
assert_eq!(d, vec![0b00, 0b01]);
assert_eq!(o, Vec::<u8>::new());
assert!(m.convert_b8(&[0], &[0], 2).is_err());
}
}