fubar dev

Mon, 01 Sep 2025 13:51:03 -0500

author
Tuomo Valkonen <tuomov@iki.fi>
date
Mon, 01 Sep 2025 13:51:03 -0500
branch
dev
changeset 150
c4e394a9c84c
parent 149
2f1798c65fd6
child 151
402d717bb5c0

fubar

src/bisection_tree/btfn.rs file | annotate | diff | comparison | revisions
src/bisection_tree/either.rs file | annotate | diff | comparison | revisions
src/bisection_tree/support.rs file | annotate | diff | comparison | revisions
src/convex.rs file | annotate | diff | comparison | revisions
src/direct_product.rs file | annotate | diff | comparison | revisions
src/euclidean.rs file | annotate | diff | comparison | revisions
src/instance.rs file | annotate | diff | comparison | revisions
src/linops.rs file | annotate | diff | comparison | revisions
src/loc.rs file | annotate | diff | comparison | revisions
src/mapping.rs file | annotate | diff | comparison | revisions
src/mapping/dataterm.rs file | annotate | diff | comparison | revisions
src/nalgebra_support.rs file | annotate | diff | comparison | revisions
src/norms.rs file | annotate | diff | comparison | revisions
src/operator_arithmetic.rs file | annotate | diff | comparison | revisions
src/types.rs file | annotate | diff | comparison | revisions
--- a/src/bisection_tree/btfn.rs	Mon Sep 01 00:04:22 2025 -0500
+++ b/src/bisection_tree/btfn.rs	Mon Sep 01 13:51:03 2025 -0500
@@ -1,6 +1,5 @@
-use crate::mapping::{
-    BasicDecomposition, DifferentiableImpl, DifferentiableMapping, Instance, Mapping, Space,
-};
+use crate::instance::{ClosedSpace, Instance, Ownable, Space};
+use crate::mapping::{BasicDecomposition, DifferentiableImpl, DifferentiableMapping, Mapping};
 use crate::types::Float;
 use numeric_literals::replace_float_literals;
 use std::iter::Sum;
@@ -37,12 +36,31 @@
     _phantoms: PhantomData<F>,
 }
 
+impl<F: Float, G, BT, const N: usize> Ownable for BTFN<F, G, BT, N>
+where
+    G: SupportGenerator<N, F, Id = BT::Data>,
+    G::SupportType: LocalAnalysis<F, BT::Agg, N>,
+    BT: BTImpl<N, F>,
+{
+    type OwnedVariant = Self;
+
+    fn into_owned(self) -> Self::OwnedVariant {
+        self
+    }
+
+    /// Returns an owned instance of a reference.
+    fn clone_owned(&self) -> Self::OwnedVariant {
+        self.clone()
+    }
+}
+
 impl<F: Float, G, BT, const N: usize> Space for BTFN<F, G, BT, N>
 where
     G: SupportGenerator<N, F, Id = BT::Data>,
     G::SupportType: LocalAnalysis<F, BT::Agg, N>,
     BT: BTImpl<N, F>,
 {
+    type OwnedSpace = Self;
     type Decomp = BasicDecomposition;
 }
 
@@ -64,11 +82,7 @@
     }
 
     fn new_arc(bt: BT, generator: Arc<G>) -> Self {
-        BTFN {
-            bt: bt,
-            generator: generator,
-            _phantoms: std::marker::PhantomData,
-        }
+        BTFN { bt, generator, _phantoms: std::marker::PhantomData }
     }
 
     /// Create a new BTFN support generator and a pre-initialised bisection tree,
@@ -161,11 +175,7 @@
 {
     /// Create a new [`PreBTFN`] with no bisection tree.
     pub fn new_pre(generator: G) -> Self {
-        BTFN {
-            bt: (),
-            generator: Arc::new(generator),
-            _phantoms: std::marker::PhantomData,
-        }
+        BTFN { bt: (), generator: Arc::new(generator), _phantoms: std::marker::PhantomData }
     }
 }
 
@@ -188,11 +198,7 @@
             bt.insert(d, &support);
         }
 
-        BTFN {
-            bt: bt,
-            generator: Arc::new(both),
-            _phantoms: std::marker::PhantomData,
-        }
+        BTFN { bt: bt, generator: Arc::new(both), _phantoms: std::marker::PhantomData }
     }
 }
 
@@ -411,7 +417,7 @@
     BT: BTImpl<N, F>,
     G: SupportGenerator<N, F, Id = BT::Data>,
     G::SupportType: LocalAnalysis<F, BT::Agg, N> + Mapping<Loc<N, F>, Codomain = V>,
