我很难将 F# 度量单位与 System.Numerics.Vector<'T>
结合使用类型。让我们看一个玩具问题:假设我们有一个数组 xs
类型 float<m>[]
出于某种原因,我们想对它的所有分量进行平方,从而得到一个类型为 float<m^2>[]
的数组。 .这对标量代码非常有效:
xs |> Array.map (fun x -> x * x) // float<m^2>[]
现在假设我们想通过在
System.Numerics.Vector<float>.Count
中执行乘法来向量化这个操作。使用 SIMD 的大小块,例如像这样: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
我相信我明白为什么会发生这种情况:F# 编译器怎么知道,是什么
System.Numerics.Vector<'T>.op_Multiply
什么算术规则适用?它实际上可以是任何操作。那么它应该如何推导出正确的单位呢?问题是 : 使这项工作的最佳方法是什么?我们如何告诉编译器适用哪些规则?
尝试 1 : 从
xs
中删除所有度量单位信息稍后再添加:// 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
问题:执行不必要的计算和/或复制操作,出于性能原因无法实现对代码进行矢量化的目的。此外,在很大程度上违背了开始使用计量单位的目的。
尝试 2 : 使用内联IL改变
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>>
问题:不需要任何额外的操作,但使用了一个不推荐使用的特性(内联 IL)并且不是完全通用的(仅关于度量单位)。
有没有人对此有更好的解决方案* ?
*请注意,上面的示例确实是一个演示一般问题的玩具问题。实际程序解决了一个更为复杂的初值问题,涉及多种物理量。
最佳答案
编译器很擅长弄清楚如何应用乘法的单位规则,这里的问题是你有一个包装类型。在你的第一个例子中,当你写 xs |> Array.map (fun x -> x * x)
,您是根据数组的元素而不是直接在数组上描述乘法。
当您有 Vector<float<m>>
,单位附在 float
而不是 Vector
所以当你尝试乘以 Vector
s,编译器不会将该类型视为具有任何单位。
鉴于该类公开的方法,我认为使用 Vector<'T>
没有简单的解决方法。直接但有包装类型的选项。
像这样的东西可以给你一个单位友好的向量:
type VectorWithUnits<'a, [<Measure>]'b> =
|VectorWithUnits of Vector<'a>
static member inline (*) (a : VectorWithUnits<'a0, 'b0>, b : VectorWithUnits<'a0, 'b1>)
: VectorWithUnits<'a0, 'b0*'b1> =
match a, b with
|VectorWithUnits a, VectorWithUnits b -> VectorWithUnits <| a * b
在这种情况下,单位附加到向量,乘法向量按预期工作,单位行为正确。
问题是现在我们可以在
Vector<'T>
上有单独和不同的度量单位注释。并在 float
本身。您可以将具有度量单位的特定类型的数组转换为一组
Vector
使用:let toFloatVectors (array : float<'m>[]) : VectorWithUnits<float,'m>[] =
let arrs = array |> Array.chunkBySize (Vector<float>.Count)
arrs |> Array.map (Array.map (float) >> Vector >> VectorWithUnits)
然后回来:
let fromFloatVectors (vectors : VectorWithUnits<float,'m>[]) : float<'m>[] =
let arr = Array.zeroCreate<float> (Array.length vectors)
vectors |> Array.iteri (fun i uVec ->
match uVec with
|VectorWithUnits vec -> vec.CopyTo arr)
arr |> Array.map (LanguagePrimitives.FloatWithMeasure<'m>)
一个骇人听闻的替代方案:
如果放弃泛型
'T
你可以做一个 float Vector
通过一些相当可怕的拳击和运行时转换,表现得很好。这滥用了测量单位是在运行时不再存在的编译时构造这一事实。type FloatVectorWithUnits<[<Measure>]'b> =
|FloatVectorWithUnits of Vector<float<'b>>
static member ( * ) (a : FloatVectorWithUnits<'b0>, b : FloatVectorWithUnits<'b1>) =
match a, b with
|FloatVectorWithUnits a, FloatVectorWithUnits b ->
let c, d = box a :?> Vector<float<'b0*'b1>>, box b :?> Vector<float<'b0*'b1>>
c * d |> FloatVectorWithUnits
关于.net - 在 System.Numerics.Vector<T> 中使用 F# 度量单位,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34568672/