Help with matrix/vector traits

I’m new to rust and nalgebra. I’ve inherited some code that used and older version of nalgebra and I’m trying to get it working with the current version and with the current rustc. I’m having trouble understanding the trait constraints and how to get them squared away.

I’ve got a struct that contains an offset vector and scale factor that’s going to be applied to a bunch of data:

#[derive(Debug, PartialEq)]
pub struct Parameters<D>
where
    D: DimName,
{
    /// The offset of the points.
    pub offset: Vector<D>,

    /// The scaling applied to the points.
    pub scale: f64,
}

Here Vector is really VectorN<f64, D>.

When I build, I get trait “not satisfied” errors. First:

   Compiling cpd v0.1.0 (/home/acbell/cpd-rs)
error[E0277]: cannot multiply `generic_array::typenum::UInt<generic_array::typenum::UTerm, generic_array::typenum::B1>` to `<D as nalgebra::DimName>::Value`

I eliminate that by adding the suggested where clause.

Then I get:

error[E0277]: the trait bound `<<D as nalgebra::DimName>::Value as std::ops::Mul<generic_array::typenum::UInt<generic_array::typenum::UTerm, generic_array::typenum::B1>>>::Output: generic_array::ArrayLength<f64>` is not satisfied

In this case, adding the suggested where clause doesn’t work. The compiler continues to complain about the ArrayLength trait not being satisifed.

This all seems way harder than it should be. I’m thinking I’m doing something very wrong just to use a scaling vector with generic size.

Any help appreciated.

Hi! This kind of errors related to multiplying type-level integers are generally due to a missing DefaultAllocator bound (though the error message output by rustc is definitely not helpful).

In order to be able to write Vector<D>, you need to tell the compiler that we are able to create the underlying storage of this vector (which in this case is a GenericArray with D elements). To fix this you need to add the DefaultAllocator: Allocator<f64, D> bound to your where clause.

Thanks very much. This works fine, but I’m not sure I understand. My generic type, D is constrained to be a DimType. How does DefaultAllocator come into play? How is it related to (bound to?) D? Or is it applying to Vector<D> itself? If so, I’m surprised that I don’t write the constraint on that type.

A VectorN<f64, D> is a type alias for Matrix<N, D, U1, Owned<N, D, U1>> where Owned<N, D, U1> is itself an alias for <DefaultAllocator as Allocator<N, R, C>>::Buffer which is where DefaultAllocator comes into play. It it used to deduce the type of the underlying data buffer used to store the components.

Thank you very much.

I’ve made progress but I’m still being troubled by trait bounds:

   Compiling cpd v0.1.0 (/home/acbell/cpd-rs)
error[E0599]: no method named `svd` found for type `nalgebra::Matrix<f64, D, D, <nalgebra::DefaultAllocator as nalgebra::allocator::Allocator<f64, D, D>>::Buffer>` in the current scope
  --> /home/acbell/cpd-rs/src/rigid/registration.rs:85:21
   |
85 |         let svd = a.svd(true, true);
   |                     ^^^
   |
   = note: the method `svd` exists but the following trait bounds were not satisfied:
           `<D as nalgebra::DimMin<D>>::Output : nalgebra::DimName`
           `<D as nalgebra::DimMin<D>>::Output : nalgebra::DimSub<nalgebra::U1>`
           `nalgebra::DefaultAllocator : nalgebra::allocator::Allocator<f64, <D as nalgebra::DimMin<D>>::Output, D>`
           `nalgebra::DefaultAllocator : nalgebra::allocator::Allocator<f64, <D as nalgebra::DimMin<D>>::Output>`
           `nalgebra::DefaultAllocator : nalgebra::allocator::Allocator<f64, D, <D as nalgebra::DimMin<D>>::Output>`

Is this another case of a simple solution for a set of complex traits? Is one just supposed to copy/paste the compiler “hints” into your code? It seems like asking a lot for downstream developers to know what traits are necessary for called methods.

Is this a normal occurrence when using rust? It seems quite tedious to have to specify the trait bounds when the compiler obviously KNOWS the trait bounds. I would understand an error if the type I actually use as the generic type didn’t meet the requirements, but to have to add these requirements upstream in every function that uses a function with extensive trait conditions seems tiresome at best.

