1050 lines
48 KiB
C++
1050 lines
48 KiB
C++
#include <array>
|
|
#include <cmath>
|
|
#include <concepts>
|
|
#include <cstdint>
|
|
#include <limits>
|
|
#include <memory>
|
|
#include <numbers>
|
|
#include <optional>
|
|
#include <span>
|
|
#include <stdexcept>
|
|
#include <type_traits>
|
|
#include <utility>
|
|
#include <variant>
|
|
|
|
#include <catch2/catch_test_macros.hpp>
|
|
#include <mfem.hpp>
|
|
#include <mpi.h>
|
|
|
|
import mean_field;
|
|
import test_helpers;
|
|
|
|
namespace solver_architecture_test {
|
|
struct MissingBackendApplicationContract { };
|
|
struct NonConstantBackendApplicationContract { };
|
|
struct WrongTypeBackendApplicationContract { };
|
|
struct OutOfDomainBackendApplicationContract { };
|
|
} // namespace solver_architecture_test
|
|
|
|
namespace mean_field::preconditioning::backend {
|
|
template <> struct Traits<solver_architecture_test::MissingBackendApplicationContract> {
|
|
static constexpr bool registered = true;
|
|
};
|
|
|
|
template <> struct Traits<solver_architecture_test::NonConstantBackendApplicationContract> {
|
|
static constexpr bool registered = true;
|
|
inline static ApplicationContract applicationContract = ApplicationContract::stationary_linear;
|
|
};
|
|
|
|
template <> struct Traits<solver_architecture_test::WrongTypeBackendApplicationContract> {
|
|
static constexpr bool registered = true;
|
|
static constexpr int applicationContract = 0;
|
|
};
|
|
|
|
template <> struct Traits<solver_architecture_test::OutOfDomainBackendApplicationContract> {
|
|
static constexpr bool registered = true;
|
|
static constexpr ApplicationContract applicationContract = static_cast<ApplicationContract>(127);
|
|
};
|
|
} // namespace mean_field::preconditioning::backend
|
|
|
|
namespace solver_architecture_test {
|
|
struct LifetimeProbe final {
|
|
const void *operatorIdentity{nullptr};
|
|
const void *preconditionerIdentity{nullptr};
|
|
const void *problemIdentity{nullptr};
|
|
const void *mapperIdentity{nullptr};
|
|
MPI_Comm communicator{MPI_COMM_NULL};
|
|
int rightHandSideWorkspaceSize{0};
|
|
int correctionWorkspaceSize{0};
|
|
bool backendDestroyed{false};
|
|
bool dependenciesAliveAtBackendDestruction{false};
|
|
bool customPreconditionerOwnsMarker{false};
|
|
bool activeRotationIsFinite{false};
|
|
std::array<double, 3> activeAngularVelocity{};
|
|
std::array<double, 3> activeRotationCenter{};
|
|
};
|
|
|
|
struct FlexibleBackend final : mean_field::solver::LinearBackendConfigurationTag {
|
|
static constexpr mean_field::preconditioning::ApplicationContract supportedPreconditionerContract =
|
|
mean_field::preconditioning::ApplicationContract::flexible;
|
|
|
|
std::shared_ptr<LifetimeProbe> probe;
|
|
bool duplicateCommunicator{false};
|
|
|
|
explicit FlexibleBackend(
|
|
std::shared_ptr<LifetimeProbe> lifetimeProbe = nullptr,
|
|
const bool ownsCommunicatorDuplicate = false
|
|
)
|
|
: probe(std::move(lifetimeProbe)),
|
|
duplicateCommunicator(ownsCommunicatorDuplicate) {
|
|
}
|
|
};
|
|
|
|
struct StationaryBackend final : mean_field::solver::LinearBackendConfigurationTag {
|
|
static constexpr mean_field::preconditioning::ApplicationContract supportedPreconditionerContract =
|
|
mean_field::preconditioning::ApplicationContract::stationary_linear;
|
|
};
|
|
|
|
struct NonStaticBackendConfiguration final : mean_field::solver::LinearBackendConfigurationTag {
|
|
mean_field::preconditioning::ApplicationContract supportedPreconditionerContract{
|
|
mean_field::preconditioning::ApplicationContract::stationary_linear
|
|
};
|
|
};
|
|
|
|
struct NonConstantBackendConfiguration final : mean_field::solver::LinearBackendConfigurationTag {
|
|
inline static mean_field::preconditioning::ApplicationContract supportedPreconditionerContract =
|
|
mean_field::preconditioning::ApplicationContract::stationary_linear;
|
|
};
|
|
|
|
struct OutOfDomainBackendConfiguration final : mean_field::solver::LinearBackendConfigurationTag {
|
|
static constexpr mean_field::preconditioning::ApplicationContract supportedPreconditionerContract =
|
|
static_cast<mean_field::preconditioning::ApplicationContract>(127);
|
|
};
|
|
|
|
class IdentityOperator final : public mfem::Operator {
|
|
public:
|
|
explicit IdentityOperator(const int size) : mfem::Operator(size) {
|
|
}
|
|
|
|
void Mult(
|
|
const mfem::Vector &input,
|
|
mfem::Vector &output
|
|
) const override {
|
|
output = input;
|
|
}
|
|
};
|
|
|
|
template <mean_field::preconditioning::ApplicationContract Contract>
|
|
class ContractInverse final : public mfem::Solver {
|
|
public:
|
|
static constexpr mean_field::preconditioning::ApplicationContract applicationContract = Contract;
|
|
|
|
explicit ContractInverse(const int size) : mfem::Solver(size) {
|
|
}
|
|
|
|
void SetOperator(const mfem::Operator &operation) override {
|
|
if (operation.Height() != Height() || operation.Width() != Width()) {
|
|
throw std::invalid_argument("The test inverse received an incompatible operator.");
|
|
}
|
|
}
|
|
|
|
void Mult(
|
|
const mfem::Vector &input,
|
|
mfem::Vector &output
|
|
) const override {
|
|
output = input;
|
|
}
|
|
};
|
|
|
|
using StationaryInverse = ContractInverse<mean_field::preconditioning::ApplicationContract::stationary_linear>;
|
|
using FlexibleInverse = ContractInverse<mean_field::preconditioning::ApplicationContract::flexible>;
|
|
|
|
struct NonStaticContract final {
|
|
mean_field::preconditioning::ApplicationContract applicationContract{
|
|
mean_field::preconditioning::ApplicationContract::stationary_linear
|
|
};
|
|
};
|
|
|
|
struct NonConstantContract final {
|
|
static mean_field::preconditioning::ApplicationContract applicationContract;
|
|
};
|
|
|
|
struct WrongTypeContract final {
|
|
static constexpr int applicationContract = 0;
|
|
};
|
|
|
|
struct OutOfDomainContract final {
|
|
static constexpr mean_field::preconditioning::ApplicationContract applicationContract =
|
|
static_cast<mean_field::preconditioning::ApplicationContract>(127);
|
|
};
|
|
|
|
struct ConflictingContract final {
|
|
static constexpr mean_field::preconditioning::ApplicationContract applicationContract =
|
|
mean_field::preconditioning::ApplicationContract::flexible;
|
|
using BackendType = mean_field::preconditioning::backend::Identity;
|
|
};
|
|
|
|
template <typename Backend> struct BackendContractCarrier final {
|
|
using BackendType = Backend;
|
|
};
|
|
|
|
struct NonStaticContractWithValidBackend final {
|
|
mean_field::preconditioning::ApplicationContract applicationContract{
|
|
mean_field::preconditioning::ApplicationContract::stationary_linear
|
|
};
|
|
using BackendType = mean_field::preconditioning::backend::Identity;
|
|
};
|
|
|
|
struct NonConstantContractWithValidBackend final {
|
|
inline static mean_field::preconditioning::ApplicationContract applicationContract =
|
|
mean_field::preconditioning::ApplicationContract::stationary_linear;
|
|
using BackendType = mean_field::preconditioning::backend::Identity;
|
|
};
|
|
|
|
template <typename Operator, typename Preconditioner> class PreparedBackend final {
|
|
public:
|
|
PreparedBackend(
|
|
const Operator &operation,
|
|
Preconditioner &preconditioner,
|
|
const MPI_Comm communicator,
|
|
std::shared_ptr<LifetimeProbe> probe,
|
|
const bool duplicateCommunicator
|
|
)
|
|
: m_operation(&operation),
|
|
m_preconditioner(&preconditioner),
|
|
m_communicator(communicator),
|
|
m_rightHandSideWorkspace(operation.Height()),
|
|
m_correctionWorkspace(operation.Width()),
|
|
m_probe(std::move(probe)) {
|
|
m_rightHandSideWorkspace = 0.0;
|
|
m_correctionWorkspace = 0.0;
|
|
if (m_probe != nullptr) {
|
|
m_probe->operatorIdentity = m_operation;
|
|
m_probe->preconditionerIdentity = m_preconditioner;
|
|
m_probe->communicator = m_communicator;
|
|
m_probe->rightHandSideWorkspaceSize = m_rightHandSideWorkspace.Size();
|
|
m_probe->correctionWorkspaceSize = m_correctionWorkspace.Size();
|
|
if constexpr (requires { preconditioner.GetPhysicalInverse().ownsMarker(); }) {
|
|
m_probe->customPreconditionerOwnsMarker = preconditioner.GetPhysicalInverse().ownsMarker();
|
|
}
|
|
if constexpr (requires { operation.GetProblem(); }) {
|
|
const auto &problem = operation.GetProblem();
|
|
m_probe->problemIdentity = std::addressof(problem);
|
|
m_probe->mapperIdentity = std::addressof(problem.GetDiscretization().domainMapper());
|
|
const auto rotation = problem.GetPreparedOperator().GetRotation();
|
|
m_probe->activeRotationIsFinite = true;
|
|
for (std::size_t component = 0; component < m_probe->activeAngularVelocity.size(); ++component) {
|
|
m_probe->activeAngularVelocity[component] =
|
|
rotation.angular_velocity()(static_cast<int>(component));
|
|
m_probe->activeRotationCenter[component] = rotation.center()(static_cast<int>(component));
|
|
m_probe->activeRotationIsFinite = m_probe->activeRotationIsFinite &&
|
|
std::isfinite(m_probe->activeAngularVelocity[component]) &&
|
|
std::isfinite(m_probe->activeRotationCenter[component]);
|
|
}
|
|
}
|
|
}
|
|
if (duplicateCommunicator) {
|
|
if (communicator == MPI_COMM_NULL || MPI_Comm_dup(communicator, &m_communicator) != MPI_SUCCESS) {
|
|
throw std::runtime_error("The test backend could not duplicate its communicator.");
|
|
}
|
|
m_ownsCommunicator = true;
|
|
if (m_probe != nullptr) {
|
|
m_probe->communicator = m_communicator;
|
|
}
|
|
}
|
|
}
|
|
|
|
PreparedBackend(const PreparedBackend &) = delete;
|
|
PreparedBackend &operator=(const PreparedBackend &) = delete;
|
|
PreparedBackend(PreparedBackend &&) = delete;
|
|
PreparedBackend &operator=(PreparedBackend &&) = delete;
|
|
|
|
~PreparedBackend() {
|
|
if (m_probe != nullptr) {
|
|
m_probe->backendDestroyed = true;
|
|
if constexpr (requires { m_preconditioner->IsCurrent(); }) {
|
|
m_probe->dependenciesAliveAtBackendDestruction =
|
|
m_operation != nullptr && m_preconditioner != nullptr && m_preconditioner->IsCurrent();
|
|
} else {
|
|
m_probe->dependenciesAliveAtBackendDestruction =
|
|
m_operation != nullptr && m_preconditioner != nullptr;
|
|
}
|
|
}
|
|
if (m_ownsCommunicator && m_communicator != MPI_COMM_NULL) {
|
|
MPI_Comm_free(&m_communicator);
|
|
}
|
|
}
|
|
|
|
[[nodiscard]] const Operator &GetOperator() const noexcept {
|
|
return *m_operation;
|
|
}
|
|
|
|
[[nodiscard]] const Preconditioner &GetPreconditioner() const noexcept {
|
|
return *m_preconditioner;
|
|
}
|
|
|
|
[[nodiscard]] MPI_Comm GetCommunicator() const noexcept {
|
|
return m_communicator;
|
|
}
|
|
|
|
[[nodiscard]] bool IsReady() const noexcept {
|
|
return m_operation != nullptr && m_preconditioner != nullptr && m_communicator != MPI_COMM_NULL &&
|
|
m_rightHandSideWorkspace.Size() == m_operation->Height() &&
|
|
m_correctionWorkspace.Size() == m_operation->Width();
|
|
}
|
|
|
|
[[nodiscard]] int RightHandSideSize() const noexcept {
|
|
return m_operation->Height();
|
|
}
|
|
|
|
[[nodiscard]] int CorrectionSize() const noexcept {
|
|
return m_operation->Width();
|
|
}
|
|
|
|
[[nodiscard]] mean_field::solver::LinearSolveReport Solve(
|
|
const mfem::Vector &rightHandSide,
|
|
mfem::Vector &correction,
|
|
const mean_field::solver::LinearSolveControl &control
|
|
) {
|
|
if (rightHandSide.Size() != RightHandSideSize() || correction.Size() != CorrectionSize()) {
|
|
throw std::invalid_argument("The test backend requires compatible preallocated vectors.");
|
|
}
|
|
control.Validate();
|
|
m_operation->Mult(correction, m_correctionWorkspace);
|
|
m_rightHandSideWorkspace = rightHandSide;
|
|
m_rightHandSideWorkspace -= m_correctionWorkspace;
|
|
const double localNorm = m_rightHandSideWorkspace.Norml2();
|
|
const double localNormSquared = localNorm * localNorm;
|
|
double globalNormSquared = 0.0;
|
|
if (MPI_Allreduce(&localNormSquared, &globalNormSquared, 1, MPI_DOUBLE, MPI_SUM, m_communicator) !=
|
|
MPI_SUCCESS) {
|
|
throw std::runtime_error("The test backend could not reduce its initial residual norm.");
|
|
}
|
|
m_correctionWorkspace = rightHandSide;
|
|
correction = m_correctionWorkspace;
|
|
return {
|
|
.status = mean_field::solver::LinearSolveStatus::converged,
|
|
.control = control,
|
|
.iterations = 0,
|
|
.restarts = 0,
|
|
.initialResidualNorm = std::sqrt(globalNormSquared),
|
|
.reportedResidualNorm = 0.0,
|
|
.trueResidualNorm = 0.0,
|
|
.relativeTrueResidualNorm = 0.0,
|
|
.operatorApplications = 0,
|
|
.inversePreconditionerApplications = 0,
|
|
.solveSeconds = 0.0
|
|
};
|
|
}
|
|
|
|
private:
|
|
const Operator *m_operation;
|
|
Preconditioner *m_preconditioner;
|
|
MPI_Comm m_communicator;
|
|
mfem::Vector m_rightHandSideWorkspace;
|
|
mfem::Vector m_correctionWorkspace;
|
|
std::shared_ptr<LifetimeProbe> m_probe;
|
|
bool m_ownsCommunicator{false};
|
|
};
|
|
|
|
template <
|
|
typename Operator,
|
|
typename Preconditioner>
|
|
[[nodiscard]] auto prepareLinearBackend(
|
|
FlexibleBackend configuration,
|
|
const Operator &operation,
|
|
Preconditioner &preconditioner,
|
|
const MPI_Comm communicator
|
|
) {
|
|
return PreparedBackend<Operator, Preconditioner>{
|
|
operation, preconditioner, communicator, std::move(configuration.probe), configuration.duplicateCommunicator
|
|
};
|
|
}
|
|
|
|
template <
|
|
typename Operator,
|
|
typename Preconditioner>
|
|
[[nodiscard]] auto prepareLinearBackend(
|
|
StationaryBackend,
|
|
const Operator &operation,
|
|
Preconditioner &preconditioner,
|
|
const MPI_Comm communicator
|
|
) {
|
|
return PreparedBackend<Operator, Preconditioner>{operation, preconditioner, communicator, nullptr, false};
|
|
}
|
|
|
|
struct BorrowingBackend final : mean_field::solver::LinearBackendConfigurationTag {
|
|
static constexpr mean_field::preconditioning::ApplicationContract supportedPreconditionerContract =
|
|
mean_field::preconditioning::ApplicationContract::flexible;
|
|
};
|
|
|
|
template <
|
|
typename Operator,
|
|
typename Preconditioner>
|
|
[[nodiscard]] auto prepareLinearBackend(
|
|
BorrowingBackend,
|
|
const Operator &,
|
|
Preconditioner &,
|
|
MPI_Comm
|
|
)
|
|
-> PreparedBackend<
|
|
Operator,
|
|
Preconditioner> &;
|
|
|
|
struct OwningPreconditionerPrescription final : mean_field::preconditioning::StellarPreconditionerPrescriptionTag {
|
|
OwningPreconditionerPrescription() : marker(std::make_unique<int>(37)) {
|
|
}
|
|
|
|
OwningPreconditionerPrescription(const OwningPreconditionerPrescription &) = delete;
|
|
OwningPreconditionerPrescription &operator=(const OwningPreconditionerPrescription &) = delete;
|
|
OwningPreconditionerPrescription(OwningPreconditionerPrescription &&) noexcept = default;
|
|
OwningPreconditionerPrescription &operator=(OwningPreconditionerPrescription &&) = delete;
|
|
|
|
std::unique_ptr<int> marker;
|
|
};
|
|
|
|
template <typename Problem> class OwningPreparedInverse final : public mfem::Solver {
|
|
public:
|
|
static constexpr mean_field::preconditioning::ApplicationContract applicationContract =
|
|
mean_field::preconditioning::ApplicationContract::flexible;
|
|
|
|
OwningPreparedInverse(
|
|
const Problem &problem,
|
|
std::unique_ptr<int> marker
|
|
)
|
|
: mfem::Solver(
|
|
problem.StateSize(),
|
|
problem.EquationSize()
|
|
),
|
|
m_problem(&problem),
|
|
m_preparationGeneration(problem.GetPreparationGeneration()),
|
|
m_marker(std::move(marker)) {
|
|
if (m_marker == nullptr) {
|
|
throw std::invalid_argument("The test preconditioner requires its owned marker.");
|
|
}
|
|
}
|
|
|
|
OwningPreparedInverse(const OwningPreparedInverse &) = delete;
|
|
OwningPreparedInverse &operator=(const OwningPreparedInverse &) = delete;
|
|
OwningPreparedInverse(OwningPreparedInverse &&) = delete;
|
|
OwningPreparedInverse &operator=(OwningPreparedInverse &&) = delete;
|
|
|
|
void SetOperator(const mfem::Operator &operation) override {
|
|
if (std::addressof(operation) != std::addressof(m_problem->GetLinearizationOperator())) {
|
|
throw std::invalid_argument("The test preconditioner cannot be rebound to another problem.");
|
|
}
|
|
}
|
|
|
|
void Mult(
|
|
const mfem::Vector &input,
|
|
mfem::Vector &output
|
|
) const override {
|
|
output = input;
|
|
}
|
|
|
|
[[nodiscard]] const Problem &GetProblem() const noexcept {
|
|
return *m_problem;
|
|
}
|
|
|
|
[[nodiscard]] bool IsCurrent() const noexcept {
|
|
return m_problem != nullptr && m_problem->IsPrepared() &&
|
|
m_preparationGeneration == m_problem->GetPreparationGeneration();
|
|
}
|
|
|
|
void Refresh() {
|
|
if (m_problem == nullptr || !m_problem->IsPrepared()) {
|
|
throw std::logic_error("The test preconditioner cannot refresh from an unprepared problem.");
|
|
}
|
|
m_preparationGeneration = m_problem->GetPreparationGeneration();
|
|
}
|
|
|
|
[[nodiscard]] bool ownsMarker() const noexcept {
|
|
return m_marker != nullptr && *m_marker == 37;
|
|
}
|
|
|
|
private:
|
|
const Problem *m_problem;
|
|
std::uint64_t m_preparationGeneration;
|
|
std::unique_ptr<int> m_marker;
|
|
};
|
|
|
|
template <typename Problem>
|
|
[[nodiscard]] auto prepareStellarPreconditioner(
|
|
OwningPreconditionerPrescription prescription,
|
|
const Problem &problem
|
|
) {
|
|
return OwningPreparedInverse<Problem>{problem, std::move(prescription.marker)};
|
|
}
|
|
|
|
struct BorrowingPreconditionerPrescription final
|
|
: mean_field::preconditioning::StellarPreconditionerPrescriptionTag { };
|
|
|
|
template <typename Problem>
|
|
[[nodiscard]] auto prepareStellarPreconditioner(
|
|
BorrowingPreconditionerPrescription,
|
|
const Problem &
|
|
) -> OwningPreparedInverse<Problem> &;
|
|
|
|
[[nodiscard]] mean_field::fem::FEM makeFiniteElements() {
|
|
const mean_field::utils::Args arguments = test_utils::setup_args();
|
|
return mean_field::fem::setup_fem(arguments.mesh_file, arguments, 0);
|
|
}
|
|
|
|
[[nodiscard]] auto makeFixedCentralDensityModel() {
|
|
using namespace mean_field;
|
|
constexpr double stellarRadius = utils::RADIUS;
|
|
constexpr double targetMass = utils::MASS;
|
|
const double polytropicConstant = 2.0 * utils::G * stellarRadius * stellarRadius / std::numbers::pi_v<double>;
|
|
const double centralDensity =
|
|
std::numbers::pi_v<double> * targetMass / (4.0 * stellarRadius * stellarRadius * stellarRadius);
|
|
return model::StellarModel(
|
|
eos::Polytrope({.n = 1.0, .K = polytropicConstant}),
|
|
surface::Isobaric({.Psurf = dimensions::PressureValue{0.0}}),
|
|
integral::FixedTotalMass({.Mtotal = dimensions::MassValue{targetMass}}),
|
|
constraint::FixedCentralDensity({.RhoC = dimensions::DensityValue{centralDensity}})
|
|
);
|
|
}
|
|
|
|
[[nodiscard]] auto makeFixedCentralDensityAngularMomentumModel() {
|
|
using namespace mean_field;
|
|
constexpr double stellarRadius = utils::RADIUS;
|
|
constexpr double targetMass = utils::MASS;
|
|
const double polytropicConstant = 2.0 * utils::G * stellarRadius * stellarRadius / std::numbers::pi_v<double>;
|
|
const double centralDensity =
|
|
std::numbers::pi_v<double> * targetMass / (4.0 * stellarRadius * stellarRadius * stellarRadius);
|
|
return model::StellarModel(
|
|
eos::Polytrope({.n = 1.0, .K = polytropicConstant}),
|
|
surface::Isobaric({.Psurf = dimensions::PressureValue{0.0}}),
|
|
integral::FixedTotalMass({.Mtotal = dimensions::MassValue{targetMass}}),
|
|
integral::FixedAngularMomentum({.Jtotal = dimensions::AngularMomentumValue{0.05}}),
|
|
constraint::FixedCentralDensity({.RhoC = dimensions::DensityValue{centralDensity}})
|
|
);
|
|
}
|
|
|
|
[[nodiscard]] auto makeMassOnlyModel() {
|
|
using namespace mean_field;
|
|
constexpr double stellarRadius = utils::RADIUS;
|
|
constexpr double targetMass = utils::MASS;
|
|
const double polytropicConstant = 2.0 * utils::G * stellarRadius * stellarRadius / std::numbers::pi_v<double>;
|
|
return model::StellarModel(
|
|
eos::Polytrope({.n = 1.0, .K = polytropicConstant}),
|
|
surface::Isobaric({.Psurf = dimensions::PressureValue{0.0}}),
|
|
integral::FixedTotalMass({.Mtotal = dimensions::MassValue{targetMass}})
|
|
);
|
|
}
|
|
|
|
[[nodiscard]] mean_field::dimensions::DensityValue matchingCentralDensity() {
|
|
constexpr double stellarRadius = mean_field::utils::RADIUS;
|
|
constexpr double targetMass = mean_field::utils::MASS;
|
|
return mean_field::dimensions::DensityValue{
|
|
std::numbers::pi_v<double> * targetMass / (4.0 * stellarRadius * stellarRadius * stellarRadius)
|
|
};
|
|
}
|
|
|
|
[[nodiscard]] mean_field::physics::RigidRotation makeRotation(const double angularSpeed = 0.0) {
|
|
mfem::Vector angularVelocity(3);
|
|
mfem::Vector center(3);
|
|
angularVelocity = 0.0;
|
|
center = 0.0;
|
|
angularVelocity(2) = angularSpeed;
|
|
center(0) = 0.125;
|
|
center(1) = -0.25;
|
|
center(2) = 0.5;
|
|
return mean_field::physics::RigidRotation{angularVelocity, center};
|
|
}
|
|
|
|
struct ImmovableRadialSeed final {
|
|
ImmovableRadialSeed() = default;
|
|
ImmovableRadialSeed(const ImmovableRadialSeed &) = delete;
|
|
ImmovableRadialSeed(ImmovableRadialSeed &&) = delete;
|
|
ImmovableRadialSeed &operator=(const ImmovableRadialSeed &) = delete;
|
|
ImmovableRadialSeed &operator=(ImmovableRadialSeed &&) = delete;
|
|
};
|
|
|
|
template <typename Model>
|
|
[[nodiscard]] mean_field::seed::RadialProfile generateRadialProfile(
|
|
const Model &,
|
|
const ImmovableRadialSeed &
|
|
);
|
|
|
|
template <typename Candidate>
|
|
concept HasEvaluate = requires(Candidate &candidate) { candidate.evaluate(); };
|
|
|
|
template <typename Candidate>
|
|
concept ExposesFiniteElementModel = requires(const Candidate &candidate) { candidate.finiteElementModel(); };
|
|
|
|
template <typename Candidate>
|
|
concept ExposesStructureManifest = requires(const Candidate &candidate) { candidate.manifest(); };
|
|
|
|
template <typename Candidate>
|
|
concept ExposesMutableMFEMState = requires(const Candidate &candidate) {
|
|
{ candidate.state() } -> std::same_as<const mfem::Vector &>;
|
|
};
|
|
|
|
template <typename Candidate>
|
|
concept ExposesStructureStateView = requires(const Candidate &candidate) { candidate.stateView(); };
|
|
|
|
template <typename Candidate>
|
|
concept ExposesOwnedProblem = requires(const Candidate &candidate) { candidate.problem(); } ||
|
|
requires(const Candidate &candidate) { candidate.GetProblem(); };
|
|
|
|
template <typename Candidate>
|
|
concept ReadsCommunicatorFromRvalue = requires(Candidate &&candidate) { std::move(candidate).communicator(); };
|
|
|
|
template <typename Candidate>
|
|
concept ReadsProblemCommunicatorFromRvalue =
|
|
requires(Candidate &&candidate) { std::move(candidate).GetCommunicator(); };
|
|
|
|
template <typename Structure, typename Prescription, typename Backend>
|
|
concept RestartableFromRvalue = requires(Structure &&structure, Prescription prescription, Backend backend) {
|
|
mean_field::solver::make(std::move(structure), std::move(prescription), std::move(backend));
|
|
};
|
|
|
|
template <typename Structure, typename Prescription, typename Backend>
|
|
concept RestartableFromLvalue = requires(Structure &structure, Prescription prescription, Backend backend) {
|
|
mean_field::solver::make(structure, std::move(prescription), std::move(backend));
|
|
};
|
|
|
|
template <typename Model, typename Discretization, typename Prescription, typename Backend, typename InitialState>
|
|
concept HighLevelSeedAccepted = requires(
|
|
Model model,
|
|
Discretization discretization,
|
|
Prescription prescription,
|
|
Backend backend,
|
|
InitialState initialState
|
|
) {
|
|
mean_field::solver::makeContext(
|
|
std::move(model), std::move(discretization), std::move(prescription), std::move(backend),
|
|
std::move(initialState)
|
|
);
|
|
};
|
|
|
|
template <typename Model, typename Discretization, typename Prescription, typename Backend, typename InitialState>
|
|
concept ExplicitSeedOverloadsAccepted = requires(
|
|
Model model,
|
|
Discretization discretization,
|
|
Prescription prescription,
|
|
Backend backend,
|
|
InitialState initialState,
|
|
mean_field::seed::StellarEquilibriumProjectionOptions options,
|
|
mean_field::physics::RigidRotation rotation
|
|
) {
|
|
mean_field::solver::makeContext(
|
|
std::move(model), std::move(discretization), std::move(prescription), std::move(backend),
|
|
std::move(initialState)
|
|
);
|
|
mean_field::solver::makeContext(
|
|
std::move(model), std::move(discretization), std::move(prescription), std::move(backend),
|
|
std::move(initialState), std::move(options)
|
|
);
|
|
mean_field::solver::makeContext(
|
|
std::move(model), std::move(discretization), std::move(prescription), std::move(backend),
|
|
std::move(initialState), std::move(rotation)
|
|
);
|
|
mean_field::solver::makeContext(
|
|
std::move(model), std::move(discretization), std::move(prescription), std::move(backend),
|
|
std::move(initialState), std::move(options), std::move(rotation)
|
|
);
|
|
};
|
|
|
|
template <typename Model, typename Discretization, typename Prescription, typename Backend>
|
|
concept DefaultSeedWithRotationAccepted = requires(
|
|
Model model,
|
|
Discretization discretization,
|
|
Prescription prescription,
|
|
Backend backend,
|
|
mean_field::physics::RigidRotation rotation,
|
|
mean_field::seed::StellarEquilibriumProjectionOptions options
|
|
) {
|
|
mean_field::solver::makeContext(
|
|
std::move(model), std::move(discretization), std::move(prescription), std::move(backend),
|
|
std::move(rotation)
|
|
);
|
|
mean_field::solver::makeContext(
|
|
std::move(model), std::move(discretization), std::move(prescription), std::move(backend),
|
|
std::move(options), std::move(rotation)
|
|
);
|
|
};
|
|
} // namespace solver_architecture_test
|
|
|
|
TEST_CASE(
|
|
"Linear Backend Boundary Is Generic And Contract-Aware",
|
|
"[solver][architecture][linear]"
|
|
) {
|
|
using namespace mean_field;
|
|
using namespace solver_architecture_test;
|
|
|
|
STATIC_CHECK(solver::LinearBackendConfiguration<FlexibleBackend>);
|
|
STATIC_CHECK(solver::LinearBackendConfiguration<StationaryBackend>);
|
|
STATIC_CHECK_FALSE(solver::LinearBackendConfiguration<NonStaticBackendConfiguration>);
|
|
STATIC_CHECK_FALSE(solver::LinearBackendConfiguration<NonConstantBackendConfiguration>);
|
|
STATIC_CHECK_FALSE(solver::LinearBackendConfiguration<OutOfDomainBackendConfiguration>);
|
|
STATIC_CHECK(solver::LinearPreconditionerApplicationContractAvailable<StationaryInverse>);
|
|
STATIC_CHECK(solver::LinearPreconditionerApplicationContractAvailable<FlexibleInverse>);
|
|
STATIC_CHECK_FALSE(solver::LinearPreconditionerApplicationContractAvailable<NonStaticContract>);
|
|
STATIC_CHECK_FALSE(solver::LinearPreconditionerApplicationContractAvailable<NonConstantContract>);
|
|
STATIC_CHECK_FALSE(solver::LinearPreconditionerApplicationContractAvailable<WrongTypeContract>);
|
|
STATIC_CHECK_FALSE(solver::LinearPreconditionerApplicationContractAvailable<OutOfDomainContract>);
|
|
STATIC_CHECK_FALSE(solver::LinearPreconditionerApplicationContractAvailable<ConflictingContract>);
|
|
STATIC_CHECK_FALSE(solver::LinearPreconditionerApplicationContractAvailable<NonStaticContractWithValidBackend>);
|
|
STATIC_CHECK_FALSE(solver::LinearPreconditionerApplicationContractAvailable<NonConstantContractWithValidBackend>);
|
|
STATIC_CHECK_FALSE(
|
|
solver::LinearPreconditionerApplicationContractAvailable<
|
|
BackendContractCarrier<MissingBackendApplicationContract>>
|
|
);
|
|
STATIC_CHECK_FALSE(
|
|
solver::LinearPreconditionerApplicationContractAvailable<
|
|
BackendContractCarrier<NonConstantBackendApplicationContract>>
|
|
);
|
|
STATIC_CHECK_FALSE(
|
|
solver::LinearPreconditionerApplicationContractAvailable<
|
|
BackendContractCarrier<WrongTypeBackendApplicationContract>>
|
|
);
|
|
STATIC_CHECK_FALSE(
|
|
solver::LinearPreconditionerApplicationContractAvailable<
|
|
BackendContractCarrier<OutOfDomainBackendApplicationContract>>
|
|
);
|
|
STATIC_CHECK(solver::LinearBackendPreconditionerCompatible<FlexibleBackend, FlexibleInverse>);
|
|
STATIC_CHECK(solver::LinearBackendPreconditionerCompatible<StationaryBackend, StationaryInverse>);
|
|
STATIC_CHECK_FALSE(solver::LinearBackendPreconditionerCompatible<StationaryBackend, FlexibleInverse>);
|
|
|
|
using DefaultModel = std::remove_cvref_t<decltype(makeFixedCentralDensityModel())>;
|
|
using DefaultProblem = equilibrium::StellarEquilibriumProblem<DefaultModel, equilibrium::StellarDiscretization>;
|
|
using DefaultPhysicalInverse =
|
|
preconditioning::PreparedStellarInverseType<preconditioning::DefaultStellarPreconditioner, DefaultProblem>;
|
|
using DefaultNormalizedInverse =
|
|
normalization::NormalizedStellarPreconditioner<DefaultProblem, DefaultPhysicalInverse>;
|
|
STATIC_CHECK(solver::LinearPreconditionerApplicationContractAvailable<DefaultPhysicalInverse>);
|
|
STATIC_CHECK(
|
|
solver::linearPreconditionerApplicationContract<DefaultPhysicalInverse> ==
|
|
preconditioning::ApplicationContract::stationary_linear
|
|
);
|
|
STATIC_CHECK(solver::LinearPreconditionerApplicationContractAvailable<DefaultNormalizedInverse>);
|
|
STATIC_CHECK(
|
|
solver::linearPreconditionerApplicationContract<DefaultNormalizedInverse> ==
|
|
preconditioning::ApplicationContract::stationary_linear
|
|
);
|
|
STATIC_CHECK(solver::LinearBackendPreconditionerCompatible<StationaryBackend, DefaultNormalizedInverse>);
|
|
STATIC_CHECK(
|
|
solver::StellarEquilibriumContextConfiguration<
|
|
DefaultModel, equilibrium::StellarDiscretization, preconditioning::DefaultStellarPreconditioner,
|
|
StationaryBackend>
|
|
);
|
|
STATIC_CHECK(solver::LinearBackendConfiguration<BorrowingBackend>);
|
|
STATIC_CHECK_FALSE(solver::LinearBackendRuntimeAvailableFor<BorrowingBackend, IdentityOperator, FlexibleInverse>);
|
|
|
|
IdentityOperator operation(4);
|
|
FlexibleInverse inverse(4);
|
|
auto prepared = prepareLinearBackend(FlexibleBackend{}, operation, inverse, MPI_COMM_WORLD);
|
|
using Prepared = std::remove_cvref_t<decltype(prepared)>;
|
|
STATIC_CHECK(solver::PreparedLinearBackendFor<Prepared, IdentityOperator, FlexibleInverse>);
|
|
STATIC_CHECK_FALSE(std::copy_constructible<Prepared>);
|
|
STATIC_CHECK_FALSE(std::move_constructible<Prepared>);
|
|
|
|
auto nullPrepared = prepareLinearBackend(FlexibleBackend{}, operation, inverse, MPI_COMM_NULL);
|
|
CHECK_FALSE(nullPrepared.IsReady());
|
|
|
|
mfem::Vector rightHandSide(4);
|
|
mfem::Vector correction(4);
|
|
rightHandSide = 2.0;
|
|
correction = -1.0;
|
|
const solver::LinearSolveControl control{
|
|
.relativeTolerance = 2.0e-7, .absoluteTolerance = 3.0e-12, .maximumIterations = 17
|
|
};
|
|
CHECK(control.ConvergenceThreshold(4.0) == 8.0e-7);
|
|
CHECK(control.ConvergenceThreshold(0.0) == control.absoluteTolerance);
|
|
CHECK_THROWS_AS(solver::LinearSolveControl{.relativeTolerance = -1.0}.Validate(), std::invalid_argument);
|
|
CHECK_THROWS_AS(
|
|
solver::LinearSolveControl{.relativeTolerance = std::numeric_limits<double>::quiet_NaN()}.Validate(),
|
|
std::invalid_argument
|
|
);
|
|
CHECK_THROWS_AS(solver::LinearSolveControl{.absoluteTolerance = -1.0}.Validate(), std::invalid_argument);
|
|
CHECK_THROWS_AS(
|
|
solver::LinearSolveControl{.absoluteTolerance = std::numeric_limits<double>::quiet_NaN()}.Validate(),
|
|
std::invalid_argument
|
|
);
|
|
CHECK_THROWS_AS(
|
|
solver::LinearSolveControl{.absoluteTolerance = std::numeric_limits<double>::infinity()}.Validate(),
|
|
std::invalid_argument
|
|
);
|
|
CHECK_THROWS_AS(solver::LinearSolveControl{.maximumIterations = 0}.Validate(), std::invalid_argument);
|
|
CHECK_THROWS_AS(control.ConvergenceThreshold(-1.0), std::invalid_argument);
|
|
CHECK_THROWS_AS(control.ConvergenceThreshold(std::numeric_limits<double>::quiet_NaN()), std::invalid_argument);
|
|
CHECK_THROWS_AS(control.ConvergenceThreshold(std::numeric_limits<double>::infinity()), std::invalid_argument);
|
|
CHECK_THROWS_AS(
|
|
solver::LinearSolveControl{.relativeTolerance = std::numeric_limits<double>::max()}.ConvergenceThreshold(2.0),
|
|
std::invalid_argument
|
|
);
|
|
const auto report = prepared.Solve(rightHandSide, correction, control);
|
|
CHECK(report.Converged());
|
|
CHECK(report.control.relativeTolerance == control.relativeTolerance);
|
|
CHECK(report.control.absoluteTolerance == control.absoluteTolerance);
|
|
CHECK(report.control.maximumIterations == control.maximumIterations);
|
|
CHECK(report.initialResidualNorm == 6.0);
|
|
correction -= rightHandSide;
|
|
CHECK(correction.Norml2() == 0.0);
|
|
}
|
|
|
|
TEST_CASE(
|
|
"Solver Assembly Owns Stable Prepared Runtime Before Evaluation",
|
|
"[solver][architecture][ownership]"
|
|
) {
|
|
using namespace mean_field;
|
|
using namespace solver_architecture_test;
|
|
|
|
auto finiteElements = makeFiniteElements();
|
|
REQUIRE(finiteElements.okay());
|
|
const void *mapperIdentity = finiteElements.domainMapperStateless.get();
|
|
const MPI_Comm expectedCommunicator = finiteElements.mesh->GetComm();
|
|
|
|
auto discretization = equilibrium::makeStellarDiscretization(
|
|
std::move(finiteElements),
|
|
normalization::PhysicalRieszDiagonal{dimensions::LengthValue{utils::RADIUS}, utils::G}
|
|
);
|
|
using Discretization = std::remove_cvref_t<decltype(discretization)>;
|
|
STATIC_CHECK(std::move_constructible<Discretization>);
|
|
STATIC_CHECK_FALSE(std::copy_constructible<Discretization>);
|
|
STATIC_CHECK_FALSE(std::is_move_assignable_v<Discretization>);
|
|
STATIC_CHECK_FALSE(std::constructible_from<Discretization, fem::FEM &>);
|
|
STATIC_CHECK_FALSE(ReadsCommunicatorFromRvalue<Discretization>);
|
|
STATIC_CHECK(
|
|
std::same_as<typename Discretization::NormalizationPrescriptionType, normalization::PhysicalRieszDiagonal<>>
|
|
);
|
|
|
|
auto probe = std::make_shared<LifetimeProbe>();
|
|
{
|
|
auto model = makeFixedCentralDensityAngularMomentumModel();
|
|
using Model = std::remove_cvref_t<decltype(model)>;
|
|
using Problem = equilibrium::StellarEquilibriumProblem<Model, Discretization>;
|
|
STATIC_CHECK(Problem::generatedRotationProviderCount == 1);
|
|
STATIC_CHECK_FALSE(ReadsProblemCommunicatorFromRvalue<Problem>);
|
|
|
|
auto context = solver::makeContext(
|
|
std::move(model), std::move(discretization), preconditioning::makePreconditioner(),
|
|
FlexibleBackend{probe, true}
|
|
);
|
|
using Context = std::remove_cvref_t<decltype(context)>;
|
|
STATIC_CHECK_FALSE(std::move_constructible<Context>);
|
|
STATIC_CHECK_FALSE(std::copy_constructible<Context>);
|
|
STATIC_CHECK_FALSE(HasEvaluate<Context>);
|
|
|
|
REQUIRE(context.isReady());
|
|
CHECK_FALSE(context.hasActiveSolver());
|
|
{
|
|
auto borrowingSolver = solver::make(context, solver::nonlinear::Newton{});
|
|
STATIC_CHECK_FALSE(std::move_constructible<std::remove_cvref_t<decltype(borrowingSolver)>>);
|
|
STATIC_CHECK_FALSE(std::copy_constructible<std::remove_cvref_t<decltype(borrowingSolver)>>);
|
|
STATIC_CHECK(HasEvaluate<std::remove_cvref_t<decltype(borrowingSolver)>>);
|
|
REQUIRE(borrowingSolver.isReady());
|
|
CHECK(context.hasActiveSolver());
|
|
}
|
|
CHECK_FALSE(context.hasActiveSolver());
|
|
CHECK_FALSE(discretization.isCurrent());
|
|
STATIC_CHECK_FALSE(ExposesFiniteElementModel<Discretization>);
|
|
CHECK_THROWS_AS(discretization.communicator(), std::logic_error);
|
|
CHECK(probe->mapperIdentity == mapperIdentity);
|
|
CHECK(probe->rightHandSideWorkspaceSize > 0);
|
|
CHECK(probe->correctionWorkspaceSize > 0);
|
|
CHECK(probe->rightHandSideWorkspaceSize == probe->correctionWorkspaceSize);
|
|
CHECK(probe->communicator != MPI_COMM_NULL);
|
|
int communicatorComparison = MPI_UNEQUAL;
|
|
REQUIRE(MPI_Comm_compare(probe->communicator, expectedCommunicator, &communicatorComparison) == MPI_SUCCESS);
|
|
CHECK((communicatorComparison == MPI_CONGRUENT || communicatorComparison == MPI_IDENT));
|
|
CHECK(probe->operatorIdentity != nullptr);
|
|
CHECK(probe->preconditionerIdentity != nullptr);
|
|
CHECK(probe->problemIdentity != nullptr);
|
|
CHECK(probe->activeRotationIsFinite);
|
|
CHECK(probe->activeAngularVelocity[0] == 0.0);
|
|
CHECK(probe->activeAngularVelocity[1] == 0.0);
|
|
CHECK(probe->activeAngularVelocity[2] > 0.0);
|
|
CHECK((probe->activeRotationCenter == std::array{0.0, 0.0, 0.0}));
|
|
|
|
CHECK_FALSE(probe->backendDestroyed);
|
|
REQUIRE(context.isReady());
|
|
}
|
|
|
|
CHECK(probe->backendDestroyed);
|
|
CHECK(probe->dependenciesAliveAtBackendDestruction);
|
|
}
|
|
|
|
TEST_CASE(
|
|
"Active Rotation Is Unavailable Before Problem Preparation",
|
|
"[solver][architecture][rotation]"
|
|
) {
|
|
using namespace mean_field;
|
|
using namespace solver_architecture_test;
|
|
|
|
auto finiteElements = makeFiniteElements();
|
|
auto problem = equilibrium::discretize(
|
|
makeFixedCentralDensityModel(), equilibrium::StellarDiscretization{std::move(finiteElements)}
|
|
);
|
|
CHECK_FALSE(problem.IsPrepared());
|
|
CHECK_THROWS(problem.GetPreparedOperator().GetRotation());
|
|
}
|
|
|
|
TEST_CASE(
|
|
"ADL Preparation Boundaries Reject Borrowed Runtime Results",
|
|
"[solver][architecture][ownership][customization]"
|
|
) {
|
|
using namespace mean_field;
|
|
using namespace solver_architecture_test;
|
|
|
|
using Model = std::remove_cvref_t<decltype(makeFixedCentralDensityModel())>;
|
|
using Discretization = equilibrium::StellarDiscretization;
|
|
using Problem = equilibrium::StellarEquilibriumProblem<Model, Discretization>;
|
|
|
|
STATIC_CHECK(preconditioning::StellarPreconditionerPrescription<OwningPreconditionerPrescription>);
|
|
STATIC_CHECK(preconditioning::PreparedStellarInverseFor<OwningPreparedInverse<Problem>, Problem>);
|
|
STATIC_CHECK(preconditioning::StellarPreconditionerRuntimeAvailableFor<OwningPreconditionerPrescription, Problem>);
|
|
STATIC_CHECK_FALSE(
|
|
solver::StellarEquilibriumContextConfiguration<
|
|
Model, Discretization, OwningPreconditionerPrescription, StationaryBackend>
|
|
);
|
|
STATIC_CHECK(preconditioning::StellarPreconditionerPrescription<BorrowingPreconditionerPrescription>);
|
|
STATIC_CHECK_FALSE(
|
|
preconditioning::StellarPreconditionerRuntimeAvailableFor<BorrowingPreconditionerPrescription, Problem>
|
|
);
|
|
}
|
|
|
|
TEST_CASE(
|
|
"Explicit Seed And Prescribed Rotation Assemble Without A Model Default",
|
|
"[solver][architecture][seed]"
|
|
) {
|
|
using namespace mean_field;
|
|
using namespace solver_architecture_test;
|
|
|
|
using MassOnlyModel = std::remove_cvref_t<decltype(makeMassOnlyModel())>;
|
|
using FixedDensityModel = std::remove_cvref_t<decltype(makeFixedCentralDensityModel())>;
|
|
STATIC_CHECK_FALSE(solver::DefaultStellarEquilibriumInitialStateAvailableFor<MassOnlyModel>);
|
|
STATIC_CHECK(solver::DefaultStellarEquilibriumInitialStateAvailableFor<FixedDensityModel>);
|
|
|
|
auto finiteElements = makeFiniteElements();
|
|
auto probe = std::make_shared<LifetimeProbe>();
|
|
auto context = solver::makeContext(
|
|
makeMassOnlyModel(), equilibrium::StellarDiscretization{std::move(finiteElements)},
|
|
OwningPreconditionerPrescription{}, FlexibleBackend{probe},
|
|
seed::LaneEmden({.centralDensity = matchingCentralDensity(), .radialSampleCount = 64}),
|
|
seed::StellarEquilibriumProjectionOptions{}, makeRotation(0.03)
|
|
);
|
|
|
|
REQUIRE(context.isReady());
|
|
CHECK(probe->customPreconditionerOwnsMarker);
|
|
CHECK(probe->activeRotationIsFinite);
|
|
CHECK((probe->activeAngularVelocity == std::array{0.0, 0.0, 0.03}));
|
|
CHECK((probe->activeRotationCenter == std::array{0.125, -0.25, 0.5}));
|
|
}
|
|
|
|
TEST_CASE(
|
|
"Only Target-Projected Seed Strategies Enter High-Level Solver Assembly",
|
|
"[solver][architecture][seed][contract]"
|
|
) {
|
|
using namespace mean_field;
|
|
using namespace solver_architecture_test;
|
|
|
|
using Model = std::remove_cvref_t<decltype(makeFixedCentralDensityModel())>;
|
|
using Discretization = equilibrium::StellarDiscretization;
|
|
using Prescription = preconditioning::DefaultStellarPreconditioner;
|
|
using Projected = seed::ProjectedEquilibriumState<Model>;
|
|
|
|
STATIC_CHECK_FALSE(solver::StellarEquilibriumInitialStateFor<Projected, Model>);
|
|
STATIC_CHECK(seed::RadialSeedStrategyFor<ImmovableRadialSeed, Model>);
|
|
STATIC_CHECK_FALSE(std::move_constructible<ImmovableRadialSeed>);
|
|
STATIC_CHECK_FALSE(solver::StellarEquilibriumInitialStateFor<ImmovableRadialSeed, Model>);
|
|
STATIC_CHECK_FALSE(HighLevelSeedAccepted<Model, Discretization, Prescription, FlexibleBackend, Projected>);
|
|
STATIC_CHECK(ExplicitSeedOverloadsAccepted<Model, Discretization, Prescription, FlexibleBackend, seed::LaneEmden>);
|
|
STATIC_CHECK_FALSE(
|
|
HighLevelSeedAccepted<Model, Discretization, Prescription, FlexibleBackend, ImmovableRadialSeed>
|
|
);
|
|
STATIC_CHECK(DefaultSeedWithRotationAccepted<Model, Discretization, Prescription, FlexibleBackend>);
|
|
using GeneratedRotationModel = std::remove_cvref_t<decltype(makeFixedCentralDensityAngularMomentumModel())>;
|
|
STATIC_CHECK_FALSE(
|
|
DefaultSeedWithRotationAccepted<GeneratedRotationModel, Discretization, Prescription, FlexibleBackend>
|
|
);
|
|
}
|
|
|
|
TEST_CASE(
|
|
"Result Views Are Read-Only And Capture Targets Are Opaque Owning Values",
|
|
"[solver][architecture][ownership][view][capture]"
|
|
) {
|
|
using namespace mean_field;
|
|
using namespace solver_architecture_test;
|
|
|
|
using Model = std::remove_cvref_t<decltype(makeFixedCentralDensityModel())>;
|
|
using Discretization = equilibrium::StellarDiscretization;
|
|
using Problem = equilibrium::StellarEquilibriumProblem<Model, Discretization>;
|
|
using Structure = equilibrium::StellarStructure<Problem>;
|
|
using Checkpoint = equilibrium::StellarCheckpoint<Problem>;
|
|
using StructureView = equilibrium::StellarStructureView<Problem>;
|
|
using CheckpointView = equilibrium::StellarCheckpointView<Problem>;
|
|
using Prescription = preconditioning::DefaultStellarPreconditioner;
|
|
|
|
STATIC_CHECK(std::move_constructible<Structure>);
|
|
STATIC_CHECK(std::is_nothrow_move_constructible_v<Structure>);
|
|
STATIC_CHECK_FALSE(std::copy_constructible<Structure>);
|
|
STATIC_CHECK_FALSE(std::is_move_assignable_v<Structure>);
|
|
STATIC_CHECK_FALSE(std::default_initializable<Structure>);
|
|
STATIC_CHECK_FALSE(std::default_initializable<Checkpoint>);
|
|
STATIC_CHECK(std::copy_constructible<StructureView>);
|
|
STATIC_CHECK(std::copy_constructible<CheckpointView>);
|
|
STATIC_CHECK(std::same_as<decltype(std::declval<const StructureView &>().state()), std::span<const mfem::real_t>>);
|
|
STATIC_CHECK(
|
|
std::same_as<
|
|
decltype(std::declval<const StructureView &>().stateDescriptors()),
|
|
std::span<const operators::RootBlockDescriptor>>
|
|
);
|
|
STATIC_CHECK(std::same_as<decltype(std::declval<const StructureView &>().model()), const Model &>);
|
|
STATIC_CHECK(std::same_as<decltype(std::declval<const StructureView &>().communicator()), MPI_Comm>);
|
|
STATIC_CHECK_FALSE(ReadsCommunicatorFromRvalue<StructureView>);
|
|
STATIC_CHECK(std::same_as<decltype(std::declval<const StructureView &>().valid()), bool>);
|
|
STATIC_CHECK(
|
|
std::same_as<
|
|
decltype(std::declval<const StructureView &>().prescribedRotation()), std::optional<physics::RigidRotation>>
|
|
);
|
|
STATIC_CHECK(std::same_as<decltype(std::declval<const StructureView &>().rotation()), physics::RigidRotation>);
|
|
STATIC_CHECK(std::same_as<decltype(std::declval<const StructureView &>().capture()), Structure>);
|
|
STATIC_CHECK(std::same_as<decltype(std::declval<const CheckpointView &>().capture()), Checkpoint>);
|
|
STATIC_CHECK_FALSE(ExposesStructureManifest<StructureView>);
|
|
STATIC_CHECK_FALSE(ExposesMutableMFEMState<StructureView>);
|
|
STATIC_CHECK_FALSE(ExposesStructureStateView<StructureView>);
|
|
STATIC_CHECK_FALSE(ExposesFiniteElementModel<StructureView>);
|
|
STATIC_CHECK_FALSE(ExposesOwnedProblem<StructureView>);
|
|
STATIC_CHECK_FALSE(RestartableFromRvalue<Structure, Prescription, FlexibleBackend>);
|
|
STATIC_CHECK_FALSE(RestartableFromLvalue<Structure, Prescription, FlexibleBackend>);
|
|
}
|
|
|
|
TEST_CASE(
|
|
"Projection Options Reach Seed Projection Before Runtime Preparation",
|
|
"[solver][architecture][seed][options]"
|
|
) {
|
|
using namespace mean_field;
|
|
using namespace solver_architecture_test;
|
|
|
|
auto finiteElements = makeFiniteElements();
|
|
auto probe = std::make_shared<LifetimeProbe>();
|
|
seed::StellarEquilibriumProjectionOptions options;
|
|
options.surfaceRadiusRelativeTolerance = std::numeric_limits<double>::quiet_NaN();
|
|
|
|
auto assemble = [&] {
|
|
return solver::makeContext(
|
|
makeFixedCentralDensityModel(), equilibrium::StellarDiscretization{std::move(finiteElements)},
|
|
preconditioning::makePreconditioner(), FlexibleBackend{probe}, options
|
|
);
|
|
};
|
|
REQUIRE_THROWS_AS(assemble(), std::invalid_argument);
|
|
CHECK(probe->operatorIdentity == nullptr);
|
|
CHECK(probe->preconditionerIdentity == nullptr);
|
|
}
|
|
|
|
TEST_CASE(
|
|
"Every Nonlinear Failure Tag Is Preserved By The Evaluation Report",
|
|
"[solver][architecture][result]"
|
|
) {
|
|
using namespace mean_field;
|
|
|
|
CHECK(solver::StellarEquilibriumFailureReport{}.reason == solver::StellarEquilibriumFailureReason::unspecified);
|
|
const std::array reasons{
|
|
solver::StellarEquilibriumFailureReason::inadmissible_state,
|
|
solver::StellarEquilibriumFailureReason::non_finite_state,
|
|
solver::StellarEquilibriumFailureReason::non_finite_residual,
|
|
solver::StellarEquilibriumFailureReason::linear_solve_failure,
|
|
solver::StellarEquilibriumFailureReason::globalization_failure,
|
|
solver::StellarEquilibriumFailureReason::stagnation,
|
|
solver::StellarEquilibriumFailureReason::iteration_limit
|
|
};
|
|
|
|
for (const auto reason : reasons) {
|
|
const solver::StellarEquilibriumFailureReport report{
|
|
.reason = reason,
|
|
.message = "retained",
|
|
.completedNonlinearIterations = 3,
|
|
.initialResidualNorm = 4.0,
|
|
.finalResidualNorm = 2.0
|
|
};
|
|
CHECK(report.reason == reason);
|
|
CHECK(report.message == "retained");
|
|
CHECK(report.completedNonlinearIterations == 3);
|
|
CHECK(report.initialResidualNorm == 4.0);
|
|
CHECK(report.finalResidualNorm == 2.0);
|
|
}
|
|
}
|