Skip to content

Commit 2a39984

Browse files
authored
Trim PiecewisePolynomials (#22099)
1 parent f859518 commit 2a39984

File tree

5 files changed

+154
-0
lines changed

5 files changed

+154
-0
lines changed

common/polynomial.h

+6
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,12 @@ class Polynomial {
175175

176176
const std::vector<Monomial>& GetMonomials() const;
177177

178+
/** Returns the vector of the coefficients of the polynomial in the order
179+
* of powers of the variable - the [i]th element is the coefficient of the
180+
* variable raised to the ith power.
181+
*
182+
* @throws std::runtime_error if this Polynomial is not univariate.
183+
*/
178184
VectorX<T> GetCoefficients() const;
179185

180186
/// Returns a set of all of the variables present in this Polynomial.

common/trajectories/BUILD.bazel

+1
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ drake_cc_library(
138138
"//common:essential",
139139
"//common:name_value",
140140
"//common:polynomial",
141+
"//math:binomial_coefficient",
141142
"//math:matrix_util",
142143
"@fmt",
143144
],

common/trajectories/piecewise_polynomial.cc

+78
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#include "drake/common/drake_assert.h"
1212
#include "drake/common/drake_throw.h"
1313
#include "drake/common/unused.h"
14+
#include "drake/math/binomial_coefficient.h"
1415
#include "drake/math/matrix_util.h"
1516

1617
using Eigen::VectorXd;
@@ -532,6 +533,83 @@ void PiecewisePolynomial<T>::shiftRight(const T& offset) {
532533
}
533534
}
534535

536+
namespace {
537+
template <typename T>
538+
Polynomial<T> ShiftPoly(Polynomial<T> poly, const T& x) {
539+
if (poly.GetVariables().size() == 0) {
540+
// Make constant polynomial.
541+
return poly;
542+
}
543+
using std::pow;
544+
// Given p(t) = a0 + a1*t + a2*t^2 + ... + an*t^n,
545+
// we want to shift the parameter to p(t + x)
546+
// = b0 + b1*t + b2*t^2 + ... + bn*t^n.
547+
// We can expand the rhs to get the values in b.
548+
DRAKE_THROW_UNLESS(poly.GetVariables().size() == 1);
549+
int n = poly.GetDegree();
550+
const auto& a = poly.GetCoefficients();
551+
Eigen::Matrix<T, Eigen::Dynamic, 1> b =
552+
Eigen::Matrix<T, Eigen::Dynamic, 1>::Zero(n + 1);
553+
for (int i = 0; i <= n; i++) {
554+
for (int j = 0; j <= i; j++) {
555+
b(j) += a(i) * drake::math::BinomialCoefficient(i, j) * pow(x, i - j);
556+
}
557+
}
558+
return Polynomial<T>(b);
559+
}
560+
} // namespace
561+
562+
template <typename T>
563+
int PiecewisePolynomial<T>::AddBreak(const T& new_break) {
564+
auto& breaks = this->get_mutable_breaks();
565+
// Check if the new break is already within the kEpsilonTime of an
566+
// existing break.
567+
for (int k = 0; k < this->get_number_of_segments(); k++) {
568+
const T& break_time = breaks[k];
569+
if (abs(break_time - new_break) < PiecewiseTrajectory<T>::kEpsilonTime) {
570+
// No need to add a new break.
571+
return k;
572+
}
573+
}
574+
DRAKE_THROW_UNLESS(this->is_time_in_range(new_break));
575+
const auto value = this->value(new_break);
576+
int i = this->get_segment_index(new_break);
577+
const T last_break_before_new = this->start_time(i);
578+
const T shift = new_break - last_break_before_new;
579+
breaks.insert(breaks.begin() + i + 1, new_break);
580+
// New polynomial matrix to be added at index i.
581+
const auto& broken_polynomial = polynomials_[i];
582+
PolynomialMatrix new_polynomial = broken_polynomial;
583+
for (int row = 0; row < rows(); row++) {
584+
for (int col = 0; col < cols(); col++) {
585+
new_polynomial(row, col) = ShiftPoly(new_polynomial(row, col), shift);
586+
}
587+
}
588+
// The i+1'th polynomial should become the new_polynomial.
589+
polynomials_.insert(polynomials_.begin() + i + 1, new_polynomial);
590+
return i + 1;
591+
}
592+
593+
template <typename T>
594+
PiecewisePolynomial<T> PiecewisePolynomial<T>::SliceByTime(
595+
const T& start_time, const T& end_time) const {
596+
DRAKE_THROW_UNLESS(start_time < end_time);
597+
DRAKE_THROW_UNLESS(this->is_time_in_range(start_time));
598+
DRAKE_THROW_UNLESS(this->is_time_in_range(end_time));
599+
PiecewisePolynomial<T> result{*this};
600+
int i = result.AddBreak(start_time);
601+
int j = result.AddBreak(end_time);
602+
std::vector<PolynomialMatrix> polynomials;
603+
std::vector<T> breaks;
604+
for (int k = i; k < j; k++) {
605+
polynomials.push_back(result.getPolynomialMatrix(k));
606+
breaks.push_back(result.get_segment_times()[k]);
607+
}
608+
// Add the last break
609+
breaks.push_back(result.get_segment_times()[j]);
610+
return PiecewisePolynomial<T>(polynomials, breaks);
611+
}
612+
535613
template <typename T>
536614
void PiecewisePolynomial<T>::setPolynomialMatrixBlock(
537615
const typename PiecewisePolynomial<T>::PolynomialMatrix& replacement,

common/trajectories/piecewise_polynomial.h

+20
Original file line numberDiff line numberDiff line change
@@ -745,6 +745,26 @@ class PiecewisePolynomial final : public PiecewiseTrajectory<T> {
745745
*/
746746
void shiftRight(const T& offset);
747747

748+
/** Adds a break at the specified time. It does not change the value of the
749+
* trajectory at any point but the number of segments increases by 1.
750+
* @returns the index of the new break.
751+
* @throws std::exception if `new_break` is not within the trajectory's
752+
* time range.
753+
* @warning If `new_break` is within PiecewiseTrajectory::kEpsilonTime from
754+
* an existing break, the new break will be silently ignored. Returns
755+
* the index of the existing break.
756+
*
757+
*/
758+
int AddBreak(const T& new_break);
759+
760+
/** Slices the trajectory within a specified time range.
761+
* q = p.SliceByTime(t1, t2) returns a PiecewisePolynomial q such that
762+
* q.start_time() = t1, q.end_time() = t2, and q(t) = p(t) for t1 <= t <= t2.
763+
* @throws std::exception if `start_time` or `end_time` is not within the
764+
* trajectory's time range.
765+
*/
766+
PiecewisePolynomial SliceByTime(const T& start_time, const T& end_time) const;
767+
748768
/**
749769
* Replaces the specified block of the PolynomialMatrix at the given
750770
* segment index.

common/trajectories/test/piecewise_polynomial_test.cc

+49
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,30 @@ void testBasicFunctionality() {
147147
piecewise1.end_time());
148148
double t = uniform(generator);
149149

150+
// Random segment index to put the break
151+
uniform_int_distribution<> segment_dist(
152+
0, piecewise2.get_number_of_segments() - 1);
153+
int segment_index = segment_dist(generator);
154+
155+
PiecewisePolynomial<T> piecewise1_with_new_break = piecewise1;
156+
EXPECT_EQ(piecewise1_with_new_break.AddBreak(t),
157+
piecewise1.get_segment_index(t) + 1);
158+
EXPECT_EQ(piecewise1_with_new_break.get_number_of_segments(),
159+
piecewise1.get_number_of_segments() + 1);
160+
161+
PiecewisePolynomial<T> piecewise2_with_new_break = piecewise2;
162+
EXPECT_EQ(piecewise2_with_new_break.AddBreak(
163+
piecewise2.start_time(segment_index)),
164+
segment_index);
165+
166+
EXPECT_EQ(piecewise2_with_new_break.get_number_of_segments(),
167+
piecewise2.get_number_of_segments());
168+
169+
double t_2 = uniform(generator);
170+
171+
auto trimmed_piecewise2 =
172+
piecewise2.SliceByTime(std::min(t, t_2), std::max(t, t_2));
173+
150174
EXPECT_TRUE(CompareMatrices(sum.value(t),
151175
piecewise1.value(t) + piecewise2.value(t), 1e-8,
152176
MatrixCompareType::absolute));
@@ -183,6 +207,31 @@ void testBasicFunctionality() {
183207
EXPECT_TRUE(CompareMatrices(piecewise2_twice.value(t),
184208
piecewise2_twice.value(t + total_time), 1e-8,
185209
MatrixCompareType::absolute));
210+
211+
// The piecewise_with_new_break values must not change.
212+
EXPECT_TRUE(CompareMatrices(piecewise1_with_new_break.value(t),
213+
piecewise1.value(t), 1e-8,
214+
MatrixCompareType::absolute));
215+
// Check the samples at the middle of every segment.
216+
for (int k = 0; k < piecewise2.get_number_of_segments(); ++k) {
217+
double t_middle = (piecewise2.start_time(k) + piecewise2.end_time(k)) / 2;
218+
EXPECT_TRUE(CompareMatrices(piecewise2.value(t_middle),
219+
piecewise2_with_new_break.value(t_middle),
220+
1e-8, MatrixCompareType::absolute));
221+
}
222+
223+
// Check if trimmed_piecewise2 is indeed a trimmed version of piecewise2.
224+
EXPECT_EQ(trimmed_piecewise2.start_time(), std::min(t, t_2));
225+
EXPECT_EQ(trimmed_piecewise2.end_time(), std::max(t, t_2));
226+
uniform_real_distribution<double> uniform_trimmed(
227+
trimmed_piecewise2.start_time(), trimmed_piecewise2.end_time());
228+
// Pick 10 random samples and check that the sampled values are the same.
229+
for (int k = 0; k < 10; k++) {
230+
double t_sample = uniform_trimmed(generator);
231+
EXPECT_TRUE(CompareMatrices(trimmed_piecewise2.value(t_sample),
232+
piecewise2.value(t_sample), 1e-8,
233+
MatrixCompareType::absolute));
234+
}
186235
}
187236
}
188237

0 commit comments

Comments
 (0)