Is this another case of a simple solution for a set of complex traits? Is one just supposed to copy/paste the compiler “hints” into your code? It seems like asking a lot for downstream developers to know what traits are necessary for called methods.

Yeah, adding the traits requested by the compiler should do the trick. This is indeed quite annoying but necessary because rust has to be very explicit with the trait bounds so they tend to propagate.

Is this a normal occurrence when using rust?

I’d say this is especially present with nalgebra because of its extensive use of traits for dimensional-genericity. Note that if you write code that is not generic wrt. the dimension of the vector, then things get much simpler and don’t require such amount of trait bounds.

Once Rust gets const-generics and specialization support, things should get much better because all those bounds on DefaultAllocator will likely disappear.

Ah thanks,

One more that might/might not be a similar issue:

$ cargo build
   Compiling cpd v0.1.0 (/home/acbell/cpd-rs)
error[E0599]: no method named `determinant` found for type `nalgebra::Matrix<f64, D, D, <nalgebra::DefaultAllocator as nalgebra::allocator::Allocator<f64, D, D>>::Buffer>` in the current scope
  --> /home/acbell/cpd-rs/src/rigid/registration.rs:89:36
   |
89 |         let det = (svd_u * svd_vt).determinant();
   |                                    ^^^^^^^^^^^

The matrix is square <f64, D, D>, so I’m not sure why I wouldn’t have determinant available.

That may be a similar issue, yes. Though I’m surprised the compiler does not gives you more details here. I guess this is because the bound DefaultAllocator: Allocator<(usize, usize), D> is missing. This is needed because the determinant performs an LU decomposition which needs to allocate a vector for keeping track of the pivots it performs. This is probably the most annoying part of those trait bounds right now: they expose some implementation details that the user definitely does not care about.

One way of simplifying those bounds though is to define your own “compound trait” similar to what I did for nalgebra-glm there.

This will allow you to simply use DefaultAllocator: Alloc<f64, D> most of the time.

EDIT: you may also be interested by the compound trait I defined there to replace the DimName traits in order to hide all the DimMin bounds. With this trait, you can simply write D: Dimension instead of D: DimName + DimMin<D> everywhere.

I think I have a similar issue, which I’ve been unable to resolve with any DefaultAllocator magic.
I’m trying to upgrade an older library using nalgebra to the latest version, and the upgrade from 0.16 -> 0.17 causes the following issue.

pub struct Filter<N, DP>
where
    N: Real,
    DP: DimName,
    <DP as DimName>::Value: Mul,
    <DP as DimName>::Value: Mul<typenum::U1>,
    <<DP as DimName>::Value as Mul<typenum::U1>>::Output: ArrayLength<N>,
    <<DP as DimName>::Value as Mul>::Output: ArrayLength<N>,
{
    pub transition: Matrix<N, DP, DP, ArrayStorage<N, DP, DP>>,
    //...
}

Error:
the trait generic_array::ArrayLengthis not implemented for<::Value as std::ops::Mul>::Output`

As mentioned, I’ve tried including DefaultAllocator: Allocator<N, DP, DP> without really understanding the core issue.

Any tips from more experienced folks?

Hi @briggers!

I believe all you need to do is remove all the <DP as DimName>::Value constraints. They are no needed if you have the DefaultAllocator constraint. Also the pub transition: Matrix<N, DP, DP, ArrayStorage<N, DP, DP>> type can be rewritten equivalently as pub transation: MatrixN<N, DP>.

pub struct Filter<N, DP> 
where
    N: Real,
    DP: DimName,
    DefaultAllocator: Allocator<N, DP, DP>
{
    pub transation: MatrixN<N, DP>,
}
1 Like

Many thanks, you nailed it.

One other note, I found this missing trait constraint issue was also resolved by using the more general Matrix<...> form so long as I used Owned for storage rather than ArrayStorage. I’ve no idea why :slight_smile:

That’s because ArrayStorage is actually the one that requires the generic_array::ArrayLength bound. By using Owned instead, the generic_array::ArrayLength traits are no longer involved because ArrayStorage is deduced implicitly.