
Type-checked matrix operations in Rust - jcla1
https://jadpole.github.io/rust/typechecked-matrix/
======
jarrettc
Lately I've been playing around with static dimensional analysis in Rust. The
overall idea is similar: Use PhantomData to add a type parameter and define an
empty struct for each unit. So you might end up with, say, Scalar<Newtons> or
Vec3<Meters>.

Dividing and multiplying units statically is where I've had trouble so far. I
think I've found a way, but it would depend on negative trait bounds, as
discussed here:

[https://github.com/rust-lang/rfcs/issues/1053](https://github.com/rust-
lang/rfcs/issues/1053)

Ideally, I'd like to be able to do something like this:

    
    
        let a: Scalar<Joules> = Scalar::new(2.0);
        let b: Scalar<Seconds> = Scalar::new(3.0);
        let c: Scalar<Watts> = a / b;
        // Watts is a type synonym for Over<Joules, Seconds>.
        // Other derived units would use Times<U, V>. E.g.:
        type Pascals = Times<Newton, Times<Meter, Meter>>.

~~~
TJSomething
But what makes Times<Times<Newton, Meter>, Meter> the same as Times<Newton,
Times<Meter, Meter>>?

~~~
dastbe
Canonicalization is tough, and requires you to define some common ordering on
your units.

Systems will use an array of unit powers, so that if the array were defined as
<Joules, Seconds, Newtons, Meters>, then acceleration would be <0,-2,0,1> and
watts would be <1,-1,0,0>. Addition and subtraction require that your arrays
are equal, and multiplication and division are pairwise additive/subtractive.

~~~
sigterm
Is it tough? There are only seven fundamental units.
[https://en.wikipedia.org/wiki/SI_base_unit](https://en.wikipedia.org/wiki/SI_base_unit)

Just represent every unit in terms of them then it's good.

One problem with the array you came up is that the units are not orthogonal,
since Joules = Newtons * Meters.

~~~
jcranmer
Actually, there's more "kinds" of units: radians (versus degrees) and
steradians come to mind.

~~~
masklinn
These are dimensionless derived units in SI equivalence (respectively m/m and
m2/m2)

------
andreaferretti
For an example of a similar technique in Nim:
[https://github.com/unicredit/linear-
algebra/](https://github.com/unicredit/linear-algebra/)

If I understand correctly the author remark about the lack of Rust support for
value parameters, it seems that it should be equivalent to the Nim feature
(static[int]) that has allowed me to write type-checked matrix operations
there

------
anon4
Ok this is seriously amazing (coming from a C++ guy). However, one thing I
don't like with putting matrix dimensions in templates is that then you can't
construct them at runtime. I do understand the obvious - that you can't have
both static type checks on all operations and runtime-determined matrix sizes.
Though I would kill for a language which would specialise my code at runtime
and throw an exception for compilation errors. So you could write, e.g.

    
    
        template <int m, int n, int l>
        Matrix<m, l>
        mul(Matrix<m, n> lhs, Matrix <n, l> rhs)
        {...impl...}
    

And then be able to call it like

    
    
        int m, n, k, l = ... read from file or whatever
        Matrix <m, n> m1 = ...;
        Matrix <k, l> m2 = ...;
        try {
            Matrix <int o, int p> m3 = m1 * m2;
            // ^ code compiled dynamically
            // or loaded from cache, based on
            // runtime types of m1 and m2.
            // o and p set to the result
            // of type inference.
            // I could imagine even having
            // specialised versions with inline
            // assembly for specific dimensions.
        } catch (DynamicCompilationException e) {
            print("dimensions not compatible");
        }
    

Java could be it, if it had reified generics. You'd create an implementation
of Num, or load one from cache, then instantiate the template and attempt to
call the mul function.

Or you could abuse the invoke dynamic feature - create specialised functions
matrixMultiply$m$n$l and classes Matrix$m$n from some other templating
language as needed, then do an invoke dynamic based on type. But this would be
very cumbersome to use, I think.

~~~
skybrian
Julia does this at runtime. You can create an arbitrarily sized and typed
array and call a function. If no implementation exists specialized for that
type, it will automatically compile one using LLVM.

(The function that creates the array will be slower since it's not type-
stable, but it can generate a type-stable function that's fast.)

------
adwhit
For assorted type system/PhantomData madness, see also:

[http://maniagnosis.crsr.net/2015/07/abstracted-algebra-in-
ru...](http://maniagnosis.crsr.net/2015/07/abstracted-algebra-in-rust.html)

[http://maniagnosis.crsr.net/2015/07/more-abstracted-
algebra-...](http://maniagnosis.crsr.net/2015/07/more-abstracted-algebra-in-
rust.html)

