1use crate::error::{QecError, Result};
2use crate::finite_group::{
3 FiniteGroupSpec, GroupAlgebraElement, left_regular_lift, right_regular_lift,
4};
5
6pub const MAX_LIFTED_PRODUCT_RING_CELLS: usize = 1_000_000;
10
11pub const MAX_LIFTED_PRODUCT_BINARY_CELLS: usize = 10_000_000;
18
19pub const MAX_LIFTED_PRODUCT_ORTHOGONALITY_ROW_PAIRS: usize = 10_000_000;
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24pub struct LiftedProductRingShape {
25 pub h_x_rows: usize,
26 pub h_z_rows: usize,
27 pub num_cols: usize,
28}
29
30#[derive(Debug, Clone, PartialEq, Eq)]
36pub struct LiftedProductRingChecks {
37 pub shape: LiftedProductRingShape,
38 pub h_x: Vec<Vec<GroupAlgebraElement>>,
39 pub h_z: Vec<Vec<GroupAlgebraElement>>,
40}
41
42#[derive(Debug, Clone, PartialEq, Eq)]
44pub struct LiftedProductBinaryChecks {
45 pub num_cols: usize,
46 pub h_x: Vec<Vec<usize>>,
47 pub h_z: Vec<Vec<usize>>,
48}
49
50pub fn checked_lifted_product_ring_shape(
53 left_rows: usize,
54 left_cols: usize,
55 right_rows: usize,
56 right_cols: usize,
57) -> Result<LiftedProductRingShape> {
58 let h_x_rows = left_rows.checked_mul(right_cols).ok_or(ring_overflow())?;
59 let h_z_rows = left_cols.checked_mul(right_rows).ok_or(ring_overflow())?;
60 let left_block_cols = left_cols.checked_mul(right_cols).ok_or(ring_overflow())?;
61 let right_block_cols = left_rows.checked_mul(right_rows).ok_or(ring_overflow())?;
62 let num_cols = left_block_cols
63 .checked_add(right_block_cols)
64 .ok_or(ring_overflow())?;
65 check_ring_cell_limit(h_x_rows, num_cols)?;
66 check_ring_cell_limit(h_z_rows, num_cols)?;
67 Ok(LiftedProductRingShape {
68 h_x_rows,
69 h_z_rows,
70 num_cols,
71 })
72}
73
74pub fn checked_lifted_product_binary_shape(
76 group: &FiniteGroupSpec,
77 left_rows: usize,
78 left_cols: usize,
79 right_rows: usize,
80 right_cols: usize,
81) -> Result<LiftedProductRingShape> {
82 let shape = checked_lifted_product_ring_shape(left_rows, left_cols, right_rows, right_cols)?;
83 let binary_h_x_rows = checked_binary_dimension(shape.h_x_rows, group.order())?;
84 let binary_h_z_rows = checked_binary_dimension(shape.h_z_rows, group.order())?;
85 let binary_cols = checked_binary_dimension(shape.num_cols, group.order())?;
86 check_binary_cell_limit("H_X", binary_h_x_rows, binary_cols)?;
87 check_binary_cell_limit("H_Z", binary_h_z_rows, binary_cols)?;
88 check_binary_row_pair_limit(binary_h_x_rows, binary_h_z_rows)?;
89 Ok(shape)
90}
91
92pub fn lifted_product_ring_checks(
94 group: &FiniteGroupSpec,
95 left: &[Vec<GroupAlgebraElement>],
96 right: &[Vec<GroupAlgebraElement>],
97) -> Result<LiftedProductRingChecks> {
98 let (left_rows, left_cols) = group_algebra_matrix_shape(left)?;
99 let (right_rows, right_cols) = group_algebra_matrix_shape(right)?;
100 validate_group_orders(group, left)?;
101 validate_group_orders(group, right)?;
102 let shape = checked_lifted_product_ring_shape(left_rows, left_cols, right_rows, right_cols)?;
103
104 let h_x = hconcat(
105 &matrix_kron_identity(group, left, right_cols)?,
106 &identity_kron_matrix(group, left_rows, &inverse_transpose(group, right)?)?,
107 )?;
108 let h_z = hconcat(
109 &identity_kron_matrix(group, left_cols, right)?,
110 &matrix_kron_identity(group, &inverse_transpose(group, left)?, right_rows)?,
111 )?;
112 debug_assert_eq!(h_x.len(), shape.h_x_rows);
113 debug_assert_eq!(h_z.len(), shape.h_z_rows);
114 debug_assert!(h_x.iter().all(|row| row.len() == shape.num_cols));
115 debug_assert!(h_z.iter().all(|row| row.len() == shape.num_cols));
116 Ok(LiftedProductRingChecks { shape, h_x, h_z })
117}
118
119pub fn lifted_product_binary_checks(
137 group: &FiniteGroupSpec,
138 left: &[Vec<GroupAlgebraElement>],
139 right: &[Vec<GroupAlgebraElement>],
140) -> Result<LiftedProductBinaryChecks> {
141 let (left_rows, left_cols) = group_algebra_matrix_shape(left)?;
142 let (right_rows, right_cols) = group_algebra_matrix_shape(right)?;
143 validate_group_orders(group, left)?;
144 validate_group_orders(group, right)?;
145 let shape =
146 checked_lifted_product_binary_shape(group, left_rows, left_cols, right_rows, right_cols)?;
147
148 let h_x_left = matrix_kron_identity(group, left, right_cols)?;
149 let h_x_right = identity_kron_matrix(group, left_rows, &transpose_without_inversion(right)?)?;
150 let h_z_left = identity_kron_matrix(group, left_cols, &invert_entries(group, right)?)?;
151 let h_z_right = matrix_kron_identity(group, &inverse_transpose(group, left)?, right_rows)?;
152
153 let h_x =
154 left_regular_lift(group, &h_x_left)?.hconcat(&right_regular_lift(group, &h_x_right)?)?;
155 let h_z =
156 right_regular_lift(group, &h_z_left)?.hconcat(&left_regular_lift(group, &h_z_right)?)?;
157 debug_assert_eq!(h_x.num_cols(), h_z.num_cols());
158 debug_assert_eq!(h_x.num_rows(), shape.h_x_rows * group.order());
159 debug_assert_eq!(h_z.num_rows(), shape.h_z_rows * group.order());
160 Ok(LiftedProductBinaryChecks {
161 num_cols: h_x.num_cols(),
162 h_x: h_x.rows().to_vec(),
163 h_z: h_z.rows().to_vec(),
164 })
165}
166
167fn ring_overflow() -> QecError {
168 QecError::GroupAlgebraDimensionOverflow {
169 operation: "lifted product ring shape",
170 }
171}
172
173fn binary_overflow() -> QecError {
174 QecError::GroupAlgebraDimensionOverflow {
175 operation: "lifted product binary shape",
176 }
177}
178
179fn checked_binary_dimension(value: usize, group_order: usize) -> Result<usize> {
180 value.checked_mul(group_order).ok_or(binary_overflow())
181}
182
183fn check_ring_cell_limit(rows: usize, num_cols: usize) -> Result<()> {
184 let cell_count = rows.checked_mul(num_cols).ok_or(ring_overflow())?;
185 if cell_count > MAX_LIFTED_PRODUCT_RING_CELLS {
186 return Err(QecError::InvalidCssConstruction {
187 construction: "lifted_product".to_owned(),
188 reason: format!(
189 "ring cell count {cell_count} exceeds maximum supported {MAX_LIFTED_PRODUCT_RING_CELLS}"
190 ),
191 });
192 }
193 Ok(())
194}
195
196fn check_binary_cell_limit(matrix_name: &str, rows: usize, num_cols: usize) -> Result<()> {
197 let cell_count = rows.checked_mul(num_cols).ok_or(binary_overflow())?;
198 if cell_count > MAX_LIFTED_PRODUCT_BINARY_CELLS {
199 return Err(QecError::InvalidCssConstruction {
200 construction: "lifted_product".to_owned(),
201 reason: format!(
202 "binary {matrix_name} cell count {cell_count} exceeds maximum supported {MAX_LIFTED_PRODUCT_BINARY_CELLS}"
203 ),
204 });
205 }
206 Ok(())
207}
208
209fn check_binary_row_pair_limit(h_x_rows: usize, h_z_rows: usize) -> Result<()> {
210 let row_pairs = h_x_rows.checked_mul(h_z_rows).ok_or(binary_overflow())?;
211 if row_pairs > MAX_LIFTED_PRODUCT_ORTHOGONALITY_ROW_PAIRS {
212 return Err(QecError::InvalidCssConstruction {
213 construction: "lifted_product".to_owned(),
214 reason: format!(
215 "binary orthogonality row-pair count {row_pairs} exceeds maximum supported {MAX_LIFTED_PRODUCT_ORTHOGONALITY_ROW_PAIRS}"
216 ),
217 });
218 }
219 Ok(())
220}
221
222fn group_algebra_matrix_shape(matrix: &[Vec<GroupAlgebraElement>]) -> Result<(usize, usize)> {
223 let Some(first_row) = matrix.first() else {
224 return Err(invalid_protograph("must contain at least one row"));
225 };
226 if first_row.is_empty() {
227 return Err(invalid_protograph("must contain at least one column"));
228 }
229 for row in matrix {
230 if row.len() != first_row.len() {
231 return Err(QecError::GroupAlgebraMatrixRowWidthMismatch {
232 expected: first_row.len(),
233 actual: row.len(),
234 });
235 }
236 }
237 Ok((matrix.len(), first_row.len()))
238}
239
240fn validate_group_orders(
241 group: &FiniteGroupSpec,
242 matrix: &[Vec<GroupAlgebraElement>],
243) -> Result<()> {
244 for row in matrix {
245 for element in row {
246 if element.group_order() != group.order() {
247 return Err(QecError::GroupAlgebraOrderMismatch {
248 expected: group.order(),
249 actual: element.group_order(),
250 });
251 }
252 }
253 }
254 Ok(())
255}
256
257fn invalid_protograph(reason: &str) -> QecError {
258 QecError::InvalidCssConstruction {
259 construction: "lifted_product".to_owned(),
260 reason: reason.to_owned(),
261 }
262}
263
264fn inverse_transpose(
265 group: &FiniteGroupSpec,
266 matrix: &[Vec<GroupAlgebraElement>],
267) -> Result<Vec<Vec<GroupAlgebraElement>>> {
268 let rows = matrix.len();
269 let cols = matrix[0].len();
270 let mut output = Vec::with_capacity(cols);
271 for col in 0..cols {
272 let mut row = Vec::with_capacity(rows);
273 for source_row in matrix {
274 let support = source_row[col]
275 .support()
276 .iter()
277 .map(|&element| group.inverse(element))
278 .collect::<Result<Vec<_>>>()?;
279 row.push(GroupAlgebraElement::new(group, support)?);
280 }
281 output.push(row);
282 }
283 Ok(output)
284}
285
286fn transpose_without_inversion(
287 matrix: &[Vec<GroupAlgebraElement>],
288) -> Result<Vec<Vec<GroupAlgebraElement>>> {
289 let rows = matrix.len();
290 let cols = matrix[0].len();
291 let mut output = Vec::with_capacity(cols);
292 for col in 0..cols {
293 let mut row = Vec::with_capacity(rows);
294 for source_row in matrix {
295 row.push(source_row[col].clone());
296 }
297 output.push(row);
298 }
299 Ok(output)
300}
301
302fn invert_entries(
303 group: &FiniteGroupSpec,
304 matrix: &[Vec<GroupAlgebraElement>],
305) -> Result<Vec<Vec<GroupAlgebraElement>>> {
306 matrix
307 .iter()
308 .map(|row| {
309 row.iter()
310 .map(|entry| {
311 let support = entry
312 .support()
313 .iter()
314 .map(|&element| group.inverse(element))
315 .collect::<Result<Vec<_>>>()?;
316 GroupAlgebraElement::new(group, support)
317 })
318 .collect()
319 })
320 .collect()
321}
322
323fn matrix_kron_identity(
324 group: &FiniteGroupSpec,
325 matrix: &[Vec<GroupAlgebraElement>],
326 identity_size: usize,
327) -> Result<Vec<Vec<GroupAlgebraElement>>> {
328 let zero = GroupAlgebraElement::new(group, Vec::new())?;
329 let mut output = Vec::with_capacity(matrix.len() * identity_size);
330 for source_row in matrix {
331 for diagonal in 0..identity_size {
332 let mut row = Vec::with_capacity(source_row.len() * identity_size);
333 for element in source_row {
334 for column in 0..identity_size {
335 row.push(if column == diagonal {
336 element.clone()
337 } else {
338 zero.clone()
339 });
340 }
341 }
342 output.push(row);
343 }
344 }
345 Ok(output)
346}
347
348fn identity_kron_matrix(
349 group: &FiniteGroupSpec,
350 identity_size: usize,
351 matrix: &[Vec<GroupAlgebraElement>],
352) -> Result<Vec<Vec<GroupAlgebraElement>>> {
353 let zero = GroupAlgebraElement::new(group, Vec::new())?;
354 let matrix_cols = matrix[0].len();
355 let mut output = Vec::with_capacity(identity_size * matrix.len());
356 for diagonal in 0..identity_size {
357 for source_row in matrix {
358 let mut row = Vec::with_capacity(identity_size * matrix_cols);
359 for block in 0..identity_size {
360 if block == diagonal {
361 row.extend(source_row.iter().cloned());
362 } else {
363 row.extend(std::iter::repeat_n(zero.clone(), matrix_cols));
364 }
365 }
366 output.push(row);
367 }
368 }
369 Ok(output)
370}
371
372fn hconcat(
373 left: &[Vec<GroupAlgebraElement>],
374 right: &[Vec<GroupAlgebraElement>],
375) -> Result<Vec<Vec<GroupAlgebraElement>>> {
376 if left.len() != right.len() {
377 return Err(invalid_protograph("internal lifted-product row mismatch"));
378 }
379 Ok(left
380 .iter()
381 .zip(right)
382 .map(|(left_row, right_row)| {
383 let mut row = left_row.clone();
384 row.extend(right_row.iter().cloned());
385 row
386 })
387 .collect())
388}