use super::{ArrayFrameImpl, GenericFrameImpl};
use crate::dense::linalg::lapack::qr::{EnablePivoting, QMode};
use crate::operator::element::Element;
use crate::{
DynArray, FrameImpl, FrameRefAccess, InnerProductSpace, Lapack, Norm, Qr, RlstError,
RlstResult, RlstScalar,
};
use num::{Float, One, Zero};
#[derive(Clone, Copy, Debug)]
pub struct FrameQrOptions<Real> {
pub atol: Real,
pub rtol: Real,
}
impl<Real: Float> Default for FrameQrOptions<Real> {
fn default() -> Self {
Self {
atol: Real::zero(),
rtol: Real::from(100).unwrap() * Real::epsilon(),
}
}
}
impl<Real: Float> FrameQrOptions<Real> {
fn threshold(self, scale: Real) -> RlstResult<Real> {
if !self.atol.is_finite()
|| !self.rtol.is_finite()
|| self.atol < Real::zero()
|| self.rtol < Real::zero()
{
return Err(RlstError::GeneralError(
"Frame QR tolerances must be finite and nonnegative".into(),
));
}
finite(self.atol.max(self.rtol * scale))
}
}
pub struct FrameQrDecomposition<Q, Item> {
pub q: Q,
pub r: DynArray<Item, 2>,
pub permutation: Vec<usize>,
}
impl<Q, Item> FrameQrDecomposition<Q, Item> {
pub fn rank(&self) -> usize {
self.r.shape()[0]
}
}
pub trait FrameQr<'a>: FrameImpl<'a> + Sized
where
Self::Space: InnerProductSpace,
<Self::Space as crate::LinearSpace>::F: RlstScalar,
{
fn qr_pivoted(
&self,
options: FrameQrOptions<<<Self::Space as crate::LinearSpace>::F as RlstScalar>::Real>,
) -> RlstResult<FrameQrDecomposition<Self, <Self::Space as crate::LinearSpace>::F>>;
}
fn finite<Real: Float>(value: Real) -> RlstResult<Real> {
if value.is_finite() {
Ok(value)
} else {
Err(RlstError::OperationFailed(
"Non-finite value in frame QR".into(),
))
}
}
fn project_twice<'a, S: InnerProductSpace>(
q: &GenericFrameImpl<'a, S>,
original: &Element<S>,
) -> RlstResult<(Element<'a, S>, Vec<S::F>)>
where
S::F: RlstScalar,
{
let mut residual = q.space.copy_from(original);
let coefficients = super::orthogonalize_twice(q, &mut residual)?;
for coefficient in &coefficients {
finite(coefficient.abs())?;
}
Ok((residual, coefficients))
}
impl<'a, S: InnerProductSpace> FrameQr<'a> for GenericFrameImpl<'a, S>
where
S::F: RlstScalar,
{
fn qr_pivoted(
&self,
options: FrameQrOptions<<S::F as RlstScalar>::Real>,
) -> RlstResult<FrameQrDecomposition<Self, S::F>> {
let n = self.len();
let mut scale = <S::F as RlstScalar>::Real::zero();
for element in self.iter() {
scale = Float::max(scale, finite(element.norm())?);
}
let threshold = options.threshold(scale)?;
let mut q = Self::new(self.space);
let mut permutation: Vec<_> = (0..n).collect();
let mut r = DynArray::<S::F, 2>::from_shape([n, n]);
while q.len() < n {
let k = q.len();
let mut best = k;
let mut largest = <S::F as RlstScalar>::Real::zero();
let mut pivot = None;
for j in k..n {
let (residual, coefficients) = project_twice(&q, &self.elements[permutation[j]])?;
for (i, coefficient) in coefficients.into_iter().enumerate() {
r[[i, j]] = coefficient;
}
let norm = finite(residual.norm())?;
if pivot.is_none() || norm > largest {
largest = norm;
best = j;
pivot = Some(residual);
}
}
if largest <= threshold {
break;
}
permutation.swap(k, best);
for i in 0..k {
let value = r[[i, k]];
r[[i, k]] = r[[i, best]];
r[[i, best]] = value;
}
r[[k, k]] = S::F::from_real(largest);
let mut pivot = pivot.unwrap();
let inverse = S::F::from_real(<S::F as RlstScalar>::Real::one() / largest);
finite(inverse.abs())?;
self.space.scale_inplace(&inverse, &mut pivot);
finite(pivot.norm())?;
q.elements.push(pivot);
}
let mut compact_r = DynArray::from_shape([q.len(), n]);
for j in 0..n {
for i in 0..q.len() {
compact_r[[i, j]] = r[[i, j]];
}
}
Ok(FrameQrDecomposition {
q,
r: compact_r,
permutation,
})
}
}
impl<'a, Item: Lapack> FrameQr<'a> for ArrayFrameImpl<'a, Item> {
fn qr_pivoted(
&self,
options: FrameQrOptions<Item::Real>,
) -> RlstResult<FrameQrDecomposition<Self, Item>> {
let [m, n] = self.matrix().shape();
let mut scale = Item::Real::zero();
for j in 0..n {
let mut norm = Item::Real::zero();
for i in 0..m {
norm = norm.hypot(finite(self.data[[i, j]].abs())?);
}
scale = Float::max(scale, finite(norm)?);
}
let threshold = options.threshold(scale)?;
if m == 0 || n == 0 || scale <= threshold {
return Ok(FrameQrDecomposition {
q: Self::new(self.space),
r: DynArray::from_shape([0, n]),
permutation: (0..n).collect(),
});
}
let factor = self.matrix().qr(EnablePivoting::Yes)?;
let full_r = factor.r_mat()?;
let k = m.min(n);
let mut rank = 0;
for j in 0..n {
let mut norm = Item::Real::zero();
for i in (0..k).rev() {
norm = finite(norm.hypot(finite(full_r[[i, j]].abs())?))?;
if norm > threshold {
rank = rank.max(i + 1);
}
}
}
let full_q = factor.q_mat(QMode::Compact)?;
let mut q_data = DynArray::from_shape([m, rank]);
for j in 0..rank {
for i in 0..m {
finite(full_q[[i, j]].abs())?;
q_data[[i, j]] = full_q[[i, j]];
}
}
let mut r = DynArray::from_shape([rank, n]);
for j in 0..n {
for i in 0..rank {
r[[i, j]] = full_r[[i, j]];
}
}
let q = Self {
space: self.space,
data: q_data,
len: rank,
};
Ok(FrameQrDecomposition {
q,
r,
permutation: factor.perm(),
})
}
}