1use serde::{Deserialize, Serialize};
2
3use crate::error::{QecError, Result};
4
5pub const COLOR_666_CONSTRUCTION_ID: &str = "color_666";
6pub const COLOR_666_TRIANGULAR_LAYOUT: &str = "triangular";
7pub const COLOR_666_STEANE_PERMUTATION: [usize; 7] = [0, 3, 6, 5, 1, 4, 2];
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
10#[serde(rename_all = "snake_case")]
11pub enum Color666Layout {
12 Triangular,
13}
14
15impl Color666Layout {
16 pub const fn as_str(self) -> &'static str {
17 match self {
18 Self::Triangular => COLOR_666_TRIANGULAR_LAYOUT,
19 }
20 }
21
22 pub fn parse(value: &str) -> Result<Self> {
23 match value {
24 COLOR_666_TRIANGULAR_LAYOUT => Ok(Self::Triangular),
25 _ => Err(QecError::InvalidCssConstruction {
26 construction: COLOR_666_CONSTRUCTION_ID.to_owned(),
27 reason: format!(
28 "unsupported layout {value:?}; supported: {COLOR_666_TRIANGULAR_LAYOUT}"
29 ),
30 }),
31 }
32 }
33}
34
35#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
36pub struct Color666FamilySpec {
37 pub distance: usize,
38 pub layout: Color666Layout,
39}
40
41#[derive(Debug, Clone, PartialEq, Eq)]
42pub struct Color666SparseChecks {
43 pub num_cols: usize,
44 pub rows: Vec<Vec<usize>>,
45}
46
47pub fn color_666_sparse_checks(spec: &Color666FamilySpec) -> Result<Color666SparseChecks> {
48 validate_distance(spec.distance)?;
49 let num_cols = color_666_num_qubits(spec.distance)?;
50 let rows = match spec.layout {
51 Color666Layout::Triangular => triangular_face_supports(spec.distance, num_cols)?,
52 };
53 Ok(Color666SparseChecks { num_cols, rows })
54}
55
56fn validate_distance(distance: usize) -> Result<()> {
57 if distance < 3 {
58 return Err(QecError::InvalidCssConstruction {
59 construction: COLOR_666_CONSTRUCTION_ID.to_owned(),
60 reason: format!("distance must be at least 3, got {distance}"),
61 });
62 }
63 if distance % 2 == 0 {
64 return Err(QecError::InvalidCssConstruction {
65 construction: COLOR_666_CONSTRUCTION_ID.to_owned(),
66 reason: format!("distance must be odd, got {distance}"),
67 });
68 }
69 Ok(())
70}
71
72fn color_666_num_qubits(distance: usize) -> Result<usize> {
73 distance
74 .checked_mul(distance)
75 .and_then(|square| square.checked_mul(3))
76 .and_then(|triple| triple.checked_add(1))
77 .map(|value| value / 4)
78 .ok_or_else(|| QecError::InvalidCssConstruction {
79 construction: COLOR_666_CONSTRUCTION_ID.to_owned(),
80 reason: "size arithmetic overflow while computing n=(3d^2+1)/4".to_owned(),
81 })
82}
83
84fn triangular_bound(distance: usize) -> Result<usize> {
85 distance
86 .checked_sub(1)
87 .and_then(|value| value.checked_mul(3))
88 .map(|value| value / 2)
89 .ok_or_else(|| QecError::InvalidCssConstruction {
90 construction: COLOR_666_CONSTRUCTION_ID.to_owned(),
91 reason: "size arithmetic overflow while computing triangular lattice bound".to_owned(),
92 })
93}
94
95#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
96struct LatticeIndex {
97 row: usize,
98 column: usize,
99}
100
101fn is_plaquette(index: LatticeIndex) -> bool {
102 index.column % 3 == 2 - (index.row % 3)
103}
104
105fn is_site(index: LatticeIndex) -> bool {
106 !is_plaquette(index)
107}
108
109fn site_index_map(bound: usize, num_cols: usize) -> Result<Vec<Vec<Option<usize>>>> {
110 let mut next = 0usize;
111 let mut map = vec![Vec::new(); bound + 1];
112 for row in 0..=bound {
113 map[row] = vec![None; row + 1];
114 for column in 0..=row {
115 let index = LatticeIndex { row, column };
116 if is_site(index) {
117 if next >= num_cols {
118 return Err(QecError::InvalidCssConstruction {
119 construction: COLOR_666_CONSTRUCTION_ID.to_owned(),
120 reason: "site count exceeded n=(3d^2+1)/4".to_owned(),
121 });
122 }
123 map[row][column] = Some(next);
124 next += 1;
125 }
126 }
127 }
128 if next != num_cols {
129 return Err(QecError::InvalidCssConstruction {
130 construction: COLOR_666_CONSTRUCTION_ID.to_owned(),
131 reason: format!("site count {next} did not match n={num_cols}"),
132 });
133 }
134 Ok(map)
135}
136
137fn triangular_face_supports(distance: usize, num_cols: usize) -> Result<Vec<Vec<usize>>> {
138 let bound = triangular_bound(distance)?;
139 let site_indices = site_index_map(bound, num_cols)?;
140 let mut rows = Vec::new();
141
142 for row in 0..=bound {
143 for column in 0..=row {
144 let index = LatticeIndex { row, column };
145 if is_plaquette(index) {
146 let mut support = face_support(bound, &site_indices, index);
147 support.sort_unstable();
148 rows.push(support);
149 }
150 }
151 }
152
153 Ok(rows)
154}
155
156fn face_support(
157 bound: usize,
158 site_indices: &[Vec<Option<usize>>],
159 face: LatticeIndex,
160) -> Vec<usize> {
161 let row = face.row as isize;
162 let column = face.column as isize;
163 let mut support = Vec::with_capacity(6);
164 for (neighbor_row, neighbor_column) in [
165 (row - 1, column - 1),
166 (row - 1, column),
167 (row, column - 1),
168 (row, column + 1),
169 (row + 1, column),
170 (row + 1, column + 1),
171 ] {
172 if neighbor_row < 0 || neighbor_column < 0 {
173 continue;
174 }
175 let neighbor_row = neighbor_row as usize;
176 let neighbor_column = neighbor_column as usize;
177 if neighbor_row > bound || neighbor_column > neighbor_row {
178 continue;
179 }
180 if let Some(site_index) = site_indices[neighbor_row][neighbor_column] {
181 support.push(site_index);
182 }
183 }
184 support
185}
186
187#[cfg(test)]
188mod tests {
189 use super::*;
190
191 #[test]
192 fn triangular_d3_rows_match_issue_fixture() {
193 let checks = color_666_sparse_checks(&Color666FamilySpec {
194 distance: 3,
195 layout: Color666Layout::Triangular,
196 })
197 .unwrap();
198
199 assert_eq!(checks.num_cols, 7);
200 assert_eq!(
201 checks.rows,
202 vec![vec![0, 1, 2, 3], vec![1, 2, 4, 5], vec![2, 3, 5, 6]]
203 );
204 }
205
206 #[test]
207 fn triangular_d5_rows_match_reviewed_fixture() {
208 let checks = color_666_sparse_checks(&Color666FamilySpec {
209 distance: 5,
210 layout: Color666Layout::Triangular,
211 })
212 .unwrap();
213
214 assert_eq!(checks.num_cols, 19);
215 assert_eq!(
216 checks.rows,
217 vec![
218 vec![0, 1, 2, 3],
219 vec![1, 2, 4, 5],
220 vec![2, 3, 5, 6, 8, 9],
221 vec![4, 5, 7, 8, 10, 11],
222 vec![6, 9, 12, 13],
223 vec![7, 10, 14, 15],
224 vec![8, 9, 11, 12, 16, 17],
225 vec![10, 11, 15, 16],
226 vec![12, 13, 17, 18],
227 ]
228 );
229 }
230
231 #[test]
232 fn rejects_invalid_distance_values() {
233 assert!(color_666_sparse_checks(&Color666FamilySpec {
234 distance: 2,
235 layout: Color666Layout::Triangular,
236 })
237 .is_err());
238 assert!(color_666_sparse_checks(&Color666FamilySpec {
239 distance: 4,
240 layout: Color666Layout::Triangular,
241 })
242 .is_err());
243 assert!(color_666_sparse_checks(&Color666FamilySpec {
244 distance: usize::MAX,
245 layout: Color666Layout::Triangular,
246 })
247 .is_err());
248 }
249
250 #[test]
251 fn defensive_lattice_helpers_report_count_and_bound_errors() {
252 assert!(matches!(
253 triangular_bound(0),
254 Err(QecError::InvalidCssConstruction {
255 construction,
256 reason
257 }) if construction == COLOR_666_CONSTRUCTION_ID
258 && reason.contains("triangular lattice bound")
259 ));
260 assert!(matches!(
261 site_index_map(1, 1),
262 Err(QecError::InvalidCssConstruction {
263 construction,
264 reason
265 }) if construction == COLOR_666_CONSTRUCTION_ID
266 && reason.contains("exceeded")
267 ));
268 assert!(matches!(
269 site_index_map(1, 4),
270 Err(QecError::InvalidCssConstruction {
271 construction,
272 reason
273 }) if construction == COLOR_666_CONSTRUCTION_ID
274 && reason.contains("did not match")
275 ));
276 }
277}