use super::linalg::ComputeStorage;
use super::compute_matrix::ComputeMatrix;
use super::{FixedMatrix, FixedPoint};
use crate::fixed_point::core_types::errors::OverflowDetected;
use crate::fixed_point::domains::binary_fixed::i256::{divmod_i256_by_i256, mul_i128_to_i256};
use crate::fixed_point::frac_config::FRAC_BITS;
use crate::fixed_point::I256;
const OVERFLOW: OverflowDetected = OverflowDetected::TierOverflow;
const WIDE_FRAC: u32 = 64;
#[derive(Clone, Debug)]
pub(crate) struct WideMatrix {
rows: usize,
cols: usize,
data: Vec<i128>,
}
#[inline]
fn round_shift(v: i128, shift: u32) -> i128 {
(v >> shift) + ((v >> (shift - 1)) & 1)
}
#[inline]
fn narrow_128(v: I256) -> Result<i128, OverflowDetected> {
let half = I256::from_i128(1) << 63usize;
let q = v.checked_add(half).ok_or(OVERFLOW)? >> 64u32;
if q.fits_in_i128() { Ok(q.as_i128()) } else { Err(OVERFLOW) }
}
fn divide_128(num: I256, den: i128) -> Result<i128, OverflowDetected> {
if den == 0 {
return Err(OverflowDetected::DivisionByZero);
}
let d = I256::from_i128(den);
let (q, r) = divmod_i256_by_i256(num, d);
let positive = num.is_negative() == d.is_negative();
let r_abs = if r.is_negative() { I256::zero() - r } else { r };
let d_abs = if d.is_negative() { I256::zero() - d } else { d };
let twice = r_abs.checked_add(r_abs).ok_or(OVERFLOW)?;
let bump = if positive { twice >= d_abs } else { twice > d_abs };
let q = if bump {
if positive { q + I256::from_i128(1) } else { q - I256::from_i128(1) }
} else {
q
};
if q.fits_in_i128() { Ok(q.as_i128()) } else { Err(OVERFLOW) }
}
#[inline]
fn widen_128(v: i128) -> I256 {
I256::from_i128(v) << 64usize
}
fn sub_dot_128(init: i128, a: &[i128], b: &[i128]) -> Result<I256, OverflowDetected> {
let mut acc = widen_128(init);
for (x, y) in a.iter().zip(b) {
acc = acc.checked_sub(mul_i128_to_i256(*x, *y)).ok_or(OVERFLOW)?;
}
Ok(acc)
}
impl WideMatrix {
pub(crate) fn new(rows: usize, cols: usize) -> Self {
WideMatrix { rows, cols, data: vec![0; rows * cols] }
}
pub(crate) fn identity(n: usize) -> Self {
let mut m = Self::new(n, n);
for i in 0..n {
m.set(i, i, 1i128 << WIDE_FRAC);
}
m
}
#[inline]
pub(crate) fn rows(&self) -> usize { self.rows }
#[inline]
pub(crate) fn get(&self, r: usize, c: usize) -> i128 { self.data[r * self.cols + c] }
#[inline]
pub(crate) fn set(&mut self, r: usize, c: usize, v: i128) { self.data[r * self.cols + c] = v; }
pub(crate) fn from_fixed_matrix(m: &FixedMatrix) -> Self {
let mut w = Self::new(m.rows(), m.cols());
for r in 0..m.rows() {
for c in 0..m.cols() {
w.set(r, c, (m.get(r, c).raw() as i128) << (WIDE_FRAC - FRAC_BITS));
}
}
w
}
pub(crate) fn from_compute(m: &ComputeMatrix) -> Self {
let mut w = Self::new(m.rows(), m.cols());
for r in 0..m.rows() {
for c in 0..m.cols() {
w.set(r, c, (m.get(r, c) as i128) << (WIDE_FRAC - 2 * FRAC_BITS));
}
}
w
}
pub(crate) fn to_fixed_matrix(&self) -> Result<FixedMatrix, OverflowDetected> {
let mut out = FixedMatrix::new(self.rows, self.cols);
for r in 0..self.rows {
for c in 0..self.cols {
let v = round_shift(self.get(r, c), WIDE_FRAC - FRAC_BITS);
out.set(r, c, FixedPoint::from_raw(i32::try_from(v).map_err(|_| OVERFLOW)?));
}
}
Ok(out)
}
pub(crate) fn to_compute(&self) -> Result<ComputeMatrix, OverflowDetected> {
let mut out = ComputeMatrix::new(self.rows, self.cols);
for r in 0..self.rows {
for c in 0..self.cols {
let v = round_shift(self.get(r, c), WIDE_FRAC - 2 * FRAC_BITS);
out.set(r, c, ComputeStorage::try_from(v).map_err(|_| OVERFLOW)?);
}
}
Ok(out)
}
pub(crate) fn scalar_from_storage(x: FixedPoint) -> i128 {
(x.raw() as i128) << (WIDE_FRAC - FRAC_BITS)
}
pub(crate) fn fraction(num: i64, den: i64) -> i128 {
divide_128(widen_128(num as i128) << 64usize, (den as i128) << WIDE_FRAC)
.expect("fraction fits the wide tier")
}
fn zip(&self, o: &Self, f: impl Fn(i128, i128) -> Option<i128>) -> Result<Self, OverflowDetected> {
assert_eq!((self.rows, self.cols), (o.rows, o.cols), "WideMatrix: dimension mismatch");
let data: Option<Vec<i128>> = self.data.iter().zip(&o.data).map(|(a, b)| f(*a, *b)).collect();
Ok(WideMatrix { rows: self.rows, cols: self.cols, data: data.ok_or(OVERFLOW)? })
}
pub(crate) fn add(&self, o: &Self) -> Result<Self, OverflowDetected> { self.zip(o, i128::checked_add) }
pub(crate) fn sub(&self, o: &Self) -> Result<Self, OverflowDetected> { self.zip(o, i128::checked_sub) }
pub(crate) fn halve(&self) -> Self {
WideMatrix { rows: self.rows, cols: self.cols, data: self.data.iter().map(|v| round_shift(*v, 1)).collect() }
}
pub(crate) fn scalar_mul(&self, s: i128) -> Result<Self, OverflowDetected> {
let data: Result<Vec<i128>, OverflowDetected> =
self.data.iter().map(|v| narrow_128(mul_i128_to_i256(*v, s))).collect();
Ok(WideMatrix { rows: self.rows, cols: self.cols, data: data? })
}
pub(crate) fn mat_mul(&self, o: &Self) -> Result<Self, OverflowDetected> {
assert_eq!(self.cols, o.rows, "WideMatrix::mat_mul: dimension mismatch");
let mut out = Self::new(self.rows, o.cols);
for r in 0..self.rows {
for c in 0..o.cols {
let a: Vec<i128> = (0..self.cols).map(|k| -self.get(r, k)).collect();
let b: Vec<i128> = (0..self.cols).map(|k| o.get(k, c)).collect();
out.set(r, c, narrow_128(sub_dot_128(0, &a, &b)?)?);
}
}
Ok(out)
}
pub(crate) fn is_zero(&self) -> bool { self.data.iter().all(|v| *v == 0) }
pub(crate) fn norm_1(&self) -> FixedPoint {
let mut best: i128 = 0;
for c in 0..self.cols {
let mut sum: i128 = 0;
for r in 0..self.rows {
sum = sum.saturating_add(self.get(r, c).saturating_abs());
}
best = best.max(sum);
}
let v = round_shift(best, WIDE_FRAC - FRAC_BITS);
FixedPoint::from_raw(v.min(i32::MAX as i128) as i32)
}
pub(crate) fn frobenius_below(&self, t: FixedPoint) -> bool {
let mut sum = I256::zero();
for v in &self.data {
match sum.checked_add(mul_i128_to_i256(*v, *v)) {
Some(s) => sum = s,
None => return false,
}
}
let tw = Self::scalar_from_storage(t);
sum < mul_i128_to_i256(tw, tw)
}
pub(crate) fn step_below(&self, reference: &Self, bits: u32) -> bool {
let sumsq = |m: &WideMatrix| -> Option<I256> {
let mut sum = I256::zero();
for v in &m.data {
sum = sum.checked_add(mul_i128_to_i256(*v, *v))?;
}
Some(sum)
};
match (sumsq(self), sumsq(reference)) {
(Some(step), Some(r)) => step < (r >> (2 * bits)),
_ => false,
}
}
pub(crate) fn lu(&self) -> Result<WideLU, OverflowDetected> {
let n = self.rows;
let mut pa = self.clone();
let mut l = Self::new(n, n);
let mut u = Self::new(n, n);
let mut perm: Vec<usize> = (0..n).collect();
for k in 0..n {
let mut max_abs = I256::zero();
let mut max_row = k;
for i in k..n {
let l_row: Vec<i128> = (0..k).map(|m| l.get(i, m)).collect();
let u_col: Vec<i128> = (0..k).map(|m| u.get(m, k)).collect();
let cand = sub_dot_128(pa.get(i, k), &l_row, &u_col)?;
let abs = if cand.is_negative() { I256::zero() - cand } else { cand };
if abs > max_abs {
max_abs = abs;
max_row = i;
}
}
if max_abs.is_zero() {
return Err(OverflowDetected::DivisionByZero);
}
if max_row != k {
for c in 0..n {
let t = pa.get(k, c);
pa.set(k, c, pa.get(max_row, c));
pa.set(max_row, c, t);
}
perm.swap(k, max_row);
for j in 0..k {
let t = l.get(k, j);
l.set(k, j, l.get(max_row, j));
l.set(max_row, j, t);
}
}
for j in k..n {
let l_row: Vec<i128> = (0..k).map(|m| l.get(k, m)).collect();
let u_col: Vec<i128> = (0..k).map(|m| u.get(m, j)).collect();
u.set(k, j, narrow_128(sub_dot_128(pa.get(k, j), &l_row, &u_col)?)?);
}
l.set(k, k, 1i128 << WIDE_FRAC);
let pivot = u.get(k, k);
for i in (k + 1)..n {
let l_row: Vec<i128> = (0..k).map(|m| l.get(i, m)).collect();
let u_col: Vec<i128> = (0..k).map(|m| u.get(m, k)).collect();
l.set(i, k, divide_128(sub_dot_128(pa.get(i, k), &l_row, &u_col)?, pivot)?);
}
}
Ok(WideLU { l, u, perm })
}
}
pub(crate) struct WideLU {
l: WideMatrix,
u: WideMatrix,
perm: Vec<usize>,
}
impl WideLU {
pub(crate) fn solve(&self, b: &[i128]) -> Result<Vec<i128>, OverflowDetected> {
let n = self.l.rows();
let pb: Vec<i128> = (0..n).map(|i| b[self.perm[i]]).collect();
let mut y = vec![0i128; n];
for i in 0..n {
let l_row: Vec<i128> = (0..i).map(|j| self.l.get(i, j)).collect();
y[i] = narrow_128(sub_dot_128(pb[i], &l_row, &y[..i])?)?;
}
let mut x = vec![0i128; n];
for i in (0..n).rev() {
let u_row: Vec<i128> = ((i + 1)..n).map(|j| self.u.get(i, j)).collect();
x[i] = divide_128(sub_dot_128(y[i], &u_row, &x[i + 1..])?, self.u.get(i, i))?;
}
Ok(x)
}
pub(crate) fn inverse(&self) -> Result<WideMatrix, OverflowDetected> {
let n = self.l.rows();
let mut inv = WideMatrix::new(n, n);
for j in 0..n {
let e: Vec<i128> = (0..n).map(|i| if i == j { 1i128 << WIDE_FRAC } else { 0 }).collect();
let col = self.solve(&e)?;
for i in 0..n {
inv.set(i, j, col[i]);
}
}
Ok(inv)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn wide_arithmetic_rounds_once() {
let third = WideMatrix::fraction(1, 3);
assert_eq!(third, 6148914691236517205);
assert_eq!(WideMatrix::fraction(2, 3), 12297829382473034411);
let mut m = WideMatrix::identity(2);
m.set(0, 1, third);
let sq = m.mat_mul(&m).unwrap();
assert_eq!(sq.get(0, 1), 2 * third);
let inv = m.lu().unwrap().inverse().unwrap();
assert_eq!(inv.get(0, 1), -third);
let f = FixedMatrix::identity(2);
assert_eq!(WideMatrix::from_fixed_matrix(&f).to_fixed_matrix().unwrap(), f);
}
}