Skip to main content

qec_code/
css.rs

1use crate::code::StabilizerCode;
2use crate::error::{QecError, Result};
3use crate::gf2;
4use crate::Pauli;
5use serde::{Deserialize, Serialize};
6
7#[derive(Debug, Clone, PartialEq, Eq)]
8pub struct SparseRowsMatrix {
9    num_cols: usize,
10    rows: Vec<Vec<usize>>,
11}
12
13impl SparseRowsMatrix {
14    pub fn new(num_cols: usize, rows: Vec<Vec<usize>>) -> Result<Self> {
15        validate_sparse_rows(num_cols, &rows)?;
16        Ok(Self { num_cols, rows })
17    }
18
19    pub fn num_cols(&self) -> usize {
20        self.num_cols
21    }
22
23    pub fn rows(&self) -> &[Vec<usize>] {
24        &self.rows
25    }
26
27    pub fn to_dense_rows(&self) -> Vec<Vec<u8>> {
28        self.rows
29            .iter()
30            .map(|row| {
31                let mut dense = vec![0; self.num_cols];
32                for &support in row {
33                    dense[support] = 1;
34                }
35                dense
36            })
37            .collect()
38    }
39
40    pub fn to_json_string(&self) -> String {
41        #[derive(Serialize)]
42        struct SparseRowsMatrixJson<'a> {
43            format: &'static str,
44            num_cols: usize,
45            rows: &'a [Vec<usize>],
46        }
47
48        let json = serde_json::to_string(&SparseRowsMatrixJson {
49            format: "sparse_rows",
50            num_cols: self.num_cols,
51            rows: &self.rows,
52        })
53        .expect("validated sparse rows matrix should always serialize");
54        json
55    }
56}
57
58#[derive(Debug, Deserialize)]
59struct SparseRowsMatrixJson {
60    format: String,
61    num_cols: usize,
62    rows: Vec<Vec<usize>>,
63}
64
65pub fn sparse_rows_matrix_from_json_str(input: &str) -> Result<SparseRowsMatrix> {
66    let value: serde_json::Value = serde_json::from_str(input)
67        .map_err(|err| QecError::InvalidCssMatrixJson(err.to_string()))?;
68
69    let format = value
70        .get("format")
71        .and_then(serde_json::Value::as_str)
72        .ok_or(QecError::MissingCssMatrixFormat)?;
73
74    if format != "sparse_rows" {
75        return Err(QecError::UnsupportedCssMatrixFormat {
76            format: format.to_owned(),
77        });
78    }
79
80    let parsed: SparseRowsMatrixJson = serde_json::from_value(value)
81        .map_err(|err| QecError::InvalidCssMatrixJson(err.to_string()))?;
82    let SparseRowsMatrixJson {
83        format: _format,
84        num_cols,
85        rows,
86    } = parsed;
87
88    SparseRowsMatrix::new(num_cols, rows)
89}
90
91#[derive(Debug, Clone, PartialEq, Eq)]
92pub struct CssCode {
93    code: StabilizerCode,
94    hx: Vec<Vec<u8>>,
95    hz: Vec<Vec<u8>>,
96}
97
98impl CssCode {
99    pub fn from_hx_hz(hx: Vec<Vec<u8>>, hz: Vec<Vec<u8>>) -> Result<Self> {
100        let n = shared_width(&hx, &hz)?;
101
102        if !checks_are_orthogonal(&hx, &hz) {
103            return Err(QecError::InvalidCssOrthogonality);
104        }
105
106        let validated_hx = hx.clone();
107        let validated_hz = hz.clone();
108
109        let mut stabilizer_rows = Vec::with_capacity(hx.len() + hz.len());
110        for row in hx {
111            let mut symplectic_row = row;
112            symplectic_row.extend(vec![0; n]);
113            stabilizer_rows.push(symplectic_row);
114        }
115        for row in hz {
116            let mut symplectic_row = vec![0; n];
117            symplectic_row.extend(row);
118            stabilizer_rows.push(symplectic_row);
119        }
120
121        let stabilizers = gf2::try_select_independent_rows(&stabilizer_rows)?
122            .into_iter()
123            .map(Pauli::from_symplectic_row)
124            .collect::<Result<Vec<_>>>()?;
125
126        Ok(Self {
127            code: StabilizerCode::from_stabilizers(n, stabilizers)?,
128            hx: validated_hx,
129            hz: validated_hz,
130        })
131    }
132
133    pub fn code(&self) -> &StabilizerCode {
134        &self.code
135    }
136
137    pub fn hx(&self) -> &[Vec<u8>] {
138        &self.hx
139    }
140
141    pub fn hz(&self) -> &[Vec<u8>] {
142        &self.hz
143    }
144}
145
146fn validate_sparse_rows(num_cols: usize, rows: &[Vec<usize>]) -> Result<()> {
147    if num_cols == 0 {
148        return Err(QecError::InvalidSparseRowsWidth { num_cols });
149    }
150
151    for (row_index, row) in rows.iter().enumerate() {
152        let mut seen = std::collections::BTreeSet::new();
153        for &support in row {
154            if support >= num_cols {
155                return Err(QecError::SparseRowSupportOutOfRange {
156                    row: row_index,
157                    support,
158                    num_cols,
159                });
160            }
161            if !seen.insert(support) {
162                return Err(QecError::DuplicateSparseRowSupport {
163                    row: row_index,
164                    support,
165                });
166            }
167        }
168    }
169    Ok(())
170}
171
172fn shared_width(hx: &[Vec<u8>], hz: &[Vec<u8>]) -> Result<usize> {
173    let n = hx
174        .first()
175        .map(Vec::len)
176        .or_else(|| hz.first().map(Vec::len))
177        .unwrap_or(0);
178
179    validate_rows(hx, n)?;
180    validate_rows(hz, n)?;
181
182    Ok(n)
183}
184
185fn validate_rows(matrix: &[Vec<u8>], expected_width: usize) -> Result<()> {
186    for (row_index, row) in matrix.iter().enumerate() {
187        if row.len() != expected_width {
188            return Err(QecError::RowWidthMismatch {
189                expected: expected_width,
190                actual: row.len(),
191            });
192        }
193
194        for (col_index, bit) in row.iter().enumerate() {
195            if *bit > 1 {
196                return Err(QecError::InvalidBinaryEntry {
197                    row: row_index,
198                    col: col_index,
199                    value: *bit,
200                });
201            }
202        }
203    }
204
205    Ok(())
206}
207
208fn checks_are_orthogonal(hx: &[Vec<u8>], hz: &[Vec<u8>]) -> bool {
209    hx.iter()
210        .all(|x_row| hz.iter().all(|z_row| dot_product_mod_2(x_row, z_row) == 0))
211}
212
213fn dot_product_mod_2(lhs: &[u8], rhs: &[u8]) -> u8 {
214    lhs.iter()
215        .zip(rhs)
216        .fold(0, |parity, (left, right)| parity ^ (*left & *right))
217}