Allocators on implementation of traits on a trait?

Hi there,

I’m trying to implement a trait on a trait, and am facing an upward battle with the allocator implementations. I am effectively trying to implement this test at a generalized level. I think I’m covering every possible case of allocators that my function would need, and yet, I get the following error (code is below).

Any hint is appreciated as I’ve hit a total roadblock and have been stalled for about ten days now.

Many thanks

> error[E0277]: cannot multiply `typenum::uint::UInt<typenum::uint::UTerm, typenum::bit::B1>` to `<<T as od::AutoDiff>::HyperStateSize as propagators::na::DimName>::Value`
>    --> src/od/mod.rs:96:5
>     |
> 96  | /     fn gradient(&self, t: f64, state: &VectorN<f64, Self::StateSize>) -> MatrixMN<f64, Self::StateSize, Self::StateSize>
> 97  | |     where
> 98  | |         DefaultAllocator: Allocator<Dual<f64>, Self::StateSize>
> 99  | |             + Allocator<Dual<f64>, Self::StateSize, Self::StateSize>
> ...   |
> 108 | |         grad
> 109 | |     }
>     | |_____^ no implementation for `<<T as od::AutoDiff>::HyperStateSize as propagators::na::DimName>::Value * typenum::uint::UInt<typenum::uint::UTerm, typenum::bit::B1>`
>     |
>     = help: the trait `std::ops::Mul<typenum::uint::UInt<typenum::uint::UTerm, typenum::bit::B1>>` is not implemented for `<<T as od::AutoDiff>::HyperStateSize as propagators::na::DimName>::Value`
>     = help: consider adding a `where <<T as od::AutoDiff>::HyperStateSize as propagators::na::DimName>::Value: std::ops::Mul<typenum::uint::UInt<typenum::uint::UTerm, typenum::bit::B1>>` bound
>     = note: required because of the requirements on the impl of `propagators::na::allocator::Allocator<od::dual_num::Dual<f64>, <T as od::AutoDiff>::HyperStateSize>` for `propagators::na::DefaultAllocator`
>     = note: the requirement `propagators::na::DefaultAllocator: propagators::na::allocator::Allocator<od::dual_num::Dual<f64>, <T as od::AutoDiff>::HyperStateSize>` appears on the impl method but not on the corresponding trait method
> 
> error[E0277]: cannot multiply `<<T as od::AutoDiff>::HyperStateSize as propagators::na::DimName>::Value` to `<<T as od::AutoDiff>::HyperStateSize as propagators::na::DimName>::Value`
>    --> src/od/mod.rs:96:5
>     |
> 96  | /     fn gradient(&self, t: f64, state: &VectorN<f64, Self::StateSize>) -> MatrixMN<f64, Self::StateSize, Self::StateSize>
> 97  | |     where
> 98  | |         DefaultAllocator: Allocator<Dual<f64>, Self::StateSize>
> 99  | |             + Allocator<Dual<f64>, Self::StateSize, Self::StateSize>
> ...   |
> 108 | |         grad
> 109 | |     }
>     | |_____^ no implementation for `<<T as od::AutoDiff>::HyperStateSize as propagators::na::DimName>::Value * <<T as od::AutoDiff>::HyperStateSize as propagators::na::DimName>::Value`
>     |
>     = help: the trait `std::ops::Mul` is not implemented for `<<T as od::AutoDiff>::HyperStateSize as propagators::na::DimName>::Value`
>     = help: consider adding a `where <<T as od::AutoDiff>::HyperStateSize as propagators::na::DimName>::Value: std::ops::Mul` bound
>     = note: required because of the requirements on the impl of `propagators::na::allocator::Allocator<od::dual_num::Dual<f64>, <T as od::AutoDiff>::HyperStateSize, <T as od::AutoDiff>::HyperStateSize>` for `propagators::na::DefaultAllocator`
>     = note: the requirement `propagators::na::DefaultAllocator: propagators::na::allocator::Allocator<od::dual_num::Dual<f64>, <T as od::AutoDiff>::HyperStateSize, <T as od::AutoDiff>::HyperStateSize>` appears on the impl method but not on the corresponding trait method
> 
> error: aborting due to 2 previous errors
> 
> For more information about this error, try `rustc --explain E0277`.
> error: Could not compile `nyx-space`.

Here is the relevant code:

extern crate dual_num;
extern crate hifitime;
extern crate nalgebra as na;
extern crate serde;

use self::dual_num::{partials_t, Dual};
use self::hifitime::instant::Instant;
use self::na::allocator::Allocator;
use self::na::{DefaultAllocator, DimName, MatrixMN, VectorN};
use celestia::{CoordinateFrame, State};

/// Provides the Kalman filters. The [examples](https://github.com/ChristopherRabotin/nyx/tree/master/examples) folder may help in the setup.
pub mod kalman;

/// Provides a range and range rate measuring models.
pub mod ranging;

/// A trait container to specify that given dynamics support linearization, and can be used for state transition matrix computation.
///
/// This trait will likely be made obsolete after the implementation of [#32](https://github.com/ChristopherRabotin/nyx/issues/32).
pub trait Linearization
where
    Self: Sized,
{
    /// Defines the state size of the estimated state
    type StateSize: DimName;
    /// Defines the gradient of the equations of motion for these dynamics.
    fn gradient(&self, t: f64, state: &VectorN<f64, Self::StateSize>) -> MatrixMN<f64, Self::StateSize, Self::StateSize>
    where
        DefaultAllocator: Allocator<f64, Self::StateSize> + Allocator<f64, Self::StateSize, Self::StateSize>;
}

