qec-code 0.3.0

Rust primitives for constructing and analyzing quantum error-correcting codes
Documentation
use crate::binary_chain_complex::{BinaryBoundaryMap, BinaryChainComplex};
use crate::error::{QecError, Result};
use crate::sparse_gf2::SparseGf2Matrix;

#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct Toric3dSpec {
    pub lx: usize,
    pub ly: usize,
    pub lz: usize,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Toric3dCssChecks {
    pub num_cols: usize,
    pub hx: Vec<Vec<usize>>,
    pub hz: Vec<Vec<usize>>,
    pub distances: Toric3dDistances,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Toric3dDistances {
    pub d_x: usize,
    pub d_z: usize,
    pub distance: usize,
}

#[derive(Debug, Clone, Copy)]
struct Toric3dDimensions {
    spec: Toric3dSpec,
    volume: usize,
    num_edges: usize,
    num_plaquettes: usize,
}

impl Toric3dDimensions {
    fn new(spec: Toric3dSpec) -> Result<Self> {
        validate_period("lx", spec.lx)?;
        validate_period("ly", spec.ly)?;
        validate_period("lz", spec.lz)?;
        let xy = checked_mul(spec.lx, spec.ly)?;
        let volume = checked_mul(xy, spec.lz)?;
        let num_edges = checked_mul(3, volume)?;
        let num_plaquettes = checked_mul(3, volume)?;
        Ok(Self {
            spec,
            volume,
            num_edges,
            num_plaquettes,
        })
    }

    fn cell(&self, x: usize, y: usize, z: usize) -> Result<usize> {
        let xy = checked_add(checked_mul(x, self.spec.ly)?, y)?;
        checked_add(checked_mul(xy, self.spec.lz)?, z)
    }

    fn x_edge(&self, x: usize, y: usize, z: usize) -> Result<usize> {
        self.cell(x, y, z)
    }

    fn y_edge(&self, x: usize, y: usize, z: usize) -> Result<usize> {
        checked_add(self.volume, self.cell(x, y, z)?)
    }

    fn z_edge(&self, x: usize, y: usize, z: usize) -> Result<usize> {
        checked_add(checked_mul(2, self.volume)?, self.cell(x, y, z)?)
    }
}

pub fn toric_3d_chain_complex(spec: Toric3dSpec) -> Result<BinaryChainComplex> {
    let dims = Toric3dDimensions::new(spec)?;
    let vertex_rows = vertex_edge_rows(&dims)?;
    let b1_matrix = SparseGf2Matrix::new(dims.volume, dims.num_edges, vertex_rows)?;
    let boundary_1 = BinaryBoundaryMap::new(1, 0, b1_matrix)?;
    let edge_rows = edge_plaquette_rows(&dims)?;
    let b2_matrix = SparseGf2Matrix::new(dims.num_edges, dims.num_plaquettes, edge_rows)?;
    let boundary_2 = BinaryBoundaryMap::new(2, 1, b2_matrix)?;
    BinaryChainComplex::new(vec![boundary_1, boundary_2])
}

pub fn toric_3d_css_checks(spec: Toric3dSpec) -> Result<Toric3dCssChecks> {
    let dims = Toric3dDimensions::new(spec)?;
    let complex = toric_3d_chain_complex(spec)?;
    let css = complex.css_view(1)?;
    Ok(Toric3dCssChecks {
        num_cols: css.num_qubits(),
        hx: css.hx().rows().to_vec(),
        hz: css.hz().rows().to_vec(),
        distances: analytic_distances(&dims)?,
    })
}

fn validate_period(parameter: &str, value: usize) -> Result<()> {
    if value < 3 {
        return Err(QecError::OutOfRangeBuiltInCssIntegerParameter {
            family: "toric_3d".to_owned(),
            parameter: parameter.to_owned(),
            value,
        });
    }
    Ok(())
}

fn checked_mul(left: usize, right: usize) -> Result<usize> {
    left.checked_mul(right)
        .ok_or_else(toric_3d_dimension_overflow)
}

fn checked_add(left: usize, right: usize) -> Result<usize> {
    left.checked_add(right)
        .ok_or_else(toric_3d_dimension_overflow)
}

fn toric_3d_dimension_overflow() -> QecError {
    QecError::SparseGf2DimensionOverflow {
        operation: "toric_3d",
    }
}

fn row_buffer(capacity: usize) -> Result<Vec<Vec<usize>>> {
    let mut rows = Vec::new();
    rows.try_reserve_exact(capacity)
        .map_err(|_| toric_3d_dimension_overflow())?;
    Ok(rows)
}

fn empty_rows(len: usize) -> Result<Vec<Vec<usize>>> {
    let mut rows = row_buffer(len)?;
    rows.resize_with(len, Vec::new);
    Ok(rows)
}

fn vertex_edge_rows(dims: &Toric3dDimensions) -> Result<Vec<Vec<usize>>> {
    let mut rows = row_buffer(dims.volume)?;
    for x in 0..dims.spec.lx {
        let previous_x = previous_coordinate(x, dims.spec.lx);
        for y in 0..dims.spec.ly {
            let previous_y = previous_coordinate(y, dims.spec.ly);
            for z in 0..dims.spec.lz {
                let previous_z = previous_coordinate(z, dims.spec.lz);
                rows.push(vec![
                    dims.x_edge(x, y, z)?,
                    dims.x_edge(previous_x, y, z)?,
                    dims.y_edge(x, y, z)?,
                    dims.y_edge(x, previous_y, z)?,
                    dims.z_edge(x, y, z)?,
                    dims.z_edge(x, y, previous_z)?,
                ]);
            }
        }
    }
    Ok(rows)
}

fn edge_plaquette_rows(dims: &Toric3dDimensions) -> Result<Vec<Vec<usize>>> {
    let mut rows = empty_rows(dims.num_edges)?;
    let mut plaquette = 0;

    for x in 0..dims.spec.lx {
        let next_x = next_coordinate(x, dims.spec.lx);
        for y in 0..dims.spec.ly {
            let next_y = next_coordinate(y, dims.spec.ly);
            for z in 0..dims.spec.lz {
                for edge in [
                    dims.x_edge(x, y, z)?,
                    dims.x_edge(x, next_y, z)?,
                    dims.y_edge(x, y, z)?,
                    dims.y_edge(next_x, y, z)?,
                ] {
                    rows[edge].push(plaquette);
                }
                plaquette = checked_add(plaquette, 1)?;
            }
        }
    }

    for x in 0..dims.spec.lx {
        let next_x = next_coordinate(x, dims.spec.lx);
        for y in 0..dims.spec.ly {
            for z in 0..dims.spec.lz {
                let next_z = next_coordinate(z, dims.spec.lz);
                for edge in [
                    dims.x_edge(x, y, z)?,
                    dims.x_edge(x, y, next_z)?,
                    dims.z_edge(x, y, z)?,
                    dims.z_edge(next_x, y, z)?,
                ] {
                    rows[edge].push(plaquette);
                }
                plaquette = checked_add(plaquette, 1)?;
            }
        }
    }

    for x in 0..dims.spec.lx {
        for y in 0..dims.spec.ly {
            let next_y = next_coordinate(y, dims.spec.ly);
            for z in 0..dims.spec.lz {
                let next_z = next_coordinate(z, dims.spec.lz);
                for edge in [
                    dims.y_edge(x, y, z)?,
                    dims.y_edge(x, y, next_z)?,
                    dims.z_edge(x, y, z)?,
                    dims.z_edge(x, next_y, z)?,
                ] {
                    rows[edge].push(plaquette);
                }
                plaquette = checked_add(plaquette, 1)?;
            }
        }
    }

    Ok(rows)
}

fn previous_coordinate(coordinate: usize, period: usize) -> usize {
    if coordinate == 0 {
        period - 1
    } else {
        coordinate - 1
    }
}

fn next_coordinate(coordinate: usize, period: usize) -> usize {
    if coordinate == period - 1 {
        0
    } else {
        coordinate + 1
    }
}

fn analytic_distances(dims: &Toric3dDimensions) -> Result<Toric3dDistances> {
    let d_x = checked_mul(dims.spec.lx, dims.spec.ly)?
        .min(checked_mul(dims.spec.lx, dims.spec.lz)?)
        .min(checked_mul(dims.spec.ly, dims.spec.lz)?);
    let d_z = dims.spec.lx.min(dims.spec.ly).min(dims.spec.lz);
    Ok(Toric3dDistances {
        d_x,
        d_z,
        distance: d_x.min(d_z),
    })
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn corrupt_boundary_composition_is_rejected() {
        let dims = Toric3dDimensions::new(Toric3dSpec {
            lx: 3,
            ly: 3,
            lz: 3,
        })
        .unwrap();
        let boundary_1 = BinaryBoundaryMap::new(
            1,
            0,
            SparseGf2Matrix::new(
                dims.volume,
                dims.num_edges,
                vertex_edge_rows(&dims).unwrap(),
            )
            .unwrap(),
        )
        .unwrap();
        let mut rows = edge_plaquette_rows(&dims).unwrap();
        rows[0].remove(0);
        let boundary_2 = BinaryBoundaryMap::new(
            2,
            1,
            SparseGf2Matrix::new(dims.num_edges, dims.num_plaquettes, rows).unwrap(),
        )
        .unwrap();

        assert!(matches!(
            BinaryChainComplex::new(vec![boundary_1, boundary_2]),
            Err(QecError::NonzeroBoundaryComposition {
                lower_dimension: 1,
                upper_dimension: 2,
                ..
            })
        ));
    }
}