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

Add ParabolicTiming for linear spline #302

Merged
merged 13 commits into from
Jan 25, 2018
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@

* Fixed CollisionGroup bugs in Hand executors: [#299](https://github.com/personalrobotics/aikido/pull/299)

* Planner

* Added parabolic timing for linear spline [#302](https://github.com/personalrobotics/aikido/pull/302)

### 0.2.0 (2018-01-09)

* State Space
Expand Down
25 changes: 25 additions & 0 deletions include/aikido/planner/parabolic/ParabolicTimer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,31 @@ std::unique_ptr<aikido::trajectory::Spline> computeParabolicTiming(
const Eigen::VectorXd& _maxVelocity,
const Eigen::VectorXd& _maxAcceleration);

/// Computes the time-optimal timing of a trajectory consisting of a linear
/// spline between states under velocity and acceleration
/// bounds. The output is a parabolic spline, encoded in cubic polynomials,
/// that \b exactly follows the input path.
///
/// The output trajectory consists of a sequence of trapezoidal velocity
/// profiles that implement bang-bang control. This trajectory must stop at
/// each waypoint that introduces a velocity discontinuity to satisfy finite
/// acceleration bounds. You should consider using a blending or smoothing
/// algorithm, which does \b not follow the exact input path, if this behavior
/// is undesirable.
///
/// 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.
///
/// \param _inputTrajectory linear spline trajectory
/// \param _maxVelocity maximum velocity for each dimension
/// \param _maxAcceleration maximum acceleration for each dimension
/// \return time optimal trajectory that satisfies acceleration constraints
std::unique_ptr<aikido::trajectory::Spline> computeParabolicTiming(
const aikido::trajectory::Spline& _inputTrajectory,
const Eigen::VectorXd& _maxVelocity,
const Eigen::VectorXd& _maxAcceleration);

/// Convert an interpolated trajectory to a piecewise linear spline trajectory
/// This function requires the \c _inputTrajectory to use a \c
/// GeodesicInterpolator.
Expand Down
39 changes: 39 additions & 0 deletions src/planner/parabolic/ParabolicTimer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ namespace aikido {
namespace planner {
namespace parabolic {

//==============================================================================
std::unique_ptr<aikido::trajectory::Spline> convertToSpline(
const aikido::trajectory::Interpolated& _inputTrajectory)
{
Expand Down Expand Up @@ -78,6 +79,7 @@ std::unique_ptr<aikido::trajectory::Spline> convertToSpline(
return outputTrajectory;
}

//==============================================================================
std::unique_ptr<aikido::trajectory::Spline> computeParabolicTiming(
const aikido::trajectory::Interpolated& _inputTrajectory,
const Eigen::VectorXd& _maxVelocity,
Expand Down Expand Up @@ -114,6 +116,43 @@ std::unique_ptr<aikido::trajectory::Spline> computeParabolicTiming(
return outputTrajectory;
}

//==============================================================================
std::unique_ptr<aikido::trajectory::Spline> computeParabolicTiming(
const aikido::trajectory::Spline& _inputTrajectory,
const Eigen::VectorXd& _maxVelocity,
const Eigen::VectorXd& _maxAcceleration)
{
const auto stateSpace = _inputTrajectory.getStateSpace();
const auto dimension = stateSpace->getDimension();

if (static_cast<std::size_t>(_maxVelocity.size()) != dimension)
throw std::invalid_argument("Velocity limits have wrong dimension.");

if (static_cast<std::size_t>(_maxAcceleration.size()) != dimension)
throw std::invalid_argument("Acceleration limits have wrong dimension.");

for (std::size_t i = 0; i < dimension; ++i)
{
if (_maxVelocity[i] <= 0.)
throw std::invalid_argument("Velocity limits must be positive.");
if (!std::isfinite(_maxVelocity[i]))
throw std::invalid_argument("Velocity limits must be finite.");

if (_maxAcceleration[i] <= 0.)
throw std::invalid_argument("Acceleration limits must be positive.");
Copy link
Member

Choose a reason for hiding this comment

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

Shouldn't be fine to allow zero velocity/acceleration limits? Could you explain for me why nonnegative limits aren't enough? 😅

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is following the code of computeParabolicTiming for an Interpolated.
I think the reason is that if the velocity/acceleration limit of one dimension is zero, it should not be considered in timing a trajectory.

if (!std::isfinite(_maxAcceleration[i]))
throw std::invalid_argument("Acceleration limits must be finite.");
}

double startTime = _inputTrajectory.getStartTime();
auto dynamicPath = detail::convertToDynamicPath(
_inputTrajectory, _maxVelocity, _maxAcceleration, false);

auto outputTrajectory
= detail::convertToSpline(*dynamicPath, startTime, stateSpace);
return outputTrajectory;
}

//==============================================================================
ParabolicTimer::ParabolicTimer(
const Eigen::VectorXd& _velocityLimits,
Expand Down
12 changes: 10 additions & 2 deletions src/planner/parabolic/ParabolicUtil.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,8 @@ std::unique_ptr<aikido::trajectory::Spline> convertToSpline(
std::unique_ptr<ParabolicRamp::DynamicPath> convertToDynamicPath(
const aikido::trajectory::Spline& _inputTrajectory,
const Eigen::VectorXd& _maxVelocity,
const Eigen::VectorXd& _maxAcceleration)
const Eigen::VectorXd& _maxAcceleration,
bool _preserveWaypointVelocity)
{
const auto stateSpace = _inputTrajectory.getStateSpace();
const auto numWaypoints = _inputTrajectory.getNumWaypoints();
Expand All @@ -214,7 +215,14 @@ std::unique_ptr<ParabolicRamp::DynamicPath> convertToDynamicPath(

auto outputPath = make_unique<ParabolicRamp::DynamicPath>();
outputPath->Init(toVector(_maxVelocity), toVector(_maxAcceleration));
outputPath->SetMilestones(milestones, velocities);
if (_preserveWaypointVelocity)
{
outputPath->SetMilestones(milestones, velocities);
}
else
{
outputPath->SetMilestones(milestones);
}
if (!outputPath->IsValid())
throw std::runtime_error("Converted DynamicPath is not valid");
return outputPath;
Expand Down
10 changes: 7 additions & 3 deletions src/planner/parabolic/ParabolicUtil.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -55,16 +55,20 @@ std::unique_ptr<aikido::trajectory::Spline> convertToSpline(

/// Convert a spline trajectory to a dynamic path
/// \param _inputTrajectory a spline trajectory
/// \param[out] _startTime the start time of the spline trajectory
/// \param _maxVelocity maximum velocity in each dimension
/// \param _maxAcceleration maximum acceleration in each dimension
/// \param _preserveWaypointVelocity preserve waypoint velocity in conversion
/// \return a dynamic path
std::unique_ptr<ParabolicRamp::DynamicPath>
convertToDynamicPath(const aikido::trajectory::Spline& _inputTrajectory,
const Eigen::VectorXd& _maxVelocity,
const Eigen::VectorXd& _maxAcceleration);
const Eigen::VectorXd& _maxAcceleration,
bool _preserveWaypointVelocity = true);

/// Convert an interpolated trajectory to a dynamic path
/// \param _inputTrajectory a spline trajectory
/// \param[out] _startTime the start time of the interploated trajectory
/// \param _maxVelocity maximum velocity in each dimension
/// \param _maxAcceleration maximum acceleration in each dimension
/// \return a dynamic path
std::unique_ptr<ParabolicRamp::DynamicPath>
convertToDynamicPath(const aikido::trajectory::Interpolated& _inputTrajectory,
Expand Down
35 changes: 35 additions & 0 deletions tests/planner/parabolic/test_ParabolicTimer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,41 @@ TEST_F(ParabolicTimerTests, StartsAtNonZeroTime)
EXPECT_TRUE(Vector2d(2.0, 3.0).isApprox(state.getValue()));
}

TEST_F(ParabolicTimerTests, InterploatedSplineEquivalence)
{
Interpolated interpolated(mStateSpace, mInterpolator);

auto state = mStateSpace->createState();
auto state2 = mStateSpace->createState();

// This is the same test as StraightLine_TriangularProfile, except that the
// trajectory starts at a non-zero time.
state.setValue(Vector2d(1., 2.));
interpolated.addWaypoint(1., state);

state.setValue(Vector2d(2., 3.));
interpolated.addWaypoint(3., state);

auto spline = convertToSpline(interpolated);

auto timedInterpolated = computeParabolicTiming(
interpolated, Vector2d::Constant(2.), Vector2d::Constant(1.));
auto timedSpline = computeParabolicTiming(
*spline, Vector2d::Constant(2.), Vector2d::Constant(1.));

timedInterpolated->evaluate(1., state);
timedSpline->evaluate(1., state2);
EXPECT_TRUE(state2.getValue().isApprox(state.getValue()));

timedInterpolated->evaluate(2., state);
timedSpline->evaluate(2., state2);
EXPECT_TRUE(state2.getValue().isApprox(state.getValue()));

timedInterpolated->evaluate(3., state);
timedSpline->evaluate(3., state2);
EXPECT_TRUE(state2.getValue().isApprox(state.getValue()));
}

TEST_F(ParabolicTimerTests, StraightLine_TriangularProfile)
{
Interpolated inputTrajectory(mStateSpace, mInterpolator);
Expand Down