Skip to content

Commit f8fe5f4

Browse files
authored
Merge pull request #3150 from pleroy/Downsampling2
DiscreteTrajectorySegment, part 4: downsampling
2 parents 95f3d66 + b0bdc09 commit f8fe5f4

4 files changed

+173
-11
lines changed

physics/discrete_trajectory_segment.hpp

+24-3
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22

33
#include <cstdint>
44
#include <iterator>
5+
#include <optional>
56

67
#include "absl/container/btree_map.h"
7-
#include "absl/container/btree_set.h"
88
#include "absl/status/status.h"
99
#include "geometry/named_quantities.hpp"
1010
#include "numerics/hermite3.hpp"
@@ -84,6 +84,9 @@ class DiscreteTrajectorySegment : public Trajectory<Frame> {
8484
Instant const& t) const override;
8585

8686
private:
87+
using DownsamplingParameters =
88+
internal_discrete_trajectory_types::DownsamplingParameters;
89+
8790
absl::Status Append(Instant const& t,
8891
DegreesOfFreedom<Frame> const& degrees_of_freedom);
8992

@@ -97,6 +100,20 @@ class DiscreteTrajectorySegment : public Trajectory<Frame> {
97100
void ForgetBefore(Instant const& t);
98101
void ForgetBefore(typename Timeline::const_iterator end);
99102

103+
// This segment must have 0 or 1 points. Occasionally removes intermediate
104+
// points from the segment when |Append|ing, ensuring that positions remain
105+
// within the desired tolerance.
106+
void SetDownsampling(DownsamplingParameters const& downsampling_parameters);
107+
108+
// Clear the downsampling parameters. From now on, all points appended to the
109+
// segment are going to be retained.
110+
void ClearDownsampling();
111+
112+
// Called by |Append| after appending a point to this segment. If
113+
// appropriate, performs downsampling and deletes some of the points of the
114+
// segment.
115+
absl::Status DownsampleIfNeeded();
116+
100117
// Returns the Hermite interpolation for the left-open, right-closed
101118
// trajectory segment bounded above by |upper|.
102119
Hermite3<Instant, Position<Frame>> GetInterpolation(
@@ -105,10 +122,14 @@ class DiscreteTrajectorySegment : public Trajectory<Frame> {
105122
virtual typename Timeline::const_iterator timeline_begin() const;
106123
virtual typename Timeline::const_iterator timeline_end() const;
107124

108-
DiscreteTrajectorySegmentIterator<Frame> self_;
125+
std::optional<DownsamplingParameters> downsampling_parameters_;
109126

127+
DiscreteTrajectorySegmentIterator<Frame> self_;
110128
Timeline timeline_;
111-
absl::btree_set<Instant> dense_points_;
129+
130+
// The number of points at the end of the segment that are part of a "dense"
131+
// span, i.e., have not been subjected to downsampling yet.
132+
std::int64_t number_of_dense_points_ = 0;
112133

113134
template<typename F>
114135
friend class internal_discrete_trajectory_iterator::

physics/discrete_trajectory_segment_body.hpp

+95-8
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,24 @@
22

33
#include "physics/discrete_trajectory_segment.hpp"
44

5+
#include <algorithm>
56
#include <iterator>
7+
#include <list>
8+
#include <vector>
69

710
#include "astronomy/epoch.hpp"
11+
#include "geometry/named_quantities.hpp"
812
#include "glog/logging.h"
13+
#include "numerics/fit_hermite_spline.hpp"
914

1015
namespace principia {
1116
namespace physics {
1217
namespace internal_discrete_trajectory_segment {
1318

1419
using astronomy::InfiniteFuture;
1520
using astronomy::InfinitePast;
21+
using geometry::Position;
22+
using numerics::FitHermiteSpline;
1623

1724
template<typename Frame>
1825
DiscreteTrajectorySegment<Frame>::DiscreteTrajectorySegment(
@@ -160,34 +167,114 @@ absl::Status DiscreteTrajectorySegment<Frame>::Append(
160167
<< "Append out of order at " << t << ", last time is "
161168
<< timeline_.crbegin()->first;
162169

163-
// TODO(phl): Downsampling.
164-
return absl::OkStatus();
170+
if (downsampling_parameters_.has_value()) {
171+
return DownsampleIfNeeded();
172+
} else {
173+
return absl::OkStatus();
174+
}
165175
}
166176

167177
template<typename Frame>
168178
void DiscreteTrajectorySegment<Frame>::ForgetAfter(Instant const& t) {
169179
ForgetAfter(timeline_.lower_bound(t));
170-
// TODO(phl): Downsampling.
171180
}
172181

173182
template<typename Frame>
174183
void DiscreteTrajectorySegment<Frame>::ForgetAfter(
175184
typename Timeline::const_iterator const begin) {
176-
timeline_.erase(begin, timeline_.end());
177-
// TODO(phl): Downsampling.
185+
std::int64_t number_of_points_to_remove =
186+
std::distance(begin, timeline_.cend());
187+
number_of_dense_points_ =
188+
std::max(0LL, number_of_dense_points_ - number_of_points_to_remove);
189+
190+
timeline_.erase(begin, timeline_.cend());
178191
}
179192

180193
template<typename Frame>
181194
void DiscreteTrajectorySegment<Frame>::ForgetBefore(Instant const& t) {
182195
ForgetBefore(timeline_.lower_bound(t));
183-
// TODO(phl): Downsampling.
184196
}
185197

186198
template<typename Frame>
187199
void DiscreteTrajectorySegment<Frame>::ForgetBefore(
188200
typename Timeline::const_iterator const end) {
189-
timeline_.erase(timeline_.begin(), end);
190-
// TODO(phl): Downsampling.
201+
std::int64_t number_of_points_to_remove =
202+
std::distance(timeline_.cbegin(), end);
203+
number_of_dense_points_ =
204+
std::max(0LL, number_of_dense_points_ - number_of_points_to_remove);
205+
206+
timeline_.erase(timeline_.cbegin(), end);
207+
}
208+
209+
template<typename Frame>
210+
void DiscreteTrajectorySegment<Frame>::SetDownsampling(
211+
DownsamplingParameters const& downsampling_parameters) {
212+
// The semantics of changing downsampling on a segment that has 2 points or
213+
// more are unclear. Let's not do that.
214+
CHECK_LE(timeline_.size(), 1);
215+
downsampling_parameters_ = downsampling_parameters;
216+
number_of_dense_points_ = timeline_.empty() ? 0 : 1;
217+
}
218+
219+
template<typename Frame>
220+
void DiscreteTrajectorySegment<Frame>::ClearDownsampling() {
221+
downsampling_parameters_ = std::nullopt;
222+
}
223+
224+
template<typename Frame>
225+
absl::Status DiscreteTrajectorySegment<Frame>::DownsampleIfNeeded() {
226+
++number_of_dense_points_;
227+
// Points, hence one more than intervals.
228+
if (number_of_dense_points_ >
229+
downsampling_parameters_->max_dense_intervals) {
230+
// Obtain iterators for all the dense points of the segment.
231+
using ConstIterators = std::vector<typename Timeline::const_iterator>;
232+
ConstIterators dense_iterators(number_of_dense_points_);
233+
CHECK_LE(dense_iterators.size(), timeline_.size());
234+
auto it = timeline_.crbegin();
235+
for (int i = dense_iterators.size() - 1; i >= 0; --i) {
236+
dense_iterators[i] = std::prev(it.base());
237+
++it;
238+
}
239+
240+
absl::StatusOr<std::list<ConstIterators::const_iterator>>
241+
right_endpoints = FitHermiteSpline<Instant, Position<Frame>>(
242+
dense_iterators,
243+
[](auto&& it) -> auto&& { return it->first; },
244+
[](auto&& it) -> auto&& { return it->second.position(); },
245+
[](auto&& it) -> auto&& { return it->second.velocity(); },
246+
downsampling_parameters_->tolerance);
247+
if (!right_endpoints.ok()) {
248+
// Note that the actual appending took place; the propagated status only
249+
// reflects a lack of downsampling.
250+
return right_endpoints.status();
251+
}
252+
253+
if (right_endpoints->empty()) {
254+
number_of_dense_points_ = timeline_.empty() ? 0 : 1;
255+
return absl::OkStatus();
256+
}
257+
258+
// Obtain the times for the right endpoints. This is necessary because we
259+
// cannot use iterators for erasing points, as they would get invalidated
260+
// after the first erasure.
261+
std::vector<Instant> right_endpoints_times;
262+
right_endpoints_times.reserve(right_endpoints.value().size());
263+
for (auto const& it_in_dense_iterators : right_endpoints.value()) {
264+
right_endpoints_times.push_back((*it_in_dense_iterators)->first);
265+
}
266+
267+
// Poke holes in the timeline at the places given by
268+
// |right_endpoints_times|. This requires one lookup per erasure.
269+
auto left_it = dense_iterators.front();
270+
for (Instant const& right : right_endpoints_times) {
271+
++left_it;
272+
auto const right_it = timeline_.find(right);
273+
left_it = timeline_.erase(left_it, right_it);
274+
}
275+
number_of_dense_points_ = std::distance(left_it, timeline_.cend());
276+
}
277+
return absl::OkStatus();
191278
}
192279

193280
template<typename Frame>

physics/discrete_trajectory_segment_test.cpp

+44
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,10 @@ using quantities::si::Milli;
3232
using quantities::si::Nano;
3333
using quantities::si::Radian;
3434
using quantities::si::Second;
35+
using testing_utilities::AppendTrajectorySegment;
3536
using testing_utilities::IsNear;
3637
using testing_utilities::NewCircularTrajectorySegment;
38+
using testing_utilities::NewEmptyTrajectorySegment;
3739
using testing_utilities::operator""_⑴;
3840
using ::testing::Eq;
3941

@@ -63,6 +65,13 @@ class DiscreteTrajectorySegmentTest : public ::testing::Test {
6365
segment_->ForgetBefore(t);
6466
}
6567

68+
void SetDownsampling(
69+
internal_discrete_trajectory_types::DownsamplingParameters const&
70+
downsampling_parameters,
71+
DiscreteTrajectorySegment<World>& segment) {
72+
segment.SetDownsampling(downsampling_parameters);
73+
}
74+
6675
DiscreteTrajectorySegment<World>* segment_;
6776
internal_discrete_trajectory_types::Segments<World> segments_;
6877
Instant const t0_;
@@ -189,5 +198,40 @@ TEST_F(DiscreteTrajectorySegmentTest, Evaluate) {
189198
IsNear(10.4_⑴ * Nano(Metre / Second)));
190199
}
191200

201+
TEST_F(DiscreteTrajectorySegmentTest, Downsampling) {
202+
auto const circle = NewEmptyTrajectorySegment<World>();
203+
auto const downsampled_circle = NewEmptyTrajectorySegment<World>();
204+
SetDownsampling({.max_dense_intervals = 50, .tolerance = 1 * Milli(Metre)},
205+
*downsampled_circle->front());
206+
AngularFrequency const ω = 3 * Radian / Second;
207+
Length const r = 2 * Metre;
208+
Time const Δt = 10 * Milli(Second);
209+
Instant const t1 = t0_;
210+
Instant const t2 = t0_ + 10 * Second;
211+
AppendTrajectorySegment(
212+
*NewCircularTrajectorySegment<World>(ω, r, Δt, t1, t2)->front(),
213+
/*to=*/*circle->front());
214+
AppendTrajectorySegment(
215+
*NewCircularTrajectorySegment<World>(ω, r, Δt, t1, t2)->front(),
216+
/*to=*/*downsampled_circle->front());
217+
218+
EXPECT_THAT(circle->front()->size(), Eq(1001));
219+
EXPECT_THAT(downsampled_circle->front()->size(), Eq(77));
220+
std::vector<Length> position_errors;
221+
std::vector<Speed> velocity_errors;
222+
for (auto const& [time, degrees_of_freedom] : *circle->front()) {
223+
position_errors.push_back(
224+
(downsampled_circle->front()->EvaluatePosition(time) -
225+
degrees_of_freedom.position()).Norm());
226+
velocity_errors.push_back(
227+
(downsampled_circle->front()->EvaluateVelocity(time) -
228+
degrees_of_freedom.velocity()).Norm());
229+
}
230+
EXPECT_THAT(*std::max_element(position_errors.begin(), position_errors.end()),
231+
IsNear(0.98_⑴ * Milli(Metre)));
232+
EXPECT_THAT(*std::max_element(velocity_errors.begin(), velocity_errors.end()),
233+
IsNear(14_⑴ * Milli(Metre / Second)));
234+
}
235+
192236
} // namespace physics
193237
} // namespace principia

physics/discrete_trajectory_types.hpp

+10
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#include "base/macros.hpp"
88
#include "geometry/named_quantities.hpp"
99
#include "physics/degrees_of_freedom.hpp"
10+
#include "quantities/quantities.hpp"
1011

1112
// An internal header to avoid replicating data structures in multiple places.
1213
// Doesn't export anything outside of its internal namespace.
@@ -21,6 +22,15 @@ namespace internal_discrete_trajectory_types {
2122

2223
using geometry::Instant;
2324
using physics::DegreesOfFreedom;
25+
using quantities::Length;
26+
27+
// |max_dense_intervals| is the maximal number of dense intervals before
28+
// downsampling occurs. |tolerance| is the tolerance for downsampling with
29+
// |FitHermiteSpline|.
30+
struct DownsamplingParameters {
31+
std::int64_t max_dense_intervals;
32+
Length tolerance;
33+
};
2434

2535
// The use of an unique_ptr here makes it possible to only depend on a forward
2636
// declaration of DiscreteTrajectorySegment.

0 commit comments

Comments
 (0)