From 070f18b6c7e8470ddf9ff94c01a8bbfbabedc311 Mon Sep 17 00:00:00 2001 From: William Throwe Date: Thu, 28 Mar 2024 17:43:04 -0400 Subject: [PATCH 1/9] Add structure representing Adams LTS coefficients --- src/Time/TimeSteppers/AdamsLts.cpp | 133 +++++++++++++++ src/Time/TimeSteppers/AdamsLts.hpp | 65 ++++++++ src/Time/TimeSteppers/CMakeLists.txt | 2 + tests/Unit/Time/TimeSteppers/CMakeLists.txt | 1 + .../Unit/Time/TimeSteppers/Test_AdamsLts.cpp | 153 ++++++++++++++++++ 5 files changed, 354 insertions(+) create mode 100644 src/Time/TimeSteppers/AdamsLts.cpp create mode 100644 src/Time/TimeSteppers/AdamsLts.hpp create mode 100644 tests/Unit/Time/TimeSteppers/Test_AdamsLts.cpp diff --git a/src/Time/TimeSteppers/AdamsLts.cpp b/src/Time/TimeSteppers/AdamsLts.cpp new file mode 100644 index 000000000000..c03432307b0b --- /dev/null +++ b/src/Time/TimeSteppers/AdamsLts.cpp @@ -0,0 +1,133 @@ +// Distributed under the MIT License. +// See LICENSE.txt for details. + +#include "Time/TimeSteppers/AdamsLts.hpp" + +#include +#include + +#include "DataStructures/MathWrapper.hpp" +#include "Time/BoundaryHistory.hpp" +#include "Time/TimeSteppers/AdamsCoefficients.hpp" +#include "Utilities/Algorithm.hpp" +#include "Utilities/GenerateInstantiations.hpp" +#include "Utilities/Gsl.hpp" + +namespace TimeSteppers::adams_lts { +namespace { +template +using OrderVector = adams_coefficients::OrderVector; + +template +LtsCoefficients& add_assign_impl(LtsCoefficients& a, const LtsCoefficients& b, + const Op& op) { + const auto key_equal = [](const LtsCoefficients::value_type& l, + const LtsCoefficients::value_type& r) { + return get<0>(l) == get<0>(r) and get<1>(l) == get<1>(r); + }; + const auto key_less = [](const LtsCoefficients::value_type& l, + const LtsCoefficients::value_type& r) { + return get<0>(l) < get<0>(r) or + (get<0>(l) == get<0>(r) and get<1>(l) < get<1>(r)); + }; + + // Two passes: first the common entries, and then the new entries. + size_t common_entries = 0; + { + auto a_it = a.begin(); + auto b_it = b.begin(); + while (a_it != a.end() and b_it != b.end()) { + if (key_less(*a_it, *b_it)) { + ++a_it; + } else if (key_less(*b_it, *a_it)) { + ++b_it; + } else { + ++common_entries; + get<2>(*a_it) += op(get<2>(*b_it)); + ++a_it; + ++b_it; + } + } + } + a.resize(a.size() + b.size() - common_entries); + { + auto write = a.rbegin(); + auto a_it = write + static_cast( + b.size() - common_entries); + auto b_it = b.rbegin(); + while (b_it != b.rend()) { + if (a_it == a.rend() or key_less(*a_it, *b_it)) { + *write = *b_it; + get<2>(*write) = op(get<2>(*write)); + ++b_it; + } else { + if (key_equal(*a_it, *b_it)) { + ++b_it; + } + *write = *a_it; + ++a_it; + } + ++write; + } + } + return a; +} +} // namespace + +LtsCoefficients& operator+=(LtsCoefficients& a, const LtsCoefficients& b) { + return add_assign_impl(a, b, [](const double x) { return x; }); +} +LtsCoefficients& operator-=(LtsCoefficients& a, const LtsCoefficients& b) { + return add_assign_impl(a, b, [](const double x) { return -x; }); +} + +LtsCoefficients operator+(LtsCoefficients&& a, LtsCoefficients&& b) { + return std::move(a += b); +} +LtsCoefficients operator+(LtsCoefficients&& a, const LtsCoefficients& b) { + return std::move(a += b); +} +LtsCoefficients operator+(const LtsCoefficients& a, LtsCoefficients&& b) { + return std::move(b += a); +} +LtsCoefficients operator+(const LtsCoefficients& a, const LtsCoefficients& b) { + auto a2 = a; + return std::move(a2 += b); +} + +LtsCoefficients operator-(LtsCoefficients&& a, LtsCoefficients&& b) { + return std::move(a -= b); +} +LtsCoefficients operator-(LtsCoefficients&& a, const LtsCoefficients& b) { + return std::move(a -= b); +} +LtsCoefficients operator-(const LtsCoefficients& a, const LtsCoefficients& b) { + auto a2 = a; + return std::move(a2 -= b); +} +LtsCoefficients operator-(const LtsCoefficients& a, LtsCoefficients&& b) { + alg::for_each(b, [](auto& coef) { get<2>(coef) *= -1.0; }); + return std::move(b += a); +} + +template +void apply_coefficients(const gsl::not_null result, + const LtsCoefficients& coefficients, + const BoundaryHistoryEvaluator& coupling) { + for (const auto& term : coefficients) { + *result += get<2>(term) * *coupling(get<0>(term), get<1>(term)); + } +} + +#define MATH_WRAPPER_TYPE(data) BOOST_PP_TUPLE_ELEM(0, data) + +#define INSTANTIATE(_, data) \ + template void apply_coefficients( \ + gsl::not_null result, \ + const LtsCoefficients& coefficients, \ + const BoundaryHistoryEvaluator& coupling); + +GENERATE_INSTANTIATIONS(INSTANTIATE, (MATH_WRAPPER_TYPES)) +#undef INSTANTIATE +#undef MATH_WRAPPER_TYPE +} // namespace TimeSteppers::adams_lts diff --git a/src/Time/TimeSteppers/AdamsLts.hpp b/src/Time/TimeSteppers/AdamsLts.hpp new file mode 100644 index 000000000000..7f78eab7f78c --- /dev/null +++ b/src/Time/TimeSteppers/AdamsLts.hpp @@ -0,0 +1,65 @@ +// Distributed under the MIT License. +// See LICENSE.txt for details. + +#pragma once + +#include +#include +#include + +#include "Time/TimeStepId.hpp" +#include "Time/TimeSteppers/AdamsCoefficients.hpp" + +/// \cond +namespace TimeSteppers { +template +class BoundaryHistoryEvaluator; +} // namespace TimeSteppers +namespace gsl { +template +class not_null; +} // namespace gsl +/// \endcond + +/// Shared LTS implementation for the two Adams-based methods. +namespace TimeSteppers::adams_lts { +// For order-k 2:1 stepping, in each small step, half the points will +// require interpolation (k entries each) and the others will not (1 +// entry each). So (2 small steps) * ((k/2 interpolations) * (from k +// points) + (k/2 non-interpolations)) = k (k + 1). (The small steps +// round k/2 in different directions and the effect cancels out.) +constexpr size_t lts_coefficients_static_size = + adams_coefficients::maximum_order * (adams_coefficients::maximum_order + 1); + +/// Storage for LTS coefficients that should not allocate in typical +/// cases. Each entry is a tuple of (local id, remote id, +/// coefficient). The contents should be kept sorted, as some +/// functions assume that. +struct LtsCoefficients + : boost::container::small_vector, + lts_coefficients_static_size> { + using boost::container::small_vector< + std::tuple, + lts_coefficients_static_size>::small_vector; +}; + +LtsCoefficients& operator+=(LtsCoefficients& a, const LtsCoefficients& b); +LtsCoefficients& operator-=(LtsCoefficients& a, const LtsCoefficients& b); + +LtsCoefficients operator+(LtsCoefficients&& a, LtsCoefficients&& b); +LtsCoefficients operator+(LtsCoefficients&& a, const LtsCoefficients& b); +LtsCoefficients operator+(const LtsCoefficients& a, LtsCoefficients&& b); +LtsCoefficients operator+(const LtsCoefficients& a, const LtsCoefficients& b); + +LtsCoefficients operator-(LtsCoefficients&& a, LtsCoefficients&& b); +LtsCoefficients operator-(LtsCoefficients&& a, const LtsCoefficients& b); +LtsCoefficients operator-(const LtsCoefficients& a, LtsCoefficients&& b); +LtsCoefficients operator-(const LtsCoefficients& a, const LtsCoefficients& b); + +/// Add the LTS boundary terms for to \p result for the given set of +/// coefficients. +template +void apply_coefficients(gsl::not_null result, + const LtsCoefficients& coefficients, + const BoundaryHistoryEvaluator& coupling); +} // namespace TimeSteppers::adams_lts diff --git a/src/Time/TimeSteppers/CMakeLists.txt b/src/Time/TimeSteppers/CMakeLists.txt index 4b6c9845a62f..8d609a2dd1d1 100644 --- a/src/Time/TimeSteppers/CMakeLists.txt +++ b/src/Time/TimeSteppers/CMakeLists.txt @@ -6,6 +6,7 @@ spectre_target_sources( PRIVATE AdamsBashforth.cpp AdamsCoefficients.cpp + AdamsLts.cpp AdamsMoultonPc.cpp ClassicalRungeKutta4.cpp DormandPrince5.cpp @@ -28,6 +29,7 @@ spectre_target_headers( HEADERS AdamsBashforth.hpp AdamsCoefficients.hpp + AdamsLts.hpp AdamsMoultonPc.hpp ClassicalRungeKutta4.hpp DormandPrince5.hpp diff --git a/tests/Unit/Time/TimeSteppers/CMakeLists.txt b/tests/Unit/Time/TimeSteppers/CMakeLists.txt index ed755c90932c..81e0e97886fa 100644 --- a/tests/Unit/Time/TimeSteppers/CMakeLists.txt +++ b/tests/Unit/Time/TimeSteppers/CMakeLists.txt @@ -5,6 +5,7 @@ set(LIBRARY_SOURCES ${LIBRARY_SOURCES} TimeSteppers/Test_AdamsBashforth.cpp TimeSteppers/Test_AdamsCoefficients.cpp + TimeSteppers/Test_AdamsLts.cpp TimeSteppers/Test_AdamsMoultonPc.cpp TimeSteppers/Test_ClassicalRungeKutta4.cpp TimeSteppers/Test_DormandPrince5.cpp diff --git a/tests/Unit/Time/TimeSteppers/Test_AdamsLts.cpp b/tests/Unit/Time/TimeSteppers/Test_AdamsLts.cpp new file mode 100644 index 000000000000..040648d04a83 --- /dev/null +++ b/tests/Unit/Time/TimeSteppers/Test_AdamsLts.cpp @@ -0,0 +1,153 @@ +// Distributed under the MIT License. +// See LICENSE.txt for details. + +#include "Utilities/StdHelpers.hpp" + +#include "Framework/TestingFramework.hpp" + +#include +#include +#include + +#include "DataStructures/DataVector.hpp" +#include "Time/BoundaryHistory.hpp" +#include "Time/Slab.hpp" +#include "Time/TimeStepId.hpp" +#include "Time/TimeSteppers/AdamsLts.hpp" +#include "Utilities/Gsl.hpp" +#include "Utilities/MakeWithValue.hpp" + +namespace { +namespace adams_lts = TimeSteppers::adams_lts; + +void test_lts_coefficients_struct() { + const Slab slab(0.0, 1.0); + const TimeStepId id1(true, 0, slab.start()); + const TimeStepId id2 = id1.next_substep(slab.duration() / 2, 1.0); + const TimeStepId id3 = id2.next_step(slab.duration() / 2); + + const adams_lts::LtsCoefficients coefs1{ + {id1, id1, 2.0}, {id1, id2, 3.0}, {id1, id3, 4.0}}; + const adams_lts::LtsCoefficients coefs2{ + {id1, id1, 5.0}, {id1, id3, 7.0}, {id2, id1, 9.0}}; + + const adams_lts::LtsCoefficients sum{ + {id1, id1, 7.0}, {id1, id2, 3.0}, {id1, id3, 11.0}, {id2, id1, 9.0}}; + const adams_lts::LtsCoefficients difference{ + {id1, id1, -3.0}, {id1, id2, 3.0}, {id1, id3, -3.0}, {id2, id1, -9.0}}; + + { + auto s = coefs1; + CHECK(&(s += coefs2) == &s); + CHECK(s == sum); + } + { + auto d = coefs1; + CHECK(&(d -= coefs2) == &d); + CHECK(d == difference); + } + + CHECK(coefs1 + coefs2 == sum); + CHECK(coefs1 - coefs2 == difference); + CHECK(adams_lts::LtsCoefficients(coefs1) + coefs2 == sum); + CHECK(adams_lts::LtsCoefficients(coefs1) - coefs2 == difference); + CHECK(coefs1 + adams_lts::LtsCoefficients(coefs2) == sum); + CHECK(coefs1 - adams_lts::LtsCoefficients(coefs2) == difference); + CHECK(adams_lts::LtsCoefficients(coefs1) + + adams_lts::LtsCoefficients(coefs2) == + sum); + CHECK(adams_lts::LtsCoefficients(coefs1) - + adams_lts::LtsCoefficients(coefs2) == + difference); + + adams_lts::LtsCoefficients allocated_coefs{}; + for (size_t i = 0; i < adams_lts::lts_coefficients_static_size + 1; ++i) { + allocated_coefs.emplace_back( + id1, + TimeStepId( + true, static_cast(i), + Slab(static_cast(i), static_cast(i + 1)).start()), + i); + } + + { + auto a = allocated_coefs; + const auto* const data = a.data(); + const auto res = std::move(a) + allocated_coefs; + CHECK(res.data() == data); + CHECK(res == allocated_coefs + allocated_coefs); + } + { + auto a = allocated_coefs; + const auto* const data = a.data(); + const auto res = std::move(a) - allocated_coefs; + CHECK(res.data() == data); + CHECK(res == allocated_coefs - allocated_coefs); + } + { + auto a = allocated_coefs; + const auto* const data = a.data(); + const auto res = allocated_coefs + std::move(a); + CHECK(res.data() == data); + CHECK(res == allocated_coefs + allocated_coefs); + } + { + auto a = allocated_coefs; + const auto* const data = a.data(); + const auto res = allocated_coefs - std::move(a); + CHECK(res.data() == data); + CHECK(res == allocated_coefs - allocated_coefs); + } + { + auto a = allocated_coefs; + auto b = allocated_coefs; + const auto* const a_data = a.data(); + const auto* const b_data = a.data(); + const auto res = std::move(a) + std::move(b); + CHECK((res.data() == a_data or res.data() == b_data)); + CHECK(res == allocated_coefs + allocated_coefs); + } + { + auto a = allocated_coefs; + auto b = allocated_coefs; + const auto* const a_data = a.data(); + const auto* const b_data = a.data(); + const auto res = std::move(a) - std::move(b); + CHECK((res.data() == a_data or res.data() == b_data)); + CHECK(res == allocated_coefs - allocated_coefs); + } +} + +template +void test_apply_coefficients(const T& used_for_size) { + const Slab slab(1.2, 3.4); + + TimeSteppers::BoundaryHistory history{}; + history.local().insert(TimeStepId(true, 0, slab.start()), 1, 1.0); + history.local().insert(TimeStepId(true, 0, slab.end()), 1, 10.0); + history.remote().insert(TimeStepId(true, 0, slab.start()), 1, 100.0); + history.remote().insert(TimeStepId(true, 0, slab.end()), 1, 10000.0); + + const adams_lts::LtsCoefficients coefficients{ + {history.local()[0], history.remote()[0], 1.0}, + {history.local()[0], history.remote()[1], 2.0}, + {history.local()[1], history.remote()[1], 3.0}}; + + const auto coupling = [&used_for_size](const double local, + const double remote) { + return make_with_value(used_for_size, local * remote); + }; + + auto result = make_with_value(used_for_size, 2.0); + adams_lts::apply_coefficients(make_not_null(&result), coefficients, + history.evaluator(coupling)); + + CHECK(result == make_with_value(used_for_size, 320102.0)); +} + +SPECTRE_TEST_CASE("Unit.Time.TimeSteppers.AdamsLts", "[Unit][Time]") { + test_lts_coefficients_struct(); + test_apply_coefficients(0.0); + test_apply_coefficients(DataVector(5, 0.0)); +} +} // namespace From 4e56132645d9c36d85e50ee785f844904ad63c35 Mon Sep 17 00:00:00 2001 From: William Throwe Date: Wed, 3 Apr 2024 17:42:42 -0400 Subject: [PATCH 2/9] Add function for generic Adams-LTS coefficients --- .clang-tidy | 3 + src/Time/TimeSteppers/AdamsLts.cpp | 316 +++++++++ src/Time/TimeSteppers/AdamsLts.hpp | 63 ++ .../Unit/Time/TimeSteppers/Test_AdamsLts.cpp | 606 ++++++++++++++++++ 4 files changed, 988 insertions(+) diff --git a/.clang-tidy b/.clang-tidy index e467891b616d..447cf868cead 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -96,6 +96,9 @@ CheckOptions: value: true - key: performance-move-const-arg.CheckTriviallyCopyableMove value: false + # The fix for this is not supported by GCC 9. + - key: modernize-loop-convert.UseCxx20ReverseRanges + value: false WarningsAsErrors: '*' # It is unclear if the header filter actually works or how to use it so # just include all headers diff --git a/src/Time/TimeSteppers/AdamsLts.cpp b/src/Time/TimeSteppers/AdamsLts.cpp index c03432307b0b..de44d95ace67 100644 --- a/src/Time/TimeSteppers/AdamsLts.cpp +++ b/src/Time/TimeSteppers/AdamsLts.cpp @@ -3,17 +3,34 @@ #include "Time/TimeSteppers/AdamsLts.hpp" +#include #include +#include +#include #include #include "DataStructures/MathWrapper.hpp" +#include "NumericalAlgorithms/Interpolation/LagrangePolynomial.hpp" +#include "Time/ApproximateTime.hpp" #include "Time/BoundaryHistory.hpp" +#include "Time/EvolutionOrdering.hpp" +#include "Time/Time.hpp" +#include "Time/TimeStepId.hpp" #include "Time/TimeSteppers/AdamsCoefficients.hpp" #include "Utilities/Algorithm.hpp" +#include "Utilities/ErrorHandling/Assert.hpp" +#include "Utilities/ErrorHandling/Error.hpp" #include "Utilities/GenerateInstantiations.hpp" #include "Utilities/Gsl.hpp" namespace TimeSteppers::adams_lts { +Time exact_substep_time(const TimeStepId& id) { + const Time result = + id.substep() == 0 ? id.step_time() : id.step_time() + id.step_size(); + ASSERT(result.value() == id.substep_time(), "Substep not at expected time."); + return result; +} + namespace { template using OrderVector = adams_coefficients::OrderVector; @@ -119,6 +136,292 @@ void apply_coefficients(const gsl::not_null result, } } +bool operator==(const AdamsScheme& a, const AdamsScheme& b) { + return a.type == b.type and a.order == b.order; +} +bool operator!=(const AdamsScheme& a, const AdamsScheme& b) { + return not(a == b); +} + +namespace { +// Collect the ids used for interpolating during a step to `end_time` +// from `times`. +// +// For implicit schemes, the step containing (or ending at) `end_time` +// must have predictor data available. +template +OrderVector find_relevant_ids( + const ConstBoundaryHistoryTimes& times, const TimeType& end_time, + const AdamsScheme& scheme) { + OrderVector ids{}; + using difference_type = std::iterator_traits< + ConstBoundaryHistoryTimes::const_iterator>::difference_type; + const evolution_less<> less{times.front().time_runs_forward()}; + // Can't do a binary search because times are not sorted during self-start. + auto used_range_end = times.end(); + while (used_range_end != times.begin()) { + if (less((used_range_end - 1)->step_time(), end_time)) { + break; + } + --used_range_end; + } + const auto number_of_past_steps = + static_cast(scheme.order) - + (scheme.type == SchemeType::Implicit ? 1 : 0); + ASSERT(used_range_end - times.begin() >= number_of_past_steps, + "Insufficient past data."); + std::copy(used_range_end - number_of_past_steps, used_range_end, + std::back_inserter(ids)); + if (scheme.type == SchemeType::Implicit) { + const auto last_step = + static_cast(used_range_end - times.begin() - 1); + ASSERT(times.number_of_substeps(last_step) == 2, + "Must have substep data for implicit stepping."); + ids.push_back(times[{last_step, 1}]); + } + return ids; +} + +// Choose the relevant times from `local` and `remote` for defining +// small steps for `small_step_scheme`, using the specified schemes +// for interpolation on the local and remote sides. +// +// This is the most recent values from the union of the `local` and +// `remote` times with any values that should only be used for +// interpolation removed. +OrderVector