Skip to main content

qec_code/
regular_classical.rs

1#![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}