diff --git a/physics/discrete_trajectory2.hpp b/physics/discrete_trajectory2.hpp index f67f263eb5..6388116234 100644 --- a/physics/discrete_trajectory2.hpp +++ b/physics/discrete_trajectory2.hpp @@ -101,17 +101,11 @@ class DiscreteTrajectory2 : public Trajectory { not_null message, std::vector const& tracked, std::vector const& exact) const; - void WriteToMessage( - not_null message, - iterator begin, iterator end, - std::vector const& tracked, - std::vector const& exact) const; - template>> - static not_null> ReadFromMessage( + static DiscreteTrajectory2 ReadFromMessage( serialization::DiscreteTrajectory const& message, - std::vector const& tracked); + std::vector const& tracked); private: using Segments = internal_discrete_trajectory_types::Segments; diff --git a/physics/discrete_trajectory2_body.hpp b/physics/discrete_trajectory2_body.hpp index dcab069c47..de3fc77739 100644 --- a/physics/discrete_trajectory2_body.hpp +++ b/physics/discrete_trajectory2_body.hpp @@ -1,9 +1,10 @@ -#pragma once +#pragma once #include "physics/discrete_trajectory2.hpp" #include +#include "absl/container/flat_hash_map.h" #include "astronomy/epoch.hpp" namespace principia { @@ -271,26 +272,94 @@ void DiscreteTrajectory2::WriteToMessage( not_null message, std::vector const& tracked, std::vector const& exact) const { - // TODO(phl): Implement. -} + // Construct a map to efficiently find if a segment must be tracked. The + // keys are pointers to segments in |tracked|, the values are the + // corresponding indices. + absl::flat_hash_map const*, int> + segment_to_position; + for (int i = 0; i < tracked.size(); ++i) { + segment_to_position.emplace(&*tracked[i], i); + } -template -void DiscreteTrajectory2::WriteToMessage( - not_null message, - iterator begin, - iterator end, - std::vector const& tracked, - std::vector const& exact) const { - // TODO(phl): Implement. + // Initialize the tracked positions to be able to recognize if some are + // missing. + message->mutable_tracked_position()->Resize( + tracked.size(), + serialization::DiscreteTrajectory::MISSING_TRACKED_POSITION); + + // The position of a segment in the repeated field |segment|. + int segment_position = 0; + for (auto sit = segments_->begin(); + sit != segments_->end(); + ++sit, ++segment_position) { + sit->WriteToMessage(message->add_segment(), exact); + + if (auto const position_it = segment_to_position.find(&*sit); + position_it != segment_to_position.end()) { + // The field |tracked_position| is indexed by the indices in |tracked|. + // Its value is the position of a tracked segment in the field |segment|. + message->set_tracked_position(position_it->second, segment_position); + } + } + + for (auto const& [t, _] : segment_by_left_endpoint_) { + t.WriteToMessage(message->add_left_endpoint()); + } + + // Check that all the segments in |tracked| were mapped. + // NOTE(phl): This might be too strong a constraint in Entwurf. + for (auto const tracked_position : message->tracked_position()) { + CHECK_NE(serialization::DiscreteTrajectory::MISSING_TRACKED_POSITION, + tracked_position); + } } template template -not_null>> +DiscreteTrajectory2 DiscreteTrajectory2::ReadFromMessage( serialization::DiscreteTrajectory const& message, - std::vector const& tracked) { - // TODO(phl): Implement. + std::vector const& tracked) { + DiscreteTrajectory2 trajectory(uninitialized); + + bool const is_pre_ζήνων = message.segment_size() == 0; + if (is_pre_ζήνων) { + // TODO(phl): Implement. + LOG(FATAL) << "Pre-Ζήνων compatibility NYI"; + } + + // First restore the segments themselves. This vector will be used to restore + // the tracked segments. + std::vector segment_iterators; + segment_iterators.reserve(message.segment_size()); + for (auto const& serialized_segment : message.segment()) { + trajectory.segments_->emplace_back(); + auto const sit = --trajectory.segments_->end(); + auto const self = SegmentIterator(trajectory.segments_.get(), sit); + *sit = DiscreteTrajectorySegment::ReadFromMessage(serialized_segment, + self); + segment_iterators.push_back(self); + } + + // Restore the tracked segments. + CHECK_EQ(tracked.size(), message.tracked_position_size()); + for (int i = 0; i < message.tracked_position_size(); ++i) { + int const tracked_position = message.tracked_position(i); + CHECK_NE(serialization::DiscreteTrajectory::MISSING_TRACKED_POSITION, + tracked_position); + *tracked[i] = segment_iterators[tracked_position]; + } + + // Finally restore the left endpoints. + auto sit = trajectory.segments_->begin(); + for (auto const& serialized_t : message.left_endpoint()) { + auto const t = Instant::ReadFromMessage(serialized_t); + trajectory.segment_by_left_endpoint_.emplace_hint( + trajectory.segment_by_left_endpoint_.end(), t, sit); + ++sit; + } + + return trajectory; } template diff --git a/physics/discrete_trajectory2_test.cpp b/physics/discrete_trajectory2_test.cpp index 66e08a3aff..6f544b0a53 100644 --- a/physics/discrete_trajectory2_test.cpp +++ b/physics/discrete_trajectory2_test.cpp @@ -7,27 +7,36 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" #include "quantities/si.hpp" +#include "serialization/physics.pb.h" #include "testing_utilities/almost_equals.hpp" #include "testing_utilities/componentwise.hpp" #include "testing_utilities/discrete_trajectory_factories.hpp" +#include "testing_utilities/matchers.hpp" namespace principia { namespace physics { using geometry::Displacement; using geometry::Frame; +using geometry::Handedness; +using geometry::Inertial; using geometry::Instant; using geometry::Velocity; using quantities::si::Metre; using quantities::si::Second; using testing_utilities::AlmostEquals; using testing_utilities::Componentwise; +using testing_utilities::EqualsProto; using testing_utilities::NewLinearTrajectoryTimeline; using ::testing::ElementsAre; class DiscreteTrajectory2Test : public ::testing::Test { protected: - using World = Frame; + using World = Frame; + // Constructs a trajectory with three 5-second segments starting at |t0| and // the given |degrees_of_freedom|. @@ -469,5 +478,43 @@ TEST_F(DiscreteTrajectory2Test, TMinTMaxEvaluate) { 0 * Metre / Second}), 0))); } +TEST_F(DiscreteTrajectory2Test, SerializationRoundTrip) { + auto const trajectory = MakeTrajectory(); + auto const trajectory_first_segment = trajectory.segments().begin(); + auto const trajectory_second_segment = std::next(trajectory_first_segment); + + serialization::DiscreteTrajectory message1; + trajectory.WriteToMessage(&message1, + /*tracked=*/{trajectory_second_segment}, + /*exact=*/ + {trajectory.lower_bound(t0_ + 2 * Second), + trajectory.lower_bound(t0_ + 3 * Second)}); + + DiscreteTrajectorySegmentIterator deserialized_second_segment; + auto const deserialized_trajectory = + DiscreteTrajectory2::ReadFromMessage( + message1, /*tracked=*/{&deserialized_second_segment}); + + // Check that the tracked segment was properly retrieved. + EXPECT_EQ(t0_ + 4 * Second, deserialized_second_segment->begin()->first); + EXPECT_EQ(t0_ + 9 * Second, deserialized_second_segment->rbegin()->first); + + // Check that the exact points are exact. + EXPECT_EQ(deserialized_trajectory.lower_bound(t0_ + 2 * Second)->second, + trajectory.lower_bound(t0_ + 2 * Second)->second); + EXPECT_EQ(deserialized_trajectory.lower_bound(t0_ + 3 * Second)->second, + trajectory.lower_bound(t0_ + 3 * Second)->second); + + serialization::DiscreteTrajectory message2; + deserialized_trajectory.WriteToMessage( + &message2, + /*tracked=*/{deserialized_second_segment}, + /*exact=*/ + {deserialized_trajectory.lower_bound(t0_ + 2 * Second), + deserialized_trajectory.lower_bound(t0_ + 3 * Second)}); + + EXPECT_THAT(message2, EqualsProto(message1)); +} + } // namespace physics } // namespace principia diff --git a/physics/discrete_trajectory_body.hpp b/physics/discrete_trajectory_body.hpp index 633e8ece35..c42d3f1bd5 100644 --- a/physics/discrete_trajectory_body.hpp +++ b/physics/discrete_trajectory_body.hpp @@ -486,10 +486,10 @@ typename DiscreteTrajectory::Downsampling DiscreteTrajectory::Downsampling::ReadFromMessage( serialization::DiscreteTrajectory::Downsampling const& message, Timeline const& timeline) { - bool const is_pre_grotendieck_haar = message.has_start_of_dense_timeline(); + bool const is_pre_haar = message.has_start_of_dense_timeline(); Downsampling downsampling({message.max_dense_intervals(), Length::ReadFromMessage(message.tolerance())}); - if (is_pre_grotendieck_haar) { + if (is_pre_haar) { // No support for forks in legacy saves, so |find| will succeed and ++ is // safe. auto it = timeline.find( diff --git a/serialization/physics.proto b/serialization/physics.proto index adebe8641d..8fcde4db63 100644 --- a/serialization/physics.proto +++ b/serialization/physics.proto @@ -83,9 +83,9 @@ message DiscreteTrajectory { required int64 max_dense_intervals = 2; required Quantity tolerance = 3; // The instant of the iterator; absent if it is the end of the timeline. - // Pre Grothendieck/Haar. + // Pre Haar. optional Point start_of_dense_timeline = 1; - // Added in Grothendieck/Haar. + // Added in Haar. repeated Point dense_timeline = 4; } message InstantaneousDegreesOfFreedom { @@ -112,6 +112,17 @@ message DiscreteTrajectory { required int32 timeline_size = 4; } optional Zfp zfp = 5; + + // All the declarations above are pre-Ζήνων. All the declarations below were + // added in Ζήνων. + + // A marker to indicate that a segment doesn't have its position tracked. + enum TrackedPosition { + MISSING_TRACKED_POSITION = -1; + } + repeated DiscreteTrajectorySegment segment = 7; + repeated Point left_endpoint = 8; + repeated int32 tracked_position = 9; } // Added in Ζήνων.