-    V: Sum + Space,
+    V: Sum + ClosedSpace,
 {
     type Codomain = V;
 
@@ -430,7 +436,7 @@
     G: SupportGenerator<N, F, Id = BT::Data>,
     G::SupportType:
         LocalAnalysis<F, BT::Agg, N> + DifferentiableMapping<Loc<N, F>, DerivativeDomain = V>,
-    V: Sum + Space,
+    V: Sum + ClosedSpace,
 {
     type Derivative = V;
 
@@ -846,12 +852,7 @@
     Cube<N, F>: P2Minimise<Loc<N, F>, F>,
 {
     fn maximise(&mut self, tolerance: F, max_steps: usize) -> (Loc<N, F>, F) {
-        let refiner = P2Refiner {
-            tolerance,
-            max_steps,
-            how: RefineMax,
-            bound: None,
-        };
+        let refiner = P2Refiner { tolerance, max_steps, how: RefineMax, bound: None };
         self.bt
             .search_and_refine(refiner, &self.generator)
             .expect("Refiner failure.")
@@ -864,24 +865,14 @@
         tolerance: F,
         max_steps: usize,
     ) -> Option<(Loc<N, F>, F)> {
-        let refiner = P2Refiner {
-            tolerance,
-            max_steps,
-            how: RefineMax,
-            bound: Some(bound),
-        };
+        let refiner = P2Refiner { tolerance, max_steps, how: RefineMax, bound: Some(bound) };
         self.bt
             .search_and_refine(refiner, &self.generator)
             .expect("Refiner failure.")
     }
 
     fn minimise(&mut self, tolerance: F, max_steps: usize) -> (Loc<N, F>, F) {
-        let refiner = P2Refiner {
-            tolerance,
-            max_steps,
-            how: RefineMin,
-            bound: None,
-        };
+        let refiner = P2Refiner { tolerance, max_steps, how: RefineMin, bound: None };
         self.bt
             .search_and_refine(refiner, &self.generator)
             .expect("Refiner failure.")
@@ -894,36 +885,21 @@
         tolerance: F,
         max_steps: usize,
     ) -> Option<(Loc<N, F>, F)> {
-        let refiner = P2Refiner {
-            tolerance,
-            max_steps,
-            how: RefineMin,
-            bound: Some(bound),
-        };
+        let refiner = P2Refiner { tolerance, max_steps, how: RefineMin, bound: Some(bound) };
         self.bt
             .search_and_refine(refiner, &self.generator)
             .expect("Refiner failure.")
     }
 
     fn has_upper_bound(&mut self, bound: F, tolerance: F, max_steps: usize) -> bool {
-        let refiner = BoundRefiner {
-            bound,
-            tolerance,
-            max_steps,
-            how: RefineMax,
-        };
+        let refiner = BoundRefiner { bound, tolerance, max_steps, how: RefineMax };
         self.bt
             .search_and_refine(refiner, &self.generator)
             .expect("Refiner failure.")
     }
 
     fn has_lower_bound(&mut self, bound: F, tolerance: F, max_steps: usize) -> bool {
-        let refiner = BoundRefiner {
-            bound,
-            tolerance,
-            max_steps,
-            how: RefineMin,
-        };
+        let refiner = BoundRefiner { bound, tolerance, max_steps, how: RefineMin };
         self.bt
             .search_and_refine(refiner, &self.generator)
             .expect("Refiner failure.")
--- a/src/bisection_tree/either.rs	Mon Sep 01 00:04:22 2025 -0500
+++ b/src/bisection_tree/either.rs	Mon Sep 01 13:51:03 2025 -0500
@@ -3,7 +3,9 @@
 
 use crate::iter::{MapF, MapZ, Mappable};
 use crate::loc::Loc;
-use crate::mapping::{DifferentiableImpl, DifferentiableMapping, Instance, Mapping, Space};
+use crate::mapping::{
+    ClosedSpace, DifferentiableImpl, DifferentiableMapping, Instance, Mapping, Space,
+};
 use crate::sets::Cube;
 use crate::types::*;
 
@@ -192,7 +194,7 @@
 
 impl<F, S1, S2, X> Mapping<X> for EitherSupport<S2, S1>
 where
-    F: Space,
+    F: ClosedSpace,
     X: Space,
     S1: Mapping<X, Codomain = F>,
     S2: Mapping<X, Codomain = F>,
@@ -210,8 +212,8 @@
 
 impl<X, S1, S2, O> DifferentiableImpl<X> for EitherSupport<S2, S1>
 where
-    O: Space,
-    X: Space,
+    O: ClosedSpace,
+    X: ClosedSpace,
     S1: DifferentiableMapping<X, DerivativeDomain = O>,
     S2: DifferentiableMapping<X, DerivativeDomain = O>,
 {
--- a/src/bisection_tree/support.rs	Mon Sep 01 00:04:22 2025 -0500
+++ b/src/bisection_tree/support.rs	Mon Sep 01 13:51:03 2025 -0500
@@ -4,7 +4,9 @@
 use super::aggregator::Bounds;
 pub use crate::bounds::{GlobalAnalysis, LocalAnalysis};
 use crate::loc::Loc;
-use crate::mapping::{DifferentiableImpl, DifferentiableMapping, Instance, Mapping, Space};
+use crate::mapping::{
+    ClosedSpace, DifferentiableImpl, DifferentiableMapping, Instance, Mapping, Space,
+};
 use crate::maputil::map2;
 use crate::norms::{Linfinity, Norm, L1, L2};
 pub use crate::operator_arithmetic::{Constant, Weighted};
@@ -46,10 +48,7 @@
     /// Translate `self` by `x`.
     #[inline]
     fn shift(self, x: Loc<N, F>) -> Shift<Self, N, F> {
-        Shift {
-            shift: x,
-            base_fn: self,
-        }
+        Shift { shift: x, base_fn: self }
     }
 }
 
@@ -60,7 +59,7 @@
     base_fn: T,
 }
 
-impl<'a, T, V: Space, F: Float, const N: usize> Mapping<Loc<N, F>> for Shift<T, N, F>
+impl<'a, T, V: ClosedSpace, F: Float, const N: usize> Mapping<Loc<N, F>> for Shift<T, N, F>
 where
     T: Mapping<Loc<N, F>, Codomain = V>,
 {
@@ -72,7 +71,8 @@
     }
 }
 
-impl<'a, T, V: Space, F: Float, const N: usize> DifferentiableImpl<Loc<N, F>> for Shift<T, N, F>
+impl<'a, T, V: ClosedSpace, F: Float, const N: usize> DifferentiableImpl<Loc<N, F>>
+    for Shift<T, N, F>
 where
     T: DifferentiableMapping<Loc<N, F>, DerivativeDomain = V>,
 {
@@ -226,10 +226,7 @@
             type Output = Weighted<T, F>;
             #[inline]
             fn $fn(self, t: F) -> Self::Output {
-                Weighted {
-                    weight: self.weight.$fn(t),
-                    base_fn: self.base_fn.clone(),
-                }
+                Weighted { weight: self.weight.$fn(t), base_fn: self.base_fn.clone() }
             }
         }
     };
--- a/src/convex.rs	Mon Sep 01 00:04:22 2025 -0500
+++ b/src/convex.rs	Mon Sep 01 13:51:03 2025 -0500
@@ -4,7 +4,7 @@
 
 use crate::error::DynResult;
 use crate::euclidean::Euclidean;
-use crate::instance::{DecompositionMut, Instance, InstanceMut};
+use crate::instance::{ClosedSpace, DecompositionMut, Instance};
 use crate::linops::{IdOp, Scaled, SimpleZeroOp, AXPY};
 use crate::mapping::{DifferentiableImpl, LipschitzDifferentiableImpl, Mapping, Space};
 use crate::norms::*;
@@ -57,7 +57,7 @@
 /// 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>
+    type Prox<'a>: Mapping<Domain, Codomain = Domain::OwnedSpace>
     where
         Self: 'a;
 
@@ -65,16 +65,15 @@
     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 {
+    fn prox<I: Instance<Domain>>(&self, τ: Self::Codomain, z: I) -> Domain::OwnedSpace {
         self.prox_mapping(τ).apply(z)
     }
 
     /// Calculate the proximal mapping with weight τ in-place
-    fn prox_mut<'b>(&self, τ: Self::Codomain, y: &'b mut Domain)
+    fn prox_mut<'b>(&self, τ: Self::Codomain, y: &'b mut Domain::OwnedSpace)
     where
-        &'b mut Domain: InstanceMut<Domain>,
         Domain::Decomp: DecompositionMut<Domain>,
-        for<'a> &'a Domain: Instance<Domain>,
+        for<'a> &'a Domain::OwnedSpace: Instance<Domain>,
     {
         *y = self.prox(τ, &*y);
     }
@@ -165,7 +164,7 @@
     Domain: Space + Norm<E, F>,
     E: NormExponent,
     F: Float,
-    NormProjection<F, E>: Mapping<Domain, Codomain = Domain>,
+    NormProjection<F, E>: Mapping<Domain, Codomain = Domain::OwnedSpace>,
 {
     type Prox<'a>
         = NormProjection<F, E>
@@ -203,12 +202,13 @@
 impl<F, E, Domain> Mapping<Domain> for NormProjection<F, E>
 where
     Domain: Normed<F> + Projection<F, E>,
+    Domain::OwnedSpace: ClosedSpace,
     F: Float,
     E: NormExponent,
 {
-    type Codomain = Domain;
+    type Codomain = Domain::OwnedSpace;
 
-    fn apply<I: Instance<Domain>>(&self, d: I) -> Domain {
+    fn apply<I: Instance<Domain>>(&self, d: I) -> Self::Codomain {
         d.own().proj_ball(self.radius, self.exponent)
     }
 }
@@ -262,7 +262,7 @@
     }
 }
 
