ANYbotics/kindr

Model neutral SO3 parametrizations - semantics of parameter

Opened this issue · 18 comments

I guess we can easy agree on:

   RotMatrix(C).apply(v) := C * v

, with * is the matrix product.

But what is with (it is basically the question about active and passive but for the definitions of parameters fo SO3)

  1. AngleAxis(90deg, x).apply(y) or RotVec(90deg * x).apply(y)
    1. z
    2. -z
  2. EulerAnglesXYZ(90deg, 0, 0).apply(y)
    1. z
    2. -z
  3. RotQuatRealFirst(sqrt(1/2), sqrt(1/2), 0, 0).apply(y) (should also be a 90deg turn around the x-axis)
    1. z
    2. -z

Here is a way out of all of this:

A parameterization must define the semantics of two things:

  1. Conversion back and forth to a rotation matrix
  2. The underlying storage.

Everything else is governed by the rules of a rotation matrix.

I think we should disallow constructors from storage and from scalars and have all construction handled by either (a) conversion from another SO3 type, or (b) factory functions that make the above very clear.

Then, as we implement this new interface, we should come up with a way to document these factories such that there is no ambiguity. I'm open for suggestions on how to call these functions.

Quaternion q1(Quaternion::wxyzJpl( w, x, y, z ));
Quaternion q2(Quaternion::wxyzHamilton( w, x, y, z ));
Quaternion q3(AngleAxis::rotateVectorAroundAxis( angle, axis ));
Quaternion q4(AngleAxis::rotateFrameAroundAxis( angle, axis ));

Documentation of these functions should always include a few numerical examples of converting to a rotation matrix and back.

I guess then, every constructor function should also have a retrieval function:

Vec4d p1 = Quaternion::asWxyzJpl(q1);
Vec4d p2 = Quaternion::asWxyzHamilton(q2);
std::pair<double, Vec3d> p3 = AngleAxis::asRotateVectorAroundAxis( q3 );
std::pair<double, Vec3d> p4 = AngleAxis::asRotateFrameAroundAxis( q4 );

I would not make these retrieval functions return the Storage type, but just define them directly.

No this is no way out of it at all :)!
You are just transforming the problem. I already boiled it down it to the core problem. Why make it more verbose again by talking about conversion to matrix? (the conversion follows automatically from this agreement)
I'm wasn't talking about documentation here, that we can make as verbose as we like. But this agreement we need to produce soon.

I vote for the versions as motivated by active physical intuition, and the usual right hand convention :
So 1.i, 2.i. and in addition with Hamilton mult, I get 3.i, but might be mistaken.

Ah, you are thinking of construction time - I'm fine with factories there. I was more talking about memory interpretation! For example, when a rot vector has three doubles stored, we need to implement its apply method somehow! - for that this was supposed to be.

Could we please have a separate issue about construction? This is really a separate problem. Unless we don't agree and have multiple parametrizations. But then we have recreated kindr with active and passive, but with less systematic names.

The user should be discouraged from thinking about what is being stored underneath.
If we have the constructors and documentation, all I care about is 3.i

Hm, ok. Then we can quickly agree :). But don't forget that people need to know the memory interpretation sometimes. And the whole functional interface is otherwise arbitrary. Imagine for example:

RotVec {
 typedef Vec3 Storage;
 Vec3 apply(Vec3 rotVec, Vec3  vec) {
   ???
 }
}

After we have fixed that, we can easily write those factories, but they are actually on a different layer towards modeling because they will be an interface starting to talk about frames and usage! The lowest layer does not need to do that. It is enough to define the apply methods or the conversions to matrix and fix the matrix application to simple matrix product. I would not even put them into the so3 folder. But have them in a different part of the library - the factory based interface to modeling. It would basically consist of these factories and would not provide model based type safety. An alternative on the same layer would be to have types referring to frames and active and passive usage, that would provide a similar service and would also be model-type safe.

I strongly disagree with this:

