void TestConvergence(Integrator const& integrator, Time const& beginning_of_convergence) { 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 + 10 * Second; #else Instant const t_final = t_initial + 100 * Second; #endif Time step = beginning_of_convergence; int const step_sizes = 50; double const step_reduction = 1.1; std::vector<double> log_step_sizes; log_step_sizes.reserve(step_sizes); std::vector<double> log_q_errors; log_step_sizes.reserve(step_sizes); std::vector<double> log_p_errors; log_step_sizes.reserve(step_sizes); std::vector<ODE::SystemState> solution; ODE harmonic_oscillator; harmonic_oscillator.compute_acceleration = std::bind(ComputeHarmonicOscillatorAcceleration, _1, _2, _3, /*evaluations=*/nullptr); IntegrationProblem<ODE> problem; problem.equation = harmonic_oscillator; ODE::SystemState const initial_state = {{q_initial}, {v_initial}, t_initial}; problem.initial_state = &initial_state; problem.t_final = t_final; ODE::SystemState final_state; problem.append_state = [&final_state](ODE::SystemState const& state) { final_state = state; }; for (int i = 0; i < step_sizes; ++i, step /= step_reduction) { integrator.Solve(problem, step); Time const t = final_state.time.value - t_initial; Length const& q = final_state.positions[0].value; Speed const& v = final_state.velocities[0].value; double const log_q_error = std::log10( AbsoluteError(q / q_initial, Cos(ω * t))); double const log_p_error = std::log10( AbsoluteError(v / v_amplitude, -Sin(ω * t))); if (log_q_error <= -13 || log_p_error <= -13) { // If we keep going the effects of finite precision will drown out // convergence. break; } log_step_sizes.push_back(std::log10(step / Second)); log_q_errors.push_back(log_q_error); log_p_errors.push_back(log_p_error); } double const q_convergence_order = Slope(log_step_sizes, log_q_errors); double const q_correlation = PearsonProductMomentCorrelationCoefficient(log_step_sizes, log_q_errors); LOG(INFO) << "Convergence order in q : " << q_convergence_order; LOG(INFO) << "Correlation : " << q_correlation; #if !defined(_DEBUG) EXPECT_THAT(RelativeError(integrator.order, q_convergence_order), Lt(0.02)); EXPECT_THAT(q_correlation, AllOf(Gt(0.99), Lt(1.01))); #endif double const v_convergence_order = Slope(log_step_sizes, log_p_errors); double const v_correlation = PearsonProductMomentCorrelationCoefficient(log_step_sizes, log_p_errors); LOG(INFO) << "Convergence order in p : " << v_convergence_order; LOG(INFO) << "Correlation : " << v_correlation; #if !defined(_DEBUG) // SPRKs with odd convergence order have a higher convergence order in p. EXPECT_THAT( RelativeError(integrator.order + (integrator.order % 2), v_convergence_order), Lt(0.02)); EXPECT_THAT(v_correlation, AllOf(Gt(0.99), Lt(1.01))); #endif }
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; problem.t_final = t_final; problem.append_state = [&solution](ODE::SystemState const& state) { solution.push_back(state); }; integrator.Solve(problem, step); EXPECT_EQ(steps, solution.size()); switch (integrator.composition) { case BA: case ABA: EXPECT_EQ(steps * integrator.evaluations, evaluations); break; case BAB: EXPECT_EQ(steps * integrator.evaluations + 1, evaluations); break; default: LOG(FATAL) << "Invalid composition"; } 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 }