1use serde::{Deserialize, Serialize};
2
3use crate::error::{QecError, Result};
4use crate::sparse_gf2::SparseGf2Matrix;
5
6pub const GENERALIZED_BICYCLE_CONSTRUCTION_ID: &str = "generalized_bicycle";
7
8#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
9pub struct GeneralizedBicycleSpec {
10 pub order: usize,
11 pub a_exponents: Vec<usize>,
12 pub b_exponents: Vec<usize>,
13}
14
15#[derive(Debug, Clone, PartialEq, Eq)]
16pub struct GeneralizedBicycleSparseChecks {
17 pub num_cols: usize,
18 pub h_x: Vec<Vec<usize>>,
19 pub h_z: Vec<Vec<usize>>,
20 pub normalized_spec: GeneralizedBicycleSpec,
21}
22
23pub fn generalized_bicycle_sparse_checks(
24 spec: &GeneralizedBicycleSpec,
25) -> Result<GeneralizedBicycleSparseChecks> {
26 let normalized_spec = normalize_spec(spec)?;
27 let a = cyclic_circulant(normalized_spec.order, &normalized_spec.a_exponents)?;
28 let b = cyclic_circulant(normalized_spec.order, &normalized_spec.b_exponents)?;
29 let h_x = a.hconcat(&b)?;
30 let h_z = b.transpose()?.hconcat(&a.transpose()?)?;
31
32 Ok(GeneralizedBicycleSparseChecks {
33 num_cols: h_x.num_cols(),
34 h_x: h_x.rows().to_vec(),
35 h_z: h_z.rows().to_vec(),
36 normalized_spec,
37 })
38}
39
40pub fn generalized_bicycle_known_distances(
41 spec: &GeneralizedBicycleSpec,
42) -> Option<(usize, usize)> {
43 (spec.order == 5 && spec.a_exponents == [0, 1] && spec.b_exponents == [0, 2]).then_some((3, 3))
44}
45
46fn normalize_spec(spec: &GeneralizedBicycleSpec) -> Result<GeneralizedBicycleSpec> {
47 if spec.order == 0 {
48 return Err(invalid("order must be nonzero"));
49 }
50 Ok(GeneralizedBicycleSpec {
51 order: spec.order,
52 a_exponents: normalize_exponents("a_exponents", spec.order, &spec.a_exponents)?,
53 b_exponents: normalize_exponents("b_exponents", spec.order, &spec.b_exponents)?,
54 })
55}
56
57fn normalize_exponents(
58 parameter: &'static str,
59 order: usize,
60 exponents: &[usize],
61) -> Result<Vec<usize>> {
62 if exponents.is_empty() {
63 return Err(invalid(format!("{parameter} must not be empty")));
64 }
65
66 let mut normalized = Vec::with_capacity(exponents.len());
67 for &exponent in exponents {
68 if exponent >= order {
69 return Err(invalid(format!(
70 "{parameter} exponent {exponent} is out of range for order {order}"
71 )));
72 }
73 normalized.push(exponent);
74 }
75 normalized.sort_unstable();
76
77 for window in normalized.windows(2) {
78 if window[0] == window[1] {
79 return Err(invalid(format!(
80 "{parameter} contains duplicate exponent {}",
81 window[0]
82 )));
83 }
84 }
85
86 Ok(normalized)
87}
88
89fn cyclic_circulant(order: usize, exponents: &[usize]) -> Result<SparseGf2Matrix> {
90 let mut rows = Vec::with_capacity(order);
91 for row in 0..order {
92 rows.push(
93 exponents
94 .iter()
95 .map(|&exponent| periodic_add(row, exponent, order))
96 .collect(),
97 );
98 }
99 SparseGf2Matrix::new(order, order, rows)
100}
101
102fn periodic_add(value: usize, shift: usize, period: usize) -> usize {
103 if shift == 0 {
104 value
105 } else if value >= period - shift {
106 value - (period - shift)
107 } else {
108 value + shift
109 }
110}
111
112fn invalid(reason: impl Into<String>) -> QecError {
113 QecError::InvalidCssConstruction {
114 construction: GENERALIZED_BICYCLE_CONSTRUCTION_ID.to_owned(),
115 reason: reason.into(),
116 }
117}
118
119#[cfg(test)]
120mod tests {
121 use super::*;
122
123 #[test]
124 fn order5_rows_match_issue_fixture() {
125 let checks = generalized_bicycle_sparse_checks(&GeneralizedBicycleSpec {
126 order: 5,
127 a_exponents: vec![0, 1],
128 b_exponents: vec![0, 2],
129 })
130 .unwrap();
131
132 assert_eq!(checks.num_cols, 10);
133 assert_eq!(
134 checks.h_x,
135 vec![
136 vec![0, 1, 5, 7],
137 vec![1, 2, 6, 8],
138 vec![2, 3, 7, 9],
139 vec![3, 4, 5, 8],
140 vec![0, 4, 6, 9],
141 ]
142 );
143 assert_eq!(
144 checks.h_z,
145 vec![
146 vec![0, 3, 5, 9],
147 vec![1, 4, 5, 6],
148 vec![0, 2, 6, 7],
149 vec![1, 3, 7, 8],
150 vec![2, 4, 8, 9],
151 ]
152 );
153 }
154}