Mon, 30 Dec 2024 09:49:08 -0500
Discrete gradients
src/discrete_gradient.rs | file | annotate | diff | comparison | revisions | |
src/linops.rs | file | annotate | diff | comparison | revisions |
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/discrete_gradient.rs Mon Dec 30 09:49:08 2024 -0500 @@ -0,0 +1,489 @@ +/*! +Simple disrete gradient operators + */ +use numeric_literals::replace_float_literals; +use nalgebra::{ + DVector, Matrix, U1, Storage, StorageMut, Dyn +}; +use crate::types::Float; +use crate::instance::Instance; +use crate::linops::{Mapping, Linear, BoundedLinear, Adjointable, GEMV}; +use crate::norms::{Norm, L2}; + +#[derive(Copy, Clone, Debug)] +/// Forward differences with Neumann boundary conditions +pub struct ForwardNeumann; + +#[derive(Copy, Clone, Debug)] +/// Forward differences with Dirichlet boundary conditions +pub struct ForwardDirichlet; + +#[derive(Copy, Clone, Debug)] +/// Backward differences with Dirichlet boundary conditions +pub struct BackwardDirichlet; + +#[derive(Copy, Clone, Debug)] +/// Backward differences with Neumann boundary conditions +pub struct BackwardNeumann; + +/// Finite differences gradient +pub struct Grad< + F : Float + nalgebra::RealField, + B : Discretisation<F>, + const N : usize +> { + dims : [usize; N], + h : F, // may be negative to implement adjoints! + discretisation : B, +} + + +/// Finite differences divergence +pub struct Div< + F : Float + nalgebra::RealField, + B : Discretisation<F>, + const N : usize +> { + dims : [usize; N], + h : F, // may be negative to implement adjoints! + discretisation : B, +} + +/// Internal: classification of a point in a 1D discretisation +pub enum DiscretisationOrInterior { + /// center, forward + LeftBoundary(usize, usize), + /// center, backward + RightBoundary(usize, usize), + /// center, (backward, forward) + Interior(usize, (usize, usize)), +} + +use DiscretisationOrInterior::*; + +/// Trait for different discretisations +pub trait Discretisation<F : Float + nalgebra::RealField> : Copy { + /// Opposite discretisation, appropriate for adjoints with negated cell width. + type Opposite : Discretisation<F>; + + /// Add to appropiate index of `v` (as determined by `b`) the appropriate difference + /// of `x` with cell width `h`. + fn add_diff_mut<SMut, S>( + &self, + v : &mut Matrix<F, Dyn, U1, SMut>, + x : &Matrix<F, Dyn, U1, S>, + α : F, + b : DiscretisationOrInterior, + ) where + SMut : StorageMut<F, Dyn, U1>, + S : Storage<F, Dyn, U1>; + + /// Give the opposite discretisation, appropriate for adjoints with negated `h`. + fn opposite(&self) -> Self::Opposite; + + /// Bound for the corresponding operator norm. + #[replace_float_literals(F::cast_from(literal))] + fn opnorm_bound(&self, h : F) -> F { + // See: Chambolle, “An Algorithm for Total Variation Minimization and Applications”. + // Ok for forward and backward differences. + // + // Fuck nalgebra for polluting everything with its own shit. + num_traits::Float::sqrt(8.0) / h + } +} + +impl<F : Float + nalgebra::RealField> Discretisation<F> for ForwardNeumann { + type Opposite = BackwardDirichlet; + + #[inline] + fn add_diff_mut<SMut, S>( + &self, + v : &mut Matrix<F, Dyn, U1, SMut>, + x : &Matrix<F, Dyn, U1, S>, + α : F, + b : DiscretisationOrInterior, + ) where + SMut : StorageMut<F, Dyn, U1>, + S : Storage<F, Dyn, U1> + { + match b { + Interior(c, (_, f)) | LeftBoundary(c, f) => { v[c] += (x[f] - x[c]) * α }, + RightBoundary(_c, _b) => { }, + } + } + + #[inline] + fn opposite(&self) -> Self::Opposite { + BackwardDirichlet + } +} + +impl<F : Float + nalgebra::RealField> Discretisation<F> for BackwardNeumann { + type Opposite = ForwardDirichlet; + + #[inline] + fn add_diff_mut<SMut, S>( + &self, + v : &mut Matrix<F, Dyn, U1, SMut>, + x : &Matrix<F, Dyn, U1, S>, + α : F, + b : DiscretisationOrInterior, + ) where + SMut : StorageMut<F, Dyn, U1>, + S : Storage<F, Dyn, U1> + { + match b { + Interior(c, (b, _)) | RightBoundary(c, b) => { v[c] += (x[c] - x[b]) * α }, + LeftBoundary(_c, _f) => { }, + } + } + + #[inline] + fn opposite(&self) -> Self::Opposite { + ForwardDirichlet + } +} + +impl<F : Float + nalgebra::RealField> Discretisation<F> for BackwardDirichlet { + type Opposite = ForwardNeumann; + + #[inline] + fn add_diff_mut<SMut, S>( + &self, + v : &mut Matrix<F, Dyn, U1, SMut>, + x : &Matrix<F, Dyn, U1, S>, + α : F, + b : DiscretisationOrInterior, + ) where + SMut : StorageMut<F, Dyn, U1>, + S : Storage<F, Dyn, U1> + { + match b { + Interior(c, (b, _f)) => { v[c] += (x[c] - x[b]) * α }, + LeftBoundary(c, _f) => { v[c] += x[c] * α }, + RightBoundary(c, b) => { v[c] -= x[b] * α }, + } + } + + #[inline] + fn opposite(&self) -> Self::Opposite { + ForwardNeumann + } +} + +impl<F : Float + nalgebra::RealField> Discretisation<F> for ForwardDirichlet { + type Opposite = BackwardNeumann; + + #[inline] + fn add_diff_mut<SMut, S>( + &self, + v : &mut Matrix<F, Dyn, U1, SMut>, + x : &Matrix<F, Dyn, U1, S>, + α : F, + b : DiscretisationOrInterior, + ) where + SMut : StorageMut<F, Dyn, U1>, + S : Storage<F, Dyn, U1> + { + match b { + Interior(c, (_b, f)) => { v[c] += (x[f] - x[c]) * α }, + LeftBoundary(c, f) => { v[c] += x[f] * α }, + RightBoundary(c, _b) => { v[c] -= x[c] * α }, + } + } + + #[inline] + fn opposite(&self) -> Self::Opposite { + BackwardNeumann + } +} + +struct Iter<'a, const N : usize> { + /// Dimensions + dims : &'a [usize; N], + /// Dimension along which to calculate differences + d : usize, + /// Stride along coordinate d + d_stride : usize, + /// Cartesian indices + i : [usize; N], + /// Linear index + k : usize, + /// Maximal linear index + len : usize +} + +impl<'a, const N : usize> Iter<'a, N> { + fn new(dims : &'a [usize; N], d : usize) -> Self { + let d_stride = dims[0..d].iter().product::<usize>(); + let len = dims.iter().product::<usize>(); + Iter{ dims, d, d_stride, i : [0; N], k : 0, len } + } +} + +impl<'a, const N : usize> Iterator for Iter<'a, N> { + type Item = DiscretisationOrInterior; + fn next(&mut self) -> Option<Self::Item> { + let res = if self.k >= self.len { + None + } else { + let cartesian_idx = self.i[self.d]; + let cartesian_max = self.dims[self.d]; + let k = self.k; + + if cartesian_idx == 0 { + Some(LeftBoundary(k, k + self.d_stride)) + } else if cartesian_idx + 1 >= cartesian_max { + Some(RightBoundary(k, k - self.d_stride)) + } else { + Some(Interior(k, (k - self.d_stride, k + self.d_stride))) + } + }; + self.k += 1; + for j in 0..N { + if self.i[j] + 1 < self.dims[j] { + self.i[j] += 1; + break + } + self.i[j] = 0 + } + res + } +} + +impl<F, B, const N : usize> Mapping<DVector<F>> +for Grad<F, B, N> +where + B : Discretisation<F>, + F : Float + nalgebra::RealField, +{ + type Codomain = DVector<F>; + fn apply<I : Instance<DVector<F>>>(&self, i : I) -> DVector<F> { + let mut y = DVector::zeros(N * self.len()); + self.apply_add(&mut y, i); + y + } +} + +#[replace_float_literals(F::cast_from(literal))] +impl<F, B, const N : usize> GEMV<F, DVector<F>> +for Grad<F, B, N> +where + B : Discretisation<F>, + F : Float + nalgebra::RealField, +{ + fn gemv<I : Instance<DVector<F>>>( + &self, y : &mut DVector<F>, α : F, i : I, β : F + ) { + if β == 0.0 { + y.as_mut_slice().iter_mut().for_each(|x| *x = 0.0); + } else if β != 1.0 { + //*y *= β; + y.as_mut_slice().iter_mut().for_each(|x| *x *= β); + } + let h = self.h; + let m = self.len(); + i.eval(|x| { + assert_eq!(x.len(), m); + for d in 0..N { + let mut v = y.generic_view_mut((d*m, 0), (Dyn(m), U1)); + for b in Iter::new(&self.dims, d) { + self.discretisation.add_diff_mut(&mut v, x, α/h, b) + } + } + }) + } +} + +impl<F, B, const N : usize> Mapping<DVector<F>> +for Div<F, B, N> +where + B : Discretisation<F>, + F : Float + nalgebra::RealField, +{ + type Codomain = DVector<F>; + fn apply<I : Instance<DVector<F>>>(&self, i : I) -> DVector<F> { + let mut y = DVector::zeros(self.len()); + self.apply_add(&mut y, i); + y + } +} + +#[replace_float_literals(F::cast_from(literal))] +impl<F, B, const N : usize> GEMV<F, DVector<F>> +for Div<F, B, N> +where + B : Discretisation<F>, + F : Float + nalgebra::RealField, +{ + fn gemv<I : Instance<DVector<F>>>( + &self, y : &mut DVector<F>, α : F, i : I, β : F + ) { + if β == 0.0 { + y.as_mut_slice().iter_mut().for_each(|x| *x = 0.0); + } else if β != 1.0 { + //*y *= β; + y.as_mut_slice().iter_mut().for_each(|x| *x *= β); + } + let h = self.h; + let m = self.len(); + i.eval(|x| { + assert_eq!(x.len(), N * m); + for d in 0..N { + let v = x.generic_view((d*m, 0), (Dyn(m), U1)); + for b in Iter::new(&self.dims, d) { + self.discretisation.add_diff_mut(y, &v, α/h, b) + } + } + }) + } +} + +impl<F, B, const N : usize> Grad<F, B, N> +where + B : Discretisation<F>, + F : Float + nalgebra::RealField +{ + /// Creates a new discrete gradient operator for the vector `u`, verifying dimensions. + pub fn new_for(u : &DVector<F>, h : F, dims : [usize; N], discretisation : B) + -> Option<Self> + { + if u.len() == dims.iter().product::<usize>() { + Some(Grad { dims, h, discretisation } ) + } else { + None + } + } + + fn len(&self) -> usize { + self.dims.iter().product::<usize>() + } +} + + +impl<F, B, const N : usize> Div<F, B, N> +where + B : Discretisation<F>, + F : Float + nalgebra::RealField +{ + /// Creates a new discrete gradient operator for the vector `u`, verifying dimensions. + pub fn new_for(u : &DVector<F>, h : F, dims : [usize; N], discretisation : B) + -> Option<Self> + { + if u.len() == dims.iter().product::<usize>() * N { + Some(Div { dims, h, discretisation } ) + } else { + None + } + } + + fn len(&self) -> usize { + self.dims.iter().product::<usize>() + } +} + +impl<F, B, const N : usize> Linear<DVector<F>> +for Grad<F, B, N> +where + B : Discretisation<F>, + F : Float + nalgebra::RealField, +{ +} + +impl<F, B, const N : usize> Linear<DVector<F>> +for Div<F, B, N> +where + B : Discretisation<F>, + F : Float + nalgebra::RealField, +{ +} + +impl<F, B, const N : usize> BoundedLinear<DVector<F>, L2, L2, F> +for Grad<F, B, N> +where + B : Discretisation<F>, + F : Float + nalgebra::RealField, + DVector<F> : Norm<F, L2>, +{ + fn opnorm_bound(&self, _ : L2, _ : L2) -> F { + // Fuck nalgebra. + self.discretisation.opnorm_bound(num_traits::Float::abs(self.h)) + } +} + + +impl<F, B, const N : usize> BoundedLinear<DVector<F>, L2, L2, F> +for Div<F, B, N> +where + B : Discretisation<F>, + F : Float + nalgebra::RealField, + DVector<F> : Norm<F, L2>, +{ + fn opnorm_bound(&self, _ : L2, _ : L2) -> F { + // Fuck nalgebra. + self.discretisation.opnorm_bound(num_traits::Float::abs(self.h)) + } +} + +impl<F, B, const N : usize> +Adjointable<DVector<F>, DVector<F>> +for Grad<F, B, N> +where + B : Discretisation<F>, + F : Float + nalgebra::RealField, +{ + type AdjointCodomain = DVector<F>; + type Adjoint<'a> = Div<F, B::Opposite, N> where Self : 'a; + + /// Form the adjoint operator of `self`. + fn adjoint(&self) -> Self::Adjoint<'_> { + Div { + dims : self.dims, + h : -self.h, + discretisation : self.discretisation.opposite(), + } + } +} + + +impl<F, B, const N : usize> +Adjointable<DVector<F>, DVector<F>> +for Div<F, B, N> +where + B : Discretisation<F>, + F : Float + nalgebra::RealField, +{ + type AdjointCodomain = DVector<F>; + type Adjoint<'a> = Grad<F, B::Opposite, N> where Self : 'a; + + /// Form the adjoint operator of `self`. + fn adjoint(&self) -> Self::Adjoint<'_> { + Grad { + dims : self.dims, + h : -self.h, + discretisation : self.discretisation.opposite(), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn grad_adjoint() { + let im = DVector::from( (0..9).map(|t| t as f64).collect::<Vec<_>>()); + let v = DVector::from( (0..18).map(|t| t as f64).collect::<Vec<_>>()); + + let grad = Grad::new_for(&im, 1.0, [3, 3], ForwardNeumann).unwrap(); + let a = grad.apply(&im).dot(&v); + let b = grad.adjoint().apply(&v).dot(&im); + assert_eq!(a, b); + + let grad = Grad::new_for(&im, 1.0, [3, 3], ForwardDirichlet).unwrap(); + let a = grad.apply(&im).dot(&v); + let b = grad.adjoint().apply(&v).dot(&im); + assert_eq!(a, b); + + } +}
--- a/src/linops.rs Sun Dec 22 17:42:14 2024 -0500 +++ b/src/linops.rs Mon Dec 30 09:49:08 2024 -0500 @@ -50,11 +50,13 @@ /// Computes `y = αAx + βy`, where `A` is `Self`. fn gemv<I : Instance<X>>(&self, y : &mut Y, α : F, x : I, β : F); + #[inline] /// Computes `y = Ax`, where `A` is `Self` fn apply_mut<I : Instance<X>>(&self, y : &mut Y, x : I){ self.gemv(y, 1.0, x, 0.0) } + #[inline] /// Computes `y += Ax`, where `A` is `Self` fn apply_add<I : Instance<X>>(&self, y : &mut Y, x : I){ self.gemv(y, 1.0, x, 1.0)