Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions src/base/norm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,15 @@ pub trait Norm<T: SimdComplexField> {
/// Euclidean norm.
#[derive(Copy, Clone, Debug)]
pub struct EuclideanNorm;

/// Lp norm.
#[derive(Copy, Clone, Debug)]
pub struct LpNorm(pub i32);

/// The induced matrix 1-norm (maximum absolute column sum).
#[derive(Copy, Clone, Debug)]
pub struct OneNorm;

/// L-infinite norm aka. Chebytchev norm aka. uniform norm aka. suppremum norm.
#[derive(Copy, Clone, Debug)]
pub struct UniformNorm;
Expand Down Expand Up @@ -120,6 +126,45 @@ impl<T: SimdComplexField> Norm<T> for LpNorm {
}
}

impl<T: SimdComplexField> Norm<T> for OneNorm {
#[inline]
fn norm<R, C, S>(&self, m: &Matrix<T, R, C, S>) -> T::SimdRealField
where
R: Dim,
C: Dim,
S: Storage<T, R, C>,
{
m.column_iter()
.map(|col| col.fold(T::SimdRealField::zero(), |a, b| a + b.simd_modulus()))
.fold(T::SimdRealField::zero(), T::SimdRealField::simd_max)
}

#[inline]
fn metric_distance<R1, C1, S1, R2, C2, S2>(
&self,
m1: &Matrix<T, R1, C1, S1>,
m2: &Matrix<T, R2, C2, S2>,
) -> T::SimdRealField
where
R1: Dim,
C1: Dim,
S1: Storage<T, R1, C1>,
R2: Dim,
C2: Dim,
S2: Storage<T, R2, C2>,
ShapeConstraint: SameNumberOfRows<R1, R2> + SameNumberOfColumns<C1, C2>,
{
m1.column_iter()
.zip(m2.column_iter())
.map(|(c1, c2)| {
c1.zip_fold(&c2, T::SimdRealField::zero(), |acc, a, b| {
acc + (a - b).simd_modulus()
})
})
.fold(T::SimdRealField::zero(), T::SimdRealField::simd_max)
}
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm in favor of having this in here, but I'll page the owner of the library privately, since I don't feel comfortable deciding this alone. The problem is that the current LpNorm implementation looks deceptively similar and the struct is poorly documented. What the current LpNorm calculates is the elementwise Lp norm and there should be a comment explaining this and giving a formula. Your documentation is much better, but still relies on everyone understanding what induced norm means. Would you be so kind to add some docs to both?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've updated the docs for both as requested, actually updated docs for all the norms with warnings when needed.

There's a meaningful inconsistency in how they all behave on matrices vs. vectors. In addition to what you said about LpNorm, UniformNorm has the same issue. EuclideanNorm on a matrix computes the Frobenius norm, not the spectral norm.

Making them align with user expectation would be a breaking change but would be cool to see at some point maybe haha.

impl<T: SimdComplexField> Norm<T> for UniformNorm {
#[inline]
fn norm<R, C, S>(&self, m: &Matrix<T, R, C, S>) -> T::SimdRealField
Expand Down Expand Up @@ -316,6 +361,16 @@ impl<T: Scalar, R: Dim, C: Dim, S: Storage<T, R, C>> Matrix<T, R, C, S> {
self.apply_norm(&LpNorm(p))
}

/// The induced matrix 1-norm (maximum absolute column sum).
#[inline]
#[must_use]
pub fn one_norm(&self) -> T::SimdRealField
where
T: SimdComplexField,
{
self.apply_norm(&OneNorm)
}

/// Attempts to normalize `self`.
///
/// The components of this matrix can be SIMD types.
Expand Down
17 changes: 15 additions & 2 deletions src/linalg/decomposition.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use crate::storage::Storage;
use crate::{
Allocator, Bidiagonal, Cholesky, ColPivQR, ComplexField, DefaultAllocator, Dim, DimDiff,
DimMin, DimMinimum, DimSub, FullPivLU, Hessenberg, LU, Matrix, OMatrix, QR, RealField, SVD,
Schur, SymmetricEigen, SymmetricTridiagonal, U1, UDU,
DimMin, DimMinimum, DimSub, FullPivLU, Hessenberg, LBLT, LU, Matrix, OMatrix, QR, RealField,
SVD, Schur, SymmetricEigen, SymmetricTridiagonal, U1, UDU,
};

/// # Rectangular matrix decomposition
Expand Down Expand Up @@ -244,6 +244,7 @@ impl<T: ComplexField, R: Dim, C: Dim, S: Storage<T, R, C>> Matrix<T, R, C, S> {
/// | -------------------------|---------------------------|--------------|
/// | Hessenberg | `Q * H * Qᵀ` | `Q` is a unitary matrix and `H` an upper-Hessenberg matrix. |
/// | Cholesky | `L * Lᵀ` | `L` is a lower-triangular matrix. |
/// | LBLT decomposition | `Pᵀ * L * B * Lᴴ * P` | `L` is unit lower-triangular, `B` is Hermitian block-diagonal, and `P` is a permutation matrix. |
/// | UDU | `U * D * Uᵀ` | `U` is a upper-triangular matrix, and `D` a diagonal matrix. |
/// | Schur decomposition | `Q * T * Qᵀ` | `Q` is an unitary matrix and `T` a quasi-upper-triangular matrix. |
/// | Symmetric eigendecomposition | `Q ~ Λ ~ Qᵀ` | `Q` is an unitary matrix, and `Λ` is a real diagonal matrix. |
Expand All @@ -260,6 +261,18 @@ impl<T: ComplexField, D: Dim, S: Storage<T, D, D>> Matrix<T, D, D, S> {
Cholesky::new(self.into_owned())
}

/// Computes the LBLT decomposition of this matrix.
/// The input matrix `self` is assumed to be Hermitian (symmetric) and the decomposition will
/// only read the lower-triangular part of `self`.
pub fn lblt(self) -> LBLT<T, D>
where
T: Copy,
T::RealField: Copy,
DefaultAllocator: Allocator<D> + Allocator<D, D>,
{
LBLT::new(self.into_owned())
}

/// Attempts to compute the UDU decomposition of this matrix.
///
/// The input matrix `self` is assumed to be symmetric and this decomposition will only read
Expand Down
87 changes: 45 additions & 42 deletions src/linalg/exp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -156,31 +156,49 @@ where
fn d4_tight(&mut self) -> T::RealField {
if self.d4_exact.is_none() {
self.calc_a4();
self.d4_exact = Some(one_norm(self.a4.as_ref().unwrap()).powf(convert(0.25)));
self.d4_exact = Some(self.a4.as_ref().unwrap().one_norm().powf(convert(0.25)));
}
self.d4_exact.clone().unwrap()
}

fn d6_tight(&mut self) -> T::RealField {
if self.d6_exact.is_none() {
self.calc_a6();
self.d6_exact = Some(one_norm(self.a6.as_ref().unwrap()).powf(convert(1.0 / 6.0)));
self.d6_exact = Some(
self.a6
.as_ref()
.unwrap()
.one_norm()
.powf(convert(1.0 / 6.0)),
);
}
self.d6_exact.clone().unwrap()
}

fn d8_tight(&mut self) -> T::RealField {
if self.d8_exact.is_none() {
self.calc_a8();
self.d8_exact = Some(one_norm(self.a8.as_ref().unwrap()).powf(convert(1.0 / 8.0)));
self.d8_exact = Some(
self.a8
.as_ref()
.unwrap()
.one_norm()
.powf(convert(1.0 / 8.0)),
);
}
self.d8_exact.clone().unwrap()
}

fn d10_tight(&mut self) -> T::RealField {
if self.d10_exact.is_none() {
self.calc_a10();
self.d10_exact = Some(one_norm(self.a10.as_ref().unwrap()).powf(convert(1.0 / 10.0)));
self.d10_exact = Some(
self.a10
.as_ref()
.unwrap()
.one_norm()
.powf(convert(1.0 / 10.0)),
);
}
self.d10_exact.clone().unwrap()
}
Expand All @@ -196,7 +214,7 @@ where

if self.d4_approx.is_none() {
self.calc_a4();
self.d4_approx = Some(one_norm(self.a4.as_ref().unwrap()).powf(convert(0.25)));
self.d4_approx = Some(self.a4.as_ref().unwrap().one_norm().powf(convert(0.25)));
}

self.d4_approx.clone().unwrap()
Expand All @@ -213,7 +231,13 @@ where

if self.d6_approx.is_none() {
self.calc_a6();
self.d6_approx = Some(one_norm(self.a6.as_ref().unwrap()).powf(convert(1.0 / 6.0)));
self.d6_approx = Some(
self.a6
.as_ref()
.unwrap()
.one_norm()
.powf(convert(1.0 / 6.0)),
);
}

self.d6_approx.clone().unwrap()
Expand All @@ -230,7 +254,13 @@ where

if self.d8_approx.is_none() {
self.calc_a8();
self.d8_approx = Some(one_norm(self.a8.as_ref().unwrap()).powf(convert(1.0 / 8.0)));
self.d8_approx = Some(
self.a8
.as_ref()
.unwrap()
.one_norm()
.powf(convert(1.0 / 8.0)),
);
}

self.d8_approx.clone().unwrap()
Expand All @@ -247,7 +277,13 @@ where

if self.d10_approx.is_none() {
self.calc_a10();
self.d10_approx = Some(one_norm(self.a10.as_ref().unwrap()).powf(convert(1.0 / 10.0)));
self.d10_approx = Some(
self.a10
.as_ref()
.unwrap()
.one_norm()
.powf(convert(1.0 / 10.0)),
);
}

self.d10_approx.clone().unwrap()
Expand Down Expand Up @@ -430,7 +466,7 @@ where
let choose_2m_m = factorial(2 * m) / (m_factorial * m_factorial);

let abs_c_recip = choose_2m_m * factorial(2 * m + 1);
let alpha = a_abs_onenorm / one_norm(a);
let alpha = a_abs_onenorm / a.one_norm();
let alpha: f64 = try_convert::<_, f64>(alpha).unwrap() / abs_c_recip as f64;

let u = 2_f64.powf(-53.0);
Expand All @@ -451,27 +487,6 @@ where
q.lu().solve(&p).unwrap()
}

fn one_norm<T, D>(m: &OMatrix<T, D, D>) -> T::RealField
where
T: ComplexField,
D: Dim,
DefaultAllocator: Allocator<D, D>,
{
let mut max = <T as ComplexField>::RealField::zero();

for i in 0..m.ncols() {
let col = m.column(i);
max = max.max(
col.iter()
.fold(<T as ComplexField>::RealField::zero(), |a, b| {
a + b.clone().abs()
}),
);
}

max
}

impl<T: ComplexField, D> OMatrix<T, D, D>
where
D: DimMin<D, Output = D>,
Expand Down Expand Up @@ -539,15 +554,3 @@ where
x
}
}

#[cfg(test)]
mod tests {
#[test]
#[allow(clippy::float_cmp)]
fn one_norm() {
use crate::Matrix3;
let m = Matrix3::new(-3.0, 5.0, 7.0, 2.0, 6.0, 4.0, 0.0, 2.0, 8.0);

assert_eq!(super::one_norm(&m), 19.0);
}
}
Loading
Loading