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(RotationTest, AppliedToTrivector) { EXPECT_THAT(rotation_a_(trivector_), AlmostEquals(Trivector<quantities::Length, World>( 4.0 * Metre), 0)); EXPECT_THAT(rotation_b_(trivector_), AlmostEquals(Trivector<quantities::Length, World>( 4.0 * Metre), 0)); EXPECT_THAT(rotation_c_(trivector_), AlmostEquals(Trivector<quantities::Length, World>( 4.0 * Metre), 0)); }
TEST_F(BodyCentredNonRotatingDynamicFrameTest, Inverse) { int const kSteps = 100; for (Instant t = t0_; t < t0_ + 1 * period_; t += period_ / kSteps) { auto const from_big_frame_at_t = big_frame_->FromThisFrameAtTime(t); auto const to_big_frame_at_t = big_frame_->ToThisFrameAtTime(t); auto const small_initial_state_transformed_and_back = from_big_frame_at_t(to_big_frame_at_t(small_initial_state_)); EXPECT_THAT(small_initial_state_transformed_and_back.position(), AlmostEquals(small_initial_state_.position(), 0, 1)); EXPECT_THAT(small_initial_state_transformed_and_back.velocity(), AlmostEquals(small_initial_state_.velocity(), 0, 1)); } }
TEST_F(RotationTest, ToQuaternion4) { R3Element<double> const v1 = {-2, -5, -6}; R3Element<double> const v2 = R3Element<double>({-3, 4, 1}).OrthogonalizationAgainst(v1); R3Element<double> v3 = Cross(v1, v2); R3Element<double> const w1 = Normalize(v1); R3Element<double> const w2 = Normalize(v2); R3Element<double> const w3 = Normalize(v3); R3x3Matrix m = {w1, w2, w3}; Rot rotation(ToQuaternion(m.Transpose())); EXPECT_THAT(rotation(e1_).coordinates(), AlmostEquals(w1, 6)); EXPECT_THAT(rotation(e2_).coordinates(), AlmostEquals(w2, 5)); EXPECT_THAT(rotation(e3_).coordinates(), AlmostEquals(w3, 1)); }
TEST_F(PermutationTest, Compose) { struct World3; using Orth12 = OrthogonalMap<World1, World2>; using Orth13 = OrthogonalMap<World1, World3>; using Orth23 = OrthogonalMap<World2, World3>; using Perm12 = Permutation<World1, World2>; using Perm13 = Permutation<World1, World3>; using Perm23 = Permutation<World2, World3>; std::vector<Perm12> const all12 = {Perm12(Perm12::XYZ), Perm12(Perm12::YZX), Perm12(Perm12::ZXY), Perm12(Perm12::XZY), Perm12(Perm12::ZYX), Perm12(Perm12::YXZ)}; std::vector<Perm23> const all23 = {Perm23(Perm23::XYZ), Perm23(Perm23::YZX), Perm23(Perm23::ZXY), Perm23(Perm23::XZY), Perm23(Perm23::ZYX), Perm23(Perm23::YXZ)}; for (Perm12 const& p12 : all12) { Orth12 const o12 = p12.Forget(); for (Perm23 const& p23 : all23) { Orth23 const o23 = p23.Forget(); Perm13 const p13 = p23 * p12; Orth13 const o13 = o23 * o12; for (Length l = 1 * Metre; l < 4 * Metre; l += 1 * Metre) { Vector<quantities::Length, World1> modified_vector( {l, vector_.coordinates().y, vector_.coordinates().z}); EXPECT_THAT(p13(modified_vector), AlmostEquals(o13(modified_vector), 0, 12)); } } } }
TEST_F(RotationTest, AppliedToBivector) { EXPECT_THAT(rotation_a_(bivector_), AlmostEquals(Bivector<quantities::Length, World>( R3Element<quantities::Length>(3.0 * Metre, 1.0 * Metre, 2.0 * Metre)), 4)); EXPECT_THAT(rotation_b_(bivector_), AlmostEquals(Bivector<quantities::Length, World>( R3Element<quantities::Length>(1.0 * Metre, -3.0 * Metre, 2.0 * Metre)), 1, 2)); EXPECT_THAT(rotation_c_(bivector_), AlmostEquals(Bivector<quantities::Length, World>( R3Element<quantities::Length>((0.5 + sqrt(3.0)) * Metre, (1.0 - 0.5 * sqrt(3.0)) * Metre, 3.0 * Metre)), 0)); }
TEST_F(RotationTest, Forget) { Orth const orthogonal_a = rotation_a_.Forget(); EXPECT_THAT(orthogonal_a(vector_), AlmostEquals(Vector<quantities::Length, World>( R3Element<quantities::Length>(3.0 * Metre, 1.0 * Metre, 2.0 * Metre)), 4)); }
TEST_F(RotationTest, Composition) { Rot const rotation_ab = rotation_a_ * rotation_b_; EXPECT_THAT(rotation_ab(vector_), AlmostEquals(Vector<quantities::Length, World>( R3Element<quantities::Length>(2.0 * Metre, 1.0 * Metre, -3.0 * Metre)), 4, 6)); }
TEST_F(RotationTest, Inverse) { EXPECT_THAT(rotation_a_.Inverse()(vector_), AlmostEquals(Vector<quantities::Length, World>( R3Element<quantities::Length>(2.0 * Metre, 3.0 * Metre, 1.0 * Metre)), 2)); EXPECT_THAT(rotation_b_.Inverse()(vector_), AlmostEquals(Vector<quantities::Length, World>( R3Element<quantities::Length>(1.0 * Metre, 3.0 * Metre, -2.0 * Metre)), 1, 2)); EXPECT_THAT(rotation_c_.Inverse()(vector_), AlmostEquals(Vector<quantities::Length, World>( R3Element<quantities::Length>((0.5 - sqrt(3.0)) * Metre, (1.0 + 0.5 * sqrt(3.0)) * Metre, 3.0 * Metre)), 0)); }
TEST_F(R3ElementTest, MixedProduct) { testing_utilities::TestBilinearMap( std::multiplies<>(), 1 * Second, 1 * JulianYear, u_, v_, 42.0, 0, 1); testing_utilities::TestBilinearMap( std::multiplies<>(), w_, a_, -1 * Day, 1 * Parsec / SpeedOfLight, -π, 0, 1); Time const t = -3 * Second; EXPECT_EQ(t * u_, u_ * t); EXPECT_THAT((u_ * t) / t, AlmostEquals(u_, 1)); }
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 }
TEST_F(R3ElementTest, Dumb3Vector) { EXPECT_EQ((e * 42) * v_, e * (42 * v_)); EXPECT_THAT(303.492345479576 * Metre / Second, AlmostEquals(a_.Norm(), 8)); testing_utilities::TestEquality(42 * v_, 43 * v_); testing_utilities::TestVectorSpace<R3Element<Speed>, double>( null_velocity_, u_, v_, w_, 0.0, 1.0, e, 42.0, 0, 1); testing_utilities::TestAlternatingBilinearMap( Cross<Speed, Speed>, u_, v_, w_, a_, 42.0, 0, 1); EXPECT_EQ(Cross(R3Element<double>(1, 0, 0), R3Element<double>(0, 1, 0)), R3Element<double>(0, 0, 1)); testing_utilities::TestSymmetricPositiveDefiniteBilinearMap( Dot<Speed, Speed>, u_, v_, w_, a_, 42.0, 0, 1); }
// The test point is at the origin and in motion. The acceleration is purely // due to Coriolis. TEST_F(BodySurfaceDynamicFrameTest, CoriolisAcceleration) { Instant const t = t0_ + 0 * Second; // The velocity is opposed to the motion and away from the centre. DegreesOfFreedom<MockFrame> const point_dof = {Displacement<MockFrame>({0 * Metre, 0 * Metre, 0 * Metre}) + MockFrame::origin, Velocity<MockFrame>({10 * Metre / Second, 20 * Metre / Second, 30 * Metre / Second})}; DegreesOfFreedom<ICRFJ2000Equator> const centre_dof = {Displacement<ICRFJ2000Equator>({0 * Metre, 0 * Metre, 0 * Metre}) + ICRFJ2000Equator::origin, Velocity<ICRFJ2000Equator>()}; EXPECT_CALL(mock_centre_trajectory_, EvaluateDegreesOfFreedom(t, _)) .Times(2) .WillRepeatedly(Return(centre_dof)); { InSequence s; EXPECT_CALL(*mock_ephemeris_, ComputeGravitationalAccelerationOnMassiveBody( check_not_null(massive_centre_), t)) .WillOnce(Return(Vector<Acceleration, ICRFJ2000Equator>({ 0 * Metre / Pow<2>(Second), 0 * Metre / Pow<2>(Second), 0 * Metre / Pow<2>(Second)}))); EXPECT_CALL(*mock_ephemeris_, ComputeGravitationalAccelerationOnMasslessBody(_, t)) .WillOnce(Return(Vector<Acceleration, ICRFJ2000Equator>())); } // The Coriolis acceleration is towards the centre and opposed to the motion. EXPECT_THAT(mock_frame_->GeometricAcceleration(t, point_dof).coordinates(), Componentwise(AlmostEquals(400 * Metre / Pow<2>(Second), 1), AlmostEquals(-200 * Metre / Pow<2>(Second), 0), VanishesBefore(1 * Metre / Pow<2>(Second), 110))); }
TEST_P(SRKNTest, ConsistentWeights) { // Check that the time argument of the force computation is correct by // integrating uniform linear motion. // We check this for all schemes. Speed const v = 1 * Metre / Second; Mass const m = 1 * Kilogram; auto compute_acceleration = [v]( Time const& t, std::vector<Length> const& q, not_null<std::vector<Acceleration>*> const result) { EXPECT_THAT(q[0], AlmostEquals(v * t, 0, 4096)); (*result)[0] = 0 * Metre / Pow<2>(Second); }; parameters_.initial.positions.emplace_back(0 * Metre); parameters_.initial.momenta.emplace_back(v); parameters_.initial.time = 0 * Second; parameters_.tmax = 16 * Second; parameters_.Δt = 1 * Second; parameters_.sampling_period = 5; integrator_->SolveTrivialKineticEnergyIncrement<Length>( compute_acceleration, parameters_, &solution_); EXPECT_THAT(solution_.back().positions.back().value, AlmostEquals(v * parameters_.tmax, 0, 4)); }
void TestTermination( Integrator const& integrator) { Length const q_initial = 1 * Metre; Speed const v_initial = 0 * Metre / Second; Instant const t_initial; Instant const t_final = t_initial + 163 * Second; Time const step = 42 * Second; int const steps = static_cast<int>(std::floor((t_final - t_initial) / step)); 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()); EXPECT_THAT(solution.back().time.value, AllOf(Gt(t_final - step), Le(t_final))); 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) { Time const t = solution[i].time.value - t_initial; EXPECT_THAT(t, AlmostEquals((i + 1) * step, 0)); } }
TEST_F(BodySurfaceDynamicFrameTest, GeometricAcceleration) { Instant const t = t0_ + period_; DegreesOfFreedom<BigSmallFrame> const point_dof = {Displacement<BigSmallFrame>({10 * Metre, 20 * Metre, 30 * Metre}) + BigSmallFrame::origin, Velocity<BigSmallFrame>({3 * Metre / Second, 2 * Metre / Second, 1 * Metre / Second})}; // We trust the functions to compute the values correctly, but this test // ensures that we don't get NaNs. EXPECT_THAT(big_frame_->GeometricAcceleration(t, point_dof), AlmostEquals(Vector<Acceleration, BigSmallFrame>({ -9.54504983899937710e5 * Metre / Pow<2>(Second), -1.90900569196062256e6 * Metre / Pow<2>(Second), -2.86351379198155506e6 * Metre / Pow<2>(Second)}), 0, 2)); }
void TestTimeReversibility(Integrator const& integrator) { Length const q_initial = 1 * Metre; Speed const v_initial = 0 * Metre / Second; Speed const v_amplitude = 1 * Metre / Second; Instant const t_initial; Instant const t_final = t_initial + 100 * Second; Time const step = 1 * Second; 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}; ODE::SystemState final_state; problem.initial_state = &initial_state; problem.t_final = t_final; problem.append_state = [&final_state](ODE::SystemState const& state) { final_state = state; }; integrator.Solve(problem, step); problem.initial_state = &final_state; problem.t_final = t_initial; integrator.Solve(problem, -step); EXPECT_EQ(t_initial, final_state.time.value); if (integrator.time_reversible) { EXPECT_THAT(final_state.positions[0].value, AlmostEquals(q_initial, 0, 8)); EXPECT_THAT(final_state.velocities[0].value, VanishesBefore(v_amplitude, 0, 16)); } else { EXPECT_THAT(AbsoluteError(q_initial, final_state.positions[0].value), Gt(1e-4 * Metre)); EXPECT_THAT(AbsoluteError(v_initial, final_state.velocities[0].value), Gt(1e-4 * Metre / Second)); } }
// Solving Δt * Δt == n. TEST_F(RootFindersTest, SquareRoots) { Instant const t_0; Instant const t_max = t_0 + 10 * Second; Length const n_max = Pow<2>(t_max - t_0) * SIUnit<Acceleration>(); for (Length n = 1 * Metre; n < n_max; n += 1 * Metre) { int evaluations = 0; auto const equation = [t_0, n, &evaluations](Instant const& t) { ++evaluations; return Pow<2>(t - t_0) * SIUnit<Acceleration>() - n; }; EXPECT_THAT(Bisect(equation, t_0, t_max) - t_0, AlmostEquals(Sqrt(n / SIUnit<Acceleration>()), 0, 1)); if (n == 25 * Metre) { EXPECT_EQ(3, evaluations); } else { EXPECT_THAT(evaluations, AllOf(Ge(49), Le(58))); } } }
// A linear acceleration identical for both bodies. The test point doesn't // move. The resulting acceleration combines centrifugal and linear. TEST_F(BodySurfaceDynamicFrameTest, LinearAcceleration) { Instant const t = t0_ + 0 * Second; DegreesOfFreedom<MockFrame> const point_dof = {Displacement<MockFrame>({10 * Metre, 20 * Metre, 30 * Metre}) + MockFrame::origin, Velocity<MockFrame>({0 * Metre / Second, 0 * Metre / Second, 0 * Metre / Second})}; DegreesOfFreedom<ICRFJ2000Equator> const centre_dof = {Displacement<ICRFJ2000Equator>({0 * Metre, 0 * Metre, 0 * Metre}) + ICRFJ2000Equator::origin, Velocity<ICRFJ2000Equator>({0 * Metre / Second, 0 * Metre / Second, 0 * Metre / Second})}; EXPECT_CALL(mock_centre_trajectory_, EvaluateDegreesOfFreedom(t, _)) .Times(2) .WillRepeatedly(Return(centre_dof)); { // The acceleration is linear + centripetal. InSequence s; EXPECT_CALL(*mock_ephemeris_, ComputeGravitationalAccelerationOnMassiveBody( check_not_null(massive_centre_), t)) .WillOnce(Return(Vector<Acceleration, ICRFJ2000Equator>({ -160 * Metre / Pow<2>(Second), 120 * Metre / Pow<2>(Second), 300 * Metre / Pow<2>(Second)}))); EXPECT_CALL(*mock_ephemeris_, ComputeGravitationalAccelerationOnMasslessBody(_, t)) .WillOnce(Return(Vector<Acceleration, ICRFJ2000Equator>())); } // The acceleration is linear + centrifugal. EXPECT_THAT(mock_frame_->GeometricAcceleration(t, point_dof), AlmostEquals(Vector<Acceleration, MockFrame>({ (-120 + 1e3) * Metre / Pow<2>(Second), (-160 + 2e3) * Metre / Pow<2>(Second), -300 * Metre / Pow<2>(Second)}), 2)); }
void TestTermination(Integrator const& integrator) { Length const q_initial = 1 * Metre; Speed const v_initial = 0 * Metre / Second; Instant const t_initial; Instant const t_final = t_initial + 1630 * Second; Time const step = 42 * Second; int const steps = static_cast<int>(std::floor((t_final - t_initial) / step)); 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()); EXPECT_THAT(solution.back().time.value, AllOf(Gt(t_final - step), Le(t_final))); Length q_error; Speed v_error; for (int i = 0; i < steps; ++i) { Time const t = solution[i].time.value - t_initial; EXPECT_THAT(t, AlmostEquals((i + 1) * step, 0)); } }
// Creates an interleaved vertex array. void Mesh::Interleave() { VertexInd newFace; for (std::size_t f = 0; f < face.size(); ++f) { // vertind 0 ae3d::Vec3 tvertex = vertex [ face[ f ].vInd [ 0 ] ]; ae3d::Vec3 tnormal = vnormal[ face[ f ].vnInd[ 0 ] ]; TexCoord ttcoord = tcoord.empty() ? TexCoord() : tcoord[ face[ f ].uvInd[ 0 ] ]; ae3d::Vec4 tcolor = colors.empty() ? ae3d::Vec4( 0, 0, 0, 1 ) : colors[ face[ f ].colInd[ 0 ] ]; // Searches face f's vertex a from the vertex list. // If it's not found, it's added. bool found = false; for (std::size_t i = 0; i < indices.size(); ++i) { if (AlmostEquals( interleavedVertices[ indices[ i ].a ].position, tvertex ) && AlmostEquals( interleavedVertices[ indices[ i ].a ].normal, tnormal ) && AlmostEquals( interleavedVertices[ indices[ i ].a ].texCoord, ttcoord ) && AlmostEquals( interleavedVertices[ indices[ i ].a ].color, tcolor )) { found = true; newFace.a = indices[ i ].a; break; } } if (!found) { Vertex newVertex; newVertex.position = tvertex; newVertex.normal = tnormal; newVertex.texCoord = ttcoord; newVertex.color = tcolor; interleavedVertices.push_back( newVertex ); newFace.a = (unsigned short)(interleavedVertices.size() - 1); } // vertind 1 tvertex = vertex [ face[ f ].vInd [ 1 ] ]; tnormal = vnormal[ face[ f ].vnInd[ 1 ] ]; ttcoord = tcoord.empty() ? TexCoord() : tcoord [ face[ f ].uvInd[ 1 ] ]; tcolor = colors.empty() ? ae3d::Vec4( 0, 0, 0, 1 ) : colors[ face[ f ].colInd[ 1 ] ]; found = false; for (std::size_t i = 0; i < indices.size(); ++i) { if (AlmostEquals( interleavedVertices[ indices[ i ].b ].position, tvertex ) && AlmostEquals( interleavedVertices[ indices[ i ].b ].normal, tnormal ) && AlmostEquals( interleavedVertices[ indices[ i ].b ].texCoord, ttcoord ) && AlmostEquals( interleavedVertices[ indices[ i ].b ].color, tcolor )) { found = true; newFace.b = indices[ i ].b; break; } } if (!found) { Vertex newVertex; newVertex.position = tvertex; newVertex.normal = tnormal; newVertex.texCoord = ttcoord; newVertex.color = tcolor; interleavedVertices.push_back( newVertex ); newFace.b = (unsigned short)(interleavedVertices.size() - 1); } // vertind 2 tvertex = vertex [ face[ f ].vInd [ 2 ] ]; tnormal = vnormal[ face[ f ].vnInd[ 2 ] ]; ttcoord = tcoord.empty() ? TexCoord() : tcoord [ face[ f ].uvInd[ 2 ] ]; tcolor = colors.empty() ? ae3d::Vec4( 0, 0, 0, 1 ) : colors[ face[ f ].colInd[ 2 ] ]; found = false; for (std::size_t i = 0; i < indices.size(); ++i) { if (AlmostEquals( interleavedVertices[ indices[ i ].c ].position, tvertex ) && AlmostEquals( interleavedVertices[ indices[ i ].c ].normal, tnormal ) && AlmostEquals( interleavedVertices[ indices[ i ].c ].texCoord, ttcoord ) && AlmostEquals( interleavedVertices[ indices[ i ].c ].color, tcolor )) { found = true; newFace.c = indices[ i ].c; break; } } if (!found) { Vertex newVertex; newVertex.position = tvertex; newVertex.normal = tnormal; newVertex.texCoord = ttcoord; newVertex.color = tcolor; interleavedVertices.push_back( newVertex ); newFace.c = (unsigned short)(interleavedVertices.size() - 1); } indices.push_back( newFace ); } if (interleavedVertices.size() > 65536) { std::cerr << "Mesh " << name << " has more than 65536 vertices!" << std::endl; exit( 1 ); } }
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)); }