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

Adding another kinodynamic timer #443

Merged
merged 27 commits into from
Aug 2, 2018
Merged

Conversation

dqyi11
Copy link
Contributor

@dqyi11 dqyi11 commented Jun 11, 2018

This PR introduces another kinodynamic timer by using a time-optimal trajectory generation algorithm, which is developed by Tobias Kunz.
Code: https://github.com/tobiaskunz/trajectories
Publication: T. Kunz, M. Stilman. Time-Optimal Trajectory Generation for Path Following with Bounded Acceleration and Velocity. Robotics: Science and Systems (RSS), 2012. http://www.roboticsproceedings.org/rss08/p27.html

This type of timer accepts both Interpolated and Spline paths. It firstly converts a non-differentiable linear-piecewise trajectory to a differentiable one by adding circular blends. It then applies a time-optimal exact path-following algorithm that maximizes the path velocities at every point along the path.

The preprocessing of adding circular blends might slightly change the shape of a path. However, unlike how the existing parabolic timer does, the timed trajectory does not make a complete stop (zero velocities) at each waypoint.

QUESTIONs:

  • Shall we create a postprocessing folder for all the postprocessing methods?

  • How are we going to enable Robot class call different timing/smoothing methods?

  • Document new methods and classes

  • Format code with make format

  • Set version target by selecting a milestone on the right side

  • Summarize this change in CHANGELOG.md

  • Add unit test(s) for this change

@dqyi11 dqyi11 added this to the Aikido 0.3.0 milestone Jun 11, 2018
@codecov
Copy link

codecov bot commented Jun 11, 2018

Codecov Report

Merging #443 into master will decrease coverage by 0.04%.
The diff coverage is 76.57%.

@@            Coverage Diff             @@
##           master     #443      +/-   ##
==========================================
- Coverage   79.01%   78.97%   -0.05%     
==========================================
  Files         256      258       +2     
  Lines        6185     6296     +111     
==========================================
+ Hits         4887     4972      +85     
- Misses       1298     1324      +26
Impacted Files Coverage Δ
include/aikido/planner/kunzretimer/KunzRetimer.hpp 100% <100%> (ø)
src/planner/kunzretimer/KunzRetimer.cpp 76.36% <76.36%> (ø)
src/planner/ompl/CRRT.cpp 75.58% <0%> (-0.59%) ⬇️
src/trajectory/Spline.cpp 94.44% <0%> (+0.92%) ⬆️

@brianhou
Copy link
Contributor

I'll try to take a more detailed look over this PR in the next couple days, but I have one quick question: how does this differ from Mintos? My understanding was that we had an implementation in features/minTOS that was working on an early version of AIKIDO, so it seemed like it wouldn't have been too difficult to update.

Shall we create a postprocessing folder for all the postprocessing methods?

I think we can continue creating a namespace/directory underneath aikido::planner for each type of planner or postprocessor. If we decide we don't like it, we can always change it later.

How are we going to enable Robot class call different timing/smoothing methods?

I don't have a clear understanding of when we should use one or the other. Is it always better to use this over the parabolic smoother, which forces us to stop at waypoints? Or are there certain planning problems where we believe that the parabolic smoother would be better? If we should always use one, then I think we can continue doing what we're doing with constructing a single postprocessor in the robot constructor. Otherwise, I guess we can construct all the postprocessors in the constructor (similar to how we construct all the planners in the robot constructor) and then delegate accordingly.

@dqyi11
Copy link
Contributor Author

dqyi11 commented Jun 23, 2018

Thanks! @brianhou
I think that minTOS is going to be used as an alternative smoother that considers constraint manifolds and kinematic limitations. I am seeing them as different postprocessing methods by the changes in path geometry. ParabolicTimer stops at each waypoint so no geometric change is introduced. This timing method will add circular blend at each waypoint so the path geometry is adjusted to be differentiable. The change of path geometry can be kept under a tolerance threshold by the parameter maxDeviation. The ParabolicSmoother and upcoming minTOS will "optimize" the path geometry according to some criteria.

I suggest to keep different postprocessing methods. When we want a robot shall strictly follow a planned path (e.g. welding?), ParabolicTiming can be used. When we want a robot to keep the path shape with a tolerance (e.g. forward pushing?), this timing can be used. When we want a robot to generate an efficient and smooth motion, smoothers can be used.

