From f37a2ad76d33f87b28544765f2d3be8b79b8e4ae Mon Sep 17 00:00:00 2001 From: Remy Derollez <43561754+Derollez@users.noreply.github.com> Date: Thu, 14 Dec 2023 17:05:58 +0100 Subject: [PATCH] feat: add dynamics contribution getters to segment solution (#291) * feat: add first version to compute accelerations in segment solution * refactor: address comments on structure and naming * refactor: functions and args * test: add cpp tests * feat: add bindings and test * cfeat: address great feedback and use statebuilder * chore: reoorg + suggestion * test: reduce test with eigen * test: try once more --- .../Trajectory/Segment.cpp | 52 +++++ .../python/test/trajectory/test_segment.py | 49 +++++ .../Astrodynamics/Trajectory/Segment.hpp | 96 +++++++- .../Astrodynamics/Trajectory/Segment.cpp | 103 +++++++++ .../Astrodynamics/Trajectory/Segment.test.cpp | 208 ++++++++++++++++++ 5 files changed, 502 insertions(+), 6 deletions(-) diff --git a/bindings/python/src/OpenSpaceToolkitAstrodynamicsPy/Trajectory/Segment.cpp b/bindings/python/src/OpenSpaceToolkitAstrodynamicsPy/Trajectory/Segment.cpp index 45509cc8f..6e7260487 100644 --- a/bindings/python/src/OpenSpaceToolkitAstrodynamicsPy/Trajectory/Segment.cpp +++ b/bindings/python/src/OpenSpaceToolkitAstrodynamicsPy/Trajectory/Segment.cpp @@ -13,6 +13,7 @@ inline void OpenSpaceToolkitAstrodynamicsPy_Trajectory_Segment(pybind11::module& using ostk::physics::time::Duration; using ostk::astro::trajectory::state::NumericalSolver; + using ostk::astro::trajectory::state::CoordinatesSubset; using ostk::astro::trajectory::Segment; using ostk::astro::Dynamics; @@ -170,6 +171,57 @@ inline void OpenSpaceToolkitAstrodynamicsPy_Trajectory_Segment(pybind11::module& )doc" ) + .def( + "get_dynamics_contribution", + &Segment::Solution::getDynamicsContribution, + arg("dynamics"), + arg("frame"), + arg("coordinates_subsets") = Array>::Empty(), + R"doc( + Compute the contribution of the provided dynamics in the provided frame for all states associated with the segment. + + Args: + dynamics (Dynamics): The dynamics. + frame (Frame): The frame. + coordinates_subsets (list[CoordinatesSubset], optional): A subset of the dynamics writing coordinates subsets to consider. + + Returns: + MatrixXd: The matrix of dynamics contributions for the selected coordinates subsets of the dynamics. + + )doc" + ) + .def( + "get_dynamics_acceleration_contribution", + &Segment::Solution::getDynamicsAccelerationContribution, + arg("dynamics"), + arg("frame"), + R"doc( + Compute the contribution of the provided dynamics to the acceleration in the provided frame for all states associated with the segment. + + Args: + dynamics (Dynamics): The dynamics. + frame (Frame): The frame. + + Returns: + np.ndarray: The matrix of dynamics contributions to acceleration. + + )doc" + ) + .def( + "get_all_dynamics_contributions", + &Segment::Solution::getAllDynamicsContributions, + arg("frame"), + R"doc( + Compute the contributions of all segment's dynamics in the provided frame for all states assocated with the segment. + + Args: + frame (Frame): The frame. + + Returns: + dict[Dynamics, np.ndarray]: The list of matrices with individual dynamics contributions. + + )doc" + ) ; diff --git a/bindings/python/test/trajectory/test_segment.py b/bindings/python/test/trajectory/test_segment.py index ef33c77cd..fdcd37281 100644 --- a/bindings/python/test/trajectory/test_segment.py +++ b/bindings/python/test/trajectory/test_segment.py @@ -1,6 +1,7 @@ # Apache License 2.0 import pytest +import numpy as np from ostk.physics import Environment from ostk.physics.time import Instant @@ -220,3 +221,51 @@ def test_solve( assert solution.compute_delta_mass() is not None assert solution.compute_delta_v(1500.0) is not None + + state_frame: Frame = state.get_frame() + + first_dynamics_contribution = solution.get_dynamics_contribution( + solution.dynamics[0], state_frame + ) + + second_dynamics_contribution = solution.get_dynamics_contribution( + solution.dynamics[1], state_frame + ) + + third_dynamics_contribution = solution.get_dynamics_contribution( + solution.dynamics[2], state_frame + ) + + assert first_dynamics_contribution is not None + assert second_dynamics_contribution is not None + assert third_dynamics_contribution is not None + assert first_dynamics_contribution.shape == (len(solution.states), 3) + assert second_dynamics_contribution.shape == (len(solution.states), 3) + assert third_dynamics_contribution.shape == (len(solution.states), 4) + + all_contributions = solution.get_all_dynamics_contributions(state_frame) + assert len(all_contributions) == len(solution.dynamics) + + assert all_contributions is not None + assert isinstance(all_contributions, dict) + assert np.array_equal( + all_contributions[solution.dynamics[0]], first_dynamics_contribution + ) + assert np.array_equal( + all_contributions[solution.dynamics[1]], second_dynamics_contribution + ) + assert np.array_equal( + all_contributions[solution.dynamics[2]], third_dynamics_contribution + ) + + acceleration_contribution = solution.get_dynamics_acceleration_contribution( + solution.dynamics[1], state_frame + ) + assert acceleration_contribution is not None + assert acceleration_contribution.shape == (len(solution.states), 3) + assert isinstance(acceleration_contribution, np.ndarray) + + with pytest.raises(Exception): + solution.get_dynamics_acceleration_contribution( + third_dynamics_contribution, state_frame + ) diff --git a/include/OpenSpaceToolkit/Astrodynamics/Trajectory/Segment.hpp b/include/OpenSpaceToolkit/Astrodynamics/Trajectory/Segment.hpp index 5b0923388..3c43abe46 100644 --- a/include/OpenSpaceToolkit/Astrodynamics/Trajectory/Segment.hpp +++ b/include/OpenSpaceToolkit/Astrodynamics/Trajectory/Segment.hpp @@ -4,9 +4,12 @@ #define __OpenSpaceToolkit_Astrodynamics_Trajectory_Segment__ #include +#include #include #include +#include + #include #include @@ -24,9 +27,12 @@ namespace trajectory { using ostk::core::ctnr::Array; +using ostk::core::ctnr::Map; using ostk::core::types::Shared; using ostk::core::types::String; +using ostk::math::object::MatrixXd; + using ostk::physics::time::Instant; using ostk::physics::time::Duration; using ostk::physics::units::Mass; @@ -37,7 +43,7 @@ using ostk::astro::Dynamics; using ostk::astro::dynamics::Thruster; using ostk::astro::EventCondition; -/// @brief Represent a propagation segment for astrodynamics purposes +/// @brief Represent a propagation segment for astrodynamics purposes class Segment { @@ -51,6 +57,15 @@ class Segment struct Solution { public: + /// @brief Constructor + /// + /// @param [in] aName Name of the segment + /// @param [in] aDynamicsArray Array of dynamics + /// @param [in] aStates Array of states for the segment + /// @param [in] aConditionIsSatisfied True if the event condition is satisfied + /// @param [in] aSegmentType Type of segment + /// @return An instance of Solution + Solution( const String& aName, const Array>& aDynamicsArray, @@ -59,25 +74,94 @@ class Segment const Segment::Type& aSegmentType ); - String name; /// Name of the segment. - Array> dynamics; /// List of dynamics used. - Array states; /// Array of states for the segment. - bool conditionIsSatisfied; /// True if the event condition is satisfied. - Segment::Type segmentType; /// Type of segment. + /// @brief Access Start Instant + /// @return Start Instant const Instant& accessStartInstant() const; + + /// @brief Access end instant + /// @return End Instant + const Instant& accessEndInstant() const; + /// @brief Get initial mass + /// @return Initial mass + Mass getInitialMass() const; + + /// @brief Get final mass + /// @return Final mass + Mass getFinalMass() const; + + /// @brief Get propagation duration + /// @return Propagation duration + Duration getPropagationDuration() const; + /// @brief Compute delta V + /// + /// @param [in] aSpecificImpulse Specific impulse + /// @return Delta V + Real computeDeltaV(const Real& aSpecificImpulse) const; + + /// @brief Compute delta mass + /// @return Delta mass + Mass computeDeltaMass() const; + /// @brief Get dynamics contribution + /// + /// @param [in] aDynamicsSPtr Dynamics + /// @param [in] aFrameSPtr Frame + /// @param [in] aCoordinatesSubsetSPtrArray Array of coordinates subsets + /// @return Dynamics contribution + + MatrixXd getDynamicsContribution( + const Shared& aDynamicsSPtr, + const Shared& aFrameSPtr, + const Array>& aCoordinatesSubsetSPtrArray = + Array>::Empty() + ) const; + + /// @brief Get dynamics acceleration contribution + /// + /// @param [in] aDynamicsSPtr Dynamics + /// @param [in] aFrameSPtr Frame + /// @return Dynamics acceleration contribution + + MatrixXd getDynamicsAccelerationContribution( + const Shared& aDynamicsSPtr, const Shared& aFrameSPtr + ) const; + + /// @brief Get all segment dynamics contributions + /// + /// @param [in] aFrameSPtr Frame + /// @return All segment dynamics contributions + + Map, MatrixXd> getAllDynamicsContributions(const Shared& aFrameSPtr) const; + + /// @brief Print the segment solution + /// + /// @param [in] anOutputStream An output stream + /// @param [in] (optional) displayDecorators If true, display decorators + void print(std::ostream& anOutputStream, bool displayDecorator = true) const; + /// @brief Output stream operator + /// + /// @param [in] anOutputStream An output stream + /// @param [in] aSolution A Solution + /// @return An output stream + friend std::ostream& operator<<(std::ostream& anOutputStream, const Solution& aSolution); + + String name; /// Name of the segment. + Array> dynamics; /// List of dynamics used. + Array states; /// Array of states for the segment. + bool conditionIsSatisfied; /// True if the event condition is satisfied. + Segment::Type segmentType; /// Type of segment. }; /// @brief Output stream operator diff --git a/src/OpenSpaceToolkit/Astrodynamics/Trajectory/Segment.cpp b/src/OpenSpaceToolkit/Astrodynamics/Trajectory/Segment.cpp index 86fad7679..4d2f43467 100644 --- a/src/OpenSpaceToolkit/Astrodynamics/Trajectory/Segment.cpp +++ b/src/OpenSpaceToolkit/Astrodynamics/Trajectory/Segment.cpp @@ -1,10 +1,13 @@ /// Apache License 2.0 +#include + #include #include #include #include +#include namespace ostk { @@ -18,6 +21,7 @@ using EarthGravitationalModel = ostk::physics::environment::gravitational::Earth using ostk::astro::trajectory::Propagator; using ostk::astro::trajectory::state::CoordinatesSubset; +using ostk::astro::trajectory::state::coordinatessubsets::CartesianVelocity; Segment::Solution::Solution( const String& aName, @@ -101,6 +105,105 @@ Mass Segment::Solution::computeDeltaMass() const return Mass::Kilograms(getInitialMass().inKilograms() - getFinalMass().inKilograms()); } +MatrixXd Segment::Solution::getDynamicsContribution( + const Shared& aDynamicsSPtr, + const Shared& aFrameSPtr, + const Array>& aCoordinatesSubsetSPtrArray +) const +{ + // Check dynamics is part of the segment dynamics + if (!dynamics.contains(aDynamicsSPtr)) + { + throw ostk::core::error::RuntimeError("Provided dynamics is not part of the segment dynamics."); + } + + // Extract write coordinates subsets from dynamics + const Array> dynamicsWriteCoordinatesSubsets = + aDynamicsSPtr->getWriteCoordinatesSubsets(); + + // Check that the provided coordinates subsets are part of the dynamics write coordinates subsets + for (auto aCoordinatesSubsetSPtr : aCoordinatesSubsetSPtrArray) + { + if (!dynamicsWriteCoordinatesSubsets.contains(aCoordinatesSubsetSPtr)) + { + throw ostk::core::error::RuntimeError(String::Format( + "Provided coordinates subset [{}] is not part of the dynamics write coordinates subsets.", + aCoordinatesSubsetSPtr->getName() + )); + } + } + + // Initialize the definitive coordinate subset array + Array> definitiveCoordinateSubsetArray = aCoordinatesSubsetSPtrArray; + + // Check value for aCoordinatesSubsetSPtrArray + if (aCoordinatesSubsetSPtrArray.isEmpty()) + { + definitiveCoordinateSubsetArray = aDynamicsSPtr->getWriteCoordinatesSubsets(); + } + + // Extract states size + const Size numberOfstates = this->states.getSize(); + + // Extract dynamics context and behavior relative to state + Array> dynamicsReadCoordinatesSubsets = aDynamicsSPtr->getReadCoordinatesSubsets(); + + // Construct state builder + const StateBuilder builder = StateBuilder(aFrameSPtr, dynamicsReadCoordinatesSubsets); + + // Compute the size of dynamicsContributionMatrix + Size dynamicsWriteSize = std::accumulate( + definitiveCoordinateSubsetArray.begin(), + definitiveCoordinateSubsetArray.end(), + 0, + [](int sum, const Shared& subset) + { + return sum + subset->getSize(); + } + ); + + // Initialize the dynamicsContributionMatrix + MatrixXd dynamicsContributionMatrix = MatrixXd::Zero(numberOfstates, dynamicsWriteSize); + + // Construct the dynamicsContributionMatrix, state by state (a.k.a row by row) + for (Index stateIndex = 0; stateIndex < numberOfstates; ++stateIndex) + { + const State& state = states[stateIndex]; + + VectorXd dynamicsContributionAtState = aDynamicsSPtr->computeContribution( + state.getInstant(), builder.reduce(state.inFrame(aFrameSPtr)).getCoordinates(), aFrameSPtr + ); + + dynamicsContributionMatrix.row(stateIndex) = dynamicsContributionAtState; + } + + return dynamicsContributionMatrix; +} + +MatrixXd Segment::Solution::getDynamicsAccelerationContribution( + const Shared& aDynamicsSPtr, const Shared& aFrameSPtr +) const +{ + return this->getDynamicsContribution(aDynamicsSPtr, aFrameSPtr, {CartesianVelocity::Default()}); +} + +Map, MatrixXd> Segment::Solution::getAllDynamicsContributions(const Shared& aFrameSPtr +) const +{ + // TBI: Use smart caching for multiple calls in the future + + // Each MatrixXd contains the contribution of a single dynamics for all the segment states + Map, MatrixXd> dynamicsContributionsMap = Map, MatrixXd>(); + + for (const Shared& aDynamicsSPtr : this->dynamics) + { + MatrixXd dynamicsContribution = this->getDynamicsContribution(aDynamicsSPtr, aFrameSPtr); + dynamicsContributionsMap.emplace(aDynamicsSPtr, dynamicsContribution); + } + + return dynamicsContributionsMap; +} + void Segment::Solution::print(std::ostream& anOutputStream, bool displayDecorator) const { if (displayDecorator) diff --git a/test/OpenSpaceToolkit/Astrodynamics/Trajectory/Segment.test.cpp b/test/OpenSpaceToolkit/Astrodynamics/Trajectory/Segment.test.cpp index 81247fc4e..9a5d12271 100644 --- a/test/OpenSpaceToolkit/Astrodynamics/Trajectory/Segment.test.cpp +++ b/test/OpenSpaceToolkit/Astrodynamics/Trajectory/Segment.test.cpp @@ -8,6 +8,7 @@ #include #include +#include #include #include #include @@ -20,18 +21,21 @@ #include +using ostk::core::ctnr::Map; using ostk::core::ctnr::Array; using ostk::core::types::String; using ostk::core::types::Shared; using ostk::core::types::Real; using ostk::math::object::VectorXd; +using ostk::math::object::MatrixXd; using ostk::physics::environment::object::Celestial; using ostk::physics::time::Instant; using ostk::physics::time::Duration; using ostk::physics::time::DateTime; using ostk::physics::time::Scale; +using ostk::physics::environment::object::Celestial; using ostk::physics::environment::object::celestial::Earth; using ostk::physics::coord::Frame; using ostk::physics::coord::Position; @@ -46,6 +50,8 @@ using ostk::astro::guidancelaw::ConstantThrust; using ostk::astro::trajectory::Segment; using ostk::astro::trajectory::LocalOrbitalFrameDirection; using ostk::astro::trajectory::LocalOrbitalFrameFactory; +using ostk::astro::Dynamics; +using ostk::astro::dynamics::AtmosphericDrag; using ostk::astro::dynamics::CentralBodyGravity; using ostk::astro::dynamics::PositionDerivative; using ostk::astro::eventcondition::InstantCondition; @@ -56,6 +62,9 @@ using ostk::astro::trajectory::state::CoordinatesBroker; using ostk::astro::trajectory::state::CoordinatesSubset; using ostk::astro::trajectory::state::coordinatessubsets::CartesianPosition; using ostk::astro::trajectory::state::coordinatessubsets::CartesianVelocity; +using EarthGravitationalModel = ostk::physics::environment::gravitational::Earth; +using EarthMagneticModel = ostk::physics::environment::magnetic::Earth; +using EarthAtmosphericModel = ostk::physics::environment::atmospheric::Earth; class OpenSpaceToolkit_Astrodynamics_Trajectory_Segment : public ::testing::Test { @@ -202,6 +211,205 @@ TEST_F(OpenSpaceToolkit_Astrodynamics_Trajectory_Segment, SegmentSolution_Comput } } +TEST_F(OpenSpaceToolkit_Astrodynamics_Trajectory_Segment, SegmentSolution_GetDynamicsContribution) +{ + { + const Segment::Solution segmentSolution = + Segment::Solution(defaultName_, defaultDynamics_, {defaultState_}, true, Segment::Type::Coast); + + const Shared stateFrame = defaultState_.accessFrame(); + for (const Shared& dynamics : defaultDynamics_) + { + EXPECT_NO_THROW(segmentSolution.getDynamicsContribution(dynamics, stateFrame)); + } + } + + { + const Segment::Solution segmentSolution = + Segment::Solution(defaultName_, defaultDynamics_, {defaultState_}, true, Segment::Type::Coast); + + const Shared stateFrame = defaultState_.accessFrame(); + for (const Shared& dynamics : defaultDynamics_) + { + const MatrixXd contributionDefault = segmentSolution.getDynamicsContribution(dynamics, stateFrame); + const Array> dynamicsWriteCoordinatesSubsets = + dynamics->getWriteCoordinatesSubsets(); + const MatrixXd contributionExplicit = + segmentSolution.getDynamicsContribution(dynamics, stateFrame, dynamicsWriteCoordinatesSubsets); + EXPECT_EQ(contributionDefault, contributionExplicit); + } + } + + { + const Segment::Solution segmentSolution = + Segment::Solution(defaultName_, defaultDynamics_, {defaultState_}, true, Segment::Type::Coast); + + const Shared stateFrame = defaultState_.accessFrame(); + + for (const Shared& dynamics : defaultDynamics_) + { + const Array> dynamicsWriteCoordinatesSubsets = + dynamics->getWriteCoordinatesSubsets(); + const Shared dynamicsWriteCoordinatesSubset = dynamicsWriteCoordinatesSubsets[0]; + const MatrixXd contribution = + segmentSolution.getDynamicsContribution(dynamics, stateFrame, {dynamicsWriteCoordinatesSubset}); + EXPECT_EQ(contribution.cols(), dynamicsWriteCoordinatesSubset->getSize()); + EXPECT_EQ(contribution.rows(), segmentSolution.states.getSize()); + } + } + + { + const Segment::Solution segmentSolution = + Segment::Solution(defaultName_, defaultDynamics_, {defaultState_}, true, Segment::Type::Coast); + + const Shared stateFrame = defaultState_.accessFrame(); + + // Construct a coordinatesSubset not part of the dynamics for which the contribution is requested + const Shared dynamics = defaultDynamics_[0]; + const Shared coordinatesSubset = CoordinatesSubset::DragCoefficient(); + + EXPECT_FALSE(dynamics->getWriteCoordinatesSubsets().contains(coordinatesSubset)); + + const String expectedString = + "Provided coordinates subset is not part of the dynamics write coordinates subsets."; + + // Test the throw and the message that is thrown + EXPECT_THROW( + { + try + { + MatrixXd contribution = + segmentSolution.getDynamicsContribution(dynamics, stateFrame, {coordinatesSubset}); + } + catch (const ostk::core::error::runtime::Undefined& e) + { + EXPECT_EQ(expectedString, e.getMessage()); + throw; + } + }, + ostk::core::error::RuntimeError + ); + } + + { + const Segment::Solution segmentSolution = + Segment::Solution(defaultName_, defaultDynamics_, {defaultState_}, true, Segment::Type::Coast); + + // Construct a dynamics not part of the segment + const Earth earth = Earth::FromModels( + std::make_shared(EarthGravitationalModel::Type::Spherical), + std::make_shared(EarthMagneticModel::Type::Undefined), + std::make_shared(EarthAtmosphericModel::Type::Exponential) + ); + const Shared earthSPtr = std::make_shared(earth); + const Shared atmosphericDragDynamics = std::make_shared(earthSPtr); + + const Shared stateFrame = defaultState_.accessFrame(); + + const String expectedString = "Provided dynamics is not part of the segment dynamics."; + + // Test the throw and the message that is thrown + EXPECT_THROW( + { + try + { + MatrixXd contribution = + segmentSolution.getDynamicsContribution(atmosphericDragDynamics, stateFrame); + } + catch (const ostk::core::error::runtime::Undefined& e) + { + EXPECT_EQ(expectedString, e.getMessage()); + throw; + } + }, + ostk::core::error::RuntimeError + ); + } +} + +TEST_F(OpenSpaceToolkit_Astrodynamics_Trajectory_Segment, SegmentSolution_GetDynamicsAccelerationContribution) +{ + { + const Segment::Solution segmentSolution = + Segment::Solution(defaultName_, defaultDynamics_, {defaultState_}, true, Segment::Type::Coast); + + const Shared stateFrame = defaultState_.accessFrame(); + + // Check error for PositionDerivative + const String expectedString = + "Provided coordinates subset is not part of the dynamics write coordinates subsets."; + + // Test the throw and the message that is thrown + EXPECT_THROW( + { + try + { + MatrixXd accelerationContribution = + segmentSolution.getDynamicsAccelerationContribution(defaultDynamics_[0], stateFrame); + } + catch (const ostk::core::error::runtime::Undefined& e) + { + EXPECT_EQ(expectedString, e.getMessage()); + throw; + } + }, + ostk::core::error::RuntimeError + ); + + // Check output for Gravity + MatrixXd accelerationContribution = + segmentSolution.getDynamicsAccelerationContribution(defaultDynamics_[1], stateFrame); + EXPECT_EQ(accelerationContribution.cols(), 3); + EXPECT_EQ(accelerationContribution.rows(), segmentSolution.states.getSize()); + } +} + +TEST_F(OpenSpaceToolkit_Astrodynamics_Trajectory_Segment, SegmentSolution_GetAllDynamicsContributions) +{ + { + const Segment::Solution segmentSolution = + Segment::Solution(defaultName_, defaultDynamics_, {defaultState_}, true, Segment::Type::Coast); + + const Shared stateFrame = defaultState_.accessFrame(); + Map, MatrixXd> contributions = segmentSolution.getAllDynamicsContributions(stateFrame); + + EXPECT_EQ(defaultDynamics_.getSize(), contributions.size()); + for (const Shared& dynamics : defaultDynamics_) + { + EXPECT_EQ(1, contributions.count(dynamics)); // Check all dynamics are present + EXPECT_EQ( + segmentSolution.states.getSize(), contributions.at(dynamics).rows() + ); // Check the number of rows corresponds to the number of states + EXPECT_GT( + contributions.at(dynamics).cols(), dynamics->getWriteCoordinatesSubsets().getSize() + ); // Check the number of columns corresponds to the number of coordinates subsets to which the dynamics + // writes + } + } + + { + const Segment::Solution segmentSolution = Segment::Solution( + defaultName_, defaultDynamics_, {initialStateWithMass_, finalStateWithMass_}, true, Segment::Type::Maneuver + ); + + const Shared stateFrame = initialStateWithMass_.accessFrame(); + Map, MatrixXd> contributions = segmentSolution.getAllDynamicsContributions(stateFrame); + + EXPECT_EQ(defaultDynamics_.getSize(), contributions.size()); + for (const Shared& dynamics : defaultDynamics_) + { + EXPECT_EQ(1, contributions.count(dynamics)); // Check all dynamics are present + EXPECT_EQ( + segmentSolution.states.getSize(), contributions.at(dynamics).rows() + ); // Check the number of rows corresponds to the number of states + EXPECT_GT( + contributions.at(dynamics).cols(), dynamics->getWriteCoordinatesSubsets().getSize() + ); // Check the number of columns corresponds to the number of coordinates subsets to which the dynamics + // writes + } + } +} + TEST_F(OpenSpaceToolkit_Astrodynamics_Trajectory_Segment, SegmentSolution_Print) { {