-
Notifications
You must be signed in to change notification settings - Fork 30
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
Conversation
Codecov Report
@@ 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
|
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
I think we can continue creating a namespace/directory underneath
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. |
Thanks! @brianhou 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 |
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) |
There was a problem hiding this comment.
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" |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
.
There was a problem hiding this comment.
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()) |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
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
?
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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*/) |
There was a problem hiding this comment.
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*/) |
There was a problem hiding this comment.
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())); |
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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 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. |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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? 🤔
I would like to have suggestions about |
(1) I think 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 (2) How about |
@brianhou I have updated the namespace and folder name as suggested. |
There was a problem hiding this 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( |
There was a problem hiding this comment.
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
.
There was a problem hiding this comment.
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.
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 😄) |
There was a problem hiding this 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) |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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}) |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: } // namespace optimalretimer
Reducing the maxDeviation from 10 to 0.1 brings the runtime down from 8 seconds to 3 seconds.
After discussing with @aditya-vk, I've renamed However, one of the tests ( @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 Thanks! Yes. They are just arbitrarily chosen ones. I will improve the test speed before merging it tomorrow morning. |
* 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
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
andSpline
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