Significantly simplify Mapping / Apply through Instance dev

Tue, 31 Dec 2024 08:30:02 -0500

author
Tuomo Valkonen <tuomov@iki.fi>
date
Tue, 31 Dec 2024 08:30:02 -0500
branch
dev
changeset 59
9226980e45a7
parent 58
1a38447a89fa
child 60
848ecc05becf

Significantly simplify Mapping / Apply through Instance

Cargo.lock file | annotate | diff | comparison | revisions
Cargo.toml file | annotate | diff | comparison | revisions
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/collection.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/lib.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/metaprogramming.rs file | annotate | diff | comparison | revisions
src/nalgebra_support.rs file | annotate | diff | comparison | revisions
src/norms.rs file | annotate | diff | comparison | revisions
src/types.rs file | annotate | diff | comparison | revisions
--- a/Cargo.lock	Sat Dec 14 09:31:27 2024 -0500
+++ b/Cargo.lock	Tue Dec 31 08:30:02 2024 -0500
@@ -17,6 +17,7 @@
  "rayon",
  "serde",
  "serde_json",
+ "simba",
 ]
 
 [[package]]
--- a/Cargo.toml	Sat Dec 14 09:31:27 2024 -0500
+++ b/Cargo.toml	Tue Dec 31 08:30:02 2024 -0500
@@ -23,6 +23,7 @@
 cpu-time = "~1.0.0"
 serde_json = "~1.0.85"
 rayon = "1.5.3"
+simba = "0.9.0"
 
 [package.metadata.docs.rs]
 rustdoc-args = [ "--html-in-header", "katex-header.html" ]
@@ -32,7 +33,7 @@
 debug = true
 
 [features]
-default = []
+default = ["nightly"]
 use_custom_thread_pool = []
 nightly = [] # enable for higher-performance implementations of some things
 
--- a/src/bisection_tree/btfn.rs	Sat Dec 14 09:31:27 2024 -0500
+++ b/src/bisection_tree/btfn.rs	Tue Dec 31 08:30:02 2024 -0500
@@ -4,7 +4,10 @@
 use std::marker::PhantomData;
 use std::sync::Arc;
 use crate::types::Float;
-use crate::mapping::{Apply, Mapping, Differentiable};
+use crate::mapping::{
+    Instance, Mapping, DifferentiableImpl, DifferentiableMapping, Space,
+    BasicDecomposition,
+};
 //use crate::linops::{Apply, Linear};
 use crate::sets::Set;
 use crate::sets::Cube;
@@ -40,10 +43,22 @@
 }
 
 impl<F : Float, G, BT, const N : usize>
+Space for BTFN<F, G, BT, N>
+where
+    G : SupportGenerator<F, N, Id=BT::Data>,
+    G::SupportType : LocalAnalysis<F, BT::Agg, N>,
+    BT : BTImpl<F, N>
+{
+    type Decomp = BasicDecomposition;
+}
+
+impl<F : Float, G, BT, const N : usize>
 BTFN<F, G, BT, N>
