dimforge / nalgebra

Linear algebra library for Rust.
https://nalgebra.org
Apache License 2.0
3.95k stars 471 forks source link

Large Statically Sized Matrices #1148

Open EvilMuffinHa opened 2 years ago

EvilMuffinHa commented 2 years ago

Hello! I'm currently using statically sized matrices which are typically very large (1024 x 1024). The main issue is I have a struct like this:

pub struct Foo<const N: usize, const M: usize> {
  pub shape: SMatrix<i128, M, N>,
  pub mean: i128
}

The size of these matrices causes a stack overflow, so I would like to create matrices which are dynamically allocated. However, I also would like to keep the constant size.

I tried using a type such as

pub type Mat<T, const R: usize, const C: usize> = Matrix<T, Const<R>, Const<C>, VecStorage<T, Const<R>, Const<C>>>;

but VecStorage<T, Const<R>, Const<C>>doesn't implement RawStorage or Storage.

I also tried creating a custom storage type, but the Storage trait requires a DefaultAllocator, which uses an ArrayStorage internally.

Creating a custom allocator also doesn't seem to work as the Storage trait requires a DefaultAllocator, and not any custom allocator.

Is there any way to add this functionality while integrating it into the current type system? I am also not fully familiar with it currently and so I might be missing something.

Andlon commented 2 years ago

nalgebra heavily relies on inferring storage from dimensions. That is, given a shape (R, C) where R: Dim, C: Dim, nalgebra infers through the impls on DefaultAllocator what the associated storage type should be. This is convenient because otherwise many operations would be ambiguous and would require more syntax input from the user.

At the same time, this largely precludes what you want to do here. On the flip side, I don't see any considerable advantage to encoding the shape of the matrix into the type if it's anyway heap-allocated. Personally I'd just use DMatrix for this, at least. What's your motivation?

EvilMuffinHa commented 2 years ago

DMatrix could still be possible, however I really like the compile time assurances provided by the const generics of the static matrices.

As this is a library, it also assures that end users cannot put in a randomly sized matrix, so I won't have to worry about checking sizes for every function that takes in matrices for user input.

I also think that if sizes are specified for heap allocated objects, it saves some time checking and resizing objects internally. Specifically, if Vec were not used and instead a pointer + shape, there would be no need to resize or have a capacity.

sebcrozet commented 2 years ago

This is a nice motivation. Unfortunately, there is no way for us to support this any time soon, without introducing a terrible amount of generics complexity, and a few arbitrary decisions (regarding the output type of an operation between an allocated object and a non-allocated object). That complexity should be significantly reduced once Rust get a complet support of const-generics, as well as specialization. Perhaps we can study this question again once this is the case.

An alternative would be to put the statically-sized matrix on a Box, but I’m afraid placement-new isn’t well supported by the language either.

Something you could do right now is to store a private DMatrix inside of your struct that keeps the const-generics. When the user needs to access the shape, you could expose that shape as a matrix slice. Something like this:

pub struct Foo<const N: usize, const M: usize> {
  shape: DMatrix<i128>,
  pub mean: i128
}

impl Foo<...> {
    fn shape(&self) -> SMatrixSlice<i128, N, M> {
        SMatrixSlice::from_slice(self.shape.as_slice)
    }
}
EvilMuffinHa commented 2 years ago

Yep, I actually tried to use Box and realized that it wouldn't work well in some cases as LLVM won't optimize out the stack allocation before copying onto the heap.

Thanks for the suggestion, I'll use that instead. Hopefully in the future I can help with adding this functionality once const generics are supported.

RemiKalbe commented 3 months ago

Now that const-generics are somewhat supported, is there any update regarding this issue? I'm getting stack overflows 😓. I chose not to use DMatrix for the same reason as @EvilMuffinHa, the type safety when doing a lot of different arithmetic calculations is really nice.