Fri, 28 Nov 2025 12:48:17 -0500
Initialise repository, separating measure from pointsource_algs
| Cargo.toml | file | annotate | diff | comparison | revisions | |
| README.md | file | annotate | diff | comparison | revisions | |
| src/base.rs | file | annotate | diff | comparison | revisions | |
| src/delta.rs | file | annotate | diff | comparison | revisions | |
| src/discrete.rs | file | annotate | diff | comparison | revisions | |
| src/lib.rs | file | annotate | diff | comparison | revisions | |
| src/merging.rs | file | annotate | diff | comparison | revisions | |
| src/python.rs | file | annotate | diff | comparison | revisions |
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Cargo.toml Fri Nov 28 12:48:17 2025 -0500 @@ -0,0 +1,38 @@ +[package] +name = "measures" +version = "0.1.0" +edition = "2021" +rust-version = "1.85" +authors = ["Tuomo Valkonen <tuomov@iki.fi>"] +description = "Representation of discrete measures" +homepage = "https://tuomov.iki.fi/software/pointsource_algs/" +repository = "https://tuomov.iki.fi/repos/pointsource_algs/" +license-file = "LICENSE" +keywords = [ + "measure", +] +categories = ["mathematics", "science"] + +[dependencies.alg_tools] +version = "~0.4.0-dev" +path = "../alg_tools" +default-features = false +#features = ["nightly"] + +[dependencies] +serde = { version = "1.0", features = ["derive"] } +nalgebra = { version = "~0.33.0", features = ["rand-no-std"] } +numeric_literals = "~0.2.0" +# All of these dependencies are only required with the `pyo3` feature +pyo3 = { version = "~0.27.0", optional = true } +numpy = { version = "~0.27.0", features = ["nalgebra"], optional = true } + +[features] +default = [] +pyo3 = ["dep:pyo3", "dep:numpy", "alg_tools/pyo3"] + +[build-dependencies] +regex = "~1.11.0" + +[profile.release] +debug = true
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/README.md Fri Nov 28 12:48:17 2025 -0500 @@ -0,0 +1,4 @@ +# measures + +This crates implements measures, in particular, sums of Dirac measures. +It was separated from `pointsource_algs`.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/base.rs Fri Nov 28 12:48:17 2025 -0500 @@ -0,0 +1,20 @@ +//! Basic definitions for measures + +use alg_tools::norms::{Norm, NormExponent}; +use alg_tools::types::Num; +use serde::Serialize; + +/// This is used with [`Norm::norm`] to indicate that a Radon norm is to be computed. +#[derive(Copy, Clone, Serialize, Debug)] +pub struct Radon; +impl NormExponent for Radon {} + +/// A trait for (Radon) measures. +/// +/// Currently has no methods, just the requirement that the Radon norm be implemented. +pub trait Measure<F: Num>: Norm<Radon, F> { + type Domain; +} + +/// Decomposition of measures +pub struct MeasureDecomp;
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/delta.rs Fri Nov 28 12:48:17 2025 -0500 @@ -0,0 +1,320 @@ +/*! +This module implementes delta measures, i.e., single spikes $\alpha \delta_x$ for some +location $x$ and mass $\alpha$. +*/ + +use super::base::*; +use alg_tools::instance::{ClosedSpace, Instance, Space}; +use alg_tools::linops::{Linear, Mapping}; +use alg_tools::loc::Loc; +use alg_tools::norms::Norm; +use alg_tools::self_ownable; +use alg_tools::types::*; +use serde::ser::{Serialize, SerializeStruct, Serializer}; +use std::ops::{Div, DivAssign, Mul, MulAssign, Neg}; + +/// Representation of a delta measure. +/// +/// This is a single spike $\alpha \delta\_x$ for some location $x$ in `Domain` and +/// a mass $\alpha$ in `F`. +#[derive(Clone, Copy, Debug)] +pub struct DeltaMeasure<Domain, F: Num> { + // This causes [`csv`] to crash. + //#[serde(flatten)] + /// Location of the spike + pub x: Domain, + /// Mass of the spike + pub α: F, +} + +const COORDINATE_NAMES: &'static [&'static str] = &["x0", "x1", "x2", "x3", "x4", "x5", "x6", "x7"]; + +// Need to manually implement serialisation as [`csv`] writer fails on +// structs with nested arrays as well as with #[serde(flatten)]. +impl<F: Num, const N: usize> Serialize for DeltaMeasure<Loc<N, F>, F> +where + F: Serialize, +{ + fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> + where + S: Serializer, + { + assert!(N <= COORDINATE_NAMES.len()); + + let mut s = serializer.serialize_struct("DeltaMeasure", N + 1)?; + for (i, e) in (0..).zip(self.x.iter()) { + s.serialize_field(COORDINATE_NAMES[i], e)?; + } + s.serialize_field("weight", &self.α)?; + s.end() + } +} + +impl<Domain, F: Float> Measure<F> for DeltaMeasure<Domain, F> { + type Domain = Domain; +} + +impl<Domain, F: Float> Norm<Radon, F> for DeltaMeasure<Domain, F> { + #[inline] + fn norm(&self, _: Radon) -> F { + self.α.abs() + } +} + +// impl<Domain : PartialEq, F : Float> Dist<Radon, F> for DeltaMeasure<Domain, F> { +// #[inline] +// fn dist(&self, other : &Self, _ : Radon) -> F { +// if self.x == other. x { +// (self.α - other.α).abs() +// } else { +// self.α.abs() + other.α.abs() +// } +// } +// } + +impl<Domain, G, F: Num> Mapping<G> for DeltaMeasure<Domain, F> +where + Domain: Space, + G::Codomain: Mul<F, Output = G::Codomain>, + G: Mapping<Domain> + Clone + ClosedSpace, + for<'b> &'b Domain: Instance<Domain>, +{ + type Codomain = G::Codomain; + + #[inline] + fn apply<I: Instance<G>>(&self, g: I) -> Self::Codomain { + g.eval(|g̃| g̃.apply(&self.x) * self.α) + } +} + +impl<Domain, G, F: Num> Linear<G> for DeltaMeasure<Domain, F> +where + Domain: Space, + G::Codomain: Mul<F, Output = G::Codomain>, + G: Mapping<Domain> + Clone + ClosedSpace, + for<'b> &'b Domain: Instance<Domain>, +{ +} + +// /// Partial blanket implementation of [`DeltaMeasure`] as a linear functional of [`Mapping`]s. +// /// A full blanket implementation is not possible due to annoying Rust limitations: only [`Apply`] +// /// on a reference is implemented, but a consuming [`Apply`] has to be implemented on a case-by-case +// /// basis, not because an implementation could not be written, but because the Rust trait system +// /// chokes up. +// impl<Domain, G, F : Num, V> Linear<G> for DeltaMeasure<Domain, F> +// where G: for<'a> Apply<&'a Domain, Output = V>, +// V : Mul<F>, +// Self: Apply<G, Output = <V as Mul<F>>::Output> { +// type Codomain = <V as Mul<F>>::Output; +// } + +// impl<'b, Domain, G, F : Num, V> Apply<&'b G> for DeltaMeasure<Domain, F> +// where G: for<'a> Apply<&'a Domain, Output = V>, +// V : Mul<F> { +// type Output = <V as Mul<F>>::Output; + +// #[inline] +// fn apply(&self, g : &'b G) -> Self::Output { +// g.apply(&self.x) * self.α +// } +// } + +// /// Implementation of the necessary apply for BTFNs +// mod btfn_apply { +// use super::*; +// use alg_tools::bisection_tree::{BTFN, BTImpl, SupportGenerator, LocalAnalysis}; + +// impl<F : Float, BT, G, V, const N : usize> Apply<BTFN<F, G, BT, N>> +// for DeltaMeasure<Loc<N, F>, F> +// where BT : BTImpl< N, F>, +// G : SupportGenerator< N, F, Id=BT::Data>, +// G::SupportType : LocalAnalysis<F, BT::Agg, N> + for<'a> Apply<&'a Loc<N, F>, Output = V>, +// V : std::iter::Sum + Mul<F> { + +// type Output = <V as Mul<F>>::Output; + +// #[inline] +// fn apply(&self, g : BTFN<F, G, BT, N>) -> Self::Output { +// g.apply(&self.x) * self.α +// } +// } +// } + +impl<D, Domain, F: Num> From<(D, F)> for DeltaMeasure<Domain, F> +where + D: Into<Domain>, +{ + #[inline] + fn from((x, α): (D, F)) -> Self { + DeltaMeasure { x: x.into(), α: α } + } +} + +impl<'a, Domain: Clone, F: Num> From<&'a DeltaMeasure<Domain, F>> for DeltaMeasure<Domain, F> { + #[inline] + fn from(d: &'a DeltaMeasure<Domain, F>) -> Self { + d.clone() + } +} + +impl<Domain, F: Num> DeltaMeasure<Domain, F> { + /// Set the mass of the spike. + #[inline] + pub fn set_mass(&mut self, α: F) { + self.α = α + } + + /// Set the location of the spike. + #[inline] + pub fn set_location(&mut self, x: Domain) { + self.x = x + } + + /// Get the mass of the spike. + #[inline] + pub fn get_mass(&self) -> F { + self.α + } + + /// Get a mutable reference to the mass of the spike. + #[inline] + pub fn get_mass_mut(&mut self) -> &mut F { + &mut self.α + } + + /// Get a reference to the location of the spike. + #[inline] + pub fn get_location(&self) -> &Domain { + &self.x + } + + /// Get a mutable reference to the location of the spike. + #[inline] + pub fn get_location_mut(&mut self) -> &mut Domain { + &mut self.x + } +} + +impl<Domain, F: Num> IntoIterator for DeltaMeasure<Domain, F> { + type Item = Self; + type IntoIter = std::iter::Once<Self>; + + #[inline] + fn into_iter(self) -> Self::IntoIter { + std::iter::once(self) + } +} + +impl<'a, Domain, F: Num> IntoIterator for &'a DeltaMeasure<Domain, F> { + type Item = Self; + type IntoIter = std::iter::Once<Self>; + + #[inline] + fn into_iter(self) -> Self::IntoIter { + std::iter::once(self) + } +} + +macro_rules! make_delta_scalarop_rhs { + ($trait:ident, $fn:ident, $trait_assign:ident, $fn_assign:ident) => { + impl<F: Num, Domain> $trait<F> for DeltaMeasure<Domain, F> { + type Output = Self; + fn $fn(mut self, b: F) -> Self { + self.α.$fn_assign(b); + self + } + } + + impl<'a, F: Num, Domain> $trait<&'a F> for DeltaMeasure<Domain, F> { + type Output = Self; + fn $fn(mut self, b: &'a F) -> Self { + self.α.$fn_assign(*b); + self + } + } + + impl<'b, F: Num, Domain: Clone> $trait<F> for &'b DeltaMeasure<Domain, F> { + type Output = DeltaMeasure<Domain, F>; + fn $fn(self, b: F) -> Self::Output { + DeltaMeasure { α: self.α.$fn(b), x: self.x.clone() } + } + } + + impl<'a, 'b, F: Num, Domain: Clone> $trait<&'a F> for &'b DeltaMeasure<Domain, F> { + type Output = DeltaMeasure<Domain, F>; + fn $fn(self, b: &'a F) -> Self::Output { + DeltaMeasure { α: self.α.$fn(*b), x: self.x.clone() } + } + } + + impl<F: Num, Domain> $trait_assign<F> for DeltaMeasure<Domain, F> { + fn $fn_assign(&mut self, b: F) { + self.α.$fn_assign(b) + } + } + + impl<'a, F: Num, Domain> $trait_assign<&'a F> for DeltaMeasure<Domain, F> { + fn $fn_assign(&mut self, b: &'a F) { + self.α.$fn_assign(*b) + } + } + }; +} + +make_delta_scalarop_rhs!(Mul, mul, MulAssign, mul_assign); +make_delta_scalarop_rhs!(Div, div, DivAssign, div_assign); + +macro_rules! make_delta_scalarop_lhs { + ($trait:ident, $fn:ident; $($f:ident)+) => { $( + impl<Domain> $trait<DeltaMeasure<Domain, $f>> for $f { + type Output = DeltaMeasure<Domain, $f>; + fn $fn(self, mut δ : DeltaMeasure<Domain, $f>) -> Self::Output { + δ.α = self.$fn(δ.α); + δ + } + } + + impl<'a, Domain : Clone> $trait<&'a DeltaMeasure<Domain, $f>> for $f { + type Output = DeltaMeasure<Domain, $f>; + fn $fn(self, δ : &'a DeltaMeasure<Domain, $f>) -> Self::Output { + DeltaMeasure{ x : δ.x.clone(), α : self.$fn(δ.α) } + } + } + + impl<'b, Domain> $trait<DeltaMeasure<Domain, $f>> for &'b $f { + type Output = DeltaMeasure<Domain, $f>; + fn $fn(self, mut δ : DeltaMeasure<Domain, $f>) -> Self::Output { + δ.α = self.$fn(δ.α); + δ + } + } + + impl<'a, 'b, Domain : Clone> $trait<&'a DeltaMeasure<Domain, $f>> for &'b $f { + type Output = DeltaMeasure<Domain, $f>; + fn $fn(self, δ : &'a DeltaMeasure<Domain, $f>) -> Self::Output { + DeltaMeasure{ x : δ.x.clone(), α : self.$fn(δ.α) } + } + } + )+ } +} + +make_delta_scalarop_lhs!(Mul, mul; f32 f64 i8 i16 i32 i64 isize u8 u16 u32 u64 usize); +make_delta_scalarop_lhs!(Div, div; f32 f64 i8 i16 i32 i64 isize u8 u16 u32 u64 usize); + +macro_rules! make_delta_unary { + ($trait:ident, $fn:ident, $type:ty) => { + impl<'a, F: Num + Neg<Output = F>, Domain: Clone> Neg for $type { + type Output = DeltaMeasure<Domain, F>; + fn $fn(self) -> Self::Output { + let mut tmp = self.clone(); + tmp.α = tmp.α.$fn(); + tmp + } + } + }; +} + +make_delta_unary!(Neg, neg, DeltaMeasure<Domain, F>); +make_delta_unary!(Neg, neg, &'a DeltaMeasure<Domain, F>); + +self_ownable!(DeltaMeasure<Domain, F> where Domain: Clone, F: Num);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/discrete.rs Fri Nov 28 12:48:17 2025 -0500 @@ -0,0 +1,1143 @@ +//! This module implementes discrete measures. + +use super::base::*; +use super::delta::*; +use alg_tools::collection::Collection; +use alg_tools::instance::{ClosedSpace, Decomposition, EitherDecomp, Instance, MyCow, Space}; +use alg_tools::iter::{MapF, Mappable}; +use alg_tools::linops::{Linear, Mapping}; +use alg_tools::loc::Loc; +use alg_tools::nalgebra_support::ToNalgebraRealField; +use alg_tools::norms::{Norm, Normed}; +use alg_tools::self_ownable; +use alg_tools::tabledump::TableDump; +use alg_tools::types::*; +use nalgebra::DVector; +use serde::ser::{Serialize, SerializeSeq, Serializer}; +use std::iter::Sum; +use std::ops::{ + Add, AddAssign, Div, DivAssign, Index, IndexMut, Mul, MulAssign, Neg, Sub, SubAssign, +}; + +/// Representation of a discrete measure. +/// +/// This is the measure $μ = ∑_{k=1}^n α_k δ_{x_k}$, consisting of several +/// [`DeltaMeasure`], i.e., “spikes” $α_k δ_{x_k}$ with weights $\alpha_k$ in `F` at locations +/// $x_k$ in `Domain`. +#[derive(Clone, Debug)] +pub struct DiscreteMeasure<Domain, F: Num> { + pub(super) spikes: Vec<DeltaMeasure<Domain, F>>, +} + +pub type RNDM<const N: usize, F = f64> = DiscreteMeasure<Loc<N, F>, F>; + +/// Iterator over the [`DeltaMeasure`] spikes of a [`DiscreteMeasure`]. +pub type SpikeIter<'a, Domain, F> = std::slice::Iter<'a, DeltaMeasure<Domain, F>>; + +/// Iterator over mutable [`DeltaMeasure`] spikes of a [`DiscreteMeasure`]. +pub type SpikeIterMut<'a, Domain, F> = std::slice::IterMut<'a, DeltaMeasure<Domain, F>>; + +/// Iterator over the locations of the spikes of a [`DiscreteMeasure`]. +pub type LocationIter<'a, Domain, F> = + std::iter::Map<SpikeIter<'a, Domain, F>, fn(&'a DeltaMeasure<Domain, F>) -> &'a Domain>; + +/// Iterator over the masses of the spikes of a [`DiscreteMeasure`]. +pub type MassIter<'a, Domain, F> = + std::iter::Map<SpikeIter<'a, Domain, F>, fn(&'a DeltaMeasure<Domain, F>) -> F>; + +/// Iterator over the mutable locations of the spikes of a [`DiscreteMeasure`]. +pub type MassIterMut<'a, Domain, F> = std::iter::Map< + SpikeIterMut<'a, Domain, F>, + for<'r> fn(&'r mut DeltaMeasure<Domain, F>) -> &'r mut F, +>; + +impl<Domain, F: Num> DiscreteMeasure<Domain, F> { + /// Create a new zero measure (empty spike set). + pub fn new() -> Self { + DiscreteMeasure { spikes: Vec::new() } + } + + /// Number of [`DeltaMeasure`] spikes in the measure + #[inline] + pub fn len(&self) -> usize { + self.spikes.len() + } + + /// Replace with the zero measure. + #[inline] + pub fn clear(&mut self) { + self.spikes.clear() + } + + /// Remove `i`:th spike, not maintaining order. + /// + /// Panics if indiex is out of bounds. + #[inline] + pub fn swap_remove(&mut self, i: usize) -> DeltaMeasure<Domain, F> { + self.spikes.swap_remove(i) + } + + /// Iterate over (references to) the [`DeltaMeasure`] spikes in this measure + #[inline] + pub fn iter_spikes(&self) -> SpikeIter<'_, Domain, F> { + self.spikes.iter() + } + + /// Iterate over mutable references to the [`DeltaMeasure`] spikes in this measure + #[inline] + pub fn iter_spikes_mut(&mut self) -> SpikeIterMut<'_, Domain, F> { + self.spikes.iter_mut() + } + + /// Iterate over the location of the spikes in this measure + #[inline] + pub fn iter_locations(&self) -> LocationIter<'_, Domain, F> { + self.iter_spikes().map(DeltaMeasure::get_location) + } + + /// Iterate over the masses of the spikes in this measure + #[inline] + pub fn iter_masses(&self) -> MassIter<'_, Domain, F> { + self.iter_spikes().map(DeltaMeasure::get_mass) + } + + /// Iterate over the masses of the spikes in this measure + #[inline] + pub fn iter_masses_mut(&mut self) -> MassIterMut<'_, Domain, F> { + self.iter_spikes_mut().map(DeltaMeasure::get_mass_mut) + } + + /// Update the masses of all the spikes to those produced by an iterator. + #[inline] + pub fn set_masses<I: Iterator<Item = F>>(&mut self, iter: I) { + self.spikes + .iter_mut() + .zip(iter) + .for_each(|(δ, α)| δ.set_mass(α)); + } + + /// Update the locations of all the spikes to those produced by an iterator. + #[inline] + pub fn set_locations<'a, I: Iterator<Item = &'a Domain>>(&mut self, iter: I) + where + Domain: 'static + Clone, + { + self.spikes + .iter_mut() + .zip(iter.cloned()) + .for_each(|(δ, α)| δ.set_location(α)); + } + + // /// Map the masses of all the spikes using a function and an iterator + // #[inline] + // pub fn zipmap_masses< + // I : Iterator<Item=F>, + // G : Fn(F, I::Item) -> F + // > (&mut self, iter : I, g : G) { + // self.spikes.iter_mut().zip(iter).for_each(|(δ, v)| δ.set_mass(g(δ.get_mass(), v))); + // } + + /// Prune all spikes with zero mass. + #[inline] + pub fn prune(&mut self) { + self.prune_by(|δ| δ.α != F::ZERO); + } + + /// Prune spikes by the predicate `g`. + #[inline] + pub fn prune_by<G: FnMut(&DeltaMeasure<Domain, F>) -> bool>(&mut self, g: G) { + self.spikes.retain(g); + } + + /// Add the spikes produced by `iter` to this measure. + #[inline] + pub fn extend<I: Iterator<Item = DeltaMeasure<Domain, F>>>(&mut self, iter: I) { + self.spikes.extend(iter); + } + + /// Add a spike to the measure + #[inline] + pub fn push(&mut self, δ: DeltaMeasure<Domain, F>) { + self.spikes.push(δ); + } + + /// Iterate over triples of masses and locations of two discrete measures, which are assumed + /// to have equal locations of same spike indices. + pub fn both_matching<'a>( + &'a self, + other: &'a DiscreteMeasure<Domain, F>, + ) -> impl Iterator<Item = (F, F, &'a Domain)> { + let m = self.len().max(other.len()); + self.iter_spikes() + .map(Some) + .chain(std::iter::repeat(None)) + .zip(other.iter_spikes().map(Some).chain(std::iter::repeat(None))) + .take(m) + .map(|(oδ, orδ)| { + match (oδ, orδ) { + (Some(δ), Some(rδ)) => (δ.α, rδ.α, &δ.x), // Assumed δ.x=rδ.x + (Some(δ), None) => (δ.α, F::ZERO, &δ.x), + (None, Some(rδ)) => (F::ZERO, rδ.α, &rδ.x), + (None, None) => panic!("This cannot happen!"), + } + }) + } + + /// Subtract `other` from `self`, assuming equal locations of same spike indices + pub fn sub_matching(&self, other: &DiscreteMeasure<Domain, F>) -> DiscreteMeasure<Domain, F> + where + Domain: Clone, + { + self.both_matching(other) + .map(|(α, β, x)| (x.clone(), α - β)) + .collect() + } + + /// Add `other` to `self`, assuming equal locations of same spike indices + pub fn add_matching(&self, other: &DiscreteMeasure<Domain, F>) -> DiscreteMeasure<Domain, F> + where + Domain: Clone, + { + self.both_matching(other) + .map(|(α, β, x)| (x.clone(), α + β)) + .collect() + } + + /// Calculate the Radon-norm distance of `self` to `other`, + /// assuming equal locations of same spike indices. + pub fn dist_matching(&self, other: &DiscreteMeasure<Domain, F>) -> F + where + F: Float, + { + self.both_matching(other) + .map(|(α, β, _)| (α - β).abs()) + .sum() + } +} + +impl<Domain, F: Num> IntoIterator for DiscreteMeasure<Domain, F> { + type Item = DeltaMeasure<Domain, F>; + type IntoIter = std::vec::IntoIter<DeltaMeasure<Domain, F>>; + + #[inline] + fn into_iter(self) -> Self::IntoIter { + self.spikes.into_iter() + } +} + +impl<'a, Domain, F: Num> IntoIterator for &'a DiscreteMeasure<Domain, F> { + type Item = &'a DeltaMeasure<Domain, F>; + type IntoIter = SpikeIter<'a, Domain, F>; + + #[inline] + fn into_iter(self) -> Self::IntoIter { + self.spikes.iter() + } +} + +impl<Domain, F: Num> Sum<DeltaMeasure<Domain, F>> for DiscreteMeasure<Domain, F> { + // Required method + fn sum<I>(iter: I) -> Self + where + I: Iterator<Item = DeltaMeasure<Domain, F>>, + { + Self::from_iter(iter) + } +} + +impl<'a, Domain: Clone, F: Num> Sum<&'a DeltaMeasure<Domain, F>> for DiscreteMeasure<Domain, F> { + // Required method + fn sum<I>(iter: I) -> Self + where + I: Iterator<Item = &'a DeltaMeasure<Domain, F>>, + { + Self::from_iter(iter.cloned()) + } +} + +impl<Domain, F: Num> Sum<DiscreteMeasure<Domain, F>> for DiscreteMeasure<Domain, F> { + // Required method + fn sum<I>(iter: I) -> Self + where + I: Iterator<Item = DiscreteMeasure<Domain, F>>, + { + Self::from_iter(iter.map(|μ| μ.into_iter()).flatten()) + } +} + +impl<'a, Domain: Clone, F: Num> Sum<&'a DiscreteMeasure<Domain, F>> for DiscreteMeasure<Domain, F> { + // Required method + fn sum<I>(iter: I) -> Self + where + I: Iterator<Item = &'a DiscreteMeasure<Domain, F>>, + { + Self::from_iter(iter.map(|μ| μ.iter_spikes()).flatten().cloned()) + } +} + +impl<Domain: Clone, F: Float> DiscreteMeasure<Domain, F> { + /// Computes `μ1 ← θ * μ1 - ζ * μ2`, pruning entries where both `μ1` (`self`) and `μ2` have + // zero weight. `μ2` will contain a pruned copy of pruned original `μ1` without arithmetic + /// performed. **This expects `self` and `μ2` to have matching coordinates in each index**. + // `μ2` can be than `self`, but not longer. + pub fn pruning_sub(&mut self, θ: F, ζ: F, μ2: &mut Self) { + for δ in &self[μ2.len()..] { + μ2.push(DeltaMeasure { x: δ.x.clone(), α: F::ZERO }); + } + debug_assert_eq!(self.len(), μ2.len()); + let mut dest = 0; + for i in 0..self.len() { + let α = self[i].α; + let α_new = θ * α - ζ * μ2[i].α; + if dest < i { + μ2[dest] = DeltaMeasure { x: self[i].x.clone(), α }; + self[dest] = DeltaMeasure { x: self[i].x.clone(), α: α_new }; + } else { + μ2[i].α = α; + self[i].α = α_new; + } + dest += 1; + } + self.spikes.truncate(dest); + μ2.spikes.truncate(dest); + } +} + +impl<Domain, F: Float> DiscreteMeasure<Domain, F> { + /// Prune all spikes with mass absolute value less than the given `tolerance`. + #[inline] + pub fn prune_approx(&mut self, tolerance: F) { + self.spikes.retain(|δ| δ.α.abs() > tolerance); + } +} + +impl<Domain, F: Float + ToNalgebraRealField> DiscreteMeasure<Domain, F> { + /// Extracts the masses of the spikes as a [`DVector`]. + pub fn masses_dvector(&self) -> DVector<F::MixedType> { + DVector::from_iterator( + self.len(), + self.iter_masses().map(|α| α.to_nalgebra_mixed()), + ) + } + + /// Sets the masses of the spikes from the values of a [`DVector`]. + pub fn set_masses_dvector(&mut self, x: &DVector<F::MixedType>) { + self.set_masses(x.iter().map(|&α| F::from_nalgebra_mixed(α))); + } + + // /// Extracts the masses of the spikes as a [`Vec`]. + // pub fn masses_vec(&self) -> Vec<F::MixedType> { + // self.iter_masses() + // .map(|α| α.to_nalgebra_mixed()) + // .collect() + // } + + // /// Sets the masses of the spikes from the values of a [`Vec`]. + // pub fn set_masses_vec(&mut self, x : &Vec<F::MixedType>) { + // self.set_masses(x.iter().map(|&α| F::from_nalgebra_mixed(α))); + // } +} + +// impl<Domain, F :Num> Index<usize> for DiscreteMeasure<Domain, F> { +// type Output = DeltaMeasure<Domain, F>; +// #[inline] +// fn index(&self, i : usize) -> &Self::Output { +// self.spikes.index(i) +// } +// } + +// impl<Domain, F :Num> IndexMut<usize> for DiscreteMeasure<Domain, F> { +// #[inline] +// fn index_mut(&mut self, i : usize) -> &mut Self::Output { +// self.spikes.index_mut(i) +// } +// } + +impl<Domain, F: Num, I: std::slice::SliceIndex<[DeltaMeasure<Domain, F>]>> Index<I> + for DiscreteMeasure<Domain, F> +{ + type Output = <I as std::slice::SliceIndex<[DeltaMeasure<Domain, F>]>>::Output; + #[inline] + fn index(&self, i: I) -> &Self::Output { + self.spikes.index(i) + } +} + +impl<Domain, F: Num, I: std::slice::SliceIndex<[DeltaMeasure<Domain, F>]>> IndexMut<I> + for DiscreteMeasure<Domain, F> +{ + #[inline] + fn index_mut(&mut self, i: I) -> &mut Self::Output { + self.spikes.index_mut(i) + } +} + +impl<Domain, F: Num, D: Into<DeltaMeasure<Domain, F>>, const K: usize> From<[D; K]> + for DiscreteMeasure<Domain, F> +{ + #[inline] + fn from(list: [D; K]) -> Self { + list.into_iter().collect() + } +} + +impl<Domain, F: Num> From<Vec<DeltaMeasure<Domain, F>>> for DiscreteMeasure<Domain, F> { + #[inline] + fn from(spikes: Vec<DeltaMeasure<Domain, F>>) -> Self { + DiscreteMeasure { spikes } + } +} + +impl<'a, Domain, F: Num, D> From<&'a [D]> for DiscreteMeasure<Domain, F> +where + &'a D: Into<DeltaMeasure<Domain, F>>, +{ + #[inline] + fn from(list: &'a [D]) -> Self { + list.into_iter().map(|d| d.into()).collect() + } +} + +impl<Domain, F: Num> From<DeltaMeasure<Domain, F>> for DiscreteMeasure<Domain, F> { + #[inline] + fn from(δ: DeltaMeasure<Domain, F>) -> Self { + DiscreteMeasure { spikes: vec![δ] } + } +} + +impl<'a, Domain: Clone, F: Num> From<&'a DeltaMeasure<Domain, F>> for DiscreteMeasure<Domain, F> { + #[inline] + fn from(δ: &'a DeltaMeasure<Domain, F>) -> Self { + DiscreteMeasure { spikes: vec![δ.clone()] } + } +} + +impl<Domain, F: Num, D: Into<DeltaMeasure<Domain, F>>> FromIterator<D> + for DiscreteMeasure<Domain, F> +{ + #[inline] + fn from_iter<T>(iter: T) -> Self + where + T: IntoIterator<Item = D>, + { + DiscreteMeasure { spikes: iter.into_iter().map(|m| m.into()).collect() } + } +} + +impl<'a, F: Num, const N: usize> TableDump<'a> for DiscreteMeasure<Loc<N, F>, F> +where + DeltaMeasure<Loc<N, F>, F>: Serialize + 'a, +{ + type Iter = std::slice::Iter<'a, DeltaMeasure<Loc<N, F>, F>>; + + // fn tabledump_headers(&'a self) -> Vec<String> { + // let mut v : Vec<String> = (0..N).map(|i| format!("x{}", i)).collect(); + // v.push("weight".into()); + // v + // } + + fn tabledump_entries(&'a self) -> Self::Iter { + // Ensure order matching the headers above + self.spikes.iter() + } +} + +// Need to manually implement serialisation for DeltaMeasure<Loc<N, F>, F> [`csv`] writer fails on +// structs with nested arrays as well as with #[serde(flatten)]. +// Then derive no longer works for DiscreteMeasure +impl<F: Num, const N: usize> Serialize for DiscreteMeasure<Loc<N, F>, F> +where + F: Serialize, +{ + fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> + where + S: Serializer, + { + let mut s = serializer.serialize_seq(Some(self.spikes.len()))?; + for δ in self.spikes.iter() { + s.serialize_element(δ)?; + } + s.end() + } +} + +impl<Domain: PartialEq, F: Float> Measure<F> for DiscreteMeasure<Domain, F> { + type Domain = Domain; +} + +impl<Domain: PartialEq, F: Float> Norm<Radon, F> for DiscreteMeasure<Domain, F> +where + DeltaMeasure<Domain, F>: Norm<Radon, F>, +{ + #[inline] + fn norm(&self, _: Radon) -> F { + self.spikes.iter().map(|m| m.norm(Radon)).sum() + } +} + +impl<Domain, G, F: Num> Mapping<G> for DiscreteMeasure<Domain, F> +where + Domain: Space, + G::Codomain: Sum + Mul<F, Output = G::Codomain>, + G: Mapping<Domain, Codomain = F> + Clone + ClosedSpace, + for<'b> &'b Domain: Instance<Domain>, +{ + type Codomain = G::Codomain; + + #[inline] + fn apply<I: Instance<G>>(&self, g: I) -> Self::Codomain { + g.eval(|g| self.spikes.iter().map(|m| g.apply(&m.x) * m.α).sum()) + } +} + +impl<Domain, G, F: Num> Linear<G> for DiscreteMeasure<Domain, F> +where + Domain: Space, + G::Codomain: Sum + Mul<F, Output = G::Codomain>, + G: Mapping<Domain, Codomain = F> + Clone + ClosedSpace, + for<'b> &'b Domain: Instance<Domain>, +{ +} + +/// Helper trait for constructing arithmetic operations for combinations +/// of [`DiscreteMeasure`] and [`DeltaMeasure`], and their references. +trait Lift<F: Num, Domain> { + type Producer: Iterator<Item = DeltaMeasure<Domain, F>>; + + #[allow(dead_code)] + /// Lifts `self` into a [`DiscreteMeasure`]. + fn lift(self) -> DiscreteMeasure<Domain, F>; + + /// Lifts `self` into a [`DiscreteMeasure`], apply either `f` or `f_mut` whether the type + /// this method is implemented for is a reference or or not. + fn lift_with( + self, + f: impl Fn(&DeltaMeasure<Domain, F>) -> DeltaMeasure<Domain, F>, + f_mut: impl FnMut(&mut DeltaMeasure<Domain, F>), + ) -> DiscreteMeasure<Domain, F>; + + /// Extend `self` into a [`DiscreteMeasure`] with the spikes produced by `iter`. + fn lift_extend<I: Iterator<Item = DeltaMeasure<Domain, F>>>( + self, + iter: I, + ) -> DiscreteMeasure<Domain, F>; + + /// Returns an iterator for producing copies of the spikes of `self`. + fn produce(self) -> Self::Producer; +} + +impl<F: Num, Domain> Lift<F, Domain> for DiscreteMeasure<Domain, F> { + type Producer = std::vec::IntoIter<DeltaMeasure<Domain, F>>; + + #[inline] + fn lift(self) -> DiscreteMeasure<Domain, F> { + self + } + + fn lift_with( + mut self, + _f: impl Fn(&DeltaMeasure<Domain, F>) -> DeltaMeasure<Domain, F>, + f_mut: impl FnMut(&mut DeltaMeasure<Domain, F>), + ) -> DiscreteMeasure<Domain, F> { + self.spikes.iter_mut().for_each(f_mut); + self + } + + #[inline] + fn lift_extend<I: Iterator<Item = DeltaMeasure<Domain, F>>>( + mut self, + iter: I, + ) -> DiscreteMeasure<Domain, F> { + self.spikes.extend(iter); + self + } + + #[inline] + fn produce(self) -> Self::Producer { + self.spikes.into_iter() + } +} + +impl<'a, F: Num, Domain: Clone> Lift<F, Domain> for &'a DiscreteMeasure<Domain, F> { + type Producer = MapF<std::slice::Iter<'a, DeltaMeasure<Domain, F>>, DeltaMeasure<Domain, F>>; + + #[inline] + fn lift(self) -> DiscreteMeasure<Domain, F> { + self.clone() + } + + fn lift_with( + self, + f: impl Fn(&DeltaMeasure<Domain, F>) -> DeltaMeasure<Domain, F>, + _f_mut: impl FnMut(&mut DeltaMeasure<Domain, F>), + ) -> DiscreteMeasure<Domain, F> { + DiscreteMeasure { spikes: self.spikes.iter().map(f).collect() } + } + + #[inline] + fn lift_extend<I: Iterator<Item = DeltaMeasure<Domain, F>>>( + self, + iter: I, + ) -> DiscreteMeasure<Domain, F> { + let mut res = self.clone(); + res.spikes.extend(iter); + res + } + + #[inline] + fn produce(self) -> Self::Producer { + // TODO: maybe not optimal to clone here and would benefit from + // a reference version of lift_extend. + self.spikes.iter().mapF(Clone::clone) + } +} + +impl<F: Num, Domain> Lift<F, Domain> for DeltaMeasure<Domain, F> { + type Producer = std::iter::Once<DeltaMeasure<Domain, F>>; + + #[inline] + fn lift(self) -> DiscreteMeasure<Domain, F> { + DiscreteMeasure { spikes: vec![self] } + } + + #[inline] + fn lift_with( + mut self, + _f: impl Fn(&DeltaMeasure<Domain, F>) -> DeltaMeasure<Domain, F>, + mut f_mut: impl FnMut(&mut DeltaMeasure<Domain, F>), + ) -> DiscreteMeasure<Domain, F> { + f_mut(&mut self); + DiscreteMeasure { spikes: vec![self] } + } + + #[inline] + fn lift_extend<I: Iterator<Item = DeltaMeasure<Domain, F>>>( + self, + iter: I, + ) -> DiscreteMeasure<Domain, F> { + let mut spikes = vec![self]; + spikes.extend(iter); + DiscreteMeasure { spikes: spikes } + } + + #[inline] + fn produce(self) -> Self::Producer { + std::iter::once(self) + } +} + +impl<'a, F: Num, Domain: Clone> Lift<F, Domain> for &'a DeltaMeasure<Domain, F> { + type Producer = std::iter::Once<DeltaMeasure<Domain, F>>; + + #[inline] + fn lift(self) -> DiscreteMeasure<Domain, F> { + DiscreteMeasure { spikes: vec![self.clone()] } + } + + #[inline] + fn lift_with( + self, + f: impl Fn(&DeltaMeasure<Domain, F>) -> DeltaMeasure<Domain, F>, + _f_mut: impl FnMut(&mut DeltaMeasure<Domain, F>), + ) -> DiscreteMeasure<Domain, F> { + DiscreteMeasure { spikes: vec![f(self)] } + } + + #[inline] + fn lift_extend<I: Iterator<Item = DeltaMeasure<Domain, F>>>( + self, + iter: I, + ) -> DiscreteMeasure<Domain, F> { + let mut spikes = vec![self.clone()]; + spikes.extend(iter); + DiscreteMeasure { spikes: spikes } + } + + #[inline] + fn produce(self) -> Self::Producer { + std::iter::once(self.clone()) + } +} + +macro_rules! make_discrete_addsub_assign { + ($rhs:ty) => { + // Discrete += (&)Discrete + impl<'a, F: Num, Domain: Clone> AddAssign<$rhs> for DiscreteMeasure<Domain, F> { + fn add_assign(&mut self, other: $rhs) { + self.spikes.extend(other.produce()); + } + } + + impl<'a, F: Num + Neg<Output = F>, Domain: Clone> SubAssign<$rhs> + for DiscreteMeasure<Domain, F> + { + fn sub_assign(&mut self, other: $rhs) { + self.spikes.extend(other.produce().map(|δ| -δ)); + } + } + }; +} + +make_discrete_addsub_assign!(DiscreteMeasure<Domain, F>); +make_discrete_addsub_assign!(&'a DiscreteMeasure<Domain, F>); +make_discrete_addsub_assign!(DeltaMeasure<Domain, F>); +make_discrete_addsub_assign!(&'a DeltaMeasure<Domain, F>); + +macro_rules! make_discrete_addsub { + ($lhs:ty, $rhs:ty, $alt_order:expr) => { + impl<'a, 'b, F: Num, Domain: Clone> Add<$rhs> for $lhs { + type Output = DiscreteMeasure<Domain, F>; + fn add(self, other: $rhs) -> DiscreteMeasure<Domain, F> { + if !$alt_order { + self.lift_extend(other.produce()) + } else { + other.lift_extend(self.produce()) + } + } + } + + impl<'a, 'b, F: Num + Neg<Output = F>, Domain: Clone> Sub<$rhs> for $lhs { + type Output = DiscreteMeasure<Domain, F>; + fn sub(self, other: $rhs) -> DiscreteMeasure<Domain, F> { + self.lift_extend(other.produce().map(|δ| -δ)) + } + } + }; +} + +make_discrete_addsub!(DiscreteMeasure<Domain, F>, DiscreteMeasure<Domain, F>, false); +make_discrete_addsub!(DiscreteMeasure<Domain, F>, &'b DiscreteMeasure<Domain, F>, false); +make_discrete_addsub!(&'a DiscreteMeasure<Domain, F>, DiscreteMeasure<Domain, F>, true); +make_discrete_addsub!( + &'a DiscreteMeasure<Domain, F>, + &'b DiscreteMeasure<Domain, F>, + false +); +make_discrete_addsub!(DeltaMeasure<Domain, F>, DiscreteMeasure<Domain, F>, false); +make_discrete_addsub!(DeltaMeasure<Domain, F>, &'b DiscreteMeasure<Domain, F>, false); +make_discrete_addsub!(&'a DeltaMeasure<Domain, F>, DiscreteMeasure<Domain, F>, true); +make_discrete_addsub!( + &'a DeltaMeasure<Domain, F>, + &'b DiscreteMeasure<Domain, F>, + false +); +make_discrete_addsub!(DiscreteMeasure<Domain, F>, DeltaMeasure<Domain, F>, false); +make_discrete_addsub!(DiscreteMeasure<Domain, F>, &'b DeltaMeasure<Domain, F>, false); +make_discrete_addsub!(&'a DiscreteMeasure<Domain, F>, DeltaMeasure<Domain, F>, false); +make_discrete_addsub!( + &'a DiscreteMeasure<Domain, F>, + &'b DeltaMeasure<Domain, F>, + false +); +make_discrete_addsub!(DeltaMeasure<Domain, F>, DeltaMeasure<Domain, F>, false); +make_discrete_addsub!(DeltaMeasure<Domain, F>, &'b DeltaMeasure<Domain, F>, false); +make_discrete_addsub!(&'a DeltaMeasure<Domain, F>, DeltaMeasure<Domain, F>, false); +make_discrete_addsub!( + &'a DeltaMeasure<Domain, F>, + &'b DeltaMeasure<Domain, F>, + false +); + +macro_rules! make_discrete_scalarop_rhs { + ($trait:ident, $fn:ident, $trait_assign:ident, $fn_assign:ident) => { + make_discrete_scalarop_rhs!(@assign DiscreteMeasure<Domain, F>, F, $trait_assign, $fn_assign); + make_discrete_scalarop_rhs!(@assign DiscreteMeasure<Domain, F>, &'a F, $trait_assign, $fn_assign); + make_discrete_scalarop_rhs!(@new DiscreteMeasure<Domain, F>, F, $trait, $fn, $fn_assign); + make_discrete_scalarop_rhs!(@new DiscreteMeasure<Domain, F>, &'a F, $trait, $fn, $fn_assign); + make_discrete_scalarop_rhs!(@new &'b DiscreteMeasure<Domain, F>, F, $trait, $fn, $fn_assign); + make_discrete_scalarop_rhs!(@new &'b DiscreteMeasure<Domain, F>, &'a F, $trait, $fn, $fn_assign); + }; + + (@assign $lhs:ty, $rhs:ty, $trait_assign:ident, $fn_assign:ident) => { + impl<'a, 'b, F : Num, Domain> $trait_assign<$rhs> for $lhs { + fn $fn_assign(&mut self, b : $rhs) { + self.spikes.iter_mut().for_each(|δ| δ.$fn_assign(b)); + } + } + }; + (@new $lhs:ty, $rhs:ty, $trait:ident, $fn:ident, $fn_assign:ident) => { + impl<'a, 'b, F : Num, Domain : Clone> $trait<$rhs> for $lhs { + type Output = DiscreteMeasure<Domain, F>; + fn $fn(self, b : $rhs) -> Self::Output { + self.lift_with(|δ| δ.$fn(b), |δ| δ.$fn_assign(b)) + } + } + }; +} + +make_discrete_scalarop_rhs!(Mul, mul, MulAssign, mul_assign); +make_discrete_scalarop_rhs!(Div, div, DivAssign, div_assign); + +macro_rules! make_discrete_unary { + ($trait:ident, $fn:ident, $type:ty) => { + impl<'a, F: Num + Neg<Output = F>, Domain: Clone> Neg for $type { + type Output = DiscreteMeasure<Domain, F>; + fn $fn(self) -> Self::Output { + self.lift_with(|δ| δ.$fn(), |δ| δ.α = δ.α.$fn()) + } + } + }; +} + +make_discrete_unary!(Neg, neg, DiscreteMeasure<Domain, F>); +make_discrete_unary!(Neg, neg, &'a DiscreteMeasure<Domain, F>); + +// impl<F : Num, Domain> Neg for DiscreteMeasure<Domain, F> { +// type Output = Self; +// fn $fn(mut self, b : F) -> Self { +// self.lift().spikes.iter_mut().for_each(|δ| δ.neg(b)); +// self +// } +// } + +macro_rules! make_discrete_scalarop_lhs { + ($trait:ident, $fn:ident; $($f:ident)+) => { $( + impl<Domain> $trait<DiscreteMeasure<Domain, $f>> for $f { + type Output = DiscreteMeasure<Domain, $f>; + fn $fn(self, mut v : DiscreteMeasure<Domain, $f>) -> Self::Output { + v.spikes.iter_mut().for_each(|δ| δ.α = self.$fn(δ.α)); + v + } + } + + impl<'a, Domain : Copy> $trait<&'a DiscreteMeasure<Domain, $f>> for $f { + type Output = DiscreteMeasure<Domain, $f>; + fn $fn(self, v : &'a DiscreteMeasure<Domain, $f>) -> Self::Output { + DiscreteMeasure{ + spikes : v.spikes.iter().map(|δ| self.$fn(δ)).collect() + } + } + } + + impl<'b, Domain> $trait<DiscreteMeasure<Domain, $f>> for &'b $f { + type Output = DiscreteMeasure<Domain, $f>; + fn $fn(self, mut v : DiscreteMeasure<Domain, $f>) -> Self::Output { + v.spikes.iter_mut().for_each(|δ| δ.α = self.$fn(δ.α)); + v + } + } + + impl<'a, 'b, Domain : Copy> $trait<&'a DiscreteMeasure<Domain, $f>> for &'b $f { + type Output = DiscreteMeasure<Domain, $f>; + fn $fn(self, v : &'a DiscreteMeasure<Domain, $f>) -> Self::Output { + DiscreteMeasure{ + spikes : v.spikes.iter().map(|δ| self.$fn(δ)).collect() + } + } + } + )+ } +} + +make_discrete_scalarop_lhs!(Mul, mul; f32 f64 i8 i16 i32 i64 isize u8 u16 u32 u64 usize); +make_discrete_scalarop_lhs!(Div, div; f32 f64 i8 i16 i32 i64 isize u8 u16 u32 u64 usize); + +impl<F: Num, Domain> Collection for DiscreteMeasure<Domain, F> { + type Element = DeltaMeasure<Domain, F>; + type RefsIter<'a> + = std::slice::Iter<'a, Self::Element> + where + Self: 'a; + + #[inline] + fn iter_refs(&self) -> Self::RefsIter<'_> { + self.iter_spikes() + } +} + +impl<Domain: Clone, F: Num> Space for DiscreteMeasure<Domain, F> { + type Principal = Self; + + type Decomp = MeasureDecomp; +} + +pub type SpikeSlice<'b, Domain, F> = &'b [DeltaMeasure<Domain, F>]; + +pub type EitherSlice<'b, Domain, F> = + EitherDecomp<Vec<DeltaMeasure<Domain, F>>, SpikeSlice<'b, Domain, F>>; + +impl<F: Num, Domain: Clone> Decomposition<DiscreteMeasure<Domain, F>> for MeasureDecomp { + type Decomposition<'b> + = EitherSlice<'b, Domain, F> + where + DiscreteMeasure<Domain, F>: 'b; + type Reference<'b> + = SpikeSlice<'b, Domain, F> + where + DiscreteMeasure<Domain, F>: 'b; + + /// Left the lightweight reference type into a full decomposition type. + fn lift<'b>(r: Self::Reference<'b>) -> Self::Decomposition<'b> { + EitherDecomp::Borrowed(r) + } +} + +impl<F: Num, Domain: Clone> Instance<DiscreteMeasure<Domain, F>, MeasureDecomp> + for DiscreteMeasure<Domain, F> +{ + fn eval_ref<'b, R>(&'b self, f: impl FnOnce(SpikeSlice<'b, Domain, F>) -> R) -> R + where + DiscreteMeasure<Domain, F>: 'b, + Self: 'b, + { + f(self.spikes.as_slice()) + } + + fn cow<'b>(self) -> MyCow<'b, DiscreteMeasure<Domain, F>> + where + Self: 'b, + { + MyCow::Owned(self) + } + + fn own(self) -> DiscreteMeasure<Domain, F> { + self + } + + fn decompose<'b>(self) -> EitherSlice<'b, Domain, F> + where + Self: 'b, + { + EitherDecomp::Owned(self.spikes) + } +} + +impl<'a, F: Num, Domain: Clone> Instance<DiscreteMeasure<Domain, F>, MeasureDecomp> + for &'a DiscreteMeasure<Domain, F> +{ + fn eval_ref<'b, R>(&'b self, f: impl FnOnce(SpikeSlice<'b, Domain, F>) -> R) -> R + where + DiscreteMeasure<Domain, F>: 'b, + Self: 'b, + { + f(self.spikes.as_slice()) + } + + fn cow<'b>(self) -> MyCow<'b, DiscreteMeasure<Domain, F>> + where + Self: 'b, + { + MyCow::Borrowed(self) + } + + fn own(self) -> DiscreteMeasure<Domain, F> { + self.clone() + } + + fn decompose<'b>(self) -> EitherSlice<'b, Domain, F> + where + Self: 'b, + { + EitherDecomp::Borrowed(self.spikes.as_slice()) + } +} + +impl<'a, F: Num, Domain: Clone> Instance<DiscreteMeasure<Domain, F>, MeasureDecomp> + for EitherSlice<'a, Domain, F> +{ + fn eval_ref<'b, R>(&'b self, f: impl FnOnce(SpikeSlice<'b, Domain, F>) -> R) -> R + where + DiscreteMeasure<Domain, F>: 'b, + Self: 'b, + { + match self { + EitherDecomp::Owned(v) => f(v.as_slice()), + EitherDecomp::Borrowed(s) => f(s), + } + } + + fn cow<'b>(self) -> MyCow<'b, DiscreteMeasure<Domain, F>> + where + Self: 'b, + { + MyCow::Owned(self.own()) + } + + fn own(self) -> DiscreteMeasure<Domain, F> { + match self { + EitherDecomp::Owned(v) => v.into(), + EitherDecomp::Borrowed(s) => s.into(), + } + } + + fn decompose<'b>(self) -> EitherSlice<'b, Domain, F> + where + Self: 'b, + { + self + } +} + +impl<'a, F: Num, Domain: Clone> Instance<DiscreteMeasure<Domain, F>, MeasureDecomp> + for &'a EitherSlice<'a, Domain, F> +{ + fn eval_ref<'b, R>(&'b self, f: impl FnOnce(SpikeSlice<'b, Domain, F>) -> R) -> R + where + DiscreteMeasure<Domain, F>: 'b, + Self: 'b, + { + match self { + EitherDecomp::Owned(v) => f(v.as_slice()), + EitherDecomp::Borrowed(s) => f(s), + } + } + + fn cow<'b>(self) -> MyCow<'b, DiscreteMeasure<Domain, F>> + where + Self: 'b, + { + MyCow::Owned(self.own()) + } + + fn own(self) -> DiscreteMeasure<Domain, F> { + match self { + EitherDecomp::Owned(v) => v.as_slice(), + EitherDecomp::Borrowed(s) => s, + } + .into() + } + + fn decompose<'b>(self) -> EitherSlice<'b, Domain, F> + where + Self: 'b, + { + match self { + EitherDecomp::Owned(v) => EitherDecomp::Borrowed(v.as_slice()), + EitherDecomp::Borrowed(s) => EitherDecomp::Borrowed(s), + } + } +} + +impl<'a, F: Num, Domain: Clone> Instance<DiscreteMeasure<Domain, F>, MeasureDecomp> + for SpikeSlice<'a, Domain, F> +{ + fn eval_ref<'b, R>(&'b self, f: impl FnOnce(SpikeSlice<'b, Domain, F>) -> R) -> R + where + DiscreteMeasure<Domain, F>: 'b, + Self: 'b, + { + f(self) + } + + fn cow<'b>(self) -> MyCow<'b, DiscreteMeasure<Domain, F>> + where + Self: 'b, + { + MyCow::Owned(self.own()) + } + + fn own(self) -> DiscreteMeasure<Domain, F> { + self.into() + } + + fn decompose<'b>(self) -> EitherSlice<'b, Domain, F> + where + Self: 'b, + { + EitherDecomp::Borrowed(self) + } +} + +impl<'a, F: Num, Domain: Clone> Instance<DiscreteMeasure<Domain, F>, MeasureDecomp> + for &'a SpikeSlice<'a, Domain, F> +{ + fn eval_ref<'b, R>(&'b self, f: impl FnOnce(SpikeSlice<'b, Domain, F>) -> R) -> R + where + DiscreteMeasure<Domain, F>: 'b, + Self: 'b, + { + f(*self) + } + + fn cow<'b>(self) -> MyCow<'b, DiscreteMeasure<Domain, F>> + where + Self: 'b, + { + MyCow::Owned(self.own()) + } + + fn own(self) -> DiscreteMeasure<Domain, F> { + (*self).into() + } + + fn decompose<'b>(self) -> EitherSlice<'b, Domain, F> + where + Self: 'b, + { + EitherDecomp::Borrowed(*self) + } +} + +impl<F: Num, Domain: Clone> Instance<DiscreteMeasure<Domain, F>, MeasureDecomp> + for DeltaMeasure<Domain, F> +{ + fn eval_ref<'b, R>(&'b self, f: impl FnOnce(SpikeSlice<'b, Domain, F>) -> R) -> R + where + DiscreteMeasure<Domain, F>: 'b, + Self: 'b, + { + f(std::slice::from_ref(self)) + } + + fn cow<'b>(self) -> MyCow<'b, DiscreteMeasure<Domain, F>> + where + Self: 'b, + { + MyCow::Owned(self.own()) + } + + fn own(self) -> DiscreteMeasure<Domain, F> { + self.into() + } + + fn decompose<'b>(self) -> EitherSlice<'b, Domain, F> + where + Self: 'b, + { + EitherDecomp::Owned(vec![self]) + } +} + +impl<'a, F: Num, Domain: Clone> Instance<DiscreteMeasure<Domain, F>, MeasureDecomp> + for &'a DeltaMeasure<Domain, F> +{ + fn eval_ref<'b, R>(&'b self, f: impl FnOnce(SpikeSlice<'b, Domain, F>) -> R) -> R + where + DiscreteMeasure<Domain, F>: 'b, + Self: 'b, + { + f(std::slice::from_ref(*self)) + } + + fn cow<'b>(self) -> MyCow<'b, DiscreteMeasure<Domain, F>> + where + Self: 'b, + { + MyCow::Owned(self.own()) + } + + fn own(self) -> DiscreteMeasure<Domain, F> { + self.into() + } + + fn decompose<'b>(self) -> EitherSlice<'b, Domain, F> + where + Self: 'b, + { + EitherDecomp::Borrowed(std::slice::from_ref(self)) + } +} + +impl<F, Domain> Normed<F> for DiscreteMeasure<Domain, F> +where + F: Float, + Domain: Clone + PartialEq, + DeltaMeasure<Domain, F>: Norm<Radon, F>, +{ + type NormExp = Radon; + + fn norm_exponent(&self) -> Radon { + Radon + } +} + +self_ownable!(DiscreteMeasure<Domain, F> where Domain: Clone, F: Num);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib.rs Fri Nov 28 12:48:17 2025 -0500 @@ -0,0 +1,18 @@ +// The main documentation is in the README. +#![doc = include_str!("../README.md")] +// We use unicode. We would like to use much more of it than Rust allows. +// Live with it. Embrace it. +#![allow(uncommon_codepoints)] +#![allow(mixed_script_confusables)] +#![allow(confusable_idents)] + +mod base; +pub use base::*; +mod delta; +pub use delta::*; +mod discrete; +pub use discrete::*; +pub mod merging; + +#[cfg(feature = "pyo3")] +pub mod python;
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/merging.rs Fri Nov 28 12:48:17 2025 -0500 @@ -0,0 +1,316 @@ +/*! +Spike merging heuristics for [`DiscreteMeasure`]s. + +This module primarily provides the [`SpikeMerging`] trait, and within it, +the [`SpikeMerging::merge_spikes`] method. The trait is implemented on +[`DiscreteMeasure<Loc<N, F>, F>`]s in dimensions `N=1` and `N=2`. +*/ + +use numeric_literals::replace_float_literals; +use serde::{Deserialize, Serialize}; +use std::cmp::Ordering; +//use clap::builder::{PossibleValuesParser, PossibleValue}; +use alg_tools::nanleast::NaNLeast; + +use super::delta::*; +use super::discrete::*; +use alg_tools::loc::Loc; +use alg_tools::types::*; + +/// Spike merging heuristic selection +#[derive(Clone, Copy, Eq, PartialEq, Serialize, Deserialize, Debug)] +#[allow(dead_code)] +pub struct SpikeMergingMethod<F> { + // Merging radius + pub radius: F, + // Enabled + pub enabled: bool, + // Interpolate merged points + pub interp: bool, +} + +#[replace_float_literals(F::cast_from(literal))] +impl<F: Float> Default for SpikeMergingMethod<F> { + fn default() -> Self { + SpikeMergingMethod { radius: 0.01, enabled: false, interp: true } + } +} + +/// Trait for dimension-dependent implementation of heuristic peak merging strategies. +pub trait SpikeMerging<F> { + /// Attempt spike merging according to [`SpikeMerging`] method. + /// + /// Returns the last [`Some`] returned by the merging candidate acceptance decision closure + /// `accept` if any merging is performed. The closure should accept as its only parameter a + /// new candidate measure (it will generally be internally mutated `self`, although this is + /// not guaranteed), and return [`None`] if the merge is accepted, and otherwise a [`Some`] of + /// an arbitrary value. This method will return that value for the *last* accepted merge, or + /// [`None`] if no merge was accepted. + /// + /// This method is stable with respect to spike locations: on merge, the weights of existing + /// removed spikes is set to zero, new ones inserted at the end of the spike vector. + /// They merge may also be performed by increasing the weights of the existing spikes, + /// without inserting new spikes. + fn merge_spikes<G>(&mut self, method: SpikeMergingMethod<F>, accept: G) -> usize + where + G: FnMut(&'_ Self) -> bool, + { + if method.enabled { + self.do_merge_spikes_radius(method.radius, method.interp, accept) + } else { + 0 + } + } + + /// Attempt to merge spikes based on a value and a fitness function. + /// + /// Calls [`SpikeMerging::merge_spikes`] with `accept` constructed from the composition of + /// `value` and `fitness`, compared to initial fitness. Returns the last return value of `value` + // for a merge accepted by `fitness`. If no merge was accepted, `value` applied to the initial + /// `self` is returned. also the number of merges is returned; + fn merge_spikes_fitness<G, H, V, O>( + &mut self, + method: SpikeMergingMethod<F>, + value: G, + fitness: H, + ) -> (V, usize) + where + G: Fn(&'_ Self) -> V, + H: Fn(&'_ V) -> O, + O: PartialOrd, + { + let mut res = value(self); + let initial_fitness = fitness(&res); + let count = self.merge_spikes(method, |μ| { + res = value(μ); + fitness(&res) <= initial_fitness + }); + (res, count) + } + + /// Attempt to merge spikes that are within radius $ρ$ of each other (unspecified norm). + /// + /// This method implements [`SpikeMerging::merge_spikes`]. + fn do_merge_spikes_radius<G>(&mut self, ρ: F, interp: bool, accept: G) -> usize + where + G: FnMut(&'_ Self) -> bool; +} + +#[replace_float_literals(F::cast_from(literal))] +impl<F: Float, const N: usize> DiscreteMeasure<Loc<N, F>, F> { + /// Attempts to merge spikes with indices `i` and `j`. + /// + /// This assumes that the weights of the two spikes have already been checked not to be zero. + /// + /// The parameter `res` points to the current “result” for [`SpikeMerging::merge_spikes`]. + /// If the merge is accepted by `accept` returning a [`Some`], `res` will be replaced by its + /// return value. + /// + /// Returns the index of `self.spikes` storing the new spike. + fn attempt_merge<G>( + &mut self, + i: usize, + j: usize, + interp: bool, + accept: &mut G, + ) -> Option<usize> + where + G: FnMut(&'_ Self) -> bool, + { + let &DeltaMeasure { x: xi, α: αi } = &self.spikes[i]; + let &DeltaMeasure { x: xj, α: αj } = &self.spikes[j]; + + if interp { + // Merge inplace + self.spikes[i].α = 0.0; + self.spikes[j].α = 0.0; + let αia = αi.abs(); + let αja = αj.abs(); + self.spikes + .push(DeltaMeasure { α: αi + αj, x: (xi * αia + xj * αja) / (αia + αja) }); + if accept(self) { + Some(self.spikes.len() - 1) + } else { + // Merge not accepted, restore modification + self.spikes[i].α = αi; + self.spikes[j].α = αj; + self.spikes.pop(); + None + } + } else { + // Attempt merge inplace, first combination + self.spikes[i].α = αi + αj; + self.spikes[j].α = 0.0; + if accept(self) { + // Merge accepted + Some(i) + } else { + // Attempt merge inplace, second combination + self.spikes[i].α = 0.0; + self.spikes[j].α = αi + αj; + if accept(self) { + // Merge accepted + Some(j) + } else { + // Merge not accepted, restore modification + self.spikes[i].α = αi; + self.spikes[j].α = αj; + None + } + } + } + } +} + +/// Sorts a vector of indices into `slice` by `compare`. +/// +/// The closure `compare` operators on references to elements of `slice`. +/// Returns the sorted vector of indices into `slice`. +pub fn sort_indices_by<V, F>(slice: &[V], mut compare: F) -> Vec<usize> +where + F: FnMut(&V, &V) -> Ordering, +{ + let mut indices = Vec::from_iter(0..slice.len()); + indices.sort_by(|&i, &j| compare(&slice[i], &slice[j])); + indices +} + +#[replace_float_literals(F::cast_from(literal))] +impl<F: Float> SpikeMerging<F> for DiscreteMeasure<Loc<1, F>, F> { + fn do_merge_spikes_radius<G>(&mut self, ρ: F, interp: bool, mut accept: G) -> usize + where + G: FnMut(&'_ Self) -> bool, + { + // Sort by coordinate into an indexing array. + let mut indices = sort_indices_by(&self.spikes, |&δ1, &δ2| { + let &Loc([x1]) = &δ1.x; + let &Loc([x2]) = &δ2.x; + // nan-ignoring ordering of floats + NaNLeast(x1).cmp(&NaNLeast(x2)) + }); + + // Initialise result + let mut count = 0; + + // Scan consecutive pairs and merge if close enough and accepted by `accept`. + if indices.len() == 0 { + return count; + } + for k in 0..(indices.len() - 1) { + let i = indices[k]; + let j = indices[k + 1]; + let &DeltaMeasure { x: Loc([xi]), α: αi } = &self.spikes[i]; + let &DeltaMeasure { x: Loc([xj]), α: αj } = &self.spikes[j]; + debug_assert!(xi <= xj); + // If close enough, attempt merging + if αi != 0.0 && αj != 0.0 && xj <= xi + ρ { + if let Some(l) = self.attempt_merge(i, j, interp, &mut accept) { + // For this to work (the debug_assert! to not trigger above), the new + // coordinate produced by attempt_merge has to be at most xj. + indices[k + 1] = l; + count += 1 + } + } + } + + count + } +} + +/// Orders `δ1` and `δ1` according to the first coordinate. +fn compare_first_coordinate<F: Float>( + δ1: &DeltaMeasure<Loc<2, F>, F>, + δ2: &DeltaMeasure<Loc<2, F>, F>, +) -> Ordering { + let &Loc([x11, ..]) = &δ1.x; + let &Loc([x21, ..]) = &δ2.x; + // nan-ignoring ordering of floats + NaNLeast(x11).cmp(&NaNLeast(x21)) +} + +#[replace_float_literals(F::cast_from(literal))] +impl<F: Float> SpikeMerging<F> for DiscreteMeasure<Loc<2, F>, F> { + fn do_merge_spikes_radius<G>(&mut self, ρ: F, interp: bool, mut accept: G) -> usize + where + G: FnMut(&'_ Self) -> bool, + { + // Sort by first coordinate into an indexing array. + let mut indices = sort_indices_by(&self.spikes, compare_first_coordinate); + + // Initialise result + let mut count = 0; + let mut start_scan_2nd = 0; + + // Scan in order + if indices.len() == 0 { + return count; + } + for k in 0..indices.len() - 1 { + let i = indices[k]; + let &DeltaMeasure { x: Loc([xi1, xi2]), α: αi } = &self[i]; + + if αi == 0.0 { + // Nothin to be done if the weight is already zero + continue; + } + + let mut closest = None; + + // Scan for second spike. We start from `start_scan_2nd + 1` with `start_scan_2nd` + // the smallest invalid merging index on the previous loop iteration, because a + // the _closest_ mergeable spike might have index less than `k` in `indices`, and a + // merge with it might have not been attempted with this spike if a different closer + // spike was discovered based on the second coordinate. + 'scan_2nd: for l in (start_scan_2nd + 1)..indices.len() { + if l == k { + // Do not attempt to merge a spike with itself + continue; + } + let j = indices[l]; + let &DeltaMeasure { x: Loc([xj1, xj2]), α: αj } = &self[j]; + + if xj1 < xi1 - ρ { + // Spike `j = indices[l]` has too low first coordinate. Update starting index + // for next iteration, and continue scanning. + start_scan_2nd = l; + continue 'scan_2nd; + } else if xj1 > xi1 + ρ { + // Break out: spike `j = indices[l]` has already too high first coordinate, no + // more close enough spikes can be found due to the sorting of `indices`. + break 'scan_2nd; + } + + // If also second coordinate is close enough, attempt merging if closer than + // previously discovered mergeable spikes. + let d2 = (xi2 - xj2).abs(); + if αj != 0.0 && d2 <= ρ { + let r1 = xi1 - xj1; + let d = (d2 * d2 + r1 * r1).sqrt(); + match closest { + None => closest = Some((l, j, d)), + Some((_, _, r)) if r > d => closest = Some((l, j, d)), + _ => {} + } + } + } + + // Attempt merging closest close-enough spike + if let Some((l, j, _)) = closest { + if let Some(n) = self.attempt_merge(i, j, interp, &mut accept) { + // If merge was succesfull, make new spike candidate for merging. + indices[l] = n; + count += 1; + let compare = |i, j| compare_first_coordinate(&self.spikes[i], &self.spikes[j]); + // Re-sort relevant range of indices + if l < k { + indices[l..k].sort_by(|&i, &j| compare(i, j)); + } else { + indices[k + 1..=l].sort_by(|&i, &j| compare(i, j)); + } + } + } + } + + count + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/python.rs Fri Nov 28 12:48:17 2025 -0500 @@ -0,0 +1,142 @@ +/*! +Python wrapper to measures. Only enabled with crate feature `pyo3`. + +These should really be in the `pointsource_pde` crate, but Rust doesn't allow that. +*/ + +use super::{DeltaMeasure, DiscreteMeasure}; +use alg_tools::loc::Loc; +use numpy::{Ix1, PyArray}; +use pyo3::prelude::*; +use pyo3::types::{PyList, PyModule, PyModuleMethods}; +use pyo3::{pymodule, Borrowed, Bound, PyResult}; + +macro_rules! create_interface { + ($name: ident, $iter:ident, $N: literal, $F:ident) => { + #[allow(non_camel_case_types)] + #[pyclass(module = "pointsource_algs")] + /// Wrapper to [`DiscreteMeasure<Loc<$N, $F>, $F>,`] + /// + /// This is mainly needed because pyo3 does not support generics, not even instantiating + /// them to specific values. + pub struct $name { + inner: DiscreteMeasure<Loc<$N, $F>, $F>, + } + + impl $name { + pub fn wrap(inner: DiscreteMeasure<Loc<$N, $F>, $F>) -> Self { + Self { inner } + } + } + + #[pymethods] + impl $name { + #[new] + pub fn new(contents: &Bound<'_, PyList>) -> PyResult<Self> { + // let vec: Vec<(PyArray<$F, Ix1>, $F)> = contents.extract()?; + // Ok(Self { inner: vec.into() }) + let mut res = DiscreteMeasure::new(); + for v in contents.iter() { + let (x, α): ([$F; $N], $F) = v.extract()?; + res.push(DeltaMeasure { x: x.into(), α }); + } + Ok(Self { inner: res }) + } + + /*pub fn from(vec: Vec<([$F; $N], $F)>) -> Self { + Self { + inner: DiscreteMeasure::from_iter(vec.into_iter()), + } + }*/ + + fn __iter__(slf: Bound<'_, Self>) -> PyResult<Py<$iter>> { + Py::new(slf.py(), $iter { measure_ref: slf.unbind(), next: 0 }) + } + } + + #[allow(non_camel_case_types)] + #[pyclass(module = "pointsource_algs")] + /// Python-side iterator for [`DiscreteMeasure<Loc<$N, $F>, $F>,`] + /// Returns tuples (weight, coords) + pub struct $iter { + measure_ref: Py<$name>, + next: usize, + } + + #[pymethods] + impl $iter { + fn __iter__(slf: PyRef<'_, Self>) -> PyRef<'_, Self> { + slf + } + + // We cannot use lifetimes in types exported to Python, so have to use a + // very primitive iterator. + fn __next__( + mut slf: PyRefMut<'_, Self>, + ) -> PyResult<Option<(Bound<'_, PyArray<$F, Ix1>>, $F)>> { + let py = slf.py(); + let meas_: PyRef<'_, $name> = slf.measure_ref.extract(py)?; + let meas = &(meas_.inner); + let next = &mut slf.next; + Ok((*next < meas.len()).then(|| { + let δ = meas[*next]; + *next += 1; + (PyArray::from_slice(py, &δ.x.0), δ.α) + })) + } + } + + // Direct access without passing through the wrappers + impl<'a, 'py> FromPyObject<'a, 'py> for DiscreteMeasure<Loc<$N, $F>, $F> { + type Error = PyErr; + fn extract(obj: Borrowed<'a, 'py, PyAny>) -> PyResult<Self> { + let wrapper: PyRef<'_, $name> = obj.extract()?; + Ok(wrapper.inner.clone()) + } + } + + impl<'a, 'py> IntoPyObject<'py> for &'a mut DiscreteMeasure<Loc<$N, $F>, $F> { + type Target = $name; + type Error = PyErr; + type Output = Bound<'py, $name>; + + fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> { + Bound::new(py, $name { inner: self.clone() }) + } + } + + impl<'py> IntoPyObject<'py> for DiscreteMeasure<Loc<$N, $F>, $F> { + type Target = $name; + type Error = PyErr; + type Output = Bound<'py, $name>; + + fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> { + Bound::new(py, $name { inner: self }) + } + } + }; +} + +create_interface!(DiscreteMeasure_1_f64, DiscreteMeasureIter_1_f64, 1, f64); +create_interface!(DiscreteMeasure_2_f64, DiscreteMeasureIter_2_f64, 2, f64); +create_interface!(DiscreteMeasure_3_f64, DiscreteMeasureIter_3_f64, 3, f64); + +/// Populates the Python module. +/// +/// Needs to be called with [`pyo3::append_to_inittab`]: +/// ``` +/// append_to_inittab!(pymod_pointsource_algs); +/// ``` +/// before initialising the intepreter. +#[pymodule] +#[pyo3(name = "measures")] +pub fn pymod(m: &Bound<'_, PyModule>) -> PyResult<()> { + //m.add_class::<crate::run::DefaultAlgorithm>()?; + m.add_class::<crate::python::DiscreteMeasure_1_f64>()?; + m.add_class::<crate::python::DiscreteMeasure_2_f64>()?; + m.add_class::<crate::python::DiscreteMeasure_3_f64>()?; + m.add_class::<crate::python::DiscreteMeasureIter_1_f64>()?; + m.add_class::<crate::python::DiscreteMeasureIter_2_f64>()?; + m.add_class::<crate::python::DiscreteMeasureIter_3_f64>()?; + Ok(()) +}