use crate::linear_algebra::{Matrix, Vector};
use crate::scalar::Numeric;
use crate::utils::error_codes::CalcError;
pub(crate) fn enorm<T: Numeric>(v: &[T]) -> T {
let rdwarf = T::MIN_POSITIVE.sqrt();
let rgiant = T::MAX.sqrt();
let agiant = rgiant / T::from_usize(v.len());
let mut small_sum = T::ZERO;
let mut mid_sum = T::ZERO;
let mut large_sum = T::ZERO;
let mut small_max = T::ZERO;
let mut large_max = T::ZERO;
for &value in v {
let a = value.abs();
if a > rdwarf && a < agiant {
mid_sum += a * a;
} else if a > rdwarf {
if a > large_max {
let ratio = large_max / a;
large_sum = T::ONE + large_sum * ratio * ratio;
large_max = a;
} else {
let ratio = a / large_max;
large_sum += ratio * ratio;
}
} else if a != T::ZERO {
if a > small_max {
let ratio = small_max / a;
small_sum = T::ONE + small_sum * ratio * ratio;
small_max = a;
} else {
let ratio = a / small_max;
small_sum += ratio * ratio;
}
}
}
if large_sum != T::ZERO {
large_max * (large_sum + (mid_sum / large_max) / large_max).sqrt()
} else if mid_sum != T::ZERO {
if mid_sum >= small_max {
(mid_sum * (T::ONE + (small_max / mid_sum) * (small_max * small_sum))).sqrt()
} else {
(small_max * ((mid_sum / small_max) + (small_max * small_sum))).sqrt()
}
} else {
small_max * small_sum.sqrt()
}
}
pub(crate) fn max<T: PartialOrd>(a: T, b: T) -> T {
if b > a { b } else { a }
}
pub(crate) fn min<T: PartialOrd>(a: T, b: T) -> T {
if b < a { b } else { a }
}
#[derive(Debug, Clone, Copy)]
#[must_use]
pub struct PivotedQr<const M: usize, const N: usize, T = f64> {
pub(crate) qr: Matrix<M, N, T>,
pub(crate) r_diag: [T; N],
pub(crate) column_norms: [T; N],
pub(crate) permutation: [usize; N],
}
impl<const M: usize, const N: usize, T: Numeric> PivotedQr<M, N, T> {
pub fn decompose(a: Matrix<M, N, T>) -> Result<Self, CalcError> {
if M < N {
return Err(CalcError::Underdetermined);
}
let mut qr = a;
let mut r_diag = [T::ZERO; N];
let mut column_norms = [T::ZERO; N];
let mut reference_norm = [T::ZERO; N];
let mut permutation = [0usize; N];
for j in 0..N {
let mut column = [T::ZERO; M];
for i in 0..M {
column[i] = qr[(i, j)];
}
let norm = enorm(&column);
column_norms[j] = norm;
r_diag[j] = norm;
reference_norm[j] = norm;
permutation[j] = j;
}
let epsmch = T::EPSILON;
let p05 = T::from_f64(0.05);
for j in 0..N {
let mut kmax = j;
for k in j..N {
if r_diag[k] > r_diag[kmax] {
kmax = k;
}
}
if kmax != j {
for i in 0..M {
let tmp = qr[(i, j)];
qr[(i, j)] = qr[(i, kmax)];
qr[(i, kmax)] = tmp;
}
r_diag[kmax] = r_diag[j];
reference_norm[kmax] = reference_norm[j];
permutation.swap(j, kmax);
}
let mut column = [T::ZERO; M];
for i in j..M {
column[i] = qr[(i, j)];
}
let mut ajnorm = enorm(&column[j..]);
if ajnorm == T::ZERO {
r_diag[j] = -ajnorm;
continue;
}
if qr[(j, j)] < T::ZERO {
ajnorm = -ajnorm;
}
for i in j..M {
qr[(i, j)] /= ajnorm;
}
qr[(j, j)] += T::ONE;
for k in (j + 1)..N {
let mut sum = T::ZERO;
for i in j..M {
sum += qr[(i, j)] * qr[(i, k)];
}
let factor = sum / qr[(j, j)];
for i in j..M {
let reflected = qr[(i, k)] - factor * qr[(i, j)];
qr[(i, k)] = reflected;
}
if r_diag[k] != T::ZERO {
let ratio = qr[(j, k)] / r_diag[k];
r_diag[k] *= max(T::ZERO, T::ONE - ratio * ratio).sqrt();
let relative = r_diag[k] / reference_norm[k];
if p05 * relative * relative <= epsmch {
let mut tail = [T::ZERO; M];
for i in (j + 1)..M {
tail[i] = qr[(i, k)];
}
r_diag[k] = enorm(&tail[(j + 1)..]);
reference_norm[k] = r_diag[k];
}
}
}
r_diag[j] = -ajnorm;
}
Ok(PivotedQr {
qr,
r_diag,
column_norms,
permutation,
})
}
pub fn r(&self) -> Matrix<N, N, T> {
Matrix::from_fn(|row, col| {
if row == col {
self.r_diag[row]
} else if col > row {
self.qr[(row, col)]
} else {
T::ZERO
}
})
}
pub fn q(&self) -> Matrix<M, N, T> {
let mut q = Matrix::from_fn(|row, col| if row == col { T::ONE } else { T::ZERO });
for col in 0..N {
for j in (0..N).rev() {
let pivot = self.qr[(j, j)];
if pivot == T::ZERO {
continue;
}
let mut sum = T::ZERO;
for i in j..M {
sum += self.qr[(i, j)] * q[(i, col)];
}
let factor = sum / pivot;
for i in j..M {
q[(i, col)] -= factor * self.qr[(i, j)];
}
}
}
q
}
#[inline]
#[must_use]
pub fn permutation(&self) -> [usize; N] {
self.permutation
}
pub fn solve_least_squares(&self, b: Vector<M, T>) -> Result<Vector<N, T>, CalcError> {
let mut qtb = b;
for j in 0..N {
let pivot = self.qr[(j, j)];
if pivot == T::ZERO {
continue;
}
let mut sum = T::ZERO;
for i in j..M {
sum += self.qr[(i, j)] * qtb[i];
}
let factor = sum / pivot;
for i in j..M {
qtb[i] -= factor * self.qr[(i, j)];
}
}
let threshold = if N == 0 {
T::ZERO
} else {
T::EPSILON * T::from_usize(M.max(N)) * self.r_diag[0].abs()
};
let mut y = [T::ZERO; N];
for row in (0..N).rev() {
if self.r_diag[row].abs() <= threshold {
return Err(CalcError::SingularMatrix);
}
let mut acc = qtb[row];
for (col, &y_value) in y.iter().enumerate().skip(row + 1) {
acc -= self.qr[(row, col)] * y_value;
}
y[row] = acc / self.r_diag[row];
}
let mut x = [T::ZERO; N];
for (j, &target) in self.permutation.iter().enumerate() {
x[target] = y[j];
}
Ok(Vector::new(x))
}
pub fn into_damped(self, b: Vector<M, T>) -> DampedLeastSquares<N, T> {
let mut transformed = b;
for j in 0..N {
let pivot = self.qr[(j, j)];
if pivot == T::ZERO {
continue;
}
let mut sum = T::ZERO;
for i in j..M {
sum += self.qr[(i, j)] * transformed[i];
}
let factor = sum / pivot;
for i in j..M {
transformed[i] -= factor * self.qr[(i, j)];
}
}
let mut qt_b = [T::ZERO; N];
for (dst, &src) in qt_b.iter_mut().zip(transformed.as_array().iter().take(N)) {
*dst = src;
}
DampedLeastSquares {
r: self.r(),
qt_b,
permutation: self.permutation,
column_norms: self.column_norms,
}
}
}
#[derive(Debug, Clone, Copy)]
#[must_use]
pub struct DampedLeastSquares<const N: usize, T = f64> {
pub(crate) r: Matrix<N, N, T>,
pub(crate) qt_b: [T; N],
pub(crate) permutation: [usize; N],
pub(crate) column_norms: [T; N],
}
impl<const N: usize, T: Numeric> DampedLeastSquares<N, T> {
pub fn solve_with_diagonal(&self, diag: &[T; N]) -> (Vector<N, T>, CholeskyFactor<N, T>) {
let mut s = self.r;
for j in 0..N {
for i in (j + 1)..N {
s[(i, j)] = s[(j, i)];
}
}
let mut saved_diag = [T::ZERO; N];
let mut wa = [T::ZERO; N];
for j in 0..N {
saved_diag[j] = s[(j, j)];
wa[j] = self.qt_b[j];
}
let mut s_diag = [T::ZERO; N];
let quarter = T::from_f64(0.25);
for j in 0..N {
let l = self.permutation[j];
if diag[l] != T::ZERO {
for entry in s_diag.iter_mut().skip(j) {
*entry = T::ZERO;
}
s_diag[j] = diag[l];
let mut qtbpj = T::ZERO;
for k in j..N {
if s_diag[k] == T::ZERO {
continue;
}
let (sin, cos) = if s[(k, k)].abs() >= s_diag[k].abs() {
let tan = s_diag[k] / s[(k, k)];
let cos = T::HALF / (quarter + quarter * tan * tan).sqrt();
(cos * tan, cos)
} else {
let cotan = s[(k, k)] / s_diag[k];
let sin = T::HALF / (quarter + quarter * cotan * cotan).sqrt();
(sin, sin * cotan)
};
s[(k, k)] = cos * s[(k, k)] + sin * s_diag[k];
let temp = cos * wa[k] + sin * qtbpj;
qtbpj = -sin * wa[k] + cos * qtbpj;
wa[k] = temp;
for i in (k + 1)..N {
let rotated = cos * s[(i, k)] + sin * s_diag[i];
s_diag[i] = -sin * s[(i, k)] + cos * s_diag[i];
s[(i, k)] = rotated;
}
}
}
s_diag[j] = s[(j, j)];
s[(j, j)] = saved_diag[j];
}
let mut nsing = N;
for j in 0..N {
if s_diag[j] == T::ZERO && nsing == N {
nsing = j;
}
if nsing < N {
wa[j] = T::ZERO;
}
}
for k in 0..nsing {
let j = nsing - 1 - k;
let mut sum = T::ZERO;
for i in (j + 1)..nsing {
sum += s[(i, j)] * wa[i];
}
wa[j] = (wa[j] - sum) / s_diag[j];
}
let mut x = [T::ZERO; N];
for (j, &target) in self.permutation.iter().enumerate() {
x[target] = wa[j];
}
(Vector::new(x), CholeskyFactor { s, s_diag })
}
pub fn solve_with_zero_diagonal(&self) -> (Vector<N, T>, CholeskyFactor<N, T>) {
self.solve_with_diagonal(&[T::ZERO; N])
}
#[must_use]
pub fn max_a_t_b_scaled(&self, b_norm: T) -> T {
if b_norm == T::ZERO {
return T::ZERO;
}
let mut result = T::ZERO;
for j in 0..N {
let l = self.permutation[j];
if self.column_norms[l] != T::ZERO {
let mut sum = T::ZERO;
for (i, &qi) in self.qt_b.iter().enumerate().take(j + 1) {
sum += self.r[(i, j)] * (qi / b_norm);
}
result = max(result, (sum / self.column_norms[l]).abs());
}
}
result
}
#[must_use]
pub fn a_x_norm(&self, x: &Vector<N, T>) -> T {
let mut w = [T::ZERO; N];
for j in 0..N {
let xl = x[self.permutation[j]];
for (i, slot) in w.iter_mut().enumerate().take(j + 1) {
*slot += self.r[(i, j)] * xl;
}
}
enorm(&w)
}
#[must_use]
pub fn is_non_singular(&self) -> bool {
if N == 0 {
return true;
}
let threshold = T::EPSILON * T::from_usize(N) * self.r[(0, 0)].abs();
(0..N).all(|j| self.r[(j, j)].abs() > threshold)
}
}
#[derive(Debug, Clone, Copy)]
#[must_use]
pub struct CholeskyFactor<const N: usize, T = f64> {
pub(crate) s: Matrix<N, N, T>,
pub(crate) s_diag: [T; N],
}
impl<const N: usize, T: Numeric> CholeskyFactor<N, T> {
#[must_use]
pub fn solve(&self, mut rhs: [T; N]) -> [T; N] {
for j in 0..N {
if self.s_diag[j] != T::ZERO {
rhs[j] /= self.s_diag[j];
} else {
rhs[j] = T::ZERO;
}
let temp = rhs[j];
for (i, slot) in rhs.iter_mut().enumerate().skip(j + 1) {
*slot -= self.s[(i, j)] * temp;
}
}
rhs
}
}