use rayon::iter::{IndexedParallelIterator, IntoParallelIterator, 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>(m: &Matrix<N>) -> bool { m.row == m.column }
pub fn is_symmetric_matrix<N>(m: &Matrix<N>) -> bool
where
N: PartialEq + Sync,
{
is_square_matrix(m)
&& points_2d((1, m.row), (1, m.column), |row, col| row < col)
.par_iter()
.all(|&p @ (row, col)| m[p] == m[(col, row)])
}
pub fn is_anti_symmetric_matrix<N>(m: &Matrix<N>) -> bool
where
N: Real,
{
is_square_matrix(m)
&& points_2d((1, m.row), (1, m.column), |row, col| row <= col)
.par_iter()
.all(|&p @ (row, col)| m[p] == -m[(col, row)].clone())
}
pub fn is_upper_triangular_matrix<N>(m: &Matrix<N>) -> bool
where
N: Number,
{
points_2d((1, m.row), (1, m.column), |row, col| row > col)
.par_iter()
.all(|&p| m[p].is_zero())
}
pub fn is_lower_triangular_matrix<N>(m: &Matrix<N>) -> bool
where
N: Number,
{
points_2d((1, m.row), (1, m.column), |row, col| row < col)
.par_iter()
.all(|&p| m[p].is_zero())
}
pub fn is_diagonal_matrix<N>(m: &Matrix<N>) -> bool
where
N: Number,
{
points_2d((1, m.row), (1, m.column), |row, col| row != col)
.par_iter()
.cloned()
.map(|p| &m[p])
.all(|e| e.is_zero())
}
pub fn is_identity_matrix<N>(m: &Matrix<N>) -> bool
where
N: Number,
{
is_square_matrix(m)
&& is_diagonal_matrix(m)
&& points_2d((1, m.row), (1, m.column), |row, col| row == col)
.par_iter()
.all(|&p| m[p].is_one())
}
pub fn is_normal_matrix<N>(m: &Matrix<N>) -> 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>(m: &Matrix<N>) -> bool
where
N: Real,
{
let transposed = transpose(m);
if m.row < m.column {
is_identity_matrix(&(m.clone() * transposed))
} else {
is_identity_matrix(&(transposed * m.clone()))
}
}
pub fn induced_l1_matrix_norm<N>(m: &Matrix<N>) -> N
where
N: Real,
{
let mut norm = N::zero();
for e in (1..=m.column).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>(m: &Matrix<N>) -> N
where
N: Real,
{
let mut norm = N::zero();
for e in (1..=m.row).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>(m: &Matrix<N>) -> N
where
N: Floating,
{
m.inner
.par_iter()
.take(m.row * m.column)
.map(|e| {
let abs = e.absolute_value();
abs.clone() * abs
})
.reduce(|| N::zero(), |a, b| a + b)
.square_root()
}
pub fn row_reduce<N>(m: &Matrix<N>) -> (usize, Matrix<N>, Matrix<N>, Matrix<N>)
where
N: Fractional,
{
let mut t = 0;
let mut p = Matrix::<N>::eyes(m.row, m.row);
let mut lt = Matrix::<N>::eyes(m.row, m.row);
let mut reduced = m.clone();
if !(is_upper_triangular_matrix(&reduced) || m.row < 2) {
let mut deviation = 0;
for row in 1..m.row {
if row + deviation > m.column {
break;
}
let mut pivot_check = reduced[(row, row + deviation)].is_zero();
while pivot_check && (row + deviation) <= m.column {
for next in (row + 1)..=m.row {
if !reduced[(next, row + deviation)].is_zero() {
let swap = Matrix::<N>::p_change(m.row, m.row, 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)..=m.row {
let next_pivot = reduced[(next, row + deviation)].clone();
if !next_pivot.is_zero() {
let factor = next_pivot / pivot.clone();
let add = Matrix::<N>::p_add(m.row, m.row, row, next, -factor);
lt = add.clone() * lt;
reduced = add * reduced;
}
}
}
}
}
(t, p, lt, reduced)
}
fn row_eliminate_inner<N>(m: &Matrix<N>) -> (Matrix<N>, Matrix<N>)
where
N: Fractional,
{
let (_, p, lt, mut eliminate) = row_reduce(m);
let mut trans = p * lt;
for row in (1..=m.row).rev() {
let mut column = 1;
while column <= m.column && eliminate[(row, column)].is_zero() {
column += 1;
}
if column == m.column + 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>::p_add(m.row, m.row, row, prev, -factor);
trans = add.clone() * trans;
eliminate = add * eliminate;
}
}
let mul = Matrix::<N>::p_muls(m.row, m.row, row, N::one() / pivot);
trans = mul.clone() * trans;
eliminate = mul * eliminate;
}
}
(trans, eliminate)
}
pub fn row_eliminate<N>(m: &Matrix<N>) -> Matrix<N>
where
N: Fractional,
{
row_eliminate_inner(m).1
}
pub fn inverse<N>(m: &Matrix<N>) -> Option<Matrix<N>>
where
N: Fractional,
{
if is_square_matrix(m) {
let det = determinant(m);
if det.is_zero() {
eprintln!(concat!(
"Error[matrix::math::inverse]: ",
"The singular matrix does not have an inverse."
));
None
} else {
Some(row_eliminate_inner(m).0)
}
} else {
eprintln!(concat!(
"Error[matrix::math::inverse]: ",
"Non-square matrices are not invertible."
));
None
}
}
pub fn determinant<N>(m: &Matrix<N>) -> N
where
N: Fractional,
{
assert!(
is_square_matrix(m),
concat!(
"Error[matrix::math::determinant]: ",
"Only square matrices can have a determinant calculated."
)
);
let (t, _, _, reduced) = row_reduce(m);
(1..=m.row)
.map(|index| reduced[(index, index)].clone())
.fold(N::one(), |acc, e| acc * e.clone())
* if t & 1 == 0 { N::one() } else { -N::one() }
}
pub fn determinant_l<N>(m: &Matrix<N>) -> N
where
N: Number,
{
assert!(
is_square_matrix(m),
concat!(
"Error[matrix::math::determinant_l]: ",
"Only square matrices can have a determinant calculated."
)
);
let mut det = N::zero();
let permutations_of_columns = permutation(&(1..=m.column).collect::<Vec<usize>>());
for p in permutations_of_columns {
let mut item = N::one();
for (row, &column) in (1..=m.row).zip(p.iter()) {
item = item * m[(row, column)].clone();
}
let ic = inversion_count(&p);
if (ic & 1) == 1 {
item = -item;
}
det = det + item;
}
det
}
pub fn adjugate_matrix<N>(m: &Matrix<N>) -> Option<Matrix<N>>
where
N: Fractional,
{
if is_square_matrix(m) {
let mut adjugate = Matrix::<N>::defaults(m.column, m.row);
for row in 1..=m.column {
for column in 1..=m.row {
adjugate[(row, column)] = determinant(&m.submatrix(column, row))
* if (row + column) & 1 == 0 {
N::one()
} else {
-N::one()
}
}
}
Some(adjugate)
} else {
eprintln!(concat!(
"Error[matrix::math::adjugate_matrix]: ",
"Only square matrices have adjugate matrices."
));
None
}
}
pub fn determinant_e<N>(m: &Matrix<N>) -> N
where
N: Number,
{
assert!(
is_square_matrix(m),
concat!(
"Error[matrix::math::determinant_e]: ",
"Only square matrices can have a determinant calculated."
)
);
match m.row {
1 => m[(1, 1)].clone(),
2 => m[(1, 1)].clone() * m[(2, 2)].clone() - m[(1, 2)].clone() * m[(2, 1)].clone(),
3 => {
m[(1, 1)].clone() * (m[(2, 2)].clone() * m[(3, 3)].clone() - m[(2, 3)].clone() * m[(3, 2)].clone())
- m[(1, 2)].clone() * (m[(2, 1)].clone() * m[(3, 3)].clone() - m[(2, 3)].clone() * m[(3, 1)].clone())
+ m[(1, 3)].clone() * (m[(2, 1)].clone() * m[(3, 2)].clone() - m[(2, 2)].clone() * m[(3, 1)].clone())
}
_ => (1..=m.column)
.into_par_iter()
.map(|column| {
let submat = m.submatrix(1, column);
determinant_e(&submat)
* if ((1 + column) & 1) == 0 {
m[(1, column)].clone()
} else {
-m[(1, column)].clone()
}
})
.reduce(|| N::zero(), |a, b| a + b),
}
}
pub fn plu_decomposition<N>(m: &Matrix<N>) -> (Matrix<N>, Matrix<N>, Matrix<N>)
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,
)
}