// Check that the small body has the right degrees of freedom in the frame of // the big body. TEST_F(BodyCentredNonRotatingDynamicFrameTest, SmallBodyInBigFrame) { int const kSteps = 100; Bivector<double, ICRFJ2000Equator> const axis({0, 0, 1}); RelativeDegreesOfFreedom<ICRFJ2000Equator> const initial_big_to_small = small_initial_state_ - big_initial_state_; ContinuousTrajectory<ICRFJ2000Equator>::Hint hint; for (Instant t = t0_; t < t0_ + 1 * period_; t += period_ / kSteps) { DegreesOfFreedom<ICRFJ2000Equator> const small_in_inertial_frame_at_t = solar_system_.trajectory(*ephemeris_, kSmall). EvaluateDegreesOfFreedom(t, &hint); auto const rotation_in_big_frame_at_t = Rotation<ICRFJ2000Equator, Big>(-2 * π * (t - t0_) * Radian / period_, axis); DegreesOfFreedom<Big> const small_in_big_frame_at_t( rotation_in_big_frame_at_t(initial_big_to_small.displacement()) + Big::origin, rotation_in_big_frame_at_t(initial_big_to_small.velocity())); auto const to_big_frame_at_t = big_frame_->ToThisFrameAtTime(t); EXPECT_THAT(AbsoluteError( to_big_frame_at_t(small_in_inertial_frame_at_t).position(), small_in_big_frame_at_t.position()), Lt(0.3 * Milli(Metre))); EXPECT_THAT(AbsoluteError( to_big_frame_at_t(small_in_inertial_frame_at_t).velocity(), small_in_big_frame_at_t.velocity()), Lt(4 * Milli(Metre) / Second)); } }
TEST_F(EmbeddedExplicitRungeKuttaNyströmIntegratorTest, Singularity) { // Integrating the position of an ideal rocket, // x"(t) = m' I_sp / m(t), // x'(0) = 0, x(0) = 0, // where m(t) = m₀ - t m'. // The solution is // x(t) = I_sp (t + (t - m₀ / m') log(m₀ / m(t)) // x'(t) = I_sp log(m₀ / m(t)) (Циолко́вский's equation). // There is a singularity at t = m₀ / m'. Variation<Mass> const mass_flow = 1 * Kilogram / Second; Mass const initial_mass = 1 * Kilogram; SpecificImpulse const specific_impulse = 1 * Newton * Second / Kilogram; Instant const t_initial; Instant const t_singular = t_initial + initial_mass / mass_flow; // After the singularity. Instant const t_final = t_initial + 2 * initial_mass / mass_flow; auto const mass = [initial_mass, t_initial, mass_flow](Instant const& t) { return initial_mass - (t - t_initial) * mass_flow; }; ODE::SystemState initial_state = {{0 * Metre}, {0 * Metre / Second}, t_initial}; Length const length_tolerance = 1 * Milli(Metre); Speed const speed_tolerance = 1 * Milli(Metre) / Second; std::vector<ODE::SystemState> solution; ODE rocket_equation; rocket_equation.compute_acceleration = [&mass, specific_impulse, mass_flow]( Instant const& t, std::vector<Length> const& position, not_null<std::vector<Acceleration>*> acceleration) { acceleration->back() = mass_flow * specific_impulse / mass(t); }; IntegrationProblem<ODE> problem; problem.append_state = [&solution](ODE::SystemState const& state) { solution.push_back(state); }; problem.equation = rocket_equation; problem.initial_state = &initial_state; problem.t_final = t_final; AdaptiveStepSize<ODE> adaptive_step_size; adaptive_step_size.first_time_step = t_final - t_initial; adaptive_step_size.safety_factor = 0.9; adaptive_step_size.tolerance_to_error_ratio = [length_tolerance, speed_tolerance]( Time const& h, ODE::SystemStateError const& error) { return std::min(length_tolerance / Abs(error.position_error[0]), speed_tolerance / Abs(error.velocity_error[0])); }; AdaptiveStepSizeIntegrator<ODE> const& integrator = DormandElMikkawyPrince1986RKN434FM<Length>(); auto const outcome = integrator.Solve(problem, adaptive_step_size); EXPECT_EQ(termination_condition::VanishingStepSize, outcome.error()); EXPECT_EQ(130, solution.size()); EXPECT_THAT(solution.back().time.value - t_initial, AlmostEquals(t_singular - t_initial, 20)); EXPECT_THAT(solution.back().positions.back().value, AlmostEquals(specific_impulse * initial_mass / mass_flow, 711)); }
TEST_F(EmbeddedExplicitRungeKuttaNyströmIntegratorTest, Serialization) { AdaptiveStepSizeIntegrator<ODE> const& integrator = EmbeddedExplicitRungeKuttaNyströmIntegrator< methods::DormandالمكاوىPrince1986RKN434FM, Length>(); Length const x_initial = 1 * Metre; Speed const v_initial = 0 * Metre / Second; Time const period = 2 * π * Second; Instant const t_initial; Instant const t_final = t_initial + 10 * period; Length const length_tolerance = 1 * Milli(Metre); Speed const speed_tolerance = 1 * Milli(Metre) / Second; auto const step_size_callback = [](bool tolerable) {}; std::vector<ODE::SystemState> solution; ODE harmonic_oscillator; harmonic_oscillator.compute_acceleration = std::bind(ComputeHarmonicOscillatorAcceleration1D, _1, _2, _3, /*evaluations=*/nullptr); IntegrationProblem<ODE> problem; problem.equation = harmonic_oscillator; problem.initial_state = {{x_initial}, {v_initial}, t_initial}; auto const append_state = [&solution](ODE::SystemState const& state) { solution.push_back(state); }; AdaptiveStepSizeIntegrator<ODE>::Parameters const parameters( /*first_time_step=*/t_final - t_initial, /*safety_factor=*/0.9); auto const tolerance_to_error_ratio = std::bind(HarmonicOscillatorToleranceRatio, _1, _2, length_tolerance, speed_tolerance, step_size_callback); auto const instance1 = integrator.NewInstance(problem, append_state, tolerance_to_error_ratio, parameters); serialization::IntegratorInstance message1; instance1->WriteToMessage(&message1); auto const instance2 = AdaptiveStepSizeIntegrator<ODE>::Instance::ReadFromMessage( message1, harmonic_oscillator, append_state, tolerance_to_error_ratio); serialization::IntegratorInstance message2; instance2->WriteToMessage(&message2); EXPECT_THAT(message1, EqualsProto(message2)); }
not_null<std::unique_ptr<Ephemeris<KSP>>> MakeEphemeris( std::vector<DegreesOfFreedom<KSP>> states) { return make_not_null_unique<Ephemeris<KSP>>( std::move(owned_bodies_), states, game_epoch_, /*fitting_tolerance=*/5 * Milli(Metre), Ephemeris<KSP>::FixedStepParameters( McLachlanAtela1992Order5Optimal<Position<KSP>>(), 45 * Minute)); }
void Test1000SecondsAt1Millisecond( Integrator const& integrator, Length const& expected_position_error, Speed const& expected_velocity_error) { Length const q_initial = 1 * Metre; Speed const v_initial = 0 * Metre / Second; Speed const v_amplitude = 1 * Metre / Second; AngularFrequency const ω = 1 * Radian / Second; Instant const t_initial; #if defined(_DEBUG) Instant const t_final = t_initial + 1 * Second; #else Instant const t_final = t_initial + 1000 * Second; #endif Time const step = 1 * Milli(Second); int const steps = static_cast<int>((t_final - t_initial) / step) - 1; int evaluations = 0; std::vector<ODE::SystemState> solution; ODE harmonic_oscillator; harmonic_oscillator.compute_acceleration = std::bind(ComputeHarmonicOscillatorAcceleration, _1, _2, _3, &evaluations); IntegrationProblem<ODE> problem; problem.equation = harmonic_oscillator; ODE::SystemState const initial_state = {{q_initial}, {v_initial}, t_initial}; problem.initial_state = &initial_state; auto append_state = [&solution](ODE::SystemState const& state) { solution.push_back(state); }; auto const instance = integrator.NewInstance(problem, std::move(append_state), step); integrator.Solve(t_final, *instance); EXPECT_EQ(steps, solution.size()); Length q_error; Speed v_error; for (int i = 0; i < steps; ++i) { Length const q = solution[i].positions[0].value; Speed const v = solution[i].velocities[0].value; Time const t = solution[i].time.value - t_initial; EXPECT_THAT(t, AlmostEquals((i + 1) * step, 0)); // TODO(egg): we may need decent trig functions for this sort of thing. q_error = std::max(q_error, AbsoluteError(q_initial * Cos(ω * t), q)); v_error = std::max(v_error, AbsoluteError(-v_amplitude * Sin(ω * t), v)); } #if !defined(_DEBUG) EXPECT_EQ(expected_position_error, q_error); EXPECT_EQ(expected_velocity_error, v_error); #endif }
BodyCentredNonRotatingDynamicFrameTest() : period_(10 * π * sqrt(5.0 / 7.0) * Second), centre_of_mass_initial_state_(Position<ICRFJ2000Equator>(), Velocity<ICRFJ2000Equator>()), big_initial_state_(Position<ICRFJ2000Equator>(), Velocity<ICRFJ2000Equator>()), small_initial_state_(Position<ICRFJ2000Equator>(), Velocity<ICRFJ2000Equator>()) { solar_system_.Initialize( SOLUTION_DIR / "astronomy" / "gravity_model_two_bodies_test.proto.txt", SOLUTION_DIR / "astronomy" / "initial_state_two_bodies_test.proto.txt"); t0_ = solar_system_.epoch(); ephemeris_ = solar_system_.MakeEphemeris( integrators::McLachlanAtela1992Order4Optimal< Position<ICRFJ2000Equator>>(), 10 * Milli(Second), 1 * Milli(Metre)); ephemeris_->Prolong(t0_ + 2 * period_); big_initial_state_ = solar_system_.initial_state(kBig); big_gravitational_parameter_ = solar_system_.gravitational_parameter(kBig); big_frame_ = std::make_unique< BodyCentredNonRotatingDynamicFrame<ICRFJ2000Equator, Big>>( ephemeris_.get(), solar_system_.massive_body(*ephemeris_, kBig)); small_initial_state_ = solar_system_.initial_state(kSmall); small_gravitational_parameter_ = solar_system_.gravitational_parameter(kSmall); small_frame_ = std::make_unique< BodyCentredNonRotatingDynamicFrame<ICRFJ2000Equator, Small>>( ephemeris_.get(), solar_system_.massive_body(*ephemeris_, kSmall)); centre_of_mass_initial_state_ = Barycentre<DegreesOfFreedom<ICRFJ2000Equator>, GravitationalParameter>( {big_initial_state_, small_initial_state_}, {big_gravitational_parameter_, small_gravitational_parameter_}); }
void BM_BarycentricRotatingDynamicFrame( benchmark::State& state) { // NOLINT(runtime/references) Time const Δt = 5 * Minute; int const steps = state.range_x(); SolarSystem<ICRFJ2000Equator> solar_system; solar_system.Initialize( SOLUTION_DIR / "astronomy" / "gravity_model.proto.txt", SOLUTION_DIR / "astronomy" / "initial_state_jd_2433282_500000000.proto.txt"); auto const ephemeris = solar_system.MakeEphemeris( McLachlanAtela1992Order5Optimal<Position<ICRFJ2000Equator>>(), 45 * Minute, 5 * Milli(Metre)); ephemeris->Prolong(solar_system.epoch() + steps * Δt); not_null<MassiveBody const*> const earth = solar_system.massive_body(*ephemeris, "Earth"); not_null<MassiveBody const*> const venus = solar_system.massive_body(*ephemeris, "Venus"); MasslessBody probe; Position<ICRFJ2000Equator> probe_initial_position = ICRFJ2000Equator::origin + Displacement<ICRFJ2000Equator>( {0.5 * AstronomicalUnit, -1 * AstronomicalUnit, 0 * AstronomicalUnit}); Velocity<ICRFJ2000Equator> probe_velocity = Velocity<ICRFJ2000Equator>({0 * SIUnit<Speed>(), 100 * Kilo(Metre) / Second, 0 * SIUnit<Speed>()}); DiscreteTrajectory<ICRFJ2000Equator> probe_trajectory; FillLinearTrajectory<ICRFJ2000Equator, DiscreteTrajectory>( probe_initial_position, probe_velocity, solar_system.epoch(), Δt, steps, &probe_trajectory); BarycentricRotatingDynamicFrame<ICRFJ2000Equator, Rendering> dynamic_frame(ephemeris.get(), earth, venus); while (state.KeepRunning()) { auto v = ApplyDynamicFrame(&probe, &dynamic_frame, probe_trajectory.Begin(), probe_trajectory.End()); } }
TEST_F(EmbeddedExplicitRungeKuttaNyströmIntegratorTest, MaxSteps) { AdaptiveStepSizeIntegrator<ODE> const& integrator = DormandElMikkawyPrince1986RKN434FM<Length>(); Length const x_initial = 1 * Metre; Speed const v_initial = 0 * Metre / Second; Speed const v_amplitude = 1 * Metre / Second; Time const period = 2 * π * Second; AngularFrequency const ω = 1 * Radian / Second; Instant const t_initial; Instant const t_final = t_initial + 10 * period; Length const length_tolerance = 1 * Milli(Metre); Speed const speed_tolerance = 1 * Milli(Metre) / Second; // The number of steps if no step limit is set. std::int64_t const steps_forward = 132; int evaluations = 0; auto const step_size_callback = [](bool tolerable) {}; std::vector<ODE::SystemState> solution; ODE harmonic_oscillator; harmonic_oscillator.compute_acceleration = std::bind(ComputeHarmonicOscillatorAcceleration, _1, _2, _3, &evaluations); IntegrationProblem<ODE> problem; problem.equation = harmonic_oscillator; ODE::SystemState const initial_state = {{x_initial}, {v_initial}, t_initial}; problem.initial_state = &initial_state; problem.t_final = t_final; problem.append_state = [&solution](ODE::SystemState const& state) { solution.push_back(state); }; AdaptiveStepSize<ODE> adaptive_step_size; adaptive_step_size.first_time_step = t_final - t_initial; adaptive_step_size.safety_factor = 0.9; adaptive_step_size.tolerance_to_error_ratio = std::bind(HarmonicOscillatorToleranceRatio, _1, _2, length_tolerance, speed_tolerance, step_size_callback); adaptive_step_size.max_steps = 100; auto const outcome = integrator.Solve(problem, adaptive_step_size); EXPECT_EQ(termination_condition::ReachedMaximalStepCount, outcome.error()); EXPECT_THAT(AbsoluteError( x_initial * Cos(ω * (solution.back().time.value - t_initial)), solution.back().positions[0].value), AllOf(Ge(8e-4 * Metre), Le(9e-4 * Metre))); EXPECT_THAT(AbsoluteError( -v_amplitude * Sin(ω * (solution.back().time.value - t_initial)), solution.back().velocities[0].value), AllOf(Ge(1e-3 * Metre / Second), Le(2e-3 * Metre / Second))); EXPECT_THAT(solution.back().time.value, Lt(t_final)); EXPECT_EQ(100, solution.size()); // Check that a |max_steps| greater than or equal to the unconstrained number // of steps has no effect. for (std::int64_t const max_steps : {steps_forward, steps_forward + 1234}) { solution.clear(); adaptive_step_size.max_steps = steps_forward; auto const outcome = integrator.Solve(problem, adaptive_step_size); EXPECT_EQ(termination_condition::Done, outcome.error()); EXPECT_THAT(AbsoluteError(x_initial, solution.back().positions[0].value), AllOf(Ge(3e-4 * Metre), Le(4e-4 * Metre))); EXPECT_THAT(AbsoluteError(v_initial, solution.back().velocities[0].value), AllOf(Ge(2e-3 * Metre / Second), Le(3e-3 * Metre / Second))); EXPECT_EQ(t_final, solution.back().time.value); EXPECT_EQ(steps_forward, solution.size()); } }
TEST_F(EmbeddedExplicitRungeKuttaNyströmIntegratorTest, Restart) { AdaptiveStepSizeIntegrator<ODE> const& integrator = EmbeddedExplicitRungeKuttaNyströmIntegrator< methods::DormandالمكاوىPrince1986RKN434FM, Length>(); Length const x_initial = 1 * Metre; Speed const v_initial = 0 * Metre / Second; Speed const v_amplitude = 1 * Metre / Second; Time const period = 2 * π * Second; AngularFrequency const ω = 1 * Radian / Second; Instant const t_initial; Time const duration = 10 * period; Length const length_tolerance = 1 * Milli(Metre); Speed const speed_tolerance = 1 * Milli(Metre) / Second; // The number of steps if no step limit is set. std::int64_t const steps_forward = 132; auto const step_size_callback = [](bool tolerable) {}; std::vector<ODE::SystemState> solution1; { ODE harmonic_oscillator; harmonic_oscillator.compute_acceleration = std::bind(ComputeHarmonicOscillatorAcceleration1D, _1, _2, _3, /*evaluations=*/nullptr); IntegrationProblem<ODE> problem; problem.equation = harmonic_oscillator; problem.initial_state = {{x_initial}, {v_initial}, t_initial}; auto const append_state = [&solution1](ODE::SystemState const& state) { solution1.push_back(state); }; AdaptiveStepSizeIntegrator<ODE>::Parameters const parameters( /*first_time_step=*/duration, /*safety_factor=*/0.9, /*max_steps=*/std::numeric_limits<std::int64_t>::max(), /*last_step_is_exact=*/false); auto const tolerance_to_error_ratio = std::bind(HarmonicOscillatorToleranceRatio, _1, _2, length_tolerance, speed_tolerance, step_size_callback); auto const instance = integrator.NewInstance(problem, append_state, tolerance_to_error_ratio, parameters); auto outcome = instance->Solve(t_initial + duration); EXPECT_EQ(termination_condition::Done, outcome.error()); // Check that the time step has been updated. EXPECT_EQ(131, solution1.size()); EXPECT_THAT( solution1[solution1.size() - 1].time.value - solution1[solution1.size() - 2].time.value, AlmostEquals(0.509'363'975'335'290'320 * Second, 0)); // Restart the integration. outcome = instance->Solve(t_initial + 2.0 * duration); EXPECT_EQ(termination_condition::Done, outcome.error()); // Check that the time step has been updated again. EXPECT_EQ(261, solution1.size()); EXPECT_THAT( solution1[solution1.size() - 1].time.value - solution1[solution1.size() - 2].time.value, AlmostEquals(0.506'410'259'195'249'068 * Second, 0)); } // Do it again in one call to |Solve| and check associativity. std::vector<ODE::SystemState> solution2; { ODE harmonic_oscillator; harmonic_oscillator.compute_acceleration = std::bind(ComputeHarmonicOscillatorAcceleration1D, _1, _2, _3, /*evaluations=*/nullptr); IntegrationProblem<ODE> problem; problem.equation = harmonic_oscillator; problem.initial_state = {{x_initial}, {v_initial}, t_initial}; auto const append_state = [&solution2](ODE::SystemState const& state) { solution2.push_back(state); }; AdaptiveStepSizeIntegrator<ODE>::Parameters const parameters( /*first_time_step=*/duration, /*safety_factor=*/0.9, /*max_steps=*/std::numeric_limits<std::int64_t>::max(), /*last_step_is_exact=*/false); auto const tolerance_to_error_ratio = std::bind(HarmonicOscillatorToleranceRatio, _1, _2, length_tolerance, speed_tolerance, step_size_callback); auto const instance = integrator.NewInstance(problem, append_state, tolerance_to_error_ratio, parameters); auto outcome = instance->Solve(t_initial + 2.0 * duration); EXPECT_EQ(termination_condition::Done, outcome.error()); } EXPECT_THAT(solution2, ElementsAreArray(solution1)); }