1use crate::binary_chain_complex::{BinaryBoundaryMap, BinaryChainComplex};
2use crate::error::{QecError, Result};
3use crate::sparse_gf2::SparseGf2Matrix;
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
6pub struct Toric3dSpec {
7 pub lx: usize,
8 pub ly: usize,
9 pub lz: usize,
10}
11
12#[derive(Debug, Clone, PartialEq, Eq)]
13pub struct Toric3dCssChecks {
14 pub num_cols: usize,
15 pub hx: Vec<Vec<usize>>,
16 pub hz: Vec<Vec<usize>>,
17 pub distances: Toric3dDistances,
18}
19
20#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21pub struct Toric3dDistances {
22 pub d_x: usize,
23 pub d_z: usize,
24 pub distance: usize,
25}
26
27#[derive(Debug, Clone, Copy)]
28struct Toric3dDimensions {
29 spec: Toric3dSpec,
30 volume: usize,
31 num_edges: usize,
32 num_plaquettes: usize,
33}
34
35impl Toric3dDimensions {
36 fn new(spec: Toric3dSpec) -> Result<Self> {
37 validate_period("lx", spec.lx)?;
38 validate_period("ly", spec.ly)?;
39 validate_period("lz", spec.lz)?;
40 let xy = checked_mul(spec.lx, spec.ly)?;
41 let volume = checked_mul(xy, spec.lz)?;
42 let num_edges = checked_mul(3, volume)?;
43 let num_plaquettes = checked_mul(3, volume)?;
44 Ok(Self {
45 spec,
46 volume,
47 num_edges,
48 num_plaquettes,
49 })
50 }
51
52 fn cell(&self, x: usize, y: usize, z: usize) -> Result<usize> {
53 let xy = checked_add(checked_mul(x, self.spec.ly)?, y)?;
54 checked_add(checked_mul(xy, self.spec.lz)?, z)
55 }
56
57 fn x_edge(&self, x: usize, y: usize, z: usize) -> Result<usize> {
58 self.cell(x, y, z)
59 }
60
61 fn y_edge(&self, x: usize, y: usize, z: usize) -> Result<usize> {
62 checked_add(self.volume, self.cell(x, y, z)?)
63 }
64
65 fn z_edge(&self, x: usize, y: usize, z: usize) -> Result<usize> {
66 checked_add(checked_mul(2, self.volume)?, self.cell(x, y, z)?)
67 }
68}
69
70pub fn toric_3d_chain_complex(spec: Toric3dSpec) -> Result<BinaryChainComplex> {
71 let dims = Toric3dDimensions::new(spec)?;
72 let vertex_rows = vertex_edge_rows(&dims)?;
73 let b1_matrix = SparseGf2Matrix::new(dims.volume, dims.num_edges, vertex_rows)?;
74 let boundary_1 = BinaryBoundaryMap::new(1, 0, b1_matrix)?;
75 let edge_rows = edge_plaquette_rows(&dims)?;
76 let b2_matrix = SparseGf2Matrix::new(dims.num_edges, dims.num_plaquettes, edge_rows)?;
77 let boundary_2 = BinaryBoundaryMap::new(2, 1, b2_matrix)?;
78 BinaryChainComplex::new(vec![boundary_1, boundary_2])
79}
80
81pub fn toric_3d_css_checks(spec: Toric3dSpec) -> Result<Toric3dCssChecks> {
82 let dims = Toric3dDimensions::new(spec)?;
83 let complex = toric_3d_chain_complex(spec)?;
84 let css = complex.css_view(1)?;
85 Ok(Toric3dCssChecks {
86 num_cols: css.num_qubits(),
87 hx: css.hx().rows().to_vec(),
88 hz: css.hz().rows().to_vec(),
89 distances: analytic_distances(&dims)?,
90 })
91}
92
93fn validate_period(parameter: &str, value: usize) -> Result<()> {
94 if value < 3 {
95 return Err(QecError::OutOfRangeBuiltInCssIntegerParameter {
96 family: "toric_3d".to_owned(),
97 parameter: parameter.to_owned(),
98 value,
99 });
100 }
101 Ok(())
102}
103
104fn checked_mul(left: usize, right: usize) -> Result<usize> {
105 left.checked_mul(right)
106 .ok_or_else(toric_3d_dimension_overflow)
107}
108
109fn checked_add(left: usize, right: usize) -> Result<usize> {
110 left.checked_add(right)
111 .ok_or_else(toric_3d_dimension_overflow)
112}
113
114fn toric_3d_dimension_overflow() -> QecError {
115 QecError::SparseGf2DimensionOverflow {
116 operation: "toric_3d",
117 }
118}
119
120fn row_buffer(capacity: usize) -> Result<Vec<Vec<usize>>> {
121 let mut rows = Vec::new();
122 rows.try_reserve_exact(capacity)
123 .map_err(|_| toric_3d_dimension_overflow())?;
124 Ok(rows)
125}
126
127fn empty_rows(len: usize) -> Result<Vec<Vec<usize>>> {
128 let mut rows = row_buffer(len)?;
129 rows.resize_with(len, Vec::new);
130 Ok(rows)
131}
132
133fn vertex_edge_rows(dims: &Toric3dDimensions) -> Result<Vec<Vec<usize>>> {
134 let mut rows = row_buffer(dims.volume)?;
135 for x in 0..dims.spec.lx {
136 let previous_x = previous_coordinate(x, dims.spec.lx);
137 for y in 0..dims.spec.ly {
138 let previous_y = previous_coordinate(y, dims.spec.ly);
139 for z in 0..dims.spec.lz {
140 let previous_z = previous_coordinate(z, dims.spec.lz);
141 rows.push(vec![
142 dims.x_edge(x, y, z)?,
143 dims.x_edge(previous_x, y, z)?,
144 dims.y_edge(x, y, z)?,
145 dims.y_edge(x, previous_y, z)?,
146 dims.z_edge(x, y, z)?,
147 dims.z_edge(x, y, previous_z)?,
148 ]);
149 }
150 }
151 }
152 Ok(rows)
153}
154
155fn edge_plaquette_rows(dims: &Toric3dDimensions) -> Result<Vec<Vec<usize>>> {
156 let mut rows = empty_rows(dims.num_edges)?;
157 let mut plaquette = 0;
158
159 for x in 0..dims.spec.lx {
160 let next_x = next_coordinate(x, dims.spec.lx);
161 for y in 0..dims.spec.ly {
162 let next_y = next_coordinate(y, dims.spec.ly);
163 for z in 0..dims.spec.lz {
164 for edge in [
165 dims.x_edge(x, y, z)?,
166 dims.x_edge(x, next_y, z)?,
167 dims.y_edge(x, y, z)?,
168 dims.y_edge(next_x, y, z)?,
169 ] {
170 rows[edge].push(plaquette);
171 }
172 plaquette = checked_add(plaquette, 1)?;
173 }
174 }
175 }
176
177 for x in 0..dims.spec.lx {
178 let next_x = next_coordinate(x, dims.spec.lx);
179 for y in 0..dims.spec.ly {
180 for z in 0..dims.spec.lz {
181 let next_z = next_coordinate(z, dims.spec.lz);
182 for edge in [
183 dims.x_edge(x, y, z)?,
184 dims.x_edge(x, y, next_z)?,
185 dims.z_edge(x, y, z)?,
186 dims.z_edge(next_x, y, z)?,
187 ] {
188 rows[edge].push(plaquette);
189 }
190 plaquette = checked_add(plaquette, 1)?;
191 }
192 }
193 }
194
195 for x in 0..dims.spec.lx {
196 for y in 0..dims.spec.ly {
197 let next_y = next_coordinate(y, dims.spec.ly);
198 for z in 0..dims.spec.lz {
199 let next_z = next_coordinate(z, dims.spec.lz);
200 for edge in [
201 dims.y_edge(x, y, z)?,
202 dims.y_edge(x, y, next_z)?,
203 dims.z_edge(x, y, z)?,
204 dims.z_edge(x, next_y, z)?,
205 ] {
206 rows[edge].push(plaquette);
207 }
208 plaquette = checked_add(plaquette, 1)?;
209 }
210 }
211 }
212
213 Ok(rows)
214}
215
216fn previous_coordinate(coordinate: usize, period: usize) -> usize {
217 if coordinate == 0 {
218 period - 1
219 } else {
220 coordinate - 1
221 }
222}
223
224fn next_coordinate(coordinate: usize, period: usize) -> usize {
225 if coordinate == period - 1 {
226 0
227 } else {
228 coordinate + 1
229 }
230}
231
232fn analytic_distances(dims: &Toric3dDimensions) -> Result<Toric3dDistances> {
233 let d_x = checked_mul(dims.spec.lx, dims.spec.ly)?
234 .min(checked_mul(dims.spec.lx, dims.spec.lz)?)
235 .min(checked_mul(dims.spec.ly, dims.spec.lz)?);
236 let d_z = dims.spec.lx.min(dims.spec.ly).min(dims.spec.lz);
237 Ok(Toric3dDistances {
238 d_x,
239 d_z,
240 distance: d_x.min(d_z),
241 })
242}
243
244#[cfg(test)]
245mod tests {
246 use super::*;
247
248 #[test]
249 fn corrupt_boundary_composition_is_rejected() {
250 let dims = Toric3dDimensions::new(Toric3dSpec {
251 lx: 3,
252 ly: 3,
253 lz: 3,
254 })
255 .unwrap();
256 let boundary_1 = BinaryBoundaryMap::new(
257 1,
258 0,
259 SparseGf2Matrix::new(
260 dims.volume,
261 dims.num_edges,
262 vertex_edge_rows(&dims).unwrap(),
263 )
264 .unwrap(),
265 )
266 .unwrap();
267 let mut rows = edge_plaquette_rows(&dims).unwrap();
268 rows[0].remove(0);
269 let boundary_2 = BinaryBoundaryMap::new(
270 2,
271 1,
272 SparseGf2Matrix::new(dims.num_edges, dims.num_plaquettes, rows).unwrap(),
273 )
274 .unwrap();
275
276 assert!(matches!(
277 BinaryChainComplex::new(vec![boundary_1, boundary_2]),
278 Err(QecError::NonzeroBoundaryComposition {
279 lower_dimension: 1,
280 upper_dimension: 2,
281 ..
282 })
283 ));
284 }
285}