I'm trying to wrap the nalgebra and/or ndarray Rust crates into an abstract LinearOperator
trait and corresponding AdjointableOperator
etc. traits. The problem is that I get significant “trait bound bloat”, having to specify even internal implementation details (Storage
, DefaultAllocator
) for the compiler not to choke up. In case of nalgebra:
impl<SM,SV,N,M,K,E> LinearOperator<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 + ClosedMul + ClosedAdd + Zero + One,
DefaultAllocator : Allocator<E,N,K>,
DefaultAllocator : Allocator<E,M,K>,
DefaultAllocator : Allocator<E,N,M>,
DefaultAllocator : Allocator<E,M,N> {
type Codomain = OMatrix<E,N,K>;
fn apply(&self, x : &Matrix<E,M,K,SV>) -> Self::Codomain {
self.mul(x)
}
}
(In case of vector to vector operations, the dimension K
would be 1. Ideally apply
would be just a function call, but the compiler doesn't currently allow defining it.) Some of this may not be necessary for LinearOperator
alone (e.g. DefaultAllocator : Allocator<E,M,N>
for the adjoint dimensions), but the compiler starts requiring them even for LinearOperator
once AdjointableOperator
is also defined. In case of ndarray the amount of lines for the bounds is less, but still require specifying hardly documented semi-internal traits.
type GM<S> = ArrayBase<S,Ix2>;
type GV<S> = ArrayBase<S,Ix1>;
impl<SM,SV,E> LinearOperator<GV<SV>> for GM<SM>
where SM: ArrayData<Elem=E>, SV: ArrayData<Elem=E>, E : LinalgScalar {
type Codomain = Array1<E>;
fn apply(&self, x : &GV<SV>) -> Array1<E> {
self.dot(x)
}
}
All of these trait bounds need to be repeated for the AdjointableOperator
and other traits, so I tried to at least compress the requirements into a custom trait:
trait ValidMatrix<E,N,M,SM>
where N : Dim, M : Dim, E : Scalar + ClosedMul + ClosedAdd + Zero + One,
DefaultAllocator : Allocator<E,N,M>, SM: Storage<E,N,M> {
}
impl<E,N,M,SM> ValidMatrix<E,N,M,SM> for Matrix<E,N,M,SM>
where N : Dim, M : Dim, E : Scalar + ClosedMul + ClosedAdd + Zero + One,
DefaultAllocator : Allocator<E,N,M>, SM: Storage<E,N,M> {
}
and then only specifying Matrix<E,N,M,SM> : ValidMatrix<E,N,M,SM>
in the definition of LinearOperator
. The compiler immediately starts complaining about DefaultAllocator
(potentially) not being implemented.
So is there any way how to avoid such “trait bound bloat”, users of crates having to basically access increasing amounts of internal implementation details of crates to implement their own traits for the exposed types? I can think of specifying mul
etc. in the ValidMatrix
trait, and the implementing those, but surely it would be more efficient to design the upstream crate in such a way in the first place.