-impl<Domain: Space + Clone, F: Num> Prox<Domain> for Zero<Domain, F> {
+impl<Domain: Space, F: Num> Prox<Domain> for Zero<Domain, F> {
     type Prox<'a>
         = IdOp<Domain>
     where
@@ -395,7 +395,7 @@
 impl<X, F> Prox<X> for Norm222<F>
 where
     F: Float,
-    X: Euclidean<F, Owned = X>,
+    X: Euclidean<F, Owned = X> + ClosedMul<F>,
 {
     type Prox<'a>
         = Scaled<F>
@@ -410,11 +410,11 @@
 impl<X, F> DifferentiableImpl<X> for Norm222<F>
 where
     F: Float,
-    X: Euclidean<F, Owned = X>,
+    X: Euclidean<F>,
 {
-    type Derivative = X;
+    type Derivative = X::Owned;
 
-    fn differential_impl<I: Instance<X>>(&self, x: I) -> X {
+    fn differential_impl<I: Instance<X>>(&self, x: I) -> Self::Derivative {
         x.own()
     }
 }
@@ -422,7 +422,7 @@
 impl<X, F> LipschitzDifferentiableImpl<X, L2> for Norm222<F>
 where
     F: Float,
-    X: Euclidean<F, Owned = X>,
+    X: Euclidean<F>,
 {
     type FloatType = F;
 
--- 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())
     }
 }
--- a/src/euclidean.rs	Mon Sep 01 00:04:22 2025 -0500
+++ b/src/euclidean.rs	Mon Sep 01 13:51:03 2025 -0500
@@ -3,13 +3,15 @@
 */
 
 use crate::instance::Instance;
-use crate::linops::AXPY;
+use crate::linops::{VectorSpace, AXPY};
 use crate::norms::{HasDual, Reflexive};
 use crate::types::*;
 use std::ops::{Add, AddAssign, Sub, SubAssign};
 
 pub mod wrap;
 
+// TODO: Euclidean & EuclideanMut
+//
 /// Space (type) with Euclidean and vector space structure
 ///
 /// The type should implement vector space operations (addition, subtraction, scalar
@@ -18,11 +20,11 @@
 // TODO: remove F parameter, use AXPY::Field
 pub trait Euclidean<F: Float = f64>:
     HasDual<F, DualSpace = Self>
-    + AXPY<Field = F>
+    + VectorSpace<Field = F>
     + Reflexive<F>
     // TODO: move the following to AXPY
-    + for<'b> Add<&'b Self, Output = <Self as AXPY>::Owned>
-    + for<'b> Sub<&'b Self, Output = <Self as AXPY>::Owned>
+    + for<'b> Add<&'b Self, Output = <Self as VectorSpace>::Owned>
+    + for<'b> Sub<&'b Self, Output = <Self as VectorSpace>::Owned>
     + for<'b> AddAssign<&'b Self>
     + for<'b> SubAssign<&'b Self>
 {
@@ -59,11 +61,20 @@
 
     /// Projection to the 2-ball.
     #[inline]
-    fn proj_ball2(mut self, ρ: F) -> Self {
-        self.proj_ball2_mut(ρ);
-        self
+    fn proj_ball2(self, ρ: F) -> Self::Owned {
+        let r = self.norm2();
+        if r > ρ {
+            self * (ρ / r)
+        } else {
+            self.into_owned()
+        }
     }
+}
 
+// TODO: remove F parameter, use AXPY::Field
+pub trait EuclideanMut<F: Float = f64>:
+    Euclidean<F> + AXPY<Field = F> + for<'b> AddAssign<&'b Self> + for<'b> SubAssign<&'b Self>
+{
     /// In-place projection to the 2-ball.
     #[inline]
     fn proj_ball2_mut(&mut self, ρ: F) {
@@ -74,8 +85,13 @@
     }
 }
 
+impl<X, F: Float> EuclideanMut<F> for X where
+    X: Euclidean<F> + AXPY<Field = F> + for<'b> AddAssign<&'b Self> + for<'b> SubAssign<&'b Self>
+{
+}
+
 /// Trait for [`Euclidean`] spaces with dimensions known at compile time.
 pub trait StaticEuclidean<F: Float = f64>: Euclidean<F> {
     /// Returns the origin
-    fn origin() -> <Self as AXPY>::Owned;
+    fn origin() -> <Self as VectorSpace>::Owned;
 }
--- a/src/instance.rs	Mon Sep 01 00:04:22 2025 -0500
+++ b/src/instance.rs	Mon Sep 01 13:51:03 2025 -0500
@@ -23,35 +23,117 @@
     }
 }
 
-impl<'b, X> MyCow<'b, X> {
+/// Trait for ownable-by-consumption objects
+pub trait Ownable {
+    type OwnedVariant: Clone;
+
+    /// Returns an owned instance, possibly consuming the original,
+    /// avoiding cloning when possible.
+    fn into_owned(self) -> Self::OwnedVariant;
+
+    /// Returns an owned instance of a reference.
+    fn clone_owned(&self) -> Self::OwnedVariant;
+}
+
+impl<'a, X: Ownable> Ownable for &'a X {
+    type OwnedVariant = X::OwnedVariant;
+
+    #[inline]
+    /// Returns an owned instance.
+    fn into_owned(self) -> Self::OwnedVariant {
+        self.clone_owned()
+    }
+
     #[inline]
-    pub fn into_owned(self) -> X
-    where
-        X: Clone,
-    {
+    /// Returns an owned instance.
+    fn clone_owned(&self) -> Self::OwnedVariant {
+        (*self).into_owned()
+    }
+}
+
+impl<'a, X: Ownable> Ownable for &'a mut X {
+    type OwnedVariant = X::OwnedVariant;
+
+    #[inline]
+    /// Returns an owned instance.
+    fn into_owned(self) -> Self::OwnedVariant {
+        self.clone_owned()
+    }
+
+    #[inline]
+    /// Returns an owned instance.
+    fn clone_owned(&self) -> Self::OwnedVariant {
+        (&**self).into_owned()
+    }
+}
+
+impl<'a, X: Ownable> Ownable for MyCow<'a, X> {
+    type OwnedVariant = X::OwnedVariant;
+
+    #[inline]
+    /// Returns an owned instance.
+    fn into_owned(self) -> Self::OwnedVariant {
         match self {
-            EitherDecomp::Owned(x) => x,
-            EitherDecomp::Borrowed(x) => x.clone(),
+            EitherDecomp::Owned(x) => x.into_owned(),
+            EitherDecomp::Borrowed(x) => x.into_owned(),
+        }
+    }
+
+    #[inline]
+    /// Returns an owned instance.
+    fn clone_owned(&self) -> Self::OwnedVariant {
+        match self {
+            EitherDecomp::Owned(x) => x.into_owned(),
+            EitherDecomp::Borrowed(x) => x.into_owned(),
         }
     }
 }
 
 /// Trait for abitrary mathematical spaces.
-pub trait Space: Instance<Self, Self::Decomp> {
+pub trait Space: Ownable<OwnedVariant = Self::OwnedSpace> + Sized {
+    type OwnedSpace: ClosedSpace;
+
     /// Default decomposition for the space
     type Decomp: Decomposition<Self>;
 }
 
+mod private {
+    pub trait Sealed {}
+}
+
+/// Helper trait for working with own types.
+pub trait Owned: Ownable<OwnedVariant = Self> + private::Sealed {}
+impl<X: Ownable<OwnedVariant = X>> private::Sealed for X {}
+impl<X: Ownable<OwnedVariant = X>> Owned for X {}
+
+/// Helper trait for working with closed spaces, operations in which should
+/// return members of the same space
+pub trait ClosedSpace: Space<OwnedSpace = Self> + Owned + Instance<Self> {}
+impl<X: Space<OwnedSpace = Self> + Owned + Instance<Self>> ClosedSpace for X {}
+
 #[macro_export]
 macro_rules! impl_basic_space {
-    ($($type:ty)*) => { $(
-        impl $crate::instance::Space for $type {
+    ($($type:ty)*) => {
+        $( $crate::impl_basic_space!($type where ); )*
+    };
+    ($type:ty where $($where:tt)*) => {
+        impl<$($where)*> $crate::instance::Space for $type {
+            type OwnedSpace = Self;
             type Decomp = $crate::instance::BasicDecomposition;
         }
-    )* };
-    ($type:ty where $($where:tt)*) => {
-        impl<$($where)*> $crate::instance::Space for $type {
-            type Decomp = $crate::instance::BasicDecomposition;
+
+        impl<$($where)*> $crate::instance::Ownable for $type {
+            type OwnedVariant = Self;
+
+            #[inline]
+            fn into_owned(self) -> Self::OwnedVariant {
+                self
+            }
+
+            #[inline]
+            fn clone_owned(&self) -> Self::OwnedVariant {
+                *self
+            }
         }
     };
 }
@@ -83,7 +165,7 @@
 #[derive(Copy, Clone, Debug)]
 pub struct BasicDecomposition;
 
-impl<X: Space + Clone> Decomposition<X> for BasicDecomposition {
+impl<X: Space> Decomposition<X> for BasicDecomposition {
     type Decomposition<'b>
         = MyCow<'b, X>
     where
@@ -104,8 +186,9 @@
 /// generalises [`std::borrow::ToOwned`], [`std::borrow::Borrow`], and [`std::borrow::Cow`].
 ///
 /// This is used, for example, by [`crate::mapping::Mapping::apply`].
-pub trait Instance<X: Space, D = <X as Space>::Decomp>: Sized
+pub trait Instance<X, D = <X as Space>::Decomp>: Sized + Ownable
 where
+    X: Space,
     D: Decomposition<X>,
 {
     /// Decomposes self according to `decomposer`, and evaluate `f` on the result.
@@ -164,7 +247,7 @@
     }
 }
 
-impl<X: Space + Clone> Instance<X, BasicDecomposition> for X {
+impl<X: Space> Instance<X, BasicDecomposition> for X {
     #[inline]
     fn eval_decompose<'b, R>(self, f: impl FnOnce(MyCow<'b, X>) -> R) -> R
     where
@@ -197,7 +280,7 @@
     }
 }
 
-impl<'a, X: Space + Clone> Instance<X, BasicDecomposition> for &'a X {
+impl<'a, X: Space> Instance<X, BasicDecomposition> for &'a X {
     #[inline]
     fn eval_decompose<'b, R>(self, f: impl FnOnce(MyCow<'b, X>) -> R) -> R
     where
@@ -218,7 +301,7 @@
 
     #[inline]
     fn own(self) -> X {
-        self.clone()
+        self.into_owned()
     }
 
     #[inline]
@@ -230,7 +313,7 @@
     }
 }
 
-impl<'a, X: Space + Clone> Instance<X, BasicDecomposition> for &'a mut X {
+impl<'a, X: Space> Instance<X, BasicDecomposition> for &'a mut X {
     #[inline]
     fn eval_decompose<'b, R>(self, f: impl FnOnce(MyCow<'b, X>) -> R) -> R
     where
@@ -251,20 +334,19 @@
 
     #[inline]
     fn own(self) -> X {
-        self.clone()
+        self.into_owned()
     }
 
     #[inline]
     fn cow<'b>(self) -> MyCow<'b, X>
     where
         Self: 'b,
-        X: Clone,
     {
         EitherDecomp::Borrowed(self)
     }
 }
 
-impl<'a, X: Space + Clone> Instance<X, BasicDecomposition> for MyCow<'a, X> {
+impl<'a, X: Space> Instance<X, BasicDecomposition> for MyCow<'a, X> {
     #[inline]
     fn eval_decompose<'b, R>(self, f: impl FnOnce(MyCow<'b, X>) -> R) -> R
     where
--- a/src/linops.rs	Mon Sep 01 00:04:22 2025 -0500
+++ b/src/linops.rs	Mon Sep 01 13:51:03 2025 -0500
@@ -5,7 +5,7 @@
 use crate::direct_product::Pair;
 use crate::error::DynResult;
 use crate::instance::Instance;
-pub use crate::mapping::{Composition, DifferentiableImpl, Mapping, Space};
+pub use crate::mapping::{ClosedSpace, Composition, DifferentiableImpl, Mapping, Space};
 use crate::norms::{HasDual, Linfinity, Norm, NormExponent, PairNorm, L1, L2};
 use crate::types::*;
 use numeric_literals::replace_float_literals;
@@ -25,16 +25,10 @@
 //     }
 // }
 
-/// Efficient in-place summation.
+/// Vector spaces
 #[replace_float_literals(Self::Field::cast_from(literal))]
-pub trait AXPY<X = Self>:
-    Space
-    + MulAssign<Self::Field>
-    + DivAssign<Self::Field>
-    + AddAssign<Self>
-    + AddAssign<Self::Owned>
-    + SubAssign<Self>
-    + SubAssign<Self::Owned>
+pub trait VectorSpace:
+    Space<OwnedSpace = Self::Owned>
     + Mul<Self::Field, Output = Self::Owned>
     + Div<Self::Field, Output = Self::Owned>
     + Add<Self, Output = Self::Owned>
@@ -42,12 +36,42 @@
     + Sub<Self, Output = Self::Owned>
     + Sub<Self::Owned, Output = Self::Owned>
     + Neg
+{
+    type Field: Num;
+    type Owned: ClosedSpace
+        + AXPY<
+            Self,
+            Field = Self::Field,
+            Owned = Self::Owned,
+            OwnedVariant = Self::Owned,
+            OwnedSpace = Self::Owned,
+        >;
+
+    /// Return a similar zero as `self`.
+    fn similar_origin(&self) -> Self::Owned;
+    // {
+    //     self.make_origin_generator().make_origin()
+    // }
+
+    /// Return a similar zero as `x`.
+    fn similar_origin_inst<I: Instance<Self>>(x: I) -> Self::Owned {
+        x.eval(|xr| xr.similar_origin())
+    }
+}
+
+/// Efficient in-place summation.
+#[replace_float_literals(Self::Field::cast_from(literal))]
+pub trait AXPY<X = Self>:
+    VectorSpace
+    + MulAssign<Self::Field>
+    + DivAssign<Self::Field>
+    + AddAssign<Self>
+    + AddAssign<Self::Owned>
+    + SubAssign<Self>
+    + SubAssign<Self::Owned>
 where
     X: Space,
 {
-    type Field: Num;
-    type Owned: AXPY<X, Field = Self::Field>;
-
     /// Computes  `y = βy + αx`, where `y` is `Self`.
     fn axpy<I: Instance<X>>(&mut self, α: Self::Field, x: I, β: Self::Field);
 
@@ -61,21 +85,8 @@
         self.axpy(α, x, 0.0)
     }
 
-    /// Return a similar zero as `self`.
-    fn similar_origin(&self) -> Self::Owned;
-    // {
-    //     self.make_origin_generator().make_origin()
-    // }
-
-    /// Return a similar zero as `x`.
-    fn similar_origin_inst<I: Instance<Self>>(x: I) -> Self::Owned {
-        x.eval(|xr| xr.similar_origin())
-    }
-
     /// Set self to zero.
     fn set_zero(&mut self);
-
-    //fn make_origin_generator(&self) -> Self::OriginGen<'_>;
 }
 
 /// Efficient in-place application for [`Linear`] operators.
@@ -178,21 +189,21 @@
     }
 }
 
