src/forward_model/bias.rs

Mon, 23 Feb 2026 18:18:02 -0500

author
Tuomo Valkonen <tuomov@iki.fi>
date
Mon, 23 Feb 2026 18:18:02 -0500
branch
dev
changeset 64
d3be4f7ffd60
parent 61
4f468d35fa29
permissions
-rw-r--r--

ATTEMPT, HAS BUGS: Make shifted_nonneg_soft_thresholding more readable

/*!
Simple parametric forward model.
 */

use super::{BasicCurvatureBoundEstimates, BoundedCurvature, BoundedCurvatureGuess, ForwardModel};
use crate::dataterm::QuadraticDataTerm;
use crate::measures::{Radon, RNDM};
use crate::prox_penalty::{RadonSquared, StepLengthBoundPair};
use crate::seminorms::DiscreteMeasureOp;
use alg_tools::direct_product::Pair;
use alg_tools::error::{DynError, DynResult};
use alg_tools::euclidean::ClosedEuclidean;
use alg_tools::linops::{BoundedLinear, IdOp, RowOp, AXPY};
use alg_tools::loc::Loc;
use alg_tools::mapping::{Mapping, Space};
use alg_tools::nalgebra_support::ToNalgebraRealField;
use alg_tools::norms::{Norm, NormExponent, PairNorm, L2};
use alg_tools::types::{ClosedAdd, Float};
use numeric_literals::replace_float_literals;

impl<Domain, F, A, E> ForwardModel<Pair<Domain, A::Observable>, F, PairNorm<E, L2, L2>>
    for RowOp<A, IdOp<A::Observable>>
where
    E: NormExponent,
    Domain: Space + Norm<E, F>,
    F: Float,
    A::Observable: ClosedAdd + Norm<L2, F> + AXPY<Field = F> + 'static,
    A: ForwardModel<Domain, F, E> + 'static,
{
    type Observable = A::Observable;

    fn write_observable(&self, b: &Self::Observable, prefix: String) -> DynError {
        self.0.write_observable(b, prefix)
    }

    /// Returns a zero observable
    fn zero_observable(&self) -> Self::Observable {
        self.0.zero_observable()
    }
}

#[replace_float_literals(F::cast_from(literal))]
impl<'a, F, A, 𝒟, Z, const N: usize>
    StepLengthBoundPair<F, QuadraticDataTerm<F, Pair<RNDM<N, F>, Z>, RowOp<A, IdOp<Z>>>>
    for Pair<&'a 𝒟, &'a IdOp<Z>>
where
    RNDM<N, F>: Space + for<'b> Norm<&'b 𝒟, F>,
    F: Float + ToNalgebraRealField,
    𝒟: DiscreteMeasureOp<Loc<N, F>, F>,
    Z: Clone + ClosedEuclidean<F>,
    A: for<'b> BoundedLinear<RNDM<N, F>, &'b 𝒟, L2, F, Codomain = Z>,
    for<'b> &'b 𝒟: NormExponent,
{
    fn step_length_bound_pair(
        &self,
        f: &QuadraticDataTerm<F, Pair<RNDM<N, F>, Z>, RowOp<A, IdOp<Z>>>,
    ) -> DynResult<(F, F)> {
        let l_0 = f.operator().0.opnorm_bound(self.0, L2)?.powi(2);
        // [A_*; B_*][A, B] = [A_*A, A_* B; B_* A, B_* B] ≤ diag(2A_*A, 2B_*B)
        // ≤ diag(2l_A𝒟_A, 2l_B𝒟_B), where now 𝒟_B=Id and l_B=1.
        Ok((2.0 * l_0, 2.0))
    }
}

#[replace_float_literals(F::cast_from(literal))]
impl<'a, F, A, Z, const N: usize>
    StepLengthBoundPair<F, QuadraticDataTerm<F, Pair<RNDM<N, F>, Z>, RowOp<A, IdOp<Z>>>>
    for Pair<&'a RadonSquared, &'a IdOp<Z>>
