use std::fmt::Display;
use crate::utils::{set_row_to_row_sum, set_vec_to_row_sum, swap_columns};
use ndarray::{s, Array, Array1, Array2};
use serde::{Deserialize, Serialize};
#[cfg(feature = "python")]
use pyo3::{prelude::*, PyObjectProtocol};
#[cfg(feature = "python")]
use std::io::{Error, ErrorKind};
#[cfg_attr(feature = "python", pyclass(name = "Tableau"))]
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Tableau {
n_qubits: usize,
table: Array2<bool>,
}
impl Display for Tableau {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"Stabilizer tableau on {} qubits:\n{:?}",
self.n_qubits, self.table
)
}
}
impl Tableau {
const fn idx_phase(&self) -> usize {
2 * self.n_qubits
}
pub fn new(n_qubits: usize) -> Self {
Tableau {
n_qubits,
table: Array::from_shape_fn((2 * n_qubits, 2 * n_qubits + 1), |(i, j)| i == j),
}
}
fn idx_x(&self, idx_col: usize) -> usize {
idx_col
}
fn idx_z(&self, idx_col: usize) -> usize {
idx_col + self.n_qubits
}
fn determinstic_result(&self, idx_target: usize) -> Option<bool> {
let determined = !self
.table
.slice(s![self.n_qubits.., idx_target])
.iter()
.any(|b| *b);
if determined {
let mut vector: Array1<bool> = Array::default(2 * self.n_qubits + 1);
for idx_destabilizer in 0..self.n_qubits {
if self.table[(idx_destabilizer, self.idx_x(idx_target))] {
set_vec_to_row_sum(&mut vector, &self.table, idx_destabilizer + self.n_qubits);
}
}
Some(vector[2 * self.n_qubits])
} else {
None
}
}
}
impl Tableau {
pub fn assert_meas(&self, idx_target: usize, expected: bool) -> Result<(), String> {
let actual = self.determinstic_result(idx_target).ok_or(format!(
"Expected {}, but measurement result would be random.",
expected
))?;
if actual != expected {
Err(format!(
"Expected {}, but measurement result would actually be {}.",
expected, actual
))
} else {
Ok(())
}
}
}
#[cfg_attr(feature = "python", pymethods)]
impl Tableau {
#[cfg(feature = "python")]
pub fn as_json(&self) -> PyResult<String> {
serde_json::to_string(self)
.map_err(|e| PyErr::from(Error::new(ErrorKind::Other, e.to_string())))
}
pub fn apply_h_mut(&mut self, idx_target: usize) {
let idxs = (self.idx_x(idx_target), self.idx_z(idx_target));
swap_columns(&mut self.table, idxs);
let idx_phase = self.idx_phase();
for idx_row in 0..2 * self.n_qubits {
let a = self.table[(idx_row, self.idx_x(idx_target))];
let b = self.table[(idx_row, self.idx_z(idx_target))];
self.table[(idx_row, idx_phase)] ^= a && b;
}
}
pub fn apply_s_mut(&mut self, idx_target: usize) {
let idx_phase = self.idx_phase();
for idx_row in 0..2 * self.n_qubits {
self.table[(idx_row, idx_phase)] ^= self.table[(idx_row, self.idx_x(idx_target))]
&& self.table[(idx_row, self.idx_z(idx_target))];
}
for idx_row in 0..2 * self.n_qubits {
let idx_x_target = self.idx_x(idx_target);
let idx_z_target = self.idx_z(idx_target);
self.table[(idx_row, idx_z_target)] ^= self.table[(idx_row, idx_x_target)];
}
}
pub fn apply_cnot_mut(&mut self, idx_control: usize, idx_target: usize) {
let idx_phase = self.idx_phase();
for idx_row in 0..2 * self.n_qubits {
self.table[(idx_row, idx_phase)] ^= self.table[(idx_row, self.idx_x(idx_control))]
&& self.table[(idx_row, self.idx_z(idx_target))]
&& (self.table[(idx_row, self.idx_x(idx_target))]
^ self.table[(idx_row, self.idx_z(idx_control))]
^ true);
}
for idx_row in 0..2 * self.n_qubits {
let idx_x_target = self.idx_x(idx_target);
let idx_x_control = self.idx_x(idx_control);
self.table[(idx_row, idx_x_target)] ^= self.table[(idx_row, idx_x_control)];
}
for idx_row in 0..2 * self.n_qubits {
let idx_z_target = self.idx_z(idx_target);
let idx_z_control = self.idx_z(idx_control);
self.table[(idx_row, idx_z_control)] ^= self.table[(idx_row, idx_z_target)];
}
}
pub fn apply_x_mut(&mut self, idx_target: usize) {
self.apply_h_mut(idx_target);
self.apply_z_mut(idx_target);
self.apply_h_mut(idx_target);
}
pub fn apply_s_adj_mut(&mut self, idx_target: usize) {
self.apply_s_mut(idx_target);
self.apply_s_mut(idx_target);
self.apply_s_mut(idx_target);
}
pub fn apply_y_mut(&mut self, idx_target: usize) {
self.apply_s_adj_mut(idx_target);
self.apply_x_mut(idx_target);
self.apply_s_mut(idx_target);
}
pub fn apply_z_mut(&mut self, idx_target: usize) {
self.apply_s_mut(idx_target);
self.apply_s_mut(idx_target);
}
pub fn apply_swap_mut(&mut self, idx_1: usize, idx_2: usize) {
self.apply_cnot_mut(idx_1, idx_2);
self.apply_cnot_mut(idx_2, idx_1);
self.apply_cnot_mut(idx_1, idx_2);
}
pub fn meas_mut(&mut self, idx_target: usize) -> bool {
if let Some(result) = self.determinstic_result(idx_target) {
return result;
}
let idx_phase = self.idx_phase();
let result = rand::random();
let collisions: Vec<usize> = self
.table
.slice(s![.., self.idx_x(idx_target)])
.indexed_iter()
.filter(|(_i, b)| **b)
.map(|(i, _b)| i)
.collect();
let idx_first: usize = self.n_qubits
+ self
.table
.slice(s![self.n_qubits.., self.idx_x(idx_target)])
.indexed_iter()
.find(|(_i, b)| **b)
.unwrap()
.0;
let old_stab = self.table.slice(s![idx_first, ..]).to_owned();
for idx_collision in collisions.iter() {
if *idx_collision != idx_first {
set_row_to_row_sum(&mut self.table, *idx_collision, idx_first);
}
}
self.table
.slice_mut(s![idx_first - self.n_qubits, ..])
.assign(&old_stab);
self.table.slice_mut(s![idx_first, ..]).fill(false);
let idx_z_target = self.idx_z(idx_target);
self.table[(idx_first, idx_z_target)] = true;
self.table[(idx_first, idx_phase)] = result;
result
}
}
#[cfg(feature = "python")]
#[pymethods]
impl Tableau {
#[new]
pub fn new_py(n_qubits: usize) -> Self {
Self::new(n_qubits)
}
}
#[cfg(feature = "python")]
#[pyproto]
impl PyObjectProtocol for Tableau {
fn __repr__(&self) -> String {
format!("<{:?}>", self)
}
fn __str__(&self) -> String {
format!("{}", self)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn bell_pair_meas_agree() {
let mut t = Tableau::new(2);
t.apply_h_mut(0);
t.apply_cnot_mut(0, 1);
let left = t.meas_mut(0);
let right = t.meas_mut(1);
assert_eq!(left, right)
}
}