#include #include #include #include #include #include #include #include #include "fesa/analysis/hht_time_integration.h" namespace { struct Oscillator { fesa::Vector q{1U}; fesa::Vector v{1U}; fesa::Vector a{1U}; fesa::Vector auxiliary{1U}; }; void Advance(Oscillator* state, const double mass, const double stiffness, const double force, const double h, const fesa::HhtParameters& parameters) { const auto predictor = fesa::PredictHht(state->q, state->v, state->auxiliary, h, parameters); if (!predictor.HasValue()) throw std::runtime_error{"Invalid oscillator predictor."}; fesa::Vector q{1U}; q[0] = (mass * predictor.Value().coordinates[0] + parameters.beta * h * h * ((1.0 + parameters.alpha) * force - mass * parameters.alpha * state->a[0])) / (mass + (1.0 + parameters.alpha) * stiffness * parameters.beta * h * h); const auto rates = fesa::EvaluateHhtRates(q, predictor.Value(), state->a, h, parameters); if (!rates.HasValue()) throw std::runtime_error{"Invalid oscillator rates."}; state->q = q; state->v = rates.Value().velocity; state->a = rates.Value().acceleration; state->auxiliary = rates.Value().algorithmic_acceleration; } using Matrix3 = std::array, 3>; Matrix3 Amplification(const double frequency_step, const double alpha) { // Independent dimensionless recurrence in (q,h*v,h^2*A), with m=1. const double beta = (1.0 - alpha) * (1.0 - alpha) / 4.0; const double gamma = 0.5 - alpha; const double stiffness = frequency_step * frequency_step; Matrix3 matrix{}; for (std::size_t column = 0; column < 3; ++column) { const double q = column == 0U ? 1.0 : 0.0; const double velocity = column == 1U ? 1.0 : 0.0; const double auxiliary = column == 2U ? 1.0 : 0.0; const double predictor = q + velocity + (0.5 - beta) * auxiliary; const double next_auxiliary = (-(1.0 + alpha) * stiffness * predictor + alpha * stiffness * q) / (1.0 + (1.0 + alpha) * stiffness * beta); matrix[0][column] = predictor + beta * next_auxiliary; matrix[1][column] = velocity + (1.0 - gamma) * auxiliary + gamma * next_auxiliary; matrix[2][column] = next_auxiliary; } return matrix; } std::array, 3> Eigenvalues(const Matrix3& matrix) { const double trace = matrix[0][0] + matrix[1][1] + matrix[2][2]; const double second = matrix[0][0] * matrix[1][1] + matrix[0][0] * matrix[2][2] + matrix[1][1] * matrix[2][2] - matrix[0][1] * matrix[1][0] - matrix[0][2] * matrix[2][0] - matrix[1][2] * matrix[2][1]; const double determinant = matrix[0][0] * (matrix[1][1] * matrix[2][2] - matrix[1][2] * matrix[2][1]) - matrix[0][1] * (matrix[1][0] * matrix[2][2] - matrix[1][2] * matrix[2][0]) + matrix[0][2] * (matrix[1][0] * matrix[2][1] - matrix[1][1] * matrix[2][0]); if (determinant == 0.0) { const auto discriminant = std::sqrt(std::complex{trace * trace - 4.0 * second, 0.0}); return {std::complex{0.0, 0.0}, 0.5 * (trace + discriminant), 0.5 * (trace - discriminant)}; } std::array, 3> roots{ std::complex{1.1, 0.0}, {-0.6, 0.9}, {-0.6, -0.9}}; bool converged = false; for (std::size_t iteration = 0; iteration < 500U; ++iteration) { const auto previous = roots; double change = 0.0; for (std::size_t i = 0; i < 3; ++i) { const auto value = ((previous[i] - trace) * previous[i] + second) * previous[i] - determinant; const auto denominator = (previous[i] - previous[(i + 1U) % 3U]) * (previous[i] - previous[(i + 2U) % 3U]); roots[i] = previous[i] - value / denominator; change = std::max(change, std::abs(roots[i] - previous[i])); } if (change <= 1.0e-13) { converged = true; break; } } EXPECT_TRUE(converged); for (const auto root : roots) { const double numerator = std::abs(((root - trace) * root + second) * root - determinant); const double scale = std::pow(std::abs(root), 3) + std::abs(trace) * std::norm(root) + std::abs(second) * std::abs(root) + std::abs(determinant); EXPECT_LE(scale > 0.0 ? numerator / scale : numerator, 1.0e-12); } return roots; } TEST(HhtEnergyVerification, C07AlphaZeroConservesLinearOscillatorEnergy) { const auto parameters = fesa::MakeHhtParameters(0.0); ASSERT_TRUE(parameters.HasValue()); Oscillator state; state.q[0] = 1.0; state.a[0] = -4.0; state.auxiliary = state.a; double maximum = 0.0; for (std::size_t step = 0; step < 640U; ++step) { Advance(&state, 1.0, 4.0, 0.0, 1.0 / 64.0, parameters.Value()); const double energy = 0.5 * state.v[0] * state.v[0] + 2.0 * state.q[0] * state.q[0]; maximum = std::max(maximum, std::abs(energy - 2.0) / 2.0); } std::cout << "C07 alpha0 max energy defect=" << maximum << '\n'; EXPECT_LE(maximum, 1.0e-10); } TEST(HhtEnergyVerification, C07DefaultAlphaReportsEnergyAndPhaseChanges) { const auto parameters = fesa::MakeHhtParameters(-0.05); ASSERT_TRUE(parameters.HasValue()); Oscillator state; state.q[0] = 1.0; state.a[0] = -4.0; state.auxiliary = state.a; double maximum = 0.0; for (std::size_t step = 0; step < 640U; ++step) { Advance(&state, 1.0, 4.0, 0.0, 1.0 / 64.0, parameters.Value()); const double energy = 0.5 * state.v[0] * state.v[0] + 2.0 * state.q[0] * state.q[0]; maximum = std::max(maximum, std::abs(energy - 2.0) / 2.0); } const double energy = 0.5 * state.v[0] * state.v[0] + 2.0 * state.q[0] * state.q[0]; std::cout << "C07 default alpha max energy defect=" << maximum << " signed endpoint=" << (energy - 2.0) / 2.0 << " displacement error=" << state.q[0] - std::cos(20.0) << " phase error=" << std::remainder(std::atan2(-state.v[0] / 2.0, state.q[0]) - 20.0, 2.0 * std::acos(-1.0)) << '\n'; EXPECT_TRUE(std::isfinite(energy)); EXPECT_LT(energy, 2.0); } TEST(HhtEnergyVerification, C07IndependentAmplificationHasThePrescribedSpectrum) { for (const double alpha : {0.0, -0.05}) { double previous_limit_error = 1.0; for (const double frequency_step : {0.01, 0.1, 1.0, 10.0, 100.0, 1000.0}) { auto roots = Eigenvalues(Amplification(frequency_step, alpha)); std::sort(roots.begin(), roots.end(), [](const auto& a, const auto& b) { return std::abs(a) < std::abs(b); }); const double radius = std::abs(roots.back()); std::cout << "C07 alpha=" << alpha << " omega*h=" << frequency_step << " spectral radius=" << radius << '\n'; if (alpha == 0.0) { EXPECT_NEAR(std::abs(roots[1]), 1.0, 1.0e-10); EXPECT_NEAR(std::abs(roots[2]), 1.0, 1.0e-10); } else { EXPECT_LE(radius, 1.0 + 1.0e-10); const double limit = (1.0 + alpha) / (1.0 - alpha); if (frequency_step >= 100.0) { const double error = std::abs(radius - limit); EXPECT_LT(error, previous_limit_error); previous_limit_error = error; std::cout << "C07 high-frequency limit=" << limit << " error=" << error << '\n'; } } } } } TEST(HhtEnergyVerification, C07SupportReactionUsesPhysicalAccelerationAndCoupling) { const auto parameters = fesa::MakeHhtParameters(-0.05); ASSERT_TRUE(parameters.HasValue()); Oscillator state; state.a[0] = 1.0; state.auxiliary = state.a; double mutation_difference = 0.0; for (std::size_t step = 1; step <= 64U; ++step) { Advance(&state, 2.0, 8.0, 2.0, 1.0 / 64.0, parameters.Value()); const std::array displacement{state.q[0], 0.0}; const std::array acceleration{state.a[0], 0.0}; const std::array, 2> mass{{{2.0, 0.5}, {0.5, 1.0}}}; const double reaction = mass[1][0] * acceleration[0] + mass[1][1] * acceleration[1] + 3.0 * displacement[1]; EXPECT_EQ(reaction, 0.5 * state.a[0]); mutation_difference = std::max( mutation_difference, std::abs(reaction - 0.5 * state.auxiliary[0])); const double momentum_rate = 2.5 * state.a[0]; const double balance = 2.0 + reaction - 8.0 * state.q[0] - momentum_rate; const double scale = 2.0 + std::abs(reaction) + std::abs(8.0 * state.q[0]) + std::abs(momentum_rate); EXPECT_LE(std::abs(balance) / scale, 1.0e-10); } EXPECT_GT(mutation_difference, 1.0e-6); } } // namespace