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

fix geodesic for SO(2) #408

Merged
merged 25 commits into from
May 17, 2018
Merged

fix geodesic for SO(2) #408

merged 25 commits into from
May 17, 2018

Conversation

aditya-vk
Copy link
Contributor

@aditya-vk aditya-vk commented May 10, 2018

This PR restricts the SO(2) state representation to lie between +pi and -pi.

  1. This ensures planning, for instance with SnapPlanner, between two angles gives the geodesic.
  2. Is similar to other statespace representations [bijective with transformation matrix representation].

This will possibly require keeping a track of another parameter to know what phase of rotation the joint is in?

Looking for proposals and suggestions on handling this in the cleanest possible way [while keeping in mind the trajectories sent to the robot].

(Did not modify tests yet.)


Before creating a pull request

  • Document new methods and classes
  • Format code with make format

Before merging a pull request

  • Set version target by selecting a milestone on the right side
  • Summarize this change in CHANGELOG.md
  • Add unit test(s) for this change

@aditya-vk aditya-vk added the pr: discussion For discussion or proposals. If it passes to state of implementation, replace with WIP. label May 10, 2018
@aditya-vk aditya-vk requested review from gilwoolee and jslee02 May 10, 2018 17:53
@@ -37,8 +37,12 @@ trajectory::InterpolatedPtr planSnap(
}
}

// Redefine the goal state
Copy link
Contributor Author

@aditya-vk aditya-vk May 10, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

~This shouldn't be necessary, will be removed.

@aditya-vk aditya-vk added this to the Aikido 0.3.0 milestone May 10, 2018
@mkoval
Copy link
Member

mkoval commented May 11, 2018

I don't think this is a good solution to the stated problem.

This ensures planning, for instance with SnapPlanner, between two angles gives the geodesic.

There is no mapping from SO(2) from R that has this property.

Is similar to other statespace representations [bijective with transformation matrix representation].

I don't see any similarity here. The whole point of the Aikido StateSpace class is to make our algorithms independent of our choice of parameterization. We do this by operating on opaque State objects using Lie group operations, rather than operating on raw Eigen vectors.


Our current implementation of GeodesicInterpolator looks good to me. I don't see why it would behave incorrectly in SO(2). What is the behavior that you are seeing?

@gilwoolee
Copy link
Contributor

gilwoolee commented May 11, 2018

I don't agree with Aditya's current proposal, but I can discuss the problem we have with the current SO2 implementation.

I also agree with @mkoval that there's nothing wrong with current GeodesicInterpolator; it's just the SO2 space.

Here's the problem with our current SO2 implementation. We keep double mAngle as our internal representation, and for inverse, compose and logMap we simply take -mAngle, mAngle1 + mAngle2 and mAngle. If we kept Matrix2d rot as our representation, the operation would've been rot^-1, rot1 * rot2, logMap(rot).

Suppose we do geodesic interpolation from SO2(2*pi) to SO2(0). This should be a no-travel. Consider the halfway point. The tangent vector we get for from->to direction in our current operation is -2pi, so the half-way point ends up being pi.

Here are two tests that fail with the current implementation:
https://github.com/personalrobotics/aikido/blob/bug/SO2/tests/statespace/test_SO2.cpp#L125
https://github.com/personalrobotics/aikido/blob/bug/SO2/tests/statespace/test_SO2.cpp#L150

I am ok with the first one (getAngle inequality), but the 2nd one is definitely an issue.

Maybe I'm misunderstanding @mkoval 's comment here:

There is no mapping from SO(2) from R that has this property.

@aditya-vk
Copy link
Contributor Author

aditya-vk commented May 11, 2018

I do not think there is anything wrong with the GeodesicInterpolator either. As @gilwoolee mentioned, the problem's with the SO(2) statespace. The underlying representation is currently an unbounded scalar value representing the angle of rotation while we expect the angle to wrap-around. The issue is in determining the tangentVector which turns out to be target - source in the current implementation, without regard to the right direction.

There are two ways to solve this right.

  1. We could clamp the angle to always lie between -pi and pi, which will lead to loss of some information, but we are guaranteed that it'll be sound with the logic in the geodesic interpolator i.e the direction of interpolation will always be correct. To add to Gilwoo's example, consider the case when my source and target are pi/6 and 11pi/6 (~ -pi/6). The current implementation would interpolate along 11pi/6 - pi/6 while it needs to interpolate the other way. [ This has been my running example :) ]. By clamping it we will have interpolated along -pi/6 - (pi/6) which is what's expected.

  2. We could use a cleaner rotation matrix representation to do the same thing but I am yet to completely trust Eigen's Rotation2D.angle() for that [shall do]. Eigen 3.3 introduced Rotation2D.smallestAngle() which made me wary of the former, hence the quick proposal with the clamp which achieves the same thing.
    a) Eigen 3.2 which is the default for Trusty:
    http://eigen.tuxfamily.org/dox-3.2/classEigen_1_1Rotation2D.html
    b) Eigen 3.3:
    http://eigen.tuxfamily.org/dox/classEigen_1_1Rotation2D.html

  3. I do not have a deep understanding about the exp/logMap, at least not as much, but according to @jslee02 and @gilwoolee, the fix might need to be in logMap. I had a brief discussion with @jslee02 about it this evening and he had thoughts I could not wrap my head around at the moment. Maybe he can explain this part?

