Fri, 28 Nov 2025 14:01:14 -0500
Add residual to DataTerm
/*! Some convex analysis basics */ use crate::error::DynResult; use crate::euclidean::Euclidean; use crate::instance::{ClosedSpace, DecompositionMut, Instance}; use crate::linops::{IdOp, Scaled, SimpleZeroOp, AXPY}; use crate::mapping::{DifferentiableImpl, LipschitzDifferentiableImpl, Mapping, Space}; use crate::norms::*; use crate::operator_arithmetic::{Constant, Weighted}; use crate::types::*; use serde::{Deserialize, Serialize}; use std::marker::PhantomData; /// Trait for convex mappings. Has no features, just serves as a constraint /// /// TODO: should constrain `Mapping::Codomain` to implement a partial order, /// but this makes everything complicated with little benefit. pub trait ConvexMapping<Domain: Space, F: Num = f64>: Mapping<Domain, Codomain = F> { /// Returns (a lower estimate of) the factor of strong convexity in the norm of `Domain`. fn factor_of_strong_convexity(&self) -> F { F::ZERO } } /// Trait for mappings with a Fenchel conjugate /// /// The conjugate type has to implement [`ConvexMapping`], but a `Conjugable` mapping need /// not be convex. pub trait Conjugable<Domain: HasDual<F>, F: Num = f64>: Mapping<Domain, Codomain = F> { type Conjugate<'a>: ConvexMapping<Domain::DualSpace, F> where Self: 'a; fn conjugate(&self) -> Self::Conjugate<'_>; } /// Trait for mappings with a Fenchel preconjugate /// /// In contrast to [`Conjugable`], the preconjugate need not implement [`ConvexMapping`], /// but a `Preconjugable` mapping has to be convex. pub trait Preconjugable<Domain, Predual, F: Num = f64>: ConvexMapping<Domain, F> where Domain: Space, Predual: HasDual<F>, { type Preconjugate<'a>: Mapping<Predual, Codomain = F> where Self: 'a; fn preconjugate(&self) -> Self::Preconjugate<'_>; } /// Trait for mappings with a proximap map /// /// The conjugate type has to implement [`ConvexMapping`], but a `Conjugable` mapping need /// not be convex. pub trait Prox<Domain: Space>: Mapping<Domain> { type Prox<'a>: Mapping<Domain, Codomain = Domain::Principal> where Self: 'a; /// Returns a proximal mapping with weight τ fn prox_mapping(&self, τ: Self::Codomain) -> Self::Prox<'_>; /// Calculate the proximal mapping with weight τ fn prox<I: Instance<Domain>>(&self, τ: Self::Codomain, z: I) -> Domain::Principal { self.prox_mapping(τ).apply(z) } /// Calculate the proximal mapping with weight τ in-place fn prox_mut<'b>(&self, τ: Self::Codomain, y: &'b mut Domain::Principal) where Domain::Decomp: DecompositionMut<Domain>, for<'a> &'a Domain::Principal: Instance<Domain>, { *y = self.prox(τ, &*y); } } /// Constraint to the unit ball of the norm described by `E`. #[derive(Copy, Clone, Debug, Serialize, Deserialize)] pub struct NormConstraint<F: Float, E: NormExponent> { radius: F, norm: NormMapping<F, E>, } impl<Domain, E, F> ConvexMapping<Domain, F> for NormMapping<F, E> where Domain: Space, E: NormExponent, F: Float, Self: Mapping<Domain, Codomain = F>, { } impl<F, E, Domain> Mapping<Domain> for NormConstraint<F, E> where Domain: Space, Domain::Principal: Norm<E, F>, F: Float, E: NormExponent, { type Codomain = F; fn apply<I: Instance<Domain>>(&self, d: I) -> F { if d.eval(|x| x.norm(self.norm.exponent)) <= self.radius { F::ZERO } else { F::INFINITY } } } impl<Domain, E, F> ConvexMapping<Domain, F> for NormConstraint<F, E> where Domain: Space, E: NormExponent, F: Float, Self: Mapping<Domain, Codomain = F>, { } impl<E, F, Domain> Conjugable<Domain, F> for NormMapping<F, E> where E: HasDualExponent, F: Float, Domain: HasDual<F>, Domain::Principal: Norm<E, F>, <Domain as HasDual<F>>::DualSpace: Norm<E::DualExp, F>, { type Conjugate<'a> = NormConstraint<F, E::DualExp> where Self: 'a; fn conjugate(&self) -> Self::Conjugate<'_> { NormConstraint { radius: F::ONE, norm: self.exponent.dual_exponent().as_mapping() } } } impl<C, E, F, Domain> Conjugable<Domain, F> for Weighted<NormMapping<F, E>, C> where C: Constant<Type = F>, E: HasDualExponent, F: Float, Domain: HasDual<F>, Domain::Principal: Norm<E, F>, <Domain as HasDual<F>>::DualSpace: Norm<E::DualExp, F>, { type Conjugate<'a> = NormConstraint<F, E::DualExp> where Self: 'a; fn conjugate(&self) -> Self::Conjugate<'_> { NormConstraint { radius: self.weight.value(), norm: self.base_fn.exponent.dual_exponent().as_mapping(), } } } impl<Domain, E, F> Prox<Domain> for NormConstraint<F, E> where Domain: Space, Domain::Principal: Norm<E, F>, E: NormExponent, F: Float, NormProjection<F, E>: Mapping<Domain, Codomain = Domain::Principal>, { type Prox<'a> = NormProjection<F, E> where Self: 'a; #[inline] fn prox_mapping(&self, _τ: Self::Codomain) -> Self::Prox<'_> { assert!(self.radius >= F::ZERO); NormProjection { radius: self.radius, exponent: self.norm.exponent } } } /// Projection to the unit ball of the norm described by `E`. #[derive(Copy, Clone, Debug, Serialize, Deserialize)] pub struct NormProjection<F: Float, E: NormExponent> { radius: F, exponent: E, } /* impl<F, Domain> Mapping<Domain> for NormProjection<F, L2> where Domain : Space + Euclidean<F> + std::ops::MulAssign<F>, F : Float, { type Codomain = Domain; fn apply<I : Instance<Domain>>(&self, d : I) -> Domain { d.own().proj_ball2(self.radius) } } */ impl<F, E, Domain> Mapping<Domain> for NormProjection<F, E> where Domain: Space, Domain::Principal: ClosedSpace + Projection<F, E>, F: Float, E: NormExponent, { type Codomain = Domain::Principal; fn apply<I: Instance<Domain>>(&self, d: I) -> Self::Codomain { d.own().proj_ball(self.radius, self.exponent) } } /// The zero mapping #[derive(Copy, Clone, Debug, Serialize, Deserialize)] pub struct Zero<Domain: Space, F: Num>(PhantomData<(Domain, F)>); impl<Domain: Space, F: Num> Zero<Domain, F> { pub fn new() -> Self { Zero(PhantomData) } } impl<Domain: Space, F: Num> Mapping<Domain> for Zero<Domain, F> { type Codomain = F; /// Compute the value of `self` at `x`. fn apply<I: Instance<Domain>>(&self, _x: I) -> Self::Codomain { F::ZERO } } impl<Domain: Space, F: Float> ConvexMapping<Domain, F> for Zero<Domain, F> {} impl<Domain: HasDual<F>, F: Float> Conjugable<Domain, F> for Zero<Domain, F> { type Conjugate<'a> = ZeroIndicator<Domain::DualSpace, F> where Self: 'a; #[inline] fn conjugate(&self) -> Self::Conjugate<'_> { ZeroIndicator::new() } } impl<Domain, Predual, F: Float> Preconjugable<Domain, Predual, F> for Zero<Domain, F> where Domain: Normed<F>, Predual: HasDual<F, PrincipalV = Predual>, { type Preconjugate<'a> = ZeroIndicator<Predual, F> where Self: 'a; #[inline] fn preconjugate(&self) -> Self::Preconjugate<'_> { ZeroIndicator::new() } } impl<Domain: Space, F: Num> Prox<Domain> for Zero<Domain, F> { type Prox<'a> = IdOp<Domain> where Self: 'a; #[inline] fn prox_mapping(&self, _τ: Self::Codomain) -> Self::Prox<'_> { IdOp::new() } } /// The zero indicator #[derive(Copy, Clone, Debug, Serialize, Deserialize)] pub struct ZeroIndicator<Domain: Space, F: Num>(PhantomData<(Domain, F)>); impl<Domain: Space, F: Num> ZeroIndicator<Domain, F> { pub fn new() -> Self { ZeroIndicator(PhantomData) } } impl<Domain, F> Mapping<Domain> for ZeroIndicator<Domain, F> where F: Float, Domain: Space, Domain::Principal: Normed<F>, { type Codomain = F; /// Compute the value of `self` at `x`. fn apply<I: Instance<Domain>>(&self, x: I) -> Self::Codomain { x.eval(|x̃| if x̃.is_zero() { F::ZERO } else { F::INFINITY }) } } impl<Domain, F: Float> ConvexMapping<Domain, F> for ZeroIndicator<Domain, F> where Domain: Space, Domain::Principal: Normed<F>, { fn factor_of_strong_convexity(&self) -> F { F::INFINITY } } impl<Domain, F: Float> Conjugable<Domain, F> for ZeroIndicator<Domain, F> where Domain: HasDual<F>, Domain::PrincipalV: Normed<F>, { type Conjugate<'a> = Zero<Domain::DualSpace, F> where Self: 'a; #[inline] fn conjugate(&self) -> Self::Conjugate<'_> { Zero::new() } } impl<Domain, Predual, F: Float> Preconjugable<Domain, Predual, F> for ZeroIndicator<Domain, F> where Domain: Space, Domain::Principal: Normed<F>, Predual: HasDual<F>, { type Preconjugate<'a> = Zero<Predual, F> where Self: 'a; #[inline] fn preconjugate(&self) -> Self::Preconjugate<'_> { Zero::new() } } impl<Domain, F> Prox<Domain> for ZeroIndicator<Domain, F> where Domain: AXPY<Field = F, PrincipalV = Domain> + Normed<F>, F: Float, { type Prox<'a> = SimpleZeroOp where Self: 'a; /// Returns a proximal mapping with weight τ fn prox_mapping(&self, _τ: F) -> Self::Prox<'_> { return SimpleZeroOp; } } /// The squared Euclidean norm divided by two #[derive(Copy, Clone, Serialize, Deserialize)] pub struct Norm222<F: Float>(PhantomData<F>); impl</*Domain: Euclidean<F>,*/ F: Float> Norm222<F> { pub fn new() -> Self { Norm222(PhantomData) } } impl<X: Euclidean<F>, F: Float> Mapping<X> for Norm222<F> { type Codomain = F; /// Compute the value of `self` at `x`. fn apply<I: Instance<X>>(&self, x: I) -> Self::Codomain { x.eval(|z| z.norm2_squared() / F::TWO) } } impl<X: Euclidean<F>, F: Float> ConvexMapping<X, F> for Norm222<F> { fn factor_of_strong_convexity(&self) -> F { F::ONE } } impl<X: Euclidean<F>, F: Float> Conjugable<X, F> for Norm222<F> { type Conjugate<'a> = Self where Self: 'a; #[inline] fn conjugate(&self) -> Self::Conjugate<'_> { Self::new() } } impl<X: Euclidean<F>, F: Float> Preconjugable<X, X, F> for Norm222<F> { type Preconjugate<'a> = Self where Self: 'a; #[inline] fn preconjugate(&self) -> Self::Preconjugate<'_> { Self::new() } } impl<X, F> Prox<X> for Norm222<F> where F: Float, X: Euclidean<F>, { type Prox<'a> = Scaled<F> where Self: 'a; fn prox_mapping(&self, τ: F) -> Self::Prox<'_> { Scaled(F::ONE / (F::ONE + τ)) } } impl<X, F> DifferentiableImpl<X> for Norm222<F> where F: Float, X: Euclidean<F>, { type Derivative = X::PrincipalV; fn differential_impl<I: Instance<X>>(&self, x: I) -> Self::Derivative { x.own() } } impl<X, F> LipschitzDifferentiableImpl<X, L2> for Norm222<F> where F: Float, X: Euclidean<F>, { type FloatType = F; fn diff_lipschitz_factor(&self, _: L2) -> DynResult<Self::FloatType> { Ok(F::ONE) } }