Quaternion q1(Quaternion::wxyz_JPL( w, x, y, z ));
Quaternion q2(Quaternion::wxyz_hamilton( w, x, y, z ));
Quaternion q3(AngleAxis::rotateVectorAroundAxis( angle, axis ));
Quaternion q4(AngleAxis::rotateFrameAroundAxis( angle, axis ));

I really liked kindr because when you read the code, you can exactly tell what is going on by only looking at the type of the rotation object.

I would strongly prefer to have different types rather than functions.

HamiltonianQuaternionPassive<double> q1(w, x, y, z); // this is much easier for a student to use
JPLQuaternionPassive<double> q2(w, x, y, z); 
AngleAxisPassive<double>
RotationVectorPassive<double>
EulerAnglesZyxPassive<double>
EulerAnglesXyzPassive<double>

I can probably acquiesce on the type names but not on the constructor.

HamiltonianQuaternionPassive<double> q1( HamiltonianQuaternionPassive<double>::wxyz(w,x,y,z) );

Question:

Now that we are using rotation matrix semantics, what is the difference between a HamiltonianQuaternionPassive and a HamiltonianQuaternionActive? With rotation matrix semantics, does a HamiltonianQuaternionPassive == JplQuaternionActive?

As expected, on this layer it is hard to agree and I would really suggest having multiple! This looks like two interfaces layers basically containing almost nothing (in terms of code). Only factory methods calling storage constructors (paul's) or constructors calling storage constructors (Christian's).

This is exactly why I propose to not touch modeling with the core of the library, we currently try to start with! And it is, why I started this issue. Please try to understand what this issue was originally meant to be about and for the model affine interface please consider having multiple interfaces. We won't agree there! And we can even treat/sell as research to compare those and to fully understand what an independent core is / could be.

I guess we still have a misunderstanding. Maybe we should talk on Monday.

I would have said that we use the "passive" view since we all work with that operation.

My understanding would have been:

[0, 0, -1]' = DirectionCosineMatrix([1, 0, 0; 0 0, 1; 0 -1 0]).apply([0, 1, 0]')
= AngleAxis(90deg, x).apply([0, 1, 0]')
= EulerAnglesXYZ(90deg, 0, 0.apply([0, 1, 0]')
= RotQuatRealFirst(sqrt(1/2), sqrt(1/2), 0, 0).apply([0, 1, 0]')

The problem here is that to do that you need to start talking about multiple frames. Otherwise it makes no sense. I would try to work in one space only: in the tuple space of Vec3. There one would not know about anything like DirectCosineMatrix but there one would already know how a 3x3 proper orthogonal matrix (=SO3) operates (by matrix multiplication) and one would know how a unit-quaternion rotates (after deciding one the mult) - and by identifying action one would already be able to convert between matrix and quat.

The only open question would be what is an AngleAxis and an EulerAngles parametrization of the same actions. This is apriory arbitrary as these physically motivated constructions have no relation to Vec3, without introducing frames - which we wouldn't.

The only thing we have in that setting is to look at the apply or convert formulas.

And there when you use the passive concepts you always include an unnecessary inversion somehow. Just exactly as when one thinks of how to apply an Passive Angel Axis - you first think of the physical intuition, which is active, and then you invert to have it passive. But why have that in a context where it is arbitrary because there are no frames?

This one can also find in the final formulas: think of rotation vector to matrix. If you use passive rotation vector definition for a Vec3, v, and a simple matrix multiplication as basis for the matrix, M, based rotation then you will end up with :

M = exp(-v^x)

(^x is the map to the skew symmetric matrices)
Why the minus, when we could have equally well M=exp(v^x)?
As we are not discussing model orientated interfaces but purely SO(3) parametrization we should not consider anything else than simplicity. And that is exactly the strength of this separation!
But yes, we can discuss that soon.

Hannes, I 100% agree with you!

I also agree with this concept. I'm just wondering how you can realize this. I will wait for the prototype.