/// A trait defining a measurement of size `MeasurementSize`
pub trait Measurement
where
    Self: Sized,
    DefaultAllocator: Allocator<f64, Self::MeasurementSize> + Allocator<f64, Self::MeasurementSize, Self::StateSize>,
{
    /// Defines the state size of the estimated state
    type StateSize: DimName;
    /// Defines how much data is measured. For example, if measuring range and range rate, this should be of size 2 (nalgebra::U2).
    type MeasurementSize: DimName;

    /// Computes a new measurement from the provided information.
    fn new<F: CoordinateFrame>(
        dt: Instant,
        tx: State<F>,
        rx: State<F>,
        obs: VectorN<f64, Self::MeasurementSize>,
        visible: bool,
    ) -> Self;

    /// Returns the measurement/observation as a vector.
    fn observation(&self) -> &VectorN<f64, Self::MeasurementSize>
    where
        DefaultAllocator: Allocator<f64, Self::MeasurementSize>;

    /// Returns the measurement sensitivity (often referred to as H tilde).
    fn sensitivity(&self) -> &MatrixMN<f64, Self::MeasurementSize, Self::StateSize>
    where
        DefaultAllocator: Allocator<f64, Self::StateSize, Self::MeasurementSize>;

    /// Returns whether the transmitter and receiver where in line of sight.
    fn visible(&self) -> bool;

    /// Returns the time at which the measurement was performed.
    fn at(&self) -> Instant;
}

/// A trait container to specify that given dynamics support linearization, and can be used for state transition matrix computation.
///
/// This trait will likely be made obsolete after the implementation of [#32](https://github.com/ChristopherRabotin/nyx/issues/32).
pub trait AutoDiff
where
    Self: Sized,
{
    /// Defines the state size of the estimated state
    type HyperStateSize: DimName;

    /// Defines the equations of motion for Dual numbers for these dynamics.
    fn dual_eom(
        &self,
        t: f64,
        state: &MatrixMN<Dual<f64>, Self::HyperStateSize, Self::HyperStateSize>,
    ) -> MatrixMN<Dual<f64>, Self::HyperStateSize, Self::HyperStateSize>
    where
        DefaultAllocator: Allocator<Dual<f64>, Self::HyperStateSize>
            + Allocator<Dual<f64>, Self::HyperStateSize, Self::HyperStateSize>
            + Allocator<f64, Self::HyperStateSize>
            + Allocator<f64, Self::HyperStateSize, Self::HyperStateSize>;
}

impl<T: AutoDiff> Linearization for T {
    type StateSize = T::HyperStateSize;

    fn gradient(&self, t: f64, state: &VectorN<f64, Self::StateSize>) -> MatrixMN<f64, Self::StateSize, Self::StateSize>
    where
        DefaultAllocator: Allocator<Dual<f64>, Self::StateSize>
            + Allocator<Dual<f64>, Self::StateSize, Self::StateSize>
            + Allocator<f64, Self::StateSize>
            + Allocator<f64, Self::StateSize, Self::StateSize>
            + Allocator<Dual<f64>, T::HyperStateSize>
            + Allocator<Dual<f64>, T::HyperStateSize, T::HyperStateSize>
            + Allocator<f64, T::HyperStateSize>
            + Allocator<f64, T::HyperStateSize, T::HyperStateSize>
    {
        let (_, grad) = partials_t(t, *state, Self::dual_eom);
        grad
    }
}

Hi !

Here is something that compiles:

impl<T: AutoDiff> Linearization for T
where
    DefaultAllocator: Allocator<Dual<f64>, T::HyperStateSize>
                    + Allocator<Dual<f64>, T::HyperStateSize, T::HyperStateSize>
{
    type StateSize = T::HyperStateSize;

    fn gradient(&self, t: f64, state: &VectorN<f64, Self::StateSize>) -> MatrixMN<f64, Self::StateSize, Self::StateSize>
        where
            DefaultAllocator: Allocator<f64, Self::StateSize> + Allocator<f64, Self::StateSize, Self::StateSize>
    {
        let (_, grad) = partials_t(t, state.clone(), |a, b| self.dual_eom(a, b));
        grad
    }
}

Because the where clause of gradient must be the same as on the trait definition, the idea here is to:

  1. Keep only the where clause required by the method definition on the Linearization trait.
  2. Put the remaining where clauses required by Self::dual_eom at the top of the impl so the impl itself will actually be valid only for those T satisfying all that is needed by Self::dual_eom.

Note we don’t even need DefaultAllocator: Allocator<f64, T::HyperStateSize> + Allocator<f64, T::HyperStateSize, T::HyperStateSize> because the compilers already knows that StateSize = T::HyperStateSize on this impl.

I have taken the liberty of slightly modifying the call to partials_t here since I got some compilation errors. I am not familiar with it, so I don’t know if the closure I put instead of Self::dual_eom actually makes sense.

Hope this fits your needs!

Awesome, that works, thanks!

Instead of changing the definition of partials_t (since the crate is published and works well), I’ve opted to change the signature of dual_eom.