diff -r d14c877e14b7 -r b3c35d16affe src/convex.rs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/convex.rs Mon Feb 03 19:22:16 2025 -0500 @@ -0,0 +1,290 @@ +/*! +Some convex analysis basics +*/ + +use std::marker::PhantomData; +use crate::types::*; +use crate::mapping::{Mapping, Space}; +use crate::linops::IdOp; +use crate::instance::{Instance, InstanceMut, DecompositionMut}; +use crate::operator_arithmetic::{Constant, Weighted}; +use crate::norms::*; + +/// 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 : Mapping +{} + +/// 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, F : Num = f64> : Mapping { + type Conjugate<'a> : ConvexMapping 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 : ConvexMapping +where + Domain : Space, + Predual : HasDual +{ + type Preconjugate<'a> : Mapping 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 : Mapping { + type Prox<'a> : Mapping 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>(&self, τ : Self::Codomain, z : I) -> Domain { + self.prox_mapping(τ).apply(z) + } + + /// Calculate the proximal mapping with weight τ in-place + fn prox_mut<'b>(&self, τ : Self::Codomain, y : &'b mut Domain) + where + &'b mut Domain : InstanceMut, + Domain:: Decomp : DecompositionMut, + for<'a> &'a Domain : Instance, + { + *y = self.prox(τ, &*y); + } +} + + +/// Constraint to the unit ball of the norm described by `E`. +pub struct NormConstraint { + radius : F, + norm : NormMapping, +} + +impl ConvexMapping for NormMapping +where + Domain : Space, + E : NormExponent, + F : Float, + Self : Mapping +{} + + +impl Mapping for NormConstraint +where + Domain : Space + Norm, + F : Float, + E : NormExponent, +{ + type Codomain = F; + + fn apply>(&self, d : I) -> F { + if d.eval(|x| x.norm(self.norm.exponent)) <= self.radius { + F::ZERO + } else { + F::INFINITY + } + } +} + +impl ConvexMapping for NormConstraint +where + Domain : Space, + E : NormExponent, + F : Float, + Self : Mapping +{} + + +impl Conjugable for NormMapping +where + E : HasDualExponent, + F : Float, + Domain : HasDual + Norm + Space, + >::DualSpace : Norm +{ + type Conjugate<'a> = NormConstraint where Self : 'a; + + fn conjugate(&self) -> Self::Conjugate<'_> { + NormConstraint { + radius : F::ONE, + norm : self.exponent.dual_exponent().as_mapping() + } + } +} + +impl Conjugable for Weighted, C> +where + C : Constant, + E : HasDualExponent, + F : Float, + Domain : HasDual + Norm + Space, + >::DualSpace : Norm +{ + type Conjugate<'a> = NormConstraint where Self : 'a; + + fn conjugate(&self) -> Self::Conjugate<'_> { + NormConstraint { + radius : self.weight.value(), + norm : self.base_fn.exponent.dual_exponent().as_mapping() + } + } +} + +impl Prox for NormConstraint +where + Domain : Space + Norm, + E : NormExponent, + F : Float, + NormProjection : Mapping, +{ + type Prox<'a> = NormProjection 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`. +pub struct NormProjection { + radius : F, + exponent : E, +} + +/* +impl Mapping for NormProjection +where + Domain : Space + Euclidean + std::ops::MulAssign, + F : Float, +{ + type Codomain = Domain; + + fn apply>(&self, d : I) -> Domain { + d.own().proj_ball2(self.radius) + } +} +*/ + +impl Mapping for NormProjection +where + Domain : Space + Projection, + F : Float, + E : NormExponent, +{ + type Codomain = Domain; + + fn apply>(&self, d : I) -> Domain { + d.own().proj_ball(self.radius, self.exponent) + } +} + + +/// The zero mapping +pub struct Zero(PhantomData<(Domain, F)>); + +impl Zero { + pub fn new() -> Self { + Zero(PhantomData) + } +} + +impl Mapping for Zero { + type Codomain = F; + + /// Compute the value of `self` at `x`. + fn apply>(&self, _x : I) -> Self::Codomain { + F::ZERO + } +} + +impl ConvexMapping for Zero { } + + +impl, F : Float> Conjugable for Zero { + type Conjugate<'a> = ZeroIndicator where Self : 'a; + + #[inline] + fn conjugate(&self) -> Self::Conjugate<'_> { + ZeroIndicator::new() + } +} + +impl Preconjugable for Zero +where + Domain : Space, + Predual : HasDual +{ + type Preconjugate<'a> = ZeroIndicator where Self : 'a; + + #[inline] + fn preconjugate(&self) -> Self::Preconjugate<'_> { + ZeroIndicator::new() + } +} + +impl Prox for Zero { + type Prox<'a> = IdOp where Self : 'a; + + #[inline] + fn prox_mapping(&self, _τ : Self::Codomain) -> Self::Prox<'_> { + IdOp::new() + } +} + + +/// The zero indicator +pub struct ZeroIndicator(PhantomData<(Domain, F)>); + +impl ZeroIndicator { + pub fn new() -> Self { + ZeroIndicator(PhantomData) + } +} + +impl, F : Float> Mapping for ZeroIndicator { + type Codomain = F; + + /// Compute the value of `self` at `x`. + fn apply>(&self, x : I) -> Self::Codomain { + x.eval(|x̃| if x̃.is_zero() { F::ZERO } else { F::INFINITY }) + } +} + +impl, F : Float> ConvexMapping for ZeroIndicator { } + +impl, F : Float> Conjugable for ZeroIndicator { + type Conjugate<'a> = Zero where Self : 'a; + + #[inline] + fn conjugate(&self) -> Self::Conjugate<'_> { + Zero::new() + } +} + +impl Preconjugable for ZeroIndicator +where + Domain : Normed, + Predual : HasDual +{ + type Preconjugate<'a> = Zero where Self : 'a; + + #[inline] + fn preconjugate(&self) -> Self::Preconjugate<'_> { + Zero::new() + } +}