use crate::{
codes::LinearCode,
css::{Css, CssOperator, CssSyndrome},
noise::NoiseModel,
};
use pauli::{Pauli, PauliOperator};
use rand::Rng;
use serde::{Deserialize, Serialize};
use sparse_bin_mat::{SparseBinMat, SparseBinSlice};
mod logicals;
use logicals::from_linear_codes;
#[derive(Debug, PartialEq, Eq, Clone, Hash, Serialize, Deserialize)]
pub struct CssCode {
pub stabilizers: Css<SparseBinMat>,
pub logicals: Css<SparseBinMat>,
}
impl CssCode {
pub fn new(x_code: &LinearCode, z_code: &LinearCode) -> Self {
Self::try_new(x_code, z_code).expect("[Error]")
}
pub fn try_new(x_code: &LinearCode, z_code: &LinearCode) -> Result<Self, CssError> {
if x_code.len() != z_code.len() {
return Err(CssError::DifferentXandZLength(x_code.len(), z_code.len()));
} else if !(x_code.parity_check_matrix() * &z_code.parity_check_matrix().transposed())
.is_zero()
{
return Err(CssError::NonOrthogonalCodes);
}
Ok(Self {
stabilizers: Css {
x: x_code.parity_check_matrix().clone(),
z: z_code.parity_check_matrix().clone(),
},
logicals: from_linear_codes(x_code, z_code),
})
}
pub fn steane_code() -> Self {
let hamming_code = LinearCode::hamming_code();
Self::new(&hamming_code, &hamming_code)
}
pub fn shor_code() -> Self {
Self {
stabilizers: Css {
x: SparseBinMat::new(9, vec![vec![0, 1, 2, 3, 4, 5], vec![3, 4, 5, 6, 7, 8]]),
z: SparseBinMat::new(
9,
vec![
vec![0, 1],
vec![1, 2],
vec![3, 4],
vec![4, 5],
vec![6, 7],
vec![7, 8],
],
),
},
logicals: Css {
x: SparseBinMat::new(9, vec![vec![0, 1, 2]]),
z: SparseBinMat::new(9, vec![vec![0, 3, 6]]),
},
}
}
pub fn toric_code(distance: usize) -> Self {
let checks = (0..distance - 1)
.map(|c| vec![c, c + 1])
.chain(std::iter::once(vec![0, distance - 1]))
.collect();
let matrix = SparseBinMat::new(distance, checks);
let code = LinearCode::from_parity_check_matrix(matrix);
Self::hypergraph_product(&code, &code)
}
pub fn hypergraph_product(first_code: &LinearCode, second_code: &LinearCode) -> Self {
let x_checks = Self::hypergraph_product_x_checks(first_code, second_code);
let z_checks = Self::hypergraph_product_z_checks(first_code, second_code);
Self::new(
&LinearCode::from_parity_check_matrix(x_checks),
&LinearCode::from_parity_check_matrix(z_checks),
)
}
fn hypergraph_product_x_checks(
first_code: &LinearCode,
second_code: &LinearCode,
) -> SparseBinMat {
SparseBinMat::identity(first_code.len())
.kron_with(second_code.parity_check_matrix())
.horizontal_concat_with(
&first_code
.parity_check_matrix()
.transposed()
.kron_with(&SparseBinMat::identity(second_code.num_checks())),
)
}
fn hypergraph_product_z_checks(
first_code: &LinearCode,
second_code: &LinearCode,
) -> SparseBinMat {
first_code
.parity_check_matrix()
.kron_with(&SparseBinMat::identity(second_code.len()))
.horizontal_concat_with(
&SparseBinMat::identity(first_code.num_checks())
.kron_with(&second_code.parity_check_matrix().transposed()),
)
}
pub fn len(&self) -> usize {
self.stabilizers.x.number_of_columns()
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn num_x_stabs(&self) -> usize {
self.stabilizers.x.number_of_rows()
}
pub fn num_z_stabs(&self) -> usize {
self.stabilizers.z.number_of_rows()
}
pub fn num_x_logicals(&self) -> usize {
self.logicals.z.number_of_rows()
}
pub fn num_z_logicals(&self) -> usize {
self.logicals.z.number_of_rows()
}
pub fn syndrome_of(&self, operator: &PauliOperator) -> CssSyndrome {
self.stabilizers
.as_ref()
.pair(CssOperator::from(operator).swap_xz())
.map(|(stabs, operator)| *stabs * operator)
}
pub fn has_logical(&self, operator: &PauliOperator) -> bool {
self.syndrome_of(operator).is_trivial()
}
pub fn has_stabilizer(&self, operator: &PauliOperator) -> bool {
self.has_logical(operator)
&& self
.logicals()
.all(|logical| logical.commutes_with(operator))
}
pub fn x_stabs_binary(&self) -> &SparseBinMat {
&self.stabilizers.x
}
pub fn z_stabs_binary(&self) -> &SparseBinMat {
&self.stabilizers.z
}
pub fn x_logicals_binary(&self) -> &SparseBinMat {
&self.logicals.x
}
pub fn z_logicals_binary(&self) -> &SparseBinMat {
&self.logicals.z
}
pub fn stabilizers<'a>(&'a self) -> impl Iterator<Item = PauliOperator> + 'a {
self.stabilizers
.map_with_pauli(move |stabs, pauli| {
stabs
.rows()
.map(move |stab| Self::operator_from_vec(pauli, stab))
})
.combine_with(Iterator::chain)
}
pub fn logicals<'a>(&'a self) -> impl Iterator<Item = PauliOperator> + 'a {
self.logicals
.map_with_pauli(move |stabs, pauli| {
stabs
.rows()
.map(move |stab| Self::operator_from_vec(pauli, stab))
})
.combine_with(Iterator::chain)
}
fn operator_from_vec(pauli: Pauli, vector: SparseBinSlice) -> PauliOperator {
PauliOperator::new(
vector.len(),
vector.non_trivial_positions().collect(),
vec![pauli; vector.weight()],
)
}
pub fn random_error<N, R>(&self, noise_model: &N, rng: &mut R) -> PauliOperator
where
N: NoiseModel<Error = PauliOperator>,
R: Rng,
{
noise_model.sample_error_of_length(self.len(), rng)
}
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum CssError {
DifferentXandZLength(usize, usize),
NonOrthogonalCodes,
}
impl std::fmt::Display for CssError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::DifferentXandZLength(x_length, z_length) => {
write!(f, "different x and z lengths: {} & {}", x_length, z_length)
}
Self::NonOrthogonalCodes => write!(f, "codes are not orthogonal"),
}
}
}
impl std::error::Error for CssError {}