qec_code/
regular_classical.rs1#![doc = include_str!("../doc/regular_classical.md")]
2
3use crate::error::{QecError, Result};
4
5pub const REGULAR_CLASSICAL_MATRIX_ALGORITHM_V1: u32 = 1;
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub struct RegularClassicalMatrixConfig {
9 pub column_count: usize,
10 pub row_count: usize,
11 pub column_weight: usize,
12 pub row_weight: usize,
13 pub seed: u64,
14 pub algorithm_version: u32,
15 pub retry_limit: usize,
16}
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub struct SplitMix64V1 {
20 state: u64,
21}
22
23impl SplitMix64V1 {
24 pub fn new(seed: u64) -> Self {
25 Self { state: seed }
26 }
27
28 pub fn state(&self) -> u64 {
29 self.state
30 }
31
32 pub fn next_u64(&mut self) -> u64 {
33 self.state = self.state.wrapping_add(0x9E3779B97F4A7C15);
34 let mut value = self.state;
35 value = (value ^ (value >> 30)).wrapping_mul(0xBF58476D1CE4E5B9);
36 value = (value ^ (value >> 27)).wrapping_mul(0x94D049BB133111EB);
37 value ^ (value >> 31)
38 }
39}
40
41pub fn bounded_index_v1(stream: &mut SplitMix64V1, upper_bound: u64) -> Option<u64> {
42 if upper_bound == 0 {
43 return None;
44 }
45
46 let threshold = 0u64.wrapping_sub(upper_bound) % upper_bound;
47 loop {
48 let value = stream.next_u64();
49 if value >= threshold {
50 return Some(value % upper_bound);
51 }
52 }
53}
54
55pub fn deterministic_regular_matrix(
56 config: RegularClassicalMatrixConfig,
57) -> Result<Vec<Vec<usize>>> {
58 validate_config(config)?;
59
60 let mut stream = SplitMix64V1::new(config.seed);
61 for attempt in 1..=config.retry_limit {
62 if let Some(rows) = try_regular_matrix_attempt(config, &mut stream)? {
63 return Ok(rows);
64 }
65 if attempt == config.retry_limit {
66 return Err(QecError::RegularClassicalMatrixGenerationExhausted {
67 retry_limit: config.retry_limit,
68 attempts: attempt,
69 algorithm_version: config.algorithm_version,
70 seed: config.seed,
71 });
72 }
73 }
74
75 unreachable!("regular matrix retry_limit is validated to be nonzero")
76}
77
78fn validate_config(config: RegularClassicalMatrixConfig) -> Result<()> {
79 if config.algorithm_version != REGULAR_CLASSICAL_MATRIX_ALGORITHM_V1 {
80 return Err(QecError::UnsupportedRegularClassicalMatrixAlgorithm {
81 algorithm_version: config.algorithm_version,
82 });
83 }
84
85 validate_nonzero(config.column_count, "column_count")?;
86 validate_nonzero(config.row_count, "row_count")?;
87 validate_nonzero(config.column_weight, "column_weight")?;
88 validate_nonzero(config.row_weight, "row_weight")?;
89 validate_nonzero(config.retry_limit, "retry_limit")?;
90
91 if config.column_weight > config.row_count {
92 return Err(QecError::InvalidRegularClassicalMatrixConfig {
93 option: "column_weight",
94 reason: "must be at most row_count".to_owned(),
95 });
96 }
97 if config.row_weight > config.column_count {
98 return Err(QecError::InvalidRegularClassicalMatrixConfig {
99 option: "row_weight",
100 reason: "must be at most column_count".to_owned(),
101 });
102 }
103
104 let column_stubs = config
105 .column_count
106 .checked_mul(config.column_weight)
107 .ok_or(QecError::RegularClassicalMatrixStubCountOverflow { side: "column" })?;
108 let row_stubs = config
109 .row_count
110 .checked_mul(config.row_weight)
111 .ok_or(QecError::RegularClassicalMatrixStubCountOverflow { side: "row" })?;
112
113 if column_stubs != row_stubs {
114 return Err(QecError::RegularClassicalMatrixStubCountMismatch {
115 column_stubs,
116 row_stubs,
117 });
118 }
119
120 Ok(())
121}
122
123fn validate_nonzero(value: usize, option: &'static str) -> Result<()> {
124 if value == 0 {
125 return Err(QecError::InvalidRegularClassicalMatrixConfig {
126 option,
127 reason: "must be greater than zero".to_owned(),
128 });
129 }
130 Ok(())
131}
132
133fn try_regular_matrix_attempt(
134 config: RegularClassicalMatrixConfig,
135 stream: &mut SplitMix64V1,
136) -> Result<Option<Vec<Vec<usize>>>> {
137 let row_stub_count = config
138 .row_count
139 .checked_mul(config.row_weight)
140 .expect("regular matrix config was validated before sampling");
141 let mut row_slots = Vec::with_capacity(row_stub_count);
142 for row in 0..config.row_count {
143 row_slots.extend(std::iter::repeat_n(row, config.row_weight));
144 }
145
146 let mut rows = vec![Vec::with_capacity(config.row_weight); config.row_count];
147 for column in 0..config.column_count {
148 let mut selected_rows = Vec::with_capacity(config.column_weight);
149 for _ in 0..config.column_weight {
150 let valid_slot_count = count_non_duplicate_slots(&row_slots, &selected_rows);
151 if valid_slot_count == 0 {
152 return Ok(None);
153 }
154
155 let selected_rank = bounded_index_v1(stream, valid_slot_count as u64)
156 .expect("valid_slot_count is checked to be nonzero");
157 let slot_index = slot_index_for_rank(&row_slots, &selected_rows, selected_rank)
158 .expect("selected rank should identify one remaining slot");
159 let row = row_slots.remove(slot_index);
160 selected_rows.push(row);
161 rows[row].push(column);
162 }
163 }
164
165 for row in &mut rows {
166 row.sort_unstable();
167 }
168 rows.sort_unstable();
169 Ok(Some(rows))
170}
171
172fn count_non_duplicate_slots(row_slots: &[usize], selected_rows: &[usize]) -> usize {
173 row_slots
174 .iter()
175 .filter(|row| !selected_rows.contains(row))
176 .count()
177}
178
179fn slot_index_for_rank(
180 row_slots: &[usize],
181 selected_rows: &[usize],
182 selected_rank: u64,
183) -> Option<usize> {
184 let mut valid_rank = 0u64;
185 for (slot_index, row) in row_slots.iter().enumerate() {
186 if selected_rows.contains(row) {
187 continue;
188 }
189 if valid_rank == selected_rank {
190 return Some(slot_index);
191 }
192 valid_rank += 1;
193 }
194 None
195}