diff -r d077dff509f1 -r b7b60b3b3eff src/linops.rs --- a/src/linops.rs Sat Sep 06 23:29:34 2025 -0500 +++ b/src/linops.rs Sun Sep 07 09:44:43 2025 -0500 @@ -156,7 +156,9 @@ X: Space, Yʹ: Space, { + /// Codomain of the adjoint operator. type AdjointCodomain: ClosedSpace; + /// Type of the adjoint operator. type Adjoint<'a>: Linear where Self: 'a; @@ -165,6 +167,29 @@ fn adjoint(&self) -> Self::Adjoint<'_>; } +/// Variant of [`Adjointable`] where the adjoint does not depend on a lifetime parameter. +/// This exists due to restrictions of Rust's type system: if `A :: Adjointable`, and we make +/// further restrictions on the adjoint operator, through, e.g. +/// ``` +/// for<'a> A::Adjoint<'a> : GEMV, +/// ``` +/// Then `'static` lifetime is forced on `X`. Having `A::SimpleAdjoint` not depend on `'a` +/// avoids this, but makes it impossible for the adjoint to be just a light wrapper around the +/// original operator. +pub trait SimplyAdjointable: Linear +where + X: Space, + Yʹ: Space, +{ + /// Codomain of the adjoint operator. + type AdjointCodomain: ClosedSpace; + /// Type of the adjoint operator. + type SimpleAdjoint: Linear; + + /// Form the adjoint operator of `self`. + fn adjoint(&self) -> Self::SimpleAdjoint; +} + /// Trait for forming a preadjoint of an operator. /// /// For an operator $A$ this is an operator $A\_\*$ @@ -187,13 +212,6 @@ fn preadjoint(&self) -> Self::Preadjoint<'_>; } -/// Adjointable operators $A: X → Y$ between reflexive spaces $X$ and $Y$. -pub trait SimplyAdjointable: Adjointable>::Codomain> {} -impl<'a, X: Space, T> SimplyAdjointable for T where - T: Adjointable>::Codomain> -{ -} - /// The identity operator #[derive(Clone, Copy, Debug, Serialize, Eq, PartialEq)] pub struct IdOp(PhantomData); @@ -253,6 +271,15 @@ } } +impl SimplyAdjointable for IdOp { + type AdjointCodomain = X::Principal; + type SimpleAdjoint = IdOp; + + fn adjoint(&self) -> Self::SimpleAdjoint { + IdOp::new() + } +} + impl Preadjointable for IdOp { type PreadjointCodomain = X::Principal; type Preadjoint<'a> @@ -497,6 +524,31 @@ } } +impl<'b, X, Y, OY, OXprime, Xprime, Yprime, F> SimplyAdjointable + for ZeroOp +where + X: HasDual, + Y: HasDual, + F: Float, + Xprime: ClosedVectorSpace, + //Xprime::Owned: AXPY, + Yprime: ClosedSpace, + OY: OriginGenerator, + OXprime: OriginGenerator + Clone, +{ + type AdjointCodomain = Xprime; + type SimpleAdjoint = ZeroOp; + // () means not (pre)adjointable. + + fn adjoint(&self) -> Self::SimpleAdjoint { + ZeroOp { + codomain_origin_generator: self.other_origin_generator.clone(), + other_origin_generator: (), + _phantoms: PhantomData, + } + } +} + impl Linear for Composition where X: Space, @@ -681,6 +733,27 @@ } } +impl SimplyAdjointable, Yʹ> for RowOp +where + A: Space, + B: Space, + Yʹ: Space, + S: SimplyAdjointable, + T: SimplyAdjointable, + Self: Linear>, + // for<'a> ColOp, T::Adjoint<'a>> : Linear< + // Yʹ, + // Codomain=Pair + // >, +{ + type AdjointCodomain = Pair; + type SimpleAdjoint = ColOp; + + fn adjoint(&self) -> Self::SimpleAdjoint { + ColOp(self.0.adjoint(), self.1.adjoint()) + } +} + impl Preadjointable, Yʹ> for RowOp where A: Space, @@ -728,6 +801,28 @@ } } +impl SimplyAdjointable> for ColOp +where + A: Space, + Xʹ: Space, + Yʹ: Space, + R: ClosedSpace + ClosedAdd, + S: SimplyAdjointable, + T: SimplyAdjointable, + Self: Linear, + // for<'a> RowOp, T::Adjoint<'a>> : Linear< + // Pair, + // Codomain=R, + // >, +{ + type AdjointCodomain = R; + type SimpleAdjoint = RowOp; + + fn adjoint(&self) -> Self::SimpleAdjoint { + RowOp(self.0.adjoint(), self.1.adjoint()) + } +} + impl Preadjointable> for ColOp where A: Space, @@ -834,6 +929,26 @@ } } +impl SimplyAdjointable, Pair> for DiagOp +where + A: Space, + B: Space, + Xʹ: Space, + Yʹ: Space, + R: ClosedSpace, + S: SimplyAdjointable, + T: SimplyAdjointable, + Self: Linear>, + for<'a> DiagOp: Linear, Codomain = R>, +{ + type AdjointCodomain = R; + type SimpleAdjoint = DiagOp; + + fn adjoint(&self) -> Self::SimpleAdjoint { + DiagOp(self.0.adjoint(), self.1.adjoint()) + } +} + impl Preadjointable, Pair> for DiagOp where A: Space,