-impl<X: Clone + Space> Mapping<X> for IdOp<X> {
-    type Codomain = X;
+impl<X: Space> Mapping<X> for IdOp<X> {
+    type Codomain = X::OwnedVariant;
 
     fn apply<I: Instance<X>>(&self, x: I) -> X {
         x.own()
     }
 }
 
-impl<X: Clone + Space> Linear<X> for IdOp<X> {}
+impl<X: Space> Linear<X> for IdOp<X> {}
 
 #[replace_float_literals(F::cast_from(literal))]
 impl<F: Num, X, Y> GEMV<F, X, Y> for IdOp<X>
 where
     Y: AXPY<X, Field = F>,
-    X: Clone + Space,
+    X: Space,
 {
     // Computes  `y = αAx + βy`, where `A` is `Self`.
     fn gemv<I: Instance<X>>(&self, y: &mut Y, α: F, x: I, β: F) {
@@ -243,10 +254,7 @@
 #[derive(Clone, Copy, Debug, Serialize, Eq, PartialEq)]
 pub struct SimpleZeroOp;
 
-impl<X> Mapping<X> for SimpleZeroOp
-where
-    X: AXPY + Instance<X>,
-{
+impl<X: VectorSpace> Mapping<X> for SimpleZeroOp {
     type Codomain = X::Owned;
 
     fn apply<I: Instance<X>>(&self, x: I) -> X::Owned {
@@ -254,14 +262,14 @@
     }
 }
 
-impl<X> Linear<X> for SimpleZeroOp where X: AXPY + Instance<X> {}
+impl<X: VectorSpace> Linear<X> for SimpleZeroOp {}
 
 #[replace_float_literals(F::cast_from(literal))]
 impl<X, Y, F> GEMV<F, X, Y> for SimpleZeroOp
 where
     F: Num,
     Y: AXPY<Field = F>,
-    X: AXPY<Field = F> + Instance<X>,
+    X: VectorSpace<Field = F> + Instance<X>,
 {
     // Computes  `y = αAx + βy`, where `A` is `Self`.
     fn gemv<I: Instance<X>>(&self, y: &mut Y, _α: F, _x: I, β: F) {
@@ -276,7 +284,7 @@
 impl<X, F, E1, E2> BoundedLinear<X, E1, E2, F> for SimpleZeroOp
 where
     F: Num,
-    X: AXPY<Field = F> + Instance<X> + Norm<E1, F>,
+    X: VectorSpace<Field = F> + Norm<E1, F>,
     E1: NormExponent,
     E2: NormExponent,
 {
@@ -288,8 +296,8 @@
 impl<X, F> Adjointable<X, X::DualSpace> for SimpleZeroOp
 where
     F: Num,
-    X: AXPY<Field = F> + Instance<X> + HasDual<F>,
-    X::DualSpace: AXPY<Owned = X::DualSpace>,
+    X: VectorSpace<Field = F> + HasDual<F>,
+    X::DualSpace: VectorSpace<Owned = X::DualSpace>,
 {
     type AdjointCodomain = X::DualSpace;
     type Adjoint<'b>
@@ -303,7 +311,7 @@
     }
 }
 
-pub trait OriginGenerator<Y: AXPY> {
+pub trait OriginGenerator<Y: VectorSpace> {
     type Ref<'b>: OriginGenerator<Y>
     where
         Self: 'b;
@@ -312,7 +320,7 @@
     fn as_ref(&self) -> Self::Ref<'_>;
 }
 
-impl<Y: AXPY> OriginGenerator<Y> for Y {
+impl<Y: VectorSpace> OriginGenerator<Y> for Y {
     type Ref<'b>
         = &'b Y
     where
@@ -329,7 +337,7 @@
     }
 }
 
-impl<'b, Y: AXPY> OriginGenerator<Y> for &'b Y {
+impl<'b, Y: VectorSpace> OriginGenerator<Y> for &'b Y {
     type Ref<'c>
         = Self
     where
@@ -348,7 +356,7 @@
 
 /// A zero operator that can be eitherh dualised or predualised (once).
 /// This is achieved by storing an oppropriate zero.
-pub struct ZeroOp<X, Y: AXPY<Field = F>, OY: OriginGenerator<Y>, O, F: Float = f64> {
+pub struct ZeroOp<X, Y: VectorSpace<Field = F>, OY: OriginGenerator<Y>, O, F: Float = f64> {
     codomain_origin_generator: OY,
     other_origin_generator: O,
     _phantoms: PhantomData<(X, Y, F)>,
@@ -357,8 +365,8 @@
 impl<X, Y, OY, F> ZeroOp<X, Y, OY, (), F>
 where
     OY: OriginGenerator<Y>,
-    X: AXPY<Field = F>,
-    Y: AXPY<Field = F>,
+    X: VectorSpace<Field = F>,
+    Y: VectorSpace<Field = F>,
     F: Float,
 {
     pub fn new(y_og: OY) -> Self {
@@ -377,7 +385,7 @@
     X: HasDual<F, DualSpace = Xprime>,
     Y: HasDual<F, DualSpace = Yprime>,
     F: Float,
-    Xprime: AXPY<Field = F, Owned = Xprime>,
+    Xprime: VectorSpace<Field = F, Owned = Xprime>,
     Xprime::Owned: AXPY<Field = F>,
     Yprime: Space + Instance<Yprime>,
 {
@@ -392,8 +400,8 @@
 
 impl<X, Y, O, OY, F> Mapping<X> for ZeroOp<X, Y, OY, O, F>
 where
-    X: Space + Instance<X>,
-    Y: AXPY<Field = F>,
+    X: Space,
+    Y: VectorSpace<Field = F>,
     F: Float,
     OY: OriginGenerator<Y>,
 {
@@ -406,8 +414,8 @@
 
 impl<X, Y, OY, O, F> Linear<X> for ZeroOp<X, Y, OY, O, F>
 where
-    X: Space + Instance<X>,
-    Y: AXPY<Field = F>,
+    X: Space,
+    Y: VectorSpace<Field = F>,
     F: Float,
     OY: OriginGenerator<Y>,
 {
@@ -416,7 +424,7 @@
 #[replace_float_literals(F::cast_from(literal))]
 impl<X, Y, OY, O, F> GEMV<F, X, Y> for ZeroOp<X, Y, OY, O, F>
 where
-    X: Space + Instance<X>,
+    X: Space,
     Y: AXPY<Field = F, Owned = Y>,
     F: Float,
     OY: OriginGenerator<Y>,
@@ -434,7 +442,7 @@
 impl<X, Y, OY, O, F, E1, E2> BoundedLinear<X, E1, E2, F> for ZeroOp<X, Y, OY, O, F>
 where
     X: Space + Instance<X> + Norm<E1, F>,
-    Y: AXPY<Field = F>,
+    Y: VectorSpace<Field = F>,
     Y::Owned: Clone,
     F: Float,
     E1: NormExponent,
@@ -452,7 +460,7 @@
     X: HasDual<F, DualSpace = Xprime>,
     Y: HasDual<F, DualSpace = Yprime>,
     F: Float,
-    Xprime: AXPY<Field = F, Owned = Xprime>,
+    Xprime: VectorSpace<Field = F, Owned = Xprime>,
     Xprime::Owned: AXPY<Field = F>,
     Yprime: Space + Instance<Yprime>,
     OY: OriginGenerator<Y>,
--- a/src/loc.rs	Mon Sep 01 00:04:22 2025 -0500
+++ b/src/loc.rs	Mon Sep 01 13:51:03 2025 -0500
@@ -4,8 +4,8 @@
 */
 
 use crate::euclidean::*;
-use crate::instance::{BasicDecomposition, Instance};
-use crate::linops::{Linear, Mapping, AXPY};
+use crate::instance::{BasicDecomposition, Instance, Ownable};
+use crate::linops::{Linear, Mapping, VectorSpace, AXPY};
 use crate::mapping::Space;
 use crate::maputil::{map1, map1_mut, map2, map2_mut, FixedLength, FixedLengthMut};
 use crate::norms::*;
@@ -27,6 +27,21 @@
     pub [F; N],
 );
 
+/// Trait for ownable-by-consumption objects
+impl<const N: usize, F: Copy> Ownable for Loc<N, F> {
+    type OwnedVariant = Self;
+
+    #[inline]
+    fn into_owned(self) -> Self::OwnedVariant {
+        self
+    }
+
+    /// Returns an owned instance of a reference.
+    fn clone_owned(&self) -> Self::OwnedVariant {
+        self.clone()
+    }
+}
+
 impl<F: Display, const N: usize> Display for Loc<N, F> {
     // Required method
     fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
@@ -648,6 +663,14 @@
 
 impl<F: Float, const N: usize> Projection<F, Linfinity> for Loc<N, F> {
     #[inline]
+    fn proj_ball(mut self, ρ: F, exp: Linfinity) -> Self {
+        self.proj_ball_mut(ρ, exp);
+        self
+    }
+}
+
+impl<F: Float, const N: usize> ProjectionMut<F, Linfinity> for Loc<N, F> {
+    #[inline]
     fn proj_ball_mut(&mut self, ρ: F, _: Linfinity) {
         self.iter_mut()
             .for_each(|v| *v = num_traits::clamp(*v, -ρ, ρ))
@@ -706,6 +729,7 @@
 }
 
 impl<F: Num, const N: usize> Space for Loc<N, F> {
+    type OwnedSpace = Self;
     type Decomp = BasicDecomposition;
 }
 
@@ -719,10 +743,32 @@
 
 impl<F: Float, const N: usize> Linear<Loc<N, F>> for Loc<N, F> {}
 
-impl<F: Float, const N: usize> AXPY<Loc<N, F>> for Loc<N, F> {
+impl<F: Float, const N: usize> VectorSpace for Loc<N, F> {
     type Field = F;
     type Owned = Self;
 
+    // #[inline]
+    // fn make_origin_generator(&self) -> StaticEuclideanOriginGenerator {
+    //     StaticEuclideanOriginGenerator
+    // }
+
+    #[inline]
+    fn similar_origin(&self) -> Self::Owned {
+        Self::ORIGIN
+    }
+
+    #[inline]
+    fn similar_origin_inst<I: Instance<Self>>(_: I) -> Self::Owned {
+        Self::ORIGIN
+    }
+
+    // #[inline]
+    // fn into_owned(self) -> Self::Owned {
+    //     self
+    // }
+}
+
+impl<F: Float, const N: usize> AXPY<Loc<N, F>> for Loc<N, F> {
     #[inline]
     fn axpy<I: Instance<Loc<N, F>>>(&mut self, α: F, x: I, β: F) {
         x.eval(|x̃| {
@@ -739,21 +785,6 @@
         x.eval(|x̃| map2_mut(self, x̃, |yi, xi| *yi = *xi))
     }
 
-    // #[inline]
-    // fn make_origin_generator(&self) -> StaticEuclideanOriginGenerator {
-    //     StaticEuclideanOriginGenerator
-    // }
-
-    #[inline]
-    fn similar_origin(&self) -> Self::Owned {
-        Self::ORIGIN
-    }
-
-    #[inline]
-    fn similar_origin_inst<I: Instance<Self>>(_: I) -> Self::Owned {
-        Self::ORIGIN
-    }
-
     #[inline]
     fn set_zero(&mut self) {
         *self = Self::ORIGIN;
--- a/src/mapping.rs	Mon Sep 01 00:04:22 2025 -0500
+++ b/src/mapping.rs	Mon Sep 01 13:51:03 2025 -0500
@@ -4,7 +4,7 @@
 
 use crate::error::DynResult;
 use crate::instance::MyCow;
-pub use crate::instance::{BasicDecomposition, Decomposition, Instance, Space};
+pub use crate::instance::{BasicDecomposition, ClosedSpace, Decomposition, Instance, Space};
 use crate::loc::Loc;
 use crate::norms::{Norm, NormExponent};
 use crate::operator_arithmetic::{Constant, Weighted};
@@ -14,7 +14,7 @@
 
 /// A mapping from `Domain` to `Self::Codomain`.
 pub trait Mapping<Domain: Space> {
-    type Codomain: Space;
+    type Codomain: ClosedSpace;
 
     /// Compute the value of `self` at `x`.
     fn apply<I: Instance<Domain>>(&self, x: I) -> Self::Codomain;
@@ -25,11 +25,7 @@
     where
         Self: Sized,
     {
-        Composition {
-            outer: self,
-            inner: other,
-            intermediate_norm_exponent: (),
-        }
+        Composition { outer: self, inner: other, intermediate_norm_exponent: () }
     }
 
     #[inline]
@@ -43,11 +39,7 @@
         Domain: Norm<E, F>,
         F: Num,
     {
-        Composition {
-            outer: self,
-            inner: other,
-            intermediate_norm_exponent: norm,
-        }
+        Composition { outer: self, inner: other, intermediate_norm_exponent: norm }
     }
 
     /// Multiply `self` by the scalar `a`.
@@ -58,10 +50,7 @@
         C: Constant,
         Self::Codomain: ClosedMul<C::Type>,
     {
-        Weighted {
-            weight: a,
-            base_fn: self,
-        }
+        Weighted { weight: a, base_fn: self }
     }
 }
 
@@ -86,7 +75,7 @@
 ///
 /// This is automatically implemented when [`DifferentiableImpl`] is.
 pub trait DifferentiableMapping<Domain: Space>: Mapping<Domain> {
-    type DerivativeDomain: Space;
+    type DerivativeDomain: ClosedSpace;
     type Differential<'b>: Mapping<Domain, Codomain = Self::DerivativeDomain>
     where
         Self: 'b;
@@ -115,7 +104,7 @@
 
 /// Helper trait for implementing [`DifferentiableMapping`]
 pub trait DifferentiableImpl<X: Space>: Sized {
-    type Derivative: Space;
+    type Derivative: ClosedSpace;
 
     /// Compute the differential of `self` at `x`, consuming the input.
     fn differential_impl<I: Instance<X>>(&self, x: I) -> Self::Derivative;
@@ -138,17 +127,11 @@
     }
 
     fn diff(self) -> Differential<'static, Domain, Self> {
-        Differential {
-            g: MyCow::Owned(self),
-            _space: PhantomData,
-        }
+        Differential { g: MyCow::Owned(self), _space: PhantomData }
     }
 
     fn diff_ref(&self) -> Differential<'_, Domain, Self> {
-        Differential {
-            g: MyCow::Borrowed(self),
-            _space: PhantomData,
-        }
+        Differential { g: MyCow::Borrowed(self), _space: PhantomData }
     }
 }
 
@@ -201,10 +184,7 @@
 pub trait FlattenCodomain<X: Space, F>: Mapping<X, Codomain = Loc<1, F>> + Sized {
     /// Flatten the codomain from [`Loc`]`<F, 1>` to `F`.
     fn flatten_codomain(self) -> FlattenedCodomain<X, F, Self> {
-        FlattenedCodomain {
-            g: self,
-            _phantoms: PhantomData,
-        }
+        FlattenedCodomain { g: self, _phantoms: PhantomData }
     }
 }
 
@@ -241,21 +221,13 @@
     /// Flatten the codomain from [`Loc`]`<F, 1>` to `F`.
     fn slice_codomain(self, slice: usize) -> SlicedCodomain<'static, X, F, Self, N> {
         assert!(slice < N);
-        SlicedCodomain {
-            g: MyCow::Owned(self),
-            slice,
-            _phantoms: PhantomData,
-        }
+        SlicedCodomain { g: MyCow::Owned(self), slice, _phantoms: PhantomData }
     }
 
     /// Flatten the codomain from [`Loc`]`<F, 1>` to `F`.
     fn slice_codomain_ref(&self, slice: usize) -> SlicedCodomain<'_, X, F, Self, N> {
         assert!(slice < N);
-        SlicedCodomain {
-            g: MyCow::Borrowed(self),
-            slice,
-            _phantoms: PhantomData,
-        }
+        SlicedCodomain { g: MyCow::Borrowed(self), slice, _phantoms: PhantomData }
     }
 }
 
@@ -294,7 +266,7 @@
     E: Copy,
     //Composition<S::Derivative, T::Derivative, E>: Space,
     S::Derivative: Mul<T::Derivative, Output = Y>,
-    Y: Space,
+    Y: ClosedSpace,
 {
     //type Derivative = Composition<S::Derivative, T::Derivative, E>;
     type Derivative = Y;
--- a/src/mapping/dataterm.rs	Mon Sep 01 00:04:22 2025 -0500
+++ b/src/mapping/dataterm.rs	Mon Sep 01 13:51:03 2025 -0500
@@ -8,7 +8,7 @@
 use super::{DifferentiableImpl, DifferentiableMapping, LipschitzDifferentiableImpl, Mapping};
 use crate::convex::ConvexMapping;
 use crate::error::DynResult;
-use crate::instance::{Instance, Space};
+use crate::instance::{ClosedSpace, Instance, Space};
 use crate::linops::{BoundedLinear, Linear, Preadjointable};
 use crate::norms::{Normed, L2};
 use crate::types::Float;
@@ -41,11 +41,7 @@
     G: Mapping<A::Codomain, Codomain = F> + Clone,
 {
     fn clone(&self) -> Self {
-        DataTerm {
-            opA: self.opA.clone(),
-            b: self.b.clone(),
-            g: self.g.clone(),
-        }
+        DataTerm { opA: self.opA.clone(), b: self.b.clone(), g: self.g.clone() }
     }
 }
 
@@ -78,7 +74,7 @@
     X: Space,
     A: Mapping<X>,
     G: Mapping<A::Codomain, Codomain = F>,
-    A::Codomain: for<'a> Sub<&'a A::Codomain, Output = A::Codomain>,
+    A::Codomain: ClosedSpace + for<'a> Sub<&'a A::Codomain, Output = A::Codomain>,
 {
     type Codomain = F;
 
@@ -97,7 +93,7 @@
     X: Normed<F>,
     A: Linear<X>,
     G: ConvexMapping<A::Codomain, F>,
-    A::Codomain: Normed<F> + for<'a> Sub<&'a A::Codomain, Output = A::Codomain>,
+    A::Codomain: ClosedSpace + Normed<F> + for<'a> Sub<&'a A::Codomain, Output = A::Codomain>,
 {
 }
 
@@ -105,9 +101,11 @@
 where
     F: Float,
     X: Space,
-    Y: Space + for<'a> Sub<&'a Y, Output = Y>,
+    Y: Space + Instance<Y> + for<'a> Sub<&'a Y, Output = Y>,
     //<A as Mapping<X>>::Codomain: Euclidean<F>,
     A: Linear<X, Codomain = Y> + Preadjointable<X, G::DerivativeDomain>,
+    G::DerivativeDomain: Instance<G::DerivativeDomain>,
+    A::PreadjointCodomain: ClosedSpace,
     //<<A as Mapping<X>>::Codomain as Euclidean<F>>::Output: Instance<<A as Mapping<X>>::Codomain>,
     G: DifferentiableMapping<Y, Codomain = F>,
 {
--- a/src/nalgebra_support.rs	Mon Sep 01 00:04:22 2025 -0500
+++ b/src/nalgebra_support.rs	Mon Sep 01 13:51:03 2025 -0500
@@ -9,7 +9,7 @@
 */
 
 use crate::euclidean::*;
-use crate::instance::Instance;
+use crate::instance::{Instance, Ownable};
 use crate::linops::*;
 use crate::mapping::{BasicDecomposition, Space};
 use crate::norms::*;
@@ -24,14 +24,36 @@
 use num_traits::identities::{One, Zero};
 use std::ops::Mul;
 
+impl<S, M, N, E> Ownable for Matrix<E, M, N, S>
+where
+    S: Storage<E, M, N>,
+    M: Dim,
+    N: Dim,
+    E: Scalar + Zero + One,
+    DefaultAllocator: Allocator<M, N>,
+{
+    type OwnedVariant = OMatrix<E, M, N>;
+
+    #[inline]
+    fn into_owned(self) -> Self::OwnedVariant {
+        Matrix::into_owned(self)
+    }
+
+    /// Returns an owned instance of a reference.
+    fn clone_owned(&self) -> Self::OwnedVariant {
+        Matrix::clone_owned(self)
+    }
+}
+
 impl<SM, N, M, E> Space for Matrix<E, N, M, SM>
 where
-    SM: Storage<E, N, M> + Clone,
+    SM: Storage<E, N, M>,
     N: Dim,
     M: Dim,
-    E: Scalar + Zero + One + ClosedAddAssign + ClosedMulAssign,
+    E: Scalar + Zero + One,
     DefaultAllocator: Allocator<N, M>,
 {
+    type OwnedSpace = OMatrix<E, N, M>;
     type Decomp = BasicDecomposition;
 }
 
@@ -103,10 +125,9 @@
     }
 }
 
-impl<SM, SV1, M, N, E> AXPY<Matrix<E, M, N, SV1>> for Matrix<E, M, N, SM>
+impl<S, M, N, E> VectorSpace for Matrix<E, M, N, S>
 where
-    SM: StorageMut<E, M, N> + Clone,
-    SV1: Storage<E, M, N> + Clone,
+    S: Storage<E, M, N>,
     M: Dim,
     N: Dim,
     E: Scalar + Zero + One + Float,
@@ -116,6 +137,22 @@
     type Owned = OMatrix<E, M, N>;
 
     #[inline]
+    fn similar_origin(&self) -> Self::Owned {
+        let (n, m) = self.shape_generic();
+        OMatrix::zeros_generic(n, m)
+    }
+}
+
+impl<SM, SV1, M, N, E> AXPY<Matrix<E, M, N, SV1>> for Matrix<E, M, N, SM>
+where
+    SM: StorageMut<E, M, N>,
+    SV1: Storage<E, M, N>,
+    M: Dim,
+    N: Dim,
+    E: Scalar + Zero + One + Float,
+    DefaultAllocator: Allocator<M, N>,
+{
+    #[inline]
     fn axpy<I: Instance<Matrix<E, M, N, SV1>>>(&mut self, α: E, x: I, β: E) {
         x.eval(|x̃| {
             assert_eq!(self.ncols(), x̃.ncols());
@@ -136,12 +173,6 @@
     fn set_zero(&mut self) {
         self.iter_mut().for_each(|e| *e = E::ZERO);
     }
-
-    #[inline]
-    fn similar_origin(&self) -> Self::Owned {
-        let (n, m) = self.shape_generic();
-        OMatrix::zeros_generic(n, m)
-    }
 }
 
 /* Implemented automatically as Euclidean.
@@ -160,6 +191,21 @@
 
 impl<SM, M, E> Projection<E, Linfinity> for Vector<E, M, SM>
 where
+    SM: Storage<E, M> + Clone,
+    M: Dim,
+    E: Scalar + Zero + One + Float + RealField,
+    DefaultAllocator: Allocator<M>,
+{
+    #[inline]
+    fn proj_ball(self, ρ: E, exp: Linfinity) -> <Self as Space>::OwnedSpace {
+        let mut owned = self.into_owned();
+        owned.proj_ball_mut(ρ, exp);
+        owned
+    }
+}
+
+impl<SM, M, E> ProjectionMut<E, Linfinity> for Vector<E, M, SM>
+where
     SM: StorageMut<E, M> + Clone,
     M: Dim,
     E: Scalar + Zero + One + Float + RealField,
--- a/src/norms.rs	Mon Sep 01 00:04:22 2025 -0500
+++ b/src/norms.rs	Mon Sep 01 13:51:03 2025 -0500
@@ -3,7 +3,8 @@
 */
 
 use crate::euclidean::*;
-use crate::linops::AXPY;
+use crate::instance::Ownable;
+use crate::linops::VectorSpace;
 use crate::mapping::{Instance, Mapping, Space};
 use crate::types::*;
 use serde::{Deserialize, Serialize};
@@ -123,16 +124,12 @@
 ///
 /// println!("{:?}, {:?}", x.proj_ball(1.0, L2), x.proj_ball(0.5, Linfinity));
 /// ```
-pub trait Projection<F: Num, Exponent: NormExponent>: Norm<Exponent, F> + Sized
-where
-    F: Float,
-{
+pub trait Projection<F: Num, Exponent: NormExponent>: Ownable + Norm<Exponent, F> {
     /// Projection of `self` to the `q`-norm-ball of radius ρ.
-    fn proj_ball(mut self, ρ: F, q: Exponent) -> Self {
-        self.proj_ball_mut(ρ, q);
-        self
-    }
+    fn proj_ball(self, ρ: F, q: Exponent) -> Self::OwnedVariant;
+}
 
+pub trait ProjectionMut<F: Num, Exponent: NormExponent>: Projection<F, Exponent> {
     /// In-place projection of `self` to the `q`-norm-ball of radius ρ.
     fn proj_ball_mut(&mut self, ρ: F, q: Exponent);
 }
@@ -146,10 +143,12 @@
 
 impl<F: Float, E: Euclidean<F> + Norm<L2, F>> Projection<F, L2> for E {
     #[inline]
-    fn proj_ball(self, ρ: F, _p: L2) -> Self {
+    fn proj_ball(self, ρ: F, _p: L2) -> Self::OwnedVariant {
         self.proj_ball2(ρ)
     }
+}
 
+impl<F: Float, E: EuclideanMut<F> + Norm<L2, F>> ProjectionMut<F, L2> for E {
     #[inline]
     fn proj_ball_mut(&mut self, ρ: F, _p: L2) {
         self.proj_ball2_mut(ρ)
@@ -229,10 +228,10 @@
     }
 }
 
-pub trait HasDual<F: Num = f64>: Normed<F> + AXPY<Field = F> {
-    type DualSpace: Normed<F> + AXPY<Field = F>;
+pub trait HasDual<F: Num = f64>: Normed<F> + VectorSpace<Field = F> {
+    type DualSpace: Normed<F> + VectorSpace<Field = F>;
 
-    fn dual_origin(&self) -> <Self::DualSpace as AXPY>::Owned;
+    fn dual_origin(&self) -> <Self::DualSpace as VectorSpace>::Owned;
 }
 
 /// Automatically implemented trait for reflexive spaces
--- a/src/operator_arithmetic.rs	Mon Sep 01 00:04:22 2025 -0500
+++ b/src/operator_arithmetic.rs	Mon Sep 01 13:51:03 2025 -0500
@@ -2,7 +2,7 @@
 Arithmetic of [`Mapping`]s.
  */
 
-use crate::instance::{Instance, Space};
+use crate::instance::{ClosedSpace, Instance, Space};
 use crate::mapping::{DifferentiableImpl, DifferentiableMapping, Mapping};
 use crate::types::*;
 use serde::Serialize;
@@ -63,7 +63,7 @@
     F: Float,
     D: Space,
     T: DifferentiableMapping<D, DerivativeDomain = V>,
-    V: Space + std::ops::Mul<F, Output = V>,
+    V: ClosedSpace + std::ops::Mul<F, Output = V>,
     C: Constant<Type = F>,
 {
     type Derivative = V;
--- a/src/types.rs	Mon Sep 01 00:04:22 2025 -0500
+++ b/src/types.rs	Mon Sep 01 13:51:03 2025 -0500
@@ -10,15 +10,12 @@
 */
 
 //use trait_set::trait_set;
+pub use num_traits::cast::AsPrimitive;
 pub use num_traits::Float as NumTraitsFloat; // needed to re-export functions.
-pub use num_traits::cast::AsPrimitive;
 
 pub use simba::scalar::{
-    ClosedAdd, ClosedAddAssign,
+    ClosedAdd, ClosedAddAssign, ClosedDiv, ClosedDivAssign, ClosedMul, ClosedMulAssign, ClosedNeg,
     ClosedSub, ClosedSubAssign,
-    ClosedMul, ClosedMulAssign,
-    ClosedDiv, ClosedDivAssign,
-    ClosedNeg
 };
 
 /// Typical integer type
@@ -34,8 +31,8 @@
 pub type float = f64;
 
 /// Casts of abstract numerical types to others via the standard `as` keyword.
-pub trait CastFrom<T : 'static + Copy> : num_traits::cast::AsPrimitive<T> {
-    fn cast_from(other : T) -> Self;
+pub trait CastFrom<T: 'static + Copy>: num_traits::cast::AsPrimitive<T> {
+    fn cast_from(other: T) -> Self;
 }
 
 macro_rules! impl_casts {
@@ -58,53 +55,71 @@
             f32 f64);
 
 /// Trait for general numeric types
-pub trait Num : 'static + Copy + Sync + Send + num::Num + num_traits::NumAssign
-                 + std::iter::Sum + std::iter::Product
-                 + std::fmt::Debug + std::fmt::Display + serde::Serialize
-                 + CastFrom<u8>   + CastFrom<u16> + CastFrom<u32> + CastFrom<u64>
-                 + CastFrom<u128> + CastFrom<usize>
-                 + CastFrom<i8>   + CastFrom<i16> + CastFrom<i32> + CastFrom<i64>
-                 + CastFrom<i128> + CastFrom<isize>
-                 + CastFrom<f32>  + CastFrom<f64>
-                 + crate::instance::Space {
-
-    const ZERO : Self;
-    const ONE : Self;
-    const TWO : Self;
+pub trait Num:
+    'static
+    + Copy
+    + Sync
+    + Send
+    + num::Num
+    + num_traits::NumAssign
+    + std::iter::Sum
+    + std::iter::Product
+    + std::fmt::Debug
+    + std::fmt::Display
+    + serde::Serialize
+    + CastFrom<u8>
+    + CastFrom<u16>
+    + CastFrom<u32>
+    + CastFrom<u64>
+    + CastFrom<u128>
+    + CastFrom<usize>
+    + CastFrom<i8>
+    + CastFrom<i16>
+    + CastFrom<i32>
+    + CastFrom<i64>
+    + CastFrom<i128>
+    + CastFrom<isize>
+    + CastFrom<f32>
+    + CastFrom<f64>
+    + crate::instance::ClosedSpace
+{
+    const ZERO: Self;
+    const ONE: Self;
+    const TWO: Self;
     /// Generic version of `Self::MAX`
-    const RANGE_MAX : Self;
+    const RANGE_MAX: Self;
     /// Generic version of `Self::MIN`
-    const RANGE_MIN : Self;
+    const RANGE_MIN: Self;
 }
 
 /// Trait for signed numeric types
-pub trait SignedNum : Num + num::Signed + std::ops::Neg<Output=Self> {}
-impl<U : Num + num::Signed + std::ops::Neg<Output=Self>> SignedNum for U { }
+pub trait SignedNum: Num + num::Signed + std::ops::Neg<Output = Self> {}
+impl<U: Num + num::Signed + std::ops::Neg<Output = Self>> SignedNum for U {}
 
 /// Trait for floating point numbers
-pub trait Float : SignedNum + num::Float /*+ From<Self::CompatibleSize>*/ {
+pub trait Float: SignedNum + num::Float /*+ From<Self::CompatibleSize>*/ {
     // An unsigned integer that can be used for indexing operations and
     // converted to F without loss.
     //type CompatibleSize : CompatibleUnsigned<Self>;
 
-    const PI : Self;
-    const E : Self;
-    const EPSILON : Self;
-    const SQRT_2 : Self;
-    const INFINITY : Self;
-    const NEG_INFINITY : Self;
-    const NAN : Self;
-    const FRAC_2_SQRT_PI : Self;
+    const PI: Self;
+    const E: Self;
+    const EPSILON: Self;
+    const SQRT_2: Self;
+    const INFINITY: Self;
+    const NEG_INFINITY: Self;
+    const NAN: Self;
+    const FRAC_2_SQRT_PI: Self;
 }
 
 /// Trait for integers
-pub trait Integer : Num + num::Integer {}
+pub trait Integer: Num + num::Integer {}
 
 /// Trait for unsigned integers
-pub trait Unsigned : Num + Integer + num::Unsigned {}
+pub trait Unsigned: Num + Integer + num::Unsigned {}
 
 /// Trait for signed integers
-pub trait Signed : SignedNum + Integer {}
+pub trait Signed: SignedNum + Integer {}
 
 macro_rules! impl_num_consts {
     ($($type:ty)*) => { $(
@@ -137,14 +152,14 @@
     #[cfg(any(target_pointer_width = "32", target_pointer_width = "16"))]
     type CompatibleSize = usize;*/
 
-    const PI : Self = std::f64::consts::PI;
-    const E : Self = std::f64::consts::E;
-    const EPSILON : Self = std::f64::EPSILON;
-    const SQRT_2 : Self = std::f64::consts::SQRT_2;
-    const INFINITY : Self = std::f64::INFINITY;
-    const NEG_INFINITY : Self = std::f64::NEG_INFINITY;
-    const NAN : Self = std::f64::NAN;
-    const FRAC_2_SQRT_PI : Self = std::f64::consts::FRAC_2_SQRT_PI;
+    const PI: Self = std::f64::consts::PI;
+    const E: Self = std::f64::consts::E;
+    const EPSILON: Self = std::f64::EPSILON;
+    const SQRT_2: Self = std::f64::consts::SQRT_2;
+    const INFINITY: Self = std::f64::INFINITY;
+    const NEG_INFINITY: Self = std::f64::NEG_INFINITY;
+    const NAN: Self = std::f64::NAN;
+    const FRAC_2_SQRT_PI: Self = std::f64::consts::FRAC_2_SQRT_PI;
 }
 
 impl Float for f32 {
@@ -155,14 +170,14 @@
     type CompatibleSize = usize;
     */
 
-    const PI : Self = std::f32::consts::PI;
-    const E : Self = std::f32::consts::E;
-    const EPSILON : Self = std::f32::EPSILON;
-    const SQRT_2 : Self = std::f32::consts::SQRT_2;
-    const INFINITY : Self = std::f32::INFINITY;
-    const NEG_INFINITY : Self = std::f32::NEG_INFINITY;
-    const NAN : Self = std::f32::NAN;
-    const FRAC_2_SQRT_PI : Self = std::f32::consts::FRAC_2_SQRT_PI;
+    const PI: Self = std::f32::consts::PI;
+    const E: Self = std::f32::consts::E;
+    const EPSILON: Self = std::f32::EPSILON;
+    const SQRT_2: Self = std::f32::consts::SQRT_2;
+    const INFINITY: Self = std::f32::INFINITY;
+    const NEG_INFINITY: Self = std::f32::NEG_INFINITY;
+    const NAN: Self = std::f32::NAN;
+    const FRAC_2_SQRT_PI: Self = std::f32::consts::FRAC_2_SQRT_PI;
 }
 
 /*
@@ -171,4 +186,3 @@
     pub trait CompatibleSigned<F : Float> = Signed + Into<F>;
 }
 */
-

mercurial