Skip to content

Commit a6a3def

Browse files
committed
add story about nalgebra
1 parent 68bcc95 commit a6a3def

File tree

2 files changed

+141
-0
lines changed

2 files changed

+141
-0
lines changed

SUMMARY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
- [😱 Status quo](./vision/status_quo.md)
1010
- [Array defaults](./vision/status_quo/array_default.md)
1111
- [Array split first method](./vision/status_quo/split_first.md)
12+
- [nalgebra](./vision/status_quo/nalgebra.md)
1213
- [✨ Shiny future](./vision/shiny_future.md)
1314
- [Array defaults](./vision/shiny_future/array_default.md)
1415
- [Array split first method](./vision/shiny_future/split_first.md)

vision/status_quo/nalgebra.md

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
# 😱 Status quo: nalgebra
2+
3+
*a huge thanks to [Andreas Borgen Longva](https://github.com/Andlon) and [Sébastien Crozet](https://github.com/sebcrozet) for the help with figuring this out*
4+
5+
[nalgebra](https://nalgebra.org/) is a linear algebra library. At the core of that library is a type `struct Matrix<T, R, C, S>` where `T` is the components scalar type, `R` and `C` represents the number of rows and columns and `S` represents the type of the buffer containing the data.
6+
7+
Relevant for const generics are the parameters `R` and `C`. These are instantiated using one of the following types:
8+
```rust
9+
// For matrices of know size.
10+
pub struct Const<const R: usize>;
11+
// For matrices with a size only known at runtime.
12+
pub struct Dynamic { value: usize }
13+
```
14+
15+
The authors of nalgebra then introduce a type alias
16+
```rust
17+
pub struct ArrayStorage<T, const R: usize, const C: usize>(pub [[T; R]; C]);
18+
/// A matrix of statically know size.
19+
pub type SMatrix<T, const R: usize, const C: usize> =
20+
Matrix<T, Const<R>, Const<C>, ArrayStorage<T, R, C>>;
21+
```
22+
23+
To deal with the lack of generic const expressions, they a trait for conversions from and to [`typenum`](https://crates.io/crates/typenum) for all `Const` up to size `127` ([source](https://github.com/dimforge/nalgebra/blob/39bb572557299a44093ea09daaff144fd6d9ea1f/src/base/dimension.rs#L273-L345)).
24+
25+
Whenever they now need some computation using `Const<N>`, they convert it to type nums, evaluate the computation using the trait system, and then convert the result back to some `Const<M>`.
26+
27+
## Disadvantages
28+
29+
While this mostly works fine, there are some disadvantages.
30+
31+
### Annoying `ToTypenum` bounds
32+
33+
Most notably this adds a lot of unnecessary bounds, consider the following impl:
34+
35+
```rust
36+
impl<T, const R1: usize, const C1: usize, const R2: usize, const C2: usize>
37+
ReshapableStorage<T, Const<R1>, Const<C1>, Const<R2>, Const<C2>> for ArrayStorage<T, R1, C1>
38+
where
39+
T: Scalar,
40+
Const<R1>: ToTypenum,
41+
Const<C1>: ToTypenum,
42+
Const<R2>: ToTypenum,
43+
Const<C2>: ToTypenum,
44+
<Const<R1> as ToTypenum>::Typenum: Mul<<Const<C1> as ToTypenum>::Typenum>,
45+
<Const<R2> as ToTypenum>::Typenum: Mul<
46+
<Const<C2> as ToTypenum>::Typenum,
47+
Output = typenum::Prod<
48+
<Const<R1> as ToTypenum>::Typenum,
49+
<Const<C1> as ToTypenum>::Typenum,
50+
>,
51+
>,
52+
{
53+
type Output = ArrayStorage<T, R2, C2>;
54+
55+
fn reshape_generic(self, _: Const<R2>, _: Const<C2>) -> Self::Output {
56+
unsafe {
57+
let data: [[T; R2]; C2] = mem::transmute_copy(&self.0);
58+
mem::forget(self.0);
59+
ArrayStorage(data)
60+
}
61+
}
62+
}
63+
```
64+
65+
### Cannot use associated constants
66+
67+
It is currently also not possible to have the size of a matrix depend on associated constants:
68+
```rust
69+
trait MyDimensions {
70+
const ROWS: usize;
71+
const COLS: usize;
72+
}
73+
74+
fn foo<Dims: MyDimensions>() {
75+
// Not possible!
76+
let matrix: SMatrix<f64, Dims::ROWS, Dims::COLS> = SMatrix::zeros();
77+
}
78+
```
79+
While this can be avoided by going to back to `typenum` and using associated types, this adds a lot of unnecessary bounds and inpacts all of the code dealing with it.
80+
81+
## Wishlist
82+
83+
Ideally, `Matrix` could be changed to the following:
84+
85+
```rust
86+
enum Dim {
87+
Const(usize),
88+
Dynamic,
89+
}
90+
91+
struct Matrix<T, const R: Dim, const C: Dim, S> { ... }
92+
93+
type SMatrix<T, const R: usize, const C: usize> =
94+
Matrix<T, Dim::Const(R), Dim::Const(C), ArrayStorage<T, R, C>>;
95+
```
96+
97+
For this to work well there have a bunch of requirements for const generics:
98+
99+
### User-defined types as const parameter types
100+
101+
We have to be able to use `Dim` as a const param type
102+
103+
### Consider injective expressions to bind generic params
104+
105+
With this change, `nalgebra` needs impls like the following
106+
107+
```rust
108+
impl<T, const R: usize, const C: usize> for SMatrix<T, R, C> {
109+
// ...
110+
}
111+
```
112+
113+
For this impl to bind `R` and `C`, the expression `Dim::Const(N)` has to bind `N`.
114+
This is sound as constructors are injective. It seems very desirable to at least
115+
enable this for expressions using constructors.
116+
117+
### Merge partial impls to be exhaustive
118+
119+
By adding one trait impl impl for `Dim::Dynamic` and one for `Dim::Const(N)`, it should be enough to consider that trait to be implemented for all `Dim`.
120+
121+
Ideally, the compiler should figure this out by itself, or it can be emulated using specialization by manually adding an impl for all `Dim` which always gets overridden.
122+
123+
### Generic const expressions
124+
125+
For example when computing the [Kronecker product](https://en.wikipedia.org/wiki/Kronecker_product) which has the following simplified signature:
126+
```rust
127+
pub fn kronecker<T, const R1: Dim, const C1: Dim, const R2: Dim, const C2: Dim>(
128+
lhs: &Matrix<T, R1, C2>,
129+
rhs: &Matrix<T, R2, C2>,
130+
) -> Matrix<T, R1 * R2, C1 * C2> {
131+
...
132+
}
133+
```
134+
135+
For this generic const expressions have to be supported.
136+
137+
### const Trait implementations
138+
139+
For `R1 * R2` to work we need const trait impls, otherwise this
140+
can be written using `mul_dim(R1, R2)` or something.

0 commit comments

Comments
 (0)