Skip to content

Commit

Permalink
[trajectories] Add PathParameterizedTrajectory (RobotLocomotion#17816)
Browse files Browse the repository at this point in the history
  • Loading branch information
mpetersen94 authored Sep 8, 2022
1 parent 04b640f commit a2caba1
Show file tree
Hide file tree
Showing 6 changed files with 357 additions and 2 deletions.
24 changes: 22 additions & 2 deletions bindings/pydrake/test/trajectories_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
from pydrake.math import BsplineBasis_, RigidTransform_, RotationMatrix_
from pydrake.polynomial import Polynomial_
from pydrake.trajectories import (
BsplineTrajectory_, PiecewisePolynomial_, PiecewisePose_,
PiecewiseQuaternionSlerp_, Trajectory, Trajectory_
BsplineTrajectory_, PathParameterizedTrajectory_, PiecewisePolynomial_,
PiecewisePose_, PiecewiseQuaternionSlerp_, Trajectory, Trajectory_
)


Expand Down Expand Up @@ -111,6 +111,26 @@ def test_bspline_trajectory(self, T):
assert_pickle(self, bspline,
lambda traj: np.array(traj.control_points()), T=T)

@numpy_compare.check_all_types
def test_path_parameterized_trajectory(self, T):
PathParameterizedTrajectory = PathParameterizedTrajectory_[T]
PiecewisePolynomial = PiecewisePolynomial_[T]

s = np.array([[1., 3., 5.]])
x = np.array([[1., 2.], [3., 4.], [5., 6.]]).transpose()
param = PiecewisePolynomial.FirstOrderHold([0., 1., 2.], s)
path = PiecewisePolynomial.ZeroOrderHold(s[0], x)
trajectory = PathParameterizedTrajectory(path, param)
self.assertIsInstance(trajectory.Clone(), PathParameterizedTrajectory)
numpy_compare.assert_float_equal(trajectory.value(t=1.5), x[:, 1:2])
self.assertEqual(trajectory.rows(), 2)
self.assertEqual(trajectory.cols(), 1)
numpy_compare.assert_float_equal(trajectory.start_time(), 0.)
numpy_compare.assert_float_equal(trajectory.end_time(), 2.)
self.assertIsInstance(trajectory.path(), PiecewisePolynomial)
self.assertIsInstance(trajectory.time_scaling(),
PiecewisePolynomial)

@numpy_compare.check_all_types
def test_piecewise_polynomial_empty_constructor(self, T):
PiecewisePolynomial = PiecewisePolynomial_[T]
Expand Down
22 changes: 22 additions & 0 deletions bindings/pydrake/trajectories_py.cc
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include "drake/bindings/pydrake/pydrake_pybind.h"
#include "drake/common/polynomial.h"
#include "drake/common/trajectories/bspline_trajectory.h"
#include "drake/common/trajectories/path_parameterized_trajectory.h"
#include "drake/common/trajectories/piecewise_polynomial.h"
#include "drake/common/trajectories/piecewise_pose.h"
#include "drake/common/trajectories/piecewise_quaternion.h"
Expand Down Expand Up @@ -179,6 +180,27 @@ struct Impl {
DefCopyAndDeepCopy(&cls);
}

{
using Class = PathParameterizedTrajectory<T>;
constexpr auto& cls_doc = doc.PathParameterizedTrajectory;
auto cls = DefineTemplateClassWithDefault<Class, Trajectory<T>>(
m, "PathParameterizedTrajectory", param, cls_doc.doc);
cls // BR
.def(py::init<const Trajectory<T>&, const Trajectory<T>&>(),
py::arg("path"), py::arg("time_scaling"),
// Keep alive, reference: `self` keeps `path` alive.
py::keep_alive<1, 2>(), // BR
// Keep alive, reference: `self` keeps `time_scaling` alive.
py::keep_alive<1, 3>(), // BR
cls_doc.ctor.doc)
.def("Clone", &Class::Clone, cls_doc.Clone.doc)
.def("path", &Class::path, py_rvp::reference_internal,
cls_doc.path.doc)
.def("time_scaling", &Class::time_scaling, py_rvp::reference_internal,
cls_doc.time_scaling.doc);
DefCopyAndDeepCopy(&cls);
}

{
using Class = PiecewiseTrajectory<T>;
constexpr auto& cls_doc = doc.PiecewiseTrajectory;
Expand Down
22 changes: 22 additions & 0 deletions common/trajectories/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ drake_cc_package_library(
deps = [
":bspline_trajectory",
":discrete_time_trajectory",
":path_parameterized_trajectory",
":piecewise_polynomial",
":piecewise_pose",
":piecewise_quaternion",
Expand Down Expand Up @@ -47,6 +48,17 @@ drake_cc_library(
],
)

drake_cc_library(
name = "path_parameterized_trajectory",
srcs = ["path_parameterized_trajectory.cc"],
hdrs = ["path_parameterized_trajectory.h"],
deps = [
":trajectory",
"//common:default_scalars",
"//common:essential",
],
)

drake_cc_library(
name = "piecewise_trajectory",
srcs = ["piecewise_trajectory.cc"],
Expand Down Expand Up @@ -145,6 +157,16 @@ drake_cc_googletest(
],
)

drake_cc_googletest(
name = "path_parameterized_trajectory_test",
deps = [
":path_parameterized_trajectory",
":piecewise_polynomial",
"//common/test_utilities:eigen_matrix_compare",
"//common/test_utilities:expect_throws_message",
],
)

drake_cc_googletest(
name = "piecewise_trajectory_test",
deps = [
Expand Down
88 changes: 88 additions & 0 deletions common/trajectories/path_parameterized_trajectory.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
#include "drake/common/trajectories/path_parameterized_trajectory.h"

#include <algorithm>

#include <fmt/ostream.h>

#include "drake/common/drake_assert.h"

namespace drake {
namespace trajectories {

template <typename T>
PathParameterizedTrajectory<T>::PathParameterizedTrajectory(
const Trajectory<T>& path, const Trajectory<T>& time_scaling)
: path_{path.Clone()}, time_scaling_{time_scaling.Clone()} {
DRAKE_DEMAND(time_scaling.rows() == 1);
DRAKE_DEMAND(time_scaling.cols() == 1);
}

template <typename T>
std::unique_ptr<Trajectory<T>> PathParameterizedTrajectory<T>::Clone() const {
return std::make_unique<PathParameterizedTrajectory<T>>(*this);
}

template <typename T>
MatrixX<T> PathParameterizedTrajectory<T>::value(const T& t) const {
using std::max;
using std::min;
const T time =
min(max(t, time_scaling_->start_time()), time_scaling_->end_time());
return path_->value(time_scaling_->value(time)(0, 0));
}

template <typename T>
T PathParameterizedTrajectory<T>::BellPolynomial(int n, int k,
const VectorX<T>& x) const {
if (n == 0 && k == 0) {
return 1;
} else if (n == 0 || k == 0) {
return 0;
}
T polynomial = 0;
T a = 1;
for (int ii = 1; ii < n - k + 2; ++ii) {
polynomial += a * BellPolynomial(n - ii, k - 1, x) * x[ii - 1];
a = a * (n - ii) / ii;
}
return polynomial;
}

template <typename T>
MatrixX<T> PathParameterizedTrajectory<T>::DoEvalDerivative(
const T& t, int derivative_order) const {
using std::max;
using std::min;
const T time =
min(max(t, time_scaling_->start_time()), time_scaling_->end_time());
if (derivative_order == 0) {
return value(time);
} else if (derivative_order > 0) {
VectorX<T> s_derivatives(derivative_order);
for (int order = 0; order < derivative_order; ++order) {
s_derivatives(order) =
time_scaling_->EvalDerivative(time, order + 1)(0, 0);
}
// Derivative is calculated using Faà di Bruno's formula with Bell
// polynomials: https://en.wikipedia.org/wiki/Fa%C3%A0_di_Bruno%27s_formula
MatrixX<T> derivative = MatrixX<T>::Zero(rows(), cols());
for (int order = 1; order <= derivative_order; ++order) {
MatrixX<T> path_partial =
path_->EvalDerivative(time_scaling_->value(time)(0, 0), order);
derivative +=
path_partial * BellPolynomial(derivative_order, order, s_derivatives);
}
return derivative;
} else {
throw std::invalid_argument(
fmt::format("Invalid derivative order ({}). The derivative order must "
"be greater than or equal to 0.",
derivative_order));
}
}

} // namespace trajectories
} // namespace drake

DRAKE_DEFINE_CLASS_TEMPLATE_INSTANTIATIONS_ON_DEFAULT_SCALARS(
class drake::trajectories::PathParameterizedTrajectory)
90 changes: 90 additions & 0 deletions common/trajectories/path_parameterized_trajectory.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
#pragma once

#include <memory>
#include <vector>

#include <Eigen/Core>

#include "drake/common/copyable_unique_ptr.h"
#include "drake/common/default_scalars.h"
#include "drake/common/trajectories/trajectory.h"

namespace drake {
namespace trajectories {

/**
* A trajectory defined by a path and timing trajectory.
*
* Using a path of form `q(s)` and a time_scaling of the form `s(t)`, a full
* trajectory of form `q(t) = q(s(t))` is modeled.
*
* @tparam_default_scalars
*/
template <typename T>
class PathParameterizedTrajectory final : public Trajectory<T> {
public:
DRAKE_DEFAULT_COPY_AND_MOVE_AND_ASSIGN(PathParameterizedTrajectory)

/** Constructs a trajectory with the given `path` and `time_scaling`.
@pre time_scaling.rows() == time_scaling.cols() == 1 */
PathParameterizedTrajectory(const Trajectory<T>& path,
const Trajectory<T>& time_scaling);

~PathParameterizedTrajectory() final = default;

// Required methods for trajectories::Trajectory interface.
std::unique_ptr<trajectories::Trajectory<T>> Clone() const override;

/** Evaluates the PathParameterizedTrajectory at the given time t.
@param t The time at which to evaluate the %PathParameterizedTrajectory.
@return The matrix of evaluated values.
@pre If T == symbolic::Expression, `t.is_constant()` must be true.
@warning If t does not lie in the range [start_time(), end_time()], the
trajectory will silently be evaluated at the closest
valid value of time to t. For example, `value(-1)` will return
`value(0)` for a trajectory defined over [0, 1]. */
MatrixX<T> value(const T& t) const override;

Eigen::Index rows() const override { return path_->rows(); }

Eigen::Index cols() const override { return path_->cols(); }

T start_time() const override { return time_scaling_->start_time(); }

T end_time() const override { return time_scaling_->end_time(); }

/** Returns the path of this trajectory. */
const trajectories::Trajectory<T>& path() const { return *path_; }

/** Returns the time_scaling of this trajectory. */
const trajectories::Trajectory<T>& time_scaling() const {
return *time_scaling_;
}

private:
// Evaluates the %PathParameterizedTrajectory derivative at the given time @p
// t. Returns the nth derivative, where `n` is the value of @p
// derivative_order.
//
// @warning This method comes with the same caveats as value(). See value()
// @pre derivative_order must be non-negative.
MatrixX<T> DoEvalDerivative(const T& t, int derivative_order) const override;

// Evaluates the Bell Polynomial B_n,k(x) for use in calculating the
// derivative.
// @pre n and k must be non-negative and the length of x must be at least n.
T BellPolynomial(int n, int k, const VectorX<T>& x) const;

bool do_has_derivative() const override {
return path_->has_derivative() && time_scaling_->has_derivative();
}

copyable_unique_ptr<Trajectory<T>> path_;
copyable_unique_ptr<Trajectory<T>> time_scaling_;
};

} // namespace trajectories
} // namespace drake

DRAKE_DECLARE_CLASS_TEMPLATE_INSTANTIATIONS_ON_DEFAULT_SCALARS(
class drake::trajectories::PathParameterizedTrajectory)
Loading

0 comments on commit a2caba1

Please sign in to comment.