diff -r 000000000000 -r eb3c7813b67a src/kernels/base.rs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/kernels/base.rs Thu Dec 01 23:07:35 2022 +0200 @@ -0,0 +1,404 @@ + +//! Things for constructing new kernels from component kernels and traits for analysing them +use serde::Serialize; +use numeric_literals::replace_float_literals; + +use alg_tools::types::*; +use alg_tools::norms::*; +use alg_tools::loc::Loc; +use alg_tools::sets::Cube; +use alg_tools::bisection_tree::{ + Support, + Bounds, + LocalAnalysis, + GlobalAnalysis, + Bounded, +}; +use alg_tools::mapping::Apply; +use alg_tools::maputil::{array_init, map2}; +use alg_tools::sets::SetOrd; + +use crate::fourier::Fourier; + +/// Representation of the product of two kernels. +/// +/// The kernels typically implement [`Support`] and [`Mapping`][alg_tools::mapping::Mapping]. +/// +/// The implementation [`Support`] only uses the [`Support::support_hint`] of the first parameter! +#[derive(Copy,Clone,Serialize,Debug)] +pub struct SupportProductFirst( + /// First kernel + pub A, + /// Second kernel + pub B +); + +impl Apply> +for SupportProductFirst +where A : for<'a> Apply<&'a Loc, Output=F>, + B : for<'a> Apply<&'a Loc, Output=F> { + type Output = F; + #[inline] + fn apply(&self, x : Loc) -> Self::Output { + self.0.apply(&x) * self.1.apply(&x) + } +} + +impl<'a, A, B, F : Float, const N : usize> Apply<&'a Loc> +for SupportProductFirst +where A : Apply<&'a Loc, Output=F>, + B : Apply<&'a Loc, Output=F> { + type Output = F; + #[inline] + fn apply(&self, x : &'a Loc) -> Self::Output { + self.0.apply(x) * self.1.apply(x) + } +} + +impl<'a, A, B, F : Float, const N : usize> Support +for SupportProductFirst +where A : Support, + B : Support { + #[inline] + fn support_hint(&self) -> Cube { + self.0.support_hint() + } + + #[inline] + fn in_support(&self, x : &Loc) -> bool { + self.0.in_support(x) + } + + #[inline] + fn bisection_hint(&self, cube : &Cube) -> [Option; N] { + self.0.bisection_hint(cube) + } +} + +impl<'a, A, B, F : Float> GlobalAnalysis> +for SupportProductFirst +where A : GlobalAnalysis>, + B : GlobalAnalysis> { + #[inline] + fn global_analysis(&self) -> Bounds { + self.0.global_analysis() * self.1.global_analysis() + } +} + +impl<'a, A, B, F : Float, const N : usize> LocalAnalysis, N> +for SupportProductFirst +where A : LocalAnalysis, N>, + B : LocalAnalysis, N> { + #[inline] + fn local_analysis(&self, cube : &Cube) -> Bounds { + self.0.local_analysis(cube) * self.1.local_analysis(cube) + } +} + +/// Representation of the sum of two kernels +/// +/// The kernels typically implement [`Support`] and [`Mapping`][alg_tools::mapping::Mapping]. +/// +/// The implementation [`Support`] only uses the [`Support::support_hint`] of the first parameter! +#[derive(Copy,Clone,Serialize,Debug)] +pub struct SupportSum( + /// First kernel + pub A, + /// Second kernel + pub B +); + +impl<'a, A, B, F : Float, const N : usize> Apply<&'a Loc> +for SupportSum +where A : Apply<&'a Loc, Output=F>, + B : Apply<&'a Loc, Output=F> { + type Output = F; + #[inline] + fn apply(&self, x : &'a Loc) -> Self::Output { + self.0.apply(x) + self.1.apply(x) + } +} + +impl Apply> +for SupportSum +where A : for<'a> Apply<&'a Loc, Output=F>, + B : for<'a> Apply<&'a Loc, Output=F> { + type Output = F; + #[inline] + fn apply(&self, x : Loc) -> Self::Output { + self.0.apply(&x) + self.1.apply(&x) + } +} + +impl<'a, A, B, F : Float, const N : usize> Support +for SupportSum +where A : Support, + B : Support, + Cube : SetOrd { + #[inline] + fn support_hint(&self) -> Cube { + self.0.support_hint().common(&self.1.support_hint()) + } + + #[inline] + fn in_support(&self, x : &Loc) -> bool { + self.0.in_support(x) || self.1.in_support(x) + } + + #[inline] + fn bisection_hint(&self, cube : &Cube) -> [Option; N] { + map2(self.0.bisection_hint(cube), + self.1.bisection_hint(cube), + |a, b| a.or(b)) + } +} + +impl<'a, A, B, F : Float> GlobalAnalysis> +for SupportSum +where A : GlobalAnalysis>, + B : GlobalAnalysis> { + #[inline] + fn global_analysis(&self) -> Bounds { + self.0.global_analysis() + self.1.global_analysis() + } +} + +impl<'a, A, B, F : Float, const N : usize> LocalAnalysis, N> +for SupportSum +where A : LocalAnalysis, N>, + B : LocalAnalysis, N>, + Cube : SetOrd { + #[inline] + fn local_analysis(&self, cube : &Cube) -> Bounds { + self.0.local_analysis(cube) + self.1.local_analysis(cube) + } +} + +/// Representation of the convolution of two kernels. +/// +/// The kernels typically implement [`Support`]s and [`Mapping`][alg_tools::mapping::Mapping]. +// +/// Trait implementations have to be on a case-by-case basis. +#[derive(Copy,Clone,Serialize,Debug,Eq,PartialEq)] +pub struct Convolution( + /// First kernel + pub A, + /// Second kernel + pub B +); + +/// Representation of the autoconvolution of a kernel. +/// +/// The kernel typically implements [`Support`] and [`Mapping`][alg_tools::mapping::Mapping]. +/// +/// Trait implementations have to be on a case-by-case basis. +#[derive(Copy,Clone,Serialize,Debug,Eq,PartialEq)] +pub struct AutoConvolution( + /// The kernel to be autoconvolved + pub A +); + +/// Representation a multi-dimensional product of a one-dimensional kernel. +/// +/// For $G: ℝ → ℝ$, this is the function $F(x\_1, …, x\_n) := \prod_{i=1}^n G(x\_i)$. +/// The kernel $G$ typically implements [`Support`] and [`Mapping`][alg_tools::mapping::Mapping] +/// on [`Loc`]. Then the product implements them on [`Loc`]. +#[derive(Copy,Clone,Serialize,Debug,Eq,PartialEq)] +struct UniformProduct( + /// The one-dimensional kernel + G +); + +impl<'a, G, F : Float, const N : usize> Apply<&'a Loc> +for UniformProduct +where G : Apply, Output=F> { + type Output = F; + #[inline] + fn apply(&self, x : &'a Loc) -> F { + x.iter().map(|&y| self.0.apply(Loc([y]))).product() + } +} + +impl Apply> +for UniformProduct +where G : Apply, Output=F> { + type Output = F; + #[inline] + fn apply(&self, x : Loc) -> F { + x.into_iter().map(|y| self.0.apply(Loc([y]))).product() + } +} + +impl Support +for UniformProduct +where G : Support { + #[inline] + fn support_hint(&self) -> Cube { + let [a] : [[F; 2]; 1] = self.0.support_hint().into(); + array_init(|| a.clone()).into() + } + + #[inline] + fn in_support(&self, x : &Loc) -> bool { + x.iter().all(|&y| self.0.in_support(&Loc([y]))) + } + + #[inline] + fn bisection_hint(&self, cube : &Cube) -> [Option; N] { + cube.map(|a, b| { + let [h] = self.0.bisection_hint(&[[a, b]].into()); + h + }) + } +} + +impl GlobalAnalysis> +for UniformProduct +where G : GlobalAnalysis> { + #[inline] + fn global_analysis(&self) -> Bounds { + let g = self.0.global_analysis(); + (0..N).map(|_| g).product() + } +} + +impl LocalAnalysis, N> +for UniformProduct +where G : LocalAnalysis, 1> { + #[inline] + fn local_analysis(&self, cube : &Cube) -> Bounds { + cube.iter_coords().map( + |&[a, b]| self.0.local_analysis(&([[a, b]].into())) + ).product() + } +} + +macro_rules! product_lpnorm { + ($lp:ident) => { + impl Norm + for UniformProduct + where G : Norm { + #[inline] + fn norm(&self, lp : $lp) -> F { + self.0.norm(lp).powi(N as i32) + } + } + } +} + +product_lpnorm!(L1); +product_lpnorm!(L2); +product_lpnorm!(Linfinity); + + +/// Trait for bounding one kernel with respect to another. +/// +/// The type `F` is the scalar field, and `T` another kernel to which `Self` is compared. +pub trait BoundedBy { + /// Calclate a bounding factor $c$ such that the Fourier transforms $ℱ\[v\] ≤ c ℱ\[u\]$ for + /// $v$ `self` and $u$ `other`. + /// + /// If no such factors exits, `None` is returned. + fn bounding_factor(&self, other : &T) -> Option; +} + +/// This [`BoundedBy`] implementation bounds $(uv) * (uv)$ by $(ψ * ψ) u$. +#[replace_float_literals(F::cast_from(literal))] +impl +BoundedBy, BaseP>> +for AutoConvolution> +where F : Float, + C : Clone + PartialEq, + BaseP : Fourier + PartialOrd, // TODO: replace by BoundedBy, + >::Transformed : Bounded + Norm { + + fn bounding_factor(&self, kernel : &SupportProductFirst, BaseP>) -> Option { + let SupportProductFirst(AutoConvolution(ref cutoff2), base_spread2) = kernel; + let AutoConvolution(SupportProductFirst(ref cutoff, ref base_spread)) = self; + let v̂ = base_spread.fourier(); + + // Verify that the cut-off and ideal physical model (base spread) are the same. + if cutoff == cutoff2 + && base_spread <= base_spread2 + && v̂.bounds().lower() >= 0.0 { + // Calculate the factor between the convolution approximation + // `AutoConvolution>` of $A_*A$ and the + // kernel of the seminorm. This depends on the physical model P being + // `SupportProductFirst` with the kernel `K` being + // a `SupportSum` involving `SupportProductFirst, BaseP>`. + Some(v̂.norm(L1)) + } else { + // We cannot compare + None + } + } +} + +impl BoundedBy> for A +where A : BoundedBy, + C : Bounded { + + #[replace_float_literals(F::cast_from(literal))] + fn bounding_factor(&self, SupportSum(ref kernel1, kernel2) : &SupportSum) -> Option { + if kernel2.bounds().lower() >= 0.0 { + self.bounding_factor(kernel1) + } else { + None + } + } +} + +/// Generates on $[a, b]$ [`Support::support_hint`] for a symmetric interval $[-r, r]$. +/// +/// It will attempt to place the subdivision point at $-r$ or $r$. +/// If neither of these points lies within $[a, b]$, `None` is returned. +#[inline] +pub(super) fn symmetric_interval_hint(r : F, a : F, b : F) -> Option { + if a < -r && -r < b { + Some(-r) + } else if a < r && r < b { + Some(r) + } else { + None + } +} + +/// Generates on $[a, b]$ [`Support::support_hint`] for a function with monotone derivative, +/// support on $[-r, r]$ and peak at $0. +/// +/// It will attempt to place the subdivision point at $-r$, $r$, or $0$, depending on which +/// gives the longer length for the shorter of the two subintervals. If none of these points +/// lies within $[a, b]$, or the resulting interval would be shorter than $0.3r$, `None` is +/// returned. +#[replace_float_literals(F::cast_from(literal))] +#[inline] +pub(super) fn symmetric_peak_hint(r : F, a : F, b : F) -> Option { + let stage1 = if a < -r { + if b <= -r { + None + } else if a + r < -b { + Some(-r) + } else { + Some(0.0) + } + } else if a < 0.0 { + if b <= 0.0 { + None + } else if a < r - b { + Some(0.0) + } else { + Some(r) + } + } else if a < r && b > r { + Some(r) + } else { + None + }; + + // Ignore stage1 hint if either side of subdivision would be just a small fraction of the + // interval + match stage1 { + Some(h) if (h - a).min(b-h) >= 0.3 * r => Some(h), + _ => None + } +}