diff -r 9738b51d90d7 -r 4f468d35fa29 src/kernels/hat_convolution.rs --- a/src/kernels/hat_convolution.rs Sun Apr 27 15:03:51 2025 -0500 +++ b/src/kernels/hat_convolution.rs Thu Feb 26 11:38:43 2026 -0500 @@ -1,30 +1,22 @@ //! Implementation of the convolution of two hat functions, //! and its convolution with a [`CubeIndicator`]. + +use alg_tools::bisection_tree::{Constant, Support}; +use alg_tools::bounds::{Bounded, Bounds, GlobalAnalysis, LocalAnalysis}; +use alg_tools::error::DynResult; +use alg_tools::loc::Loc; +use alg_tools::mapping::{DifferentiableImpl, Instance, LipschitzDifferentiableImpl, Mapping}; +use alg_tools::maputil::array_init; +use alg_tools::norms::*; +use alg_tools::sets::Cube; +use alg_tools::types::*; +use anyhow::anyhow; use numeric_literals::replace_float_literals; use serde::Serialize; -use alg_tools::types::*; -use alg_tools::norms::*; -use alg_tools::loc::Loc; -use alg_tools::sets::Cube; -use alg_tools::bisection_tree::{ - Support, - Constant, - Bounds, - LocalAnalysis, - GlobalAnalysis, - Bounded, -}; -use alg_tools::mapping::{ - Mapping, - Instance, - DifferentiableImpl, - Differential, -}; -use alg_tools::maputil::array_init; -use crate::types::Lipschitz; +use super::ball_indicator::CubeIndicator; use super::base::*; -use super::ball_indicator::CubeIndicator; +use crate::types::Lipschitz; /// Hat convolution kernel. /// @@ -69,21 +61,26 @@ // $$ // [∇f(x\_1, …, x\_n)]_j = \frac{4}{σ} (h\*h)'(x\_j/σ) \prod\_{j ≠ i} \frac{4}{σ} (h\*h)(x\_i/σ) // $$ -#[derive(Copy,Clone,Debug,Serialize,Eq)] -pub struct HatConv { +#[derive(Copy, Clone, Debug, Serialize, Eq)] +pub struct HatConv { /// The parameter $σ$ of the kernel. - pub radius : S, + pub radius: S, } -impl PartialEq> for HatConv -where S1 : Constant, - S2 : Constant { - fn eq(&self, other : &HatConv) -> bool { +impl PartialEq> for HatConv +where + S1: Constant, + S2: Constant, +{ + fn eq(&self, other: &HatConv) -> bool { self.radius.value() == other.radius.value() } } -impl<'a, S, const N : usize> HatConv where S : Constant { +impl<'a, S, const N: usize> HatConv +where + S: Constant, +{ /// Returns the $σ$ parameter of the kernel. #[inline] pub fn radius(&self) -> S::Type { @@ -91,25 +88,27 @@ } } -impl<'a, S, const N : usize> Mapping> for HatConv -where S : Constant { +impl<'a, S, const N: usize> Mapping> for HatConv +where + S: Constant, +{ type Codomain = S::Type; #[inline] - fn apply>>(&self, y : I) -> Self::Codomain { + fn apply>>(&self, y: I) -> Self::Codomain { let σ = self.radius(); - y.cow().product_map(|x| { - self.value_1d_σ1(x / σ) / σ - }) + y.decompose().product_map(|x| self.value_1d_σ1(x / σ) / σ) } } #[replace_float_literals(S::Type::cast_from(literal))] -impl Lipschitz for HatConv -where S : Constant { +impl Lipschitz for HatConv +where + S: Constant, +{ type FloatType = S::Type; #[inline] - fn lipschitz_factor(&self, L1 : L1) -> Option { + fn lipschitz_factor(&self, L1: L1) -> DynResult { // For any ψ_i, we have // ∏_{i=1}^N ψ_i(x_i) - ∏_{i=1}^N ψ_i(y_i) // = [ψ_1(x_1)-ψ_1(y_1)] ∏_{i=2}^N ψ_i(x_i) @@ -119,86 +118,91 @@ // |∏_{i=1}^N ψ_i(x_i) - ∏_{i=1}^N ψ_i(y_i)| // ≤ ∑_{j=1}^N |ψ_j(x_j)-ψ_j(y_j)| ∏_{j ≠ i} \max_j |ψ_j| let σ = self.radius(); - let l1d = self.lipschitz_1d_σ1() / (σ*σ); + let l1d = self.lipschitz_1d_σ1() / (σ * σ); let m1d = self.value_1d_σ1(0.0) / σ; - Some(l1d * m1d.powi(N as i32 - 1)) + Ok(l1d * m1d.powi(N as i32 - 1)) } } -impl Lipschitz for HatConv -where S : Constant { +impl Lipschitz for HatConv +where + S: Constant, +{ type FloatType = S::Type; #[inline] - fn lipschitz_factor(&self, L2 : L2) -> Option { - self.lipschitz_factor(L1).map(|l1| l1 * ::cast_from(N).sqrt()) + fn lipschitz_factor(&self, L2: L2) -> DynResult { + self.lipschitz_factor(L1) + .map(|l1| l1 * ::cast_from(N).sqrt()) } } - -impl<'a, S, const N : usize> DifferentiableImpl> for HatConv -where S : Constant { - type Derivative = Loc; +impl<'a, S, const N: usize> DifferentiableImpl> for HatConv +where + S: Constant, +{ + type Derivative = Loc; #[inline] - fn differential_impl>>(&self, y0 : I) -> Self::Derivative { - let y = y0.cow(); + fn differential_impl>>(&self, y0: I) -> Self::Derivative { + let y = y0.decompose(); let σ = self.radius(); let σ2 = σ * σ; - let vs = y.map(|x| { - self.value_1d_σ1(x / σ) / σ - }); - product_differential(&*y, &vs, |x| { - self.diff_1d_σ1(x / σ) / σ2 - }) + let vs = y.map(|x| self.value_1d_σ1(x / σ) / σ); + product_differential(&*y, &vs, |x| self.diff_1d_σ1(x / σ) / σ2) } } - #[replace_float_literals(S::Type::cast_from(literal))] -impl<'a, F : Float, S, const N : usize> Lipschitz -for Differential<'a, Loc, HatConv> -where S : Constant { +impl<'a, F: Float, S, const N: usize> LipschitzDifferentiableImpl, L2> + for HatConv +where + S: Constant, +{ type FloatType = F; #[inline] - fn lipschitz_factor(&self, _l2 : L2) -> Option { - let h = self.base_fn(); - let σ = h.radius(); - Some(product_differential_lipschitz_factor::( - h.value_1d_σ1(0.0) / σ, - h.lipschitz_1d_σ1() / (σ*σ), - h.maxabsdiff_1d_σ1() / (σ*σ), - h.lipschitz_diff_1d_σ1() / (σ*σ), - )) + fn diff_lipschitz_factor(&self, _l2: L2) -> DynResult { + let σ = self.radius(); + product_differential_lipschitz_factor::( + self.value_1d_σ1(0.0) / σ, + self.lipschitz_1d_σ1() / (σ * σ), + self.maxabsdiff_1d_σ1() / (σ * σ), + self.lipschitz_diff_1d_σ1() / (σ * σ), + ) } } - #[replace_float_literals(S::Type::cast_from(literal))] -impl<'a, F : Float, S, const N : usize> HatConv -where S : Constant { +impl<'a, F: Float, S, const N: usize> HatConv +where + S: Constant, +{ /// Computes the value of the kernel for $n=1$ with $σ=1$. #[inline] - fn value_1d_σ1(&self, x : F) -> F { + fn value_1d_σ1(&self, x: F) -> F { let y = x.abs(); if y >= 1.0 { 0.0 } else if y > 0.5 { - - (8.0/3.0) * (y - 1.0).powi(3) - } else /* 0 ≤ y ≤ 0.5 */ { - (4.0/3.0) + 8.0 * y * y * (y - 1.0) + -(8.0 / 3.0) * (y - 1.0).powi(3) + } else + /* 0 ≤ y ≤ 0.5 */ + { + (4.0 / 3.0) + 8.0 * y * y * (y - 1.0) } } /// Computes the differential of the kernel for $n=1$ with $σ=1$. #[inline] - fn diff_1d_σ1(&self, x : F) -> F { + fn diff_1d_σ1(&self, x: F) -> F { let y = x.abs(); if y >= 1.0 { 0.0 } else if y > 0.5 { - - 8.0 * (y - 1.0).powi(2) - } else /* 0 ≤ y ≤ 0.5 */ { + -8.0 * (y - 1.0).powi(2) + } else + /* 0 ≤ y ≤ 0.5 */ + { (24.0 * y - 16.0) * y } } @@ -220,13 +224,15 @@ /// Computes the second differential of the kernel for $n=1$ with $σ=1$. #[inline] #[allow(dead_code)] - fn diff2_1d_σ1(&self, x : F) -> F { + fn diff2_1d_σ1(&self, x: F) -> F { let y = x.abs(); if y >= 1.0 { 0.0 } else if y > 0.5 { - - 16.0 * (y - 1.0) - } else /* 0 ≤ y ≤ 0.5 */ { + -16.0 * (y - 1.0) + } else + /* 0 ≤ y ≤ 0.5 */ + { 48.0 * y - 16.0 } } @@ -239,40 +245,46 @@ } } -impl<'a, S, const N : usize> Support for HatConv -where S : Constant { +impl<'a, S, const N: usize> Support for HatConv +where + S: Constant, +{ #[inline] - fn support_hint(&self) -> Cube { + fn support_hint(&self) -> Cube { let σ = self.radius(); array_init(|| [-σ, σ]).into() } #[inline] - fn in_support(&self, y : &Loc) -> bool { + fn in_support(&self, y: &Loc) -> bool { let σ = self.radius(); y.iter().all(|x| x.abs() <= σ) } #[inline] - fn bisection_hint(&self, cube : &Cube) -> [Option; N] { + fn bisection_hint(&self, cube: &Cube) -> [Option; N] { let σ = self.radius(); cube.map(|c, d| symmetric_peak_hint(σ, c, d)) } } #[replace_float_literals(S::Type::cast_from(literal))] -impl GlobalAnalysis> for HatConv -where S : Constant { +impl GlobalAnalysis> for HatConv +where + S: Constant, +{ #[inline] fn global_analysis(&self) -> Bounds { Bounds(0.0, self.apply(Loc::ORIGIN)) } } -impl LocalAnalysis, N> for HatConv -where S : Constant { +impl LocalAnalysis, N> for HatConv +where + S: Constant, +{ #[inline] - fn local_analysis(&self, cube : &Cube) -> Bounds { + fn local_analysis(&self, cube: &Cube) -> Bounds { // The function is maximised/minimised where the 2-norm is minimised/maximised. let lower = self.apply(cube.maxnorm_point()); let upper = self.apply(cube.minnorm_point()); @@ -281,39 +293,38 @@ } #[replace_float_literals(C::Type::cast_from(literal))] -impl<'a, C : Constant, const N : usize> Norm -for HatConv { +impl<'a, C: Constant, const N: usize> Norm for HatConv { #[inline] - fn norm(&self, _ : L1) -> C::Type { + fn norm(&self, _: L1) -> C::Type { 1.0 } } #[replace_float_literals(C::Type::cast_from(literal))] -impl<'a, C : Constant, const N : usize> Norm -for HatConv { +impl<'a, C: Constant, const N: usize> Norm for HatConv { #[inline] - fn norm(&self, _ : Linfinity) -> C::Type { + fn norm(&self, _: Linfinity) -> C::Type { self.bounds().upper() } } #[replace_float_literals(F::cast_from(literal))] -impl<'a, F : Float, R, C, const N : usize> Mapping> -for Convolution, HatConv> -where R : Constant, - C : Constant { - +impl<'a, F: Float, R, C, const N: usize> Mapping> + for Convolution, HatConv> +where + R: Constant, + C: Constant, +{ type Codomain = F; #[inline] - fn apply>>(&self, y : I) -> F { + fn apply>>(&self, y: I) -> F { let Convolution(ref ind, ref hatconv) = self; let β = ind.r.value(); let σ = hatconv.radius(); // This is just a product of one-dimensional versions - y.cow().product_map(|x| { + y.decompose().product_map(|x| { // With $u_σ(x) = u_1(x/σ)/σ$ the normalised hat convolution // we have // $$ @@ -329,37 +340,32 @@ } #[replace_float_literals(F::cast_from(literal))] -impl<'a, F : Float, R, C, const N : usize> DifferentiableImpl> -for Convolution, HatConv> -where R : Constant, - C : Constant { - - type Derivative = Loc; +impl<'a, F: Float, R, C, const N: usize> DifferentiableImpl> + for Convolution, HatConv> +where + R: Constant, + C: Constant, +{ + type Derivative = Loc; #[inline] - fn differential_impl>>(&self, y0 : I) -> Loc { - let y = y0.cow(); + fn differential_impl>>(&self, y0: I) -> Loc { + let y = y0.decompose(); let Convolution(ref ind, ref hatconv) = self; let β = ind.r.value(); let σ = hatconv.radius(); let σ2 = σ * σ; - let vs = y.map(|x| { - self.value_1d_σ1(x / σ, β / σ) - }); - product_differential(&*y, &vs, |x| { - self.diff_1d_σ1(x / σ, β / σ) / σ2 - }) + let vs = y.map(|x| self.value_1d_σ1(x / σ, β / σ)); + product_differential(&*y, &vs, |x| self.diff_1d_σ1(x / σ, β / σ) / σ2) } } - /// Integrate $f$, whose support is $[c, d]$, on $[a, b]$. /// If $b > d$, add $g()$ to the result. #[inline] #[replace_float_literals(F::cast_from(literal))] -fn i(a : F, b : F, c : F, d : F, f : impl Fn(F) -> F, - g : impl Fn() -> F) -> F { +fn i(a: F, b: F, c: F, d: F, f: impl Fn(F) -> F, g: impl Fn() -> F) -> F { if b < c { 0.0 } else if b <= d { @@ -368,7 +374,9 @@ } else { f(b) - f(a) } - } else /* b > d */ { + } else + /* b > d */ + { g() + if a <= c { f(d) - f(c) } else if a < d { @@ -380,84 +388,130 @@ } #[replace_float_literals(F::cast_from(literal))] -impl Convolution, HatConv> -where R : Constant, - C : Constant { - +impl Convolution, HatConv> +where + R: Constant, + C: Constant, +{ /// Calculates the value of the 1D hat convolution further convolved by a interval indicator. /// As both functions are piecewise polynomials, this is implemented by explicit integral over /// all subintervals of polynomiality of the cube indicator, using easily formed /// antiderivatives. #[inline] - pub fn value_1d_σ1(&self, x : F, β : F) -> F { + pub fn value_1d_σ1(&self, x: F, β: F) -> F { // The integration interval let a = x - β; let b = x + β; #[inline] - fn pow4(x : F) -> F { + fn pow4(x: F) -> F { let y = x * x; y * y } - + // Observe the factor 1/6 at the front from the antiderivatives below. // The factor 4 is from normalisation of the original function. - (4.0/6.0) * i(a, b, -1.0, -0.5, + (4.0 / 6.0) + * i( + a, + b, + -1.0, + -0.5, // (2/3) (y+1)^3 on -1 < y ≤ -1/2 // The antiderivative is (2/12)(y+1)^4 = (1/6)(y+1)^4 - |y| pow4(y+1.0), - || i(a, b, -0.5, 0.0, - // -2 y^3 - 2 y^2 + 1/3 on -1/2 < y ≤ 0 - // The antiderivative is -1/2 y^4 - 2/3 y^3 + 1/3 y - |y| y*(-y*y*(y*3.0 + 4.0) + 2.0), - || i(a, b, 0.0, 0.5, - // 2 y^3 - 2 y^2 + 1/3 on 0 < y < 1/2 - // The antiderivative is 1/2 y^4 - 2/3 y^3 + 1/3 y - |y| y*(y*y*(y*3.0 - 4.0) + 2.0), - || i(a, b, 0.5, 1.0, - // -(2/3) (y-1)^3 on 1/2 < y ≤ 1 - // The antiderivative is -(2/12)(y-1)^4 = -(1/6)(y-1)^4 - |y| -pow4(y-1.0), - || 0.0 + |y| pow4(y + 1.0), + || { + i( + a, + b, + -0.5, + 0.0, + // -2 y^3 - 2 y^2 + 1/3 on -1/2 < y ≤ 0 + // The antiderivative is -1/2 y^4 - 2/3 y^3 + 1/3 y + |y| y * (-y * y * (y * 3.0 + 4.0) + 2.0), + || { + i( + a, + b, + 0.0, + 0.5, + // 2 y^3 - 2 y^2 + 1/3 on 0 < y < 1/2 + // The antiderivative is 1/2 y^4 - 2/3 y^3 + 1/3 y + |y| y * (y * y * (y * 3.0 - 4.0) + 2.0), + || { + i( + a, + b, + 0.5, + 1.0, + // -(2/3) (y-1)^3 on 1/2 < y ≤ 1 + // The antiderivative is -(2/12)(y-1)^4 = -(1/6)(y-1)^4 + |y| -pow4(y - 1.0), + || 0.0, + ) + }, ) + }, ) - ) - ) + }, + ) } /// Calculates the derivative of the 1D hat convolution further convolved by a interval /// indicator. The implementation is similar to [`Self::value_1d_σ1`], using the fact that /// $(θ * ψ)' = θ * ψ'$. #[inline] - pub fn diff_1d_σ1(&self, x : F, β : F) -> F { + pub fn diff_1d_σ1(&self, x: F, β: F) -> F { // The integration interval let a = x - β; let b = x + β; // The factor 4 is from normalisation of the original function. - 4.0 * i(a, b, -1.0, -0.5, - // (2/3) (y+1)^3 on -1 < y ≤ -1/2 - |y| (2.0/3.0) * (y + 1.0).powi(3), - || i(a, b, -0.5, 0.0, + 4.0 * i( + a, + b, + -1.0, + -0.5, + // (2/3) (y+1)^3 on -1 < y ≤ -1/2 + |y| (2.0 / 3.0) * (y + 1.0).powi(3), + || { + i( + a, + b, + -0.5, + 0.0, // -2 y^3 - 2 y^2 + 1/3 on -1/2 < y ≤ 0 - |y| -2.0*(y + 1.0) * y * y + (1.0/3.0), - || i(a, b, 0.0, 0.5, + |y| -2.0 * (y + 1.0) * y * y + (1.0 / 3.0), + || { + i( + a, + b, + 0.0, + 0.5, // 2 y^3 - 2 y^2 + 1/3 on 0 < y < 1/2 - |y| 2.0*(y - 1.0) * y * y + (1.0/3.0), - || i(a, b, 0.5, 1.0, - // -(2/3) (y-1)^3 on 1/2 < y ≤ 1 - |y| -(2.0/3.0) * (y - 1.0).powi(3), - || 0.0 - ) - ) + |y| 2.0 * (y - 1.0) * y * y + (1.0 / 3.0), + || { + i( + a, + b, + 0.5, + 1.0, + // -(2/3) (y-1)^3 on 1/2 < y ≤ 1 + |y| -(2.0 / 3.0) * (y - 1.0).powi(3), + || 0.0, + ) + }, + ) + }, ) + }, ) } } /* impl<'a, F : Float, R, C, const N : usize> Lipschitz -for Differential, Convolution, HatConv>> +for Differential, Convolution, HatConv>> where R : Constant, C : Constant { @@ -471,11 +525,11 @@ } */ -impl -Convolution, HatConv> -where R : Constant, - C : Constant { - +impl Convolution, HatConv> +where + R: Constant, + C: Constant, +{ #[inline] fn get_r(&self) -> F { let Convolution(ref ind, ref hatconv) = self; @@ -483,25 +537,26 @@ } } -impl Support -for Convolution, HatConv> -where R : Constant, - C : Constant { - +impl Support + for Convolution, HatConv> +where + R: Constant, + C: Constant, +{ #[inline] - fn support_hint(&self) -> Cube { + fn support_hint(&self) -> Cube { let r = self.get_r(); array_init(|| [-r, r]).into() } #[inline] - fn in_support(&self, y : &Loc) -> bool { + fn in_support(&self, y: &Loc) -> bool { let r = self.get_r(); y.iter().all(|x| x.abs() <= r) } #[inline] - fn bisection_hint(&self, cube : &Cube) -> [Option; N] { + fn bisection_hint(&self, cube: &Cube) -> [Option; N] { // It is not difficult to verify that [`HatConv`] is C^2. // Therefore, so is [`Convolution, HatConv>`] so that a finer // subdivision for the hint than this is not particularly useful. @@ -510,22 +565,26 @@ } } -impl GlobalAnalysis> -for Convolution, HatConv> -where R : Constant, - C : Constant { +impl GlobalAnalysis> + for Convolution, HatConv> +where + R: Constant, + C: Constant, +{ #[inline] fn global_analysis(&self) -> Bounds { Bounds(F::ZERO, self.apply(Loc::ORIGIN)) } } -impl LocalAnalysis, N> -for Convolution, HatConv> -where R : Constant, - C : Constant { +impl LocalAnalysis, N> + for Convolution, HatConv> +where + R: Constant, + C: Constant, +{ #[inline] - fn local_analysis(&self, cube : &Cube) -> Bounds { + fn local_analysis(&self, cube: &Cube) -> Bounds { // The function is maximised/minimised where the absolute value is minimised/maximised. let lower = self.apply(cube.maxnorm_point()); let upper = self.apply(cube.minnorm_point()); @@ -542,7 +601,6 @@ } } - /// This [`BoundedBy`] implementation bounds $u * u$ by $(ψ * ψ) u$ for $u$ a hat convolution and /// $ψ = χ_{[-a,a]^N}$ for some $a>0$. /// @@ -550,17 +608,18 @@ /// where we take $ψ = χ_{[-a,a]^N}$ and $χ = χ_{[-σ,σ]^N}$ for $σ$ the width of the hat /// convolution. #[replace_float_literals(F::cast_from(literal))] -impl -BoundedBy>, HatConv>> -for AutoConvolution> -where F : Float, - C : Constant, - S : Constant { - +impl + BoundedBy>, HatConv>> + for AutoConvolution> +where + F: Float, + C: Constant, + S: Constant, +{ fn bounding_factor( &self, - kernel : &SupportProductFirst>, HatConv> - ) -> Option { + kernel: &SupportProductFirst>, HatConv>, + ) -> DynResult { // We use the comparison $ℱ[𝒜(ψ v)] ≤ L_1 ℱ[𝒜(ψ)u] ⟺ I_{v̂} v̂ ≤ L_1 û$ with // $ψ = χ_{[-w, w]}$ satisfying $supp v ⊂ [-w, w]$, i.e. $w ≥ σ$. Here $v̂ = ℱ[v]$ and // $I_{v̂} = ∫ v̂ d ξ. For this relationship to be valid, we need $v̂ ≥ 0$, which is guaranteed @@ -574,10 +633,10 @@ // `SupportProductFirst>, HatConv>` // is wide enough, and that the hat convolution has the same radius as ours. if σ <= a && hatconv2 == &self.0 { - Some(bounding_1d.powi(N as i32)) + Ok(bounding_1d.powi(N as i32)) } else { // We cannot compare - None + Err(anyhow!("Incomparable factors")) } } } @@ -586,46 +645,44 @@ /// /// This is based on Example 3.3 in the manuscript. #[replace_float_literals(F::cast_from(literal))] -impl -BoundedBy> -for AutoConvolution> -where F : Float, - C : Constant { - +impl BoundedBy> for AutoConvolution> +where + F: Float, + C: Constant, +{ /// Returns an estimate of the factor $L_1$. /// /// Returns `None` if `kernel` does not have the same width as hat convolution that `self` /// is based on. - fn bounding_factor( - &self, - kernel : &HatConv - ) -> Option { + fn bounding_factor(&self, kernel: &HatConv) -> DynResult { if kernel == &self.0 { - Some(1.0) + Ok(1.0) } else { // We cannot compare - None + Err(anyhow!("Incomparable kernels")) } } } #[cfg(test)] mod tests { + use super::HatConv; + use crate::kernels::{BallIndicator, Convolution, CubeIndicator}; use alg_tools::lingrid::linspace; + use alg_tools::loc::Loc; use alg_tools::mapping::Mapping; use alg_tools::norms::Linfinity; - use alg_tools::loc::Loc; - use crate::kernels::{BallIndicator, CubeIndicator, Convolution}; - use super::HatConv; /// Tests numerically that [`HatConv`] is monotone. #[test] fn hatconv_monotonicity() { let grid = linspace(0.0, 1.0, 100000); - let hatconv : HatConv = HatConv{ radius : 1.0 }; + let hatconv: HatConv = HatConv { radius: 1.0 }; let mut vals = grid.into_iter().map(|t| hatconv.apply(Loc::from(t))); let first = vals.next().unwrap(); - let monotone = vals.fold((first, true), |(prev, ok), t| (prev, ok && prev >= t)).1; + let monotone = vals + .fold((first, true), |(prev, ok), t| (prev, ok && prev >= t)) + .1; assert!(monotone); } @@ -633,21 +690,27 @@ #[test] fn convolution_cubeind_hatconv_monotonicity() { let grid = linspace(-2.0, 0.0, 100000); - let hatconv : Convolution, HatConv> - = Convolution(BallIndicator { r : 0.5, exponent : Linfinity }, - HatConv{ radius : 1.0 } ); + let hatconv: Convolution, HatConv> = + Convolution(BallIndicator { r: 0.5, exponent: Linfinity }, HatConv { + radius: 1.0, + }); let mut vals = grid.into_iter().map(|t| hatconv.apply(Loc::from(t))); let first = vals.next().unwrap(); - let monotone = vals.fold((first, true), |(prev, ok), t| (prev, ok && prev <= t)).1; + let monotone = vals + .fold((first, true), |(prev, ok), t| (prev, ok && prev <= t)) + .1; assert!(monotone); let grid = linspace(0.0, 2.0, 100000); - let hatconv : Convolution, HatConv> - = Convolution(BallIndicator { r : 0.5, exponent : Linfinity }, - HatConv{ radius : 1.0 } ); + let hatconv: Convolution, HatConv> = + Convolution(BallIndicator { r: 0.5, exponent: Linfinity }, HatConv { + radius: 1.0, + }); let mut vals = grid.into_iter().map(|t| hatconv.apply(Loc::from(t))); let first = vals.next().unwrap(); - let monotone = vals.fold((first, true), |(prev, ok), t| (prev, ok && prev >= t)).1; + let monotone = vals + .fold((first, true), |(prev, ok), t| (prev, ok && prev >= t)) + .1; assert!(monotone); } }