use super::FixedPoint;
use super::FixedVector;
use super::FixedMatrix;
use super::compute_matrix::{compute_lu_decompose, ComputeLU, ComputeMatrix};
use super::linalg::{
compute_tier_sub_dot_compute, upscale_to_compute, round_to_storage, compute_abs, compute_product,
householder_vector_compute, reflect_compute, ComputeStorage,
compute_deflation_threshold, compute_noise_floor, compute_stagnation_threshold,
compute_scale_up, downscale_shifted_to_storage, scale_up_exponent,
Rotation, STAGNATION_SWEEPS,
};
use super::wide_acc::{exact_dot_compute, exact_sub_dot_compute, narrow_product_to_compute};
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,
compute: ComputeLU,
}
pub fn lu_decompose(a: &FixedMatrix) -> Result<LUDecomposition, OverflowDetected> {
assert!(a.is_square(), "lu_decompose: matrix must be square");
let compute = compute_lu_decompose(&ComputeMatrix::from_fixed_matrix(a))?;
Ok(LUDecomposition {
l: narrow_matrix(compute.l())?,
u: narrow_matrix(compute.u())?,
perm: compute.perm().to_vec(),
num_swaps: compute.num_swaps(),
compute,
})
}
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 bc: Vec<ComputeStorage> = (0..n).map(|i| upscale_to_compute(b[i].raw())).collect();
narrow_vector(&self.compute.solve(&bc)?)
}
pub fn determinant(&self) -> FixedPoint {
FixedPoint::from_raw(round_to_storage(self.compute.determinant()))
}
pub fn refine(&self, a: &FixedMatrix, b: &FixedVector, x: &FixedVector) -> Result<FixedVector, OverflowDetected> {
let n = a.rows();
let x_raw: Vec<BinaryStorage> = (0..n).map(|j| x[j].raw()).collect();
let r: Vec<ComputeStorage> = (0..n)
.map(|i| compute_tier_sub_dot_compute(b[i].raw(), &a.row_raw_range(i, 0, n), &x_raw))
.collect();
let dx = self.compute.solve(&r)?;
let refined: Result<Vec<ComputeStorage>, OverflowDetected> = (0..n)
.map(|i| compute_checked_add(upscale_to_compute(x[i].raw()), dx[i]))
.collect();
narrow_vector(&refined?)
}
pub fn inverse(&self) -> Result<FixedMatrix, OverflowDetected> {
narrow_matrix(&self.compute.inverse()?)
}
}
fn narrow_matrix(c: &ComputeMatrix) -> Result<FixedMatrix, OverflowDetected> {
let mut out = FixedMatrix::new(c.rows(), c.cols());
for i in 0..c.rows() {
for j in 0..c.cols() {
out.set(i, j, FixedPoint::from_raw(downscale_to_storage(c.get(i, j))?));
}
}
Ok(out)
}
fn narrow_vector(c: &[ComputeStorage]) -> Result<FixedVector, OverflowDetected> {
let values: Result<Vec<FixedPoint>, OverflowDetected> =
c.iter().map(|v| downscale_to_storage(*v).map(FixedPoint::from_raw)).collect();
Ok(FixedVector::from_slice(&values?))
}
#[derive(Clone, Debug)]
pub struct QRDecomposition {
pub q: FixedMatrix,
pub r: FixedMatrix,
compute_q: ComputeMatrix,
compute_r: ComputeMatrix,
}
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 = ComputeMatrix::from_fixed_matrix(a);
let mut q = ComputeMatrix::identity(m);
for k in 0..n {
let x: Vec<ComputeStorage> = (k..m).map(|i| r.get(i, k)).collect();
let Some((v, vv)) = householder_vector_compute(&x)? else { continue };
for j in k..n {
let mut col: Vec<ComputeStorage> = (k..m).map(|i| r.get(i, j)).collect();
reflect_compute(&mut col, &v, vv)?;
for (i, c) in (k..m).zip(col) {
r.set(i, j, c);
}
}
for i in (k + 1)..m {
r.set(i, k, make_compute_int(0));
}
for i in 0..m {
let mut row: Vec<ComputeStorage> = (k..m).map(|j| q.get(i, j)).collect();
reflect_compute(&mut row, &v, vv)?;
for (j, c) in (k..m).zip(row) {
q.set(i, j, c);
}
}
}
Ok(QRDecomposition { q: narrow_matrix(&q)?, r: narrow_matrix(&r)?, compute_q: q, compute_r: 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 bc: Vec<ComputeStorage> = (0..m).map(|j| upscale_to_compute(b[j].raw())).collect();
let mut qtb = Vec::with_capacity(m);
for i in 0..m {
let q_col: Vec<ComputeStorage> = (0..m).map(|j| self.compute_q.get(j, i)).collect();
qtb.push(narrow_product_to_compute(exact_dot_compute(&q_col, &bc)?)?);
}
let mut x = vec![make_compute_int(0); n];
for i in (0..n).rev() {
let diag = self.compute_r.get(i, i);
if compute_is_zero(&diag) {
return Err(OverflowDetected::DivisionByZero);
}
let r_row: Vec<ComputeStorage> = (i + 1..n).map(|j| self.compute_r.get(i, j)).collect();
let numerator = exact_sub_dot_compute(qtb[i], &r_row, &x[i + 1..n])?;
x[i] = compute_divide(numerator, diag)?;
}
narrow_vector(&x)
}
}
#[derive(Clone, Debug)]
pub struct CholeskyDecomposition {
pub l: FixedMatrix,
compute: ComputeMatrix,
}
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 lc = ComputeMatrix::new(n, n);
for i in 0..n {
let row_i: Vec<ComputeStorage> = (0..i).map(|k| lc.get(i, k)).collect();
let diag = exact_sub_dot_compute(upscale_to_compute(a.get(i, i).raw()), &row_i, &row_i)?;
if compute_is_negative(&diag) || compute_is_zero(&diag) {
return Err(OverflowDetected::DomainError);
}
let l_ii = sqrt_at_compute_tier(diag);
lc.set(i, i, l_ii);
for j in (i + 1)..n {
let row_j: Vec<ComputeStorage> = (0..i).map(|k| lc.get(j, k)).collect();
let numerator = exact_sub_dot_compute(upscale_to_compute(a.get(j, i).raw()), &row_j, &row_i)?;
lc.set(j, i, compute_divide(numerator, l_ii)?);
}
}
Ok(CholeskyDecomposition { l: narrow_matrix(&lc)?, compute: lc })
}
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 l = &self.compute;
let mut y = vec![make_compute_int(0); n];
for i in 0..n {
let l_row: Vec<ComputeStorage> = (0..i).map(|k| l.get(i, k)).collect();
let numerator = exact_sub_dot_compute(upscale_to_compute(b[i].raw()), &l_row, &y[..i])?;
y[i] = compute_divide(numerator, l.get(i, i))?;
}
let mut x = vec![make_compute_int(0); n];
for i in (0..n).rev() {
let lt_row: Vec<ComputeStorage> = (i + 1..n).map(|k| l.get(k, i)).collect();
let numerator = exact_sub_dot_compute(y[i], <_row, &x[i + 1..n])?;
x[i] = compute_divide(numerator, l.get(i, i))?;
}
narrow_vector(&x)
}
pub fn determinant(&self) -> FixedPoint {
let n = self.l.rows();
let mut det_l = make_compute_int(1);
for i in 0..n {
det_l = compute_multiply(det_l, self.compute.get(i, i));
}
FixedPoint::from_raw(round_to_storage(compute_multiply(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_compute_column(
mat: &mut ComputeMatrix, col: usize, start: usize, v: &[ComputeStorage], v_dot_v: ComputeStorage,
) -> Result<(), OverflowDetected> {
let mut w: Vec<ComputeStorage> = (start..start + v.len()).map(|i| mat.get(i, col)).collect();
reflect_compute(&mut w, v, v_dot_v)?;
for (k, value) in w.into_iter().enumerate() {
mat.set(start + k, col, value);
}
Ok(())
}
fn reflect_compute_row(
mat: &mut ComputeMatrix, row: usize, start: usize, v: &[ComputeStorage], v_dot_v: ComputeStorage,
) -> Result<(), OverflowDetected> {
let mut w: Vec<ComputeStorage> = (start..start + v.len()).map(|c| mat.get(row, c)).collect();
reflect_compute(&mut w, v, v_dot_v)?;
for (k, value) in w.into_iter().enumerate() {
mat.set(row, start + k, value);
}
Ok(())
}
fn rotate_compute_columns(mat: &mut ComputeMatrix, a: usize, b: usize, rot: &Rotation) -> Result<(), OverflowDetected> {
for r in 0..mat.rows() {
let (new_a, new_b) = rot.apply_compute(mat.get(r, a), mat.get(r, b))?;
mat.set(r, a, new_a);
mat.set(r, b, new_b);
}
Ok(())
}
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 = ComputeMatrix::from_fixed_matrix(a);
let mut v = ComputeMatrix::identity(n);
let mut converged = false;
let mut best_off: Option<ComputeStorage> = 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 = compute_deflation_threshold(compute_abs(s.get(p, p)).max(compute_abs(s.get(q, q))));
if compute_abs(s.get(p, q)) > 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 !compute_is_zero(&largest) {
jacobi_rotate(&mut s, &mut v, p, q)?;
}
let mut eigen_pairs: Vec<(FixedPoint, usize)> = Vec::with_capacity(n);
for i in 0..n {
eigen_pairs.push((FixedPoint::from_raw(downscale_to_storage(s.get(i, i))?), i));
}
eigen_pairs.sort_by(|a, b| b.0.abs().partial_cmp(&a.0.abs()).unwrap_or(std::cmp::Ordering::Equal));
let vectors_all = narrow_matrix(&v)?;
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, vectors_all.get(r, *orig_idx));
}
}
Ok(EigenDecomposition { values, vectors })
}
fn largest_off_diagonal(s: &ComputeMatrix) -> (ComputeStorage, usize, usize) {
let n = s.rows();
let (mut largest, mut at_p, mut at_q) = (make_compute_int(0), 0, 1);
for p in 0..n {
for q in (p + 1)..n {
let value = compute_abs(s.get(p, q));
if value > largest {
largest = value;
at_p = p;
at_q = q;
}
}
}
(largest, at_p, at_q)
}
fn off_diagonal_within_stagnation_bound(s: &ComputeMatrix) -> bool {
let n = s.rows();
(0..n).all(|p| {
((p + 1)..n).all(|q| {
compute_abs(s.get(p, q)) <= compute_stagnation_threshold(compute_abs(s.get(p, p)).max(compute_abs(s.get(q, q))))
})
})
}
fn jacobi_rotate(s: &mut ComputeMatrix, v: &mut ComputeMatrix, 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 compute_is_zero(&a_pq) {
return Ok(());
}
let one = make_compute_int(1);
let num = compute_checked_add(a_pp, compute_negate(a_qq))?;
let den = compute_checked_add(a_pq, a_pq)?;
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_compute(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 = compute_product(t, a_pq)?;
s.set(p, p, compute_checked_add(a_pp, shift)?);
s.set(q, q, compute_checked_add(a_qq, compute_negate(shift))?);
s.set(p, q, make_compute_int(0));
s.set(q, p, make_compute_int(0));
for r in 0..v.rows() {
let (new_a, new_b) = rot.apply_compute(v.get(r, p), v.get(r, q))?;
v.set(r, p, new_a);
v.set(r, q, new_b);
}
Ok(())
}
#[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 = ComputeMatrix::from_fixed_matrix(a);
let mut u_acc = ComputeMatrix::identity(m);
let mut v_acc = ComputeMatrix::identity(n);
for j in 0..n {
let column: Vec<ComputeStorage> = (j..m).map(|i| b.get(i, j)).collect();
if let Some((v_hh, vtv)) = householder_vector_compute(&column)? {
for c in j..n {
reflect_compute_column(&mut b, c, j, &v_hh, vtv)?;
}
for r in 0..m {
reflect_compute_row(&mut u_acc, r, j, &v_hh, vtv)?;
}
for i in (j + 1)..m {
b.set(i, j, make_compute_int(0));
}
}
if j + 1 < n {
let row: Vec<ComputeStorage> = (j + 1..n).map(|c| b.get(j, c)).collect();
if let Some((v_hh, vtv)) = householder_vector_compute(&row)? {
for r in j..m {
reflect_compute_row(&mut b, r, j + 1, &v_hh, vtv)?;
}
for r in 0..n {
reflect_compute_row(&mut v_acc, r, j + 1, &v_hh, vtv)?;
}
for c in (j + 2)..n {
b.set(j, c, make_compute_int(0));
}
}
}
}
let mut d: Vec<ComputeStorage> = (0..n).map(|i| b.get(i, i)).collect();
let mut e: Vec<ComputeStorage> = (0..n.saturating_sub(1)).map(|i| b.get(i, i + 1)).collect();
let zero = make_compute_int(0);
let mut exponent = vec![0u32; n];
let floor = compute_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_abs(e[q_end - 2]) <= compute_deflation_threshold(compute_abs(d[q_end - 1]).max(compute_abs(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_abs(e[p - 1]) > compute_deflation_threshold(compute_abs(d[p]).max(compute_abs(d[p - 1]))) {
p -= 1;
}
let k = scale_up_exponent(&d[p..=q].iter().chain(&e[p..q]).copied().collect::<Vec<_>>());
if k > 0 {
for i in p..=q {
d[i] = compute_scale_up(d[i], k);
exponent[i] += k;
}
for i in p..q {
e[i] = compute_scale_up(e[i], k);
}
if stall_block == (p, q) {
for best in stall_best.iter_mut() {
*best = compute_scale_up(*best, k);
}
}
}
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_abs(e[ie]) <= compute_stagnation_threshold(compute_abs(d[ie]).max(compute_abs(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_abs(d[id]) <= compute_stagnation_threshold(neighbour);
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_abs(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_compute_columns(&mut v_acc, j, q, &rot)?;
}
iter_count += 1;
continue;
}
if let Some(i) = (p..q).find(|&i| compute_abs(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_compute_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_compute_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_compute_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_shifted_to_storage(compute_abs(d[i]), exponent[i])?));
if compute_is_negative(&d[i]) {
for r in 0..n {
v_acc.set(r, i, compute_negate(v_acc.get(r, i)));
}
}
}
let (u_acc, v_acc) = (narrow_matrix(&u_acc)?, narrow_matrix(&v_acc)?);
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)))
}
#[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 = ComputeMatrix::from_fixed_matrix(a);
let mut q_acc = ComputeMatrix::identity(n);
let zero = make_compute_int(0);
for k in 0..n.saturating_sub(2) {
let start = k + 1;
let column: Vec<ComputeStorage> = (start..n).map(|i| h.get(i, k)).collect();
let (v_hh, vtv) = match householder_vector_compute(&column)? {
Some(reflector) => reflector,
None => continue,
};
for c in k..n {
reflect_compute_column(&mut h, c, start, &v_hh, vtv)?;
}
for r in 0..n {
reflect_compute_row(&mut h, r, start, &v_hh, vtv)?;
}
for r in 0..n {
reflect_compute_row(&mut q_acc, r, start, &v_hh, vtv)?;
}
for i in (start + 1)..n {
h.set(i, k, 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<ComputeStorage> = Vec::new();
let mut stall_count = 0usize;
while nn > 0 {
let mut l = nn - 1;
while l > 0 {
let bound = compute_deflation_threshold(compute_abs(h.get(l, l)).max(compute_abs(h.get(l - 1, l - 1))));
if compute_abs(h.get(l, l - 1)) <= bound {
h.set(l, l - 1, 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<ComputeStorage> = ((l + 1)..nn).map(|i| compute_abs(h.get(i, i - 1))).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| compute_abs(h.get(i, i - 1))).expect("block of size >= 3");
if compute_abs(h.get(i, i - 1))
<= compute_stagnation_threshold(compute_abs(h.get(i, i)).max(compute_abs(h.get(i - 1, i - 1))))
{
h.set(i, i - 1, zero);
its = 0;
stall_count = 0;
iter_count += 1;
continue;
}
}
let block: Vec<ComputeStorage> = (l..nn).flat_map(|i| (l..nn).map(move |j| (i, j))).map(|(i, j)| h.get(i, j)).collect();
let k = scale_up_exponent(&block);
let (trace, det) = if its > 0 && its % 10 == 0 {
exceptional_shifts(&h, l, nn, its, k)?
} else {
trailing_shifts(&h, nn, k)?
};
francis_step(&mut h, &mut q_acc, l, nn, trace, det, k)?;
its += 1;
iter_count += 1;
}
Ok(SchurDecomposition { q: narrow_matrix(&q_acc)?, t: narrow_matrix(&h)? })
}
fn trailing_shifts(h: &ComputeMatrix, nn: usize, k: u32) -> Result<(ComputeStorage, ComputeStorage), OverflowDetected> {
let hs = |i: usize, j: usize| compute_scale_up(h.get(i, j), k);
let (a, b) = (hs(nn - 2, nn - 2), hs(nn - 2, nn - 1));
let (c, d) = (hs(nn - 1, nn - 2), hs(nn - 1, nn - 1));
let trace = compute_checked_add(a, d)?;
let det = exact_sub_dot_compute(make_compute_int(0), &[b, compute_negate(a)], &[c, d])?;
Ok((trace, det))
}
fn exceptional_shifts(h: &ComputeMatrix, l: usize, nn: usize, its: usize, k: u32) -> Result<(ComputeStorage, ComputeStorage), OverflowDetected> {
let hs = |i: usize, j: usize| compute_scale_up(h.get(i, j), k);
let (s_c, anchor) = if its % 20 == 10 {
(compute_checked_add(compute_abs(hs(l + 1, l)), compute_abs(hs(l + 2, l + 1)))?, hs(l, l))
} else {
(compute_checked_add(compute_abs(hs(nn - 1, nn - 2)), compute_abs(hs(nn - 2, nn - 3)))?, hs(nn - 1, nn - 1))
};
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(anchor, 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 ComputeMatrix, q_acc: &mut ComputeMatrix, l: usize, nn: usize,
trace: ComputeStorage, det: ComputeStorage, k: u32,
) -> Result<(), OverflowDetected> {
let n = h.rows();
let zero = make_compute_int(0);
let hs = |i: usize, j: usize| compute_scale_up(h.get(i, j), k);
let (h11, h12, h21) = (hs(l, l), hs(l, l + 1), hs(l + 1, l));
let (h22, h32) = (hs(l + 1, l + 1), hs(l + 2, l + 1));
let x = exact_sub_dot_compute(det, &[compute_negate(h11), compute_negate(h12), trace], &[h11, h21, h11])?;
let y = compute_product(h21, compute_sum(&[h11, h22, compute_negate(trace)])?)?;
let z = compute_product(h21, h32)?;
if let Some((v_hh, vtv)) = householder_vector_compute(&[x, y, z])? {
for c in l..n {
reflect_compute_column(h, c, l, &v_hh, vtv)?;
}
for r in 0..nn.min(l + 4) {
reflect_compute_row(h, r, l, &v_hh, vtv)?;
}
for r in 0..n {
reflect_compute_row(q_acc, r, l, &v_hh, vtv)?;
}
}
for k in (l + 1)..(nn - 1) {
if k + 2 < nn {
let column: Vec<ComputeStorage> = (k..k + 3).map(|i| h.get(i, k - 1)).collect();
if let Some((v_hh, vtv)) = householder_vector_compute(&column)? {
for c in (k - 1)..n {
reflect_compute_column(h, c, k, &v_hh, vtv)?;
}
for r in 0..nn.min(k + 4) {
reflect_compute_row(h, r, k, &v_hh, vtv)?;
}
for r in 0..n {
reflect_compute_row(q_acc, r, k, &v_hh, vtv)?;
}
}
h.set(k + 1, k - 1, zero);
h.set(k + 2, k - 1, zero);
} else {
let rot = Rotation::zeroing_compute(h.get(k, k - 1), h.get(k + 1, k - 1))?;
for c in (k - 1)..n {
let (top, bottom) = rot.apply_compute(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_compute(h.get(r, k), h.get(r, k + 1))?;
h.set(r, k, left);
h.set(r, k + 1, right);
}
rotate_compute_columns(q_acc, k, k + 1, &rot)?;
h.set(k + 1, k - 1, zero);
}
}
Ok(())
}
fn split_real_block(h: &mut ComputeMatrix, q_acc: &mut ComputeMatrix, i: usize) -> Result<(), OverflowDetected> {
let n = h.rows();
if compute_is_zero(&h.get(i + 1, i)) {
return Ok(());
}
let k = scale_up_exponent(&[h.get(i, i), h.get(i, i + 1), h.get(i + 1, i), h.get(i + 1, i + 1)]);
let hs = |r: usize, c: usize| compute_scale_up(h.get(r, c), k);
let (a, b) = (hs(i, i), hs(i, i + 1));
let (c, d) = (hs(i + 1, i), hs(i + 1, i + 1));
let p = compute_halve(compute_checked_add(a, compute_negate(d))?);
let disc = exact_sub_dot_compute(make_compute_int(0), &[compute_negate(p), compute_negate(b)], &[p, c])?;
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, c)?;
for col in i..n {
let (top, bottom) = rot.apply_compute(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_compute(h.get(row, i), h.get(row, i + 1))?;
h.set(row, i, left);
h.set(row, i + 1, right);
}
rotate_compute_columns(q_acc, i, i + 1, &rot)?;
h.set(i + 1, i, make_compute_int(0));
Ok(())
}
#[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());
}
}