uor_matmul_codec/matrix.rs
1//! Coded matrices (§6.3).
2
3use core::ops::Range;
4
5use uor_matmul_core::{Alphabet, Bound, Element};
6
7use crate::tier::Codec;
8
9/// A borrowed matrix of codes, together with the codec that decodes them.
10///
11/// The codes are borrowed and the codec's table is borrowed, so a
12/// `CodedMatrix` is a handful of pointers and three integers. Nothing here is
13/// owned, nothing is copied, and nothing is allocated (R7, C1).
14#[derive(Clone, Copy, Debug)]
15pub struct CodedMatrix<'a, E: Element, Bd: Bound, C: Codec<E, Bd>> {
16 codec: C,
17 rows: usize,
18 cols: usize,
19 codes: &'a [C::Code],
20 _marker: core::marker::PhantomData<fn() -> (E, Bd)>,
21}
22
23impl<'a, E: Element, Bd: Bound, C: Codec<E, Bd>> CodedMatrix<'a, E, Bd, C> {
24 /// Borrow `codes` as an `rows x cols` coded matrix.
25 ///
26 /// `None` only when the codes do not describe the declared shape, which
27 /// means no such matrix exists. There is nothing else to validate: the
28 /// codec's table is already `Alphabet<E, Bd>`, so its image is in the
29 /// alphabet by construction (§6.3).
30 ///
31 /// `CK-06`: the codec's own decoded lengths must sum to the declared row
32 /// width, on every row. It is the *codec* that says how long a code is, so
33 /// a variable-length tier needs no special case here and no separate matrix
34 /// type --- which is what makes run coding a tier rather than a second
35 /// algorithm (S5b).
36 pub fn new(codec: C, rows: usize, cols: usize, codes: &'a [C::Code]) -> Option<Self> {
37 if C::MAX_BLOCK == 0 {
38 return None;
39 }
40 // A fixed-width tier's shape is arithmetic, so checking it is too.
41 if C::IS_FIXED_WIDTH {
42 if !cols.is_multiple_of(C::MAX_BLOCK) {
43 return None;
44 }
45 let per_row = cols / C::MAX_BLOCK;
46 if rows.checked_mul(per_row)? != codes.len() {
47 return None;
48 }
49 return Some(Self {
50 codec,
51 rows,
52 cols,
53 codes,
54 _marker: core::marker::PhantomData,
55 });
56 }
57 let mut at = 0usize;
58 for _ in 0..rows {
59 let mut width = 0usize;
60 while width < cols {
61 let code = *codes.get(at)?;
62 let n = codec.decode_len(code);
63 if n == 0 {
64 // A code that produces nothing would make the walk
65 // non-terminating; no such matrix exists.
66 return None;
67 }
68 width = width.checked_add(n)?;
69 at = at.checked_add(1)?;
70 }
71 if width != cols {
72 // The last code of the row overshot it: the codes describe a
73 // different shape from the declared one.
74 return None;
75 }
76 }
77 if at != codes.len() {
78 return None;
79 }
80 Some(Self {
81 codec,
82 rows,
83 cols,
84 codes,
85 _marker: core::marker::PhantomData,
86 })
87 }
88
89 /// Rows.
90 pub const fn rows(&self) -> usize {
91 self.rows
92 }
93
94 /// Decoded elements per row.
95 pub const fn cols(&self) -> usize {
96 self.cols
97 }
98
99 /// The codec.
100 pub const fn codec(&self) -> &C {
101 &self.codec
102 }
103
104 /// Codes per row, for a fixed-width tier.
105 ///
106 /// A variable-length tier has no such constant; use
107 /// [`CodedMatrix::row_code_range`], which walks the codec's own lengths.
108 pub const fn codes_per_row(&self) -> usize {
109 self.cols / C::MAX_BLOCK
110 }
111
112 /// The half-open range of codes belonging to row `r`.
113 ///
114 /// Arithmetic for a fixed-width tier, which is every tier but a run codec.
115 /// That matters more than it looks: for a run codec this walks rows `0..r`,
116 /// so a driver reading one element at a time runs in O(k^2 n) instead of
117 /// O(m k n). [`CodedMatrix::column_walk`] is what a driver uses instead ---
118 /// it carries the cursor from row to row, so a column is one pass over the
119 /// codes whatever the tier.
120 pub fn row_code_range(&self, r: usize) -> Range<usize> {
121 if C::IS_FIXED_WIDTH {
122 let per_row = self.codes_per_row();
123 return r * per_row..(r + 1) * per_row;
124 }
125 // Walk the rows before `r` to find where it starts, then walk `r`
126 // itself to find where it ends. `new` already established that the
127 // lengths sum to `cols` on every row, so neither walk can run off.
128 let mut start = 0usize;
129 for _ in 0..r {
130 let mut width = 0usize;
131 while width < self.cols {
132 width += self.codec.decode_len(self.codes[start]);
133 start += 1;
134 }
135 }
136 let mut end = start;
137 let mut width = 0usize;
138 while width < self.cols {
139 width += self.codec.decode_len(self.codes[end]);
140 end += 1;
141 }
142 start..end
143 }
144
145 /// The codec, for a walker that has to ask it lengths.
146 pub const fn codec_ref(&self) -> &C {
147 &self.codec
148 }
149
150 /// The raw code slice.
151 pub const fn codes(&self) -> &'a [C::Code] {
152 self.codes
153 }
154
155 /// Decode row `r`. `out.len() >= cols`. The caller owns the buffer.
156 ///
157 /// Returns how many elements were written, which `CodedMatrix::new`
158 /// established is exactly `cols` (`CK-06`).
159 pub fn decode_row_into(&self, r: usize, out: &mut [Alphabet<E, Bd>]) -> usize {
160 let range = self.row_code_range(r);
161 self.codec.decode_seq(&self.codes[range], out)
162 }
163
164 /// Streaming decode, for a caller whose buffer is smaller than one row.
165 ///
166 /// This is what makes the library usable on a microcontroller whose RAM
167 /// cannot hold a decoded row, and it is what makes the zero-scratch
168 /// traversal possible (S13).
169 pub fn decode_range_into(&self, r: usize, cols: Range<usize>, out: &mut [Alphabet<E, Bd>]) {
170 for (slot, col) in out.iter_mut().zip(cols) {
171 *slot = self.at(r, col);
172 }
173 }
174
175 /// Column `c`, walked down every row, in one pass over the codes.
176 ///
177 /// [`CodedMatrix::at`] is O(1) for a fixed-width tier and O(row) for a
178 /// variable-length one --- *plus* the [`CodedMatrix::row_code_range`] walk
179 /// over rows `0..r`. A driver that loops `for r in 0..rows { at(r, c) }`
180 /// therefore pays O(rows^2) on a run codec, which is exactly the hazard the
181 /// note on `row_code_range` names, and exactly what the zero-offer coded
182 /// traversal was doing: `O(m n k^2)` where the identity is `O(m n k)`.
183 ///
184 /// This carries the cursor from one row to the next, so a whole column costs
185 /// one pass over the codes whatever the tier. It allocates nothing: the state
186 /// is two indices (R7).
187 pub fn column_walk<'m>(&'m self, c: usize) -> ColumnWalk<'m, 'a, E, Bd, C> {
188 ColumnWalk {
189 m: self,
190 col: c,
191 row: 0,
192 cursor: 0,
193 }
194 }
195
196 /// The element at `(r, c)` of the decoded matrix.
197 ///
198 /// O(1) for a fixed-width tier. For a variable-length one it walks the row,
199 /// because the run boundaries are the data; a caller reading a whole row
200 /// from such a tier should use [`CodedMatrix::decode_row_into`], which walks
201 /// it once instead of once per element.
202 pub fn at(&self, r: usize, c: usize) -> Alphabet<E, Bd> {
203 if C::IS_FIXED_WIDTH {
204 let block = C::MAX_BLOCK;
205 let code = self.codes[r * self.codes_per_row() + c / block];
206 return self.codec.decode_element(code, c % block);
207 }
208 let range = self.row_code_range(r);
209 let mut width = 0usize;
210 for &code in &self.codes[range] {
211 let n = self.codec.decode_len(code);
212 if c < width + n {
213 return self.codec.decode_element(code, c - width);
214 }
215 width += n;
216 }
217 Alphabet::ZERO
218 }
219}
220
221/// One column of a [`CodedMatrix`], walked down the rows.
222///
223/// See [`CodedMatrix::column_walk`] for why this exists rather than a loop over
224/// [`CodedMatrix::at`].
225#[derive(Clone, Copy)]
226pub struct ColumnWalk<'m, 'a, E: Element, Bd: Bound, C: Codec<E, Bd>> {
227 m: &'m CodedMatrix<'a, E, Bd, C>,
228 col: usize,
229 row: usize,
230 cursor: usize,
231}
232
233impl<E: Element, Bd: Bound, C: Codec<E, Bd>> Iterator for ColumnWalk<'_, '_, E, Bd, C> {
234 type Item = Alphabet<E, Bd>;
235
236 fn next(&mut self) -> Option<Self::Item> {
237 if self.row >= self.m.rows() || self.col >= self.m.cols() {
238 return None;
239 }
240 let out = if C::IS_FIXED_WIDTH {
241 // Arithmetic, and the cursor is unused: a fixed-width row starts
242 // where the previous one ended by construction.
243 let block = C::MAX_BLOCK;
244 let code = self.m.codes()[self.row * self.m.codes_per_row() + self.col / block];
245 self.m.codec_ref().decode_element(code, self.col % block)
246 } else {
247 // Walk this row once: capture the element as the run containing it
248 // goes past, and leave the cursor at the row's end for the next call.
249 // `CodedMatrix::new` established that the lengths sum to `cols` on
250 // every row, so this cannot run off (`CK-06`).
251 let mut found = Alphabet::ZERO;
252 let mut width = 0usize;
253 while width < self.m.cols() {
254 let code = self.m.codes()[self.cursor];
255 let n = self.m.codec_ref().decode_len(code);
256 if self.col >= width && self.col < width + n {
257 found = self.m.codec_ref().decode_element(code, self.col - width);
258 }
259 width += n;
260 self.cursor += 1;
261 }
262 found
263 };
264 self.row += 1;
265 Some(out)
266 }
267
268 fn size_hint(&self) -> (usize, Option<usize>) {
269 let left = self.m.rows().saturating_sub(self.row); // R3-ok: a remaining count, not an accumulation
270 (left, Some(left))
271 }
272}
273
274impl<E: Element, Bd: Bound, C: Codec<E, Bd>> ExactSizeIterator for ColumnWalk<'_, '_, E, Bd, C> {}