dimforge / simba

Set of mathematical traits to facilitate the use of SIMD-based AoSoA (Array of Struct of Array) storage pattern.
Apache License 2.0
293 stars 29 forks source link

Blanket impl of SimdPartialOrd prevents any Wrapper<T> of T: SimdValue defining vectorised field impls #44

Open cormacrelf opened 1 year ago

cormacrelf commented 1 year ago

SimdPartialOrd is blanket implemented for any T: SimdValue<Element = T, SimdBool = bool> + PartialOrd.

I am working on the num_dual crate, trying to make its dual number algebra work like a scalar does for nalgebra purposes. Here's a simplified illustration of the problem (playground).

The illustration has a DualNumber<T> type, which I would like to eventually support DualNumber<f32x4> etc. The object is to put one of those inside a nalgebra vector, i.e. Vector3<DualNumber<f32x4>>, and have that type produce vectorized code. So DualNumber<Simd<[f32; N]>> has to implement everything that f32 does. Including SimdRealField/SimdComplexField.

All of the SimdRealField/SimdComplexField traits require SimdPartialOrd. There is a blanket implementation of SimdPartialOrd, and the existence of that blanket impl prevents any type like DualNumber<T> implementing SimdPartialOrd manually and generic over T's SimdValue implementation / choice of SimdBool.

A type like this needs to have type SimdBool = T::SimdBool; in its SimdValue, and then use the vectorised SimdBool in its implementations of SimdRealField/SimdComplexField. Since I can't implement SimdPartialOrd and am forced to rely on the blanket impl, I can't really generate those vectorised bools. And moreover, since I cannot implement SimdPartialOrd, I have to constrain the SimdRealField/SimdComplexField implementations to T: SimdValue<Element = T, SimdBool = bool>, which means I cannot ever use those traits on DualNumber<f32x4>.

Essentially, the blanket implementation on SimdPartialOrd has foreclosed the possibility of vectorising any new algebraic field implementations in such a way that nalgebra can use them. The resulting DualNumber can only be used with f32 and f64 directly. It only has the un-vectorised code in RealField and ComplexField.

There is one workaround, which is unacceptable: do not implement PartialOrd on DualNumber. Then the trait solver will reject that blanket impl and you can make your own. But the whole point of this exercise was to have operator overloading work with nalgebra, I'm not giving up PartialOrd.

My recommendation is to add a marker trait like the one in the illustration, reproduced here:

trait OptIn {}
impl OptIn for f32 {}
impl OptIn for f64 {}

impl<T> SimdPartialOrd for T
where
      T: SimdValue<Element = T, SimdBool = bool> + PartialOrd,
      // and crucially, add this as a constraint
      T: OptIn,
{}

That way, because DualNumber<T> chooses not to implement OptIn, it is unaffected by the blanket impl of SimdPartialOrd. It can then