Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generics with 0.11 become really hard #222

Open
arturoc opened this issue Feb 17, 2017 · 13 comments
Open

Generics with 0.11 become really hard #222

arturoc opened this issue Feb 17, 2017 · 13 comments

Comments

@arturoc
Copy link
Contributor

arturoc commented Feb 17, 2017

I might be missing some new trait that might make this easier but things seem to have got really complicated when using generics and nalgebra 0.11

I have code like this in 0.10:

extern crate nalgebra;
extern crate num;

use nalgebra::*;

pub fn bezier<T, U>(from: U, cp1: U, cp2: U, to: U, resolution: u32) -> Vec<U>
	where T: BaseFloat,
		  U: FloatVector<T> + Copy {
      let _3:T = num::traits::cast(3.0).unwrap();
      let c:U = (cp1 - from) * _3;
      let b:U = (cp2 - cp1) * _3 - c;
      let a:U = to - from - c - b;

      (0 .. resolution+1).map(|i|{
          let t: T = num::traits::cast(i as f32 / resolution as f32).unwrap();
          let t2 = t*t;
          let t3 = t2*t;
          a*t3 + b*t2 + c*t + from
      }).collect()
}

fn main() {
    bezier(Vector2::new(0f32, 0.), 
               Vector2::new(10., 10.), 
               Vector2::new(20., -10.), 
               Vector2::new(30., 0.), 
               20);
}

where a function takes a vector of any dimension U and it's type T and internally casts some real numbers to T and does some basic operations with the vector type and the numbers.

in 0.11 that becomes something like:

extern crate nalgebra;
extern crate alga;
extern crate num;

use nalgebra::*;
use std::ops::{Add,Sub,Mul};

pub fn bezier<T, D, S>(from: ColumnVector<T,D,S>, 
                                     cp1: ColumnVector<T,D,S>, 
                                     cp2: ColumnVector<T,D,S>, 
                                     to: ColumnVector<T,D,S>, 
                                     resolution: u32) -> Vec<ColumnVector<T,D,S>>
	where T: alga::general::Real + num::NumCast,
		  D: DimName,
                  ColumnVector<T,D,S>: Add<Output=ColumnVector<T,D,S>> +
                        Sub<Output=ColumnVector<T,D,S>> + 
                        Mul<T,Output=ColumnVector<T,D,S>> + 
                        Copy{
      let _3:T = num::traits::cast(3.0).unwrap();
      let c = (cp1 - from) * _3;
      let b = (cp2 - cp1) * _3 - c;
      let a = to - from - c - b;

      (0 .. resolution+1).map(|i|{
          let t: T = num::traits::cast(i as f32 / resolution as f32).unwrap();
          let t2 = t*t;
          let t3 = t2*t;
          a*t3 + b*t2 + c*t + from
      }).collect()
}

fn main() {
    let b = bezier(Vector2::new(0f32, 0.), Vector2::new(10., 10.), Vector2::new(20., -10.), Vector2::new(30., 0.), 20);
}

which is already way more complex than before, needing to specify every operation used over the vector, again i'm not sure if there's any new class that does something similar to FloatVector but is suspect there's not since the storage type makes it harder to generalize every possible vector.

i also guess that using the num crate for number casting shouldn't be needed anymore with alga? but can't find anything to substitute it, have tried with supersetOf but can't get it to work.

now if i try to add a line of code where i get a zero vector things get even more complicated:

