--- a/src/seminorms.rs Sun Apr 27 15:03:51 2025 -0500 +++ b/src/seminorms.rs Thu Feb 26 11:38:43 2026 -0500 @@ -6,13 +6,15 @@ use crate::measures::{DeltaMeasure, DiscreteMeasure, Radon, SpikeIter, RNDM}; use alg_tools::bisection_tree::*; +use alg_tools::bounds::Bounded; +use alg_tools::error::DynResult; use alg_tools::instance::Instance; use alg_tools::iter::{FilterMapX, Mappable}; use alg_tools::linops::{BoundedLinear, Linear, Mapping}; use alg_tools::loc::Loc; use alg_tools::mapping::RealMapping; use alg_tools::nalgebra_support::ToNalgebraRealField; -use alg_tools::norms::Linfinity; +use alg_tools::norms::{Linfinity, Norm, NormExponent}; use alg_tools::sets::Cube; use alg_tools::types::*; use itertools::Itertools; @@ -68,37 +70,37 @@ // /// A trait alias for simple convolution kernels. -pub trait SimpleConvolutionKernel<F: Float, const N: usize>: - RealMapping<F, N> + Support<F, N> + Bounded<F> + Clone + 'static +pub trait SimpleConvolutionKernel<const N: usize, F: Float = f64>: + RealMapping<N, F> + Support<N, F> + Bounded<F> + Clone + 'static { } -impl<T, F: Float, const N: usize> SimpleConvolutionKernel<F, N> for T where - T: RealMapping<F, N> + Support<F, N> + Bounded<F> + Clone + 'static +impl<T, F: Float, const N: usize> SimpleConvolutionKernel<N, F> for T where + T: RealMapping<N, F> + Support<N, F> + Bounded<F> + Clone + 'static { } /// [`SupportGenerator`] for [`ConvolutionOp`]. #[derive(Clone, Debug)] -pub struct ConvolutionSupportGenerator<F: Float, K, const N: usize> +pub struct ConvolutionSupportGenerator<K, const N: usize, F: Float = f64> where - K: SimpleConvolutionKernel<F, N>, + K: SimpleConvolutionKernel<N, F>, { kernel: K, - centres: RNDM<F, N>, + centres: RNDM<N, F>, } -impl<F: Float, K, const N: usize> ConvolutionSupportGenerator<F, K, N> +impl<F: Float, K, const N: usize> ConvolutionSupportGenerator<K, N, F> where - K: SimpleConvolutionKernel<F, N>, + K: SimpleConvolutionKernel<N, F>, { /// Construct the convolution kernel corresponding to `δ`, i.e., one centered at `δ.x` and /// weighted by `δ.α`. #[inline] fn construct_kernel<'a>( &'a self, - δ: &'a DeltaMeasure<Loc<F, N>, F>, - ) -> Weighted<Shift<K, F, N>, F> { + δ: &'a DeltaMeasure<Loc<N, F>, F>, + ) -> Weighted<Shift<K, N, F>, F> { self.kernel.clone().shift(δ.x).weigh(δ.α) } @@ -108,21 +110,21 @@ #[inline] fn construct_kernel_and_id_filtered<'a>( &'a self, - (id, δ): (usize, &'a DeltaMeasure<Loc<F, N>, F>), - ) -> Option<(usize, Weighted<Shift<K, F, N>, F>)> { + (id, δ): (usize, &'a DeltaMeasure<Loc<N, F>, F>), + ) -> Option<(usize, Weighted<Shift<K, N, F>, F>)> { (δ.α != F::ZERO).then(|| (id.into(), self.construct_kernel(δ))) } } -impl<F: Float, K, const N: usize> SupportGenerator<F, N> for ConvolutionSupportGenerator<F, K, N> +impl<F: Float, K, const N: usize> SupportGenerator<N, F> for ConvolutionSupportGenerator<K, N, F> where - K: SimpleConvolutionKernel<F, N>, + K: SimpleConvolutionKernel<N, F>, { type Id = usize; - type SupportType = Weighted<Shift<K, F, N>, F>; + type SupportType = Weighted<Shift<K, N, F>, F>; type AllDataIter<'a> = FilterMapX< 'a, - Zip<RangeFrom<usize>, SpikeIter<'a, Loc<F, N>, F>>, + Zip<RangeFrom<usize>, SpikeIter<'a, Loc<N, F>, F>>, Self, (Self::Id, Self::SupportType), >; @@ -150,13 +152,13 @@ pub struct ConvolutionOp<F, K, BT, const N: usize> where F: Float + ToNalgebraRealField, - BT: BTImpl<F, N, Data = usize>, - K: SimpleConvolutionKernel<F, N>, + BT: BTImpl<N, F, Data = usize>, + K: SimpleConvolutionKernel<N, F>, { /// Depth of the [`BT`] bisection tree for the outputs [`Mapping::apply`]. depth: BT::Depth, /// Domain of the [`BT`] bisection tree for the outputs [`Mapping::apply`]. - domain: Cube<F, N>, + domain: Cube<N, F>, /// The convolution kernel kernel: K, _phantoms: PhantomData<(F, BT)>, @@ -165,13 +167,13 @@ impl<F, K, BT, const N: usize> ConvolutionOp<F, K, BT, N> where F: Float + ToNalgebraRealField, - BT: BTImpl<F, N, Data = usize>, - K: SimpleConvolutionKernel<F, N>, + BT: BTImpl<N, F, Data = usize>, + K: SimpleConvolutionKernel<N, F>, { /// Creates a new convolution operator $𝒟$ with `kernel` on `domain`. /// /// The output of [`Mapping::apply`] is a [`BT`] of given `depth`. - pub fn new(depth: BT::Depth, domain: Cube<F, N>, kernel: K) -> Self { + pub fn new(depth: BT::Depth, domain: Cube<N, F>, kernel: K) -> Self { ConvolutionOp { depth: depth, domain: domain, @@ -181,7 +183,7 @@ } /// Returns the support generator for this convolution operator. - fn support_generator(&self, μ: RNDM<F, N>) -> ConvolutionSupportGenerator<F, K, N> { + fn support_generator(&self, μ: RNDM<N, F>) -> ConvolutionSupportGenerator<K, N, F> { // TODO: can we avoid cloning μ? ConvolutionSupportGenerator { kernel: self.kernel.clone(), @@ -195,18 +197,18 @@ } } -impl<F, K, BT, const N: usize> Mapping<RNDM<F, N>> for ConvolutionOp<F, K, BT, N> +impl<F, K, BT, const N: usize> Mapping<RNDM<N, F>> for ConvolutionOp<F, K, BT, N> where F: Float + ToNalgebraRealField, - BT: BTImpl<F, N, Data = usize>, - K: SimpleConvolutionKernel<F, N>, - Weighted<Shift<K, F, N>, F>: LocalAnalysis<F, BT::Agg, N>, + BT: BTImpl<N, F, Data = usize>, + K: SimpleConvolutionKernel<N, F>, + Weighted<Shift<K, N, F>, F>: LocalAnalysis<F, BT::Agg, N>, { - type Codomain = BTFN<F, ConvolutionSupportGenerator<F, K, N>, BT, N>; + type Codomain = BTFN<F, ConvolutionSupportGenerator<K, N, F>, BT, N>; fn apply<I>(&self, μ: I) -> Self::Codomain where - I: Instance<RNDM<F, N>>, + I: Instance<RNDM<N, F>>, { let g = self.support_generator(μ.own()); BTFN::construct(self.domain.clone(), self.depth, g) @@ -214,46 +216,67 @@ } /// [`ConvolutionOp`]s as linear operators over [`DiscreteMeasure`]s. -impl<F, K, BT, const N: usize> Linear<RNDM<F, N>> for ConvolutionOp<F, K, BT, N> +impl<F, K, BT, const N: usize> Linear<RNDM<N, F>> for ConvolutionOp<F, K, BT, N> where F: Float + ToNalgebraRealField, - BT: BTImpl<F, N, Data = usize>, - K: SimpleConvolutionKernel<F, N>, - Weighted<Shift<K, F, N>, F>: LocalAnalysis<F, BT::Agg, N>, + BT: BTImpl<N, F, Data = usize>, + K: SimpleConvolutionKernel<N, F>, + Weighted<Shift<K, N, F>, F>: LocalAnalysis<F, BT::Agg, N>, { } -impl<F, K, BT, const N: usize> BoundedLinear<RNDM<F, N>, Radon, Linfinity, F> +impl<F, K, BT, const N: usize> BoundedLinear<RNDM<N, F>, Radon, Linfinity, F> for ConvolutionOp<F, K, BT, N> where F: Float + ToNalgebraRealField, - BT: BTImpl<F, N, Data = usize>, - K: SimpleConvolutionKernel<F, N>, - Weighted<Shift<K, F, N>, F>: LocalAnalysis<F, BT::Agg, N>, + BT: BTImpl<N, F, Data = usize>, + K: SimpleConvolutionKernel<N, F>, + Weighted<Shift<K, N, F>, F>: LocalAnalysis<F, BT::Agg, N>, { - fn opnorm_bound(&self, _: Radon, _: Linfinity) -> F { + fn opnorm_bound(&self, _: Radon, _: Linfinity) -> DynResult<F> { // With μ = ∑_i α_i δ_{x_i}, we have // |𝒟μ|_∞ // = sup_z |∑_i α_i φ(z - x_i)| // ≤ sup_z ∑_i |α_i| |φ(z - x_i)| // ≤ ∑_i |α_i| |φ|_∞ // = |μ|_ℳ |φ|_∞ - self.kernel.bounds().uniform() + Ok(self.kernel.bounds().uniform()) } } -impl<F, K, BT, const N: usize> DiscreteMeasureOp<Loc<F, N>, F> for ConvolutionOp<F, K, BT, N> +impl<'a, F, K, BT, const N: usize> NormExponent for &'a ConvolutionOp<F, K, BT, N> +where + F: Float + ToNalgebraRealField, + BT: BTImpl<N, F, Data = usize>, + K: SimpleConvolutionKernel<N, F>, + Weighted<Shift<K, N, F>, F>: LocalAnalysis<F, BT::Agg, N>, +{ +} + +impl<'a, F, K, BT, const N: usize> Norm<&'a ConvolutionOp<F, K, BT, N>, F> for RNDM<N, F> where F: Float + ToNalgebraRealField, - BT: BTImpl<F, N, Data = usize>, - K: SimpleConvolutionKernel<F, N>, - Weighted<Shift<K, F, N>, F>: LocalAnalysis<F, BT::Agg, N>, + BT: BTImpl<N, F, Data = usize>, + K: SimpleConvolutionKernel<N, F>, + Weighted<Shift<K, N, F>, F>: LocalAnalysis<F, BT::Agg, N>, { - type PreCodomain = PreBTFN<F, ConvolutionSupportGenerator<F, K, N>, N>; + fn norm(&self, op𝒟: &'a ConvolutionOp<F, K, BT, N>) -> F { + self.apply(op𝒟.apply(self)).sqrt() + } +} + +impl<F, K, BT, const N: usize> DiscreteMeasureOp<Loc<N, F>, F> for ConvolutionOp<F, K, BT, N> +where + F: Float + ToNalgebraRealField, + BT: BTImpl<N, F, Data = usize>, + K: SimpleConvolutionKernel<N, F>, + Weighted<Shift<K, N, F>, F>: LocalAnalysis<F, BT::Agg, N>, +{ + type PreCodomain = PreBTFN<F, ConvolutionSupportGenerator<K, N, F>, N>; fn findim_matrix<'a, I>(&self, points: I) -> DMatrix<F::MixedType> where - I: ExactSizeIterator<Item = &'a Loc<F, N>> + Clone, + I: ExactSizeIterator<Item = &'a Loc<N, F>> + Clone, { // TODO: Preliminary implementation. It be best to use sparse matrices or // possibly explicit operators without matrices @@ -268,7 +291,7 @@ /// A version of [`Mapping::apply`] that does not instantiate the [`BTFN`] codomain with /// a bisection tree, instead returning a [`PreBTFN`]. This can improve performance when /// the output is to be added as the right-hand-side operand to a proper BTFN. - fn preapply(&self, μ: RNDM<F, N>) -> Self::PreCodomain { + fn preapply(&self, μ: RNDM<N, F>) -> Self::PreCodomain { BTFN::new_pre(self.support_generator(μ)) } } @@ -277,27 +300,27 @@ /// for [`ConvolutionSupportGenerator`]. macro_rules! make_convolutionsupportgenerator_scalarop_rhs { ($trait:ident, $fn:ident, $trait_assign:ident, $fn_assign:ident) => { - impl<F: Float, K: SimpleConvolutionKernel<F, N>, const N: usize> std::ops::$trait_assign<F> - for ConvolutionSupportGenerator<F, K, N> + impl<F: Float, K: SimpleConvolutionKernel<N, F>, const N: usize> std::ops::$trait_assign<F> + for ConvolutionSupportGenerator<K, N, F> { fn $fn_assign(&mut self, t: F) { self.centres.$fn_assign(t); } } - impl<F: Float, K: SimpleConvolutionKernel<F, N>, const N: usize> std::ops::$trait<F> - for ConvolutionSupportGenerator<F, K, N> + impl<F: Float, K: SimpleConvolutionKernel<N, F>, const N: usize> std::ops::$trait<F> + for ConvolutionSupportGenerator<K, N, F> { - type Output = ConvolutionSupportGenerator<F, K, N>; + type Output = ConvolutionSupportGenerator<K, N, F>; fn $fn(mut self, t: F) -> Self::Output { std::ops::$trait_assign::$fn_assign(&mut self.centres, t); self } } - impl<'a, F: Float, K: SimpleConvolutionKernel<F, N>, const N: usize> std::ops::$trait<F> - for &'a ConvolutionSupportGenerator<F, K, N> + impl<'a, F: Float, K: SimpleConvolutionKernel<N, F>, const N: usize> std::ops::$trait<F> + for &'a ConvolutionSupportGenerator<K, N, F> { - type Output = ConvolutionSupportGenerator<F, K, N>; + type Output = ConvolutionSupportGenerator<K, N, F>; fn $fn(self, t: F) -> Self::Output { ConvolutionSupportGenerator { kernel: self.kernel.clone(), @@ -314,20 +337,20 @@ /// Generates an unary operation (e.g. [`std::ops::Neg`]) for [`ConvolutionSupportGenerator`]. macro_rules! make_convolutionsupportgenerator_unaryop { ($trait:ident, $fn:ident) => { - impl<F: Float, K: SimpleConvolutionKernel<F, N>, const N: usize> std::ops::$trait - for ConvolutionSupportGenerator<F, K, N> + impl<F: Float, K: SimpleConvolutionKernel<N, F>, const N: usize> std::ops::$trait + for ConvolutionSupportGenerator<K, N, F> { - type Output = ConvolutionSupportGenerator<F, K, N>; + type Output = ConvolutionSupportGenerator<K, N, F>; fn $fn(mut self) -> Self::Output { self.centres = self.centres.$fn(); self } } - impl<'a, F: Float, K: SimpleConvolutionKernel<F, N>, const N: usize> std::ops::$trait - for &'a ConvolutionSupportGenerator<F, K, N> + impl<'a, F: Float, K: SimpleConvolutionKernel<N, F>, const N: usize> std::ops::$trait + for &'a ConvolutionSupportGenerator<K, N, F> { - type Output = ConvolutionSupportGenerator<F, K, N>; + type Output = ConvolutionSupportGenerator<K, N, F>; fn $fn(self) -> Self::Output { ConvolutionSupportGenerator { kernel: self.kernel.clone(),