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.checked_add(ALIGN - 1)
40 .map(|value| value & !(ALIGN - 1))
41 .expect("GfMatrix stride overflow")
42 }
43
44 pub fn zeros(rows: usize, cols: usize) -> Self {
46 let stride = Self::padded_stride(cols);
47 GfMatrix {
48 rows,
49 cols,
50 stride,
51 data: AlignedBuffer::zeroed(rows.checked_mul(stride).expect("GfMatrix size overflow")),
52 }
53 }
54
55 pub fn identity(k: usize) -> Self {
57 let mut m = Self::zeros(k, k);
58 for i in 0..k {
59 m.data.as_mut_slice()[i * m.stride + i] = 1;
60 }
61 m
62 }
63
64 pub fn rows(&self) -> usize {
66 self.rows
67 }
68 pub fn cols(&self) -> usize {
70 self.cols
71 }
72 pub fn stride(&self) -> usize {
74 self.stride
75 }
76
77 #[inline]
79 pub fn row(&self, r: usize) -> &[u8] {
80 let off = r * self.stride;
81 &self.data.as_slice()[off..off + self.cols]
82 }
83
84 #[inline]
86 pub fn row_mut(&mut self, r: usize) -> &mut [u8] {
87 let off = r * self.stride;
88 let cols = self.cols;
89 &mut self.data.as_mut_slice()[off..off + cols]
90 }
91
92 pub fn row_axpy(&mut self, dst: usize, c: u8, src: usize) {
98 self.row_axpy_from(dst, c, src, 0);
99 }
100
101 fn row_axpy_from(&mut self, dst: usize, c: u8, src: usize, start: usize) {
102 assert_ne!(dst, src, "GfMatrix::row_axpy: dst and src must differ");
103 let stride = self.stride;
104 let cols = self.cols;
105 let (dst_off, src_off) = (dst * stride, src * stride);
106
107 let (src_slice, dst_slice) = if dst > src {
109 let (lo, hi) = self.data.as_mut_slice().split_at_mut(dst_off);
110 (&lo[src_off + start..src_off + cols], &mut hi[start..cols])
111 } else {
112 let (lo, hi) = self.data.as_mut_slice().split_at_mut(src_off);
113 (&hi[start..cols], &mut lo[dst_off + start..dst_off + cols])
114 };
115
116 unsafe { kernel::axpy_unchecked(c, src_slice, dst_slice) };
118 }
119
120 pub fn row_scale(&mut self, r: usize, c: u8) {
122 self.row_scale_from(r, c, 0);
123 }
124
125 fn row_scale_from(&mut self, r: usize, c: u8, start: usize) {
126 let stride = self.stride;
127 let cols = self.cols;
128 let off = r * stride;
129 kernel::scale_inplace(c, &mut self.data.as_mut_slice()[off + start..off + cols]);
130 }
131
132 pub fn swap_rows(&mut self, a: usize, b: usize) {
134 if a == b {
135 return;
136 }
137 let stride = self.stride;
138 let (a_off, b_off) = (a * stride, b * stride);
139 let data = self.data.as_mut_slice();
140 if a < b {
141 let (lo, hi) = data.split_at_mut(b_off);
142 lo[a_off..a_off + stride].swap_with_slice(&mut hi[..stride]);
143 } else {
144 let (lo, hi) = data.split_at_mut(a_off);
145 lo[b_off..b_off + stride].swap_with_slice(&mut hi[..stride]);
146 }
147 }
148
149 pub fn gaussian_elimination(&mut self) -> usize {
151 let rows = self.rows;
152 let cols = self.cols;
153 let mut pivot_row = 0usize;
154
155 for col in 0..cols {
156 let found =
157 (pivot_row..rows).find(|&r| self.data.as_slice()[r * self.stride + col] != 0);
158 let Some(r) = found else { continue };
159
160 if r != pivot_row {
161 self.swap_rows(r, pivot_row);
162 }
163
164 let pivot_val = self.data.as_slice()[pivot_row * self.stride + col];
165 let operation_start = col & !(ALIGN - 1);
166 if pivot_val != 1 {
167 let inv = EXP[255 - LOG[pivot_val as usize] as usize];
168 self.row_scale_from(pivot_row, inv, operation_start);
169 }
170
171 for r2 in 0..rows {
172 if r2 == pivot_row {
173 continue;
174 }
175 let coeff = self.data.as_slice()[r2 * self.stride + col];
176 if coeff != 0 {
177 self.row_axpy_from(r2, coeff, pivot_row, operation_start);
178 }
179 }
180
181 pivot_row += 1;
182 }
183
184 pivot_row
185 }
186
187 pub fn is_full_rank(&self) -> bool {
189 for r in 0..self.rows {
190 let off = r * self.stride;
191 if self.data.as_slice()[off..off + self.cols]
192 .iter()
193 .all(|&b| b == 0)
194 {
195 return false;
196 }
197 }
198 true
199 }
200}
201
202#[cfg(test)]
203#[cfg(feature = "alloc")]
204mod tests {
205 use super::*;
206
207 #[test]
208 fn rows_are_aligned() {
209 let m = GfMatrix::zeros(8, 100);
210 for r in 0..8 {
211 let ptr = m.row(r).as_ptr() as usize;
212 assert_eq!(ptr % ALIGN, 0, "row {r} not {ALIGN}-byte aligned");
213 }
214 }
215
216 #[test]
217 #[should_panic(expected = "GfMatrix stride overflow")]
218 fn oversized_columns_panic_before_allocation() {
219 let _ = GfMatrix::zeros(1, usize::MAX);
220 }
221
222 #[test]
223 fn identity_rref_unchanged() {
224 let mut m = GfMatrix::identity(4);
225 let rank = m.gaussian_elimination();
226 assert_eq!(rank, 4);
227 let expected = GfMatrix::identity(4);
228 for i in 0..4 {
229 assert_eq!(m.row(i), expected.row(i));
230 }
231 }
232
233 #[test]
234 fn rank_of_zero_matrix() {
235 let mut m = GfMatrix::zeros(3, 3);
236 let rank = m.gaussian_elimination();
237 assert_eq!(rank, 0);
238 }
239
240 #[test]
241 fn rank_2x2_invertible() {
242 let mut m = GfMatrix::zeros(2, 2);
243 m.data.as_mut_slice()[0] = 1;
244 m.data.as_mut_slice()[1] = 2;
245 m.data.as_mut_slice()[m.stride] = 3;
246 m.data.as_mut_slice()[m.stride + 1] = 4;
247 let rank = m.gaussian_elimination();
248 assert_eq!(rank, 2);
249 assert_eq!(m.row(0)[0], 1);
250 assert_eq!(m.row(0)[1], 0);
251 assert_eq!(m.row(1)[0], 0);
252 assert_eq!(m.row(1)[1], 1);
253 }
254
255 #[test]
256 fn axpy_row_operation() {
257 use crate::kernel::scalar::gf_mul;
258
259 let mut m = GfMatrix::zeros(2, 4);
260 m.data.as_mut_slice()[0] = 1;
262 m.data.as_mut_slice()[m.stride + 1] = 1;
263 m.row_axpy(1, 0x03, 0);
264 assert_eq!(m.row(1)[0], gf_mul(0x03, 1));
265 assert_eq!(m.row(1)[1], 1);
266 assert_eq!(m.row(1)[2], 0);
267 assert_eq!(m.row(1)[3], 0);
268 }
269
270 #[test]
271 #[should_panic(expected = "dst and src must differ")]
272 fn row_axpy_panics_on_same_row() {
273 let mut m = GfMatrix::zeros(2, 4);
274 m.row_axpy(0, 0x03, 0);
275 }
276
277 #[test]
278 fn row_scale_matches_kernel() {
279 let mut m = GfMatrix::zeros(1, 8);
280 for i in 0..8 {
281 m.data.as_mut_slice()[i] = (i as u8).wrapping_add(1);
282 }
283 let before = m.row(0).to_vec();
284 m.row_scale(0, 0x53);
285 let mut expected = before.clone();
286 crate::kernel::scale_inplace(0x53, &mut expected);
287 assert_eq!(m.row(0), expected.as_slice());
288 }
289}