#![forbid(unsafe_code)]
use core::array::from_fn;
use core::cmp::Ordering;
use la_stack::{BigInt, BigRational, DeterminantSign, RationalMatrix, RationalVector};
use super::bench_utils::OrAbort;
#[derive(Clone, Copy, Debug)]
pub enum RationalInputKind {
Small,
Wide256,
Wide1024,
}
impl RationalInputKind {
pub const ALL: [Self; 3] = [Self::Small, Self::Wide256, Self::Wide1024];
#[must_use]
pub const fn name(self) -> &'static str {
match self {
Self::Small => "small",
Self::Wide256 => "wide256",
Self::Wide1024 => "wide1024",
}
}
const fn component_bits(self) -> Option<usize> {
match self {
Self::Small => None,
Self::Wide256 => Some(256),
Self::Wide1024 => Some(1024),
}
}
}
#[must_use]
pub struct ValidatedRationalInput<const D: usize> {
matrix: RationalMatrix<D>,
rhs: RationalVector<D>,
}
impl<const D: usize> ValidatedRationalInput<D> {
pub const fn matrix(&self) -> &RationalMatrix<D> {
&self.matrix
}
pub const fn rhs(&self) -> &RationalVector<D> {
&self.rhs
}
}
pub fn rational_input<const D: usize>(kind: RationalInputKind) -> ValidatedRationalInput<D> {
let mut rows = from_fn(|row| {
from_fn(|col| {
if row == col {
let diagonal = 2 * D + row + 1;
BigRational::from_integer(BigInt::from(diagonal))
} else {
let raw_numerator = (row * 3 + col * 5) % 5;
let numerator = i64::try_from(raw_numerator).or_abort("rational numerator") - 2;
let denominator = (row + col) % 7 + 2;
BigRational::new(BigInt::from(numerator), BigInt::from(denominator))
}
})
});
if let Some(bits) = kind.component_bits() {
for (row, entries) in rows.iter_mut().enumerate() {
let shift = bits + 16 * row;
let factor = BigRational::new(
(BigInt::from(1) << shift) + BigInt::from(1),
(BigInt::from(1) << (shift + 1)) - BigInt::from(1),
);
for entry in entries {
*entry *= &factor;
}
}
}
let expected_solution =
from_fn(|index| BigRational::new(BigInt::from(index + 1), BigInt::from(index + 2)));
let rhs_data = rational_matvec(&rows, &expected_solution);
let matrix = RationalMatrix::try_from_rows(rows.clone())
.or_abort("rational benchmark matrix construction");
let rhs =
RationalVector::try_new(rhs_data.clone()).or_abort("rational benchmark RHS construction");
let reference_determinant = rational_determinant_gaussian(rows.clone());
assert_eq!(matrix.det(), reference_determinant);
assert_eq!(matrix.det_sign(), determinant_sign(&reference_determinant));
let reference_solution = rational_solve_gaussian(rows, rhs_data)
.or_abort("rational Gaussian benchmark validation solve");
assert_eq!(reference_solution, expected_solution);
assert_eq!(
matrix
.solve(&rhs)
.or_abort("row-cleared Bareiss benchmark validation solve")
.into_array(),
expected_solution
);
ValidatedRationalInput { matrix, rhs }
}
fn rational_matvec<const D: usize>(
rows: &[[BigRational; D]; D],
vector: &[BigRational; D],
) -> [BigRational; D] {
from_fn(|row| {
rows[row]
.iter()
.zip(vector.iter())
.map(|(coefficient, component)| coefficient * component)
.sum()
})
}
#[must_use]
pub fn rational_determinant_gaussian<const D: usize>(
mut rows: [[BigRational; D]; D],
) -> BigRational {
let zero = BigRational::from_integer(BigInt::from(0));
let mut determinant = BigRational::from_integer(BigInt::from(1));
let mut odd_swaps = false;
for pivot_col in 0..D {
let Some(pivot_row) = (pivot_col..D).find(|&row| rows[row][pivot_col] != zero) else {
return zero;
};
if pivot_row != pivot_col {
rows.swap(pivot_col, pivot_row);
odd_swaps = !odd_swaps;
}
let (pivot_rows, rows_below) = rows.split_at_mut(pivot_col + 1);
let pivot_entries = &pivot_rows[pivot_col];
let pivot = &pivot_entries[pivot_col];
determinant *= pivot;
for row_entries in rows_below {
let factor = &row_entries[pivot_col] / pivot;
for (entry, pivot_entry) in row_entries
.iter_mut()
.zip(pivot_entries.iter())
.skip(pivot_col + 1)
{
*entry -= &factor * pivot_entry;
}
row_entries[pivot_col] = zero.clone();
}
}
if odd_swaps { -determinant } else { determinant }
}
#[must_use]
pub fn rational_solve_gaussian<const D: usize>(
mut rows: [[BigRational; D]; D],
mut rhs: [BigRational; D],
) -> Option<[BigRational; D]> {
let zero = BigRational::from_integer(BigInt::from(0));
for pivot_col in 0..D {
let pivot_row = (pivot_col..D).find(|&row| rows[row][pivot_col] != zero)?;
if pivot_row != pivot_col {
rows.swap(pivot_col, pivot_row);
rhs.swap(pivot_col, pivot_row);
}
let (pivot_rows, rows_below) = rows.split_at_mut(pivot_col + 1);
let pivot_entries = &pivot_rows[pivot_col];
let pivot = &pivot_entries[pivot_col];
let (pivot_rhs_entries, rhs_below) = rhs.split_at_mut(pivot_col + 1);
let pivot_rhs = &pivot_rhs_entries[pivot_col];
for (row_entries, rhs_entry) in rows_below.iter_mut().zip(rhs_below) {
let factor = &row_entries[pivot_col] / pivot;
for (entry, pivot_entry) in row_entries
.iter_mut()
.zip(pivot_entries.iter())
.skip(pivot_col + 1)
{
*entry -= &factor * pivot_entry;
}
let rhs_update = &factor * pivot_rhs;
*rhs_entry -= rhs_update;
row_entries[pivot_col] = zero.clone();
}
}
let mut solution = from_fn(|_| zero.clone());
for row in (0..D).rev() {
let mut value = rhs[row].clone();
for (coefficient, component) in rows[row].iter().zip(solution.iter()).skip(row + 1) {
value -= coefficient * component;
}
solution[row] = value / &rows[row][row];
}
Some(solution)
}
fn determinant_sign(value: &BigRational) -> DeterminantSign {
let zero = BigRational::from_integer(BigInt::from(0));
match value.cmp(&zero) {
Ordering::Less => DeterminantSign::Negative,
Ordering::Equal => DeterminantSign::Zero,
Ordering::Greater => DeterminantSign::Positive,
}
}