1use std::collections::HashSet;
2
3use super::apm::{AffinePermutation, ApmCssManifestEntry, build_apm_css_checks};
4use super::color_666::{Color666FamilySpec, Color666Layout, color_666_sparse_checks};
5use super::toric_3d::{Toric3dSpec, toric_3d_css_checks};
6use crate::error::{QecError, Result};
7
8#[derive(Debug, Clone, PartialEq, Eq)]
9pub struct BuiltInCssChecks {
10 pub code_id: &'static str,
11 pub num_cols: usize,
12 pub hx: Vec<Vec<usize>>,
13 pub hz: Vec<Vec<usize>>,
14}
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17pub struct BuiltInCssCatalogEntry {
18 pub spec: &'static str,
19 pub description: &'static str,
20}
21
22#[derive(Debug, Clone, PartialEq, Eq)]
23pub enum BuiltInCssCodeSpec {
24 Fixed {
25 code_id: &'static str,
26 },
27 Family {
28 family: BuiltInCssFamily,
29 params: BuiltInCssParams,
30 },
31}
32
33#[derive(Debug, Clone, Copy, PartialEq, Eq)]
34pub enum BuiltInCssFamily {
35 RepetitionX,
36 RepetitionZ,
37 SurfaceRotated,
38 Color666,
39 Toric,
40 Toric3d,
41 BivariateBicycle,
42 ApmKasai,
43}
44
45#[derive(Debug, Clone, PartialEq, Eq)]
46pub enum BuiltInCssParams {
47 Distance { distance: usize },
48 Toric3d(Toric3dSpec),
49 BivariateBicycle(BivariateBicycleParams),
50 ApmKasai { p: usize },
51}
52
53#[derive(Debug, Clone, PartialEq, Eq)]
54pub struct BivariateBicycleParams {
55 pub lx: usize,
56 pub ly: usize,
57 pub a_terms: Vec<(usize, usize)>,
58 pub b_terms: Vec<(usize, usize)>,
59}
60
61const BUILT_IN_CSS_CATALOG: &[BuiltInCssCatalogEntry] = &[
62 BuiltInCssCatalogEntry {
63 spec: "steane",
64 description: "fixed [[7,1,3]] CSS code",
65 },
66 BuiltInCssCatalogEntry {
67 spec: "bb72",
68 description: "fixed [[72,12,6]] bivariate-bicycle CSS code",
69 },
70 BuiltInCssCatalogEntry {
71 spec: "apm_kasai:p=96",
72 description: "fixed Table A1 P=96 APM-CSS code",
73 },
74 BuiltInCssCatalogEntry {
75 spec: "apm_kasai:p=192",
76 description: "fixed Table A1 P=192 APM-CSS code",
77 },
78 BuiltInCssCatalogEntry {
79 spec: "bb:lx=<period-x>,ly=<period-y>,a=<dx>:<dy>|...,b=<dx>:<dy>|...",
80 description: "bivariate-bicycle CSS family over periodic lattice",
81 },
82 BuiltInCssCatalogEntry {
83 spec: "repetition_x:d=<distance>",
84 description: "X-check chain, distance >= 2",
85 },
86 BuiltInCssCatalogEntry {
87 spec: "repetition_z:d=<distance>",
88 description: "Z-check chain, distance >= 2",
89 },
90 BuiltInCssCatalogEntry {
91 spec: "surface_rotated:d=<distance>",
92 description: "rotated surface CSS code, distance >= 2",
93 },
94 BuiltInCssCatalogEntry {
95 spec: "color_666:d=<distance>",
96 description: "triangular 6.6.6 color CSS code, odd distance >= 3",
97 },
98 BuiltInCssCatalogEntry {
99 spec: "toric:d=<distance>",
100 description: "periodic square-lattice toric CSS code, distance >= 2",
101 },
102 BuiltInCssCatalogEntry {
103 spec: "toric_3d:lx=<period-x>,ly=<period-y>,lz=<period-z>",
104 description: "periodic cubic 3D toric CSS code, periods >= 3",
105 },
106];
107
108pub fn built_in_css_catalog() -> &'static [BuiltInCssCatalogEntry] {
109 BUILT_IN_CSS_CATALOG
110}
111
112pub fn parse_built_in_css_code_spec(input: &str) -> Result<BuiltInCssCodeSpec> {
113 if let Some((family_name, params_text)) = input.split_once(':') {
114 return parse_built_in_css_family_spec(family_name, params_text);
115 }
116
117 match input {
118 "steane" => Ok(BuiltInCssCodeSpec::Fixed { code_id: "steane" }),
119 "bb72" => Ok(BuiltInCssCodeSpec::Fixed { code_id: "bb72" }),
120 "apm_kasai" => Err(QecError::MissingBuiltInCssParameter {
121 family: input.to_owned(),
122 parameter: "p".to_owned(),
123 }),
124 "repetition_x" | "repetition_z" | "surface_rotated" | "color_666" | "toric" => {
125 Err(QecError::MissingBuiltInCssParameter {
126 family: input.to_owned(),
127 parameter: "d".to_owned(),
128 })
129 }
130 "toric_3d" => Err(QecError::MissingBuiltInCssParameter {
131 family: input.to_owned(),
132 parameter: "lx".to_owned(),
133 }),
134 "bb" => Err(QecError::MissingBuiltInCssParameter {
135 family: input.to_owned(),
136 parameter: "lx".to_owned(),
137 }),
138 _ => Err(QecError::UnknownBuiltInCssCode {
139 code_id: input.to_owned(),
140 }),
141 }
142}
143
144fn parse_built_in_css_family_spec(
145 family_name: &str,
146 params_text: &str,
147) -> Result<BuiltInCssCodeSpec> {
148 match family_name {
149 "repetition_x" => {
150 parse_distance_family_spec(family_name, BuiltInCssFamily::RepetitionX, params_text)
151 }
152 "repetition_z" => {
153 parse_distance_family_spec(family_name, BuiltInCssFamily::RepetitionZ, params_text)
154 }
155 "surface_rotated" => {
156 parse_distance_family_spec(family_name, BuiltInCssFamily::SurfaceRotated, params_text)
157 }
158 "color_666" => {
159 parse_distance_family_spec(family_name, BuiltInCssFamily::Color666, params_text)
160 }
161 "toric" => parse_distance_family_spec(family_name, BuiltInCssFamily::Toric, params_text),
162 "toric_3d" => {
163 let spec = parse_toric_3d_params(family_name, params_text)?;
164 Ok(BuiltInCssCodeSpec::Family {
165 family: BuiltInCssFamily::Toric3d,
166 params: BuiltInCssParams::Toric3d(spec),
167 })
168 }
169 "apm_kasai" => {
170 let p = parse_apm_kasai_params(family_name, params_text)?;
171 Ok(BuiltInCssCodeSpec::Family {
172 family: BuiltInCssFamily::ApmKasai,
173 params: BuiltInCssParams::ApmKasai { p },
174 })
175 }
176 "bb" => {
177 let params = parse_bivariate_bicycle_params(family_name, params_text)?;
178 Ok(BuiltInCssCodeSpec::Family {
179 family: BuiltInCssFamily::BivariateBicycle,
180 params: BuiltInCssParams::BivariateBicycle(params),
181 })
182 }
183 _ => Err(QecError::UnknownBuiltInCssFamily {
184 family: family_name.to_owned(),
185 }),
186 }
187}
188
189fn parse_distance_family_spec(
190 family_name: &str,
191 family: BuiltInCssFamily,
192 params_text: &str,
193) -> Result<BuiltInCssCodeSpec> {
194 let distance = parse_repetition_distance(family_name, params_text)?;
195
196 Ok(BuiltInCssCodeSpec::Family {
197 family,
198 params: BuiltInCssParams::Distance { distance },
199 })
200}
201
202fn parse_repetition_distance(family_name: &str, params_text: &str) -> Result<usize> {
203 if params_text.is_empty() {
204 return Err(QecError::MissingBuiltInCssParameter {
205 family: family_name.to_owned(),
206 parameter: "d".to_owned(),
207 });
208 }
209
210 let mut distance = None;
211
212 for pair in params_text.split(',') {
213 let Some((key, value)) = pair.split_once('=') else {
214 return Err(QecError::UnexpectedBuiltInCssParameter {
215 family: family_name.to_owned(),
216 parameter: pair.to_owned(),
217 });
218 };
219
220 match key {
221 "d" => {
222 if distance.is_some() {
223 return Err(QecError::DuplicateBuiltInCssParameter {
224 family: family_name.to_owned(),
225 parameter: "d".to_owned(),
226 });
227 }
228
229 let parsed = value.parse::<usize>().map_err(|_| {
230 QecError::InvalidBuiltInCssIntegerParameter {
231 family: family_name.to_owned(),
232 parameter: "d".to_owned(),
233 value: value.to_owned(),
234 }
235 })?;
236
237 if parsed == 0 {
238 return Err(QecError::OutOfRangeBuiltInCssIntegerParameter {
239 family: family_name.to_owned(),
240 parameter: "d".to_owned(),
241 value: parsed,
242 });
243 }
244
245 distance = Some(parsed);
246 }
247 _ => {
248 return Err(QecError::UnexpectedBuiltInCssParameter {
249 family: family_name.to_owned(),
250 parameter: key.to_owned(),
251 });
252 }
253 }
254 }
255
256 distance.ok_or_else(|| QecError::MissingBuiltInCssParameter {
257 family: family_name.to_owned(),
258 parameter: "d".to_owned(),
259 })
260}
261
262fn parse_toric_3d_params(family_name: &str, params_text: &str) -> Result<Toric3dSpec> {
263 if params_text.is_empty() {
264 return Err(QecError::MissingBuiltInCssParameter {
265 family: family_name.to_owned(),
266 parameter: "lx".to_owned(),
267 });
268 }
269
270 let mut lx = None;
271 let mut ly = None;
272 let mut lz = None;
273 for pair in params_text.split(',') {
274 let Some((key, value)) = pair.split_once('=') else {
275 return Err(QecError::UnexpectedBuiltInCssParameter {
276 family: family_name.to_owned(),
277 parameter: pair.to_owned(),
278 });
279 };
280
281 match key {
282 "lx" => parse_unique_positive_usize_param(family_name, "lx", value, &mut lx)?,
283 "ly" => parse_unique_positive_usize_param(family_name, "ly", value, &mut ly)?,
284 "lz" => parse_unique_positive_usize_param(family_name, "lz", value, &mut lz)?,
285 _ => {
286 return Err(QecError::UnexpectedBuiltInCssParameter {
287 family: family_name.to_owned(),
288 parameter: key.to_owned(),
289 });
290 }
291 }
292 }
293
294 let spec = Toric3dSpec {
295 lx: lx.ok_or_else(|| QecError::MissingBuiltInCssParameter {
296 family: family_name.to_owned(),
297 parameter: "lx".to_owned(),
298 })?,
299 ly: ly.ok_or_else(|| QecError::MissingBuiltInCssParameter {
300 family: family_name.to_owned(),
301 parameter: "ly".to_owned(),
302 })?,
303 lz: lz.ok_or_else(|| QecError::MissingBuiltInCssParameter {
304 family: family_name.to_owned(),
305 parameter: "lz".to_owned(),
306 })?,
307 };
308 toric_3d_css_checks(spec)?;
309 Ok(spec)
310}
311
312fn parse_bivariate_bicycle_params(
313 family_name: &str,
314 params_text: &str,
315) -> Result<BivariateBicycleParams> {
316 if params_text.is_empty() {
317 return Err(QecError::MissingBuiltInCssParameter {
318 family: family_name.to_owned(),
319 parameter: "lx".to_owned(),
320 });
321 }
322
323 let mut lx = None;
324 let mut ly = None;
325 let mut a_terms = None;
326 let mut b_terms = None;
327
328 for pair in params_text.split(',') {
329 let Some((key, value)) = pair.split_once('=') else {
330 return Err(QecError::UnexpectedBuiltInCssParameter {
331 family: family_name.to_owned(),
332 parameter: pair.to_owned(),
333 });
334 };
335
336 match key {
337 "lx" => parse_unique_positive_usize_param(family_name, "lx", value, &mut lx)?,
338 "ly" => parse_unique_positive_usize_param(family_name, "ly", value, &mut ly)?,
339 "a" => parse_unique_bivariate_bicycle_terms(family_name, "a", value, &mut a_terms)?,
340 "b" => parse_unique_bivariate_bicycle_terms(family_name, "b", value, &mut b_terms)?,
341 _ => {
342 return Err(QecError::UnexpectedBuiltInCssParameter {
343 family: family_name.to_owned(),
344 parameter: key.to_owned(),
345 });
346 }
347 }
348 }
349
350 let params = BivariateBicycleParams {
351 lx: lx.ok_or_else(|| QecError::MissingBuiltInCssParameter {
352 family: family_name.to_owned(),
353 parameter: "lx".to_owned(),
354 })?,
355 ly: ly.ok_or_else(|| QecError::MissingBuiltInCssParameter {
356 family: family_name.to_owned(),
357 parameter: "ly".to_owned(),
358 })?,
359 a_terms: a_terms.ok_or_else(|| QecError::MissingBuiltInCssParameter {
360 family: family_name.to_owned(),
361 parameter: "a".to_owned(),
362 })?,
363 b_terms: b_terms.ok_or_else(|| QecError::MissingBuiltInCssParameter {
364 family: family_name.to_owned(),
365 parameter: "b".to_owned(),
366 })?,
367 };
368
369 validate_bivariate_bicycle_params(¶ms)?;
370 Ok(params)
371}
372
373const APM_KASAI_SUPPORTED_P_VALUES: &str = "96, 192";
374const APM_KASAI_P96_CODE_ID: &str = "apm_kasai:p=96";
375const APM_KASAI_P192_CODE_ID: &str = "apm_kasai:p=192";
376const APM_KASAI_P96_P: u64 = 96;
377const APM_KASAI_P192_P: u64 = 192;
378const APM_KASAI_J: u64 = 3;
379const APM_KASAI_L: u64 = 12;
380const APM_KASAI_P96_F: &[(u64, u64)] = &[(5, 41), (85, 77), (73, 66), (1, 0), (1, 72), (37, 9)];
381const APM_KASAI_P96_G: &[(u64, u64)] = &[(61, 15), (1, 24), (89, 62), (25, 22), (85, 93), (25, 78)];
382const APM_KASAI_P192_F: &[(u64, u64)] = &[
383 (71, 127),
384 (97, 80),
385 (67, 117),
386 (163, 165),
387 (25, 60),
388 (187, 33),
389];
390const APM_KASAI_P192_G: &[(u64, u64)] = &[
391 (163, 165),
392 (55, 183),
393 (167, 79),
394 (139, 41),
395 (109, 78),
396 (31, 27),
397];
398
399fn parse_apm_kasai_params(family_name: &str, params_text: &str) -> Result<usize> {
400 if params_text.is_empty() {
401 return Err(QecError::MissingBuiltInCssParameter {
402 family: family_name.to_owned(),
403 parameter: "p".to_owned(),
404 });
405 }
406
407 let mut p = None;
408 for pair in params_text.split(',') {
409 let Some((key, value)) = pair.split_once('=') else {
410 return Err(QecError::UnexpectedBuiltInCssParameter {
411 family: family_name.to_owned(),
412 parameter: pair.to_owned(),
413 });
414 };
415
416 match key {
417 "p" => {
418 if p.is_some() {
419 return Err(QecError::DuplicateBuiltInCssParameter {
420 family: family_name.to_owned(),
421 parameter: "p".to_owned(),
422 });
423 }
424 p = Some(value.parse::<usize>().map_err(|_| {
425 QecError::InvalidBuiltInCssIntegerParameter {
426 family: family_name.to_owned(),
427 parameter: "p".to_owned(),
428 value: value.to_owned(),
429 }
430 })?);
431 }
432 _ => {
433 return Err(QecError::UnexpectedBuiltInCssParameter {
434 family: family_name.to_owned(),
435 parameter: key.to_owned(),
436 });
437 }
438 }
439 }
440
441 Ok(p.expect("apm_kasai parameter parser should require p before success"))
442}
443
444fn apm_kasai_css_checks(p: usize) -> Result<BuiltInCssChecks> {
445 let entry = match p {
446 96 => apm_kasai_manifest_entry(
447 APM_KASAI_P96_CODE_ID,
448 APM_KASAI_P96_P,
449 APM_KASAI_P96_F,
450 APM_KASAI_P96_G,
451 ),
452 192 => apm_kasai_manifest_entry(
453 APM_KASAI_P192_CODE_ID,
454 APM_KASAI_P192_P,
455 APM_KASAI_P192_F,
456 APM_KASAI_P192_G,
457 ),
458 _ => {
459 return Err(QecError::UnsupportedBuiltInCssIntegerParameter {
460 family: "apm_kasai".to_owned(),
461 parameter: "p".to_owned(),
462 value: p,
463 supported: APM_KASAI_SUPPORTED_P_VALUES.to_owned(),
464 note: "available Table A1 APM-CSS instances".to_owned(),
465 });
466 }
467 };
468
469 Ok(build_apm_css_checks(&entry).expect("pinned APM Kasai manifest must build"))
470}
471
472fn apm_kasai_manifest_entry(
473 code_id: &'static str,
474 p: u64,
475 f_params: &[(u64, u64)],
476 g_params: &[(u64, u64)],
477) -> ApmCssManifestEntry {
478 let affine = |slope, offset| {
479 AffinePermutation::new(p, slope, offset)
480 .expect("pinned APM Kasai affine maps must be permutations")
481 };
482 let f = f_params
483 .iter()
484 .map(|&(slope, offset)| affine(slope, offset))
485 .collect();
486 let g = g_params
487 .iter()
488 .map(|&(slope, offset)| affine(slope, offset))
489 .collect();
490
491 ApmCssManifestEntry::new(code_id, p, APM_KASAI_J, APM_KASAI_L, f, g)
492 .expect("pinned APM Kasai manifest must satisfy invariants")
493}
494
495fn parse_unique_positive_usize_param(
496 family_name: &str,
497 parameter: &'static str,
498 value: &str,
499 slot: &mut Option<usize>,
500) -> Result<()> {
501 if slot.is_some() {
502 return Err(QecError::DuplicateBuiltInCssParameter {
503 family: family_name.to_owned(),
504 parameter: parameter.to_owned(),
505 });
506 }
507
508 let parsed =
509 value
510 .parse::<usize>()
511 .map_err(|_| QecError::InvalidBuiltInCssIntegerParameter {
512 family: family_name.to_owned(),
513 parameter: parameter.to_owned(),
514 value: value.to_owned(),
515 })?;
516
517 if parsed == 0 {
518 return Err(QecError::OutOfRangeBuiltInCssIntegerParameter {
519 family: family_name.to_owned(),
520 parameter: parameter.to_owned(),
521 value: parsed,
522 });
523 }
524
525 *slot = Some(parsed);
526 Ok(())
527}
528
529fn parse_unique_bivariate_bicycle_terms(
530 family_name: &str,
531 parameter: &'static str,
532 value: &str,
533 slot: &mut Option<Vec<(usize, usize)>>,
534) -> Result<()> {
535 if slot.is_some() {
536 return Err(QecError::DuplicateBuiltInCssParameter {
537 family: family_name.to_owned(),
538 parameter: parameter.to_owned(),
539 });
540 }
541
542 let mut terms = Vec::new();
543 for term in value.split('|') {
544 let Some((dx_text, dy_text)) = term.split_once(':') else {
545 return Err(QecError::InvalidBuiltInCssIntegerParameter {
546 family: family_name.to_owned(),
547 parameter: parameter.to_owned(),
548 value: term.to_owned(),
549 });
550 };
551
552 let dx =
553 dx_text
554 .parse::<usize>()
555 .map_err(|_| QecError::InvalidBuiltInCssIntegerParameter {
556 family: family_name.to_owned(),
557 parameter: parameter.to_owned(),
558 value: dx_text.to_owned(),
559 })?;
560 let dy =
561 dy_text
562 .parse::<usize>()
563 .map_err(|_| QecError::InvalidBuiltInCssIntegerParameter {
564 family: family_name.to_owned(),
565 parameter: parameter.to_owned(),
566 value: dy_text.to_owned(),
567 })?;
568
569 terms.push((dx, dy));
570 }
571
572 *slot = Some(terms);
573 Ok(())
574}
575
576const STEANE_ROW_SUPPORTS: &[&[usize]] = &[&[0, 3, 5, 6], &[1, 3, 4, 6], &[2, 4, 5, 6]];
577
578const BB72_LX: usize = 6;
579const BB72_LY: usize = 6;
580const BB72_A_TERMS: &[(usize, usize)] = &[(3, 0), (0, 1), (0, 2)];
581const BB72_B_TERMS: &[(usize, usize)] = &[(0, 3), (1, 0), (2, 0)];
582
583fn bb72_bivariate_bicycle_params() -> BivariateBicycleParams {
584 BivariateBicycleParams {
585 lx: BB72_LX,
586 ly: BB72_LY,
587 a_terms: BB72_A_TERMS.to_vec(),
588 b_terms: BB72_B_TERMS.to_vec(),
589 }
590}
591
592fn bivariate_bicycle_checks(
593 lx: usize,
594 ly: usize,
595 a_terms: &[(usize, usize)],
596 b_terms: &[(usize, usize)],
597) -> (Vec<Vec<usize>>, Vec<Vec<usize>>) {
598 let block = lx * ly;
599 let index = |x: usize, y: usize| -> usize { (x % lx) * ly + (y % ly) };
600 let periodic_add = |coord: usize, shift: usize, period: usize| -> usize {
601 if shift == 0 {
602 coord
603 } else if coord >= period - shift {
604 coord - (period - shift)
605 } else {
606 coord + shift
607 }
608 };
609 let mut hx = Vec::with_capacity(block);
610 let mut hz = Vec::with_capacity(block);
611
612 for x in 0..lx {
613 for y in 0..ly {
614 let mut x_row = Vec::new();
615 for &(dx, dy) in a_terms {
616 let dx = dx % lx;
617 let dy = dy % ly;
618 x_row.push(index(periodic_add(x, dx, lx), periodic_add(y, dy, ly)));
619 }
620 for &(dx, dy) in b_terms {
621 let dx = dx % lx;
622 let dy = dy % ly;
623 x_row.push(block + index(periodic_add(x, dx, lx), periodic_add(y, dy, ly)));
624 }
625 x_row.sort_unstable();
626 hx.push(x_row);
627
628 let mut z_row = Vec::new();
629 for &(dx, dy) in b_terms {
630 z_row.push(index((x + lx - dx % lx) % lx, (y + ly - dy % ly) % ly));
631 }
632 for &(dx, dy) in a_terms {
633 z_row.push(block + index((x + lx - dx % lx) % lx, (y + ly - dy % ly) % ly));
634 }
635 z_row.sort_unstable();
636 hz.push(z_row);
637 }
638 }
639
640 (hx, hz)
641}
642
643pub fn bivariate_bicycle_css_checks(params: BivariateBicycleParams) -> Result<BuiltInCssChecks> {
644 validate_bivariate_bicycle_params(¶ms)?;
645
646 let (hx, hz) = bivariate_bicycle_checks(params.lx, params.ly, ¶ms.a_terms, ¶ms.b_terms);
647
648 Ok(BuiltInCssChecks {
649 code_id: "bb",
650 num_cols: 2 * params.lx * params.ly,
651 hx,
652 hz,
653 })
654}
655
656fn validate_bivariate_bicycle_params(params: &BivariateBicycleParams) -> Result<()> {
657 if params.lx == 0 {
658 return Err(QecError::OutOfRangeBuiltInCssIntegerParameter {
659 family: "bb".to_owned(),
660 parameter: "lx".to_owned(),
661 value: 0,
662 });
663 }
664
665 if params.ly == 0 {
666 return Err(QecError::OutOfRangeBuiltInCssIntegerParameter {
667 family: "bb".to_owned(),
668 parameter: "ly".to_owned(),
669 value: 0,
670 });
671 }
672
673 validate_bivariate_bicycle_terms("bb", "a_terms", params.lx, params.ly, ¶ms.a_terms)?;
674 validate_bivariate_bicycle_terms("bb", "b_terms", params.lx, params.ly, ¶ms.b_terms)?;
675 Ok(())
676}
677
678fn validate_bivariate_bicycle_terms(
679 family: &'static str,
680 parameter: &'static str,
681 lx: usize,
682 ly: usize,
683 terms: &[(usize, usize)],
684) -> Result<()> {
685 if terms.is_empty() {
686 return Err(QecError::MissingBuiltInCssParameter {
687 family: family.to_owned(),
688 parameter: parameter.to_owned(),
689 });
690 }
691
692 let mut seen = HashSet::new();
693 for &(dx, dy) in terms {
694 let normalized = (dx % lx, dy % ly);
695 if !seen.insert(normalized) {
696 return Err(QecError::DuplicateBuiltInCssParameter {
697 family: family.to_owned(),
698 parameter: parameter.to_owned(),
699 });
700 }
701 }
702
703 Ok(())
704}
705
706pub fn built_in_css_checks(code_id: &str) -> Result<BuiltInCssChecks> {
707 match parse_built_in_css_code_spec(code_id)? {
708 BuiltInCssCodeSpec::Fixed { code_id } => fixed_built_in_css_checks(code_id),
709 BuiltInCssCodeSpec::Family {
710 family: BuiltInCssFamily::BivariateBicycle,
711 params: BuiltInCssParams::BivariateBicycle(params),
712 } => bivariate_bicycle_css_checks(params),
713 BuiltInCssCodeSpec::Family { family, params } => family_css_checks(family, params),
714 }
715}
716
717fn fixed_built_in_css_checks(code_id: &'static str) -> Result<BuiltInCssChecks> {
718 match code_id {
719 "steane" => {
720 let hx = STEANE_ROW_SUPPORTS
721 .iter()
722 .map(|row| row.to_vec())
723 .collect::<Vec<_>>();
724
725 Ok(BuiltInCssChecks {
726 code_id: "steane",
727 num_cols: 7,
728 hx: hx.clone(),
729 hz: hx,
730 })
731 }
732 "bb72" => {
733 let mut checks = bivariate_bicycle_css_checks(bb72_bivariate_bicycle_params())?;
734 checks.code_id = "bb72";
735 Ok(checks)
736 }
737 _ => Err(QecError::UnknownBuiltInCssCode {
738 code_id: code_id.to_owned(),
739 }),
740 }
741}
742
743fn family_css_checks(
744 family: BuiltInCssFamily,
745 params: BuiltInCssParams,
746) -> Result<BuiltInCssChecks> {
747 match family {
748 BuiltInCssFamily::RepetitionX => {
749 let BuiltInCssParams::Distance { distance } = params else {
750 unreachable!("repetition_x only uses distance params");
751 };
752 let hx = chain_supports("repetition_x", distance)?;
753 Ok(BuiltInCssChecks {
754 code_id: "repetition_x",
755 num_cols: distance,
756 hx,
757 hz: vec![],
758 })
759 }
760 BuiltInCssFamily::RepetitionZ => {
761 let BuiltInCssParams::Distance { distance } = params else {
762 unreachable!("repetition_z only uses distance params");
763 };
764 let hz = chain_supports("repetition_z", distance)?;
765 Ok(BuiltInCssChecks {
766 code_id: "repetition_z",
767 num_cols: distance,
768 hx: vec![],
769 hz,
770 })
771 }
772 BuiltInCssFamily::SurfaceRotated => {
773 let BuiltInCssParams::Distance { distance } = params else {
774 unreachable!("surface_rotated only uses distance params");
775 };
776 surface_rotated_css_checks(distance)
777 }
778 BuiltInCssFamily::Color666 => {
779 let BuiltInCssParams::Distance { distance } = params else {
780 unreachable!("color_666 only uses distance params");
781 };
782 let checks = color_666_sparse_checks(&Color666FamilySpec {
783 distance,
784 layout: Color666Layout::Triangular,
785 })?;
786 Ok(BuiltInCssChecks {
787 code_id: "color_666",
788 num_cols: checks.num_cols,
789 hx: checks.rows.clone(),
790 hz: checks.rows,
791 })
792 }
793 BuiltInCssFamily::Toric => {
794 let BuiltInCssParams::Distance { distance } = params else {
795 unreachable!("toric only uses distance params");
796 };
797 toric_css_checks(distance)
798 }
799 BuiltInCssFamily::Toric3d => {
800 let BuiltInCssParams::Toric3d(spec) = params else {
801 unreachable!("toric_3d only uses toric_3d params");
802 };
803 let checks = toric_3d_css_checks(spec)?;
804 Ok(BuiltInCssChecks {
805 code_id: "toric_3d",
806 num_cols: checks.num_cols,
807 hx: checks.hx,
808 hz: checks.hz,
809 })
810 }
811 BuiltInCssFamily::ApmKasai => {
812 let BuiltInCssParams::ApmKasai { p } = params else {
813 unreachable!("apm_kasai only uses p params");
814 };
815 apm_kasai_css_checks(p)
816 }
817 BuiltInCssFamily::BivariateBicycle => unreachable!("bb specs are parser-only here"),
818 }
819}
820
821fn chain_supports(family: &'static str, distance: usize) -> Result<Vec<Vec<usize>>> {
822 if distance < 2 {
823 return Err(QecError::OutOfRangeBuiltInCssIntegerParameter {
824 family: family.to_owned(),
825 parameter: "d".to_owned(),
826 value: distance,
827 });
828 }
829
830 Ok((0..distance - 1).map(|col| vec![col, col + 1]).collect())
831}
832
833fn surface_rotated_css_checks(distance: usize) -> Result<BuiltInCssChecks> {
834 if distance < 2 {
835 return Err(QecError::OutOfRangeBuiltInCssIntegerParameter {
836 family: "surface_rotated".to_owned(),
837 parameter: "d".to_owned(),
838 value: distance,
839 });
840 }
841
842 let (hx, hz) = rotated_surface_supports(distance);
843
844 Ok(BuiltInCssChecks {
845 code_id: "surface_rotated",
846 num_cols: distance * distance,
847 hx,
848 hz,
849 })
850}
851
852fn rotated_surface_supports(distance: usize) -> (Vec<Vec<usize>>, Vec<Vec<usize>>) {
853 let mut hx = Vec::new();
854 let mut hz = Vec::new();
855
856 for ax in 0..=distance {
857 for ay in 0..=distance {
858 let on_boundary_1 = ax == 0 || ax == distance;
859 let on_boundary_2 = ay == 0 || ay == distance;
860 let parity = (ax % 2) != (ay % 2);
861 if on_boundary_1 && parity {
862 continue;
863 }
864 if on_boundary_2 && !parity {
865 continue;
866 }
867
868 let support = rotated_surface_measure_support(distance, ax, ay);
869 if support.is_empty() {
870 continue;
871 }
872
873 if parity {
874 hx.push(support);
875 } else {
876 hz.push(support);
877 }
878 }
879 }
880
881 (hx, hz)
882}
883
884fn rotated_surface_measure_support(distance: usize, ax: usize, ay: usize) -> Vec<usize> {
885 let mut support = Vec::new();
886 let mx = (2 * ax) as isize;
887 let my = (2 * ay) as isize;
888
889 for (dx, dy) in [(1isize, 1isize), (1, -1), (-1, 1), (-1, -1)] {
890 let x = mx + dx;
891 let y = my + dy;
892 if x >= 1
893 && x <= (2 * distance - 1) as isize
894 && y >= 1
895 && y <= (2 * distance - 1) as isize
896 && x % 2 == 1
897 && y % 2 == 1
898 {
899 let qx = ((x - 1) / 2) as usize;
900 let qy = ((y - 1) / 2) as usize;
901 if qx < distance && qy < distance {
902 support.push(rotated_surface_data_index(distance, qx, qy));
903 }
904 }
905 }
906
907 support.sort_unstable();
908 support.dedup();
909 support
910}
911
912fn rotated_surface_data_index(distance: usize, x: usize, y: usize) -> usize {
913 x * distance + y
914}
915
916fn toric_css_checks(distance: usize) -> Result<BuiltInCssChecks> {
917 if distance < 2 {
918 return Err(QecError::OutOfRangeBuiltInCssIntegerParameter {
919 family: "toric".to_owned(),
920 parameter: "d".to_owned(),
921 value: distance,
922 });
923 }
924
925 let (hx, hz) = toric_supports(distance);
926
927 Ok(BuiltInCssChecks {
928 code_id: "toric",
929 num_cols: 2 * distance * distance,
930 hx,
931 hz,
932 })
933}
934
935fn toric_supports(distance: usize) -> (Vec<Vec<usize>>, Vec<Vec<usize>>) {
936 let mut hx = Vec::with_capacity(distance * distance);
937 let mut hz = Vec::with_capacity(distance * distance);
938
939 for x in 0..distance {
940 for y in 0..distance {
941 hx.push(toric_x_check_support(distance, x, y));
942 hz.push(toric_z_check_support(distance, x, y));
943 }
944 }
945
946 (hx, hz)
947}
948
949fn toric_x_check_support(distance: usize, x: usize, y: usize) -> Vec<usize> {
950 sorted_toric_row([
951 toric_horizontal_index(distance, x, y),
952 toric_horizontal_index(distance, x, wrap_prev(y, distance)),
953 toric_vertical_index(distance, x, y),
954 toric_vertical_index(distance, wrap_prev(x, distance), y),
955 ])
956}
957
958fn toric_z_check_support(distance: usize, x: usize, y: usize) -> Vec<usize> {
959 sorted_toric_row([
960 toric_horizontal_index(distance, x, y),
961 toric_horizontal_index(distance, wrap_next(x, distance), y),
962 toric_vertical_index(distance, x, y),
963 toric_vertical_index(distance, x, wrap_next(y, distance)),
964 ])
965}
966
967fn sorted_toric_row(mut row: [usize; 4]) -> Vec<usize> {
968 row.sort_unstable();
969 row.to_vec()
970}
971
972fn toric_horizontal_index(distance: usize, x: usize, y: usize) -> usize {
973 x * distance + y
974}
975
976fn toric_vertical_index(distance: usize, x: usize, y: usize) -> usize {
977 distance * distance + x * distance + y
978}
979
980fn wrap_prev(value: usize, distance: usize) -> usize {
981 (value + distance - 1) % distance
982}
983
984fn wrap_next(value: usize, distance: usize) -> usize {
985 (value + 1) % distance
986}
987
988#[cfg(test)]
989mod tests {
990 use super::*;
991
992 #[test]
993 #[should_panic(expected = "color_666 only uses distance params")]
994 fn color_666_family_checks_reject_mismatched_internal_params() {
995 let _ = family_css_checks(
996 BuiltInCssFamily::Color666,
997 BuiltInCssParams::ApmKasai { p: 96 },
998 );
999 }
1000
1001 #[test]
1002 #[should_panic(expected = "toric_3d only uses toric_3d params")]
1003 fn toric_3d_family_checks_reject_mismatched_internal_params() {
1004 let _ = family_css_checks(
1005 BuiltInCssFamily::Toric3d,
1006 BuiltInCssParams::ApmKasai { p: 96 },
1007 );
1008 }
1009}