use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
use crate::{
matrix::{
Matrix,
utils::{points_2d, transpose},
},
number::{
traits::{floating::Floating, fractional::Fractional, number::Number, real::Real},
utils::{inversion_count, permutation},
},
};
pub const fn is_square_matrix<N, const R: usize, const C: usize>(_: &Matrix<N, R, C>) -> bool { R == C }
pub fn is_symmetric_matrix<N, const R: usize, const C: usize>(m: &Matrix<N, R, C>) -> bool
where
N: PartialEq + Sync,
{
is_square_matrix(m)
&& points_2d((1, R), (1, C), |row, col| row < col)
.par_iter()
.all(|&p @ (row, col)| m[p] == m[(col, row)])
}
pub fn is_anti_symmetric_matrix<N, const R: usize, const C: usize>(m: &Matrix<N, R, C>) -> bool
where
N: Real,
{
is_square_matrix(m)
&& points_2d((1, R), (1, C), |row, col| row <= col)
.par_iter()
.all(|&p @ (row, col)| m[p] == -m[(col, row)].clone())
}
pub fn is_upper_triangular_matrix<N, const R: usize, const C: usize>(m: &Matrix<N, R, C>) -> bool
where
N: Number,
{
points_2d((1, R), (1, C), |row, col| row > col)
.par_iter()
.all(|&p| m[p].is_zero())
}
pub fn is_lower_triangular_matrix<N, const R: usize, const C: usize>(m: &Matrix<N, R, C>) -> bool
where
N: Number,
{
points_2d((1, R), (1, C), |row, col| row < col)
.par_iter()
.all(|&p| m[p].is_zero())
}
pub fn is_diagonal_matrix<N, const R: usize, const C: usize>(m: &Matrix<N, R, C>) -> bool
where
N: Number,
{
points_2d((1, R), (1, C), |row, col| row != col)
.par_iter()
.cloned()
.map(|p| &m[p])
.all(|e| e.is_zero())
}
pub fn is_identity_matrix<N, const R: usize, const C: usize>(m: &Matrix<N, R, C>) -> bool
where
N: Number,
{
is_square_matrix(m)
&& is_diagonal_matrix(m)
&& points_2d((1, R), (1, C), |row, col| row == col)
.par_iter()
.all(|&p| m[p].is_one())
}
pub fn is_normal_matrix<N, const R: usize, const C: usize>(m: &Matrix<N, R, C>) -> bool
where
N: Real,
{
let transposed = transpose(m);
is_square_matrix(m) && (transposed.clone() * m.clone() == m.clone() * transposed)
}
pub fn is_orthogonal_matrix<N, const R: usize, const C: usize>(m: &Matrix<N, R, C>) -> bool
where
N: Real,
{
let transposed = transpose(m);
if R < C {
is_identity_matrix(&(m.clone() * transposed))
} else {
is_identity_matrix(&(transposed * m.clone()))
}
}
pub fn induced_l1_matrix_norm<N, const R: usize, const C: usize>(m: &Matrix<N, R, C>) -> N
where
N: Real,
{
let mut norm = N::zero();
for e in (1..=C).map(|p| {
m.get_column(p)
.map(|c| {
c.linear_iter()
.map(|e| e.absolute_value())
.fold(N::zero(), |acc, e| acc + e)
})
.expect(concat!(
"Error[matrix::math::induced_l1_matrix_norm]: ",
"Failed to retrieve column vectors of the matrix."
))
}) {
if e > norm {
norm = e;
}
}
norm
}
pub fn induced_l_inf_matrix_norm<N, const R: usize, const C: usize>(m: &Matrix<N, R, C>) -> N
where
N: Real,
{
let mut norm = N::zero();
for e in (1..=R).map(|p| {
m.get_row(p)
.map(|r| {
r.linear_iter()
.map(|e| e.absolute_value())
.fold(N::zero(), |acc, e| acc + e)
})
.expect(concat!(
"Error[matrix::math::induced_l_inf_matrix_norm]: ",
"Failed to retrieve row vectors of the matrix."
))
}) {
if e > norm {
norm = e;
}
}
norm
}
pub fn frobenius_norm<N, const R: usize, const C: usize>(m: &Matrix<N, R, C>) -> N
where
N: Floating,
{
m.inner
.par_iter()
.map(|e| {
let abs = e.absolute_value();
abs.clone() * abs
})
.reduce(|| N::zero(), |a, b| a + b)
.square_root()
}
pub fn row_reduce<N, const R: usize, const C: usize>(
m: &Matrix<N, R, C>,
) -> (usize, Matrix<N, R, R>, Matrix<N, R, R>, Matrix<N, R, C>)
where
N: Fractional,
{
let mut t = 0;
let mut p = Matrix::<N, R, R>::eyes();
let mut lt = Matrix::<N, R, R>::eyes();
let mut reduced = m.clone();
if !(is_upper_triangular_matrix(&reduced) || R < 2) {
let mut deviation = 0;
for row in 1..R {
if row + deviation > C {
break;
}
let mut pivot_check = reduced[(row, row + deviation)].is_zero();
while pivot_check && (row + deviation) <= C {
for next in (row + 1)..=R {
if !reduced[(next, row + deviation)].is_zero() {
let swap = Matrix::<N, R, R>::p_change(row, next);
p = swap.clone() * p;
reduced = swap * reduced;
t += 1;
pivot_check = false;
}
}
if pivot_check {
deviation += 1;
} else {
break;
}
}
if pivot_check {
break;
} else {
let pivot = reduced[(row, row + deviation)].clone();
for next in (row + 1)..=R {
let next_pivot = reduced[(next, row + deviation)].clone();
if !next_pivot.is_zero() {
let factor = next_pivot / pivot.clone();
let add = Matrix::<N, R, R>::p_add(row, next, -factor);
lt = add.clone() * lt;
reduced = add * reduced;
}
}
}
}
}
(t, p, lt, reduced)
}
fn row_eliminate_inner<N, const R: usize, const C: usize>(m: &Matrix<N, R, C>) -> (Matrix<N, R, R>, Matrix<N, R, C>)
where
N: Fractional,
{
let (_, p, lt, mut eliminate) = row_reduce(m);
let mut trans = p * lt;
for row in (1..=R).rev() {
let mut column = 1;
while column <= C && eliminate[(row, column)].is_zero() {
column += 1;
}
if column == C + 1 {
continue;
} else {
let pivot = eliminate[(row, column)].clone();
for prev in (1..row).rev() {
let prev_pivot = eliminate[(prev, column)].clone();
if !prev_pivot.is_zero() {
let factor = prev_pivot / pivot.clone();
let add = Matrix::<N, R, R>::p_add(row, prev, -factor);
trans = add.clone() * trans;
eliminate = add * eliminate;
}
}
let mul = Matrix::<N, R, R>::p_muls(row, N::one() / pivot);
trans = mul.clone() * trans;
eliminate = mul * eliminate;
}
}
(trans, eliminate)
}
pub fn row_eliminate<N, const R: usize, const C: usize>(m: &Matrix<N, R, C>) -> Matrix<N, R, C>
where
N: Fractional,
{
row_eliminate_inner(m).1
}
pub fn inverse<N, const R: usize, const C: usize>(m: &Matrix<N, R, C>) -> Option<Matrix<N, R, R>>
where
N: Fractional,
{
let det = determinant(m);
if det.is_none_or(|e| e.is_zero()) {
eprintln!(concat!(
"Error[matrix::math::inverse]: ",
"The singular matrix does not have an inverse."
));
None
} else {
Some(row_eliminate_inner(m).0)
}
}
pub fn determinant<N, const R: usize, const C: usize>(m: &Matrix<N, R, C>) -> Option<N>
where
N: Fractional,
{
if is_square_matrix(m) {
let (t, _, _, reduced) = row_reduce(m);
Some(
(1..=R)
.map(|index| reduced[(index, index)].clone())
.fold(N::one(), |acc, e| acc * e.clone())
* if t & 1 == 0 { N::one() } else { -N::one() },
)
} else {
eprintln!("Error[matrix::math::determinant]: Only square matrices have determinants.");
None
}
}
pub fn determinant_l<N, const R: usize, const C: usize>(m: &Matrix<N, R, C>) -> Option<N>
where
N: Number,
{
if is_square_matrix(m) {
let mut det = N::zero();
let permutations_of_columns = permutation(&(1..=C).collect::<Vec<usize>>());
for p in permutations_of_columns {
let mut item = N::one();
for (row, &column) in (1..=R).zip(p.iter()) {
item = item * m[(row, column)].clone();
}
let ic = inversion_count(&p);
if (ic & 1) == 1 {
item = -item;
}
det = det + item;
}
Some(det)
} else {
eprintln!("Error[matrix::math::determinant_l]: Only square matrices have determinants.");
None
}
}
pub fn adjugate_matrix<N, const R: usize, const C: usize>(m: &Matrix<N, R, C>) -> Option<Matrix<N, R, R>>
where
N: Fractional,
[(); R - 1]:,
[(); C - 1]:,
{
if is_square_matrix(m) {
let mut adjugate = Matrix::<N, R, R>::default();
for row in 1..=R {
for column in 1..=C {
adjugate[(row, column)] = determinant(&m.submatrix(column, row)).expect(&format!(
concat!(
"Error[matrix::math::adjugate_matrix]: ",
"Failed to retrieve the determinant ",
"of the submatrix({}, {}) of the matrix."
),
row, column
)) * if (row + column) & 1 == 0 {
N::one()
} else {
-N::one()
}
}
}
Some(adjugate)
} else {
eprintln!("Error[matrix::math::adjugate_matrix]: Only square matrices have adjugate matrices.");
None
}
}
pub fn plu_decomposition<N, const R: usize, const C: usize>(
m: &Matrix<N, R, C>,
) -> (Matrix<N, R, R>, Matrix<N, R, R>, Matrix<N, R, C>)
where
N: Fractional,
{
let (_, p, l_inv, reduced) = row_reduce(m);
(
p,
inverse(&l_inv).expect(concat!(
"Error[matrix::math::plu_decomposition]: ",
"Failed to retrieve the inverse."
)),
reduced,
)
}