@gilwoolee thanks for the tests! I should have started with that :)

@jslee02
Copy link
Member

jslee02 commented May 11, 2018

The whole point of the Aikido StateSpace class is to make our algorithms independent of our choice of parameterization.

This is true. The only problem I can see is SO2::logMap(). The current implementation (not of this PR) returns the parameter (i.e., rotation angle) as it is, which can lead to none geodesic interpolation in some cases.

Essentially the output of logMap(R) encodes the tangential direction at the origin of SO(2) to travel from identity to R so that R = exp(t*logMap(R)), t = 1, and GeodesicInterpolator simply interpolates from a point to a point following the direction. That said, logMap(R) should return the "geodesic" tangent vector to achieve geodesic behavior from using GeodesicInterpolator.

The criteria for logMap of SO(2) I think is that:

  • the length of the image is 2pi (e.g., [0, 2*pi) or (-pi, pi]) so that it becomes bijective,
  • the range of the image is in (-pi, pi] (or [-pi, pi)) so that one can travel to any R through geodesic.

For example, the range of [0, 2*pi) would make GeodesicInterpolator always interpolates counterclockwise even for rotation from 0 to -pi/6, which is not geodesic.

Also, I think the current function names of get/setAngle can confuse people because it sounds like angle is a properties so that the user can get the exactly same angle as it's set. However, as @mkoval pointed out, SO2 is parameter agnostic. That means which representation to use is the implementation detail (it can be 2x2 matrix or angle with different ranges), and setAngle is just a helper function to set SO(2) from angle, which means it sets a SO(2) state using a angle. It'd be good if the function name denotes the differences (setting property vs converting from an angle to a SO(2) point). Similarly, getAngle needs to be changed, and which range to return is our choice (or we can specify which range to use).

All putting together, here is my proposal:

  1. Change SO2::logMap() to return a scalar in (-pi, pi].
  2. Change the name of SO2::setAngle() to SO2::fromAngle().
  3. Change the name of SO2::getAngle() to SO2::toAngle().
  4. Change SO2::toAngle() to return a scalar in (-pi, pi] documenting this explicitly.
  5. [optional] Add option to SO2::toAngle() that specifies the range the user want.

@aditya-vk
Copy link
Contributor Author

aditya-vk commented May 11, 2018

Thanks @jslee02 ! 👍
The implementation in this PR currently handles 1 and 4 which ensures the correct behavior. Let me change the function names for more clarity, and add some tests for us to be convinced some corner cases aren't missed. :)

@codecov
Copy link

codecov bot commented May 12, 2018

Codecov Report

Merging #408 into master will increase coverage by 0.11%.
The diff coverage is 93.54%.

@@            Coverage Diff             @@
##           master     #408      +/-   ##
==========================================
+ Coverage   79.56%   79.68%   +0.11%     
==========================================
  Files         231      231              
  Lines        5955     5960       +5     
==========================================
+ Hits         4738     4749      +11     
+ Misses       1217     1211       -6
Impacted Files Coverage Δ
src/distance/SO2Angular.cpp 100% <ø> (ø) ⬆️
include/aikido/statespace/SO2.hpp 100% <ø> (ø) ⬆️
src/constraint/uniform/SO2UniformSampler.cpp 100% <100%> (ø) ⬆️
src/statespace/dart/SO2Joint.cpp 100% <100%> (ø) ⬆️
include/aikido/statespace/detail/SO2-impl.hpp 100% <100%> (ø) ⬆️
src/statespace/SO2.cpp 94.2% <92.45%> (+9.82%) ⬆️

@aditya-vk
Copy link
Contributor Author

Updated for further discussion. 👍

Copy link
Member

@jslee02 jslee02 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The current implementation looks good to me. I have some nitpicks though.

Also, it'd be great if you could add high-level comments to the tests.

@@ -71,78 +34,108 @@ class SO2 : virtual public StateSpace
/// \return new \c ScopedState
ScopedState createState() const;

/// Gets state as a rotation angle.
/// Returns state as a rotation angle.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Let's describe the return angle is in the range of (-pi, pi].

/// \param _angle rotation angle
void setAngle(State* _state, double _angle) const;
/// \param[in] angle Rotation angle.
/// \param[out] state State corresponding to angle
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Let's order the argument docstring as the corresponding arguments are ordered.

/// \return rotation angle
double getAngle(const State* _state) const;
/// \param[in] state State.
double toAngle(const State* state) const;

/// Sets state to a rotation angle.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Let's update the description something like "Sets state from a rotation angle.".


