use crate::{
core::prelude::*,
errors::prelude::*,
linalg::prelude::*,
numeric::prelude::*,
validators::prelude::*,
};
pub trait ArrayLinalgDecompositions<N: NumericOps> where Self: Sized + Clone {
fn qr(&self) -> LinalgResult<N>;
}
impl <N: NumericOps> ArrayLinalgDecompositions<N> for Array<N> {
fn qr(&self) -> LinalgResult<N> {
self.is_dim_unsupported(&[0, 1])?;
self.is_square()?;
if self.ndim()? == 2 {
Ok(vec![Self::gram_schmidt(self)?])
} else {
let shape = self.get_shape()?;
let sub_shape = shape[self.ndim()? - 2 ..].to_vec();
let qrs = self
.ravel()?
.split(self.len()? / sub_shape.iter().product::<usize>(), None)?
.iter()
.map(|arr| arr.reshape(&sub_shape).qr())
.collect::<Vec<Result<Vec<(Self, Self)>, _>>>()
.has_error()?.into_iter()
.flat_map(Result::unwrap)
.collect::<Vec<(Self, Self)>>();
Ok(qrs)
}
}
}
impl <N: NumericOps> ArrayLinalgDecompositions<N> for Result<Array<N>, ArrayError> {
fn qr(&self) -> LinalgResult<N> {
self.clone()?.qr()
}
}
trait QRHelper<N: NumericOps> {
fn gram_schmidt(arr: &Array<N>) -> Result<(Array<N>, Array<N>), ArrayError> {
fn project<N: NumericOps>(u: &Array<N>, a: &Array<N>) -> Result<Array<N>, ArrayError> {
let cols = u.len()?;
let result = u.inner(a).broadcast_to(vec![cols])?
/ u.inner(u).broadcast_to(vec![cols])?
* u.clone();
Ok(result)
}
fn normalize<N: NumericOps>(arr: &Array<N>) -> Result<Array<N>, ArrayError> {
let norm = arr
.get_elements()?
.into_iter()
.map(|u| u.to_f64().powi(2))
.sum::<f64>().sqrt();
Array::single(N::from(norm))
.broadcast_to(vec![arr.len()?])
}
let (mut u_vecs, mut e_vecs) = (vec![], vec![]);
for col in arr.get_columns()? {
let mut a = col;
for u in &u_vecs { a -= project(u, &a)? }
u_vecs.push(a.clone());
let a_norm = normalize(&a)?;
e_vecs.push(a / a_norm);
}
let q = Array::concatenate(e_vecs, None)
.reshape(&arr.get_shape()?)
.transpose(None)?;
let r = q
.transpose(None)
.dot(arr)?;
Ok((q, r))
}
}
impl <N: NumericOps> QRHelper<N> for Array<N> {}