use super::FixedPoint;
use super::FixedVector;
use super::FixedMatrix;
use super::interval::exact_product;
use super::linalg::{
compute_tier_dot_raw, compute_tier_sub_dot_raw, compute_tier_sub_dot_compute,
upscale_to_compute, round_to_storage, compute_abs, compute_product, deflation_threshold, exact_dot,
householder_vector, noise_floor, reflect, scale_by, stagnation_threshold, ComputeStorage,
Rotation, STAGNATION_SWEEPS,
};
use super::wide_acc::widen_storage;
use crate::fixed_point::universal::fasc::stack_evaluator::compute::{
sqrt_at_compute_tier, compute_divide, downscale_to_storage,
compute_multiply, compute_add, compute_negate,
compute_is_negative, compute_is_zero,
compute_checked_add, compute_checked_divide, compute_halve, make_compute_int,
};
use crate::fixed_point::universal::fasc::stack_evaluator::BinaryStorage;
use crate::fixed_point::core_types::errors::OverflowDetected;
#[derive(Clone, Debug)]
pub struct LUDecomposition {
pub l: FixedMatrix,
pub u: FixedMatrix,
pub perm: Vec<usize>,
pub num_swaps: usize,
}
pub fn lu_decompose(a: &FixedMatrix) -> Result<LUDecomposition, OverflowDetected> {
assert!(a.is_square(), "lu_decompose: matrix must be square");
let n = a.rows();
let mut pa = a.clone();
let mut l = FixedMatrix::new(n, n);
let mut u = FixedMatrix::new(n, n);
let mut perm: Vec<usize> = (0..n).collect();
let mut num_swaps: usize = 0;
for k in 0..n {
let mut max_abs = FixedPoint::ZERO;
let mut max_row = k;
for i in k..n {
let candidate = if k == 0 {
pa.get(i, k)
} else {
let l_row = l.row_raw_range(i, 0, k);
let u_col = u.col_raw_range(k, 0, k);
FixedPoint::from_raw(compute_tier_sub_dot_raw(pa.get(i, k).raw(), &l_row, &u_col))
};
if candidate.abs() > max_abs {
max_abs = candidate.abs();
max_row = i;
}
}
if max_abs.is_zero() {
return Err(OverflowDetected::DivisionByZero);
}
if max_row != k {
pa.swap_rows(k, max_row);
perm.swap(k, max_row);
num_swaps += 1;
for j in 0..k {
let tmp = l.get(k, j);
l.set(k, j, l.get(max_row, j));
l.set(max_row, j, tmp);
}
}
for j in k..n {
if k == 0 {
u.set(k, j, pa.get(k, j));
} else {
let l_row = l.row_raw_range(k, 0, k);
let u_col = u.col_raw_range(j, 0, k);
u.set(k, j, FixedPoint::from_raw(
compute_tier_sub_dot_raw(pa.get(k, j).raw(), &l_row, &u_col)
));
}
}
let pivot = u.get(k, k);
l.set(k, k, FixedPoint::one()); for i in (k + 1)..n {
let numerator = if k == 0 {
pa.get(i, k)
} else {
let l_row = l.row_raw_range(i, 0, k);
let u_col = u.col_raw_range(k, 0, k);
FixedPoint::from_raw(compute_tier_sub_dot_raw(pa.get(i, k).raw(), &l_row, &u_col))
};
l.set(i, k, numerator / pivot);
}
}
Ok(LUDecomposition { l, u, perm, num_swaps })
}
impl LUDecomposition {
pub fn solve(&self, b: &FixedVector) -> Result<FixedVector, OverflowDetected> {
let n = self.l.rows();
assert_eq!(b.len(), n, "LU solve: dimension mismatch");
let mut pb = FixedVector::new(n);
for i in 0..n {
pb[i] = b[self.perm[i]];
}
let mut y = FixedVector::new(n);
for i in 0..n {
if i == 0 {
y[0] = pb[0];
} else {
let l_row = self.l.row_raw_range(i, 0, i);
let y_raw: Vec<BinaryStorage> = (0..i).map(|j| y[j].raw()).collect();
y[i] = FixedPoint::from_raw(
compute_tier_sub_dot_raw(pb[i].raw(), &l_row, &y_raw)
);
}
}
let mut x = FixedVector::new(n);
for i in (0..n).rev() {
let diag = self.u.get(i, i);
if diag.is_zero() {
return Err(OverflowDetected::DivisionByZero);
}
if i == n - 1 {
x[n - 1] = y[n - 1] / diag;
} else {
let u_row = self.u.row_raw_range(i, i + 1, n);
let x_raw: Vec<BinaryStorage> = (i + 1..n).map(|j| x[j].raw()).collect();
let numerator = FixedPoint::from_raw(
compute_tier_sub_dot_raw(y[i].raw(), &u_row, &x_raw)
);
x[i] = numerator / diag;
}
}
Ok(x)
}
pub fn determinant(&self) -> FixedPoint {
let n = self.u.rows();
use crate::fixed_point::universal::fasc::stack_evaluator::compute::compute_multiply;
let mut acc = upscale_to_compute(self.u.get(0, 0).raw());
for i in 1..n {
acc = compute_multiply(acc, upscale_to_compute(self.u.get(i, i).raw()));
}
let det_raw = round_to_storage(acc);
let det = FixedPoint::from_raw(det_raw);
if self.num_swaps % 2 == 1 { -det } else { det }
}
pub fn refine(&self, a: &FixedMatrix, b: &FixedVector, x: &FixedVector) -> Result<FixedVector, OverflowDetected> {
let n = a.rows();
let mut r = FixedVector::new(n);
for i in 0..n {
let a_row = a.row_raw_range(i, 0, n);
let x_raw: Vec<BinaryStorage> = (0..n).map(|j| x[j].raw()).collect();
r[i] = FixedPoint::from_raw(
compute_tier_sub_dot_raw(b[i].raw(), &a_row, &x_raw)
);
}
let dx = self.solve(&r)?;
let mut x_refined = FixedVector::new(n);
for i in 0..n {
x_refined[i] = x[i] + dx[i];
}
Ok(x_refined)
}
pub fn inverse(&self) -> Result<FixedMatrix, OverflowDetected> {
let n = self.l.rows();
let mut inv = FixedMatrix::new(n, n);
for j in 0..n {
let mut e_j = FixedVector::new(n);
e_j[j] = FixedPoint::one();
let col = self.solve(&e_j)?;
for i in 0..n {
inv.set(i, j, col[i]);
}
}
Ok(inv)
}
}
#[derive(Clone, Debug)]
pub struct QRDecomposition {
pub q: FixedMatrix,
pub r: FixedMatrix,
}
pub fn qr_decompose(a: &FixedMatrix) -> Result<QRDecomposition, OverflowDetected> {
let m = a.rows();
let n = a.cols();
assert!(m >= n, "qr_decompose: requires m >= n");
let mut r = a.clone();
let mut q = FixedMatrix::identity(m);
let two = FixedPoint::from_int(2);
for k in 0..n {
let col_len = m - k;
let x_raw: Vec<BinaryStorage> = (k..m).map(|i| r.get(i, k).raw()).collect();
let norm_sq = FixedPoint::from_raw(compute_tier_dot_raw(&x_raw, &x_raw));
if norm_sq.is_zero() {
continue;
}
let norm_x = norm_sq.try_sqrt()?;
let x_0 = r.get(k, k);
let alpha = if x_0.is_negative() { norm_x } else { -norm_x };
let mut v = Vec::<FixedPoint>::with_capacity(col_len);
v.push(x_0 - alpha);
for i in 1..col_len {
v.push(FixedPoint::from_raw(x_raw[i]));
}
let v_raw: Vec<BinaryStorage> = v.iter().map(|fp| fp.raw()).collect();
let vtv = FixedPoint::from_raw(compute_tier_dot_raw(&v_raw, &v_raw));
if vtv.is_zero() {
continue;
}
for j in k..n {
let col_j_raw: Vec<BinaryStorage> = (k..m).map(|i| r.get(i, j).raw()).collect();
let vt_rj = FixedPoint::from_raw(compute_tier_dot_raw(&v_raw, &col_j_raw));
let scale = two * vt_rj / vtv;
for i in k..m {
let r_ij = r.get(i, j);
r.set(i, j, r_ij - scale * v[i - k]);
}
}
for i in 0..m {
let q_row_raw: Vec<BinaryStorage> = (k..m).map(|j| q.get(i, j).raw()).collect();
let qi_dot_v = FixedPoint::from_raw(compute_tier_dot_raw(&q_row_raw, &v_raw));
let scale = two * qi_dot_v / vtv;
for j in k..m {
let q_ij = q.get(i, j);
q.set(i, j, q_ij - scale * v[j - k]);
}
}
}
Ok(QRDecomposition { q, r })
}
impl QRDecomposition {
pub fn solve(&self, b: &FixedVector) -> Result<FixedVector, OverflowDetected> {
let m = self.q.rows();
let n = self.r.cols();
assert_eq!(b.len(), m, "QR solve: dimension mismatch");
let mut qtb = FixedVector::new(m);
for i in 0..m {
let q_col_raw: Vec<BinaryStorage> = (0..m).map(|j| self.q.get(j, i).raw()).collect();
let b_raw: Vec<BinaryStorage> = (0..m).map(|j| b[j].raw()).collect();
qtb[i] = FixedPoint::from_raw(compute_tier_dot_raw(&q_col_raw, &b_raw));
}
let mut x = FixedVector::new(n);
for i in (0..n).rev() {
let diag = self.r.get(i, i);
if diag.is_zero() {
return Err(OverflowDetected::DivisionByZero);
}
if i == n - 1 {
x[n - 1] = qtb[n - 1] / diag;
} else {
let r_row = self.r.row_raw_range(i, i + 1, n);
let x_raw: Vec<BinaryStorage> = (i + 1..n).map(|j| x[j].raw()).collect();
let numerator = FixedPoint::from_raw(
compute_tier_sub_dot_raw(qtb[i].raw(), &r_row, &x_raw)
);
x[i] = numerator / diag;
}
}
Ok(x)
}
}
#[derive(Clone, Debug)]
pub struct CholeskyDecomposition {
pub l: FixedMatrix,
}
pub fn cholesky_decompose(a: &FixedMatrix) -> Result<CholeskyDecomposition, OverflowDetected> {
assert!(a.is_square(), "cholesky_decompose: matrix must be square");
let n = a.rows();
let mut l = FixedMatrix::new(n, n);
for i in 0..n {
let diag_compute = if i == 0 {
upscale_to_compute(a.get(0, 0).raw())
} else {
let l_row = l.row_raw_range(i, 0, i);
compute_tier_sub_dot_compute(a.get(i, i).raw(), &l_row, &l_row)
};
if compute_is_negative(&diag_compute) || compute_is_zero(&diag_compute) {
return Err(OverflowDetected::DomainError);
}
let sqrt_compute = sqrt_at_compute_tier(diag_compute);
let l_ii_raw = downscale_to_storage(sqrt_compute)
.map_err(|_| OverflowDetected::TierOverflow)?;
let l_ii = FixedPoint::from_raw(l_ii_raw);
l.set(i, i, l_ii);
let l_ii_compute = upscale_to_compute(l_ii.raw());
for j in (i + 1)..n {
let numerator_compute = if i == 0 {
upscale_to_compute(a.get(j, i).raw())
} else {
let l_j_row = l.row_raw_range(j, 0, i);
let l_i_row = l.row_raw_range(i, 0, i);
compute_tier_sub_dot_compute(a.get(j, i).raw(), &l_j_row, &l_i_row)
};
let quotient_compute = compute_divide(numerator_compute, l_ii_compute)
.map_err(|_| OverflowDetected::DivisionByZero)?;
let l_ji_raw = downscale_to_storage(quotient_compute)
.map_err(|_| OverflowDetected::TierOverflow)?;
l.set(j, i, FixedPoint::from_raw(l_ji_raw));
}
}
Ok(CholeskyDecomposition { l })
}
impl CholeskyDecomposition {
pub fn solve(&self, b: &FixedVector) -> Result<FixedVector, OverflowDetected> {
let n = self.l.rows();
assert_eq!(b.len(), n, "Cholesky solve: dimension mismatch");
let mut y = FixedVector::new(n);
for i in 0..n {
let diag = self.l.get(i, i);
if i == 0 {
y[0] = b[0] / diag;
} else {
let l_row = self.l.row_raw_range(i, 0, i);
let y_raw: Vec<BinaryStorage> = (0..i).map(|j| y[j].raw()).collect();
let numerator = FixedPoint::from_raw(
compute_tier_sub_dot_raw(b[i].raw(), &l_row, &y_raw)
);
y[i] = numerator / diag;
}
}
let mut x = FixedVector::new(n);
for i in (0..n).rev() {
let diag = self.l.get(i, i);
if i == n - 1 {
x[n - 1] = y[n - 1] / diag;
} else {
let lt_row = self.l.col_raw_range(i, i + 1, n);
let x_raw: Vec<BinaryStorage> = (i + 1..n).map(|j| x[j].raw()).collect();
let numerator = FixedPoint::from_raw(
compute_tier_sub_dot_raw(y[i].raw(), <_row, &x_raw)
);
x[i] = numerator / diag;
}
}
Ok(x)
}
pub fn determinant(&self) -> FixedPoint {
let n = self.l.rows();
let mut det_l = FixedPoint::one();
for i in 0..n {
det_l = det_l * self.l.get(i, i);
}
det_l * det_l
}
}
const ITERATIONS_PER_N_SQUARED: usize = 30;
const JACOBI_MAX_SWEEPS: usize = 100;
const SCHUR_FLOOR_ITERATIONS: usize = 30;
fn reflect_column(
mat: &mut FixedMatrix, col: usize, start: usize, v: &[BinaryStorage], v_dot_v: ComputeStorage,
) -> Result<(), OverflowDetected> {
let mut w: Vec<BinaryStorage> = (start..start + v.len()).map(|i| mat.get(i, col).raw()).collect();
reflect(&mut w, v, v_dot_v)?;
for (k, value) in w.into_iter().enumerate() {
mat.set(start + k, col, FixedPoint::from_raw(value));
}
Ok(())
}
fn reflect_row(
mat: &mut FixedMatrix, row: usize, start: usize, v: &[BinaryStorage], v_dot_v: ComputeStorage,
) -> Result<(), OverflowDetected> {
let mut w: Vec<BinaryStorage> = (start..start + v.len()).map(|c| mat.get(row, c).raw()).collect();
reflect(&mut w, v, v_dot_v)?;
for (k, value) in w.into_iter().enumerate() {
mat.set(row, start + k, FixedPoint::from_raw(value));
}
Ok(())
}
fn rotate_columns(mat: &mut FixedMatrix, a: usize, b: usize, rot: &Rotation) -> Result<(), OverflowDetected> {
for r in 0..mat.rows() {
let (new_a, new_b) = rot.apply(mat.get(r, a), mat.get(r, b))?;
mat.set(r, a, new_a);
mat.set(r, b, new_b);
}
Ok(())
}
fn checked_add_fp(a: FixedPoint, b: FixedPoint) -> Result<FixedPoint, OverflowDetected> {
a.raw().checked_add(b.raw()).map(FixedPoint::from_raw).ok_or(OverflowDetected::TierOverflow)
}
fn checked_sub_fp(a: FixedPoint, b: FixedPoint) -> Result<FixedPoint, OverflowDetected> {
a.raw().checked_sub(b.raw()).map(FixedPoint::from_raw).ok_or(OverflowDetected::TierOverflow)
}
fn compute_sum(terms: &[ComputeStorage]) -> Result<ComputeStorage, OverflowDetected> {
let mut acc = make_compute_int(0);
for term in terms {
acc = compute_checked_add(acc, *term)?;
}
Ok(acc)
}
fn compute_hypot(a: ComputeStorage, b: ComputeStorage) -> Result<ComputeStorage, OverflowDetected> {
let (x, y) = (compute_abs(a), compute_abs(b));
let (big, small) = if x >= y { (x, y) } else { (y, x) };
if compute_is_zero(&big) {
return Ok(big);
}
let one = make_compute_int(1);
let ratio = compute_divide(small, big)?;
Ok(compute_multiply(big, sqrt_at_compute_tier(compute_add(one, compute_multiply(ratio, ratio)))))
}
#[derive(Clone, Debug)]
pub struct EigenDecomposition {
pub values: FixedVector,
pub vectors: FixedMatrix,
}
pub fn eigen_symmetric(a: &FixedMatrix) -> Result<EigenDecomposition, OverflowDetected> {
eigen_symmetric_within(a, JACOBI_MAX_SWEEPS)
}
fn eigen_symmetric_within(a: &FixedMatrix, max_sweeps: usize) -> Result<EigenDecomposition, OverflowDetected> {
assert!(a.is_square(), "eigen_symmetric: matrix must be square");
let n = a.rows();
if n == 0 {
return Ok(EigenDecomposition {
values: FixedVector::new(0),
vectors: FixedMatrix::new(0, 0),
});
}
if n == 1 {
return Ok(EigenDecomposition {
values: FixedVector::from_slice(&[a.get(0, 0)]),
vectors: FixedMatrix::identity(1),
});
}
let mut s = a.clone();
let mut v = FixedMatrix::identity(n);
let mut converged = false;
let mut best_off: Option<FixedPoint> = None;
let mut stagnant = 0usize;
for _sweep in 0..max_sweeps {
let mut rotated = false;
for p in 0..n {
for q in (p + 1)..n {
let bound = deflation_threshold(s.get(p, p).abs().max(s.get(q, q).abs()));
if s.get(p, q).abs() > bound {
jacobi_rotate(&mut s, &mut v, p, q)?;
rotated = true;
}
}
}
if !rotated {
converged = true;
break;
}
let (off, _, _) = largest_off_diagonal(&s);
if best_off.map_or(true, |best| off < best) {
best_off = Some(off);
stagnant = 0;
} else {
stagnant += 1;
}
if stagnant >= STAGNATION_SWEEPS && off_diagonal_within_stagnation_bound(&s) {
converged = true;
break;
}
}
if !converged {
return Err(OverflowDetected::PrecisionLimit);
}
let (largest, p, q) = largest_off_diagonal(&s);
if !largest.is_zero() {
jacobi_rotate(&mut s, &mut v, p, q)?;
}
let mut eigen_pairs: Vec<(FixedPoint, usize)> = (0..n)
.map(|i| (s.get(i, i), i))
.collect();
eigen_pairs.sort_by(|a, b| b.0.abs().partial_cmp(&a.0.abs()).unwrap_or(std::cmp::Ordering::Equal));
let mut values = FixedVector::new(n);
let mut vectors = FixedMatrix::new(n, n);
for (k, (val, orig_idx)) in eigen_pairs.iter().enumerate() {
values[k] = *val;
for r in 0..n {
vectors.set(r, k, v.get(r, *orig_idx));
}
}
Ok(EigenDecomposition { values, vectors })
}
fn largest_off_diagonal(s: &FixedMatrix) -> (FixedPoint, usize, usize) {
let n = s.rows();
let (mut largest, mut at_p, mut at_q) = (FixedPoint::ZERO, 0, 1);
for p in 0..n {
for q in (p + 1)..n {
let value = s.get(p, q).abs();
if value > largest {
largest = value;
at_p = p;
at_q = q;
}
}
}
(largest, at_p, at_q)
}
fn off_diagonal_within_stagnation_bound(s: &FixedMatrix) -> bool {
let n = s.rows();
(0..n).all(|p| {
((p + 1)..n).all(|q| s.get(p, q).abs() <= stagnation_threshold(s.get(p, p).abs().max(s.get(q, q).abs())))
})
}
fn jacobi_rotate(s: &mut FixedMatrix, v: &mut FixedMatrix, p: usize, q: usize) -> Result<(), OverflowDetected> {
let n = s.rows();
let (a_pp, a_qq, a_pq) = (s.get(p, p), s.get(q, q), s.get(p, q));
if a_pq.is_zero() {
return Ok(());
}
let one = make_compute_int(1);
let num = compute_checked_add(upscale_to_compute(a_pp.raw()), compute_negate(upscale_to_compute(a_qq.raw())))?;
let den = compute_checked_add(upscale_to_compute(a_pq.raw()), upscale_to_compute(a_pq.raw()))?;
let negative = !compute_is_zero(&num) && (compute_is_negative(&num) != compute_is_negative(&den));
let (num_abs, den_abs) = (compute_abs(num), compute_abs(den));
let t_abs = if num_abs <= den_abs {
let tau = compute_divide(num_abs, den_abs)?;
let root = sqrt_at_compute_tier(compute_add(one, compute_multiply(tau, tau)));
compute_divide(one, compute_add(tau, root))?
} else {
let r = compute_divide(den_abs, num_abs)?;
let root = sqrt_at_compute_tier(compute_add(one, compute_multiply(r, r)));
compute_divide(r, compute_add(one, root))?
};
let t = if negative { compute_negate(t_abs) } else { t_abs };
let cs = compute_divide(one, sqrt_at_compute_tier(compute_add(one, compute_multiply(t, t))))?;
let rot = Rotation::from_parts(cs, compute_multiply(t, cs));
for r in 0..n {
if r == p || r == q {
continue;
}
let (new_rp, new_rq) = rot.apply(s.get(r, p), s.get(r, q))?;
s.set(r, p, new_rp);
s.set(p, r, new_rp);
s.set(r, q, new_rq);
s.set(q, r, new_rq);
}
let shift = scale_by(t, a_pq)?;
s.set(p, p, checked_add_fp(a_pp, shift)?);
s.set(q, q, checked_sub_fp(a_qq, shift)?);
s.set(p, q, FixedPoint::ZERO);
s.set(q, p, FixedPoint::ZERO);
rotate_columns(v, p, q, &rot)
}
#[derive(Clone, Debug)]
pub struct SVDDecomposition {
pub u: FixedMatrix,
pub sigma: FixedVector,
pub vt: FixedMatrix,
}
pub fn svd_decompose(a: &FixedMatrix) -> Result<SVDDecomposition, OverflowDetected> {
svd_decompose_within(a, ITERATIONS_PER_N_SQUARED)
}
fn svd_decompose_within(a: &FixedMatrix, iterations_per_n_squared: usize) -> Result<SVDDecomposition, OverflowDetected> {
let (m, n) = (a.rows(), a.cols());
if m == 0 || n == 0 {
return Ok(SVDDecomposition {
u: FixedMatrix::identity(m),
sigma: FixedVector::new(0),
vt: FixedMatrix::identity(n),
});
}
if m < n {
let at = a.transpose();
let mut result = svd_decompose_within(&at, iterations_per_n_squared)?;
let u_new = result.vt.transpose();
let vt_new = result.u.transpose();
result.u = u_new;
result.vt = vt_new;
return Ok(result);
}
let mut b = a.clone();
let mut u_acc = FixedMatrix::identity(m);
let mut v_acc = FixedMatrix::identity(n);
for j in 0..n {
let column: Vec<BinaryStorage> = (j..m).map(|i| b.get(i, j).raw()).collect();
if let Some((v_hh, vtv)) = householder_vector(&column)? {
for c in j..n {
reflect_column(&mut b, c, j, &v_hh, vtv)?;
}
for r in 0..m {
reflect_row(&mut u_acc, r, j, &v_hh, vtv)?;
}
}
if j + 1 < n {
let row: Vec<BinaryStorage> = (j + 1..n).map(|c| b.get(j, c).raw()).collect();
if let Some((v_hh, vtv)) = householder_vector(&row)? {
for r in j..m {
reflect_row(&mut b, r, j + 1, &v_hh, vtv)?;
}
for r in 0..n {
reflect_row(&mut v_acc, r, j + 1, &v_hh, vtv)?;
}
}
}
}
let mut d: Vec<ComputeStorage> = (0..n).map(|i| upscale_to_compute(b.get(i, i).raw())).collect();
let mut e: Vec<ComputeStorage> = (0..n.saturating_sub(1)).map(|i| upscale_to_compute(b.get(i, i + 1).raw())).collect();
let zero = make_compute_int(0);
let floor = noise_floor();
let max_iter = iterations_per_n_squared * n * n;
let mut iter_count = 0usize;
let mut q_end = n;
let mut stall_block = (usize::MAX, usize::MAX);
let mut stall_best: Vec<ComputeStorage> = Vec::new();
let mut stall_count = 0usize;
loop {
while q_end > 1
&& compute_within(e[q_end - 2], deflation_threshold(storage_magnitude(d[q_end - 1], d[q_end - 2])?))
{
q_end -= 1;
}
if q_end <= 1 {
break;
}
if iter_count >= max_iter {
return Err(OverflowDetected::PrecisionLimit);
}
let q = q_end - 1;
let mut p = q;
while p > 0 && !compute_within(e[p - 1], deflation_threshold(storage_magnitude(d[p], d[p - 1])?)) {
p -= 1;
}
let magnitudes: Vec<ComputeStorage> =
(p..q).map(|k| compute_abs(e[k])).chain((p..=q).map(|k| compute_abs(d[k]))).collect();
let mut forced_zero: Option<usize> = None;
if stall_block != (p, q) {
stall_block = (p, q);
stall_best = magnitudes;
stall_count = 0;
} else {
let mut improved = false;
for (best, magnitude) in stall_best.iter_mut().zip(magnitudes) {
if magnitude < *best {
*best = magnitude;
improved = true;
}
}
stall_count = if improved { 0 } else { stall_count + 1 };
}
if stall_count >= STAGNATION_SWEEPS {
stall_count = 0;
let ie = (p..q).min_by_key(|&k| compute_abs(e[k])).expect("active block has a superdiagonal entry");
let id = (p..=q).min_by_key(|&k| compute_abs(d[k])).expect("active block has a diagonal entry");
let e_ok = compute_within(e[ie], stagnation_threshold(storage_magnitude(d[ie], d[ie + 1])?));
let mut neighbour = zero;
if id > 0 {
neighbour = neighbour.max(compute_abs(d[id - 1])).max(compute_abs(e[id - 1]));
}
if id < q {
neighbour = neighbour.max(compute_abs(e[id]));
}
let d_ok = compute_within(d[id], stagnation_threshold(storage_magnitude(neighbour, zero)?));
if e_ok && (!d_ok || compute_abs(e[ie]) <= compute_abs(d[id])) {
e[ie] = zero;
iter_count += 1;
continue;
}
if d_ok {
forced_zero = Some(id);
}
}
if compute_within(d[q], floor) || forced_zero == Some(q) {
d[q] = zero;
let mut bulge = e[q - 1];
e[q - 1] = zero;
for j in (p..q).rev() {
let rot = Rotation::zeroing_compute(d[j], bulge)?;
d[j] = rot.combine_compute(d[j], bulge)?;
if j > p {
bulge = rot.neg_sin_times_compute(e[j - 1])?;
e[j - 1] = rot.cos_times_compute(e[j - 1])?;
}
rotate_columns(&mut v_acc, j, q, &rot)?;
}
iter_count += 1;
continue;
}
if let Some(i) = (p..q).find(|&i| compute_within(d[i], floor) || forced_zero == Some(i)) {
d[i] = zero;
let mut bulge = e[i];
e[i] = zero;
for j in (i + 1)..=q {
let rot = Rotation::zeroing_compute(d[j], bulge)?;
d[j] = rot.combine_compute(d[j], bulge)?;
if j < q {
bulge = rot.neg_sin_times_compute(e[j])?;
e[j] = rot.cos_times_compute(e[j])?;
}
rotate_columns(&mut u_acc, j, i, &rot)?;
}
iter_count += 1;
continue;
}
let shift = wilkinson_shift(d[q - 1], e[q - 1], d[q], if q >= 2 { Some(e[q - 2]) } else { None })?;
let mut x = compute_checked_add(compute_product(d[p], d[p])?, compute_negate(shift))?;
let mut z = compute_product(d[p], e[p])?;
for i in p..q {
let rot = Rotation::zeroing_compute(x, z)?;
if i > p {
e[i - 1] = rot.combine_compute(e[i - 1], z)?;
}
(d[i], e[i]) = rot.apply_compute(d[i], e[i])?;
let bulge = rot.sin_times_compute(d[i + 1])?;
d[i + 1] = rot.cos_times_compute(d[i + 1])?;
rotate_columns(&mut v_acc, i, i + 1, &rot)?;
let rot2 = Rotation::zeroing_compute(d[i], bulge)?;
d[i] = rot2.combine_compute(d[i], bulge)?;
(e[i], d[i + 1]) = rot2.apply_compute(e[i], d[i + 1])?;
rotate_columns(&mut u_acc, i, i + 1, &rot2)?;
if i + 1 < q {
x = e[i];
z = rot2.sin_times_compute(e[i + 1])?;
e[i + 1] = rot2.cos_times_compute(e[i + 1])?;
}
}
iter_count += 1;
}
let mut values: Vec<FixedPoint> = Vec::with_capacity(n);
for i in 0..n {
values.push(FixedPoint::from_raw(downscale_to_storage(compute_abs(d[i]))?));
if compute_is_negative(&d[i]) {
for r in 0..n {
v_acc.set(r, i, -v_acc.get(r, i));
}
}
}
let mut indices: Vec<usize> = (0..n).collect();
indices.sort_by(|&a, &b| values[b].cmp(&values[a]));
let mut sigma = FixedVector::new(n);
let mut u_sorted = FixedMatrix::new(m, m);
let mut vt_sorted = FixedMatrix::new(n, n);
for (new_idx, &old_idx) in indices.iter().enumerate() {
sigma[new_idx] = values[old_idx];
for r in 0..m {
u_sorted.set(r, new_idx, u_acc.get(r, old_idx));
}
for r in 0..n {
vt_sorted.set(new_idx, r, v_acc.get(r, old_idx));
}
}
for new_idx in n..m {
for r in 0..m {
u_sorted.set(r, new_idx, u_acc.get(r, new_idx));
}
}
Ok(SVDDecomposition {
u: u_sorted,
sigma,
vt: vt_sorted,
})
}
fn wilkinson_shift(
d_prev: ComputeStorage, e_last: ComputeStorage, d_last: ComputeStorage, e_prev: Option<ComputeStorage>,
) -> Result<ComputeStorage, OverflowDetected> {
let e_prev_sq = match e_prev {
Some(ep) => compute_product(ep, ep)?,
None => make_compute_int(0),
};
let f = compute_checked_add(compute_product(d_prev, d_prev)?, e_prev_sq)?;
let g = compute_checked_add(compute_product(d_last, d_last)?, compute_product(e_last, e_last)?)?;
let h = compute_product(d_prev, e_last)?;
let diff = compute_halve(compute_checked_add(f, compute_negate(g))?);
if compute_is_zero(&diff) && compute_is_zero(&h) {
return Ok(g);
}
let disc = compute_hypot(diff, h)?;
let denom = if compute_is_negative(&diff) {
compute_checked_add(diff, compute_negate(disc))?
} else {
compute_checked_add(diff, disc)?
};
let ratio = compute_checked_divide(h, denom)?;
compute_checked_add(g, compute_negate(compute_multiply(h, ratio)))
}
fn storage_magnitude(a: ComputeStorage, b: ComputeStorage) -> Result<FixedPoint, OverflowDetected> {
Ok(FixedPoint::from_raw(downscale_to_storage(compute_abs(a).max(compute_abs(b)))?))
}
#[inline]
fn compute_within(v: ComputeStorage, bound: FixedPoint) -> bool {
compute_abs(v) <= upscale_to_compute(bound.raw())
}
#[derive(Clone, Debug)]
pub struct SchurDecomposition {
pub q: FixedMatrix,
pub t: FixedMatrix,
}
pub fn schur_decompose(a: &FixedMatrix) -> Result<SchurDecomposition, OverflowDetected> {
schur_decompose_within(a, ITERATIONS_PER_N_SQUARED)
}
fn schur_decompose_within(a: &FixedMatrix, iterations_per_n_squared: usize) -> Result<SchurDecomposition, OverflowDetected> {
assert!(a.is_square(), "schur_decompose: matrix must be square");
let n = a.rows();
if n <= 1 {
return Ok(SchurDecomposition {
q: FixedMatrix::identity(n),
t: a.clone(),
});
}
let mut h = a.clone();
let mut q_acc = FixedMatrix::identity(n);
for k in 0..n.saturating_sub(2) {
let start = k + 1;
let column: Vec<BinaryStorage> = (start..n).map(|i| h.get(i, k).raw()).collect();
let (v_hh, vtv) = match householder_vector(&column)? {
Some(reflector) => reflector,
None => continue,
};
for c in k..n {
reflect_column(&mut h, c, start, &v_hh, vtv)?;
}
for r in 0..n {
reflect_row(&mut h, r, start, &v_hh, vtv)?;
}
for r in 0..n {
reflect_row(&mut q_acc, r, start, &v_hh, vtv)?;
}
for i in (start + 1)..n {
h.set(i, k, FixedPoint::ZERO);
}
}
let max_iter = iterations_per_n_squared * n * n;
let mut iter_count = 0usize;
let mut its = 0usize; let mut nn = n;
let mut stall_block = (usize::MAX, usize::MAX);
let mut stall_best: Vec<FixedPoint> = Vec::new();
let mut stall_count = 0usize;
while nn > 0 {
let mut l = nn - 1;
while l > 0 {
let bound = deflation_threshold(h.get(l, l).abs().max(h.get(l - 1, l - 1).abs()));
if h.get(l, l - 1).abs() <= bound {
h.set(l, l - 1, FixedPoint::ZERO);
break;
}
l -= 1;
}
match nn - l {
1 => {
nn -= 1;
its = 0;
continue;
}
2 => {
split_real_block(&mut h, &mut q_acc, l)?;
nn -= 2;
its = 0;
continue;
}
_ => {}
}
if iter_count >= max_iter {
return Err(OverflowDetected::PrecisionLimit);
}
let subdiagonal: Vec<FixedPoint> = ((l + 1)..nn).map(|i| h.get(i, i - 1).abs()).collect();
if stall_block != (l, nn) {
stall_block = (l, nn);
stall_best = subdiagonal.clone();
stall_count = 0;
} else {
let mut improved = false;
for (best, entry) in stall_best.iter_mut().zip(&subdiagonal) {
if *entry < *best {
*best = *entry;
improved = true;
}
}
stall_count = if improved { 0 } else { stall_count + 1 };
}
if its >= SCHUR_FLOOR_ITERATIONS && stall_count >= STAGNATION_SWEEPS {
let i = ((l + 1)..nn).min_by_key(|&i| h.get(i, i - 1).abs()).expect("block of size >= 3");
if h.get(i, i - 1).abs() <= stagnation_threshold(h.get(i, i).abs().max(h.get(i - 1, i - 1).abs())) {
h.set(i, i - 1, FixedPoint::ZERO);
its = 0;
stall_count = 0;
iter_count += 1;
continue;
}
}
let (trace, det) = if its > 0 && its % 10 == 0 {
exceptional_shifts(&h, l, nn, its)?
} else {
trailing_shifts(&h, nn)?
};
francis_step(&mut h, &mut q_acc, l, nn, trace, det)?;
its += 1;
iter_count += 1;
}
Ok(SchurDecomposition { q: q_acc, t: h })
}
fn trailing_shifts(h: &FixedMatrix, nn: usize) -> Result<(ComputeStorage, ComputeStorage), OverflowDetected> {
let (a, b) = (h.get(nn - 2, nn - 2), h.get(nn - 2, nn - 1));
let (c, d) = (h.get(nn - 1, nn - 2), h.get(nn - 1, nn - 1));
let trace = compute_checked_add(upscale_to_compute(a.raw()), upscale_to_compute(d.raw()))?;
let det = compute_checked_add(exact_product(a.raw(), d.raw()), compute_negate(exact_product(b.raw(), c.raw())))?;
Ok((trace, det))
}
fn exceptional_shifts(h: &FixedMatrix, l: usize, nn: usize, its: usize) -> Result<(ComputeStorage, ComputeStorage), OverflowDetected> {
let (s, anchor) = if its % 20 == 10 {
(checked_add_fp(h.get(l + 1, l).abs(), h.get(l + 2, l + 1).abs())?, h.get(l, l))
} else {
(checked_add_fp(h.get(nn - 1, nn - 2).abs(), h.get(nn - 2, nn - 3).abs())?, h.get(nn - 1, nn - 1))
};
let s_c = upscale_to_compute(s.raw());
let three_quarters = compute_divide(make_compute_int(3), make_compute_int(4))?;
let seven_sixteenths = compute_divide(make_compute_int(7), make_compute_int(16))?;
let w = compute_checked_add(upscale_to_compute(anchor.raw()), compute_multiply(three_quarters, s_c))?;
let trace = compute_checked_add(w, w)?;
let det = compute_checked_add(
compute_multiply(w, w),
compute_multiply(seven_sixteenths, compute_multiply(s_c, s_c)),
)?;
Ok((trace, det))
}
fn francis_step(
h: &mut FixedMatrix, q_acc: &mut FixedMatrix, l: usize, nn: usize,
trace: ComputeStorage, det: ComputeStorage,
) -> Result<(), OverflowDetected> {
let n = h.rows();
let (h11, h12, h21) = (h.get(l, l), h.get(l, l + 1), h.get(l + 1, l));
let (h22, h32) = (h.get(l + 1, l + 1), h.get(l + 2, l + 1));
let h11_c = upscale_to_compute(h11.raw());
let x = compute_sum(&[
exact_product(h11.raw(), h11.raw()),
exact_product(h12.raw(), h21.raw()),
compute_negate(compute_multiply(trace, h11_c)),
det,
])?;
let y = compute_multiply(
upscale_to_compute(h21.raw()),
compute_sum(&[h11_c, upscale_to_compute(h22.raw()), compute_negate(trace)])?,
);
let z = exact_product(h21.raw(), h32.raw());
let norm = compute_hypot(compute_hypot(x, y)?, z)?;
if compute_is_zero(&norm) {
return Ok(());
}
let lead = if compute_is_negative(&x) {
compute_checked_add(x, compute_negate(norm))?
} else {
compute_checked_add(x, norm)?
};
let v_hh = direction_to_storage(&[lead, y, z])?;
let vtv = exact_dot(&v_hh, &v_hh)?;
if !compute_is_zero(&vtv) {
for c in l..n {
reflect_column(h, c, l, &v_hh, vtv)?;
}
for r in 0..nn.min(l + 4) {
reflect_row(h, r, l, &v_hh, vtv)?;
}
for r in 0..n {
reflect_row(q_acc, r, l, &v_hh, vtv)?;
}
}
for k in (l + 1)..(nn - 1) {
if k + 2 < nn {
let column: Vec<BinaryStorage> = (k..k + 3).map(|i| h.get(i, k - 1).raw()).collect();
if let Some((v_hh, vtv)) = householder_vector(&column)? {
for c in (k - 1)..n {
reflect_column(h, c, k, &v_hh, vtv)?;
}
for r in 0..nn.min(k + 4) {
reflect_row(h, r, k, &v_hh, vtv)?;
}
for r in 0..n {
reflect_row(q_acc, r, k, &v_hh, vtv)?;
}
}
h.set(k + 1, k - 1, FixedPoint::ZERO);
h.set(k + 2, k - 1, FixedPoint::ZERO);
} else {
let rot = Rotation::zeroing(h.get(k, k - 1), h.get(k + 1, k - 1))?;
for c in (k - 1)..n {
let (top, bottom) = rot.apply(h.get(k, c), h.get(k + 1, c))?;
h.set(k, c, top);
h.set(k + 1, c, bottom);
}
for r in 0..nn {
let (left, right) = rot.apply(h.get(r, k), h.get(r, k + 1))?;
h.set(r, k, left);
h.set(r, k + 1, right);
}
rotate_columns(q_acc, k, k + 1, &rot)?;
h.set(k + 1, k - 1, FixedPoint::ZERO);
}
}
Ok(())
}
fn split_real_block(h: &mut FixedMatrix, q_acc: &mut FixedMatrix, i: usize) -> Result<(), OverflowDetected> {
let n = h.rows();
let (a, b) = (h.get(i, i), h.get(i, i + 1));
let (c, d) = (h.get(i + 1, i), h.get(i + 1, i + 1));
if c.is_zero() {
return Ok(());
}
let p = compute_halve(compute_checked_add(upscale_to_compute(a.raw()), compute_negate(upscale_to_compute(d.raw())))?);
let disc = compute_checked_add(compute_multiply(p, p), exact_product(b.raw(), c.raw()))?;
if compute_is_negative(&disc) {
return Ok(());
}
let root = sqrt_at_compute_tier(disc);
let lead = if compute_is_negative(&p) {
compute_checked_add(p, compute_negate(root))?
} else {
compute_checked_add(p, root)?
};
let rot = Rotation::zeroing_compute(lead, upscale_to_compute(c.raw()))?;
for col in i..n {
let (top, bottom) = rot.apply(h.get(i, col), h.get(i + 1, col))?;
h.set(i, col, top);
h.set(i + 1, col, bottom);
}
for row in 0..(i + 2) {
let (left, right) = rot.apply(h.get(row, i), h.get(row, i + 1))?;
h.set(row, i, left);
h.set(row, i + 1, right);
}
rotate_columns(q_acc, i, i + 1, &rot)?;
h.set(i + 1, i, FixedPoint::ZERO);
Ok(())
}
fn direction_to_storage(values: &[ComputeStorage]) -> Result<Vec<BinaryStorage>, OverflowDetected> {
let largest = |vs: &[ComputeStorage]| {
let mut big = make_compute_int(0);
for value in vs {
let magnitude = compute_abs(*value);
if magnitude > big {
big = magnitude;
}
}
big
};
let mut scaled = values.to_vec();
if compute_is_zero(&largest(&scaled)) {
return Ok(vec![FixedPoint::ZERO.raw(); values.len()]);
}
let limit = widen_storage(direction_magnitude());
while largest(&scaled) > limit {
for value in scaled.iter_mut() {
*value = compute_halve(*value);
}
}
let half_limit = compute_halve(limit);
while largest(&scaled) <= half_limit {
for value in scaled.iter_mut() {
*value = compute_checked_add(*value, *value)?;
}
}
Ok(scaled.into_iter().map(compute_to_storage_exact).collect())
}
#[inline]
fn direction_magnitude() -> BinaryStorage {
#[cfg(table_format = "q16_16")]
{ 1i32 << (2 * crate::fixed_point::frac_config::FRAC_BITS).min(29) }
#[cfg(table_format = "q32_32")]
{ 1i64 << 61 }
#[cfg(table_format = "q64_64")]
{ 1i128 << 125 }
#[cfg(table_format = "q128_128")]
{ crate::fixed_point::I256::from_i128(1) << 253usize }
#[cfg(table_format = "q256_256")]
{ crate::fixed_point::I512::from_i128(1) << 509usize }
}
#[inline]
fn compute_to_storage_exact(v: ComputeStorage) -> BinaryStorage {
#[cfg(table_format = "q16_16")]
{ v as i32 }
#[cfg(table_format = "q32_32")]
{ v as i64 }
#[cfg(table_format = "q64_64")]
{ v.as_i128() }
#[cfg(table_format = "q128_128")]
{ v.as_i256() }
#[cfg(table_format = "q256_256")]
{ v.as_i512() }
}
#[cfg(test)]
mod tests {
use super::*;
fn int_matrix(rows: &[&[i32]]) -> FixedMatrix {
FixedMatrix::from_fn(rows.len(), rows[0].len(), |i, j| FixedPoint::from_int(rows[i][j]))
}
#[test]
fn iteration_budget_exhaustion_is_an_error() {
let a = int_matrix(&[&[4, 1, 2], &[1, 3, 1], &[2, 1, 5]]);
assert_eq!(eigen_symmetric_within(&a, 0).unwrap_err(), OverflowDetected::PrecisionLimit);
assert_eq!(svd_decompose_within(&a, 0).unwrap_err(), OverflowDetected::PrecisionLimit);
assert_eq!(schur_decompose_within(&a, 0).unwrap_err(), OverflowDetected::PrecisionLimit);
assert!(eigen_symmetric(&a).is_ok());
assert!(svd_decompose(&a).is_ok());
assert!(schur_decompose(&a).is_ok());
}
}