qec-code 0.3.0

Rust primitives for constructing and analyzing quantum error-correcting codes
Documentation
use crate::error::{QecError, Result};
use crate::gf2::BinaryRow;
use crate::{Pauli, StabilizerCode};

#[derive(Debug, Clone)]
pub struct LoweredDistanceProblem {
    pub model: qec_ilp_core::BinaryIlpModel,
    pub stabilizer_var_count: usize,
    pub logical_var_count: usize,
    pub symplectic_var_offset: usize,
    pub qubit_activity_offset: usize,
    pub nonzero_logical_constraint_row: Option<qec_ilp_core::LinearConstraint>,
}

pub fn lower_distance_problem(code: &StabilizerCode) -> Result<LoweredDistanceProblem> {
    if code.num_logical_qubits() == 0 {
        return Err(QecError::DistanceWitnessNotFound);
    }

    let stabilizer_rows = code.stabilizer_rows();
    let basis = code.canonical_logical_basis()?;
    let logical_rows = basis
        .logical_x
        .iter()
        .chain(&basis.logical_z)
        .map(Pauli::to_symplectic_row)
        .collect::<Vec<_>>();
    let width = code
        .n()
        .checked_mul(2)
        .ok_or(QecError::UnsupportedExhaustiveEnumeration { n: code.n() })?;
    let stabilizer_var_count = stabilizer_rows.len();
    let logical_var_count = logical_rows.len();
    let symplectic_var_offset = stabilizer_var_count + logical_var_count;
    let qubit_activity_offset = symplectic_var_offset + width;

    let mut binary_vars = Vec::new();
    for i in 0..stabilizer_var_count {
        binary_vars.push(qec_ilp_core::ModelVar {
            name: format!("lambda_{i}"),
            objective: 0.0,
            lower: 0.0,
            upper: 1.0,
        });
    }
    for i in 0..logical_var_count {
        binary_vars.push(qec_ilp_core::ModelVar {
            name: format!("logical_{i}"),
            objective: 0.0,
            lower: 0.0,
            upper: 1.0,
        });
    }
    for c in 0..width {
        binary_vars.push(qec_ilp_core::ModelVar {
            name: format!("p_{c}"),
            objective: 0.0,
            lower: 0.0,
            upper: 1.0,
        });
    }
    for q in 0..code.n() {
        binary_vars.push(qec_ilp_core::ModelVar {
            name: format!("y_{q}"),
            objective: 1.0,
            lower: 0.0,
            upper: 1.0,
        });
    }

    let integer_vars = (0..width)
        .map(|c| qec_ilp_core::ModelVar {
            name: format!("t_{c}"),
            objective: 0.0,
            lower: 0.0,
            upper: f64::INFINITY,
        })
        .collect::<Vec<_>>();

    let mut constraints = (0..width)
        .map(|c| coordinate_parity_row(c, &stabilizer_rows, &logical_rows, symplectic_var_offset))
        .collect::<Vec<_>>();

    let logical_terms = (stabilizer_var_count..(stabilizer_var_count + logical_var_count))
        .map(|index| (index, 1.0))
        .collect::<Vec<_>>();
    let nonzero_logical_constraint_row = qec_ilp_core::LinearConstraint {
        name: "logical_nonzero".into(),
        sense: qec_ilp_core::ConstraintSense::Ge,
        binary_terms: logical_terms,
        integer_terms: vec![],
        rhs: 1.0,
    };
    constraints.push(nonzero_logical_constraint_row.clone());

    for qubit in 0..code.n() {
        constraints.extend(weight_rows_for_qubit(
            qubit,
            symplectic_var_offset,
            qubit_activity_offset,
            code.n(),
        ));
    }

    Ok(LoweredDistanceProblem {
        model: qec_ilp_core::BinaryIlpModel {
            binary_vars,
            integer_vars,
            constraints,
            solution_binary_prefix_len: qubit_activity_offset + code.n(),
        },
        stabilizer_var_count,
        logical_var_count,
        symplectic_var_offset,
        qubit_activity_offset,
        nonzero_logical_constraint_row: Some(nonzero_logical_constraint_row),
    })
}

fn coordinate_parity_row(
    coord: usize,
    stabilizers: &[BinaryRow],
    logicals: &[BinaryRow],
    symplectic_var_offset: usize,
) -> qec_ilp_core::LinearConstraint {
    let mut binary_terms = Vec::new();
    for (index, row) in stabilizers.iter().enumerate() {
        if row[coord] == 1 {
            binary_terms.push((index, 1.0));
        }
    }
    for (index, row) in logicals.iter().enumerate() {
        if row[coord] == 1 {
            binary_terms.push((stabilizers.len() + index, 1.0));
        }
    }
    binary_terms.push((symplectic_var_offset + coord, -1.0));

    qec_ilp_core::LinearConstraint {
        name: format!("coord_{coord}"),
        sense: qec_ilp_core::ConstraintSense::Eq,
        binary_terms,
        integer_terms: vec![(coord, -2.0)],
        rhs: 0.0,
    }
}

fn weight_rows_for_qubit(
    qubit: usize,
    symplectic_var_offset: usize,
    qubit_activity_offset: usize,
    n: usize,
) -> Vec<qec_ilp_core::LinearConstraint> {
    let x_index = symplectic_var_offset + qubit;
    let z_index = symplectic_var_offset + n + qubit;
    let y_index = qubit_activity_offset + qubit;

    vec![
        qec_ilp_core::LinearConstraint {
            name: format!("weight_x_{qubit}"),
            sense: qec_ilp_core::ConstraintSense::Le,
            binary_terms: vec![(x_index, 1.0), (y_index, -1.0)],
            integer_terms: vec![],
            rhs: 0.0,
        },
        qec_ilp_core::LinearConstraint {
            name: format!("weight_z_{qubit}"),
            sense: qec_ilp_core::ConstraintSense::Le,
            binary_terms: vec![(z_index, 1.0), (y_index, -1.0)],
            integer_terms: vec![],
            rhs: 0.0,
        },
        qec_ilp_core::LinearConstraint {
            name: format!("weight_or_{qubit}"),
            sense: qec_ilp_core::ConstraintSense::Le,
            binary_terms: vec![(y_index, 1.0), (x_index, -1.0), (z_index, -1.0)],
            integer_terms: vec![],
            rhs: 0.0,
        },
    ]
}