decorum icon indicating copy to clipboard operation
decorum copied to clipboard

Implement field and other numeric traits from alga.

Open NeverGivinUp opened this issue 5 years ago • 4 comments

Other crates like approx and alga (used by nalgebra) define traits and implement them for native floating point numbers. They do use generics, so that Decorums numbers can be used. Some of their functionality isn't available though with Decorum's numbers, as their traits are not implemented for Decorums numbers.

Due to Rust's Orphan Rule users of Decorum and those libraries cannot implement the other libraries' traits for their use of Decorum. Either the libraries declaring the traits or Decorum must implement them. What strategy does Decorum use for implementing foreign traits? What dependency hierarchy should be created? Should those libraries depend on Decorum or should Decorum depend on those libraries?

I can imagine creating features in Decorum for use with well known libraries, like the above mentioned, might work.

NeverGivinUp avatar Jul 08 '19 17:07 NeverGivinUp

In general, Decorum takes on the burden of implementing traits in the Rust ecosystem for its types. Are there specific traits that you think should be implemented?

olson-sean-k avatar Jul 09 '19 02:07 olson-sean-k

I need the traits from approx as well as ComplexField and RealField from alga (which for its part requires approx's UlpsEq. Here are the full definitions:

pub trait RealField:
    ComplexField<RealField = Self>
    + RelativeEq<Epsilon = Self>
    + UlpsEq<Epsilon = Self>
    + Lattice
    + Signed
    + Bounded
{
    // NOTE: a real must be bounded because, no matter the chosen representation, being `Copy` implies that it occupies a statically-known size, meaning that it must have min/max values.
// elided
}

pub trait ComplexField:
    SubsetOf<Self>
    + SupersetOf<f64>
    + Field
    + Copy
    + Num
    + NumAssign
    + FromPrimitive
    + Neg<Output = Self>
    + MeetSemilattice
    + JoinSemilattice
//    + RelativeEq<Epsilon = Self>
//    + UlpsEq<Epsilon = Self>
    + Send
    + Sync
    + Any
    + 'static
    + Debug
    + Display
{// elided}

I think implementing approx is doable. E.g. by defining a feature in the toml,

plugging the following in lib.rs:

#[cfg(feature = "approx_traits")]
#[macro_use]
extern crate approx;

and the following in proxy.rs

#[cfg(feature = "approx_traits")]
mod approx_traits {
    extern crate approx;

    use approx::{AbsDiffEq, RelativeEq, UlpsEq};
    use crate::{ConstrainedFloat, Encoding};
    use crate::constraint::FloatConstraint;
    use num_traits::Float;
    use crate::Primitive;
    use crate::constraint::ConstraintEq;

    impl<T, P> AbsDiffEq for ConstrainedFloat<T, P>
        where
            T: Float + Primitive + Encoding + AbsDiffEq<Epsilon=T>,
            P: ConstraintEq<T> + FloatConstraint<T> {
        type Epsilon = Self;

        fn default_epsilon() -> Self::Epsilon {
            Encoding::epsilon()
        }

        fn abs_diff_eq(&self, other: &Self, epsilon: Self) -> bool {
            self.value.abs_diff_eq(&other.value, epsilon.value)
        }
    }

    impl<T, P> RelativeEq for ConstrainedFloat<T, P>
        where
            T: Float + Primitive + RelativeEq<Epsilon=T>,
            P: ConstraintEq<T> + FloatConstraint<T> {
        fn default_max_relative() -> Self::Epsilon {
            Self::from_inner_unchecked(T::default_max_relative())
        }

        fn relative_eq(&self, other: &Self, epsilon: Self, max_relative: Self) -> bool {
            self.value.relative_eq(&other.value, epsilon.value, max_relative.value)
        }
    }

    impl<T, P> UlpsEq for ConstrainedFloat<T, P>
        where
            T: Float + Primitive + UlpsEq<Epsilon=T>,
            P: ConstraintEq<T> + FloatConstraint<T> {
        fn default_max_ulps() -> u32 {
            T::default_max_ulps()
        }

        fn ulps_eq(&self, other: &Self, epsilon: Self, max_ulps: u32) -> bool {
            self.value.ulps_eq(&other.value, epsilon.value, max_ulps)
        }
    }

    #[cfg(test)]
    mod tests {
        use crate::{N32, R32, R64};
        use num_traits::Zero;

        #[test]
        fn ulps_eq() {
            assert_ulps_eq!(R64::zero(), R64::from(0.0000000000000001), max_ulps = 4);
            assert_ulps_ne!(R64::zero(), R64::from(0.000000000000001), max_ulps = 4);
            assert_ulps_eq!(R32::zero(), R32::from(0.0000001), max_ulps = 4);
            assert_ulps_ne!(R32::zero(), R32::from(0.000001), max_ulps = 4);
            assert_ulps_eq!(N32::zero(), N32::from(0.0000001), max_ulps = 4);
            assert_ulps_ne!(N32::zero(), N32::from(0.000001), max_ulps = 4);
        }
    }
}

nalgebra on the other hand I don't know how to implement, because (aside from my problems understanding the logic of nalgebra) it seems to require implementors of ComplexField to also declare they can do (logically) everything that f64 can do, which is exactly what I don't want, when I'm using R64 or F64, see my alga issue.

NeverGivinUp avatar Jul 15 '19 11:07 NeverGivinUp

Thanks for the details! This is reminiscent of problems mentioned in #10 and a related issue I opened against num-traits: numeric traits in the ecosystem are not always consistent with each other. decorum is mostly concerned with infinities and NaN, while approx and alga are more concerned with epsilons and ULPs.

It's not entirely clear to me where these trait implementations should live, but at first glance it seems appropriate for approx's traits to be implemented by decorum behind a Cargo feature (as suggested) and for alga to provide less restrictive blanket implementations for RealField. I'll try to put together a change for approx and wait to see what the alga maintainers have to say.

olson-sean-k avatar Jul 15 '19 18:07 olson-sean-k

Support for approx was added in 1f5b00f. The alga issue has not yet been addressed. I'm going to rename this issue to focus on alga traits.

olson-sean-k avatar Aug 06 '20 20:08 olson-sean-k