where
    RNDM<N, F>: Space + Norm<Radon, F>,
    F: Float + ToNalgebraRealField,
    Z: Clone + ClosedEuclidean<F>,
    A: BoundedLinear<RNDM<N, F>, Radon, L2, F, Codomain = Z>,
{
    fn step_length_bound_pair(
        &self,
        f: &QuadraticDataTerm<F, Pair<RNDM<N, F>, Z>, RowOp<A, IdOp<Z>>>,
    ) -> DynResult<(F, F)> {
        let l_0 = f.operator().0.opnorm_bound(Radon, L2)?.powi(2);
        // [A_*; B_*][A, B] = [A_*A, A_* B; B_* A, B_* B] ≤ diag(2A_*A, 2B_*B)
        // ≤ diag(2l_A𝒟_A, 2l_B𝒟_B), where now 𝒟_B=Id and l_B=1.
        Ok((2.0 * l_0, 2.0))
    }
}

/*
/// This `impl` is bit of an abuse as the codomain of `Apre` is a [`Pair`] of a measure predual,
/// to which this `impl` applies, and another space.
impl<F, Apre, Z> LipschitzValues
for ColOp<Apre, IdOp<Z>>
where
    F : Float,
    Z : Clone + Space + ClosedAdd,
    Apre : LipschitzValues<FloatType = F>,
{
    type FloatType = F;
    /// Return (if one exists) a factor $L$ such that $A_*z$ is $L$-Lipschitz for all
    /// $z$ in the unit ball.
    fn value_unit_lipschitz_factor(&self) -> Option<Self::FloatType> {
        self.0.value_unit_lipschitz_factor()
    }

    /// Return (if one exists) a factor $L$ such that $∇A_*z$ is $L$-Lipschitz for all
    /// $z$ in the unit ball.
    fn value_diff_unit_lipschitz_factor(&self) -> Option<Self::FloatType> {
        self.0.value_diff_unit_lipschitz_factor()
    }
}
*/

use BoundedCurvatureGuess::*;

/// Curvature error control: helper bounds for (4.2d), (5.2a), (5.2b), (5.15a), and (5.16a).
///
/// Based on Lemma 5.11 and Example 5.12, the helper bound for (5.15a) and (5.16a) is (3.8).
/// Due to Example 6.1, defining $v^k$ as the projection $F'$ to the predual space of the
/// measures, returns, if possible, and subject to the guess being correct, factors $ℓ_F$ and
/// $Θ²$ such that $B_{P_ℳ^* F'(μ, z)} dγ ≤ ℓ_F c_2$ and
/// $⟨P_ℳ^*[F'(μ+Δ, z)-F'(μ, z)]|Δ⟩ ≤ Θ²|γ|(c_2)‖γ‖$, where $Δ=(π_♯^1-π_♯^0)γ$.
/// For our $F(μ, z)=\frac{1}{2}\|Aμ+z-b\|^2$, we have $F'(μ, z)=A\_*(Aμ+z-b)$, so
/// $F'(μ+Δ, z)-F'(μ, z)=A\_*AΔ$ is independent of $z$, and the bounding can be calculated
/// as in the case without $z$, based on Lemma 3.8.
///
/// We use Remark 5.15 and Example 5.16 for (4.2d) and (5.2a) with the additional effect of $z$.
/// This is based on a Lipschitz estimate for $∇v^k$, where we still, similarly to the Example,
/// have $∇v^k(x)=∇A\_*(x)[Aμ^k+z^k-b]$. We estimate the final term similarly to the example,
/// assuming for the guess [`BetterThanZero`] that every iterate is better than $(μ, z)=0$.
/// This the final estimate is exactly as in the example, without $z$.
/// Thus we can directly use [`BasicCurvatureBoundEstimates`] on the operator $A$.
impl<F, A, Z, const N: usize> BoundedCurvature<F>
    for QuadraticDataTerm<F, Pair<RNDM<N, F>, Z>, RowOp<A, IdOp<Z>>>
where
    F: Float,
    Z: Clone + ClosedEuclidean<F>,
    A: Mapping<RNDM<N, F>, Codomain = Z>,
    A: BasicCurvatureBoundEstimates<F>,
{
    fn curvature_bound_components(
        &self,
        guess: BoundedCurvatureGuess,
    ) -> (DynResult<F>, DynResult<F>) {
        match guess {
            BetterThanZero => {
                let opA = &self.operator().0;
                let b = self.data();
                let (ℓ_F0, θ2) = opA.basic_curvature_bound_components();
                (ℓ_F0.map(|l| l * b.norm2()), θ2)
            }
        }
    }
}

mercurial