I struggle using F# units of measure in combination with the System.Numerics.Vector<'T>
type. Let's have a look at a toy problem: Assume we have an array xs
of type float<m>[]
and for some reason we wanted to square all its components, resulting in an array of type float<m^2>[]
. That works perfectly well with scalar code:
xs |> Array.map (fun x -> x * x) // float<m^2>[]
Now suppose we wanted to vectorize this operation by performing the multiplication in System.Numerics.Vector<float>.Count
-sized chunks using SIMD, for instance like so:
open System.Numerics
let simdWidth = Vector<float>.Count
// fill with dummy data
let xs = Array.init (simdWidth * 10) (fun i -> float i * 1.0<m>)
// array to store the results
let rs: float<m^2> array = Array.zeroCreate (xs |> Array.length)
// number of SIMD operations required
let chunks = (xs |> Array.length) / simdWidth
// for simplicity, assume xs.Length % simdWidth = 0
for i = 0 to chunks - 1 do
let v = Vector<_>(xs, i * simdWidth) // Vector<float<m>>, containing xs.[i .. i+simdWidth-1]
let u = v * v // Vector<float<m>>; expected: Vector<float<m^2>>
u.CopyTo(rs, i * simdWidth) // units mismatch
I believe I understand why this happens: How could the F# compiler know, what System.Numerics.Vector<'T>.op_Multiply
does and what arithmetic rules apply? It could literally be any operation. So how should it be able to deduce the correct units?
The question is: What the best way to make this work? How can we tell the compiler which rules apply?
Attempt 1: Remove all units of measure information from xs
and add it back later on:
// remove all UoM from all arrays
let xsWoM = Array.map (fun x -> x / 1.0<m>) xs
// ...
// perform computation using xsWoM etc.
// ...
// add back units again
let xs = Array.map (fun x -> x * 1.0<m>) xsWoM
Problems: Performs unnecessary computations and/or copy operations, defeats the purpose of vectorizing the code for performance reasons. Also, largely defeats the purpose of using UoM to begin with.
Attempt 2: Use inline IL to change the return type of Vector<'T>.op_Multiply
:
// reinterpret x to be of type 'b
let inline retype (x: 'a) : 'b = (# "" x: 'b #)
let inline (.*.) (u: Vector<float<'m>>) (v: Vector<float<'m>>): Vector<float<'m^2>> = u * v |> retype
// ...
let u = v .*. v // asserts type Vector<float<m^2>>
Problems: Doesn't require any additional operations, but uses a deprecated feature (inline IL) and isn't fully generic (only with respect to the unit of measure).
Does anyone have a better solution for this*?
*Note that the above example really is a toy problem to demonstrate the general issue. The real program solves a much more complicate initial value problem, involving many kinds of physical quantities.