--- a/src/direct_product.rs Mon Sep 01 00:04:22 2025 -0500 +++ b/src/direct_product.rs Mon Sep 01 13:51:03 2025 -0500 @@ -6,8 +6,10 @@ */ use crate::euclidean::Euclidean; -use crate::instance::{Decomposition, DecompositionMut, Instance, InstanceMut, MyCow}; -use crate::linops::AXPY; +use crate::instance::{ + ClosedSpace, Decomposition, DecompositionMut, Instance, InstanceMut, MyCow, Ownable, +}; +use crate::linops::{VectorSpace, AXPY}; use crate::loc::Loc; use crate::mapping::Space; use crate::norms::{HasDual, Norm, NormExponent, Normed, PairNorm, L2}; @@ -262,6 +264,25 @@ impl_scalar_mut!(MulAssign, mul_assign); impl_scalar_mut!(DivAssign, div_assign); +/// Trait for ownable-by-consumption objects +impl<A, B> Ownable for Pair<A, B> +where + A: Ownable, + B: Ownable, +{ + type OwnedVariant = Pair<A::OwnedVariant, B::OwnedVariant>; + + #[inline] + fn into_owned(self) -> Self::OwnedVariant { + Pair(self.0.into_owned(), self.1.into_owned()) + } + + /// Returns an owned instance of a reference. + fn clone_owned(&self) -> Self::OwnedVariant { + Pair(self.0.clone_owned(), self.1.clone_owned()) + } +} + /// We only support 'closed' `Euclidean` `Pair`s, as more general ones cause /// compiler overflows. impl<A, B, F: Float> Euclidean<F> for Pair<A, B> @@ -270,19 +291,19 @@ B: Euclidean<F>, //Pair<A, B>: Euclidean<F>, Self: Sized - + Mul<F, Output = <Self as AXPY>::Owned> + + Mul<F, Output = <Self as VectorSpace>::Owned> + MulAssign<F> - + Div<F, Output = <Self as AXPY>::Owned> + + Div<F, Output = <Self as VectorSpace>::Owned> + DivAssign<F> - + Add<Self, Output = <Self as AXPY>::Owned> - + Sub<Self, Output = <Self as AXPY>::Owned> - + for<'b> Add<&'b Self, Output = <Self as AXPY>::Owned> - + for<'b> Sub<&'b Self, Output = <Self as AXPY>::Owned> + + Add<Self, Output = <Self as VectorSpace>::Owned> + + Sub<Self, Output = <Self as VectorSpace>::Owned> + + for<'b> Add<&'b Self, Output = <Self as VectorSpace>::Owned> + + for<'b> Sub<&'b Self, Output = <Self as VectorSpace>::Owned> + AddAssign<Self> + for<'b> AddAssign<&'b Self> + SubAssign<Self> + for<'b> SubAssign<&'b Self> - + Neg<Output = <Self as AXPY>::Owned>, + + Neg<Output = <Self as VectorSpace>::Owned>, { fn dot<I: Instance<Self>>(&self, other: I) -> F { other.eval_decompose(|Pair(u, v)| self.0.dot(u) + self.1.dot(v)) @@ -297,6 +318,28 @@ } } +impl<F, A, B, O, P> VectorSpace for Pair<A, B> +where + A: VectorSpace<Field = F, Owned = O, OwnedVariant = O>, + B: VectorSpace<Field = F, Owned = P, OwnedVariant = P>, + O: ClosedSpace + AXPY<A, Field = F, Owned = O, OwnedVariant = O>, + P: ClosedSpace + AXPY<B, Field = F, Owned = P, OwnedVariant = P>, + F: Num, +{ + type Field = F; + type Owned = Pair<O, P>; + + /// Return a similar zero as `self`. + fn similar_origin(&self) -> Self::Owned { + Pair(self.0.similar_origin(), self.1.similar_origin()) + } + + // #[inline] + // fn into_owned(self) -> Self::Owned { + // Pair(self.0.into_owned(), self.1.into_owned()) + // } +} + impl<F, A, B, U, V> AXPY<Pair<U, V>> for Pair<A, B> where U: Space, @@ -306,13 +349,7 @@ F: Num, Self: MulAssign<F> + DivAssign<F>, Pair<A, B>: MulAssign<F> + DivAssign<F>, - //A::Owned: MulAssign<F>, - //B::Owned: MulAssign<F>, - //Pair<A::Owned, B::Owned>: AXPY<Pair<U, V>, Field = F>, { - type Field = F; - type Owned = Pair<A::Owned, B::Owned>; - fn axpy<I: Instance<Pair<U, V>>>(&mut self, α: F, x: I, β: F) { x.eval_decompose(|Pair(u, v)| { self.0.axpy(α, u, β); @@ -334,11 +371,6 @@ }) } - /// Return a similar zero as `self`. - fn similar_origin(&self) -> Self::Owned { - Pair(self.0.similar_origin(), self.1.similar_origin()) - } - /// Set self to zero. fn set_zero(&mut self) { self.0.set_zero(); @@ -562,7 +594,7 @@ { type DualSpace = Pair<A::DualSpace, B::DualSpace>; - fn dual_origin(&self) -> <Self::DualSpace as AXPY>::Owned { + fn dual_origin(&self) -> <Self::DualSpace as VectorSpace>::Owned { Pair(self.0.dual_origin(), self.1.dual_origin()) } }