Skip to main content

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> {}