1#[cfg(feature = "alloc")]
13extern crate alloc;
14
15use crate::aligned::{AlignedBuffer, ALIGN};
16use crate::field::tables::{EXP, LOG};
17use crate::kernel;
18
19#[cfg(feature = "alloc")]
25pub struct GfMatrix {
26 pub(crate) rows: usize,
27 pub(crate) cols: usize,
28 pub(crate) stride: usize,
30 pub(crate) data: AlignedBuffer,
32}
33
34#[cfg(feature = "alloc")]
35impl GfMatrix {
36 #[inline]
38 pub fn padded_stride(cols: usize) -> usize {
39 (cols + ALIGN - 1) & !(ALIGN - 1)
40 }
41
42 pub fn zeros(rows: usize, cols: usize) -> Self {
44 let stride = Self::padded_stride(cols);
45 GfMatrix {
46 rows,
47 cols,
48 stride,
49 data: AlignedBuffer::zeroed(rows * stride),
50 }
51 }
52
53 pub fn identity(k: usize) -> Self {
55 let mut m = Self::zeros(k, k);
56 for i in 0..k {
57 m.data.as_mut_slice()[i * m.stride + i] = 1;
58 }
59 m
60 }
61
62 pub fn rows(&self) -> usize {
64 self.rows
65 }
66 pub fn cols(&self) -> usize {
68 self.cols
69 }
70 pub fn stride(&self) -> usize {
72 self.stride
73 }
74
75 #[inline]
77 pub fn row(&self, r: usize) -> &[u8] {
78 let off = r * self.stride;
79 &self.data.as_slice()[off..off + self.cols]
80 }
81
82 #[inline]
84 pub fn row_mut(&mut self, r: usize) -> &mut [u8] {
85 let off = r * self.stride;
86 let cols = self.cols;
87 &mut self.data.as_mut_slice()[off..off + cols]
88 }
89
90 pub fn row_axpy(&mut self, dst: usize, c: u8, src: usize) {
96 assert_ne!(dst, src, "GfMatrix::row_axpy: dst and src must differ");
97 let stride = self.stride;
98 let cols = self.cols;
99 let (dst_off, src_off) = (dst * stride, src * stride);
100
101 let (src_slice, dst_slice) = if dst > src {
103 let (lo, hi) = self.data.as_mut_slice().split_at_mut(dst_off);
104 (&lo[src_off..src_off + cols], &mut hi[..cols])
105 } else {
106 let (lo, hi) = self.data.as_mut_slice().split_at_mut(src_off);
107 (&hi[..cols], &mut lo[dst_off..dst_off + cols])
108 };
109
110 kernel::axpy(c, src_slice, dst_slice);
111 }
112
113 pub fn row_scale(&mut self, r: usize, c: u8) {
115 let stride = self.stride;
116 let cols = self.cols;
117 let off = r * stride;
118 kernel::scale_inplace(c, &mut self.data.as_mut_slice()[off..off + cols]);
119 }
120
121 pub fn swap_rows(&mut self, a: usize, b: usize) {
123 if a == b {
124 return;
125 }
126 let stride = self.stride;
127 let (a_off, b_off) = (a * stride, b * stride);
128 let data = self.data.as_mut_slice();
129 if a < b {
130 let (lo, hi) = data.split_at_mut(b_off);
131 lo[a_off..a_off + stride].swap_with_slice(&mut hi[..stride]);
132 } else {
133 let (lo, hi) = data.split_at_mut(a_off);
134 lo[b_off..b_off + stride].swap_with_slice(&mut hi[..stride]);
135 }
136 }
137
138 pub fn gaussian_elimination(&mut self) -> usize {
140 let rows = self.rows;
141 let cols = self.cols;
142 let mut pivot_row = 0usize;
143
144 for col in 0..cols {
145 let found =
146 (pivot_row..rows).find(|&r| self.data.as_slice()[r * self.stride + col] != 0);
147 let Some(r) = found else { continue };
148
149 if r != pivot_row {
150 self.swap_rows(r, pivot_row);
151 }
152
153 let pivot_val = self.data.as_slice()[pivot_row * self.stride + col];
154 if pivot_val != 1 {
155 let inv = EXP[255 - LOG[pivot_val as usize] as usize];
156 self.row_scale(pivot_row, inv);
157 }
158
159 for r2 in 0..rows {
160 if r2 == pivot_row {
161 continue;
162 }
163 let coeff = self.data.as_slice()[r2 * self.stride + col];
164 if coeff != 0 {
165 self.row_axpy(r2, coeff, pivot_row);
166 }
167 }
168
169 pivot_row += 1;
170 }
171
172 pivot_row
173 }
174
175 pub fn is_full_rank(&self) -> bool {
177 for r in 0..self.rows {
178 let off = r * self.stride;
179 if self.data.as_slice()[off..off + self.cols]
180 .iter()
181 .all(|&b| b == 0)
182 {
183 return false;
184 }
185 }
186 true
187 }
188}
189
190#[cfg(test)]
191#[cfg(feature = "alloc")]
192mod tests {
193 use super::*;
194
195 #[test]
196 fn rows_are_aligned() {
197 let m = GfMatrix::zeros(8, 100);
198 for r in 0..8 {
199 let ptr = m.row(r).as_ptr() as usize;
200 assert_eq!(ptr % ALIGN, 0, "row {r} not {ALIGN}-byte aligned");
201 }
202 }
203
204 #[test]
205 fn identity_rref_unchanged() {
206 let mut m = GfMatrix::identity(4);
207 let rank = m.gaussian_elimination();
208 assert_eq!(rank, 4);
209 let expected = GfMatrix::identity(4);
210 for i in 0..4 {
211 assert_eq!(m.row(i), expected.row(i));
212 }
213 }
214
215 #[test]
216 fn rank_of_zero_matrix() {
217 let mut m = GfMatrix::zeros(3, 3);
218 let rank = m.gaussian_elimination();
219 assert_eq!(rank, 0);
220 }
221
222 #[test]
223 fn rank_2x2_invertible() {
224 let mut m = GfMatrix::zeros(2, 2);
225 m.data.as_mut_slice()[0] = 1;
226 m.data.as_mut_slice()[1] = 2;
227 m.data.as_mut_slice()[m.stride] = 3;
228 m.data.as_mut_slice()[m.stride + 1] = 4;
229 let rank = m.gaussian_elimination();
230 assert_eq!(rank, 2);
231 assert_eq!(m.row(0)[0], 1);
232 assert_eq!(m.row(0)[1], 0);
233 assert_eq!(m.row(1)[0], 0);
234 assert_eq!(m.row(1)[1], 1);
235 }
236
237 #[test]
238 fn axpy_row_operation() {
239 use crate::kernel::scalar::gf_mul;
240
241 let mut m = GfMatrix::zeros(2, 4);
242 m.data.as_mut_slice()[0] = 1;
244 m.data.as_mut_slice()[m.stride + 1] = 1;
245 m.row_axpy(1, 0x03, 0);
246 assert_eq!(m.row(1)[0], gf_mul(0x03, 1));
247 assert_eq!(m.row(1)[1], 1);
248 assert_eq!(m.row(1)[2], 0);
249 assert_eq!(m.row(1)[3], 0);
250 }
251
252 #[test]
253 #[should_panic(expected = "dst and src must differ")]
254 fn row_axpy_panics_on_same_row() {
255 let mut m = GfMatrix::zeros(2, 4);
256 m.row_axpy(0, 0x03, 0);
257 }
258
259 #[test]
260 fn row_scale_matches_kernel() {
261 let mut m = GfMatrix::zeros(1, 8);
262 for i in 0..8 {
263 m.data.as_mut_slice()[i] = (i as u8).wrapping_add(1);
264 }
265 let before = m.row(0).to_vec();
266 m.row_scale(0, 0x53);
267 let mut expected = before.clone();
268 crate::kernel::scale_inplace(0x53, &mut expected);
269 assert_eq!(m.row(0), expected.as_slice());
270 }
271}