-where G : SupportGenerator<F, N, Id=BT::Data>,
-      G::SupportType : LocalAnalysis<F, BT::Agg, N>,
-      BT : BTImpl<F, N> {
+where
+    G : SupportGenerator<F, N, Id=BT::Data>,
+    G::SupportType : LocalAnalysis<F, BT::Agg, N>,
+    BT : BTImpl<F, N>
+{
 
     /// Create a new BTFN from a support generator and a pre-initialised bisection tree.
     ///
@@ -390,64 +405,40 @@
 // Apply, Mapping, Differentiate
 //
 
-impl<'a, F : Float, G, BT, V, const N : usize> Apply<&'a Loc<F, N>>
+impl<F : Float, G, BT, V, const N : usize> Mapping<Loc<F, N>>
 for BTFN<F, G, BT, N>
-where BT : BTImpl<F, N>,
-      G : SupportGenerator<F, N, Id=BT::Data>,
-      G::SupportType : LocalAnalysis<F, BT::Agg, N> + Apply<&'a Loc<F, N>, Output = V>,
-      V : Sum {
+where
+    BT : BTImpl<F, N>,
+    G : SupportGenerator<F, N, Id=BT::Data>,
+    G::SupportType : LocalAnalysis<F, BT::Agg, N> + Mapping<Loc<F, N>, Codomain = V>,
+    V : Sum + Space,
+{
 
-    type Output = V;
+    type Codomain = V;
 
-    fn apply(&self, x : &'a Loc<F, N>) -> Self::Output {
-        self.bt.iter_at(x)
-            .map(|&d| self.generator.support_for(d).apply(x)).sum()
+    fn apply<I : Instance<Loc<F,N>>>(&self, x : I) -> Self::Codomain {
+        let xc = x.cow();
+        self.bt.iter_at(&*xc)
+            .map(|&d| self.generator.support_for(d).apply(&*xc)).sum()
     }
 }
 
-impl<F : Float, G, BT, V, const N : usize> Apply<Loc<F, N>>
+impl<F : Float, G, BT, V, const N : usize> DifferentiableImpl<Loc<F, N>>
 for BTFN<F, G, BT, N>
-where BT : BTImpl<F, N>,
-      G : SupportGenerator<F, N, Id=BT::Data>,
-      G::SupportType : LocalAnalysis<F, BT::Agg, N> + Apply<Loc<F, N>, Output = V>,
-      V : Sum {
-
-    type Output = V;
-
-    fn apply(&self, x : Loc<F, N>) -> Self::Output {
-        self.bt.iter_at(&x)
-            .map(|&d| self.generator.support_for(d).apply(x)).sum()
-    }
-}
-
-impl<'a, F : Float, G, BT, V, const N : usize> Differentiable<&'a Loc<F, N>>
-for BTFN<F, G, BT, N>
-where BT : BTImpl<F, N>,
-      G : SupportGenerator<F, N, Id=BT::Data>,
-      G::SupportType : LocalAnalysis<F, BT::Agg, N> + Differentiable<&'a Loc<F, N>, Derivative = V>,
-      V : Sum {
+where
+    BT : BTImpl<F, N>,
+    G : SupportGenerator<F, N, Id=BT::Data>,
+    G::SupportType : LocalAnalysis<F, BT::Agg, N>
+        + DifferentiableMapping<Loc<F, N>, DerivativeDomain = V>,
+    V : Sum + Space,
+{
 
     type Derivative = V;
 
-    fn differential(&self, x : &'a Loc<F, N>) -> Self::Derivative {
-        self.bt.iter_at(x)
-            .map(|&d| self.generator.support_for(d).differential(x))
-            .sum()
-    }
-}
-
-impl<F : Float, G, BT, V, const N : usize> Differentiable<Loc<F, N>>
-for BTFN<F, G, BT, N>
-where BT : BTImpl<F, N>,
-      G : SupportGenerator<F, N, Id=BT::Data>,
-      G::SupportType : LocalAnalysis<F, BT::Agg, N> + Differentiable<Loc<F, N>, Derivative = V>,
-      V : Sum {
-
-    type Derivative = V;
-
-    fn differential(&self, x : Loc<F, N>) -> Self::Derivative {
-        self.bt.iter_at(&x)
-            .map(|&d| self.generator.support_for(d).differential(x))
+    fn differential_impl<I : Instance<Loc<F, N>>>(&self, x :I) -> Self::Derivative {
+        let xc = x.cow();
+        self.bt.iter_at(&*xc)
+            .map(|&d| self.generator.support_for(d).differential(&*xc))
             .sum()
     }
 }
@@ -840,7 +831,7 @@
 impl<F : Float, G, BT, const N : usize> BTFN<F, G, BT, N>
 where BT : BTSearch<F, N, Agg=Bounds<F>>,
       G : SupportGenerator<F, N, Id=BT::Data>,
-      G::SupportType : Mapping<Loc<F, N>,Codomain=F>
+      G::SupportType : Mapping<Loc<F, N>, Codomain=F>
                        + LocalAnalysis<F, Bounds<F>, N>,
       Cube<F, N> : P2Minimise<Loc<F, N>, F> {
 
--- a/src/bisection_tree/either.rs	Sat Dec 14 09:31:27 2024 -0500
+++ b/src/bisection_tree/either.rs	Tue Dec 31 08:30:02 2024 -0500
@@ -3,7 +3,13 @@
 use std::sync::Arc;
 
 use crate::types::*;
-use crate::mapping::{Apply, Differentiable};
+use crate::mapping::{
+    Instance,
+    Mapping,
+    DifferentiableImpl,
+    DifferentiableMapping,
+    Space,
+};
 use crate::iter::{Mappable, MapF, MapZ};
 use crate::sets::Cube;
 use crate::loc::Loc;
@@ -177,12 +183,17 @@
     }
 }
 
-impl<F, S1, S2, X> Apply<X> for EitherSupport<S1, S2>
-where S1 : Apply<X, Output=F>,
-      S2 : Apply<X, Output=F> {
-    type Output = F;
+impl<F, S1, S2, X> Mapping<X> for EitherSupport<S1, S2>
+where
+    F : Space,
+    X : Space,
+    S1 : Mapping<X, Codomain=F>,
+    S2 : Mapping<X, Codomain=F>,
+{
+    type Codomain = F;
+
     #[inline]
-    fn apply(&self, x : X) -> F {
+    fn apply<I : Instance<X>>(&self, x : I) -> F {
         match self {
             EitherSupport::Left(ref a) => a.apply(x),
             EitherSupport::Right(ref b) => b.apply(x),
@@ -190,12 +201,17 @@
     }
 }
 
-impl<F, S1, S2, X> Differentiable<X> for EitherSupport<S1, S2>
-where S1 : Differentiable<X, Derivative=F>,
-      S2 : Differentiable<X, Derivative=F> {
-    type Derivative = F;
+impl<X, S1, S2, O> DifferentiableImpl<X> for EitherSupport<S1, S2>
+where
+    O : Space,
+    X : Space,
+    S1 : DifferentiableMapping<X, DerivativeDomain=O>,
+    S2 : DifferentiableMapping<X, DerivativeDomain=O>,
+{
+    type Derivative = O;
+
     #[inline]
-    fn differential(&self, x : X) -> F {
+    fn differential_impl<I : Instance<X>>(&self, x : I) -> O {
         match self {
             EitherSupport::Left(ref a) => a.differential(x),
             EitherSupport::Right(ref b) => b.differential(x),
--- a/src/bisection_tree/support.rs	Sat Dec 14 09:31:27 2024 -0500
+++ b/src/bisection_tree/support.rs	Tue Dec 31 08:30:02 2024 -0500
@@ -6,7 +6,9 @@
 use std::ops::{MulAssign,DivAssign,Neg};
 use crate::types::{Float, Num};
 use crate::maputil::map2;
-use crate::mapping::{Apply, Differentiable};
+use crate::mapping::{
+    Instance, Mapping, DifferentiableImpl, DifferentiableMapping, Space
+};
 use crate::sets::Cube;
 use crate::loc::Loc;
 use super::aggregator::Bounds;
@@ -127,39 +129,23 @@
     base_fn : T,
 }
 
-impl<'a, T, V, F : Float, const N : usize> Apply<&'a Loc<F, N>> for Shift<T,F,N>
-where T : Apply<Loc<F, N>, Output=V> {
-    type Output = V;
+impl<'a, T, V : Space, F : Float, const N : usize> Mapping<Loc<F, N>> for Shift<T,F,N>
+where T : Mapping<Loc<F, N>, Codomain=V> {
+    type Codomain = V;
+
     #[inline]
-    fn apply(&self, x : &'a Loc<F, N>) -> Self::Output {
-        self.base_fn.apply(x - &self.shift)
+    fn apply<I : Instance<Loc<F, N>>>(&self, x : I) -> Self::Codomain {
+        self.base_fn.apply(x.own() - &self.shift)
     }
 }
 
-impl<'a, T, V, F : Float, const N : usize> Apply<Loc<F, N>> for Shift<T,F,N>
-where T : Apply<Loc<F, N>, Output=V> {
-    type Output = V;
-    #[inline]
-    fn apply(&self, x : Loc<F, N>) -> Self::Output {
-        self.base_fn.apply(x - &self.shift)
-    }
-}
-
-impl<'a, T, V, F : Float, const N : usize> Differentiable<&'a Loc<F, N>> for Shift<T,F,N>
-where T : Differentiable<Loc<F, N>, Derivative=V> {
+impl<'a, T, V : Space, F : Float, const N : usize> DifferentiableImpl<Loc<F, N>> for Shift<T,F,N>
+where T : DifferentiableMapping<Loc<F, N>, DerivativeDomain=V> {
     type Derivative = V;
+
     #[inline]
-    fn differential(&self, x : &'a Loc<F, N>) -> Self::Derivative {
-        self.base_fn.differential(x - &self.shift)
-    }
-}
-
-impl<'a, T, V, F : Float, const N : usize> Differentiable<Loc<F, N>> for Shift<T,F,N>
-where T : Differentiable<Loc<F, N>, Derivative=V> {
-    type Derivative = V;
-    #[inline]
-    fn differential(&self, x : Loc<F, N>) -> Self::Derivative {
-        self.base_fn.differential(x - &self.shift)
+    fn differential_impl<I : Instance<Loc<F, N>>>(&self, x : I) -> Self::Derivative {
+        self.base_fn.differential(x.own() - &self.shift)
     }
 }
 
@@ -228,46 +214,26 @@
     pub base_fn : T,
 }
 
-impl<'a, T, V, F : Float, C, const N : usize> Apply<&'a Loc<F, N>> for Weighted<T, C>
-where T : for<'b> Apply<&'b Loc<F, N>, Output=V>,
-      V : std::ops::Mul<F,Output=V>,
+impl<'a, T, V, F : Float, C, const N : usize> Mapping<Loc<F, N>> for Weighted<T, C>
+where T : Mapping<Loc<F, N>, Codomain=V>,
+      V : Space + std::ops::Mul<F,Output=V>,
       C : Constant<Type=F> {
-    type Output = V;
+    type Codomain = V;
+
     #[inline]
-    fn apply(&self, x : &'a Loc<F, N>) -> Self::Output {
+    fn apply<I : Instance<Loc<F, N>>>(&self, x : I) -> Self::Codomain {
         self.base_fn.apply(x) * self.weight.value()
     }
 }
 
-impl<'a, T, V, F : Float, C, const N : usize> Apply<Loc<F, N>> for Weighted<T, C>
-where T : Apply<Loc<F, N>, Output=V>,
-      V : std::ops::Mul<F,Output=V>,
-      C : Constant<Type=F> {
-    type Output = V;
-    #[inline]
-    fn apply(&self, x : Loc<F, N>) -> Self::Output {
-        self.base_fn.apply(x) * self.weight.value()
-    }
-}
-
-impl<'a, T, V, F : Float, C, const N : usize> Differentiable<&'a Loc<F, N>> for Weighted<T, C>
-where T : for<'b> Differentiable<&'b Loc<F, N>, Derivative=V>,
-      V : std::ops::Mul<F, Output=V>,
+impl<'a, T, V, F : Float, C, const N : usize> DifferentiableImpl<Loc<F, N>> for Weighted<T, C>
+where T : DifferentiableMapping<Loc<F, N>, DerivativeDomain=V>,
+      V : Space + std::ops::Mul<F, Output=V>,
       C : Constant<Type=F> {
     type Derivative = V;
+
     #[inline]
-    fn differential(&self, x : &'a Loc<F, N>) -> Self::Derivative {
-        self.base_fn.differential(x) * self.weight.value()
-    }
-}
-
-impl<'a, T, V, F : Float, C, const N : usize> Differentiable<Loc<F, N>> for Weighted<T, C>
-where T : Differentiable<Loc<F, N>, Derivative=V>,
-      V : std::ops::Mul<F, Output=V>,
-      C : Constant<Type=F> {
-    type Derivative = V;
-    #[inline]
-    fn differential(&self, x : Loc<F, N>) -> Self::Derivative {
+    fn differential_impl<I : Instance<Loc<F, N>>>(&self, x : I) -> Self::Derivative {
         self.base_fn.differential(x) * self.weight.value()
     }
 }
@@ -380,21 +346,12 @@
     pub T
 );
 
-impl<'a, T, F : Float, const N : usize> Apply<&'a Loc<F, N>> for Normalised<T>
-where T : Norm<F, L1> + for<'b> Apply<&'b Loc<F, N>, Output=F> {
-    type Output = F;
+impl<'a, T, F : Float, const N : usize> Mapping<Loc<F, N>> for Normalised<T>
+where T : Norm<F, L1> + Mapping<Loc<F,N>, Codomain=F> {
+    type Codomain = F;
+
     #[inline]
-    fn apply(&self, x : &'a Loc<F, N>) -> Self::Output {
-        let w = self.0.norm(L1);
-        if w == F::ZERO { F::ZERO } else { self.0.apply(x) / w }
-    }
-}
-
-impl<'a, T, F : Float, const N : usize> Apply<Loc<F, N>> for Normalised<T>
-where T : Norm<F, L1> + Apply<Loc<F,N>, Output=F> {
-    type Output = F;
-    #[inline]
-    fn apply(&self, x : Loc<F, N>) -> Self::Output {
+    fn apply<I : Instance<Loc<F, N>>>(&self, x : I) -> Self::Codomain {
         let w = self.0.norm(L1);
         if w == F::ZERO { F::ZERO } else { self.0.apply(x) / w }
     }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/collection.rs	Tue Dec 31 08:30:02 2024 -0500
@@ -0,0 +1,54 @@
+/*!
+Abstract collections of objects.
+*/
+
+use crate::loc::Loc;
+
+/// An abstract collection of elements.
+pub trait Collection : IntoIterator<Item = Self::Element> {
+    /// Type of elements of the collection
+    type Element;
+    /// Iterator over references to elements of the collection
+    type RefsIter<'a> : Iterator<Item=&'a Self::Element> where Self : 'a;
+
+    /// Returns an iterator over references to elements of the collection.
+    fn iter_refs(&self) -> Self::RefsIter<'_>;
+}
+
+/// An abstract collection of mutable elements.
+pub trait CollectionMut : Collection {
+    /// Iterator over references to elements of the collection
+    type RefsIterMut<'a> : Iterator<Item=&'a mut Self::Element> where Self : 'a;
+
+    /// Returns an iterator over references to elements of the collection.
+    fn iter_refs_mut(&mut self) -> Self::RefsIterMut<'_>;
+}
+
+/// Helps implement Collection and CollectionMut for slice-like collections.
+#[macro_export]
+macro_rules! slice_like_collection {
+    ($type : ty where $($where:tt)*) => {
+        impl<$($where)*> Collection for $type {
+            type Element = E;
+            type RefsIter<'_a> = std::slice::Iter<'_a, E> where Self : '_a;
+
+            #[inline]
+            fn iter_refs(&self) -> Self::RefsIter<'_> {
+                self.iter()
+            }
+        }
+
+        impl<$($where)*> CollectionMut for $type {
+            type RefsIterMut<'_a> = std::slice::IterMut<'_a, E> where Self : '_a;
+
+            #[inline]
+            fn iter_refs_mut(&mut self) -> Self::RefsIterMut<'_> {
+                self.iter_mut()
+            }
+        }
+    }
+}
+
+slice_like_collection!(Vec<E> where E);
+slice_like_collection!([E; N] where E, const N : usize);
+slice_like_collection!(Loc<E, N> where E, const N : usize);
--- a/src/convex.rs	Sat Dec 14 09:31:27 2024 -0500
+++ b/src/convex.rs	Tue Dec 31 08:30:02 2024 -0500
@@ -2,20 +2,24 @@
 Some convex analysis basics
 */
 
-use crate::mapping::{Apply, Mapping};
+use crate::types::*;
+use crate::mapping::{Mapping, Space};
+use crate::instance::{Instance, InstanceMut, DecompositionMut};
+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<Domain> : Mapping<Domain> {}
+pub trait ConvexMapping<Domain : Space> : Mapping<Domain>
+{}
 
 /// 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<Domain> : Mapping<Domain> {
-    type DualDomain;
+pub trait Conjugable<Domain : Space> : Mapping<Domain> {
+    type DualDomain : Space;
     type Conjugate<'a> : ConvexMapping<Self::DualDomain> where Self : 'a;
 
     fn conjugate(&self) -> Self::Conjugate<'_>;
@@ -25,8 +29,8 @@
 ///
 /// In contrast to [`Conjugable`], the preconjugate need not implement [`ConvexMapping`],
 /// but a `Preconjugable` mapping has be convex.
-pub trait Preconjugable<Domain> : ConvexMapping<Domain> {
-    type PredualDomain;
+pub trait Preconjugable<Domain : Space> : ConvexMapping<Domain> {
+    type PredualDomain : Space;
     type Preconjugate<'a> : Mapping<Self::PredualDomain> where Self : 'a;
 
     fn preconjugate(&self) -> Self::Preconjugate<'_>;
@@ -36,15 +40,78 @@
 ///
 /// The conjugate type has to implement [`ConvexMapping`], but a `Conjugable` mapping need
 /// not be convex.
-pub trait HasProx<Domain> : Mapping<Domain> {
+pub trait Prox<Domain : Space> : Mapping<Domain> {
     type Prox<'a> : Mapping<Domain, Codomain=Domain> 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, z : Domain, τ : Self::Codomain) -> Domain {
+    fn prox<I : Instance<Domain>>(&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>,
+        Domain:: Decomp : DecompositionMut<Domain>,
+        for<'a> &'a Domain : Instance<Domain>,
+    {
+        *y = self.prox(τ, &*y);
+    }
 }
 
+
+pub struct NormConjugate<F : Float, E : NormExponent>(NormMapping<F, E>);
+
+impl<Domain, E, F> ConvexMapping<Domain> for NormMapping<F, E>
+where
+    Domain : Space,
+    E : NormExponent,
+    F : Float,
+    Self : Mapping<Domain, Codomain=F> {}
+
+
+impl<Domain, E, F> ConvexMapping<Domain> for NormConjugate<F, E>
+where
+    Domain : Space,
+    E : NormExponent,
+    F : Float,
+    Self : Mapping<Domain, Codomain=F> {}
+
+
+impl<F, E, Domain> Mapping<Domain> for NormConjugate<F, E>
+where
+    Domain : Space + Norm<F, E>,
+    F : Float,
+    E : NormExponent,
+{
+    type Codomain = F;
+
+    fn apply<I : Instance<Domain>>(&self, d : I) -> F {
+        if d.eval(|x| x.norm(self.0.exponent)) <= F::ONE {
+            F::ZERO
+        } else {
+            F::INFINITY
+        }
+    }
+}
+
+
+
+impl<E, F, Domain> Conjugable<Domain> for NormMapping<F, E>
+where
+    E : NormExponent + Clone,
+    F : Float,
+    Domain : Norm<F, E> + Space,
+{
+
+    type DualDomain = Domain;
+    type Conjugate<'a> = NormConjugate<F, E> where Self : 'a;
+
+    fn conjugate(&self) -> Self::Conjugate<'_> {
+        NormConjugate(self.clone())
+    }
+}
+
--- a/src/direct_product.rs	Sat Dec 14 09:31:27 2024 -0500
+++ b/src/direct_product.rs	Tue Dec 31 08:30:02 2024 -0500
@@ -9,65 +9,81 @@
 use std::clone::Clone;
 use serde::{Serialize, Deserialize};
 use crate::types::{Num, Float};
-use crate::{maybe_lifetime, maybe_ref, impl_vectorspace_ops};
+use crate::{maybe_lifetime, maybe_ref};
 use crate::euclidean::{Dot, Euclidean};
+use crate::instance::{Instance, InstanceMut, Decomposition, DecompositionMut, MyCow};
+use crate::mapping::Space;
+use crate::linops::AXPY;
+use crate::loc::Loc;
+use crate::norms::{Norm, PairNorm, NormExponent};
 
-#[derive(Debug,Clone,PartialEq,Eq,Serialize,Deserialize)]
+#[derive(Debug,Clone,Copy,PartialEq,Eq,Serialize,Deserialize)]
 pub struct Pair<A, B> (pub A, pub B);
 
 impl<A, B> Pair<A,B> {
-    pub fn new(a : A, b : B) -> Pair<A,B> { Pair{ 0 : a, 1 : b } }
+    pub fn new(a : A, b : B) -> Pair<A,B> { Pair(a, b) }
 }
 
 impl<A, B> From<(A,B)> for Pair<A,B> {
     #[inline]
-    fn from((a, b) : (A, B)) -> Pair<A,B> { Pair{ 0 : a, 1 : b } }
+    fn from((a, b) : (A, B)) -> Pair<A,B> { Pair(a, b) }
+}
+
+impl<A, B> From<Pair<A,B>> for (A,B) {
+    #[inline]
+    fn from(Pair(a, b) : Pair<A, B>) -> (A,B) { (a, b) }
 }
 
 macro_rules! impl_binop {
-    ($trait : ident, $fn : ident, $refl:ident, $refr:ident) => {
-        impl_binop!(@doit: $trait, $fn;
-                           maybe_lifetime!($refl, &'l Pair<A,B>),
-                               (maybe_lifetime!($refl, &'l A), maybe_lifetime!($refl, &'l B));
+    (($a : ty, $b : ty), $trait : ident, $fn : ident, $refl:ident, $refr:ident) => {
+        impl_binop!(@doit: $a, $b, $trait, $fn;
+                           maybe_lifetime!($refl, &'l Pair<$a,$b>),
+                           (maybe_lifetime!($refl, &'l $a),
+                            maybe_lifetime!($refl, &'l $b));
                            maybe_lifetime!($refr, &'r Pair<Ai,Bi>),
-                               (maybe_lifetime!($refr, &'r Ai), maybe_lifetime!($refr, &'r Bi));
+                           (maybe_lifetime!($refr, &'r Ai),
+                            maybe_lifetime!($refr, &'r Bi));
                            $refl, $refr);
     };
 
-    (@doit: $trait:ident, $fn:ident;
+    (@doit: $a:ty, $b:ty,
+            $trait:ident, $fn:ident;
             $self:ty, ($aself:ty, $bself:ty);
             $in:ty, ($ain:ty, $bin:ty);
             $refl:ident, $refr:ident) => {
-        impl<'l, 'r, A, B, Ai, Bi> $trait<$in>
+        impl<'l, 'r, Ai, Bi> $trait<$in>
         for $self
         where $aself: $trait<$ain>,
               $bself: $trait<$bin> {
             type Output = Pair<<$aself as $trait<$ain>>::Output,
-                                       <$bself as $trait<$bin>>::Output>;
+                               <$bself as $trait<$bin>>::Output>;
 
             #[inline]
             fn $fn(self, y : $in) -> Self::Output {
-                Pair { 0 : maybe_ref!($refl, self.0).$fn(maybe_ref!($refr, y.0)),
-                               1 : maybe_ref!($refl, self.1).$fn(maybe_ref!($refr, y.1)) }
+                Pair(maybe_ref!($refl, self.0).$fn(maybe_ref!($refr, y.0)),
+                     maybe_ref!($refl, self.1).$fn(maybe_ref!($refr, y.1)))
             }
         }
     };
 }
 
 macro_rules! impl_assignop {
-    ($trait : ident, $fn : ident, $refr:ident) => {
-        impl_assignop!(@doit: $trait, $fn;
+    (($a : ty, $b : ty), $trait : ident, $fn : ident, $refr:ident) => {
+        impl_assignop!(@doit: $a, $b,
+                              $trait, $fn;
                               maybe_lifetime!($refr, &'r Pair<Ai,Bi>),
-                                  (maybe_lifetime!($refr, &'r Ai), maybe_lifetime!($refr, &'r Bi));
+                              (maybe_lifetime!($refr, &'r Ai),
+                               maybe_lifetime!($refr, &'r Bi));
                               $refr);
     };
-    (@doit: $trait:ident, $fn:ident;
+    (@doit: $a : ty, $b : ty,
+            $trait:ident, $fn:ident;
             $in:ty, ($ain:ty, $bin:ty);
             $refr:ident) => {
-        impl<'r, A, B, Ai, Bi> $trait<$in>
-        for Pair<A,B>
-        where A: $trait<$ain>,
-              B: $trait<$bin> {
+        impl<'r, Ai, Bi> $trait<$in>
+        for Pair<$a,$b>
+        where $a: $trait<$ain>,
+              $b: $trait<$bin> {
             #[inline]
             fn $fn(&mut self, y : $in) -> () {
                 self.0.$fn(maybe_ref!($refr, y.0));
@@ -78,26 +94,29 @@
 }
 
 macro_rules! impl_scalarop {
-    ($trait : ident, $fn : ident, $refl:ident) => {
-        impl_scalarop!(@doit: $trait, $fn;
-                              maybe_lifetime!($refl, &'l Pair<A,B>),
-                                  (maybe_lifetime!($refl, &'l A), maybe_lifetime!($refl, &'l B));
+    (($a : ty, $b : ty), $field : ty, $trait : ident, $fn : ident, $refl:ident) => {
+        impl_scalarop!(@doit: $field,
+                              $trait, $fn;
+                              maybe_lifetime!($refl, &'l Pair<$a,$b>),
+                              (maybe_lifetime!($refl, &'l $a),
+                               maybe_lifetime!($refl, &'l $b));
                               $refl);
     };
-    (@doit: $trait:ident, $fn:ident;
+    (@doit: $field : ty,
+            $trait:ident, $fn:ident;
             $self:ty, ($aself:ty, $bself:ty);
             $refl:ident) => {
         // Scalar as Rhs
-        impl<'l, F : Num, A, B> $trait<F>
+        impl<'l> $trait<$field>
         for $self
-        where $aself: $trait<F>,
-              $bself: $trait<F> {
-            type Output = Pair<<$aself as $trait<F>>::Output,
-                                       <$bself as $trait<F>>::Output>;
+        where $aself: $trait<$field>,
+              $bself: $trait<$field> {
+            type Output = Pair<<$aself as $trait<$field>>::Output,
+                               <$bself as $trait<$field>>::Output>;
             #[inline]
-            fn $fn(self, a : F) -> Self::Output {
-                Pair{ 0 : maybe_ref!($refl, self.0).$fn(a),
-                              1 : maybe_ref!($refl, self.1).$fn(a)}
+            fn $fn(self, a : $field) -> Self::Output {
+                Pair(maybe_ref!($refl, self.0).$fn(a),
+                     maybe_ref!($refl, self.1).$fn(a))
             }
         }
     }
@@ -106,37 +125,38 @@
 // Not used due to compiler overflow
 #[allow(unused_macros)]
 macro_rules! impl_scalarlhs_op {
-    ($trait:ident, $fn:ident, $refr:ident, $field:ty) => {
-        impl_scalarlhs_op!(@doit: $trait, $fn, 
-                                  maybe_lifetime!($refr, &'r Pair<Ai,Bi>),
-                                  (maybe_lifetime!($refr, &'r Ai), maybe_lifetime!($refr, &'r Bi));
+    (($a : ty, $b : ty), $field : ty, $trait:ident, $fn:ident, $refr:ident) => {
+        impl_scalarlhs_op!(@doit: $trait, $fn,
+                                  maybe_lifetime!($refr, &'r Pair<$a,$b>),
+                                  (maybe_lifetime!($refr, &'r $a),
+                                   maybe_lifetime!($refr, &'r $b));
                                   $refr, $field);
     };
-    (@doit: $trait:ident, $fn:ident, 
+    (@doit: $trait:ident, $fn:ident,
             $in:ty, ($ain:ty, $bin:ty);
             $refr:ident, $field:ty) => {
-        impl<'r, Ai, Bi> $trait<$in>
+        impl<'r> $trait<$in>
         for $field
         where $field : $trait<$ain>
                      + $trait<$bin> {
             type Output = Pair<<$field as $trait<$ain>>::Output,
-                                       <$field as $trait<$bin>>::Output>;
+                               <$field as $trait<$bin>>::Output>;
             #[inline]
             fn $fn(self, x : $in) -> Self::Output {
-                Pair{ 0 : self.$fn(maybe_ref!($refr, x.0)),
-                              1 : self.$fn(maybe_ref!($refr, x.1))}
+                Pair(self.$fn(maybe_ref!($refr, x.0)),
+                     self.$fn(maybe_ref!($refr, x.1)))
             }
         }
     };
 }
 
 macro_rules! impl_scalar_assignop {
-    ($trait : ident, $fn : ident) => {
-        impl<'r, F : Num, A, B> $trait<F>
-        for Pair<A,B>
-        where A: $trait<F>, B: $trait<F> {
+    (($a : ty, $b : ty), $field : ty, $trait : ident, $fn : ident) => {
+        impl<'r> $trait<$field>
+        for Pair<$a, $b>
+        where $a: $trait<$field>, $b: $trait<$field> {
             #[inline]
-            fn $fn(&mut self, a : F) -> () {
+            fn $fn(&mut self, a : $field) -> () {
                 self.0.$fn(a);
                 self.1.$fn(a);
             }
@@ -145,34 +165,77 @@
 }
 
 macro_rules! impl_unaryop {
-    ($trait:ident, $fn:ident, $refl:ident) => {
+    (($a : ty, $b : ty), $trait:ident, $fn:ident, $refl:ident) => {
         impl_unaryop!(@doit: $trait, $fn;
-                             maybe_lifetime!($refl, &'l Pair<A,B>),
-                                 (maybe_lifetime!($refl, &'l A), maybe_lifetime!($refl, &'l B));
+                             maybe_lifetime!($refl, &'l Pair<$a,$b>),
+                             (maybe_lifetime!($refl, &'l $a),
+                              maybe_lifetime!($refl, &'l $b));
                              $refl);
     };
     (@doit: $trait:ident, $fn:ident;
             $self:ty, ($aself:ty, $bself:ty);
             $refl : ident) => {
-        impl<'l, A, B> $trait
+        impl<'l> $trait
         for $self
         where $aself: $trait,
               $bself: $trait {
             type Output = Pair<<$aself as $trait>::Output,
-                                       <$bself as $trait>::Output>;
+                               <$bself as $trait>::Output>;
             #[inline]
             fn $fn(self) -> Self::Output {
-                Pair{ 0 : maybe_ref!($refl, self.0).$fn(),
-                              1 : maybe_ref!($refl, self.1).$fn()}
+                Pair(maybe_ref!($refl, self.0).$fn(),
+                     maybe_ref!($refl, self.1).$fn())
             }
         }
     }
 }
 
+#[macro_export]
+macro_rules! impl_pair_vectorspace_ops {
+    (($a:ty, $b:ty), $field:ty) => {
+        impl_pair_vectorspace_ops!(@binary, ($a, $b), Add, add);
+        impl_pair_vectorspace_ops!(@binary, ($a, $b), Sub, sub);
+        impl_pair_vectorspace_ops!(@assign, ($a, $b), AddAssign, add_assign);
+        impl_pair_vectorspace_ops!(@assign, ($a, $b), SubAssign, sub_assign);
+        impl_pair_vectorspace_ops!(@scalar, ($a, $b), $field, Mul, mul);
+        impl_pair_vectorspace_ops!(@scalar, ($a, $b), $field, Div, div);
+        // Compiler overflow
+        // $(
+        //     impl_pair_vectorspace_ops!(@scalar_lhs, ($a, $b), $field, $impl_scalarlhs_op, Mul, mul);
+        // )*
+        impl_pair_vectorspace_ops!(@scalar_assign, ($a, $b), $field, MulAssign, mul_assign);
+        impl_pair_vectorspace_ops!(@scalar_assign, ($a, $b), $field, DivAssign, div_assign);
+        impl_pair_vectorspace_ops!(@unary, ($a, $b), Neg, neg);
+    };
+    (@binary, ($a : ty, $b : ty), $trait : ident, $fn : ident) => {
+        impl_binop!(($a, $b), $trait, $fn, ref, ref);
+        impl_binop!(($a, $b), $trait, $fn, ref, noref);
+        impl_binop!(($a, $b), $trait, $fn, noref, ref);
+        impl_binop!(($a, $b), $trait, $fn, noref, noref);
+    };
+    (@assign, ($a : ty, $b : ty), $trait : ident, $fn :ident) => {
+        impl_assignop!(($a, $b), $trait, $fn, ref);
+        impl_assignop!(($a, $b), $trait, $fn, noref);
+    };
+    (@scalar, ($a : ty, $b : ty), $field : ty, $trait : ident, $fn :ident) => {
+        impl_scalarop!(($a, $b), $field, $trait, $fn, ref);
+        impl_scalarop!(($a, $b), $field, $trait, $fn, noref);
+    };
+    (@scalar_lhs, ($a : ty, $b : ty), $field : ty, $trait : ident, $fn : ident) => {
+        impl_scalarlhs_op!(($a, $b), $field, $trait, $fn, ref);
+        impl_scalarlhs_op!(($a, $b), $field, $trait, $fn, noref);
+    };
+    (@scalar_assign, ($a : ty, $b : ty), $field : ty, $trait : ident, $fn : ident) => {
+        impl_scalar_assignop!(($a, $b), $field, $trait, $fn);
+    };
+    (@unary, ($a : ty, $b : ty), $trait : ident, $fn :  ident) => {
+        impl_unaryop!(($a, $b), $trait, $fn, ref);
+        impl_unaryop!(($a, $b), $trait, $fn, noref);
+    };
+}
 
-impl_vectorspace_ops!(impl_binop, impl_assignop, impl_scalarop, impl_scalarlhs_op,
-                      impl_scalar_assignop, impl_unaryop);
-
+impl_pair_vectorspace_ops!((f32, f32), f32);
+impl_pair_vectorspace_ops!((f64, f64), f64);
 
 impl<A, B, U, V, F> Dot<Pair<U, V>, F> for Pair<A, B>
 where
@@ -186,15 +249,28 @@
     }
 }
 
-impl<A, B, F> Euclidean<F>  for Pair<A, B>
+type PairOutput<F, A, B> = Pair<<A as Euclidean<F>>::Output, <B as Euclidean<F>>::Output>;
+
+impl<A, B, F> Euclidean<F> for Pair<A, B>
 where
     A : Euclidean<F>,
     B : Euclidean<F>,
-    F : Float
+    F : Float,
+    PairOutput<F, A, B> : Euclidean<F>,
+    Self : Sized + Dot<Self,F>
+          + Mul<F, Output=PairOutput<F, A, B>> + MulAssign<F>
+          + Div<F, Output=PairOutput<F, A, B>> + DivAssign<F>
+          + Add<Self, Output=PairOutput<F, A, B>>
+          + Sub<Self, Output=PairOutput<F, A, B>>
+          + for<'b> Add<&'b Self, Output=PairOutput<F, A, B>>
+          + for<'b> Sub<&'b Self, Output=PairOutput<F, A, B>>
+          + AddAssign<Self> + for<'b> AddAssign<&'b Self>
+          + SubAssign<Self> + for<'b> SubAssign<&'b Self>
+          + Neg<Output=PairOutput<F, A, B>>
 {
-    type Output = Pair<<A as Euclidean<F>>::Output, <B as Euclidean<F>>::Output>;
+    type Output = PairOutput<F, A, B>;
 
-    fn similar_origin(&self) -> <Self as Euclidean<F>>::Output {
+    fn similar_origin(&self) -> PairOutput<F, A, B> {
         Pair(self.0.similar_origin(), self.1.similar_origin())
     }
 
@@ -203,26 +279,192 @@
     }
 }
 
-use crate::linops::AXPY;
-
 impl<F, A, B, U, V> AXPY<F, Pair<U, V>> for Pair<A, B>
 where
+    U : Space,
+    V : Space,
     A : AXPY<F, U>,
     B : AXPY<F, V>,
     F : Num
 {
-    fn axpy(&mut self, α : F, x : &Pair<U, V>, β : F) {
-        self.0.axpy(α, &x.0, β);
-        self.1.axpy(α, &x.1, β);
+
+    fn axpy<I : Instance<Pair<U,V>>>(&mut self, α : F, x : I, β : F) {
+        let Pair(u, v) = x.decompose();
+        self.0.axpy(α, u, β);
+        self.1.axpy(α, v, β);
+    }
+
+    fn copy_from<I : Instance<Pair<U,V>>>(&mut self, x : I) {
+        let Pair(u, v) = x.decompose();
+        self.0.copy_from(u);
+        self.1.copy_from(v);
+    }
+
+    fn scale_from<I : Instance<Pair<U,V>>>(&mut self, α : F, x : I) {
+        let Pair(u, v) = x.decompose();
+        self.0.scale_from(α, u);
+        self.1.scale_from(α, v);
+    }
+}
+
+/// [`Decomposition`] for working with [`Pair`]s.
+#[derive(Copy, Clone, Debug)]
+pub struct PairDecomposition<D, Q>(D, Q);
+
+impl<A : Space, B : Space> Space for Pair<A, B> {
+    type Decomp = PairDecomposition<A::Decomp, B::Decomp>;
+}
+
+impl<A, B, D, Q> Decomposition<Pair<A, B>> for PairDecomposition<D,Q>
+where
+    A : Space,
+    B : Space,
+    D : Decomposition<A>,
+    Q : Decomposition<B>,
+{
+    type Decomposition<'b> = Pair<D::Decomposition<'b>, Q::Decomposition<'b>> where Pair<A, B> : 'b;
+    type Reference<'b> = Pair<D::Reference<'b>, Q::Reference<'b>> where Pair<A, B> : 'b;
+
+    #[inline]
+    fn lift<'b>(Pair(u, v) : Self::Reference<'b>) -> Self::Decomposition<'b> {
+        Pair(D::lift(u), Q::lift(v))
+    }
+}
+
+impl<A, B, U, V, D, Q> Instance<Pair<A, B>, PairDecomposition<D, Q>> for Pair<U, V>
+where
+    A : Space,
+    B : Space,
+    D : Decomposition<A>,
+    Q : Decomposition<B>,
+    U : Instance<A, D>,
+    V : Instance<B, Q>,
+{
+    #[inline]
+    fn decompose<'b>(self)
+        -> <PairDecomposition<D, Q> as Decomposition<Pair<A, B>>>::Decomposition<'b>
+    where Self : 'b, Pair<A, B> : 'b
+    {
+        Pair(self.0.decompose(), self.1.decompose())
+    }
+
+    #[inline]
+    fn ref_instance(&self)
+        -> <PairDecomposition<D, Q> as Decomposition<Pair<A, B>>>::Reference<'_>
+    {
+        Pair(self.0.ref_instance(), self.1.ref_instance())
+    }
+
+    #[inline]
+    fn cow<'b>(self) -> MyCow<'b, Pair<A, B>> where Self : 'b{
+        MyCow::Owned(Pair(self.0.own(), self.1.own()))
     }
 
-    fn copy_from(&mut self, x : &Pair<U, V>,) {
-        self.0.copy_from(&x.0);
-        self.1.copy_from(&x.1);
+    #[inline]
+    fn own(self) -> Pair<A, B> {
+        Pair(self.0.own(), self.1.own())
+    }
+}
+
+
+impl<'a, A, B, U, V, D, Q> Instance<Pair<A, B>, PairDecomposition<D, Q>> for &'a Pair<U, V>
+where
+    A : Space,
+    B : Space,
+    D : Decomposition<A>,
+    Q : Decomposition<B>,
+    U : Instance<A, D>,
+    V : Instance<B, Q>,
+    &'a U : Instance<A, D>,
+    &'a V : Instance<B, Q>,
+{
+    #[inline]
+    fn decompose<'b>(self)
+        -> <PairDecomposition<D, Q> as Decomposition<Pair<A, B>>>::Decomposition<'b>
+    where Self : 'b, Pair<A, B> : 'b
+    {
+        Pair(D::lift(self.0.ref_instance()), Q::lift(self.1.ref_instance()))
+    }
+
+    #[inline]
+    fn ref_instance(&self)
+        -> <PairDecomposition<D, Q> as Decomposition<Pair<A, B>>>::Reference<'_>
+    {
+        Pair(self.0.ref_instance(), self.1.ref_instance())
+    }
+
+    #[inline]
+    fn cow<'b>(self) -> MyCow<'b, Pair<A, B>> where Self : 'b {
+        MyCow::Owned(self.own())
+    }
+
+    #[inline]
+    fn own(self) -> Pair<A, B> {
+        let Pair(ref u, ref v) = self;
+        Pair(u.own(), v.own())
     }
 
-    fn scale_from(&mut self, α : F, x : &Pair<U, V>) {
-        self.0.scale_from(α, &x.0);
-        self.1.scale_from(α, &x.1);
+}
+
+impl<A, B, D, Q> DecompositionMut<Pair<A, B>> for PairDecomposition<D,Q>
+where
+    A : Space,
+    B : Space,
+    D : DecompositionMut<A>,
+    Q : DecompositionMut<B>,
+{
+    type ReferenceMut<'b> = Pair<D::ReferenceMut<'b>, Q::ReferenceMut<'b>> where Pair<A, B> : 'b;
+}
+
+impl<A, B, U, V, D, Q> InstanceMut<Pair<A, B>, PairDecomposition<D, Q>> for Pair<U, V>
+where
+    A : Space,
+    B : Space,
+    D : DecompositionMut<A>,
+    Q : DecompositionMut<B>,
+    U : InstanceMut<A, D>,
+    V : InstanceMut<B, Q>,
+{
+    #[inline]
+    fn ref_instance_mut(&mut self)
+        -> <PairDecomposition<D, Q> as DecompositionMut<Pair<A, B>>>::ReferenceMut<'_>
+    {
+        Pair(self.0.ref_instance_mut(), self.1.ref_instance_mut())
     }
-}
\ No newline at end of file
+}
+
+impl<'a, A, B, U, V, D, Q> InstanceMut<Pair<A, B>, PairDecomposition<D, Q>> for &'a mut Pair<U, V>
+where
+    A : Space,
+    B : Space,
+    D : DecompositionMut<A>,
+    Q : DecompositionMut<B>,
+    U : InstanceMut<A, D>,
+    V : InstanceMut<B, Q>,
+{
+    #[inline]
+    fn ref_instance_mut(&mut self)
+        -> <PairDecomposition<D, Q> as DecompositionMut<Pair<A, B>>>::ReferenceMut<'_>
+    {
+        Pair(self.0.ref_instance_mut(), self.1.ref_instance_mut())
+    }
+}
+
+
+impl<F, A, B, ExpA, ExpB, ExpJ> Norm<F, PairNorm<ExpA, ExpB, ExpJ>>
+for Pair<A,B>
+where
+    F : Num,
+    ExpA : NormExponent,
+    ExpB : NormExponent,
+    ExpJ : NormExponent,
+    A : Norm<F, ExpA>,
+    B : Norm<F, ExpB>,
+    Loc<F, 2> : Norm<F, ExpJ>,
+{
+    fn norm(&self, PairNorm(expa, expb, expj) : PairNorm<ExpA, ExpB, ExpJ>) -> F {
+        Loc([self.0.norm(expa), self.1.norm(expb)]).norm(expj)
+    }
+}
+
+
--- a/src/euclidean.rs	Sat Dec 14 09:31:27 2024 -0500
+++ b/src/euclidean.rs	Tue Dec 31 08:30:02 2024 -0500
@@ -2,8 +2,9 @@
 Euclidean spaces.
 */
 
+use std::ops::{Mul, MulAssign, Div, DivAssign, Add, Sub, AddAssign, SubAssign, Neg};
 use crate::types::*;
-use std::ops::{Mul, MulAssign, Div, DivAssign, Add, Sub, AddAssign, SubAssign, Neg};
+use crate::mapping::Space;
 
 /// Space (type) with a defined dot product.
 ///
@@ -18,7 +19,7 @@
 /// The type should implement vector space operations (addition, subtraction, scalar
 /// multiplication and scalar division) along with their assignment versions, as well
 /// as the [`Dot`] product with respect to `Self`.
-pub trait Euclidean<F : Float> : Sized + Dot<Self,F>
+pub trait Euclidean<F : Float> : Space + Dot<Self,F>
         + Mul<F, Output=<Self as Euclidean<F>>::Output> + MulAssign<F>
         + Div<F, Output=<Self as Euclidean<F>>::Output> + DivAssign<F>
         + Add<Self, Output=<Self as Euclidean<F>>::Output>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/instance.rs	Tue Dec 31 08:30:02 2024 -0500
@@ -0,0 +1,282 @@
+/*!
+Helper traits to work with references or owned values of types and their subsets.
+*/
+
+#[derive(Clone, Copy)]
+pub enum EitherDecomp<A, B> {
+    Owned(A),
+    Borrowed(B),
+}
+
+/// A very basic implementation of [`Cow`] without a [`Clone`] trait dependency.
+pub type MyCow<'b, X> = EitherDecomp<X, &'b X>;
+
+impl<'b, X> std::ops::Deref for MyCow<'b, X> {
+    type Target = X;
+
+    #[inline]
+    fn deref(&self) -> &Self::Target {
+        match self {
+            EitherDecomp::Owned(x) => &x,
+            EitherDecomp::Borrowed(x) => x,
+        }
+    }
+}
+
+impl<'b, X> MyCow<'b, X> {
+    #[inline]
+    pub fn into_owned(self) -> X where X : Clone {
+        match self {
+            EitherDecomp::Owned(x) => x,
+            EitherDecomp::Borrowed(x) => x.clone(),
+        }
+    }
+}
+
+/// Trait for abitrary mathematical spaces.
+pub trait Space : Instance<Self, Self::Decomp> {
+    /// Default decomposition for the space
+    type Decomp : Decomposition<Self>;
+}
+
+#[macro_export]
+macro_rules! impl_basic_space {
+    ($($type:ty)*) => { $(
+        impl $crate::instance::Space for $type {
+            type Decomp = $crate::instance::BasicDecomposition;
+        }
+    )* };
+    ($type:ty where $($where:tt)*) => {
+        impl<$($where)*> $crate::instance::Space for $type {
+            type Decomp = $crate::instance::BasicDecomposition;
+        }
+    };
+}
+
+impl_basic_space!(u8 u16 u32 u64 u128 usize
+                  i8 i16 i32 i64 i128 isize
+                  f32 f64);
+
+/// Marker type for decompositions to be used with [`Instance`].
+pub trait Decomposition<X : Space> : Sized {
+    /// Possibly owned form of the decomposition
+    type Decomposition<'b> : Instance<X, Self> where X : 'b;
+    /// Unlikely owned form of the decomposition.
+    /// Type for a lightweight intermediate conversion that does not own the original variable.
+    /// Usually this is just a reference, but may also be a lightweight structure that
+    /// contains references; see the implementation for [`crate::direct_product::Pair`].
+    type Reference<'b> : Instance<X, Self> + Copy where X : 'b;
+
+    /// Left the lightweight reference type into a full decomposition type.
+    fn lift<'b>(r : Self::Reference<'b>) -> Self::Decomposition<'b>;
+}
+
+/// Most common [`Decomposition`] (into `Either<X, &'b X>`) that allows working with owned
+/// values and all sorts of references.
+#[derive(Copy, Clone, Debug)]
+pub struct BasicDecomposition;
+
+impl<X : Space + Clone> Decomposition<X> for BasicDecomposition {
+    type Decomposition<'b> = MyCow<'b, X> where X : 'b;
+    type Reference<'b> = &'b X where X : 'b;
+
+    #[inline]
+    fn lift<'b>(r : Self::Reference<'b>) -> Self::Decomposition<'b> {
+        MyCow::Borrowed(r)
+    }
+}
+
+/// Helper trait for functions to work with either owned values or references to either the
+/// “principal type” `X` or types some present a subset of `X`. In the latter sense, this
+/// generalises [`std::borrow::ToOwned`], [`std::borrow::Borrow`], and [`std::borrow::Cow`].
+/// This type also includes iteratation facilities when `X` is a [`Collection`], to avoid
+/// the possibly costly conversion of such subset types into `X`.
+pub trait Instance<X : Space, D = <X as Space>::Decomp> : Sized where D : Decomposition<X> {
+    /// Decomposes self according to `decomposer`.
+    fn decompose<'b>(self) -> D::Decomposition<'b>
+    where Self : 'b, X : 'b;
+  
+    /// Returns a lightweight instance of `self`.
+    fn ref_instance(&self) -> D::Reference<'_>;
+
+    /// Returns an owned instance of `X`, cloning or converting non-true instances when necessary.
+    fn own(self) -> X;
+
+    // ************** automatically implemented methods below from here **************
+
+    /// Returns an owned instance or reference to `X`, converting non-true instances when necessary.
+    ///
+    /// Default implementation uses [`Self::own`]. Consumes the input.
+    fn cow<'b>(self) -> MyCow<'b, X> where Self : 'b {
+        MyCow::Owned(self.own())
+    }
+    
+    #[inline]
+    /// Evaluates `f` on a reference to self.
+    ///
+    /// Default implementation uses [`Self::cow`]. Consumes the input.
+    fn eval<'b, R>(self, f : impl FnOnce(&X) -> R) -> R
+    where X : 'b, Self : 'b
+    {
+        f(&*self.cow())
+    }
+
+    #[inline]
+    /// Evaluates `f` or `g` depending on whether a reference or owned value is available.
+    ///
+    /// Default implementation uses [`Self::cow`]. Consumes the input.
+    fn either<'b, R>(
+        self,
+        f : impl FnOnce(X) -> R,
+        g : impl FnOnce(&X) -> R
+    ) -> R
+    where Self : 'b
+    {
+        match self.cow() {
+            EitherDecomp::Owned(x) => f(x),
+            EitherDecomp::Borrowed(x) => g(x),
+        }
+    }
+}
+
+
+impl<X : Space + Clone> Instance<X, BasicDecomposition> for X {
+    #[inline]
+    fn decompose<'b>(self) -> <BasicDecomposition as Decomposition<X>>::Decomposition<'b>
+    where Self : 'b, X : 'b
+    {
+        MyCow::Owned(self)
+    }
+
+    #[inline]
+    fn own(self) -> X {
+        self
+    }
+
+    #[inline]
+    fn cow<'b>(self) -> MyCow<'b, X> where Self : 'b {
+        MyCow::Owned(self)
+    }
+
+    #[inline]
+    fn ref_instance(&self) -> <BasicDecomposition as Decomposition<X>>::Reference<'_> {
+        self
+    }
+}
+
+impl<'a, X : Space + Clone> Instance<X, BasicDecomposition> for &'a X {
+    #[inline]
+    fn decompose<'b>(self) -> <BasicDecomposition as Decomposition<X>>::Decomposition<'b>
+    where Self : 'b, X : 'b
+    {
+        MyCow::Borrowed(self)
+    }
+
+    #[inline]
+    fn own(self) -> X {
+        self.clone()
+    }
+
+    #[inline]
+    fn cow<'b>(self) -> MyCow<'b, X> where Self : 'b {
+        MyCow::Borrowed(self)
+    }
+
+    #[inline]
+    fn ref_instance(&self) -> <BasicDecomposition as Decomposition<X>>::Reference<'_> {
+        *self
+    }
+}
+
+impl<'a, X : Space + Clone> Instance<X, BasicDecomposition> for &'a mut X {
+    #[inline]
+    fn  decompose<'b>(self) -> <BasicDecomposition as Decomposition<X>>::Decomposition<'b>
+    where Self : 'b, X : 'b
+    {
+        EitherDecomp::Borrowed(self)
+    }
+
+    #[inline]
+    fn own(self) -> X {
+        self.clone()
+    }
+
+    #[inline]
+    fn cow<'b>(self) -> MyCow<'b, X> where Self : 'b, X : Clone {
+        EitherDecomp::Borrowed(self)
+    }
+
+    #[inline]
+    fn ref_instance(&self) -> <BasicDecomposition as Decomposition<X>>::Reference<'_> {
+        *self
+    }
+}
+
+impl<'a, X : Space + Clone> Instance<X, BasicDecomposition> for MyCow<'a, X> {
+
+    #[inline]
+    fn  decompose<'b>(self) -> <BasicDecomposition as Decomposition<X>>::Decomposition<'b>
+    where Self : 'b, X : 'b
+    {
+        self
+    }
+
+    #[inline]
+    fn own(self) -> X {
+        match self {
+            MyCow::Borrowed(a) => a.own(),
+            MyCow::Owned(b) => b.own()
+        }
+    }
+
+    #[inline]
+    fn cow<'b>(self) -> MyCow<'b, X> where Self : 'b {
+        match self {
+            MyCow::Borrowed(a) => a.cow(),
+            MyCow::Owned(b) => b.cow()
+        }
+    }
+
+    #[inline]
+    fn ref_instance(&self) -> <BasicDecomposition as Decomposition<X>>::Reference<'_> {
+        match self {
+            MyCow::Borrowed(a) => a,
+            MyCow::Owned(b) => &b,
+        }
+    }
+}
+
+/// Marker type for mutable decompositions to be used with [`InstanceMut`].
+pub trait DecompositionMut<X : Space> : Sized {
+    type ReferenceMut<'b> : InstanceMut<X, Self> where X : 'b;
+}
+
+
+/// Helper trait for functions to work with mutable references.
+pub trait InstanceMut<X : Space , D = <X as Space>::Decomp> : Sized where D : DecompositionMut<X> {
+    /// Returns a mutable decomposition of self.
+    fn ref_instance_mut(&mut self) -> D::ReferenceMut<'_>;
+}
+
+impl<X : Space> DecompositionMut<X> for BasicDecomposition {
+    type ReferenceMut<'b> = &'b mut X where X : 'b;
+}
+
+/// This impl may seem pointless, but allows throwaway mutable scratch variables
+impl<'a, X : Space> InstanceMut<X, BasicDecomposition> for X {
+    #[inline]
+    fn ref_instance_mut(&mut self)
+        -> <BasicDecomposition as DecompositionMut<X>>::ReferenceMut<'_>
+    {
+        self
+    }
+}
+
+impl<'a, X : Space> InstanceMut<X, BasicDecomposition> for &'a mut X {
+    #[inline]
+    fn ref_instance_mut(&mut self)
+        -> <BasicDecomposition as DecompositionMut<X>>::ReferenceMut<'_>
+    {
+        self
+    }
+}
--- a/src/lib.rs	Sat Dec 14 09:31:27 2024 -0500
+++ b/src/lib.rs	Tue Dec 31 08:30:02 2024 -0500
@@ -11,13 +11,12 @@
     feature(maybe_uninit_uninit_array,maybe_uninit_array_assume_init,maybe_uninit_slice),
     feature(float_minimum_maximum),
     feature(get_mut_unchecked),
+    feature(cow_is_borrowed),
 )]
 
-// They don't work:
-//#![feature(negative_impls)]
-//#![feature(specialization)]
-
 pub mod types;
+pub mod instance;
+pub mod collection;
 pub mod nanleast;
 pub mod error;
 pub mod parallelism;
--- a/src/linops.rs	Sat Dec 14 09:31:27 2024 -0500
+++ b/src/linops.rs	Tue Dec 31 08:30:02 2024 -0500
@@ -4,59 +4,69 @@
 
 use numeric_literals::replace_float_literals;
 use std::marker::PhantomData;
+use serde::Serialize;
 use crate::types::*;
-use serde::Serialize;
-pub use crate::mapping::Apply;
+pub use crate::mapping::{Mapping, Space};
 use crate::direct_product::Pair;
+use crate::instance::Instance;
+use crate::norms::{NormExponent, PairNorm, L1, L2, Linfinity};
 
 /// Trait for linear operators on `X`.
-pub trait Linear<X> : Apply<X, Output=Self::Codomain>
-                      + for<'a> Apply<&'a X, Output=Self::Codomain> {
-    type Codomain;
-}
+pub trait Linear<X : Space> : Mapping<X>
+{ }
 
 /// Efficient in-place summation.
 #[replace_float_literals(F::cast_from(literal))]
-pub trait AXPY<F : Num, X = Self> {
+pub trait AXPY<F, X = Self> : Space
+where
+    F : Num,
+    X : Space,
+{
     /// Computes  `y = βy + αx`, where `y` is `Self`.
-    fn axpy(&mut self, α : F, x : &X, β : F);
+    fn axpy<I : Instance<X>>(&mut self, α : F, x : I, β : F);
 
     /// Copies `x` to `self`.
-    fn copy_from(&mut self, x : &X) {
+    fn copy_from<I : Instance<X>>(&mut self, x : I) {
         self.axpy(1.0, x, 0.0)
     }
 
     /// Computes  `y = αx`, where `y` is `Self`.
-    fn scale_from(&mut self, α : F, x : &X) {
+    fn scale_from<I : Instance<X>>(&mut self, α : F, x : I) {
         self.axpy(α, x, 0.0)
     }
 }
 
 /// Efficient in-place application for [`Linear`] operators.
 #[replace_float_literals(F::cast_from(literal))]
-pub trait GEMV<F : Num, X, Y = <Self as Linear<X>>::Codomain> : Linear<X> {
+pub trait GEMV<F : Num, X : Space, Y = <Self as Mapping<X>>::Codomain> : Linear<X> {
     /// Computes  `y = αAx + βy`, where `A` is `Self`.
-    fn gemv(&self, y : &mut Y, α : F, x : &X, β : F);
+    fn gemv<I : Instance<X>>(&self, y : &mut Y, α : F, x : I, β : F);
 
     /// Computes `y = Ax`, where `A` is `Self`
-    fn apply_mut(&self, y : &mut Y, x : &X){
+    fn apply_mut<I : Instance<X>>(&self, y : &mut Y, x : I){
         self.gemv(y, 1.0, x, 0.0)
     }
 
     /// Computes `y += Ax`, where `A` is `Self`
-    fn apply_add(&self, y : &mut Y, x : &X){
+    fn apply_add<I : Instance<X>>(&self, y : &mut Y, x : I){
         self.gemv(y, 1.0, x, 1.0)
     }
 }
 
 
 /// Bounded linear operators
-pub trait BoundedLinear<X> : Linear<X> {
-    type FloatType : Float;
+pub trait BoundedLinear<X, XExp, CodExp, F = f64> : Linear<X>
+where
+    F : Num,
+    X : Space,
+    XExp : NormExponent,
+    CodExp : NormExponent
+{
     /// A bound on the operator norm $\|A\|$ for the linear operator $A$=`self`.
     /// This is not expected to be the norm, just any bound on it that can be
-    /// reasonably implemented.
-    fn opnorm_bound(&self) -> Self::FloatType;
+    /// reasonably implemented. The [`NormExponent`] `xexp`  indicates the norm
+    /// in `X`, and `codexp` in the codomain.
+    fn opnorm_bound(&self, xexp : XExp, codexp : CodExp) -> F;
 }
 
 // Linear operator application into mutable target. The [`AsRef`] bound
@@ -70,16 +80,16 @@
 }*/
 
 /// Trait for forming the adjoint operator of `Self`.
-pub trait Adjointable<X,Yʹ> : Linear<X> {
-    type AdjointCodomain;
+pub trait Adjointable<X, Yʹ> : Linear<X>
+where
+    X : Space,
+    Yʹ : Space,
+{
+    type AdjointCodomain : Space;
     type Adjoint<'a> : Linear<Yʹ, Codomain=Self::AdjointCodomain> where Self : 'a;
 
     /// Form the adjoint operator of `self`.
     fn adjoint(&self) -> Self::Adjoint<'_>;
-
-    /*fn adjoint_apply(&self, y : &Yʹ) -> Self::AdjointCodomain {
-        self.adjoint().apply(y)
-    }*/
 }
 
 /// Trait for forming a preadjoint of an operator.
@@ -89,189 +99,206 @@
 /// operator. The space `Ypre` is the predual of its codomain, and should be the
 /// domain of the adjointed operator. `Self::Preadjoint` should be
 /// [`Adjointable`]`<'a,Ypre,X>`.
-pub trait Preadjointable<X,Ypre> : Linear<X> {
-    type PreadjointCodomain;
-    type Preadjoint<'a> : Adjointable<Ypre, X, Codomain=Self::PreadjointCodomain> where Self : 'a;
 
-    /// Form the preadjoint operator of `self`.
+pub trait Preadjointable<X : Space, Ypre : Space> : Linear<X> {
+    type PreadjointCodomain : Space;
+    type Preadjoint<'a> : Adjointable<
+        Ypre, X, AdjointCodomain=Self::Codomain, Codomain=Self::PreadjointCodomain
+    > where Self : 'a;
+
+    /// Form the adjoint operator of `self`.
     fn preadjoint(&self) -> Self::Preadjoint<'_>;
 }
 
 /// Adjointable operators $A: X → Y$ between reflexive spaces $X$ and $Y$.
-pub trait SimplyAdjointable<X> : Adjointable<X,<Self as Linear<X>>::Codomain> {}
-impl<'a,X,T> SimplyAdjointable<X> for T where T : Adjointable<X,<Self as Linear<X>>::Codomain> {}
+pub trait SimplyAdjointable<X : Space> : Adjointable<X,<Self as Mapping<X>>::Codomain> {}
+impl<'a,X : Space, T> SimplyAdjointable<X> for T
+where T : Adjointable<X,<Self as Mapping<X>>::Codomain> {}
 
 /// The identity operator
 #[derive(Clone,Copy,Debug,Serialize,Eq,PartialEq)]
 pub struct IdOp<X> (PhantomData<X>);
 
 impl<X> IdOp<X> {
-    fn new() -> IdOp<X> { IdOp(PhantomData) }
+    pub fn new() -> IdOp<X> { IdOp(PhantomData) }
 }
 
-impl<X> Apply<X> for IdOp<X> {
-    type Output = X;
+impl<X : Clone + Space> Mapping<X> for IdOp<X> {
+    type Codomain = X;
 
-    fn apply(&self, x : X) -> X {
-        x
+    fn apply<I : Instance<X>>(&self, x : I) -> X {
+        x.own()
     }
 }
 
-impl<'a, X> Apply<&'a X> for IdOp<X> where X : Clone {
-    type Output = X;
-    
-    fn apply(&self, x : &'a X) -> X {
-        x.clone()
-    }
-}
-
-impl<X> Linear<X> for IdOp<X> where X : Clone {
-    type Codomain = X;
-}
-
+impl<X : Clone + 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<F, X>, X : Clone {
+impl<F : Num, X, Y> GEMV<F, X, Y> for IdOp<X>
+where
+    Y : AXPY<F, X>,
+    X : Clone + Space
+{
     // Computes  `y = αAx + βy`, where `A` is `Self`.
-    fn gemv(&self, y : &mut Y, α : F, x : &X, β : F) {
+    fn gemv<I : Instance<X>>(&self, y : &mut Y, α : F, x : I, β : F) {
         y.axpy(α, x, β)
     }
 
-    fn apply_mut(&self, y : &mut Y, x : &X){
+    fn apply_mut<I : Instance<X>>(&self, y : &mut Y, x : I){
         y.copy_from(x);
     }
 }
 
-impl<X> BoundedLinear<X> for IdOp<X> where X : Clone {
-    type FloatType = float;
-    fn opnorm_bound(&self) -> float { 1.0 }
+impl<F, X, E> BoundedLinear<X, E, E, F> for IdOp<X>
+where
+    X : Space + Clone,
+    F : Num,
+    E : NormExponent
+{
+    fn opnorm_bound(&self, _xexp : E, _codexp : E) -> F { F::ONE }
 }
 
-impl<X> Adjointable<X,X> for IdOp<X> where X : Clone {
+impl<X : Clone + Space> Adjointable<X,X> for IdOp<X> {
     type AdjointCodomain=X;
     type Adjoint<'a> = IdOp<X> where X : 'a;
+
     fn adjoint(&self) -> Self::Adjoint<'_> { IdOp::new() }
 }
 
+impl<X : Clone + Space> Preadjointable<X,X> for IdOp<X> {
+    type PreadjointCodomain=X;
+    type Preadjoint<'a> = IdOp<X> where X : 'a;
+
+    fn preadjoint(&self) -> Self::Preadjoint<'_> { IdOp::new() }
+}
+
 /// “Row operator” $(S, T)$; $(S, T)(x, y)=Sx + Ty$.
 pub struct RowOp<S, T>(pub S, pub T);
 
 use std::ops::Add;
 
-impl<A, B, S, T> Apply<Pair<A, B>> for RowOp<S, T>
+impl<A, B, S, T> Mapping<Pair<A, B>> for RowOp<S, T>
 where
-    S : Apply<A>,
-    T : Apply<B>,
-    S::Output : Add<T::Output>
+    A : Space,
+    B : Space,
+    S : Mapping<A>,
+    T : Mapping<B>,
+    S::Codomain : Add<T::Codomain>,
+    <S::Codomain as Add<T::Codomain>>::Output : Space,
+
 {
-    type Output = <S::Output as Add<T::Output>>::Output;
+    type Codomain = <S::Codomain as Add<T::Codomain>>::Output;
 
-    fn apply(&self, Pair(a, b) : Pair<A, B>) -> Self::Output {
+    fn apply<I : Instance<Pair<A, B>>>(&self, x : I) -> Self::Codomain {
+        let Pair(a, b) = x.decompose();
         self.0.apply(a) + self.1.apply(b)
     }
 }
 
-impl<'a, A, B, S, T> Apply<&'a Pair<A, B>> for RowOp<S, T>
+impl<A, B, S, T> Linear<Pair<A, B>> for RowOp<S, T>
 where
-    S : Apply<&'a A>,
-    T : Apply<&'a B>,
-    S::Output : Add<T::Output>
-{
-    type Output = <S::Output as Add<T::Output>>::Output;
+    A : Space,
+    B : Space,
+    S : Linear<A>,
+    T : Linear<B>,
+    S::Codomain : Add<T::Codomain>,
+    <S::Codomain as Add<T::Codomain>>::Output : Space,
+{ }
 
-    fn apply(&self, Pair(ref a, ref b) : &'a Pair<A, B>) -> Self::Output {
-        self.0.apply(a) + self.1.apply(b)
-    }
-}
 
-impl<A, B, S, T, D> Linear<Pair<A, B>> for RowOp<S, T>
+impl<'b, F, S, T, Y, U, V> GEMV<F, Pair<U, V>, Y> for RowOp<S, T>
 where
-    RowOp<S, T> : Apply<Pair<A, B>, Output=D> + for<'a>  Apply<&'a Pair<A, B>, Output=D>,
-{
-    type Codomain = D;
-}
-
-impl<F, S, T, Y, U, V> GEMV<F, Pair<U, V>, Y> for RowOp<S, T>
-where
+    U : Space,
+    V : Space,
     S : GEMV<F, U, Y>,
     T : GEMV<F, V, Y>,
     F : Num,
     Self : Linear<Pair<U, V>, Codomain=Y>
 {
-    fn gemv(&self, y : &mut Y, α : F, x : &Pair<U, V>, β : F) {
-        self.0.gemv(y, α, &x.0, β);
-        self.1.gemv(y, α, &x.1, β);
+    fn gemv<I : Instance<Pair<U, V>>>(&self, y : &mut Y, α : F, x : I, β : F) {
+        let Pair(u, v) = x.decompose();
+        self.0.gemv(y, α, u, β);
+        self.1.gemv(y, α, v, F::ONE);
     }
 
-    fn apply_mut(&self, y : &mut Y, x : &Pair<U, V>){
-        self.0.apply_mut(y, &x.0);
-        self.1.apply_mut(y, &x.1);
+    fn apply_mut<I : Instance<Pair<U, V>>>(&self, y : &mut Y, x : I) {
+        let Pair(u, v) = x.decompose();
+        self.0.apply_mut(y, u);
+        self.1.apply_mut(y, v);
     }
 
     /// Computes `y += Ax`, where `A` is `Self`
-    fn apply_add(&self, y : &mut Y, x : &Pair<U, V>){
-        self.0.apply_add(y, &x.0);
-        self.1.apply_add(y, &x.1);
+    fn apply_add<I : Instance<Pair<U, V>>>(&self, y : &mut Y, x : I) {
+        let Pair(u, v) = x.decompose();
+        self.0.apply_add(y, u);
+        self.1.apply_add(y, v);
     }
 }
 
-
 /// “Column operator” $(S; T)$; $(S; T)x=(Sx, Tx)$.
 pub struct ColOp<S, T>(pub S, pub T);
 
-impl<A, S, T, O> Apply<A> for ColOp<S, T>
+impl<A, S, T> Mapping<A> for ColOp<S, T>
 where
-    S : for<'a> Apply<&'a A, Output=O>,
-    T : Apply<A>,
-    A : std::borrow::Borrow<A>,
+    A : Space,
+    S : Mapping<A>,
+    T : Mapping<A>,
 {
-    type Output = Pair<O, T::Output>;
+    type Codomain = Pair<S::Codomain, T::Codomain>;
 
-    fn apply(&self, a : A) -> Self::Output {
-        Pair(self.0.apply(a.borrow()), self.1.apply(a))
+    fn apply<I : Instance<A>>(&self, a : I) -> Self::Codomain {
+        Pair(self.0.apply(a.ref_instance()), self.1.apply(a))
     }
 }
 
-impl<A, S, T, D> Linear<A> for ColOp<S, T>
+impl<A, S, T> Linear<A> for ColOp<S, T>
 where
-    ColOp<S, T> : Apply<A, Output=D> + for<'a>  Apply<&'a A, Output=D>,
-{
-    type Codomain = D;
-}
+    A : Space,
+    S : Mapping<A>,
+    T : Mapping<A>,
+{ }
 
 impl<F, S, T, A, B, X> GEMV<F, X, Pair<A, B>> for ColOp<S, T>
 where
+    X : Space,
     S : GEMV<F, X, A>,
     T : GEMV<F, X, B>,
     F : Num,
     Self : Linear<X, Codomain=Pair<A, B>>
 {
-    fn gemv(&self, y : &mut Pair<A, B>, α : F, x : &X, β : F) {
-        self.0.gemv(&mut y.0, α, x, β);
+    fn gemv<I : Instance<X>>(&self, y : &mut Pair<A, B>, α : F, x : I, β : F) {
+        self.0.gemv(&mut y.0, α, x.ref_instance(), β);
         self.1.gemv(&mut y.1, α, x, β);
     }
 
-    fn apply_mut(&self, y : &mut Pair<A, B>, x : &X){
-        self.0.apply_mut(&mut y.0, x);
+    fn apply_mut<I : Instance<X>>(&self, y : &mut Pair<A, B>, x : I){
+        self.0.apply_mut(&mut y.0, x.ref_instance());
         self.1.apply_mut(&mut y.1, x);
     }
 
     /// Computes `y += Ax`, where `A` is `Self`
-    fn apply_add(&self, y : &mut Pair<A, B>, x : &X){
-        self.0.apply_add(&mut y.0, x);
+    fn apply_add<I : Instance<X>>(&self, y : &mut Pair<A, B>, x : I){
+        self.0.apply_add(&mut y.0, x.ref_instance());
         self.1.apply_add(&mut y.1, x);
     }
 }
 
 
-impl<A, B, Yʹ, R, S, T> Adjointable<Pair<A,B>,Yʹ> for RowOp<S, T>
+impl<A, B, Yʹ, S, T> Adjointable<Pair<A,B>, Yʹ> for RowOp<S, T>
 where
+    A : Space,
+    B : Space,
+    Yʹ : Space,
     S : Adjointable<A, Yʹ>,
     T : Adjointable<B, Yʹ>,
     Self : Linear<Pair<A, B>>,
-    for<'a> ColOp<S::Adjoint<'a>, T::Adjoint<'a>> : Linear<Yʹ, Codomain=R>,
+    // for<'a> ColOp<S::Adjoint<'a>, T::Adjoint<'a>> : Linear<
+    //     Yʹ,
+    //     Codomain=Pair<S::AdjointCodomain, T::AdjointCodomain>
+    // >,
 {
-    type AdjointCodomain = R;
+    type AdjointCodomain = Pair<S::AdjointCodomain, T::AdjointCodomain>;
     type Adjoint<'a> = ColOp<S::Adjoint<'a>, T::Adjoint<'a>> where Self : 'a;
 
     fn adjoint(&self) -> Self::Adjoint<'_> {
@@ -279,14 +306,21 @@
     }
 }
 
-impl<A, B, Yʹ, R, S, T> Preadjointable<Pair<A,B>,Yʹ> for RowOp<S, T>
+impl<A, B, Yʹ, S, T> Preadjointable<Pair<A,B>, Yʹ> for RowOp<S, T>
 where
+    A : Space,
+    B : Space,
+    Yʹ : Space,
     S : Preadjointable<A, Yʹ>,
     T : Preadjointable<B, Yʹ>,
     Self : Linear<Pair<A, B>>,
-    for<'a> ColOp<S::Preadjoint<'a>, T::Preadjoint<'a>> : Adjointable<Yʹ, Pair<A,B>, Codomain=R>,
+    for<'a> ColOp<S::Preadjoint<'a>, T::Preadjoint<'a>> : Adjointable<
+        Yʹ, Pair<A,B>,
+        Codomain=Pair<S::PreadjointCodomain, T::PreadjointCodomain>,
+        AdjointCodomain = Self::Codomain,
+    >,
 {
-    type PreadjointCodomain = R;
+    type PreadjointCodomain = Pair<S::PreadjointCodomain, T::PreadjointCodomain>;
     type Preadjoint<'a> = ColOp<S::Preadjoint<'a>, T::Preadjoint<'a>> where Self : 'a;
 
     fn preadjoint(&self) -> Self::Preadjoint<'_> {
@@ -297,10 +331,17 @@
 
 impl<A, Xʹ, Yʹ, R, S, T> Adjointable<A,Pair<Xʹ,Yʹ>> for ColOp<S, T>
 where
-    S : Adjointable<A, Xʹ>,
-    T : Adjointable<A, Yʹ>,
+    A : Space,
+    Xʹ : Space,
+    Yʹ : Space,
+    R : Space + ClosedAdd,
+    S : Adjointable<A, Xʹ, AdjointCodomain = R>,
+    T : Adjointable<A, Yʹ, AdjointCodomain = R>,
     Self : Linear<A>,
-    for<'a> RowOp<S::Adjoint<'a>, T::Adjoint<'a>> : Linear<Pair<Xʹ,Yʹ>, Codomain=R>,
+    // for<'a> RowOp<S::Adjoint<'a>, T::Adjoint<'a>> : Linear<
+    //     Pair<Xʹ,Yʹ>,
+    //     Codomain=R,
+    // >,
 {
     type AdjointCodomain = R;
     type Adjoint<'a> = RowOp<S::Adjoint<'a>, T::Adjoint<'a>> where Self : 'a;
@@ -312,10 +353,18 @@
 
 impl<A, Xʹ, Yʹ, R, S, T> Preadjointable<A,Pair<Xʹ,Yʹ>> for ColOp<S, T>
 where
-    S : Preadjointable<A, Xʹ>,
-    T : Preadjointable<A, Yʹ>,
+    A : Space,
+    Xʹ : Space,
+    Yʹ : Space,
+    R : Space + ClosedAdd,
+    S : Preadjointable<A, Xʹ, PreadjointCodomain = R>,
+    T : Preadjointable<A, Yʹ, PreadjointCodomain = R>,
     Self : Linear<A>,
-    for<'a> RowOp<S::Preadjoint<'a>, T::Preadjoint<'a>> : Adjointable<Pair<Xʹ,Yʹ>, A, Codomain=R>,
+    for<'a> RowOp<S::Preadjoint<'a>, T::Preadjoint<'a>> : Adjointable<
+        Pair<Xʹ,Yʹ>, A,
+        Codomain = R,
+        AdjointCodomain = Self::Codomain,
+    >,
 {
     type PreadjointCodomain = R;
     type Preadjoint<'a> = RowOp<S::Preadjoint<'a>, T::Preadjoint<'a>> where Self : 'a;
@@ -328,67 +377,73 @@
 /// Diagonal operator
 pub struct DiagOp<S, T>(pub S, pub T);
 
-impl<A, B, S, T> Apply<Pair<A, B>> for DiagOp<S, T>
+impl<A, B, S, T> Mapping<Pair<A, B>> for DiagOp<S, T>
 where
-    S : Apply<A>,
-    T : Apply<B>,
+    A : Space,
+    B : Space,
+    S : Mapping<A>,
+    T : Mapping<B>,
 {
-    type Output = Pair<S::Output, T::Output>;
+    type Codomain = Pair<S::Codomain, T::Codomain>;
 
-    fn apply(&self, Pair(a, b) : Pair<A, B>) -> Self::Output {
-        Pair(self.0.apply(a), self.1.apply(b))
-    }
-}
-
-impl<'a, A, B, S, T> Apply<&'a Pair<A, B>> for DiagOp<S, T>
-where
-    S : Apply<&'a A>,
-    T : Apply<&'a B>,
-{
-    type Output = Pair<S::Output, T::Output>;
-
-    fn apply(&self, Pair(ref a, ref b) : &'a Pair<A, B>) -> Self::Output {
+    fn apply<I : Instance<Pair<A, B>>>(&self, x : I) -> Self::Codomain {
+        let Pair(a, b) = x.decompose();
         Pair(self.0.apply(a), self.1.apply(b))
     }
 }
 
-impl<A, B, S, T, D> Linear<Pair<A, B>> for DiagOp<S, T>
+impl<A, B, S, T> Linear<Pair<A, B>> for DiagOp<S, T>
 where
-    DiagOp<S, T> : Apply<Pair<A, B>, Output=D> + for<'a>  Apply<&'a Pair<A, B>, Output=D>,
-{
-    type Codomain = D;
-}
+    A : Space,
+    B : Space,
+    S : Linear<A>,
+    T : Linear<B>,
+{ }
 
 impl<F, S, T, A, B, U, V> GEMV<F, Pair<U, V>, Pair<A, B>> for DiagOp<S, T>
 where
+    A : Space,
+    B : Space,
+    U : Space,
+    V : Space,
     S : GEMV<F, U, A>,
     T : GEMV<F, V, B>,
     F : Num,
-    Self : Linear<Pair<U, V>, Codomain=Pair<A, B>>
+    Self : Linear<Pair<U, V>, Codomain=Pair<A, B>>,
 {
-    fn gemv(&self, y : &mut Pair<A, B>, α : F, x : &Pair<U, V>, β : F) {
-        self.0.gemv(&mut y.0, α, &x.0, β);
-        self.1.gemv(&mut y.1, α, &x.1, β);
+    fn gemv<I : Instance<Pair<U, V>>>(&self, y : &mut Pair<A, B>, α : F, x : I, β : F) {
+        let Pair(u, v) = x.decompose();
+        self.0.gemv(&mut y.0, α, u, β);
+        self.1.gemv(&mut y.1, α, v, β);
     }
 
-    fn apply_mut(&self, y : &mut Pair<A, B>, x : &Pair<U, V>){
-        self.0.apply_mut(&mut y.0, &x.0);
-        self.1.apply_mut(&mut y.1, &x.1);
+    fn apply_mut<I : Instance<Pair<U, V>>>(&self, y : &mut Pair<A, B>, x : I){
+        let Pair(u, v) = x.decompose();
+        self.0.apply_mut(&mut y.0, u);
+        self.1.apply_mut(&mut y.1, v);
     }
 
     /// Computes `y += Ax`, where `A` is `Self`
-    fn apply_add(&self, y : &mut Pair<A, B>, x : &Pair<U, V>){
-        self.0.apply_add(&mut y.0, &x.0);
-        self.1.apply_add(&mut y.1, &x.1);
+    fn apply_add<I : Instance<Pair<U, V>>>(&self, y : &mut Pair<A, B>, x : I){
+        let Pair(u, v) = x.decompose();
+        self.0.apply_add(&mut y.0, u);
+        self.1.apply_add(&mut y.1, v);
     }
 }
 
-impl<A, B, Xʹ, Yʹ, R, S, T> Adjointable<Pair<A,B>,Pair<Xʹ,Yʹ>> for DiagOp<S, T>
+impl<A, B, Xʹ, Yʹ, R, S, T> Adjointable<Pair<A,B>, Pair<Xʹ,Yʹ>> for DiagOp<S, T>
 where
+    A : Space,
+    B : Space,
+    Xʹ: Space,
+    Yʹ : Space,
+    R : Space,
     S : Adjointable<A, Xʹ>,
     T : Adjointable<B, Yʹ>,
     Self : Linear<Pair<A, B>>,
-    for<'a> DiagOp<S::Adjoint<'a>, T::Adjoint<'a>> : Linear<Pair<Xʹ,Yʹ>, Codomain=R>,
+    for<'a> DiagOp<S::Adjoint<'a>, T::Adjoint<'a>> : Linear<
+        Pair<Xʹ,Yʹ>, Codomain=R,
+    >,
 {
     type AdjointCodomain = R;
     type Adjoint<'a> = DiagOp<S::Adjoint<'a>, T::Adjoint<'a>> where Self : 'a;
@@ -398,12 +453,21 @@
     }
 }
 
-impl<A, B, Xʹ, Yʹ, R, S, T> Preadjointable<Pair<A,B>,Pair<Xʹ,Yʹ>> for DiagOp<S, T>
+impl<A, B, Xʹ, Yʹ, R, S, T> Preadjointable<Pair<A,B>, Pair<Xʹ,Yʹ>> for DiagOp<S, T>
 where
+    A : Space,
+    B : Space,
+    Xʹ: Space,
+    Yʹ : Space,
+    R : Space,
     S : Preadjointable<A, Xʹ>,
     T : Preadjointable<B, Yʹ>,
     Self : Linear<Pair<A, B>>,
-    for<'a> DiagOp<S::Preadjoint<'a>, T::Preadjoint<'a>> : Adjointable<Pair<Xʹ,Yʹ>, Pair<A, B>, Codomain=R>,
+    for<'a> DiagOp<S::Preadjoint<'a>, T::Preadjoint<'a>> : Adjointable<
+        Pair<Xʹ,Yʹ>, Pair<A, B>,
+        Codomain=R,
+        AdjointCodomain = Self::Codomain,
+    >,
 {
     type PreadjointCodomain = R;
     type Preadjoint<'a> = DiagOp<S::Preadjoint<'a>, T::Preadjoint<'a>> where Self : 'a;
@@ -416,3 +480,65 @@
 /// Block operator
 pub type BlockOp<S11, S12, S21, S22> = ColOp<RowOp<S11, S12>, RowOp<S21, S22>>;
 
+
+macro_rules! pairnorm {
+    ($expj:ty) => {
+        impl<F, A, B, S, T, ExpA, ExpB, ExpR>
+        BoundedLinear<Pair<A, B>, PairNorm<ExpA, ExpB, $expj>, ExpR, F>
+        for RowOp<S, T>
+        where
+            F : Float,
+            A : Space,
+            B : Space,
+            S : BoundedLinear<A, ExpA, ExpR, F>,
+            T : BoundedLinear<B, ExpB, ExpR, F>,
+            S::Codomain : Add<T::Codomain>,
+            <S::Codomain as Add<T::Codomain>>::Output : Space,
+            ExpA : NormExponent,
+            ExpB : NormExponent,
+            ExpR : NormExponent,
+        {
+            fn opnorm_bound(
+                &self,
+                PairNorm(expa, expb, _) : PairNorm<ExpA, ExpB, $expj>,
+                expr : ExpR
+            ) -> F {
+                // An application of the triangle inequality bounds the norm by the maximum
+                // of the individual norms. A simple observation shows this to be exact.
+                let na = self.0.opnorm_bound(expa, expr);
+                let nb = self.1.opnorm_bound(expb, expr);
+                na.max(nb)
+            }
+        }
+        
+        impl<F, A, S, T, ExpA, ExpS, ExpT>
+        BoundedLinear<A, ExpA, PairNorm<ExpS, ExpT, $expj>, F>
+        for ColOp<S, T>
+        where
+            F : Float,
+            A : Space,
+            S : BoundedLinear<A, ExpA, ExpS, F>,
+            T : BoundedLinear<A, ExpA, ExpT, F>,
+            ExpA : NormExponent,
+            ExpS : NormExponent,
+            ExpT : NormExponent,
+        {
+            fn opnorm_bound(
+                &self,
+                expa : ExpA,
+                PairNorm(exps, expt, _) : PairNorm<ExpS, ExpT, $expj>
+            ) -> F {
+                // This is based on the rule for RowOp and ‖A^*‖ = ‖A‖, hence,
+                // for A=[S; T], ‖A‖=‖[S^*, T^*]‖ ≤ max{‖S^*‖, ‖T^*‖} = max{‖S‖, ‖T‖}
+                let ns = self.0.opnorm_bound(expa, exps);
+                let nt = self.1.opnorm_bound(expa, expt);
+                ns.max(nt)
+            }
+        }
+    }
+}
+
+pairnorm!(L1);
+pairnorm!(L2);
+pairnorm!(Linfinity);
+
--- a/src/loc.rs	Sat Dec 14 09:31:27 2024 -0500
+++ b/src/loc.rs	Tue Dec 31 08:30:02 2024 -0500
@@ -10,9 +10,12 @@
 use crate::maputil::{FixedLength,FixedLengthMut,map1,map2,map1_mut,map2_mut};
 use crate::euclidean::*;
 use crate::norms::*;
-use crate::linops::AXPY;
+use crate::linops::{AXPY, Mapping, Linear};
+use crate::instance::{Instance, BasicDecomposition};
+use crate::mapping::Space;
 use serde::ser::{Serialize, Serializer, SerializeSeq};
 
+
 /// A container type for (short) `N`-dimensional vectors of element type `F`.
 ///
 /// Supports basic operations of an [`Euclidean`] space, several [`Norm`]s, and
@@ -657,19 +660,35 @@
     }
 }
 
+
+impl<F : Num, const N : usize> Space for Loc<F, N> {
+    type Decomp = BasicDecomposition;
+}
+
+impl<F : Num, const N : usize> Mapping<Loc<F, N>> for Loc<F, N> {
+    type Codomain = F;
+
+    fn apply<I : Instance<Loc<F, N>>>(&self, x : I) -> Self::Codomain {
+        x.eval(|x̃| self.dot(x̃))
+    }
+}
+
+impl<F : Num, const N : usize> Linear<Loc<F, N>> for Loc<F, N> { }
+
 impl<F : Num, const N : usize> AXPY<F, Loc<F, N>> for Loc<F, N> {
-
     #[inline]
-    fn axpy(&mut self, α : F, x : &Loc<F, N>, β : F) {
-        if β == F::ZERO {
-            map2_mut(self, x, |yi, xi| { *yi = α * (*xi) })
-        } else {
-            map2_mut(self, x, |yi, xi| { *yi = β * (*yi) + α * (*xi) })
-        }
+    fn axpy<I : Instance<Loc<F, N>>>(&mut self, α : F, x : I, β : F) {
+        x.eval(|x̃| {
+            if β == F::ZERO {
+                map2_mut(self, x̃, |yi, xi| { *yi = α * (*xi) })
+            } else {
+                map2_mut(self, x̃, |yi, xi| { *yi = β * (*yi) + α * (*xi) })
+            }
+        })
     }
 
     #[inline]
-    fn copy_from(&mut self, x : &Loc<F, N>) {
-        map2_mut(self, x, |yi, xi| *yi = *xi )
+    fn copy_from<I : Instance<Loc<F, N>>>(&mut self, x : I) {
+        x.eval(|x̃| map2_mut(self, x̃, |yi, xi| *yi = *xi ))
     }
 }
--- a/src/mapping.rs	Sat Dec 14 09:31:27 2024 -0500
+++ b/src/mapping.rs	Tue Dec 31 08:30:02 2024 -0500
@@ -3,43 +3,20 @@
 */
 
 use std::marker::PhantomData;
+use std::borrow::Cow;
 use crate::types::Float;
 use serde::Serialize;
 use crate::loc::Loc;
-
-/// Trait for application of `Self` as a mathematical function or operator on `X`.
-pub trait Apply<X> {
-    type Output;
-
-    /// Compute the value of `self` at `x`.
-    fn apply(&self, x : X) -> Self::Output;
-}
-
-/// This blanket implementation is a workaround helper to Rust trait system limitations.
-///
-/// It is introduced because the trait system does not allow blanket implementations of both
-/// [`Apply<X>`] and [`Apply<&'a X>`]. With this, the latter is implemented automatically for
-/// the reference, which can be sufficient to apply the operation in another blanket implementation.
-impl<'a, T, X> Apply<X> for &'a T where T : Apply<X> {
-    type Output = <T as Apply<X>>::Output;
-
-    #[inline]
-    fn apply(&self, x : X) -> Self::Output {
-        (*self).apply(x)
-    }
-}
+pub use crate::instance::{Instance, Decomposition, BasicDecomposition, Space};
 
 /// A mapping from `Domain` to `Codomain`.
 ///
 /// This is automatically implemented when the relevant [`Apply`] are implemented.
-pub trait Mapping<Domain> : Apply<Domain, Output=Self::Codomain>
-                            + for<'a> Apply<&'a Domain, Output=Self::Codomain> {
-    type Codomain;
-}
+pub trait Mapping<Domain : Space> {
+    type Codomain : Space;
 
-impl<Domain, Codomain, T> Mapping<Domain> for T
-where T : Apply<Domain, Output=Codomain> + for<'a> Apply<&'a Domain, Output=Codomain> {
-    type Codomain = Codomain;
+    /// Compute the value of `self` at `x`.
+    fn apply<I : Instance<Domain>>(&self, x : I) -> Self::Codomain;
 }
 
 /// Automatically implemented shorthand for referring to [`Mapping`]s from [`Loc<F, N>`] to `F`.
@@ -49,15 +26,6 @@
 impl<F : Float, T, const N : usize> RealMapping<F, N> for T
 where T : Mapping<Loc<F, N>, Codomain = F> {}
 
-/// Automatically implemented shorthand for referring to differentiable [`Mapping`]s from
-/// [`Loc<F, N>`] to `F`.
-pub trait DifferentiableRealMapping<F : Float, const N : usize>
-: DifferentiableMapping<Loc<F, N>, Codomain = F, DerivativeDomain=Loc<F, N>> {}
-
-impl<F : Float, T, const N : usize> DifferentiableRealMapping<F, N> for T
-where T : DifferentiableMapping<Loc<F, N>, Codomain = F, DerivativeDomain=Loc<F, N>> {}
-
-
 /// A helper trait alias for referring to [`Mapping`]s from [`Loc<F, N>`] to [`Loc<F, M>`].
 pub trait RealVectorField<F : Float, const N : usize, const M : usize>
 : Mapping<Loc<F, N>, Codomain = Loc<F, M>> {}
@@ -65,69 +33,70 @@
 impl<F : Float, T, const N : usize, const M : usize> RealVectorField<F, N, M> for T
 where T : Mapping<Loc<F, N>, Codomain = Loc<F, M>> {}
 
-
-/// Trait for calculation the differential of `Self` as a mathematical function on `X`.
-pub trait Differentiable<X> : Sized {
-    type Derivative;
-
-    /// Compute the differential of `self` at `x`.
-    fn differential(&self, x : X) -> Self::Derivative;
-}
-
-impl<'g, X, G : Differentiable<X>> Differentiable<X> for &'g G {
-    type Derivative = G::Derivative;
-    #[inline]
-    fn differential(&self, x : X) -> Self::Derivative {
-        (*self).differential(x)
-    }
-}
-
 /// A differentiable mapping from `Domain` to [`Mapping::Codomain`], with differentials
 /// `Differential`.
 ///
-/// This is automatically implemented when the relevant [`Differentiable`] are implemented.
-pub trait DifferentiableMapping<Domain>
-: Mapping<Domain>
-  + Differentiable<Domain, Derivative=Self::DerivativeDomain>
-  + for<'a> Differentiable<&'a Domain, Derivative=Self::DerivativeDomain> {
-    type DerivativeDomain;
-    type Differential : Mapping<Domain, Codomain=Self::DerivativeDomain>;
-    type DifferentialRef<'b> : Mapping<Domain, Codomain=Self::DerivativeDomain> where Self : 'b;
+/// This is automatically implemented when [`DifferentiableImpl`] is.
+pub trait DifferentiableMapping<Domain : Space> : Mapping<Domain> {
+    type DerivativeDomain : Space;
+    type Differential<'b> : Mapping<Domain, Codomain=Self::DerivativeDomain> where Self : 'b;
+
+    /// Calculate differential at `x`
+    fn differential<I : Instance<Domain>>(&self, x : I) -> Self::DerivativeDomain;
 
     /// Form the differential mapping of `self`.
-    fn diff(self) -> Self::Differential;
+    fn diff(self) -> Self::Differential<'static>;
+
     /// Form the differential mapping of `self`.
-    fn diff_ref(&self) -> Self::DifferentialRef<'_>;
+    fn diff_ref(&self) -> Self::Differential<'_>;
 }
 
+/// Automatically implemented shorthand for referring to differentiable [`Mapping`]s from
+/// [`Loc<F, N>`] to `F`.
+pub trait DifferentiableRealMapping<F : Float, const N : usize>
+: DifferentiableMapping<Loc<F, N>, Codomain = F, DerivativeDomain = Loc<F, N>> {}
 
-impl<Domain, Derivative, T> DifferentiableMapping<Domain> for T
-where T : Mapping<Domain>
-          + Differentiable<Domain, Derivative=Derivative>
-          + for<'a> Differentiable<&'a Domain,Derivative=Derivative> {
-    type DerivativeDomain = Derivative;
-    type Differential = Differential<Domain, Self>;
-    type DifferentialRef<'b> = Differential<Domain, &'b Self> where Self : 'b;
+impl<F : Float, T, const N : usize> DifferentiableRealMapping<F, N> for T
+where T : DifferentiableMapping<Loc<F, N>, Codomain = F, DerivativeDomain = Loc<F, N>> {}
+
+/// Helper trait for implementing [`DifferentiableMapping`]
+pub trait DifferentiableImpl<X : Space> : Sized {
+    type Derivative : Space;
+
+    /// Compute the differential of `self` at `x`, consuming the input.
+    fn differential_impl<I : Instance<X>>(&self, x : I) -> Self::Derivative;
+}
+
+impl<T, Domain> DifferentiableMapping<Domain> for T
+where
+    Domain : Space,
+    T : Clone + Mapping<Domain> + DifferentiableImpl<Domain>
+{
+    type DerivativeDomain = T::Derivative;
+    type Differential<'b> = Differential<'b, Domain, Self> where Self : 'b;
     
-    /// Form the differential mapping of `self`.
-    fn diff(self) -> Self::Differential {
-        Differential{ g : self, _space : PhantomData }
+    #[inline]
+    fn differential<I : Instance<Domain>>(&self, x : I) -> Self::DerivativeDomain {
+        self.differential_impl(x)
     }
 
-    /// Form the differential mapping of `self`.
-    fn diff_ref(&self) -> Self::DifferentialRef<'_> {
-        Differential{ g : self, _space : PhantomData }
+    fn diff(self) -> Differential<'static, Domain, Self> {
+        Differential{ g : Cow::Owned(self), _space : PhantomData }
+    }
+
+    fn diff_ref(&self) -> Differential<'_, Domain, Self> {
+        Differential{ g : Cow::Borrowed(self), _space : PhantomData }
     }
 }
 
 /// A sum of [`Mapping`]s.
 #[derive(Serialize, Debug, Clone)]
-pub struct Sum<Domain, M : Mapping<Domain>> {
+pub struct Sum<Domain, M> {
     components : Vec<M>,
     _domain : PhantomData<Domain>,
 }
 
-impl<Domain, M : Mapping<Domain>> Sum<Domain, M> {
+impl<Domain, M> Sum<Domain, M> {
     /// Construct from an iterator.
     pub fn new<I : Iterator<Item = M>>(iter : I) -> Self {
         Sum { components : iter.collect(), _domain : PhantomData }
@@ -140,123 +109,107 @@
 }
 
 
-impl<Domain, M> Apply<Domain> for Sum<Domain, M>
-where M : Mapping<Domain>,
-      M::Codomain : std::iter::Sum {
-    type Output = M::Codomain;
+impl<Domain, M> Mapping<Domain> for Sum<Domain, M>
+where
+    Domain : Space + Clone,
+    M : Mapping<Domain>,
+    M::Codomain : std::iter::Sum + Clone
+{
+    type Codomain = M::Codomain;
 
-    fn apply(&self, x : Domain) -> Self::Output {
-        self.components.iter().map(|c| c.apply(&x)).sum()
+    fn apply<I : Instance<Domain>>(&self, x : I) -> Self::Codomain {
+        let xr = x.ref_instance();
+        self.components.iter().map(|c| c.apply(xr)).sum()
     }
 }
 
-impl<'a, Domain, M> Apply<&'a Domain> for Sum<Domain, M>
-where M : Mapping<Domain>,
-      M::Codomain : std::iter::Sum {
-    type Output = M::Codomain;
-
-    fn apply(&self, x : &'a Domain) -> Self::Output {
-        self.components.iter().map(|c| c.apply(x)).sum()
-    }
-}
-
-impl<Domain, M> Differentiable<Domain> for Sum<Domain, M>
-where M : DifferentiableMapping<Domain>,
-      M :: Codomain : std::iter::Sum,
-      M :: DerivativeDomain : std::iter::Sum,
-      Domain : Copy {
-
+impl<Domain, M> DifferentiableImpl<Domain> for Sum<Domain, M>
+where
+    Domain : Space + Clone,
+    M : DifferentiableMapping<Domain>,
+    M :: DerivativeDomain : std::iter::Sum
+{
     type Derivative = M::DerivativeDomain;
 
-    fn differential(&self, x : Domain) -> Self::Derivative {
-        self.components.iter().map(|c| c.differential(x)).sum()
+    fn differential_impl<I : Instance<Domain>>(&self, x : I) -> Self::Derivative {
+        let xr = x.ref_instance();
+        self.components.iter().map(|c| c.differential(xr)).sum()
     }
 }
 
 /// Container for the differential [`Mapping`] of a [`Differentiable`] mapping.
-pub struct Differential<X, G : DifferentiableMapping<X>> {
-    g : G,
+pub struct Differential<'a, X, G : Clone> {
+    g : Cow<'a, G>,
     _space : PhantomData<X>
 }
 
-impl<X, G : DifferentiableMapping<X>> Differential<X, G> {
+impl<'a, X, G : Clone> Differential<'a, X, G> {
     pub fn base_fn(&self) -> &G {
         &self.g
     }
 }
 
-impl<X, G : DifferentiableMapping<X>> Apply<X> for Differential<X, G> {
-    type Output = G::DerivativeDomain;
+impl<'a, X, G> Mapping<X> for Differential<'a, X, G>
+where
+    X : Space,
+    G : Clone + DifferentiableMapping<X>
+{
+    type Codomain = G::DerivativeDomain;
 
     #[inline]
-    fn apply(&self, x : X) -> Self::Output {
-        self.g.differential(x)
+    fn apply<I : Instance<X>>(&self, x : I) -> Self::Codomain {
+        (*self.g).differential(x)
     }
 }
 
-impl<'a, X, G : DifferentiableMapping<X>> Apply<&'a X> for Differential<X, G> {
-    type Output = G::DerivativeDomain;
-
-    #[inline]
-    fn apply(&self, x : &'a X) -> Self::Output {
-        self.g.differential(x)
-    }
-}
-
-
 /// Container for flattening [`Loc`]`<F, 1>` codomain of a [`Mapping`] to `F`.
-pub struct FlattenedCodomain<X, F, G : Mapping<X, Codomain=Loc<F, 1>>> {
+pub struct FlattenedCodomain<X, F, G> {
     g : G,
     _phantoms : PhantomData<(X, F)>
 }
 
-impl<X, F, G : Mapping<X, Codomain=Loc<F, 1>>> Apply<X> for FlattenedCodomain<X, F, G> {
-    type Output = F;
+impl<F : Space, X, G> Mapping<X> for FlattenedCodomain<X, F, G>
+where
+    X : Space,
+    G: Mapping<X, Codomain=Loc<F, 1>>
+{
+    type Codomain = F;
 
     #[inline]
-    fn apply(&self, x : X) -> Self::Output {
+    fn apply<I : Instance<X>>(&self, x : I) -> Self::Codomain {
         self.g.apply(x).flatten1d()
     }
 }
 
 /// An auto-trait for constructing a [`FlattenCodomain`] structure for
 /// flattening the codomain of a [`Mapping`] from [`Loc`]`<F, 1>` to `F`.
-pub trait FlattenCodomain<X, F> : Mapping<X, Codomain=Loc<F, 1>> + Sized {
+pub trait FlattenCodomain<X : Space, F> : Mapping<X, Codomain=Loc<F, 1>> + Sized {
     /// Flatten the codomain from [`Loc`]`<F, 1>` to `F`.
     fn flatten_codomain(self) -> FlattenedCodomain<X, F, Self> {
         FlattenedCodomain{ g : self, _phantoms : PhantomData }
     }
 }
 
-impl<X, F, G : Sized + Mapping<X, Codomain=Loc<F, 1>>> FlattenCodomain<X, F> for G {}
-
+impl<X : Space, F, G : Sized + Mapping<X, Codomain=Loc<F, 1>>> FlattenCodomain<X, F> for G {}
 
 /// Container for dimensional slicing [`Loc`]`<F, N>` codomain of a [`Mapping`] to `F`.
-pub struct SlicedCodomain<X, F, G : Mapping<X, Codomain=Loc<F, N>>, const N : usize> {
-    g : G,
+pub struct SlicedCodomain<'a, X, F, G : Clone, const N : usize> {
+    g : Cow<'a, G>,
     slice : usize,
     _phantoms : PhantomData<(X, F)>
 }
 
-impl<X, F : Copy, G : Mapping<X, Codomain=Loc<F, N>>, const N : usize> Apply<X>
-for SlicedCodomain<X, F, G, N> {
-    type Output = F;
+impl<'a, X, F, G, const N : usize> Mapping<X> for SlicedCodomain<'a, X, F, G, N>
+where
+    X : Space,
+    F : Copy + Space,
+    G : Mapping<X, Codomain=Loc<F, N>> + Clone,
+{
+    type Codomain = F;
 
     #[inline]
-    fn apply(&self, x : X) -> Self::Output {
-        let tmp : [F; N] = self.g.apply(x).into();
-        // Safety: `slice_codomain` below checks the range.
-        unsafe { *tmp.get_unchecked(self.slice) }
-    }
-}
-
-impl<'a, X, F : Copy, G : Mapping<X, Codomain=Loc<F, N>>, const N : usize> Apply<&'a X>
-for SlicedCodomain<X, F, G, N> {
-    type Output = F;
-
-    #[inline]
-    fn apply(&self, x : &'a X) -> Self::Output {
-        let tmp : [F; N] = self.g.apply(x).into();
+    fn apply<I : Instance<X>>(&self, x : I) -> Self::Codomain {
+        let tmp : [F; N] = (*self.g).apply(x).into();
         // Safety: `slice_codomain` below checks the range.
         unsafe { *tmp.get_unchecked(self.slice) }
     }
@@ -264,21 +217,22 @@
 
 /// An auto-trait for constructing a [`FlattenCodomain`] structure for
 /// flattening the codomain of a [`Mapping`] from [`Loc`]`<F, 1>` to `F`.
-pub trait SliceCodomain<X, F : Copy, const N : usize> : Mapping<X, Codomain=Loc<F, N>> + Sized {
+pub trait SliceCodomain<X : Space, F : Copy, const N : usize>
+    : Mapping<X, Codomain=Loc<F, N>> + Clone + Sized
+{
     /// Flatten the codomain from [`Loc`]`<F, 1>` to `F`.
-    fn slice_codomain(self, slice : usize) -> SlicedCodomain<X, F, Self, N> {
+    fn slice_codomain(self, slice : usize) -> SlicedCodomain<'static, X, F, Self, N> {
         assert!(slice < N);
-        SlicedCodomain{ g : self, slice, _phantoms : PhantomData }
+        SlicedCodomain{ g : Cow::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> {
+    fn slice_codomain_ref(&self, slice : usize) -> SlicedCodomain<'_, X, F, Self, N> {
         assert!(slice < N);
-        SlicedCodomain{ g : self, slice, _phantoms : PhantomData }
+        SlicedCodomain{ g : Cow::Borrowed(self), slice, _phantoms : PhantomData }
     }
 }
 
-impl<X, F : Copy, G : Sized + Mapping<X, Codomain=Loc<F, N>>, const N : usize>
+impl<X : Space, F : Copy, G : Sized + Mapping<X, Codomain=Loc<F, N>> + Clone, const N : usize>
 SliceCodomain<X, F, N>
 for G {}
-
--- a/src/metaprogramming.rs	Sat Dec 14 09:31:27 2024 -0500
+++ b/src/metaprogramming.rs	Tue Dec 31 08:30:02 2024 -0500
@@ -41,63 +41,3 @@
     (noref, &$x:ty) => { $x };
 }
 
-/// Use as
-/// ```ignore
-/// impl_vectorspace_ops!(impl_binop, impl_assignop, impl_scalarop, impl_scalar_assignop, impl_unaryop);
-/// ```
-/// with `impl_binop`, `impl_assignop`, `impl_scalarop`, and `impl_unaryop` macros provided.
-/// For example usage see the [power][crate::vectorspace::powerspace] and
-/// [product spaces][crate::vectorspace::productspace] implementations.
-#[macro_export]
-macro_rules! impl_vectorspace_ops {
-    ($impl_binop:ident, $impl_assignop:ident, $impl_scalarop:ident, $impl_scalarlhs_op:ident,
-        $impl_scalar_assignop:ident, $impl_unaryop:ident) => {
-        impl_vectorspace_ops!($impl_binop, $impl_assignop, $impl_scalarop, $impl_scalarlhs_op,
-                                $impl_scalar_assignop, $impl_unaryop,
-                                (f32, f64,
-                                num::complex::Complex<f32>,
-                                num::complex::Complex<f64>));
-    };
-    ($impl_binop:ident, $impl_assignop:ident, $impl_scalarop:ident, $impl_scalarlhs_op:ident,
-        $impl_scalar_assignop:ident, $impl_unaryop:ident, ($($field:ty),+)) => {
-        impl_vectorspace_ops!(@binary, $impl_binop, Add, add);
-        impl_vectorspace_ops!(@binary, $impl_binop, Sub, sub);
-        impl_vectorspace_ops!(@assign, $impl_assignop, AddAssign, add_assign);
-        impl_vectorspace_ops!(@assign, $impl_assignop, SubAssign, sub_assign);
-        impl_vectorspace_ops!(@scalar, $impl_scalarop, Mul, mul);
-        impl_vectorspace_ops!(@scalar, $impl_scalarop, Div, div);
-        // Compiler overflow
-        // $(
-        //     impl_vectorspace_ops!(@scalar_lhs, $impl_scalarlhs_op, Mul, mul, $field);
-        // )*
-        impl_vectorspace_ops!(@scalar_assign, $impl_scalar_assignop, MulAssign, mul_assign);
-        impl_vectorspace_ops!(@scalar_assign, $impl_scalar_assignop, DivAssign, div_assign);
-        impl_vectorspace_ops!(@unary, $impl_unaryop, Neg, neg);
-    };
-    (@binary, $impl:ident, $trait : ident, $fn : ident) => {
-        $impl!($trait, $fn, ref, ref);
-        $impl!($trait, $fn, ref, noref);
-        $impl!($trait, $fn, noref, ref);
-        $impl!($trait, $fn, noref, noref);
-    };
-    (@assign, $impl:ident, $trait : ident, $fn :ident) => {
-        $impl!($trait, $fn, ref);
-        $impl!($trait, $fn, noref);
-    };
-    (@scalar, $impl:ident, $trait : ident, $fn :ident) => {
-        $impl!($trait, $fn, ref);
-        $impl!($trait, $fn, noref);
-    };
-    (@scalar_lhs, $impl:ident, $trait : ident, $fn : ident, $field: ty) => {
-        // These operators need workarounds
-        $impl!($trait, $fn, ref, $field);
-        $impl!($trait, $fn, noref, $field);
-    };
-    (@scalar_assign, $impl:ident, $trait : ident, $fn :ident) => {
-        $impl!($trait, $fn);
-    };
-    (@unary, $impl:ident, $trait : ident, $fn :ident) => {
-        $impl!($trait, $fn, ref);
-        $impl!($trait, $fn, noref);
-    };
-}
--- a/src/nalgebra_support.rs	Sat Dec 14 09:31:27 2024 -0500
+++ b/src/nalgebra_support.rs	Tue Dec 31 08:30:02 2024 -0500
@@ -23,51 +23,49 @@
 use num_traits::identities::{Zero, One};
 use crate::linops::*;
 use crate::euclidean::*;
+use crate::mapping::{Space, BasicDecomposition};
 use crate::types::Float;
 use crate::norms::*;
+use crate::instance::Instance;
 
-impl<SM,SV,N,M,K,E> Apply<Matrix<E,M,K,SV>> for Matrix<E,N,M,SM>
-where SM: Storage<E,N,M>, SV: Storage<E,M,K>,
-        N : Dim, M : Dim, K : Dim, E : Scalar + Zero + One + ClosedAddAssign + ClosedMulAssign,
-        DefaultAllocator : Allocator<N,K>,
-        DefaultAllocator : Allocator<M,K>,
-        DefaultAllocator : Allocator<N,M>,
-        DefaultAllocator : Allocator<M,N> {
-    type Output = OMatrix<E,N,K>;
-
-    #[inline]
-    fn apply(&self, x : Matrix<E,M,K,SV>) -> Self::Output {
-        self.mul(x)
-    }
+impl<SM,N,M,E> Space for Matrix<E,N,M,SM>
+where
+    SM: Storage<E,N,M> + Clone,
+    N : Dim, M : Dim, E : Scalar + Zero + One + ClosedAddAssign + ClosedMulAssign,
+    DefaultAllocator : Allocator<N,M>,
+{
+    type Decomp = BasicDecomposition;
 }
 
-impl<'a, SM,SV,N,M,K,E> Apply<&'a Matrix<E,M,K,SV>> for Matrix<E,N,M,SM>
-where SM: Storage<E,N,M>, SV: Storage<E,M,K>,
-        N : Dim, M : Dim, K : Dim, E : Scalar + Zero + One + ClosedAddAssign + ClosedMulAssign,
-        DefaultAllocator : Allocator<N,K>,
-        DefaultAllocator : Allocator<M,K>,
-        DefaultAllocator : Allocator<N,M>,
-        DefaultAllocator : Allocator<M,N> {
-    type Output = OMatrix<E,N,K>;
-
-    #[inline]
-    fn apply(&self, x : &'a Matrix<E,M,K,SV>) -> Self::Output {
-        self.mul(x)
-    }
-}
-
-impl<'a, SM,SV,N,M,K,E> Linear<Matrix<E,M,K,SV>> for Matrix<E,N,M,SM>
-where SM: Storage<E,N,M>, SV: Storage<E,M,K>,
+impl<SM,SV,N,M,K,E> Mapping<Matrix<E,M,K,SV>> for Matrix<E,N,M,SM>
+where SM: Storage<E,N,M>, SV: Storage<E,M,K> + Clone,
         N : Dim, M : Dim, K : Dim, E : Scalar + Zero + One + ClosedAddAssign + ClosedMulAssign,
         DefaultAllocator : Allocator<N,K>,
         DefaultAllocator : Allocator<M,K>,
         DefaultAllocator : Allocator<N,M>,
         DefaultAllocator : Allocator<M,N> {
     type Codomain = OMatrix<E,N,K>;
+
+    #[inline]
+    fn apply<I : Instance<Matrix<E,M,K,SV>>>(
+        &self, x : I
+    ) -> Self::Codomain {
+        x.either(|owned| self.mul(owned), |refr| self.mul(refr))
+    }
+}
+
+
+impl<'a, SM,SV,N,M,K,E> Linear<Matrix<E,M,K,SV>> for Matrix<E,N,M,SM>
+where SM: Storage<E,N,M>, SV: Storage<E,M,K> + Clone,
+        N : Dim, M : Dim, K : Dim, E : Scalar + Zero + One + ClosedAddAssign + ClosedMulAssign,
+        DefaultAllocator : Allocator<N,K>,
+        DefaultAllocator : Allocator<M,K>,
+        DefaultAllocator : Allocator<N,M>,
+        DefaultAllocator : Allocator<M,N> {
 }
 
 impl<SM,SV1,SV2,N,M,K,E> GEMV<E, Matrix<E,M,K,SV1>, Matrix<E,N,K,SV2>> for Matrix<E,N,M,SM>
-where SM: Storage<E,N,M>, SV1: Storage<E,M,K>, SV2: StorageMut<E,N,K>,
+where SM: Storage<E,N,M>, SV1: Storage<E,M,K> + Clone, SV2: StorageMut<E,N,K>,
       N : Dim, M : Dim, K : Dim, E : Scalar + Zero + One + Float,
       DefaultAllocator : Allocator<N,K>,
       DefaultAllocator : Allocator<M,K>,
@@ -75,34 +73,36 @@
       DefaultAllocator : Allocator<M,N> {
 
     #[inline]
-    fn gemv(&self, y : &mut Matrix<E,N,K,SV2>, α : E, x : &Matrix<E,M,K,SV1>, β : E) {
-        Matrix::gemm(y, α, self, x, β)
+    fn gemv<I : Instance<Matrix<E,M,K,SV1>>>(
+        &self, y : &mut Matrix<E,N,K,SV2>, α : E, x : I, β : E
+    ) {
+        x.eval(|x̃| Matrix::gemm(y, α, self, x̃, β))
     }
 
     #[inline]
-    fn apply_mut<'a>(&self, y : &mut Matrix<E,N,K,SV2>, x : &Matrix<E,M,K,SV1>) {
-        self.mul_to(x, y)
+    fn apply_mut<'a, I : Instance<Matrix<E,M,K,SV1>>>(&self, y : &mut Matrix<E,N,K,SV2>, x : I) {
+        x.eval(|x̃| self.mul_to(x̃, y))
     }
 }
 
 impl<SM,SV1,M,E> AXPY<E, Vector<E,M,SV1>> for Vector<E,M,SM>
-where SM: StorageMut<E,M>, SV1: Storage<E,M>,
+where SM: StorageMut<E,M> + Clone, SV1: Storage<E,M> + Clone,
       M : Dim, E : Scalar + Zero + One + Float,
       DefaultAllocator : Allocator<M> {
 
     #[inline]
-    fn axpy(&mut self, α : E, x : &Vector<E,M,SV1>, β : E) {
-        Matrix::axpy(self, α, x, β)
+    fn axpy<I : Instance<Vector<E,M,SV1>>>(&mut self, α : E, x : I, β : E) {
+        x.eval(|x̃| Matrix::axpy(self, α, x̃, β))
     }
 
     #[inline]
-    fn copy_from(&mut self, y : &Vector<E,M,SV1>) {
-        Matrix::copy_from(self, y)
+    fn copy_from<I : Instance<Vector<E,M,SV1>>>(&mut self, y : I) {
+        y.eval(|ỹ| Matrix::copy_from(self, ỹ))
     }
 }
 
 impl<SM,M,E> Projection<E, Linfinity> for Vector<E,M,SM>
-where SM: StorageMut<E,M>,
+where SM: StorageMut<E,M> + Clone,
       M : Dim, E : Scalar + Zero + One + Float + RealField,
       DefaultAllocator : Allocator<M> {
     #[inline]
@@ -111,9 +111,9 @@
     }
 }
 
-impl<'own,SV1,SV2,SM,N,M,K,E> Adjointable<Matrix<E,M,K,SV1>,Matrix<E,N,K,SV2>>
+impl<'own,SV1,SV2,SM,N,M,K,E> Adjointable<Matrix<E,M,K,SV1>, Matrix<E,N,K,SV2>>
 for Matrix<E,N,M,SM>
-where SM: Storage<E,N,M>, SV1: Storage<E,M,K>, SV2: Storage<E,N,K>,
+where SM: Storage<E,N,M>, SV1: Storage<E,M,K> + Clone, SV2: Storage<E,N,K> + Clone,
       N : Dim, M : Dim, K : Dim, E : Scalar + Zero + One + SimdComplexField,
       DefaultAllocator : Allocator<N,K>,
       DefaultAllocator : Allocator<M,K>,
@@ -170,7 +170,7 @@
 impl<E,M,S> Euclidean<E>
 for Vector<E,M,S>
 where M : Dim,
-      S : StorageMut<E,M>,
+      S : StorageMut<E,M> + Clone,
       E : Float + Scalar + Zero + One + RealField,
       DefaultAllocator : Allocator<M> {
 
@@ -195,7 +195,7 @@
 impl<E,M,S> StaticEuclidean<E>
 for Vector<E,M,S>
 where M : DimName,
-      S : StorageMut<E,M>,
+      S : StorageMut<E,M> + Clone,
       E : Float + Scalar + Zero + One + RealField,
       DefaultAllocator : Allocator<M> {
 
--- a/src/norms.rs	Sat Dec 14 09:31:27 2024 -0500
+++ b/src/norms.rs	Tue Dec 31 08:30:02 2024 -0500
@@ -3,18 +3,31 @@
 */
 
 use serde::Serialize;
+use std::marker::PhantomData;
 use crate::types::*;
 use crate::euclidean::*;
+use crate::mapping::{Mapping, Space, Instance};
 
 //
 // Abstract norms
 //
 
+#[derive(Copy,Clone,Debug)]
+/// Helper structure to convert a [`NormExponent`] into a [`Mapping`]
+pub struct NormMapping<F : Float, E : NormExponent>{
+    pub(crate) exponent : E,
+    _phantoms : PhantomData<F>
+}
+
 /// An exponent for norms.
 ///
 // Just a collection of desirable attributes for a marker type
-pub trait NormExponent : Copy + Send + Sync + 'static {}
-
+pub trait NormExponent : Copy + Send + Sync + 'static {
+    /// Return the norm as a mappin
+    fn as_mapping<F : Float>(self) -> NormMapping<F, Self> {
+        NormMapping{ exponent : self, _phantoms : PhantomData }
+    }
+}
 
 /// Exponent type for the 1-[`Norm`].
 #[derive(Copy,Debug,Clone,Serialize,Eq,PartialEq)]
@@ -37,6 +50,15 @@
 pub struct L21;
 impl NormExponent for L21 {}
 
+/// Norms for pairs (a, b). ‖(a,b)‖ = ‖(‖a‖_A, ‖b‖_B)‖_J
+/// For use with [`crate::direct_product::Pair`]
+#[derive(Copy,Debug,Clone,Serialize,Eq,PartialEq)]
+pub struct PairNorm<A, B, J>(pub A, pub B, pub J);
+
+impl<A, B, J> NormExponent for PairNorm<A, B, J>
+where A : NormExponent, B : NormExponent, J : NormExponent {}
+
+
 /// A Huber/Moreau–Yosida smoothed [`L1`] norm. (Not a norm itself.)
 ///
 /// The parameter γ of this type is the smoothing factor. Zero means no smoothing, and higher
@@ -154,3 +176,29 @@
     }
 }
 
+impl<F : Float, E : Norm<F, L2>> Norm<F, L21> for Vec<E> {
+    fn norm(&self, _l21 : L21) -> F {
+        self.iter().map(|e| e.norm(L2)).sum()
+    }
+}
+
+impl<F : Float, E : Dist<F, L2>> Dist<F, L21> for Vec<E> {
+    fn dist(&self, other : &Self, _l21 : L21) -> F {
+        self.iter().zip(other.iter()).map(|(e, g)| e.dist(g, L2)).sum()
+    }
+}
+
+impl<E, F, Domain> Mapping<Domain> for NormMapping<F, E>
+where
+    F : Float,
+    E : NormExponent,
+    Domain : Space + Norm<F, E>,
+{
+    type Codomain = F;
+
+    #[inline]
+    fn apply<I : Instance<Domain>>(&self, x : I) -> F {
+        x.eval(|r| r.norm(self.exponent))
+    }
+}
+
--- a/src/types.rs	Sat Dec 14 09:31:27 2024 -0500
+++ b/src/types.rs	Tue Dec 31 08:30:02 2024 -0500
@@ -12,6 +12,13 @@
 //use trait_set::trait_set;
 pub use num_traits::Float as NumTraitsFloat; // needed to re-export functions.
 pub use num_traits::cast::AsPrimitive;
+pub use simba::scalar::{
+    ClosedAdd, ClosedAddAssign,
+    ClosedSub, ClosedSubAssign,
+    ClosedMul, ClosedMulAssign,
+    ClosedDiv, ClosedDivAssign,
+    ClosedNeg
+};
 
 /// Typical integer type
 #[allow(non_camel_case_types)]
@@ -57,7 +64,8 @@
                  + CastFrom<u128> + CastFrom<usize>
                  + CastFrom<i8>   + CastFrom<i16> + CastFrom<i32> + CastFrom<i64>
                  + CastFrom<i128> + CastFrom<isize>
-                 + CastFrom<f32>  + CastFrom<f64> {
+                 + CastFrom<f32>  + CastFrom<f64>
+                 + crate::instance::Space {
 
     const ZERO : Self;
     const ONE : Self;

mercurial