/// Gets state as an Eigen transformation.
/// Returns state as an Eigen transformation.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Eigen rotation.?

void setRotation(State* _state, const Eigen::Rotation2Dd& _rotation) const;
/// \param[in] rotation Eigen transformation.
/// \param[out] state State corresponding to rotation.
void fromRotation(State* state, const Eigen::Rotation2Dd& rotation) const;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's order the docstring as the argument listed.

~State() = default;

/// Returns state as a rotation angle.
double toAngle() const;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Let's inform that the range of the return angle.

void fromRotation(const Eigen::Rotation2Dd& rotation);

private:
double mAngle;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Please add docstring.

if (_state1 == _out || _state2 == _out)
throw std::invalid_argument("Output aliases input.");
#ifndef NDEBUG // Debug mode
assert(state1 != out && state2 != out);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know why we wanted to disable this in release mode, but it seems we need this precondition check (using exception) in both modes according to the docstring.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch 👍

throw std::invalid_argument("Output aliases input.");
#ifndef NDEBUG // Debug mode
assert(out != in);
#endif
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto with SO2::compose().

}
#ifndef NDEBUG // Debug mode
assert(tangent.rows() == 1);
#endif
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, the original warning message seems useful. Let's keep it.

@aditya-vk aditya-vk added pr: work-in-progress Calls for preliminary review of the PR. Remove when PR is ready for complete review. and removed pr: discussion For discussion or proposals. If it passes to state of implementation, replace with WIP. labels May 14, 2018
@aditya-vk
Copy link
Contributor Author

Sure. I addressed the minor comments. I'll add a few more tests for catching invalid arguments and notify by removing the WIP label. Thanks!

double boundedAngle = std::fmod(angle, 2.0 * M_PI);
if (boundedAngle > M_PI)
boundedAngle -= 2.0 * M_PI;
if (boundedAngle < -M_PI)
Copy link
Contributor

@brianhou brianhou May 14, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like this permits boundedAngle to be -pi, while the documentation says that it is bounded in (-pi, pi].

if (_out == _in)
throw std::invalid_argument("Output aliases input.");
if (out == in)
throw std::invalid_argument("Output aliases input");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Undo deleted period?

gilwoolee
gilwoolee previously approved these changes May 14, 2018
Copy link
Contributor

@gilwoolee gilwoolee left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good overall. Added some minor comments.

/// \param _angle rotation angle
void setAngle(State* _state, double _angle) const;
/// \param[out] state State corresponding to angle
/// \param[in] angle Rotation angle.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we specify the range for angle here? (If it allows (-inf, inf), mention that)


/// Sets state from a rotation angle.
///
/// \param[in] angle Rotation angle
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above, specify the range for angle.

@@ -25,9 +25,9 @@ double SO2Angular::distance(
const aikido::statespace::StateSpace::State* _state2) const
{
// Difference between angles
double diff = mStateSpace->getAngle(
double diff = mStateSpace->toAngle(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Are we correcting the variable names in this PR or not? (_state)

Copy link
Contributor Author

@aditya-vk aditya-vk May 15, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was doing it only in the SO2.cpp/hpp since I was mostly modifying them for this PR. I think I'll leave this for another PR and just have SO2 stuff cleaned up in code style.

@@ -8,38 +8,59 @@ using Eigen::Rotation2Dd;

static constexpr double TOLERANCE{1e-6};

TEST(SO2, BoundedAngle)
{
SO2::State s1(3 * M_PI_2);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would add tests to check the exact bounds -pi and pi as well.

jslee02
jslee02 previously approved these changes May 15, 2018
Copy link
Member

@jslee02 jslee02 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me.

@aditya-vk aditya-vk removed the pr: work-in-progress Calls for preliminary review of the PR. Remove when PR is ready for complete review. label May 15, 2018
@jslee02
Copy link
Member

jslee02 commented May 15, 2018

@aditya-vk Please don't forget to update the changelog for completeness.

@mkoval
Copy link
Member

mkoval commented May 16, 2018

👍 the latest round of changes look good to me. Thanks @aditya-vk for the great work and to @jslee02 for the excellent summary of the situation as it stood before this pull request.

jslee02
jslee02 previously approved these changes May 16, 2018
@aditya-vk aditya-vk merged commit 2259e04 into master May 17, 2018
@aditya-vk aditya-vk deleted the bugfix/avk/so2 branch May 18, 2018 01:42
gilwoolee pushed a commit that referenced this pull request Jan 21, 2019
* restrict so2 between -pi to pi + cleanup

* make format

* WIP: changes in progress

* proposal complete

* cascade updates to function names

* test the new changes to SO2 space

* cascade update of change in function names

* make format

* corrections in test_SO2

* correct test_SO2Angular

* make format

* remove unnecessary code block

* correct test in test_metaSkeletonStateSpace

* address JS's comments

* address Gilwoo's comments

* add tests for when functions throw

* update log

* Fix incorrect merging of previous commit
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants