use crate::{approx, num};
use crate::traits::*;
use crate::types::*;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct UpperHessenberg3 <S> (Matrix3 <S>);
pub fn decompose_hessenberg_3 <S : Real> (m : Matrix3 <S>)
-> Option <(Matrix3 <S>, UpperHessenberg3 <S>)>
{
if m == Matrix3::zero() {
return None
}
let v = m.cols[0];
let househ = Matrix3::elementary_reflector (v, 1);
let q = househ;
let h = UpperHessenberg3 (q.transpose() * m * q);
Some ((q, h))
}
pub fn decompose_schur_3 <S> (h : UpperHessenberg3 <S>) -> (Matrix3 <S>, Matrix3 <S>)
where S : Real + approx::AbsDiffEq <Epsilon=S>
{
fn get_slice <S : Ring> (
rows : [[S; 3]; 3], row_s : usize, row_e : usize, column_s : usize, column_e : usize
) -> [[S; 3]; 3] {
debug_assert!(row_s < 3);
debug_assert!(row_e < 3);
debug_assert!(column_s < 3);
debug_assert!(column_e < 3);
let mut m = [[S::zero(); 3]; 3];
for r in row_s..=row_e {
for c in column_s..=column_e {
m[r - row_s][c - column_s] = rows[r][c];
}
}
m
}
fn set_slice <S : Copy> (
mut rows : [[S; 3]; 3], slice : [[S; 3]; 3],
row_s : usize, row_e : usize, column_s : usize, column_e : usize
) -> [[S; 3]; 3] {
debug_assert!(row_s < 3);
debug_assert!(row_e < 3);
debug_assert!(column_s < 3);
debug_assert!(column_e < 3);
for r in row_s..=row_e {
for c in column_s..=column_e {
rows[r][c] = slice[r - row_s][c - column_s];
}
}
rows
}
fn givens_cosine_sine_pair <S : Real> (a : S, b : S) -> (S, S) {
if b == S::zero() {
(S::one(), S::zero())
} else if a == S::zero() {
(S::zero(), S::one())
} else {
let l = (a * a + b * b).sqrt();
(a.abs() / l, a.signum_or_zero() * b / l)
}
}
let mut h = h.mat().into_row_arrays();
let mut u = Matrix3::<S>::identity().into_row_arrays();
loop {
let h_qq = h[1][1];
let h_pp = h[2][2];
let s = h_qq + h_pp;
let h_qp = h[1][2];
let h_pq = h[2][1];
let t = h_qq * h_pp - h_qp * h_pq;
let h_11 = h[0][0];
let h_12 = h[0][1];
let h_21 = h[1][0];
let mut x = h_11 * h_11 + h_12 * h_21 - s * h_11 + t;
let h_22 = h[1][1];
let mut y = h_21 * (h_11 + h_22 - s);
let h_32 = h[2][1];
let z = h_21 * h_32;
let b = vector3 (x, y, z);
let hr = Matrix3::elementary_reflector (b, 0);
h = (hr.transpose() * Matrix3::from_row_arrays (get_slice (h, 0, 2, 0, 2)))
.into_row_arrays();
h = (Matrix3::from_row_arrays (get_slice (h, 0, 2, 0, 2)) * hr)
.into_row_arrays();
u = (Matrix3::from_row_arrays (get_slice (u, 0, 2, 0, 2)) * hr)
.into_row_arrays();
let h_k2_k1 = h[1][0];
x = h_k2_k1;
let h_k3_k1 = h[2][0];
y = h_k3_k1;
let (c, s) = givens_cosine_sine_pair (x, y);
let g = Matrix3::fill_zeros (Matrix2::from_row_arrays ([
[c, s],
[-s ,c]
]));
{
let temp = (g * Matrix3::from_row_arrays (get_slice (h, 1, 2, 0, 2)))
.into_row_arrays();
h = set_slice (h, temp, 1, 2, 0, 2);
}
{
let g_trans = g.transpose();
let temp = (Matrix3::from_row_arrays (get_slice (h, 0, 2, 1, 2)) * g_trans)
.into_row_arrays();
h = set_slice (h, temp, 0, 2, 1, 2);
let u_slice =
(Matrix3::from_row_arrays (get_slice (h, 0, 2, 1, 2)) * g_trans)
.into_row_arrays();
u = set_slice (u, u_slice, 0, 2, 1, 2);
}
let m = h[1][1].abs();
let n = h[2][2].abs();
if h[2][1].abs() < S::default_epsilon() * (m + n) {
h[2][1] = S::zero();
break
} else {
let k = h[0][0].abs();
let l = h[1][1].abs();
if h[1][0].abs() < S::default_epsilon() * (k + l) {
h[1][0] = S::zero();
break
}
}
}
(Matrix3::from_row_arrays (u), Matrix3::from_row_arrays (h))
}
pub fn eigensystem_2 <S> (matrix : Matrix2 <S>) -> [Option <(S, Vector2 <S>)>; 2] where
S : OrderedField + Sqrt + approx::AbsDiffEq <Epsilon=S> + std::fmt::Debug
{
if matrix == Matrix2::zero() {
return [Some ((S::zero(), Vector2::zero())), None]
}
let mut eigenpairs = if matrix[(0,1)] == S::zero() && matrix[(1,0)] == S::zero() {
let eigenvalues = matrix.diagonal();
let eigenvectors = Matrix2::identity().cols;
std::array::from_fn (|i| Some((eigenvalues[i], eigenvectors[i])))
} else {
let trace = matrix.trace();
let determinant = matrix.determinant();
let system_matrix = |eigenvalue| matrix - Matrix2::broadcast_diagonal (eigenvalue);
super::solve_quadratic_equation (S::one(), -trace, determinant)
.map (|maybe_eigenvalue| maybe_eigenvalue.map (|eigenvalue|
(eigenvalue, solve_system_2_homogeneous (system_matrix (eigenvalue)).unwrap())))
};
eigenpairs.sort_by (|a, b| match (a, b) {
(Some (a), Some (b)) => b.0.partial_cmp (&a.0).unwrap(),
(None, Some (_)) => std::cmp::Ordering::Greater,
(Some (_), None) => std::cmp::Ordering::Less,
(None, None) => std::cmp::Ordering::Equal
});
eigenpairs
}
pub fn eigensystem_3 <S> (matrix : Matrix3 <S>) -> [Option <(S, Vector3 <S>)>; 3] where
S : Real + num::NumCast + approx::RelativeEq <Epsilon=S> + std::fmt::Debug
{
if matrix == Matrix3::zero() {
return [Some ((S::zero(), Vector3::zero())), None, None]
}
let mut eigenpairs = if Matrix3::with_diagonal (matrix.diagonal()) == matrix {
let eigenvalues = matrix.diagonal();
let eigenvectors = Matrix3::identity().cols;
std::array::from_fn (|i| Some((eigenvalues[i], eigenvectors[i])))
} else {
fn eigen_2by2 <S : Real> (a11 : S, a12 : S, a21 : S, a22 : S) -> (S, S) {
let m = S::half() * (a11 + a22);
let p = a11 * a22 - a12 * a21;
let k = (m * m - p).sqrt();
let l1 = m + k;
let l2 = m - k;
(l1, l2)
}
let (_q, h) = decompose_hessenberg_3 (matrix).unwrap();
let (_q, u) = decompose_schur_3 (h);
let u = u.into_row_arrays();
let mut eigenvalues = Vec::with_capacity (3);
let mut i = 0;
while i < 3 {
if i == 2 || u[i+1][i] == S::zero() {
eigenvalues.push (u[i][i]);
i += 1;
} else {
let a_ii = u[i][i];
let a_ii1 = u[i][i+1];
let a_i1i = u[i+1][i];
let a_i1i1 = u[i+1][i+1];
let (l1, l2) = eigen_2by2 (a_ii, a_ii1, a_i1i, a_i1i1);
eigenvalues.push (l1);
eigenvalues.push (l2);
i += 2;
}
}
eigenvalues.dedup_by (|a, b| approx::relative_eq!(a, b,
max_relative = S::eight() * S::default_max_relative()));
let mut eigenpairs = [None; 3];
let mut i = 0;
for eigenvalue in eigenvalues {
let diff = matrix - Matrix3::identity() * eigenvalue;
for eigenvector in solve_system_3_homogeneous (diff) {
eigenpairs[i] = Some ((eigenvalue, eigenvector));
i += 1;
}
}
eigenpairs
};
eigenpairs.sort_by (|a, b| match (a, b) {
(Some (a), Some (b)) => b.0.partial_cmp (&a.0).unwrap(),
(None, Some (_)) => std::cmp::Ordering::Greater,
(Some (_), None) => std::cmp::Ordering::Less,
(None, None) => std::cmp::Ordering::Equal
});
eigenpairs
}
pub fn eigensystem_3_classical <S> (matrix : Matrix3 <S>)
-> [Option <(S, Vector3 <S>)>; 3]
where S : Real + Cbrt + num::NumCast + approx::RelativeEq <Epsilon=S> + std::fmt::Debug {
use num::One;
let mut eigenpairs = if Matrix3::with_diagonal (matrix.diagonal()) == matrix {
let eigenvalues = matrix.diagonal();
let eigenvectors = Matrix3::identity().cols;
std::array::from_fn (|i| Some((eigenvalues[i], eigenvectors[i])))
} else {
let trace = matrix.trace();
let sum_of_principal_minors = (0..3).map (|i| matrix.minor (i, i)).sum::<S>();
let determinant = matrix.determinant();
let system_matrix = |eigenvalue| matrix - Matrix3::broadcast_diagonal (eigenvalue);
let eigenvalues = super::solve_cubic_equation (
NonZero::one(), -trace, sum_of_principal_minors, -determinant);
let mut eigenpairs = vec![];
for eigenvalue in eigenvalues.iter().flatten().copied() {
let eigenvectors = solve_system_3_homogeneous (system_matrix (eigenvalue));
for eigenvector in eigenvectors {
eigenpairs.push ((eigenvalue, eigenvector));
}
}
std::array::from_fn (|i| eigenpairs.get (i).copied())
};
eigenpairs.sort_by (|a, b| match (a, b) {
(Some (a), Some (b)) => b.0.partial_cmp (&a.0).unwrap(),
(None, Some (_)) => std::cmp::Ordering::Greater,
(Some (_), None) => std::cmp::Ordering::Less,
(None, None) => std::cmp::Ordering::Equal
});
eigenpairs
}
pub fn row_reduce3 <S> (m : Matrix3 <S>) -> Matrix3 <S> where
S : OrderedField + approx::RelativeEq <Epsilon=S> + std::fmt::Debug
{
let mut rows = m.into_row_arrays();
let mut h = 0; let mut k = 0; while h <= 2 && k <= 2 {
let abs_row = |i : usize| rows[i][k].abs();
let i_max = (h..3).max_by (|i, j| abs_row (*i).partial_cmp (&abs_row (*j)).unwrap())
.unwrap();
if !approx::abs_diff_eq!(rows[i_max][k], S::zero(),
epsilon = S::four() * S::default_epsilon()
) {
rows.swap (h, i_max);
for i in h+1..3 {
let f = rows[i][k] / rows[h][k];
rows[i][k] = S::zero();
#[expect(clippy::needless_range_loop)]
for j in k+1..3 {
let sub = rows[h][j] * f;
if approx::relative_eq!(rows[i][j], sub,
max_relative = S::eight() * S::default_max_relative()
) {
rows[i][j] = S::zero();
} else {
rows[i][j] -= rows[h][j] * f;
}
}
}
h += 1;
}
k += 1;
}
Matrix3::from_row_arrays (rows)
}
pub fn solve_system_2_homogeneous <S> (system_matrix : Matrix2 <S>)
-> Option <Vector2 <S>>
where S : Field + approx::AbsDiffEq <Epsilon=S> {
if system_matrix == Matrix2::zero() {
return None
}
if LinearIso::is_invertible_approx (system_matrix, None) {
Some (Vector2::zero())
} else {
let [
[a, c],
[b, d]
] = system_matrix.into_col_arrays();
if b != S::zero() {
Some (vector2 (S::one(), -a/b))
} else if d != S::zero() {
Some (vector2 (S::one(), -c/d))
} else {
Some (vector2 (S::zero(), S::one()))
}
}
}
pub fn solve_system_3_homogeneous <S> (system_matrix : Matrix3 <S>)
-> Vec <Vector3 <S>>
where S : OrderedField + num::NumCast + approx::RelativeEq <Epsilon=S> + std::fmt::Debug {
if system_matrix == Matrix3::zero() {
return vec![]
}
if LinearIso::is_invertible_approx (system_matrix, Some (S::from (0.001).unwrap())) {
vec![Vector3::zero()]
} else {
let row_echelon_form = row_reduce3 (system_matrix).into_row_arrays();
let mut pivot_cols = [usize::MAX; 3];
for i in 0..3 {
#[expect(clippy::needless_range_loop)]
for j in 0..3 {
if approx::abs_diff_ne!(row_echelon_form[i][j], S::zero(),
epsilon = S::four() * S::default_epsilon()
) {
pivot_cols[i] = j;
break
}
}
}
debug_assert!(pivot_cols.iter().any (|i| *i != usize::MAX),
"we checked that the system matrix is non-zero, so there should be at least 1 \
pivot");
debug_assert!(!pivot_cols.iter().all (|i| *i != usize::MAX),
"we checked that the system matrix is singular, so there should never be 3 pivots"
);
let mut basis = vec![];
for free_col in (0..3).filter (|col| !pivot_cols.contains (col)) {
let mut vec = Vector3::zero();
vec[free_col] = S::one();
for i in (0..3).rev().filter (|i| pivot_cols[*i] != usize::MAX) {
let pivot_col = pivot_cols[i];
let row = row_echelon_form[i];
vec[pivot_col] = -(pivot_col+1..3).map (|j| row[j] * vec[j]).sum::<S>()
/ row[pivot_col];
}
basis.push (vec);
}
debug_assert!(!basis.is_empty());
basis
}
}
impl <S> ElementaryReflector for Matrix3 <S> where S : OrderedField + Sqrt {
type Vector = Vector3 <S>;
fn elementary_reflector (v : Vector3 <S>, index : usize) -> Matrix3 <S> {
debug_assert!(index < 3);
let d = &v[index..];
let d_0 = d[0];
let d_norm = d.iter().copied().map (|x| x * x).sum::<S>().sqrt();
let alpha = if d_0 >= S::zero() {
-d_norm
} else {
d_norm
};
if alpha == S::zero() {
return Matrix3::identity()
}
let d_m = d.len();
let mut v = vec![S::zero(); d_m];
v[0] = (S::half() * (S::one() - d_0 / alpha)).sqrt();
let p = -alpha * v[0];
if d_m > 1 {
for i in 1..d_m {
v[i] = d[i] / (S::two() * p);
}
}
let mut w = Vector3::zero();
for i in index..3 {
w[i] = v[i - index];
}
let w_dyadp = w.outer_product (w);
Matrix3::<S>::identity() - (w_dyadp * S::two())
}
}
impl <S> UpperHessenberg3 <S> {
pub fn mat (self) -> Matrix3 <S> {
self.0
}
}
#[cfg(test)]
mod tests {
#![expect(clippy::unreadable_literal)]
use super::*;
use approx::RelativeEq;
#[test]
fn eigensystem_2d() {
let mat = Matrix2::from_row_arrays ([
[1.0, 1.0],
[0.0, 0.0] ]);
let (eigenvalues, eigenvectors) : (Vec<_>, Vec<_>) =
eigensystem_2 (mat).map (Option::unwrap).iter().copied().unzip();
assert_eq!(eigenvalues, [1.0, 0.0]);
assert_eq!(eigenvectors, [
[1.0, 0.0],
[1.0, -1.0]
].map (Vector2::from));
let mat = Matrix2::from_row_arrays ([
[1.0, 0.0],
[1.0, 0.0] ]);
let (eigenvalues, eigenvectors) : (Vec<_>, Vec<_>) =
eigensystem_2 (mat).map (Option::unwrap).iter().copied().unzip();
assert_eq!(eigenvalues, [1.0, 0.0]);
assert_eq!(eigenvectors, [
[1.0, 1.0],
[0.0, 1.0]
].map (Vector2::from));
let mat = Matrix2::from_row_arrays ([
[2.0, 1.0],
[1.0, 2.0] ]);
let (eigenvalues, eigenvectors) : (Vec<_>, Vec<_>) =
eigensystem_2 (mat).map (Option::unwrap).iter().copied().unzip();
assert_eq!(eigenvalues, [3.0, 1.0]);
assert_eq!(eigenvectors, [
[1.0, 1.0],
[1.0, -1.0]
].map (Vector2::from));
let mat = Matrix2::from_row_arrays ([
[1.0, 0.0],
[1.0, 3.0] ]);
let (eigenvalues, eigenvectors) : (Vec<_>, Vec<_>) =
eigensystem_2 (mat).map (Option::unwrap).iter().copied().unzip();
assert_eq!(eigenvalues, [3.0, 1.0]);
assert_eq!(eigenvectors, [
[0.0, 1.0],
[1.0, -0.5]
].map (Vector2::from));
}
#[test]
fn eigensystem_3d() {
let mat = Matrix3::<f32>::from_row_arrays ([
[0.0, 1.0, 0.0],
[0.0, 0.0, 1.0],
[0.0, 0.0, 0.0]
]);
let (eigenvalues, eigenvectors) : (Vec<_>, Vec<_>) =
eigensystem_3_classical (mat).iter().flatten().copied().unzip();
assert_eq!(eigenvalues, [0.0]);
assert_eq!(eigenvectors, [
[1.0, 0.0, 0.0]
].map (Vector3::from));
let mat = Matrix3::<f32>::from_row_arrays ([
[2.0, 1.0, 0.0],
[0.0, 2.0, 0.0],
[0.0, 0.0, 2.0]
]);
let (eigenvalues, eigenvectors) : (Vec<_>, Vec<_>) =
eigensystem_3_classical (mat).iter().flatten().copied().unzip();
assert_eq!(eigenvalues, [2.0, 2.0]);
assert_eq!(eigenvectors, [
[1.0, 0.0, 0.0],
[0.0, 0.0, 1.0]
].map (Vector3::from));
let mat = Matrix3::<f32>::from_row_arrays ([
[9.714286, 0.0, 8.571428], [0.0, 1.1428572, 0.0], [8.571428, 0.0, 9.714286]
]);
let (eigenvalues, eigenvectors) : (Vec<_>, Vec<_>) =
eigensystem_3 (mat).iter().flatten().copied().unzip();
assert_eq!(eigenvalues, [18.285717, 1.1428576, 1.1428576]);
approx::assert_relative_eq!(eigenvectors[0], [ 1.0, 0.0, 1.0].into(),
max_relative = 4.0 * f32::default_max_relative());
assert_eq!(eigenvectors[1], [ 0.0, 1.0, 0.0].into());
assert_eq!(eigenvectors[2], [-1.0, 0.0, 1.0].into());
}
#[test]
fn row_reduce_3d() {
let mat = Matrix3::from_row_arrays ([
[0.0, 1.0, 0.0],
[0.0, 0.0, 1.0],
[1.0, 0.0, 0.0] ]);
let reduced = row_reduce3 (mat);
assert_eq!(reduced, Matrix3::identity());
let mat = Matrix3::from_row_arrays ([
[0.0, 1.0, 0.0],
[0.0, 0.0, -1.0],
[1.0, 0.0, 0.0] ]);
let reduced = row_reduce3 (mat);
assert_eq!(reduced, Matrix3::from_row_arrays ([
[1.0, 0.0, 0.0],
[0.0, 1.0, 0.0],
[0.0, 0.0, -1.0] ]));
let mat = Matrix3::from_row_arrays ([
[1.0, 2.0, 3.0],
[2.0, 4.0, 6.0],
[1.0, 1.0, 1.0] ]);
let reduced = row_reduce3 (mat);
assert_eq!(reduced, Matrix3::from_row_arrays ([
[2.0, 4.0, 6.0],
[0.0, -1.0, -2.0],
[0.0, 0.0, 0.0] ]));
}
#[test]
fn solve_homogeneous_2d() {
let mat = Matrix2::from_row_arrays ([
[1.0, 1.0],
[0.0, 0.0] ]);
assert_eq!(solve_system_2_homogeneous (mat), Some (vector2 (1.0, -1.0)));
let mat = Matrix2::from_row_arrays ([
[1.0, 0.0],
[1.0, 0.0] ]);
assert_eq!(solve_system_2_homogeneous (mat), Some (vector2 (0.0, 1.0)));
}
#[test]
fn solve_homogeneous_3d() {
let mat = Matrix3::from_row_arrays ([
[1.0, 2.0, 3.0],
[4.0, 5.0, 6.0],
[7.0, 8.0, 9.0] ]);
let solution = solve_system_3_homogeneous (mat);
debug_assert_eq!(solution.len(), 1);
solution.into_iter().for_each (|basis| assert_eq!(mat * basis, Vector3::zero()));
let mat = Matrix3::from_row_arrays ([
[1.0, 2.0, 3.0],
[2.0, 4.0, 2.0],
[3.0, 6.0, 9.0] ]);
let solution = solve_system_3_homogeneous (mat);
debug_assert_eq!(solution.len(), 1);
solution.into_iter().for_each (|basis| assert_eq!(mat * basis, Vector3::zero()));
let mat = Matrix3::from_row_arrays ([
[2.0, 4.0, 6.0],
[4.0, 8.0, 12.0],
[6.0, 12.0, 18.0] ]);
let solution = solve_system_3_homogeneous (mat);
debug_assert_eq!(solution.len(), 2);
solution.into_iter().for_each (|basis| assert_eq!(mat * basis, Vector3::zero()));
let mat = Matrix3::from_row_arrays ([
[ 4.0, -1.0, 2.0],
[ 8.0, -2.0, 4.0],
[12.0, -3.0, 6.0] ]);
let solution = solve_system_3_homogeneous (mat);
debug_assert_eq!(solution.len(), 2);
solution.into_iter().for_each (|basis| assert_eq!(mat * basis, Vector3::zero()));
}
}