use crate::matrices::{Column, Matrix, Row};
use crate::numeric::extra::{Real, RealRef, Sqrt};
use crate::numeric::{Numeric, NumericRef};
use crate::tensors::views::{TensorRef, TensorView};
use crate::tensors::{Dimension, Tensor};
pub fn inverse<T: Numeric>(matrix: &Matrix<T>) -> Option<Matrix<T>>
where
for<'a> &'a T: NumericRef<T>,
{
if matrix.rows() != matrix.columns() {
return None;
}
if matrix.rows() == 1 {
let element = matrix.scalar();
if element == T::zero() {
return None;
}
return Some(Matrix::from_scalar(T::one() / element));
}
match determinant::<T>(matrix) {
Some(det) => {
if det == T::zero() {
return None;
}
let determinant_reciprocal = T::one() / det;
let mut cofactor_matrix = Matrix::empty(T::zero(), matrix.size());
for i in 0..matrix.rows() {
for j in 0..matrix.columns() {
let ij_minor = minor::<T>(matrix, i, j)?;
let sign = i8::pow(-1, (i.rem_euclid(2) + j.rem_euclid(2)) as u32);
let sign = if sign == 1 {
T::one()
} else {
T::zero() - T::one()
};
cofactor_matrix.set(i, j, sign * ij_minor);
}
}
cofactor_matrix.transpose_mut();
cofactor_matrix.map_mut(|element| element * determinant_reciprocal.clone());
Some(cofactor_matrix)
}
None => None,
}
}
pub fn inverse_tensor<T, S, I>(tensor: I) -> Option<Tensor<T, 2>>
where
T: Numeric,
for<'a> &'a T: NumericRef<T>,
I: Into<TensorView<T, S, 2>>,
S: TensorRef<T, 2>,
{
inverse_less_generic::<T, S>(tensor.into())
}
fn inverse_less_generic<T, S>(tensor: TensorView<T, S, 2>) -> Option<Tensor<T, 2>>
where
T: Numeric,
for<'a> &'a T: NumericRef<T>,
S: TensorRef<T, 2>,
{
let shape = tensor.shape();
if !crate::tensors::dimensions::is_square(&shape) {
return None;
}
if shape[0].1 == 1 {
let element = tensor
.iter()
.next()
.expect("1x1 tensor must have a single element");
if element == T::zero() {
return None;
}
return Some(Tensor::from(shape, vec![T::one() / element]));
}
match determinant_less_generic::<T, _>(&tensor) {
Some(det) => {
if det == T::zero() {
return None;
}
let determinant_reciprocal = T::one() / det;
let mut cofactor_matrix = Tensor::empty(shape, T::zero());
for ([i, j], x) in cofactor_matrix.iter_reference_mut().with_index() {
let ij_minor = minor_tensor::<T, _>(&tensor, i, j)?;
let sign = i8::pow(-1, (i.rem_euclid(2) + j.rem_euclid(2)) as u32);
let sign = if sign == 1 {
T::one()
} else {
T::zero() - T::one()
};
*x = sign * ij_minor;
}
cofactor_matrix.transpose_mut([shape[1].0, shape[0].0]);
cofactor_matrix.map_mut(|element| element * determinant_reciprocal.clone());
Some(cofactor_matrix)
}
None => None,
}
}
fn minor<T: Numeric>(matrix: &Matrix<T>, i: Row, j: Column) -> Option<T>
where
for<'a> &'a T: NumericRef<T>,
{
minor_mut::<T>(&mut matrix.clone(), i, j)
}
fn minor_mut<T: Numeric>(matrix: &mut Matrix<T>, i: Row, j: Column) -> Option<T>
where
for<'a> &'a T: NumericRef<T>,
{
if matrix.rows() == 1 || matrix.columns() == 1 {
return None;
}
if matrix.rows() != matrix.columns() {
return None;
}
matrix.remove_row(i);
matrix.remove_column(j);
determinant::<T>(matrix)
}
fn minor_tensor<T, S>(tensor: &TensorView<T, S, 2>, i: usize, j: usize) -> Option<T>
where
T: Numeric,
for<'a> &'a T: NumericRef<T>,
S: TensorRef<T, 2>,
{
use crate::tensors::views::{IndexRange, TensorMask};
let shape = tensor.shape();
if shape[0].1 == 1 || shape[1].1 == 1 {
return None;
}
if !crate::tensors::dimensions::is_square(&shape) {
return None;
}
let minored = TensorView::from(
TensorMask::from_all(
tensor.source_ref(),
[Some(IndexRange::new(i, 1)), Some(IndexRange::new(j, 1))],
)
.expect("Having just checked tensor is at least 2x2 we should be able to take a mask"),
);
determinant_less_generic::<T, _>(&minored)
}
pub fn determinant<T: Numeric>(matrix: &Matrix<T>) -> Option<T>
where
for<'a> &'a T: NumericRef<T>,
{
if matrix.rows() != matrix.columns() {
return None;
}
let length = matrix.rows();
match length {
0 => return None,
1 => return Some(matrix.scalar()),
_ => (),
};
determinant_less_generic::<T, _>(&TensorView::from(
crate::interop::TensorRefMatrix::from(matrix).ok()?,
))
}
pub fn determinant_tensor<T, S, I>(tensor: I) -> Option<T>
where
T: Numeric,
for<'a> &'a T: NumericRef<T>,
I: Into<TensorView<T, S, 2>>,
S: TensorRef<T, 2>,
{
determinant_less_generic::<T, S>(&tensor.into())
}
fn determinant_less_generic<T, S>(tensor: &TensorView<T, S, 2>) -> Option<T>
where
T: Numeric,
for<'a> &'a T: NumericRef<T>,
S: TensorRef<T, 2>,
{
let shape = tensor.shape();
if !crate::tensors::dimensions::is_square(&shape) {
return None;
}
let length = shape[0].1;
if length == 0 {
return None;
}
let matrix = tensor.index();
if length == 1 {
return Some(matrix.get([0, 0]));
}
let mut sum = T::zero();
with_each_permutation(&mut (0..length).collect(), &mut |permutation, even_swap| {
let signature = if even_swap {
T::one()
} else {
T::zero() - T::one()
};
let mut product = T::one();
for (n, i) in permutation.iter().enumerate() {
let element = matrix.get_ref([n, *i]);
product = product * element;
}
sum = sum.clone() + (signature * product);
});
Some(sum)
}
#[allow(dead_code)] fn factorial(n: usize) -> usize {
(1..=n).product()
}
fn heaps_permutations<T: Clone, F>(k: usize, list: &mut Vec<T>, consumer: &mut F)
where
F: FnMut(&mut Vec<T>),
{
if k == 1 {
consumer(list);
return;
}
for i in 0..k {
heaps_permutations(k - 1, list, consumer);
if i < k - 1 {
if k % 2 == 0 {
list.swap(i, k - 1);
} else {
list.swap(0, k - 1);
}
}
}
}
#[allow(dead_code)] fn generate_permutations<T: Clone>(list: &mut Vec<T>) -> Vec<(Vec<T>, bool)> {
let mut permutations = Vec::with_capacity(factorial(list.len()));
let mut even_swaps = true;
heaps_permutations(list.len(), list, &mut |permuted| {
permutations.push((permuted.clone(), even_swaps));
even_swaps = !even_swaps;
});
permutations
}
fn with_each_permutation<T: Clone, F>(list: &mut Vec<T>, consumer: &mut F)
where
F: FnMut(&mut Vec<T>, bool),
{
let mut even_swaps = true;
heaps_permutations(list.len(), list, &mut |permuted| {
consumer(permuted, even_swaps);
even_swaps = !even_swaps;
});
}
#[cfg(test)]
#[test]
fn test_permutations() {
let mut list = vec![1, 2, 3];
let permutations = generate_permutations(&mut list);
assert!(permutations.contains(&(vec![1, 2, 3], true)));
assert!(permutations.contains(&(vec![3, 2, 1], false)));
assert!(permutations.contains(&(vec![2, 3, 1], true)));
assert!(permutations.contains(&(vec![1, 3, 2], false)));
assert!(permutations.contains(&(vec![2, 1, 3], false)));
assert!(permutations.contains(&(vec![3, 1, 2], true)));
assert_eq!(permutations.len(), 6);
let mut list = vec![1, 2, 3, 4, 5];
let permuted = generate_permutations(&mut list);
assert!(permuted.contains(&(vec![1, 2, 3, 4, 5], true)));
assert!(permuted.contains(&(vec![1, 2, 3, 5, 4], false)));
assert!(permuted.contains(&(vec![1, 2, 5, 3, 4], true)));
let mut list = vec![0, 1];
let permuted = generate_permutations(&mut list);
assert!(permuted.contains(&(vec![0, 1], true)));
assert!(permuted.contains(&(vec![1, 0], false)));
assert_eq!(permuted.len(), 2);
}
pub fn covariance_column_features<T: Numeric>(matrix: &Matrix<T>) -> Matrix<T>
where
for<'a> &'a T: NumericRef<T>,
{
let features = matrix.columns();
let samples = T::from_usize(matrix.rows())
.expect("The maximum value of the matrix type T cannot represent this many samples");
let mut covariance_matrix = Matrix::empty(T::zero(), (features, features));
covariance_matrix.map_mut_with_index(|_, i, j| {
let feature_i_mean: T = matrix.column_iter(i).sum::<T>() / &samples;
let feature_j_mean: T = matrix.column_iter(j).sum::<T>() / &samples;
matrix
.column_reference_iter(i)
.map(|x| x - &feature_i_mean)
.zip(matrix.column_reference_iter(j).map(|y| y - &feature_j_mean))
.map(|(x, y)| x * y)
.sum::<T>()
/ &samples
});
covariance_matrix
}
pub fn covariance_row_features<T: Numeric>(matrix: &Matrix<T>) -> Matrix<T>
where
for<'a> &'a T: NumericRef<T>,
{
let features = matrix.rows();
let samples = T::from_usize(matrix.columns())
.expect("The maximum value of the matrix type T cannot represent this many samples");
let mut covariance_matrix = Matrix::empty(T::zero(), (features, features));
covariance_matrix.map_mut_with_index(|_, i, j| {
let feature_i_mean: T = matrix.row_iter(i).sum::<T>() / &samples;
let feature_j_mean: T = matrix.row_iter(j).sum::<T>() / &samples;
matrix
.row_reference_iter(i)
.map(|x| x - &feature_i_mean)
.zip(matrix.row_reference_iter(j).map(|y| y - &feature_j_mean))
.map(|(x, y)| x * y)
.sum::<T>()
/ &samples
});
covariance_matrix
}
#[track_caller]
pub fn covariance<T, S, I>(tensor: I, feature_dimension: Dimension) -> Tensor<T, 2>
where
T: Numeric,
for<'a> &'a T: NumericRef<T>,
I: Into<TensorView<T, S, 2>>,
S: TensorRef<T, 2>,
{
covariance_less_generic::<T, S>(tensor.into(), feature_dimension)
}
#[track_caller]
fn covariance_less_generic<T, S>(
tensor: TensorView<T, S, 2>,
feature_dimension: Dimension,
) -> Tensor<T, 2>
where
T: Numeric,
for<'a> &'a T: NumericRef<T>,
S: TensorRef<T, 2>,
{
let shape = tensor.shape();
let features_index = {
if shape[0].0 == feature_dimension {
0
} else if shape[1].0 == feature_dimension {
1
} else {
panic!(
"Feature dimension {:?} is not present in the input tensor's shape: {:?}",
feature_dimension, shape
);
}
};
let (feature_dimension, features) = shape[features_index];
let (_sample_dimension, samples) = shape[1 - features_index];
let samples = T::from_usize(samples)
.expect("The maximum value of the matrix type T cannot represent this many samples");
let mut covariance_matrix = Tensor::empty([("i", features), ("j", features)], T::zero());
covariance_matrix.map_mut_with_index(|[i, j], _| {
#[rustfmt::skip]
let feature_i_mean: T = tensor
.select([(feature_dimension, i)])
.iter()
.sum::<T>() / &samples;
#[rustfmt::skip]
let feature_j_mean: T = tensor
.select([(feature_dimension, j)])
.iter()
.sum::<T>() / &samples;
tensor
.select([(feature_dimension, i)])
.iter_reference()
.map(|x| x - &feature_i_mean)
.zip(
tensor
.select([(feature_dimension, j)])
.iter_reference()
.map(|y| y - &feature_j_mean),
)
.map(|(x, y)| x * y)
.sum::<T>()
/ &samples
});
covariance_matrix
}
pub fn mean<I, T: Numeric>(mut data: I) -> T
where
I: Iterator<Item = T>,
{
let mut next = data.next();
assert!(next.is_some(), "Provided iterator must not be empty");
let mut count = T::zero();
let mut sum = T::zero();
while next.is_some() {
count = count + T::one();
sum = sum + next.unwrap();
next = data.next();
}
sum / count
}
pub fn variance<I, T: Numeric>(data: I) -> T
where
I: Iterator<Item = T>,
{
let list = data.collect::<Vec<T>>();
assert!(!list.is_empty(), "Provided iterator must not be empty");
let m = mean(list.iter().cloned());
mean(
list.into_iter()
.map(|x| (x.clone() - m.clone()) * (x - m.clone())),
)
}
pub fn softmax<I, T: Real>(data: I) -> Vec<T>
where
I: Iterator<Item = T>,
{
let list = data.collect::<Vec<T>>();
if list.is_empty() {
return Vec::with_capacity(0);
}
let max = list
.iter()
.max_by(|a, b| a.partial_cmp(b).expect("NaN should not be in list"))
.unwrap();
let denominator: T = list.iter().cloned().map(|x| (x - max).exp()).sum();
list.iter()
.cloned()
.map(|x| (x - max).exp() / denominator.clone())
.collect()
}
pub fn f1_score<T: Numeric>(precision: T, recall: T) -> T {
(T::one() + T::one()) * ((precision.clone() * recall.clone()) / (precision + recall))
}
pub fn cholesky_decomposition<T: Numeric + Sqrt<Output = T>>(
matrix: &Matrix<T>,
) -> Option<Matrix<T>>
where
for<'a> &'a T: NumericRef<T>,
{
let lower_triangular = cholesky_decomposition_less_generic::<T, _>(&TensorView::from(
crate::interop::TensorRefMatrix::from(matrix).ok()?,
))?;
Some(lower_triangular.into_matrix())
}
pub fn cholesky_decomposition_tensor<T, S, I>(tensor: I) -> Option<Tensor<T, 2>>
where
T: Numeric + Sqrt<Output = T>,
for<'a> &'a T: NumericRef<T>,
I: Into<TensorView<T, S, 2>>,
S: TensorRef<T, 2>,
{
cholesky_decomposition_less_generic::<T, S>(&tensor.into())
}
#[rustfmt::skip]
fn cholesky_decomposition_less_generic<T, S>(tensor: &TensorView<T, S, 2>) -> Option<Tensor<T, 2>>
where
T: Numeric + Sqrt<Output = T>,
for<'a> &'a T: NumericRef<T>,
S: TensorRef<T, 2>,
{
let shape = tensor.shape();
if !crate::tensors::dimensions::is_square(&shape) {
return None;
}
let mut lower_triangular = Tensor::empty(shape, T::zero());
let mut lower_triangular_indexing = lower_triangular.index_mut();
let tensor_indexing = tensor.index();
let n = shape[0].1;
for i in 0..n {
for j in 0..=i {
let sum = {
let mut sum = T::zero();
for k in 0..j {
sum = &sum
+ (lower_triangular_indexing.get_ref([i, k])
* lower_triangular_indexing.get_ref([j, k]));
}
sum
};
*lower_triangular_indexing.get_ref_mut([i, j]) = if i == j {
let entry_squared = tensor_indexing.get_ref([i, j]) - sum;
if entry_squared <= T::zero() {
return None;
}
entry_squared.sqrt()
} else {
(tensor_indexing.get_ref([i, j]) - sum)
* (T::one() / lower_triangular_indexing.get_ref([j, j]))
};
}
}
Some(lower_triangular)
}
#[derive(Clone, Debug)]
#[non_exhaustive]
pub struct LDLTDecomposition<T> {
pub l: Matrix<T>,
pub d: Matrix<T>,
}
impl<T: std::fmt::Display + Clone> std::fmt::Display for LDLTDecomposition<T> {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
writeln!(f, "L:\n{}", &self.l)?;
write!(f, "D:\n{}", &self.d)
}
}
impl<T> LDLTDecomposition<T> {
pub fn from_unchecked(l: Matrix<T>, d: Matrix<T>) -> LDLTDecomposition<T> {
LDLTDecomposition { l, d }
}
}
pub fn ldlt_decomposition<T>(matrix: &Matrix<T>) -> Option<LDLTDecomposition<T>>
where
T: Numeric,
for<'a> &'a T: NumericRef<T>,
{
let decomposition = ldlt_decomposition_less_generic::<T, _>(&TensorView::from(
crate::interop::TensorRefMatrix::from(matrix).ok()?,
))?;
Some(LDLTDecomposition {
l: decomposition.l.into_matrix(),
d: decomposition.d.into_matrix(),
})
}
#[derive(Clone, Debug)]
#[non_exhaustive]
pub struct LDLTDecompositionTensor<T> {
pub l: Tensor<T, 2>,
pub d: Tensor<T, 2>,
}
impl<T: std::fmt::Display + Clone> std::fmt::Display for LDLTDecompositionTensor<T> {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
writeln!(f, "L:\n{}", &self.l)?;
write!(f, "D:\n{}", &self.d)
}
}
impl<T> LDLTDecompositionTensor<T> {
pub fn from_unchecked(l: Tensor<T, 2>, d: Tensor<T, 2>) -> LDLTDecompositionTensor<T> {
LDLTDecompositionTensor { l, d }
}
}
pub fn ldlt_decomposition_tensor<T, S, I>(tensor: I) -> Option<LDLTDecompositionTensor<T>>
where
T: Numeric,
for<'a> &'a T: NumericRef<T>,
I: Into<TensorView<T, S, 2>>,
S: TensorRef<T, 2>,
{
ldlt_decomposition_less_generic::<T, S>(&tensor.into())
}
fn ldlt_decomposition_less_generic<T, S>(
tensor: &TensorView<T, S, 2>,
) -> Option<LDLTDecompositionTensor<T>>
where
T: Numeric,
for<'a> &'a T: NumericRef<T>,
S: TensorRef<T, 2>,
{
let shape = tensor.shape();
if !crate::tensors::dimensions::is_square(&shape) {
return None;
}
let mut lower_triangular = Tensor::empty(shape, T::zero());
let mut diagonal = Tensor::empty(shape, T::zero());
let mut lower_triangular_indexing = lower_triangular.index_mut();
let mut diagonal_indexing = diagonal.index_mut();
let tensor_indexing = tensor.index();
let n = shape[0].1;
for j in 0..n {
#[rustfmt::skip]
let sum = {
let mut sum = T::zero();
for k in 0..j {
sum = &sum + (
lower_triangular_indexing.get_ref([j, k]) *
lower_triangular_indexing.get_ref([j, k]) *
diagonal_indexing.get_ref([k, k])
);
}
sum
};
*diagonal_indexing.get_ref_mut([j, j]) = {
let entry = tensor_indexing.get_ref([j, j]) - sum;
if entry == T::zero() {
return None;
}
entry
};
for i in j..n {
#[rustfmt::skip]
let x = if i == j {
T::one()
} else {
let sum = {
let mut sum = T::zero();
for k in 0..j {
sum = &sum + (
lower_triangular_indexing.get_ref([i, k]) *
lower_triangular_indexing.get_ref([j, k]) *
diagonal_indexing.get_ref([k, k])
);
}
sum
};
(tensor_indexing.get_ref([i, j]) - sum) *
(T::one() / diagonal_indexing.get_ref([j, j]))
};
*lower_triangular_indexing.get_ref_mut([i, j]) = x;
}
}
Some(LDLTDecompositionTensor {
l: lower_triangular,
d: diagonal,
})
}
#[derive(Clone, Debug)]
#[non_exhaustive]
pub struct QRDecomposition<T> {
pub q: Matrix<T>,
pub r: Matrix<T>,
}
impl<T: std::fmt::Display + Clone> std::fmt::Display for QRDecomposition<T> {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
writeln!(f, "Q:\n{}", &self.q)?;
write!(f, "R:\n{}", &self.r)
}
}
impl<T> QRDecomposition<T> {
pub fn from_unchecked(q: Matrix<T>, r: Matrix<T>) -> QRDecomposition<T> {
QRDecomposition { q, r }
}
}
fn householder_matrix_tensor<T: Real>(vector: Vec<T>, names: [Dimension; 2]) -> Tensor<T, 2>
where
for<'a> &'a T: RealRef<T>,
{
let x = Tensor::from([("r", vector.len())], vector);
let shape = x.shape();
let rows = shape[0].1;
let length = x.euclidean_length();
let a = {
let sign = x.first();
if sign > T::zero() { length } else { -length }
};
let u = {
let mut u = x;
let mut u_indexing = u.index_mut();
*u_indexing.get_ref_mut([0]) = u_indexing.get_ref([0]) + a;
u
};
let v = {
let length = u.euclidean_length();
u.map(|element| element / &length)
};
let identity = Tensor::diagonal([(names[0], rows), (names[1], rows)], T::one());
let two = T::one() + T::one();
let v_column = Tensor::from([(names[0], rows), (names[1], 1)], v.iter().collect());
let v_row = Tensor::from([(names[0], 1), (names[1], rows)], v.iter().collect());
identity - ((v_column * v_row) * two)
}
pub fn qr_decomposition<T: Real>(matrix: &Matrix<T>) -> Option<QRDecomposition<T>>
where
for<'a> &'a T: RealRef<T>,
{
let decomposition = qr_decomposition_less_generic::<T, _>(&TensorView::from(
crate::interop::TensorRefMatrix::from(matrix).ok()?,
))?;
Some(QRDecomposition {
q: decomposition.q.into_matrix(),
r: decomposition.r.into_matrix(),
})
}
#[derive(Clone, Debug)]
#[non_exhaustive]
pub struct QRDecompositionTensor<T> {
pub q: Tensor<T, 2>,
pub r: Tensor<T, 2>,
}
impl<T: std::fmt::Display + Clone> std::fmt::Display for QRDecompositionTensor<T> {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
writeln!(f, "Q:\n{}", &self.q)?;
write!(f, "R:\n{}", &self.r)
}
}
impl<T> QRDecompositionTensor<T> {
pub fn from_unchecked(q: Tensor<T, 2>, r: Tensor<T, 2>) -> QRDecompositionTensor<T> {
QRDecompositionTensor { q, r }
}
}
pub fn qr_decomposition_tensor<T, S, I>(tensor: I) -> Option<QRDecompositionTensor<T>>
where
T: Real,
for<'a> &'a T: RealRef<T>,
I: Into<TensorView<T, S, 2>>,
S: TensorRef<T, 2>,
{
qr_decomposition_less_generic::<T, S>(&tensor.into())
}
fn qr_decomposition_less_generic<T, S>(
tensor: &TensorView<T, S, 2>,
) -> Option<QRDecompositionTensor<T>>
where
T: Real,
for<'a> &'a T: RealRef<T>,
S: TensorRef<T, 2>,
{
let shape = tensor.shape();
let names = crate::tensors::dimensions::names_of(&shape);
let rows = shape[0].1;
let columns = shape[1].1;
if columns > rows {
return None;
}
let iterations = std::cmp::min(rows - 1, columns);
let mut q = None;
let mut r = tensor.map(|x| x);
for c in 0..iterations {
let submatrix_first_column = r
.select([(shape[1].0, c)])
.iter()
.skip(c)
.collect::<Vec<_>>();
let h = householder_matrix_tensor::<T>(submatrix_first_column, names);
let h = {
let h_indexing = h.index();
let mut identity = Tensor::diagonal([(shape[0].0, rows), (shape[1].0, rows)], T::one());
let inset = c;
for ([i, j], x) in identity.iter_reference_mut().with_index() {
if i >= inset && j >= inset {
*x = h_indexing.get([i - inset, j - inset]);
}
}
identity
};
r = &h * r;
match q {
None => q = Some(h),
Some(h_previous) => q = Some(h_previous * h),
}
}
Some(QRDecompositionTensor {
q: q.unwrap(),
r,
})
}