#[cfg(feature = "alloc")]
extern crate alloc;
use crate::aligned::{AlignedBuffer, ALIGN};
use crate::field::tables::{EXP, LOG};
use crate::kernel;
#[cfg(feature = "alloc")]
pub struct GfMatrix {
pub(crate) rows: usize,
pub(crate) cols: usize,
pub(crate) stride: usize,
pub(crate) data: AlignedBuffer,
}
#[cfg(feature = "alloc")]
impl GfMatrix {
#[inline]
pub fn padded_stride(cols: usize) -> usize {
cols.checked_add(ALIGN - 1)
.map(|value| value & !(ALIGN - 1))
.expect("GfMatrix stride overflow")
}
pub fn zeros(rows: usize, cols: usize) -> Self {
let stride = Self::padded_stride(cols);
GfMatrix {
rows,
cols,
stride,
data: AlignedBuffer::zeroed(rows.checked_mul(stride).expect("GfMatrix size overflow")),
}
}
pub fn identity(k: usize) -> Self {
let mut m = Self::zeros(k, k);
for i in 0..k {
m.data.as_mut_slice()[i * m.stride + i] = 1;
}
m
}
pub fn rows(&self) -> usize {
self.rows
}
pub fn cols(&self) -> usize {
self.cols
}
pub fn stride(&self) -> usize {
self.stride
}
#[inline]
pub fn row(&self, r: usize) -> &[u8] {
let off = r * self.stride;
&self.data.as_slice()[off..off + self.cols]
}
#[inline]
pub fn row_mut(&mut self, r: usize) -> &mut [u8] {
let off = r * self.stride;
let cols = self.cols;
&mut self.data.as_mut_slice()[off..off + cols]
}
pub fn row_axpy(&mut self, dst: usize, c: u8, src: usize) {
self.row_axpy_from(dst, c, src, 0);
}
fn row_axpy_from(&mut self, dst: usize, c: u8, src: usize, start: usize) {
assert_ne!(dst, src, "GfMatrix::row_axpy: dst and src must differ");
let stride = self.stride;
let cols = self.cols;
let (dst_off, src_off) = (dst * stride, src * stride);
let (src_slice, dst_slice) = if dst > src {
let (lo, hi) = self.data.as_mut_slice().split_at_mut(dst_off);
(&lo[src_off + start..src_off + cols], &mut hi[start..cols])
} else {
let (lo, hi) = self.data.as_mut_slice().split_at_mut(src_off);
(&hi[start..cols], &mut lo[dst_off + start..dst_off + cols])
};
unsafe { kernel::axpy_unchecked(c, src_slice, dst_slice) };
}
pub fn row_scale(&mut self, r: usize, c: u8) {
self.row_scale_from(r, c, 0);
}
fn row_scale_from(&mut self, r: usize, c: u8, start: usize) {
let stride = self.stride;
let cols = self.cols;
let off = r * stride;
kernel::scale_inplace(c, &mut self.data.as_mut_slice()[off + start..off + cols]);
}
pub fn swap_rows(&mut self, a: usize, b: usize) {
if a == b {
return;
}
let stride = self.stride;
let (a_off, b_off) = (a * stride, b * stride);
let data = self.data.as_mut_slice();
if a < b {
let (lo, hi) = data.split_at_mut(b_off);
lo[a_off..a_off + stride].swap_with_slice(&mut hi[..stride]);
} else {
let (lo, hi) = data.split_at_mut(a_off);
lo[b_off..b_off + stride].swap_with_slice(&mut hi[..stride]);
}
}
pub fn gaussian_elimination(&mut self) -> usize {
let rows = self.rows;
let cols = self.cols;
let mut pivot_row = 0usize;
for col in 0..cols {
let found =
(pivot_row..rows).find(|&r| self.data.as_slice()[r * self.stride + col] != 0);
let Some(r) = found else { continue };
if r != pivot_row {
self.swap_rows(r, pivot_row);
}
let pivot_val = self.data.as_slice()[pivot_row * self.stride + col];
let operation_start = col & !(ALIGN - 1);
if pivot_val != 1 {
let inv = EXP[255 - LOG[pivot_val as usize] as usize];
self.row_scale_from(pivot_row, inv, operation_start);
}
for r2 in 0..rows {
if r2 == pivot_row {
continue;
}
let coeff = self.data.as_slice()[r2 * self.stride + col];
if coeff != 0 {
self.row_axpy_from(r2, coeff, pivot_row, operation_start);
}
}
pivot_row += 1;
}
pivot_row
}
pub fn is_full_rank(&self) -> bool {
for r in 0..self.rows {
let off = r * self.stride;
if self.data.as_slice()[off..off + self.cols]
.iter()
.all(|&b| b == 0)
{
return false;
}
}
true
}
}
#[cfg(test)]
#[cfg(feature = "alloc")]
mod tests {
use super::*;
#[test]
fn rows_are_aligned() {
let m = GfMatrix::zeros(8, 100);
for r in 0..8 {
let ptr = m.row(r).as_ptr() as usize;
assert_eq!(ptr % ALIGN, 0, "row {r} not {ALIGN}-byte aligned");
}
}
#[test]
#[should_panic(expected = "GfMatrix stride overflow")]
fn oversized_columns_panic_before_allocation() {
let _ = GfMatrix::zeros(1, usize::MAX);
}
#[test]
fn identity_rref_unchanged() {
let mut m = GfMatrix::identity(4);
let rank = m.gaussian_elimination();
assert_eq!(rank, 4);
let expected = GfMatrix::identity(4);
for i in 0..4 {
assert_eq!(m.row(i), expected.row(i));
}
}
#[test]
fn rank_of_zero_matrix() {
let mut m = GfMatrix::zeros(3, 3);
let rank = m.gaussian_elimination();
assert_eq!(rank, 0);
}
#[test]
fn rank_2x2_invertible() {
let mut m = GfMatrix::zeros(2, 2);
m.data.as_mut_slice()[0] = 1;
m.data.as_mut_slice()[1] = 2;
m.data.as_mut_slice()[m.stride] = 3;
m.data.as_mut_slice()[m.stride + 1] = 4;
let rank = m.gaussian_elimination();
assert_eq!(rank, 2);
assert_eq!(m.row(0)[0], 1);
assert_eq!(m.row(0)[1], 0);
assert_eq!(m.row(1)[0], 0);
assert_eq!(m.row(1)[1], 1);
}
#[test]
fn axpy_row_operation() {
use crate::kernel::scalar::gf_mul;
let mut m = GfMatrix::zeros(2, 4);
m.data.as_mut_slice()[0] = 1;
m.data.as_mut_slice()[m.stride + 1] = 1;
m.row_axpy(1, 0x03, 0);
assert_eq!(m.row(1)[0], gf_mul(0x03, 1));
assert_eq!(m.row(1)[1], 1);
assert_eq!(m.row(1)[2], 0);
assert_eq!(m.row(1)[3], 0);
}
#[test]
#[should_panic(expected = "dst and src must differ")]
fn row_axpy_panics_on_same_row() {
let mut m = GfMatrix::zeros(2, 4);
m.row_axpy(0, 0x03, 0);
}
#[test]
fn row_scale_matches_kernel() {
let mut m = GfMatrix::zeros(1, 8);
for i in 0..8 {
m.data.as_mut_slice()[i] = (i as u8).wrapping_add(1);
}
let before = m.row(0).to_vec();
m.row_scale(0, 0x53);
let mut expected = before.clone();
crate::kernel::scale_inplace(0x53, &mut expected);
assert_eq!(m.row(0), expected.as_slice());
}
}