pub fn bezier<T, D, S>(from: ColumnVector<T,D,S>, cp1: ColumnVector<T,D,S>, cp2: ColumnVector<T,D,S>, to: ColumnVector<T,D,S>, resolution: u32) -> Vec<ColumnVector<T,D,S>>
	where T: alga::general::Real + num::NumCast,
		  D: DimName,
                       ColumnVector<T,D,S>: Add<Output=ColumnVector<T,D,S>> + 
                           Sub<Output=ColumnVector<T,D,S>> + 
                           Mul<T,Output=ColumnVector<T,D,S>> + 
                           Copy {

    let v: ColumnVector<T,D,S> = zero();
    ...

now i get:

error[E0277]: the trait bound `S: nalgebra::storage::OwnedStorage<T, D, nalgebra::U1>` is not satisfied
  --> src/main.rs:55:34
   |
55 |     let v: ColumnVector<T,D,S> = zero();
   |                                  ^^^^ the trait `nalgebra::storage::OwnedStorage<T, D, nalgebra::U1>` is not implemented for `S`
   |
   = help: consider adding a `where S: nalgebra::storage::OwnedStorage<T, D, nalgebra::U1>` bound
   = note: required because of the requirements on the impl of `alga::general::Identity<alga::general::Additive>` for `nalgebra::Matrix<T, D, nalgebra::U1, S>`
   = note: required by `nalgebra::zero`

error[E0277]: the trait bound `S: nalgebra::storage::Storage<T, D, nalgebra::U1>` is not satisfied
  --> src/main.rs:55:34
   |
55 |     let v: ColumnVector<T,D,S> = zero();
   |                                  ^^^^ the trait `nalgebra::storage::Storage<T, D, nalgebra::U1>` is not implemented for `S`
   |
   = help: consider adding a `where S: nalgebra::storage::Storage<T, D, nalgebra::U1>` bound
   = note: required because of the requirements on the impl of `alga::general::Identity<alga::general::Additive>` for `nalgebra::Matrix<T, D, nalgebra::U1, S>`
   = note: required by `nalgebra::zero`

error: aborting due to 2 previous errors

if i add to the where clause of the function S: storage::Storage<T,D,U1> then i get:

error[E0271]: type mismatch resolving `<<S as nalgebra::storage::Storage<T, D, nalgebra::U1>>::Alloc as nalgebra::allocator::Allocator<T, D, nalgebra::U1>>::Buffer == S`
  --> src/main.rs:50:1
   |
50 |   pub fn bezier_zero<T, D, S>(from: ColumnVector<T,D,S>, cp1: ColumnVector<T,D,S>, cp2: ColumnVector<T,D,S>, to: ColumnVector<T,D,S>, resolution: u32) -> Vec<ColumnVector<T,D,S>>
   |  _^ starting here...
51 | | 	where T: alga::general::Real + num::NumCast,
52 | | 		  D: DimName,
53 | |           S: storage::OwnedStorage<T,D,U1>,
54 | |           ColumnVector<T,D,S>: Add<Output=ColumnVector<T,D,S>> + Sub<Output=ColumnVector<T,D,S>> + Mul<T,Output=ColumnVector<T,D,S>> + Copy{
55 | |
56 | |     let v: ColumnVector<T,D,S> = zero();
57 | |     vec![v]
58 | | }
   | |_^ ...ending here: expected associated type, found type parameter
   |
   = note: expected type `<<S as nalgebra::storage::Storage<T, D, nalgebra::U1>>::Alloc as nalgebra::allocator::Allocator<T, D, nalgebra::U1>>::Buffer`
   = note:    found type `S`
   = note: required by `nalgebra::storage::OwnedStorage`

error: aborting due to previous error

which doesn't give a clear hint of a possible solution anymore.

the real solution is not to add an storage type but instead add alga::general::Identity<alga::general::Additive> to the ColumnVector where clause

I've found similar problems while trying to port some code to 0.11 where adding some trait to solve some error triggers another to a point where the signature of every function becomes extra long and it's really hard to figure out but even once you figure it out then calling two functions from a thrid becomes another problem cause the requirements of the two functions somehow clash.

Not sure if there's already something like that in alga or nalgebra but if would be useful to have something similar to the old FloatVector that allows to specify the most commonly used VectorN without having to worry about specifying all the possible operations, storage type...

error messages have also become really cryptic because of the usage of Matrix for every type although i guess there's no solution for that unless the rust compiler optionally allowed to show aliases for types in errors instead of the original type

@sebcrozet
Copy link
Member

I agree that using the generic matrix and vector types is too difficult to be verry useful at the moment. The bounds on the data storage type are not always easy to set and are the one thing I did not document on the users guide yet...

The closest to the old FloatVector from nalgebra is the trait FiniteDimInnerSpace<N> from alga::linear but the name is not verry obvious. I think you are right that something like FloatVector should be re-added to nalgebra, so I will.

Regarding number conversion, the old cast fonction is now called convert.

@arturoc
Copy link
Contributor Author

arturoc commented Feb 18, 2017

sounds great. i'm using this at the moment to bring back BaseNum, NumVec and FloatVec in case it's useful:

pub trait BaseNum: Scalar +
                   alga::general::Identity<alga::general::Additive> +
                   alga::general::Identity<alga::general::Multiplicative> +
                   num::Zero +
                   num::One +
                   Add<Self, Output = Self> + Sub<Self, Output = Self> +
                   Mul<Self, Output = Self> + Div<Self, Output = Self> +
                   Rem<Self, Output = Self> +
                   AddAssign<Self> + SubAssign<Self> +
                   MulAssign<Self> + DivAssign<Self> +
                   RemAssign<Self> +
                   PartialOrd +
                   'static{
}

impl BaseNum for i8 { }
impl BaseNum for i16 { }
impl BaseNum for i32 { }
impl BaseNum for i64 { }
//impl BaseNum for isize { }
impl BaseNum for u8 { }
impl BaseNum for u16 { }
impl BaseNum for u32 { }
impl BaseNum for u64 { }
//impl BaseNum for usize { }
impl BaseNum for f32 { }
impl BaseNum for f64 { }

pub trait BaseInt: BaseNum +
                   Shl<Self, Output = Self> +
                   ShlAssign<Self> +
                   Shr<Self, Output=Self> +
                   ShrAssign<Self>{}

impl BaseInt for i8 { }
impl BaseInt for i16 { }
impl BaseInt for i32 { }
impl BaseInt for i64 { }
//impl BaseNum for isize { }
impl BaseInt for u8 { }
impl BaseInt for u16 { }
impl BaseInt for u32 { }
impl BaseInt for u64 { }
//impl BaseNum for usize { }

/*
 * Vector related traits.
 */
/// Trait grouping most common operations on vectors.
pub trait NumVec<N>: Add<Self, Output = Self> + Sub<Self, Output = Self> +
                        // Mul<Self, Output = Self> + Div<Self, Output = Self> +

                        // Add<N, Output = Self> + Sub<N, Output = Self> +
                        Mul<N, Output = Self> + Div<N, Output = Self> +

                        AddAssign<Self> + SubAssign<Self> +
                        // MulAssign<Self> + DivAssign<Self> +

                        // AddAssign<N> + SubAssign<N> +
                        MulAssign<N> + DivAssign<N> +

                        alga::general::Identity<alga::general::Additive> +
                        alga::linear::FiniteDimVectorSpace +
                        PartialEq + Axpy<N>
                        where Self: Sized {
}

/// Trait of vector with components implementing the `BaseFloat` trait.
pub trait FloatVec<N: alga::general::Real>: NumVec<N> +
                        alga::linear::NormedSpace +
                        Neg<Output = Self> +
                        alga::linear::FiniteDimInnerSpace {
}


impl<N: BaseNum + alga::general::AbstractField + Neg<Output=N>> NumVec<N> for Vector1<N>{}
impl<N: BaseNum + alga::general::AbstractField + Neg<Output=N>> NumVec<N> for Vector2<N>{}
impl<N: BaseNum + alga::general::AbstractField + Neg<Output=N>> NumVec<N> for Vector3<N>{}
impl<N: BaseNum + alga::general::AbstractField + Neg<Output=N>> NumVec<N> for Vector4<N>{}
impl<N: BaseNum + alga::general::AbstractField + Neg<Output=N>> NumVec<N> for Vector5<N>{}
impl<N: BaseNum + alga::general::AbstractField + Neg<Output=N>> NumVec<N> for Vector6<N>{}

impl<N: BaseNum + alga::general::Real> FloatVec<N> for Vector1<N>{}
impl<N: BaseNum + alga::general::Real> FloatVec<N> for Vector2<N>{}
impl<N: BaseNum + alga::general::Real> FloatVec<N> for Vector3<N>{}
impl<N: BaseNum + alga::general::Real> FloatVec<N> for Vector4<N>{}
impl<N: BaseNum + alga::general::Real> FloatVec<N> for Vector5<N>{}
impl<N: BaseNum + alga::general::Real> FloatVec<N> for Vector6<N>{}

@sebcrozet
Copy link
Member

sebcrozet commented Feb 26, 2017

The traits you are proposing for vectors are interesting and I would be willing to add them (under the name FloatVector and NumVector though).

Is there any specific reason (or issue you might have faced) why you implement them for specific vector types instead of implementing for any type T ?

@arturoc
Copy link
Contributor Author

arturoc commented Feb 27, 2017

with any type T do you mean implementing them for VectorN<T,D>? that would make sense, i think i just did it like this cause the requirements for the D in VectorN are also really hard to get right.

btw there was an error in the original post where NumVector was only accepting real numbers, the correct implementation would be:

pub trait BaseNum: Scalar +
                   alga::general::Identity<alga::general::Additive> +
                   alga::general::Identity<alga::general::Multiplicative> +
                   num::Zero +
                   num::One +
                   Add<Self, Output = Self> + Sub<Self, Output = Self> +
                   Mul<Self, Output = Self> + Div<Self, Output = Self> +
                   Rem<Self, Output = Self> +
                   AddAssign<Self> + SubAssign<Self> +
                   MulAssign<Self> + DivAssign<Self> +
                   RemAssign<Self> +
                   PartialOrd +
                   'static{
}

impl BaseNum for i8 { }
impl BaseNum for i16 { }
impl BaseNum for i32 { }
impl BaseNum for i64 { }
impl BaseNum for isize { }
impl BaseNum for u8 { }
impl BaseNum for u16 { }
impl BaseNum for u32 { }
impl BaseNum for u64 { }
impl BaseNum for usize { }
impl BaseNum for f32 { }
impl BaseNum for f64 { }

pub trait BaseInt: BaseNum +
                   Shl<Self, Output = Self> +
                   ShlAssign<Self> +
                   Shr<Self, Output=Self> +
                   ShrAssign<Self>{}

impl BaseInt for i8 { }
impl BaseInt for i16 { }
impl BaseInt for i32 { }
impl BaseInt for i64 { }
impl BaseInt for isize { }
impl BaseInt for u8 { }
impl BaseInt for u16 { }
impl BaseInt for u32 { }
impl BaseInt for u64 { }
impl BaseInt for usize { }

/*
 * Vector related traits.
 */
/// Trait grouping most common operations on vectors.
pub trait NumVector<N>: Add<Self, Output = Self> + Sub<Self, Output = Self> +
                        // Mul<Self, Output = Self> + Div<Self, Output = Self> +

                        // Add<N, Output = Self> + Sub<N, Output = Self> +
                        Mul<N, Output = Self> + Div<N, Output = Self> +

                        AddAssign<Self> + SubAssign<Self> +
                        // MulAssign<Self> + DivAssign<Self> +

                        // AddAssign<N> + SubAssign<N> +
                        MulAssign<N> + DivAssign<N> +

                        alga::general::Identity<alga::general::Additive> +
                        PartialEq
                        where Self: Sized {
}

/// Trait of vector with components implementing the `BaseFloat` trait.
pub trait FloatVector<N: alga::general::Real>: NumVec<N> +
                        alga::linear::NormedSpace +
                        Neg<Output = Self> +
                        Axpy<N> +
                        alga::linear::FiniteDimVectorSpace +
                        alga::linear::FiniteDimInnerSpace {
}


impl<N: BaseNum> NumVector<N> for Vector1<N>{}
impl<N: BaseNum> NumVector<N> for Vector2<N>{}
impl<N: BaseNum> NumVector<N> for Vector3<N>{}
impl<N: BaseNum> NumVector<N> for Vector4<N>{}
impl<N: BaseNum> NumVector<N> for Vector5<N>{}
impl<N: BaseNum> NumVector<N> for Vector6<N>{}

impl<N: BaseNum + alga::general::Real> FloatVector<N> for Vector1<N>{}
impl<N: BaseNum + alga::general::Real> FloatVector<N> for Vector2<N>{}
impl<N: BaseNum + alga::general::Real> FloatVector<N> for Vector3<N>{}
impl<N: BaseNum + alga::general::Real> FloatVector<N> for Vector4<N>{}
impl<N: BaseNum + alga::general::Real> FloatVector<N> for Vector5<N>{}
impl<N: BaseNum + alga::general::Real> FloatVector<N> for Vector6<N>{}

pub trait NumPoint<N>: // Sub<Self, Output = NumVector<N>> + //TODO: Output Vec
                        // Mul<Self, Output = Self> + Div<Self, Output = Self> +

                        // Add<N, Output = Self> + Sub<N, Output = Self> +
                        Mul<N, Output = Self> + Div<N, Output = Self> +

                        // MulAssign<Self> + DivAssign<Self> +

                        // AddAssign<N> + SubAssign<N> +
                        MulAssign<N> + DivAssign<N> +
                        PartialEq
                        where Self: Sized {
}

pub trait FloatPoint<N: alga::general::Real>: NumPoint<N> +
                        Neg<Output = Self> +
                        Axpy<N> +
                        alga::linear::AffineSpace +
                        alga::linear::EuclideanSpace {
}


impl<N: BaseNum> NumPoint<N> for Point1<N>{}
impl<N: BaseNum> NumPoint<N> for Point2<N>{}
impl<N: BaseNum> NumPoint<N> for Point3<N>{}
impl<N: BaseNum> NumPoint<N> for Point4<N>{}
impl<N: BaseNum> NumPoint<N> for Point5<N>{}
impl<N: BaseNum> NumPoint<N> for Point6<N>{}

impl<N: BaseNum + alga::general::Real> FloatPoint<N> for Point1<N>{}
impl<N: BaseNum + alga::general::Real> FloatPoint<N> for Point2<N>{}
impl<N: BaseNum + alga::general::Real> FloatPoint<N> for Point3<N>{}
impl<N: BaseNum + alga::general::Real> FloatPoint<N> for Point4<N>{}
impl<N: BaseNum + alga::general::Real> FloatPoint<N> for Point5<N>{}
impl<N: BaseNum + alga::general::Real> FloatPoint<N> for Point6<N>{}

@milibopp
Copy link
Collaborator

@arturoc have you been doing anything further on this issue?

It might be worth starting with the specialized impls and then replacing them with the more general version, once it is clear, how to do that. That should be a compatible extension, as far as I can see.

@arturoc
Copy link
Contributor Author

arturoc commented Aug 1, 2017

i have something working here:

https://github.com/arturoc/na/blob/master/src/lib.rs#L397-L526

that is a library i did a while ago when nalgebra changed to long names (Vec3 -> Vector3...) since i had a large codebase using the old naming and i wasn't sure i wanted to change everything to the new naming so that library wraps nalgebra to keep the old short names for the elements most used in graphics programming.

if there's any interest i can create a pull request for the part related to this (of course using long names not the ones i have)

apart from that over time i've also implemented:

  • fast versions of multiplication, inverse... by explicitly doing it for the most common types instead of relying on iterators, there was a more general way to solve it implementing it for the storage but the template system was too hard to work with at that point so i just implemented some methods that you have to call explicitly. I suspect this is already solved from the comments from @sebcrozet about performance a couple of days ago anyway.

  • swizzles: so you can call:

let xy = v3.xy()
let xz = v3.xz()
....

which work for Vector2/3/4 and Point2/3/4

  • Macros to easily create vectors:
let one = vec3!(1.); //sets all components to 1
let composed = vec3!(v2, z); // creates a Vector3 from a Vector2 and a float
...

I can create a PR for any of those features if they seem interesting or feel free to pull them from my repo and adapt them if you prefer

@milibopp
Copy link
Collaborator

milibopp commented Aug 1, 2017

It might be of interest, if it facilitates generic programming. How does this relate to our existing integration with num_traits? It seems like at least part of what is proposed here is already covered by that, is it not?

Regarding the other features, I would just open a new issue and see whether there is general interest in it. It seems nice in terms of ergonomics, but I wonder how often it would really be used.

@arturoc
Copy link
Contributor Author

arturoc commented Aug 1, 2017

How does this relate to our existing integration with num_traits

I haven't followed that but most probably is already covered then (can you point me to it?) i did this a while ago and never sent a PR cause i wasn't sure it was complete, i've been using it for a while now and haven't had any trouble.

@milibopp
Copy link
Collaborator

milibopp commented Aug 1, 2017

You can find num_traits on crates.io and GitHub. I just had a look at how far the integration actually goes, but it seems we are only using Zero, One and Bounded at the moment. It also has a Float and Num traits that may be useful for this. It's basically all the experimental stuff that used to be in std::num before Rust 1.0.

We could make use of these existing abstractions instead of creating our own, where possible. That's also more interoperable with other crates providing generic code for these abstractions.

@grtlr
Copy link
Contributor

grtlr commented Nov 12, 2018

Recently, I have tried to create generic structs that contain MatrixN and VectorN members. With the current traits this gets complicated really quickly. Are there any new features on the horizon of Rust that will make things easier?

I'm trying to achieve things similar to this post: https://discourse.nphysics.org/t/using-nalgebra-in-generics/90/3

@sebcrozet
Copy link
Member

@grtlr Right now the best you can do is the solution based on DefaultAllocator bounds:

extern crate nalgebra as na;
use na::{DefaultAllocator, Scalar, Dim, VectorN, MatrixN};
use na::allocator::Allocator;

fn main() {}

struct NPoint<N: Dim>(VectorN<usize, N>)
    where DefaultAllocator: Allocator<usize, N> ;

struct Container<T: Scalar, D: Dim>
    where DefaultAllocator: Allocator<T, D, D>
{
    x: MatrixN<T, D>
}

The two features from the language that would significantly improve this are const-generics and specialization. AFAIK const-generics are being worked on actively: rust-lang/rust#53645. Not sure about specialization though.

@nravic
Copy link

nravic commented Dec 7, 2019

@sebcrozet Sorry for resuscitating this thread. How would you go about making a generic implementation of a struct like this?

struct Container<T: Scalar, D: Dim>
    where DefaultAllocator: Allocator<T, D, D>
{
    x: MatrixN<T, D>
}

Is it possible?

Thanks!

@sebcrozet
Copy link
Member

Hi @nravic! Yes, such a container is possible as long as you have the right trait bounds. The struct you are suggesting is the right way of doing it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants