dimforge / alga

Abstract algebra for Rust.
194 stars 39 forks source link

Are the real numbers a normed space? #89

Open mgeier opened 5 years ago

mgeier commented 5 years ago

I'm very much not a mathematician, nor am I a Rust expert, so please forgive me if this is a silly question.

I'm working on a spline implementation which I will mostly use for 3D curves, but I would also like it to work for 2D curves and for 1D (I don't know if those can still be called "curves").

For 2D and 3D I'm using vector types from the nalgebra crate (which implement several alga traits).

Since my implementation is supposed to work on any vector space that has a norm, I think it would make sense to use the alga::linear::NormedSpace trait bound.

For the 1D case I could of course use a 1D vector from nalgebra, but I think it would be much nicer if I could also use a "normal" scalar type, like e.g. f64.

This doesn't work though, because NormedSpace doesn't seem to be implemented for f64 (it looks like it's only implemented for Complex<N>).

Now my question: are real numbers a normed space? I would think so, but what do I know ...

If that makes at least some mathematical sense, would it be possible to implement NormedSpace for scalars?

I'm thinking about something like this:

impl<T: RealField> NormedSpace for T {
    type RealField = T;

    ...

    fn norm(&self) -> T {
        self.abs()
    }

    ...
}

Or probably this should use ComplexField instead?

WaDelma commented 5 years ago

There should be implementation of NormedSpace for f32 and f64: https://github.com/rustsim/alga/blob/dev/alga/src/linear/vector.rs#L286

WaDelma commented 5 years ago

And yes real numbers form a normed space over multiple different norms.

(I wouldn't call floating point numbers real numbers, but it is what it is.)

mgeier commented 5 years ago

Thanks for the answer, I'm glad that my question wasn't complete nonsense ...

It turns out that the implementation in question was added in #86, only 13 days ago! I was using the latest alga release, that's why I didn't see it.

Using the dev branch I can now do what I was asking for, but I'm still wondering: Wouldn't it be possible to add this as a blanket implementation for all types T that implement alga::general::ComplexField (instead of just the concrete implementations for f32 and f64)?

And since ComplexField is already implemented for num_complex::Complex, wouldn't this also make the implementation of NormedSpace for Complex (https://github.com/rustsim/alga/blob/dev/alga/src/linear/vector.rs#L292-L339) obsolete?

sebcrozet commented 5 years ago

Wouldn't it be possible to add this as a blanket implementation for all types T that implement alga::general::ComplexField

I believe this would prevent implementation for, e.g., Vector3<T> because writing impl VectorSpace for Vector3<T> would be detected as a conflicting impl by the compiler (even though it does not actually conflicts with the blanket impl).

mgeier commented 4 years ago

Sorry for the late response!

I came up with a simplified example where I think the aforementioned blanket implementation is missing:

use alga::general::RealField;
use alga::linear::VectorSpace;

struct SomeStruct<S: RealField, V: VectorSpace<Field = S>> {
    s: S,
    v: V,
}

struct LimitedStruct<S: RealField> {
    inner: SomeStruct<S, S>,
}

playground link

This fails to compile:

error[E0277]: the trait bound `S: alga::linear::vector::VectorSpace` is not satisfied

I don't know how this could be implemented, but I think theoretically this should work, because anything that is a RealField should also be a (albeit trivial) VectorSpace, shouldn't it?

How could I make this work?

In case you are wondering what my concrete application is, you can have a look at https://github.com/AudioSceneDescriptionFormat/asdfspline-rust/blob/master/src/monotonecubicspline.rs. There I have a MonotoneCubicSpline, which only really makes sense with scalar values, but I would like to implement it based on the more generic PiecewiseCubicCurve, which is implemented for vector values.

mgeier commented 4 years ago

I still think a blanket implementation would make sense (if it would be possible), but in the meantime, there is a work-around for my example above, just adding the "vector" requirement to the "scalar" type:

struct LimitedStruct<S: RealField + VectorSpace<Field = S>> {
    inner: SomeStruct<S, S>,
}

To me, this looks a bit recursive (S appearing in the trait bounds of S), but the compiler seems to accept it.

In my actual use case this got a bit too complicated (I've not fully implemented it, so I don't know whether it would have actually worked), so I came up with an alternative approach:

I just added a new type parameter F: Fn(V) -> S which is used to pass/store a function that calculates the norm of a vector of type V. Now a user has to manually pass this function in a few places and handle an additional type parameter, both of which is a little annoying (but it works). The big advantage of this approach, though, is that now a user can implement a "norm" function for any foreign vector type without violating Rust's orphan rule.

mgeier commented 4 years ago

Sorry for the comment-spam, but just after posting my previous comment, I've read https://blog.mgattozzi.dev/orphan-rules/, which explains a nice work-around to avoid the limitations of Rust's orphan rule.

I've implemented this in my project and it works very well, for details see this commit: https://github.com/AudioSceneDescriptionFormat/asdfspline-rust/commit/c75463a1c084c38d8cb06c4f3ee3025fba71b074.

Instead of providing a function multiple times (as in my above-mentioned work-around), the user creates a dummy struct (to have something to implement a trait on) and implements a trait (involving their dummy struct) for the type in question that should get a norm() function:

pub struct DummyVec3;

impl NormWrapper<DummyVec3> for Vec3 {
    type Norm = f32;

    fn norm(&self) -> Self::Norm {
        self.norm()
    }
}

I think in my case this work-around is nicer than the function-passing work-around. There are very few situations where the dummy struct has to be specified with a turbofish (which is a little ugly), but most of the times, everything is deduced automatically.