prav_core/qec_engine.rs
1//! High-level QEC decoder API.
2//!
3//! This module provides [`QecEngine`], a convenient wrapper around [`DecodingState`]
4//! that handles the common decode cycle: reset, load syndromes, decode.
5
6use crate::arena::Arena;
7use crate::decoder::{DecodingState, EdgeCorrection};
8use crate::topology::Topology;
9
10/// High-level quantum error correction decoder engine.
11///
12/// `QecEngine` wraps a [`DecodingState`] and provides a simple interface for
13/// repeated decoding cycles. It automatically handles the sparse reset between
14/// cycles for optimal performance.
15///
16/// # Type Parameters
17///
18/// - `'a` - Lifetime of the backing arena memory
19/// - `T: Topology` - The lattice topology (e.g., [`SquareGrid`](crate::SquareGrid))
20/// - `STRIDE_Y` - Compile-time Y stride (must match grid dimensions)
21///
22/// # Example
23///
24/// ```ignore
25/// use prav_core::{Arena, QecEngine, SquareGrid, EdgeCorrection};
26///
27/// // Setup
28/// let mut buffer = [0u8; 1024 * 1024];
29/// let mut arena = Arena::new(&mut buffer);
30/// let mut engine: QecEngine<SquareGrid, 32> = QecEngine::new(&mut arena, 32, 32, 1);
31///
32/// // Decode cycle
33/// let mut corrections = [EdgeCorrection::default(); 512];
34/// let syndromes: &[u64] = &[/* syndrome data */];
35///
36/// let num_corrections = engine.process_cycle_dense(syndromes, &mut corrections);
37///
38/// // Apply corrections[0..num_corrections] to your physical qubits
39/// ```
40///
41/// # Performance
42///
43/// For repeated decoding, `QecEngine` uses sparse reset internally, which only
44/// resets modified blocks. This is much faster than full reinitialization at
45/// typical error rates.
46pub struct QecEngine<'a, T: Topology, const STRIDE_Y: usize> {
47 /// Internal decoder state.
48 decoder: DecodingState<'a, T, STRIDE_Y>,
49}
50
51impl<'a, T: Topology + Sync, const STRIDE_Y: usize> QecEngine<'a, T, STRIDE_Y> {
52 /// Creates a new QEC engine for the given grid dimensions.
53 ///
54 /// # Arguments
55 ///
56 /// * `arena` - Arena allocator for all internal allocations.
57 /// * `width` - Grid width in nodes.
58 /// * `height` - Grid height in nodes.
59 /// * `depth` - Grid depth (1 for 2D codes, >1 for 3D codes).
60 ///
61 /// # Panics
62 ///
63 /// Panics if `STRIDE_Y` doesn't match the calculated stride for the dimensions.
64 /// The stride is `max(width, height, depth).next_power_of_two()`.
65 pub fn new(arena: &mut Arena<'a>, width: usize, height: usize, depth: usize) -> Self {
66 Self {
67 decoder: DecodingState::new(arena, width, height, depth),
68 }
69 }
70
71 /// Processes a complete decoding cycle with dense syndrome input.
72 ///
73 /// This is the main entry point for decoding. It performs:
74 /// 1. Sparse reset of modified state from previous cycle
75 /// 2. Syndrome loading from dense bitarray
76 /// 3. Cluster growth
77 /// 4. Correction extraction via peeling
78 ///
79 /// # Arguments
80 ///
81 /// * `dense_defects` - Syndrome measurements as dense bitarray. Each u64
82 /// represents 64 nodes, where bit `i` indicates node `(blk * 64 + i)` has
83 /// a syndrome.
84 /// * `out_corrections` - Output buffer for edge corrections. Should be sized
85 /// to hold the maximum expected corrections (typically `num_nodes / 2`).
86 ///
87 /// # Returns
88 ///
89 /// Number of corrections written to `out_corrections`.
90 ///
91 /// # Example
92 ///
93 /// ```ignore
94 /// let syndromes = generate_syndromes(error_rate);
95 /// let mut corrections = [EdgeCorrection::default(); 1024];
96 ///
97 /// let count = engine.process_cycle_dense(&syndromes, &mut corrections);
98 /// for i in 0..count {
99 /// apply_correction(corrections[i]);
100 /// }
101 /// ```
102 pub fn process_cycle_dense(
103 &mut self,
104 dense_defects: &[u64],
105 out_corrections: &mut [EdgeCorrection],
106 ) -> usize {
107 self.decoder.sparse_reset();
108
109 self.decoder.load_dense_syndromes(dense_defects);
110
111 self.decoder.decode(out_corrections)
112 }
113
114 /// Loads erasure information for the next decoding cycle.
115 ///
116 /// Erasures indicate qubits that were lost (e.g., photon loss in optical
117 /// systems). Erased qubits are excluded from cluster growth.
118 ///
119 /// # Arguments
120 ///
121 /// * `erasures` - Dense bitarray where bit `i` in `erasures[blk]` indicates
122 /// node `(blk * 64 + i)` is erased.
123 ///
124 /// # Note
125 ///
126 /// Call this before `process_cycle_dense` if your system has erasures.
127 /// The erasure mask persists until changed.
128 pub fn load_erasures(&mut self, erasures: &[u64]) {
129 self.decoder.load_erasures(erasures);
130 }
131}