So my original suggestion was whether we can have an aikido::planner::postprocessing namespace for these different postprocessing methods. The current aikido::planner::parabolic can be moved under that one.

@brianhou brianhou mentioned this pull request Jul 2, 2018
5 tasks
CHANGELOG.md Outdated
@@ -43,6 +43,7 @@
* Added flags to WorldStateSaver to specify what to save: [#339](https://github.com/personalrobotics/aikido/pull/339)
* Changed interface for TrajectoryPostProcessor: [#341](https://github.com/personalrobotics/aikido/pull/341)
* Planning calls with InverseKinematicsSampleable constraints explicitly set MetaSkeleton to solve IK with: [#379](https://github.com/personalrobotics/aikido/pull/379)
* Add a kinodynamic timer that generates a time-optimal smooth trajectory without completely stopping at each waypoint: [#443}(https://github.com/personalrobotics/aikido/pull/443)
Copy link
Contributor

Choose a reason for hiding this comment

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

Typo: [#433]

#include <aikido/common/Spline.hpp>
#include <aikido/common/StepSequence.hpp>

#include "Path.h"
Copy link
Contributor

Choose a reason for hiding this comment

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

@jslee02 Are relative paths to the external files okay here or should this be something else?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I would also like to know the standard. This one is following the style in HauserParabolicTimer.

Copy link
Member

@jslee02 jslee02 Jul 20, 2018

Choose a reason for hiding this comment

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

It seems this is the current convention unless we change the CMakeLists.txt files in the external directories because the include directories of the targets are exactly the directory where the files are located (e.g., <aikido_dir>/src/external/hauserparabolicsmoother/).

One potential problem with this approach is that the file name can be ambiguous when there are two files with the same name in the different external libraries. I have a proposal to resolve this problem:

src/
  external/
    lib1/
      CMakeLists.txt  # include ./include
      include/
        lib1/
      src/
    lib2/
      CMakeLists.txt  # include ./include
      include/
        lib2/
      src/

then we can include the files without conflicts over external libraries as

#include "lib1/same_name.h"
#include "lib2/same_name.h"

With this proposal, the path to the external headers in the external sources should be changed as the same way above. There is a way to avoid this modification, but I personally prefer this because the leading project name in the header inclusion would also allow the externals to avoid potential name conflicts when they also have dependency.

TL;DR: This is our current convention. I have a proposal, which shouldn't block this PR anyway.

{
Trajectory trajectory(
Path(waypoints, maxDeviation), maxVelocities, maxAccelerations, timeStep);
if (trajectory.isValid())
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's flip this to something like

if (!trajectory.isValid())
  return nullptr;
...

const aikido::constraint::TestablePtr& constraint = nullptr) override;

/// Returns the velocity limits of the dimensions
const Eigen::VectorXd& getVelocityLimits() const;
Copy link
Contributor

Choose a reason for hiding this comment

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

Are all these getters and setters useful? I'm not sure if they're necessary.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think the getters are useful. For example, when we want to validate whether a timed trajectory violates the limits but only have the access to the timer handle. I prefer to keep the setters as well. It provides a way of adjusting timer parameters without reconstructing a new timer object.

//==============================================================================
std::unique_ptr<aikido::trajectory::Spline>
createSplineFromWaypointsAndConstraints(
const std::list<Eigen::VectorXd>& waypoints,
Copy link
Contributor

Choose a reason for hiding this comment

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

Why a list over a vector?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Because the external codes take in a list of Eigen::VectorXd. If we use vector, we will either have to do an extra conversion or hack the external code.

}

double startTime = inputTrajectory.getStartTime();
// create waypoints from Interpolated path
Copy link
Contributor

Choose a reason for hiding this comment

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

I think it would be nice to create Eigen::VectorXd getWaypoints(trajectory::Interpolated traj) (and for splines), then call the appropriate function from here.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done! Two new functions are added.

std::unique_ptr<aikido::trajectory::Spline> KinodynamicTimer::postprocess(
const aikido::trajectory::Spline& inputTraj,
const aikido::common::RNG& /*rng*/,
const aikido::constraint::TestablePtr& /*constraint*/)
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we need to test that this constraint isn't being violated.

std::unique_ptr<aikido::trajectory::Spline> KinodynamicTimer::postprocess(
const aikido::trajectory::Interpolated& inputTraj,
const aikido::common::RNG& /*rng*/,
const aikido::constraint::TestablePtr& /*constraint*/)
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we need to test that this constraint isn't being violated.

inputTrajectory, Vector2d::Constant(2.), Vector2d::Constant(1.));

timedTrajectory->evaluate(1., state);
EXPECT_TRUE(Vector2d(1.0, 2.0).isApprox(state.getValue()));
Copy link
Contributor

Choose a reason for hiding this comment

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

I think there are some Eigen things in https://github.com/personalrobotics/aikido/blob/master/tests/eigen_tests.hpp that we could use instead? The tests would be more descriptive when they fail.

EXPECT_TRUE(Vector2d(0.5, 0.5).isApprox(tangentVector));

timedTrajectory->evaluateDerivative(3.0, 1, tangentVector);
// TODO: isApprox does not work when comparing against zero.
Copy link
Contributor

Choose a reason for hiding this comment

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

I think the same eigen_tests.hpp might also help here.

@brianhou
Copy link
Contributor

@dqyi11 It seems that #446 depends on this PR being merged first. Would you mind addressing the comments when you have the chance? Thanks!

@dqyi11
Copy link
Contributor Author

dqyi11 commented Jul 19, 2018

@brianhou Yes. I have started working on that. Will get that done soon.

/// profiles that implement bang-bang control. This function curently only
/// supports \c RealVector, \c SO2, and compound state spaces of those types.
/// Additionally, this function requires that \c inputTrajectory to be
/// interpolated using a \c GeodesicInterpolator.
Copy link
Contributor

Choose a reason for hiding this comment

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

Why does the input trajectory need to have been interpolated using GeodesicInterpolator? Why the explicit mention? Just curious. Any sequence of waypoints should be fine right?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think this is necessary. The interpolation implies the shape of a path given a sequence of waypoints. If you choose a different interpolator, the shape of a path will be different (length might also be changed). The timing algorithm works on the assumption of the geodesic interpolation.

/// \param[in] maxDeviation maximum deviation from a waypoint in doing circular
/// blending around the waypoint
/// \param[in] timeStep time step in following the path
/// \return time optimal trajectory that satisfies acceleration constraints
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: mention velocity constraints too? 🤔

@dqyi11
Copy link
Contributor Author

dqyi11 commented Jul 20, 2018

I would like to have suggestions about
(1) the current namespace aikido::planner::kinodynamic (or where the source codes shall be in); and
(2) the current name of the external source folder name optimal_time_path_following in aikido/src/external.

@brianhou
Copy link
Contributor

(1) I think kinodynamic might not be a good namespace, since we might want to have actual kinodynamic planners. What about optimal_retimer?

I agree that eventually it makes sense to move all the postprocessing methods into their own namespace, although we'll also have to decide whether that would be aikido::postprocessing or aikido::planner::postprocessing or something else.

(2) How about kunz_optimal_retimer?

@dqyi11
Copy link
Contributor Author

dqyi11 commented Jul 25, 2018

@brianhou I have updated the namespace and folder name as suggested.
I would suggest aikido::planner::postprocessing. My reason is that Planning, Control and Perception are currently three main modules in aikido. Postprocessing seems part of Planning. I think prpy uses the same structure.

Copy link
Contributor

@brianhou brianhou left a comment

Choose a reason for hiding this comment

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

Since we're now putting this in the optimalretimer namespace, do you think it would make more sense to rename KinodynamicTimer to OptimalRetimer and computeKinodynamicTiming to computeOptimalTiming?


//==============================================================================
std::unique_ptr<aikido::trajectory::Spline>
createSplineFromWaypointsAndConstraints(
Copy link
Contributor

Choose a reason for hiding this comment

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

In order to match the parabolic smoother implementation more closely, I think it would be helpful to replace this function with three functions in the detail namespace:

  • convertToKunzTrajectory(Interpolated traj, VectorXd maxVelocity, VectorXd maxAcceleration, double maxDeviation, double timestep)
  • convertToKunzTrajectory(Spline traj, VectorXd maxVelocity, VectorXd maxAcceleration, double maxDeviation, double timestep)
  • convertToSpline(Trajectory traj, double startTime, StateSpace stateSpace)

Then, computeOptimalTiming should basically look like computeParabolicTiming.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Great idea! I have refactored the functions.

@brianhou
Copy link
Contributor

Also, @jslee02 if you could take a look over this PR to check if there are any nits that we should fix before merging, that would be great! (I know you're super busy, so just whenever you get a chance 😄)

jslee02
jslee02 previously approved these changes Jul 26, 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.

LGTM! Just a few nitpicks.

CHANGELOG.md Outdated
@@ -47,6 +47,7 @@
* Added flags to WorldStateSaver to specify what to save: [#339](https://github.com/personalrobotics/aikido/pull/339)
* Changed interface for TrajectoryPostProcessor: [#341](https://github.com/personalrobotics/aikido/pull/341)
* Planning calls with InverseKinematicsSampleable constraints explicitly set MetaSkeleton to solve IK with: [#379](https://github.com/personalrobotics/aikido/pull/379)
* Add a kinodynamic timer that generates a time-optimal smooth trajectory without completely stopping at each waypoint: [#443](https://github.com/personalrobotics/aikido/pull/443)
Copy link
Member

Choose a reason for hiding this comment

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

Nit: Added ~

@@ -0,0 +1,18 @@
find_package(EIGEN3 REQUIRED)

add_library("${PROJECT_NAME}_external_optimaltimepathfollowing" STATIC
Copy link
Member

Choose a reason for hiding this comment

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

Match the directory name and the library name?

set(sources
KinodynamicTimer.cpp)

add_library("${PROJECT_NAME}_planner_kinodynamic" SHARED ${sources})
Copy link
Member

Choose a reason for hiding this comment

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

Match the directory name and the library name?

/// blending around the waypoint
/// \param[in] timeStep time step in following the path
/// \return time optimal trajectory that satisfies velocity and acceleration
/// constraints
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 start the description with a capital letter. For example, \param[in] inputTrajectory Input piecewise geodesic trajectory.

double mTimeStep;
};

} // namespace optimal_retimer
Copy link
Member

Choose a reason for hiding this comment

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

Nit: } // namespace optimalretimer

@brianhou
Copy link
Contributor

brianhou commented Aug 2, 2018

After discussing with @aditya-vk, I've renamed OptimalRetimer to KunzRetimer. This is just a cosmetic change, so I believe the tests should still pass.

However, one of the tests (KunzRetimerTests.timingArbitraryMultipleWaypoints) is quite slow, initially taking 8 seconds on my laptop with the original parameter settings. I brought it down to 3 seconds by tweaking the maxDeviation parameter, but I'd still like it to be below the 1 second mark.

@dqyi11 do those waypoint values come from somewhere? If we're just choosing arbitrary points, maybe we can choose arbitrary ones that we can retime more quickly 😅

brianhou
brianhou previously approved these changes Aug 2, 2018
@dqyi11
Copy link
Contributor Author

dqyi11 commented Aug 2, 2018

@brianhou Thanks! Yes. They are just arbitrarily chosen ones. I will improve the test speed before merging it tomorrow morning.

jslee02
jslee02 previously approved these changes Aug 2, 2018
@dqyi11 dqyi11 dismissed stale reviews from jslee02 and brianhou via 70175fd August 2, 2018 19:56
@brianhou brianhou merged commit e827fac into master Aug 2, 2018
@brianhou brianhou deleted the enhancement/dqyi/addRetiming branch August 2, 2018 21:36
gilwoolee pushed a commit that referenced this pull request Jan 21, 2019
* add kinodynamic retiming

* integrate retiming code

* add unit tests

* finish unit tests

* add docstrings and tests for post-processing

* code format

* fix tests for post processor

* fix code style

* code clean

* add CHANGELOG

* address Brian's comments

* address AVK's comments

* rename folder

* rename namespace

* Fix formatting.

* rename functions

* fix doc strings

* refactor the functions in detail namespace

* Rename OptimalRetimer to KunzRetimer.

* Speed up KunzRetimer tests.

Reducing the maxDeviation from 10 to 0.1 brings the runtime down from 8
seconds to 3 seconds.

* modify test values for speeding
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.

4 participants