2

Running into what seems like an odd issue when trying to multiply a square matrix A with X rows and X columns by a vector x with X rows, constructed from a struct with generic sizing. Just a basic A*x = b operation. The end goal is to have a struct type with a generic size X that can be declared when the struct is created, rather than use a static sizing.

The data struct and associated methods are as follows:

struct Foo<T, const X: usize>{
    A: na::base::SMatrix<T, X, X>,
    x: na::base::SVector<T, X>
}

impl<T, const X: usize> Foo<T, X> {
    fn b(&self) -> na::base::SVector<T, X> {
        self.A*self.x
    }
}

However, the compiler throws an error when trying to multiply the two, that A and x are incompatible:

error[E0369]: cannot multiply `Matrix<T, Const<X>, Const<X>, ArrayStorage<T, X, X>>` by `Matrix<T, Const<X>, Const<1_usize>, ArrayStorage<T, X, 1_usize>>`
   --> src\main.rs:989:15
    |
989 |         self.A*self.x
    |         ------^------ Matrix<T, Const<X>, Const<1_usize>, ArrayStorage<T, X, 1_usize>>
    |         |
    |         Matrix<T, Const<X>, Const<X>, ArrayStorage<T, X, X>>

What am I doing wrong here? I'm still learning Rust's generic type system, is there something in the impl declaration I should change? Would appreciate any feedback on implementation.

1 Answers1

1

The problem is that nalgebra can't multiply matrxic by vector for arbitrary T. What if T was Dog for example - we don't have a way to multiply and add Dogs together.

This means you need to restrict type types of T that your function will accept. I found a list of these by digging into the nalgebra multiplication functions. Adding these as a where clause on your impl lets everything work:

impl<T, const X: usize> Foo<T, X> 
where T: na::Scalar + num_traits::Zero + num_traits::One + na::ClosedAdd + na::ClosedMul + Copy,
{
    fn b(&self) -> na::base::SVector<T, X> {
        self.A*self.x
    }
}

It seems likely that some of these can be removed (I'd only really expect Zero and ClosedAdd, ClosedMul).

It also seems likely that there is a simpler umbrella trait somewhere that provides the traits you need which can simplify the definitions. But you can create one yourself if you can't find one, and you're finding that T: ... is spreading throughout your code.

pub trait MyScalar : na::Scalar + num_traits::Zero + num_traits::One + na::ClosedAdd + na::ClosedMul + Copy {}

impl<T> MyScalar for T
where
  T: na::Scalar + num_traits::Zero + num_traits::One + na::ClosedAdd + na::ClosedMul + Copy {}

Then the implementation looks like:

impl<T, const X: usize> Foo<T, X> 
where T: MyScalar,
{
    fn b(&self) -> na::base::SVector<T, X> {
        self.A*self.x
    }
}
Michael Anderson
  • 70,661
  • 7
  • 134
  • 187