1use serde::{Deserialize, Serialize};
2
3use crate::error::{QecError, Result};
4use crate::family_contract::CssClassicalCheckSpec;
5use crate::sparse_gf2::SparseGf2Matrix;
6
7pub const LA_CROSS_CONSTRUCTION_ID: &str = "la_cross";
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
10#[serde(rename_all = "snake_case")]
11pub enum LaCrossBoundary {
12 Open,
13 Periodic,
14}
15
16impl LaCrossBoundary {
17 pub const fn as_str(self) -> &'static str {
18 match self {
19 Self::Open => "open",
20 Self::Periodic => "periodic",
21 }
22 }
23
24 pub fn parse(value: &str) -> Result<Self> {
25 match value {
26 "open" => Ok(Self::Open),
27 "periodic" => Ok(Self::Periodic),
28 _ => Err(invalid(format!("unknown la_cross boundary {value}"))),
29 }
30 }
31}
32
33#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
34pub struct LaCrossSpec {
35 pub seed_length: usize,
36 pub reach: usize,
37 pub boundary: LaCrossBoundary,
38}
39
40#[derive(Debug, Clone, PartialEq, Eq)]
41pub(crate) struct LaCrossClassicalCheck {
42 pub spec: LaCrossSpec,
43 pub check: CssClassicalCheckSpec,
44}
45
46pub(crate) fn la_cross_classical_check(spec: &LaCrossSpec) -> Result<LaCrossClassicalCheck> {
47 validate_la_cross_spec(spec)?;
48 let rows = match spec.boundary {
49 LaCrossBoundary::Open => open_rows(spec.seed_length, spec.reach)?,
50 LaCrossBoundary::Periodic => periodic_rows(spec.seed_length, spec.reach)?,
51 };
52 let matrix = SparseGf2Matrix::new(rows.len(), spec.seed_length, rows)?;
53 Ok(LaCrossClassicalCheck {
54 spec: spec.clone(),
55 check: CssClassicalCheckSpec {
56 num_cols: matrix.num_cols(),
57 rows: matrix.rows().to_vec(),
58 },
59 })
60}
61
62pub(crate) fn la_cross_known_distances(spec: &LaCrossSpec) -> Option<(usize, usize)> {
63 (spec.seed_length == 5 && spec.reach == 2 && spec.boundary == LaCrossBoundary::Open)
64 .then_some((3, 3))
65}
66
67fn validate_la_cross_spec(spec: &LaCrossSpec) -> Result<()> {
68 if spec.seed_length < 2 {
69 return Err(invalid(format!(
70 "seed_length must be at least 2, got {}",
71 spec.seed_length
72 )));
73 }
74 if spec.reach == 0 {
75 return Err(invalid("reach must be nonzero"));
76 }
77 if spec.reach >= spec.seed_length {
78 return Err(invalid(format!(
79 "reach must be less than seed_length, got reach {} and seed_length {}",
80 spec.reach, spec.seed_length
81 )));
82 }
83 preflight_hgp_dimensions(spec)
84}
85
86fn preflight_hgp_dimensions(spec: &LaCrossSpec) -> Result<()> {
87 let row_count = classical_row_count(spec);
88 spec.seed_length
89 .checked_mul(spec.seed_length)
90 .and_then(|left| {
91 row_count
92 .checked_mul(row_count)
93 .and_then(|right| left.checked_add(right))
94 })
95 .ok_or_else(|| overflow("HGP data qubit count"))?;
96 row_count
97 .checked_mul(spec.seed_length)
98 .ok_or_else(|| overflow("HGP check count"))?;
99 Ok(())
100}
101
102fn classical_row_count(spec: &LaCrossSpec) -> usize {
103 match spec.boundary {
104 LaCrossBoundary::Open => spec.seed_length - spec.reach,
105 LaCrossBoundary::Periodic => spec.seed_length,
106 }
107}
108
109fn open_rows(seed_length: usize, reach: usize) -> Result<Vec<Vec<usize>>> {
110 let row_count = seed_length - reach;
111 let mut rows = Vec::new();
112 rows.try_reserve_exact(row_count)
113 .map_err(|_| overflow("classical row allocation"))?;
114 for row in 0..row_count {
115 rows.push(vec![row, row + 1, row + reach]);
116 }
117 Ok(rows)
118}
119
120fn periodic_rows(seed_length: usize, reach: usize) -> Result<Vec<Vec<usize>>> {
121 let mut rows = Vec::new();
122 rows.try_reserve_exact(seed_length)
123 .map_err(|_| overflow("classical row allocation"))?;
124 for row in 0..seed_length {
125 rows.push(vec![
126 row,
127 periodic_add(row, 1, seed_length),
128 periodic_add(row, reach, seed_length),
129 ]);
130 }
131 Ok(rows)
132}
133
134fn periodic_add(value: usize, shift: usize, period: usize) -> usize {
135 let shift = shift % period;
136 if shift == 0 {
137 value
138 } else if value >= period - shift {
139 value - (period - shift)
140 } else {
141 value + shift
142 }
143}
144
145fn invalid(reason: impl Into<String>) -> QecError {
146 QecError::InvalidCssConstruction {
147 construction: LA_CROSS_CONSTRUCTION_ID.to_owned(),
148 reason: reason.into(),
149 }
150}
151
152fn overflow(operation: &'static str) -> QecError {
153 invalid(format!("la_cross dimension overflow during {operation}"))
154}
155
156#[cfg(test)]
157mod tests {
158 use super::*;
159
160 #[test]
161 fn open_5_2_classical_rows_match_issue_fixture() {
162 let check = la_cross_classical_check(&LaCrossSpec {
163 seed_length: 5,
164 reach: 2,
165 boundary: LaCrossBoundary::Open,
166 })
167 .unwrap();
168
169 assert_eq!(check.check.num_cols, 5);
170 assert_eq!(
171 check.check.rows,
172 vec![vec![0, 1, 2], vec![1, 2, 3], vec![2, 3, 4]]
173 );
174 }
175
176 #[test]
177 fn periodic_5_2_rows_wrap_deterministically() {
178 let check = la_cross_classical_check(&LaCrossSpec {
179 seed_length: 5,
180 reach: 2,
181 boundary: LaCrossBoundary::Periodic,
182 })
183 .unwrap();
184
185 assert_eq!(
186 check.check.rows,
187 vec![
188 vec![0, 1, 2],
189 vec![1, 2, 3],
190 vec![2, 3, 4],
191 vec![0, 3, 4],
192 vec![0, 1, 4],
193 ]
194 );
195 }
196}